<commit_before>"""Data model underlying access control mechanism.

Stores information about locations, users and permission. Provides
methods that map to REST operations that can be perfomed on users,
locations and permissions resources. Allows to retrieve externally
visible attributes of these resources, the attributes are returned as
a resource representation by REST methods.

Makes sure entered emails and paths are valid. Allows to determine if
a user can access a given path.
"""

from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.forms import ValidationError
from wwwhisper_auth import  url_path

import re
import uuid

SITE_URL = getattr(settings, 'SITE_URL', None)
if SITE_URL is None:
    raise ImproperlyConfigured(
        'WWWhisper requires SITE_URL to be set in django settings.py file');

class CreationException(Exception):
    """Raised when creation of a new location or user failed."""
    pass;

class ValidatedModel(models.Model):
    """Base class for all model classes.

    Makes sure all constraints are preserved before changed data is
    saved.
    """

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(ValidatedModel, self).save(*args, **kwargs)

    class Meta:
        # Do not create a DB table for ValidatedModel.
        abstract = True

# Because Django authentication mechanism is used, users need to be
# represented by a standard Django User class. But some additions are
# needed:

"""Externaly visible UUID of a user.

Allows to identify a REST resource representing a user. UUID is stored
in the username field when User object is created. Standard primary
key ids are not used for external identification purposes, because
those ids can be reused after object is deleted."""
User.uuid = property(lambda(self): self.username)

"""Returns externaly visible attributes of the user resource."""
User.attributes_dict = lambda(self): \
    _add_common_attributes(self, {'email': self.email})

class Location(ValidatedModel):
    """A location for which access control rules are defined.

    Location is uniquely identified by its canonical path. All access
    control rules defined for a location apply also to sub-paths,
    unless a more specific location exists. In such case the more
    specific location takes precedence over the more generic one.

    For example, if a location with a path /pub is defined and a user
    foo@example.com is granted access to this location, the user can
    access /pub and all sub path of /pub. But if a location with a
    path /pub/beer is added, and the user foo@example.com is not
    granted access to this location, the user won't be able to access
    /pub/beer and all its sub-paths.

    Attributes:
      path: Canonical path of the location.
      uuid: Externally visible UUID of the location, allows to identify a REST
          resource representing the location.
      open_access: If true, access to the location does not require
          authentication.
    """

    path = models.CharField(max_length=2000, null=False, primary_key=True)
    uuid = models.CharField(max_length=36, null=False, db_index=True,
                            editable=False)
    open_access = models.BooleanField(default=False, null=False)

    def grant_open_access(self):
        """Allows access to the location without authentication.

        For authenticated users, access is also always allowed.
        """
        self.open_access = True;
        self.save();

    def revoke_open_access(self):
        """Disallows access to the location without authentication."""
        self.open_access= False
        self.save();

    def can_access(self, user_uuid):
        """Determines if a user can access the location.

        Args:
            user_uuid: string UUID of a user.

        Returns:
            True if the user is granted permission to access the
            location or it the location is open.
        """
        return (self.open_access
                or _find(Permission,
                         user__username=user_uuid,
                         http_location=self.path) is not None)

    def grant_access(self, user_uuid):
        """Allows access to the location by a given user.

        Args:
            user_uuid: string UUID of a user.

        Returns:
            (new Permission object, True) if access to the location was
                sucesfully granted.
            (existing Permission object, False) if user already had
                granted access to the location.

        Raises:
            LookupError: No user with a given UUID.
        """
        user = _find(User, username=user_uuid)
        if user is None:
            raise LookupError('User not found')
        permission = _find(
            Permission, http_location_id=self.path, user_id=user.id)
        created = False
        if permission is None:
            created = True
            permission = Permission.objects.create(
                http_location_id=self.path, user_id=user.id)
            permission.save()
        return (permission, created)

    def revoke_access(self, user_uuid):
        """Disallows access to the location by a given user.

        Args:
            user_uuid: string UUID of a user.

        Raises:
            LookupError: No user with a given UUID or the user can not
                access the location.
        """
        permission = self.get_permission(user_uuid)
        permission.delete()

    def get_permission(self, user_uuid):
        """Gets Permission object for a given user.

        Args:
            user_uuid: string UUID of a user.

        Raises:
            LookupError: No user with a given UUID or the user can not
                access the location.
        """
        user = _find(User, username=user_uuid)
        if user is None:
            raise LookupError('User not found.')
        permission = _find(
            Permission, http_location_id=self.path, user_id=user.id)
        if permission is None:
            raise LookupError('User can not access location.')
        return permission

    def allowed_users(self):
        """"Returns a list of users that can access the location."""
        return [permission.user for permission in
                Permission.objects.filter(http_location=self.path)]

    def attributes_dict(self):
        """Returns externally visible attributes of the location resource."""
        return _add_common_attributes(self, {
                'path': self.path,
                'openAccess': self.open_access,
                'allowedUsers': [
                    user.attributes_dict() for user in self.allowed_users()
                    ],
                })

    @models.permalink
    def get_absolute_url(self):
        """Constructs URL of the location resource."""
        return ('wwwhisper_location', (), {'uuid' : self.uuid})

    def save(self, *args, **kwargs):
        if not self.uuid:
            self.uuid = str(uuid.uuid4())
        return super(Location, self).save(*args, **kwargs)

    def __unicode__(self):
        return "%s" % (self.path)

class Permission(ValidatedModel):
    http_location = models.ForeignKey(Location)
    user = models.ForeignKey(User)

    def attributes_dict(self):
        return _add_common_attributes(
            self, {'user': self.user.attributes_dict()})

    @models.permalink
    def get_absolute_url(self):
        return ('wwwhisper_allowed_user', (),
                {'location_uuid' : self.http_location.uuid,
                 'user_uuid': self.user.uuid})

    def __unicode__(self):
        return "%s, %s" % (self.http_location, self.user.email)

class Collection(object):
    def all(self):
        return self.model_class.objects.all()

    def find_item(self, uuid):
        filter_args = {self.uuid_column_name: uuid}
        return _find(self.model_class, **filter_args)

    def delete_item(self, uuid):
        item = self.find_item(uuid)
        if item is None:
            return False
        item.delete()
        return True

class UsersCollection(Collection):
    collection_name = 'users'
    item_name = 'user'
    model_class = User
    uuid_column_name = 'username'

    def create_item(self, email):
        try:
            encoded_email = _encode_email(email)
        except ValidationError, ex:
            raise CreationException(ex)
        if _find(User, email=encoded_email) is not None:
            raise CreationException('User already exists.')
        user = User.objects.create(
            username=str(uuid.uuid4()), email=encoded_email, is_active=True)
        return user

    def find_item_by_email(self, email):
        try:
            encoded_email = _encode_email(email)
        except ValidationError, ex:
            return None
        return _find(self.model_class, email=encoded_email);

class LocationsCollection(Collection):
    collection_name = 'locations'
    item_name = 'location'
    model_class = Location
    uuid_column_name = 'uuid'

    def create_item(self, path):
        if not url_path.is_canonical(path):
            raise CreationException(
                'Path should be absolute and normalized (starting with / '\
                    'without /../ or /./ or //).')
        if url_path.contains_fragment(path):
            raise CreationException(
                "Path should not contain fragment ('#' part).")
        if url_path.contains_query(path):
            raise CreationException(
                "Path should not contain query ('?' part).")
        if url_path.contains_params(path):
            raise CreationException(
                "Path should not contain parameters (';' part).")
        if _find(Location, path=path) is not None:
            raise CreationException('Location already exists.')
        location = Location.objects.create(path=path)
        location.save()
        return location

    def find_parent(self, normalized_path):
        normalized_path_len = len(normalized_path)
        longest_matched_location = None
        longest_matched_location_len = -1

        for location in Location.objects.all():
            probed_path = location.path
            probed_path_len = len(probed_path)
            trailing_slash_index = None
            if probed_path[probed_path_len - 1] == '/':
                trailing_slash_index = probed_path_len - 1
            else:
                trailing_slash_index = probed_path_len

            if (normalized_path.startswith(probed_path) and
                probed_path_len > longest_matched_location_len and
                (probed_path_len == normalized_path_len or
                 normalized_path[trailing_slash_index] == '/')) :
                longest_matched_location_len = probed_path_len
                longest_matched_location = location
        return longest_matched_location

def full_url(absolute_path):
    return SITE_URL + absolute_path

def _urn_from_uuid(uuid):
    return 'urn:uuid:' + uuid

def _add_common_attributes(item, attributes_dict):
    attributes_dict['self'] = full_url(item.get_absolute_url())
    if hasattr(item, 'uuid'):
        attributes_dict['id'] = _urn_from_uuid(item.uuid)
    return attributes_dict

def _find(model_class, **kwargs):
    item = model_class.objects.filter(**kwargs)
    count = item.count()
    assert count <= 1
    if count == 0:
        return None
    return item.get()

def _encode_email(email):
    encoded_email = email.lower()
    if not _is_email_valid(encoded_email):
        raise ValidationError('Invalid email format.')
    return encoded_email

def _is_email_valid(email):
    """Validates email with regexp defined by BrowserId:
    browserid/browserid/static/dialog/resources/validation.js
    """
    return re.match(
        "^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$",
        email) != None
<commit_msg>Comments added.<commit_after>"""Data model underlying access control mechanism.

Stores information about locations, users and permission. Provides
methods that map to REST operations that can be perfomed on users,
locations and permissions resources. Allows to retrieve externally
visible attributes of these resources, the attributes are returned as
a resource representation by REST methods.

Makes sure entered emails and paths are valid. Allows to determine if
a user can access a given path.
"""

from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.forms import ValidationError
from wwwhisper_auth import  url_path

import re
import uuid

SITE_URL = getattr(settings, 'SITE_URL', None)
if SITE_URL is None:
    raise ImproperlyConfigured(
        'WWWhisper requires SITE_URL to be set in django settings.py file');

class CreationException(Exception):
    """Raised when creation of a new location or user failed."""
    pass;

class ValidatedModel(models.Model):
    """Base class for all model classes.

    Makes sure all constraints are preserved before changed data is
    saved.
    """

    def save(self, *args, **kwargs):
        self.full_clean()
        return super(ValidatedModel, self).save(*args, **kwargs)

    class Meta:
        # Do not create a DB table for ValidatedModel.
        abstract = True

# Because Django authentication mechanism is used, users need to be
# represented by a standard Django User class. But some additions are
# needed:

"""Externaly visible UUID of a user.

Allows to identify a REST resource representing a user. UUID is stored
in the username field when User object is created. Standard primary
key ids are not used for external identification purposes, because
those ids can be reused after object is deleted."""
User.uuid = property(lambda(self): self.username)

"""Returns externaly visible attributes of the user resource."""
User.attributes_dict = lambda(self): \
    _add_common_attributes(self, {'email': self.email})

class Location(ValidatedModel):
    """A location for which access control rules are defined.

    Location is uniquely identified by its canonical path. All access
    control rules defined for a location apply also to sub-paths,
    unless a more specific location exists. In such case the more
    specific location takes precedence over the more generic one.

    For example, if a location with a path /pub is defined and a user
    foo@example.com is granted access to this location, the user can
    access /pub and all sub path of /pub. But if a location with a
    path /pub/beer is added, and the user foo@example.com is not
    granted access to this location, the user won't be able to access
    /pub/beer and all its sub-paths.

    Attributes:
      path: Canonical path of the location.
      uuid: Externally visible UUID of the location, allows to identify a REST
          resource representing the location.
      open_access: If true, access to the location does not require
          authentication.
    """

    path = models.CharField(max_length=2000, null=False, primary_key=True)
    uuid = models.CharField(max_length=36, null=False, db_index=True,
                            editable=False)
    open_access = models.BooleanField(default=False, null=False)

    def grant_open_access(self):
        """Allows access to the location without authentication.

        For authenticated users, access is also always allowed.
        """
        self.open_access = True;
        self.save();

    def revoke_open_access(self):
        """Disallows access to the location without authentication."""
        self.open_access= False
        self.save();

    def can_access(self, user_uuid):
        """Determines if a user can access the location.

        Args:
            user_uuid: string UUID of a user.

        Returns:
            True if the user is granted permission to access the
            location or it the location is open.
        """
        return (self.open_access
                or _find(Permission,
                         user__username=user_uuid,
                         http_location=self.path) is not None)

    def grant_access(self, user_uuid):
        """Grants access to the location to a given user.

        Args:
            user_uuid: string UUID of a user.

        Returns:
            (new Permission object, True) if access to the location was
                sucesfully granted.
            (existing Permission object, False) if user already had
                granted access to the location.

        Raises:
            LookupError: No user with a given UUID.
        """
        user = _find(User, username=user_uuid)
        if user is None:
            raise LookupError('User not found')
        permission = _find(
            Permission, http_location_id=self.path, user_id=user.id)
        created = False
        if permission is None:
            created = True
            permission = Permission.objects.create(
                http_location_id=self.path, user_id=user.id)
            permission.save()
        return (permission, created)

    def revoke_access(self, user_uuid):
        """Revokes access to the location from a given user.

        Args:
            user_uuid: string UUID of a user.

        Raises:
            LookupError: No user with a given UUID or the user can not
                access the location.
        """
        permission = self.get_permission(user_uuid)
        permission.delete()

    def get_permission(self, user_uuid):
        """Gets Permission object for a given user.

        Args:
            user_uuid: string UUID of a user.

        Raises:
            LookupError: No user with a given UUID or the user can not
                access the location.
        """
        user = _find(User, username=user_uuid)
        if user is None:
            raise LookupError('User not found.')
        permission = _find(
            Permission, http_location_id=self.path, user_id=user.id)
        if permission is None:
            raise LookupError('User can not access location.')
        return permission

    def allowed_users(self):
        """"Returns a list of users that can access the location."""
        return [permission.user for permission in
                Permission.objects.filter(http_location=self.path)]

    def attributes_dict(self):
        """Returns externally visible attributes of the location resource."""
        return _add_common_attributes(self, {
                'path': self.path,
                'openAccess': self.open_access,
                'allowedUsers': [
                    user.attributes_dict() for user in self.allowed_users()
                    ],
                })

    @models.permalink
    def get_absolute_url(self):
        """Constructs URL of the location resource."""
        return ('wwwhisper_location', (), {'uuid' : self.uuid})

    def save(self, *args, **kwargs):
        if not self.uuid:
            self.uuid = str(uuid.uuid4())
        return super(Location, self).save(*args, **kwargs)

    def __unicode__(self):
        return "%s" % (self.path)

class Permission(ValidatedModel):
    """Connects a location with a user that can access the location.

    Attributes:
        http_location: The location to which the Permission object gives access.
        user: The user that is given acces to the location.
    """

    http_location = models.ForeignKey(Location)
    user = models.ForeignKey(User)

    def attributes_dict(self):
        """Returns externally visible attributes of the permission resource."""
        return _add_common_attributes(
            self, {'user': self.user.attributes_dict()})

    @models.permalink
    def get_absolute_url(self):
        """Constructs URL of the permission resource."""
        return ('wwwhisper_allowed_user', (),
                {'location_uuid' : self.http_location.uuid,
                 'user_uuid': self.user.uuid})

    def __unicode__(self):
        return "%s, %s" % (self.http_location, self.user.email)

class Collection(object):
    """A common base class for managing a collection of resources.

    Resources in the collection are of the same type and need to be
    identified by an UUID.

    Attributes (Need to be defined in subclasses):
        item_name: Name of a resource stored in the collection.
        model_class: Class that manages storage of the resource.
        uuid_column_name: Name of a column in model class that stores
            a resource uuid.
    """

    def all(self):
        """Returns all items in the collection."""
        return self.model_class.objects.all()

    def find_item(self, uuid):
        """Finds an item with a given UUID.

        Returns:
           The item or None if not found.
        """
        filter_args = {self.uuid_column_name: uuid}
        return _find(self.model_class, **filter_args)

    def delete_item(self, uuid):
        """Deletes an item with a given UUID.

        Returns:
           True if the item existed and was deleted, False if not found.
        """
        item = self.find_item(uuid)
        if item is None:
            return False
        item.delete()
        return True

    @property
    def collection_name(self):
        return self.item_name + 's'

class UsersCollection(Collection):
    """Collection of users resources."""

    item_name = 'user'
    model_class = User
    uuid_column_name = 'username'

    def create_item(self, email):
        """Creates a new User object.

        Args:
            email: an email of the created user.

        Raises:
            CreationException if the email is not valid or if a user
            with such email already exists.
        """
        try:
            encoded_email = _encode_email(email)
        except ValidationError, ex:
            raise CreationException(ex)
        if _find(User, email=encoded_email) is not None:
            raise CreationException('User already exists.')
        user = User.objects.create(
            username=str(uuid.uuid4()), email=encoded_email, is_active=True)
        return user

    def find_item_by_email(self, email):
        """Finds user with a given email.

        Returns:
            User object or None if not found.
        """
        try:
            encoded_email = _encode_email(email)
        except ValidationError, ex:
            return None
        return _find(self.model_class, email=encoded_email);

class LocationsCollection(Collection):
    item_name = 'location'
    model_class = Location
    uuid_column_name = 'uuid'

    def create_item(self, path):
        if not url_path.is_canonical(path):
            raise CreationException(
                'Path should be absolute and normalized (starting with / '\
                    'without /../ or /./ or //).')
        if url_path.contains_fragment(path):
            raise CreationException(
                "Path should not contain fragment ('#' part).")
        if url_path.contains_query(path):
            raise CreationException(
                "Path should not contain query ('?' part).")
        if url_path.contains_params(path):
            raise CreationException(
                "Path should not contain parameters (';' part).")
        if _find(Location, path=path) is not None:
            raise CreationException('Location already exists.')
        location = Location.objects.create(path=path)
        location.save()
        return location

    def find_parent(self, normalized_path):
        normalized_path_len = len(normalized_path)
        longest_matched_location = None
        longest_matched_location_len = -1

        for location in Location.objects.all():
            probed_path = location.path
            probed_path_len = len(probed_path)
            trailing_slash_index = None
            if probed_path[probed_path_len - 1] == '/':
                trailing_slash_index = probed_path_len - 1
            else:
                trailing_slash_index = probed_path_len

            if (normalized_path.startswith(probed_path) and
                probed_path_len > longest_matched_location_len and
                (probed_path_len == normalized_path_len or
                 normalized_path[trailing_slash_index] == '/')) :
                longest_matched_location_len = probed_path_len
                longest_matched_location = location
        return longest_matched_location

def full_url(absolute_path):
    return SITE_URL + absolute_path

def _urn_from_uuid(uuid):
    return 'urn:uuid:' + uuid

def _add_common_attributes(item, attributes_dict):
    attributes_dict['self'] = full_url(item.get_absolute_url())
    if hasattr(item, 'uuid'):
        attributes_dict['id'] = _urn_from_uuid(item.uuid)
    return attributes_dict

def _find(model_class, **kwargs):
    item = model_class.objects.filter(**kwargs)
    count = item.count()
    assert count <= 1
    if count == 0:
        return None
    return item.get()

def _encode_email(email):
    encoded_email = email.lower()
    if not _is_email_valid(encoded_email):
        raise ValidationError('Invalid email format.')
    return encoded_email

def _is_email_valid(email):
    """Validates email with regexp defined by BrowserId:
    browserid/browserid/static/dialog/resources/validation.js
    """
    return re.match(
        "^[\w.!#$%&'*+\-/=?\^`{|}~]+@[a-z0-9-]+(\.[a-z0-9-]+)+$",
        email) != None
<|endoftext|>


<commit_before>from __future__ import unicode_literals

import ast
import json

from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import smart_text
from django.utils.six import string_types
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

class ListField(with_metaclass(models.SubfieldBase, models.CharField)):
    """Model field for python lists.

    Note: this extends from CharField so all restrictions apply to this model
    field that apply to CharField (i.e. max_length)
    """
    description = "Stores a python list"
    form_class = None
    choices_form_class = forms.TypedMultipleChoiceField

    default_error_messages = {
        'invalid_list': _("'%(value)s' value must be a list type."),
        'invalid_choice': _("'%(value)s' is not a valid choice.")
    }

    def __init__(self, max_length=2000, form_class=None,
                 choices_form_class=None, *args, **kwargs):
        """
        :param max_length: this is the total length the string could be for all
            characters in the string. So, a field value of:

            ['hello', 'world'] == length of 18
        :param form_class: the form field class to user for this field.
        """
        if form_class:
            self.default_field_class = form_class

        if choices_form_class:
            self.choices_form_class = choices_form_class

        super(ListField, self).__init__(max_length=max_length, *args, **kwargs)

    def to_python(self, value):
        if value == None:
            return value

        if not value:
            return []

        if isinstance(value, list):
            if len(value) == 0 or not self.choices:
                return value

            # Validate choices
            valid_choices = tuple(choice[0] for choice in self.choices)

            for item in value:
                if item not in valid_choices:
                    raise ValidationError(
                        self.error_messages['invalid_choice'],
                        code='invalid_choice',
                        params={'value': item}
                    )

            return value

        try:
            return ast.literal_eval(value)
        except ValueError as e:
            raise ValidationError(
                self.error_messages['invalid_list'],
                code='invalid_list',
                params={'value': value}
            )

    def get_prep_value(self, value):
        if value is None:
            return value

        if not isinstance(value, list):
            raise ValidationError(
                self.error_messages['invalid_list'],
                code='invalid_list',
                params={'value': value}
            )

        return smart_text(value)

    def formfield(self, form_class=None, choices_form_class=None, **kwargs):
        """Make the default formfield a CommaSeparatedListField."""
        defaults = {
            'form_class': form_class or self.get_form_class(),
            'choices_form_class': choices_form_class or self.get_choices_form_class()
        }
        defaults.update(kwargs)

        return super(ListField, self).formfield(**defaults)

    def get_form_class(self):
        """Gets the form field class to user for the field."""
        return self.form_class

    def get_choices_form_class(self):
        return self.choices_form_class

    def validate(self, value, model_instance, **kwargs):
        """This follows the validate rules for choices_form_class field used.
        """
        self.get_choices_form_class().validate(value, model_instance, **kwargs)

class IntegerListField(with_metaclass(models.SubfieldBase, ListField)):
    """Wrapper around ListField ensuring all values are integers."""

    default_error_messages = {
        'invalid_integers': _('All values in list "%(value)s" must be of '
                              'integer types.'),
        'invalid_out_of_range_min': _('"%(value)s" out of range. Must be '
                                      'greater than or equal to %(min)s'),
        'invalid_out_of_range_max': _('"%(value)s" out of range. Must be '
                                      'less than or equal to %(max)s')
    }

    def __init__(self, min_value=None, max_value=None, *args, **kwargs):
        """
        :param min_value: the minimum value that can be in the list
        :param max_value: the maximum value that can be in the list
        """
        self.min_value = min_value
        self.max_value = max_value
        super(IntegerListField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        val = super(IntegerListField, self).to_python(value)

        if isinstance(val, list):

            try:
                val = [int(item) for item in val]
            except:
                raise ValidationError(
                    self.error_messages['invalid_integers'],
                    code='invalid_integers',
                    params={'value': val}
                )

            # Validate that all items in the list are integers.
            for item in val:

                if self.min_value != None and item < self.min_value:
                    raise ValidationError(
                        self.error_messages['invalid_out_of_range_min'],
                        code='invalid_out_of_range_min',
                        params={'value': item, 'min': self.min_value}
                    )

                if self.max_value != None and item > self.max_value:
                    raise ValidationError(
                        self.error_messages['invalid_out_of_range_max'],
                        code='invalid_out_of_range_max',
                        params={'value': item, 'max': self.max_value}
                    )

        return val

    def get_prep_value(self, value):

        prepped_value = super(IntegerListField, self).get_prep_value(value)

        if value:
            # Remove all spaces from the stored string since all items will be
            # integers anyway and spaces aren't needed.
            return prepped_value.replace(' ', '')

        return prepped_value

class JSONField(with_metaclass(models.SubfieldBase, models.TextField)):
    """Simple JSON field that stores python structures as JSON strings
    on database.

    Borrowed from django-social-auth :):

    https://github.com/omab/django-social-auth/blob/master/social_auth/fields.py

    """

    def to_python(self, value):
        """
        Convert the input JSON value into python structures, raises
        django.core.exceptions.ValidationError if the data can't be converted.
        """
        if isinstance(value, dict):
            return value

        if self.blank and not value:
            return None

        if isinstance(value, string_types):
            try:
                return json.loads(value)
            except Exception as e:
                raise ValidationError(str(e))

        return value

    def validate(self, value, model_instance):
        """Check value is a valid JSON string, raise ValidationError on
        error."""
        if isinstance(value, string_types):
            super(JSONField, self).validate(value, model_instance)
            try:
                json.loads(value)
            except Exception as e:
                raise ValidationError(str(e))

    def get_prep_value(self, value):
        """Convert value to JSON string before save"""
        try:
            return json.dumps(value)
        except Exception as e:
            raise ValidationError(str(e))

    def value_to_string(self, obj):
        """Return value from object converted to string properly"""
        return smart_text(self.get_prep_value(self._get_val_from_obj(obj)))
<commit_msg>fix for is not None check<commit_after>from __future__ import unicode_literals

import ast
import json

from django import forms
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import smart_text
from django.utils.six import string_types
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

class ListField(with_metaclass(models.SubfieldBase, models.CharField)):
    """Model field for python lists.

    Note: this extends from CharField so all restrictions apply to this model
    field that apply to CharField (i.e. max_length)
    """
    description = "Stores a python list"
    form_class = None
    choices_form_class = forms.TypedMultipleChoiceField

    default_error_messages = {
        'invalid_list': _("'%(value)s' value must be a list type."),
        'invalid_choice': _("'%(value)s' is not a valid choice.")
    }

    def __init__(self, max_length=2000, form_class=None,
                 choices_form_class=None, *args, **kwargs):
        """
        :param max_length: this is the total length the string could be for all
            characters in the string. So, a field value of:

            ['hello', 'world'] == length of 18
        :param form_class: the form field class to user for this field.
        """
        if form_class:
            self.default_field_class = form_class

        if choices_form_class:
            self.choices_form_class = choices_form_class

        super(ListField, self).__init__(max_length=max_length, *args, **kwargs)

    def to_python(self, value):
        if value == None:
            return value

        if not value:
            return []

        if isinstance(value, list):
            if len(value) == 0 or not self.choices:
                return value

            # Validate choices
            valid_choices = tuple(choice[0] for choice in self.choices)

            for item in value:
                if item not in valid_choices:
                    raise ValidationError(
                        self.error_messages['invalid_choice'],
                        code='invalid_choice',
                        params={'value': item}
                    )

            return value

        try:
            return ast.literal_eval(value)
        except ValueError as e:
            raise ValidationError(
                self.error_messages['invalid_list'],
                code='invalid_list',
                params={'value': value}
            )

    def get_prep_value(self, value):
        if value is None:
            return value

        if not isinstance(value, list):
            raise ValidationError(
                self.error_messages['invalid_list'],
                code='invalid_list',
                params={'value': value}
            )

        return smart_text(value)

    def formfield(self, form_class=None, choices_form_class=None, **kwargs):
        """Make the default formfield a CommaSeparatedListField."""
        defaults = {
            'form_class': form_class or self.get_form_class(),
            'choices_form_class': choices_form_class or self.get_choices_form_class()
        }
        defaults.update(kwargs)

        return super(ListField, self).formfield(**defaults)

    def get_form_class(self):
        """Gets the form field class to user for the field."""
        return self.form_class

    def get_choices_form_class(self):
        return self.choices_form_class

    def validate(self, value, model_instance, **kwargs):
        """This follows the validate rules for choices_form_class field used.
        """
        self.get_choices_form_class().validate(value, model_instance, **kwargs)

class IntegerListField(with_metaclass(models.SubfieldBase, ListField)):
    """Wrapper around ListField ensuring all values are integers."""

    default_error_messages = {
        'invalid_integers': _('All values in list "%(value)s" must be of '
                              'integer types.'),
        'invalid_out_of_range_min': _('"%(value)s" out of range. Must be '
                                      'greater than or equal to %(min)s'),
        'invalid_out_of_range_max': _('"%(value)s" out of range. Must be '
                                      'less than or equal to %(max)s')
    }

    def __init__(self, min_value=None, max_value=None, *args, **kwargs):
        """
        :param min_value: the minimum value that can be in the list
        :param max_value: the maximum value that can be in the list
        """
        self.min_value = min_value
        self.max_value = max_value
        super(IntegerListField, self).__init__(*args, **kwargs)

    def to_python(self, value):
        val = super(IntegerListField, self).to_python(value)

        if isinstance(val, list):

            try:
                val = [int(item) for item in val]
            except:
                raise ValidationError(
                    self.error_messages['invalid_integers'],
                    code='invalid_integers',
                    params={'value': val}
                )

            # Validate that all items in the list are integers.
            for item in val:

                if self.min_value is not None and item < self.min_value:
                    raise ValidationError(
                        self.error_messages['invalid_out_of_range_min'],
                        code='invalid_out_of_range_min',
                        params={'value': item, 'min': self.min_value}
                    )

                if self.max_value is not None and item > self.max_value:
                    raise ValidationError(
                        self.error_messages['invalid_out_of_range_max'],
                        code='invalid_out_of_range_max',
                        params={'value': item, 'max': self.max_value}
                    )

        return val

    def get_prep_value(self, value):

        prepped_value = super(IntegerListField, self).get_prep_value(value)

        if value:
            # Remove all spaces from the stored string since all items will be
            # integers anyway and spaces aren't needed.
            return prepped_value.replace(' ', '')

        return prepped_value

class JSONField(with_metaclass(models.SubfieldBase, models.TextField)):
    """Simple JSON field that stores python structures as JSON strings
    on database.

    Borrowed from django-social-auth :):

    https://github.com/omab/django-social-auth/blob/master/social_auth/fields.py

    """

    def to_python(self, value):
        """
        Convert the input JSON value into python structures, raises
        django.core.exceptions.ValidationError if the data can't be converted.
        """
        if isinstance(value, dict):
            return value

        if self.blank and not value:
            return None

        if isinstance(value, string_types):
            try:
                return json.loads(value)
            except Exception as e:
                raise ValidationError(str(e))

        return value

    def validate(self, value, model_instance):
        """Check value is a valid JSON string, raise ValidationError on
        error."""
        if isinstance(value, string_types):
            super(JSONField, self).validate(value, model_instance)
            try:
                json.loads(value)
            except Exception as e:
                raise ValidationError(str(e))

    def get_prep_value(self, value):
        """Convert value to JSON string before save"""
        try:
            return json.dumps(value)
        except Exception as e:
            raise ValidationError(str(e))

    def value_to_string(self, obj):
        """Return value from object converted to string properly"""
        return smart_text(self.get_prep_value(self._get_val_from_obj(obj)))
<|endoftext|>


<commit_before>"""Utilities for building mock database models and fields."""

from __future__ import unicode_literals

from django.db import models
from django.db.models.base import ModelState
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
from django.utils import six
from django.utils.encoding import force_bytes
from django.utils.functional import curry

from django_evolution.compat.datastructures import OrderedDict
from django_evolution.compat.models import (get_remote_field,
                                            get_remote_field_model)

def create_field(proj_sig, field_name, field_type, field_attrs, parent_model):
    """Create a Django field instance for the given signature data.

    This creates a field in a way that's compatible with a variety of versions
    of Django. It takes in data such as the field's name and attributes
    and creates an instance that can be used like any field found on a model.

    Args:
        field_name (unicode):
            The name of the field.

        field_type (cls):
            The class for the type of field being constructed. This must be a
            subclass of :py:class:`django.db.models.Field`.

        field_attrs (dict):
            Attributes to set on the field.

        parent_model (cls):
            The parent model that would own this field. This must be a
            subclass of :py:class:`django.db.models.Model`.

    Returns:
        django.db.models.Field:
        A new field instance matching the provided data.
    """
    # Convert to the standard string format for each version of Python, to
    # simulate what the format would be for the default name.
    field_name = str(field_name)

    # related_model isn't a valid field attribute, so it must be removed
    # prior to instantiating the field, but it must be restored
    # to keep the signature consistent.
    related_model = field_attrs.pop('related_model', None)

    if related_model:
        related_app_name, related_model_name = related_model.split('.')
        related_model_sig = proj_sig[related_app_name][related_model_name]
        to = MockModel(proj_sig, related_app_name, related_model_name,
                       related_model_sig, stub=True)

        if (issubclass(field_type, models.ForeignKey) and
            hasattr(models, 'CASCADE') and
            'on_delete' not in field_attrs):
            # Starting in Django 2.0, on_delete is a requirement for
            # ForeignKeys. If not provided in the signature, we want to
            # default this to CASCADE, which is the value that Django
            # previously defaulted to.
            field_attrs['on_delete'] = models.CASCADE

        field = field_type(to, name=field_name, **field_attrs)
        field_attrs['related_model'] = related_model
    else:
        field = field_type(name=field_name, **field_attrs)

    if field_type is models.ManyToManyField and parent_model is not None:
        # Starting in Django 1.2, a ManyToManyField must have a through
        # model defined. This will be set internally to an auto-created
        # model if one isn't specified. We have to fake that model.
        through_model = field_attrs.get('through_model', None)
        through_model_sig = None

        if through_model:
            through_app_name, through_model_name = through_model.split('.')
            through_model_sig = proj_sig[through_app_name][through_model_name]
        elif hasattr(field, '_get_m2m_attr'):
            # Django >= 1.2
            remote_field = get_remote_field(field)
            remote_field_model = get_remote_field_model(remote_field)

            to = remote_field_model._meta.object_name.lower()

            if (remote_field_model == RECURSIVE_RELATIONSHIP_CONSTANT or
                to == parent_model._meta.object_name.lower()):
                from_ = 'from_%s' % to
                to = 'to_%s' % to
            else:
                from_ = parent_model._meta.object_name.lower()

            # This corresponds to the signature in
            # related.create_many_to_many_intermediary_model
            through_app_name = parent_model.app_name
            through_model_name = '%s_%s' % (parent_model._meta.object_name,
                                            field.name)
            through_model = '%s.%s' % (through_app_name, through_model_name)

            fields = OrderedDict()
            fields['id'] = {
                'field_type': models.AutoField,
                'primary_key': True,
            }

            fields[from_] = {
                'field_type': models.ForeignKey,
                'related_model': '%s.%s' % (parent_model.app_name,
                                            parent_model._meta.object_name),
                'related_name': '%s+' % through_model_name,
            }

            fields[to] = {
                'field_type': models.ForeignKey,
                'related_model': related_model,
                'related_name': '%s+' % through_model_name,
            }

            through_model_sig = {
                'meta': {
                    'db_table': field._get_m2m_db_table(parent_model._meta),
                    'managed': True,
                    'auto_created': True,
                    'app_label': through_app_name,
                    'unique_together': ((from_, to),),
                    'pk_column': 'id',
                },
                'fields': fields,
            }

            field.auto_created = True

        if through_model_sig:
            through = MockModel(proj_sig, through_app_name, through_model_name,
                                through_model_sig)
            get_remote_field(field).through = through

        field.m2m_db_table = curry(field._get_m2m_db_table, parent_model._meta)
        field.set_attributes_from_rel()

    field.set_attributes_from_name(field_name)

    # Needed in Django >= 1.7, for index building.
    field.model = parent_model

    return field

class MockMeta(object):
    """A mock of a models Options object, based on the model signature.

    This emulates the standard Meta class for a model, storing data and
    providing mock functions for setting up fields from a signature.
    """

    def __init__(self, proj_sig, app_name, model_name, model_sig):
        """Initialize the meta instance.

        Args:
            proj_sig (dict):
                The project's schema signature.

            app_name (unicode):
                The name of the Django application owning the model.

            model_name (unicode):
                The name of the model.

            model_sig (dict):
                The model's schema signature.
        """
        self.object_name = model_name
        self.app_label = app_name
        self.meta = {
            'order_with_respect_to': None,
            'has_auto_field': None,
            'db_tablespace': None,
            'swapped': False,
            'index_together': [],
            'indexes': [],
        }
        self.meta.update(model_sig['meta'])
        self._fields = OrderedDict()
        self._many_to_many = OrderedDict()
        self.abstract = False
        self.managed = True
        self.proxy = False
        self._model_sig = model_sig
        self._proj_sig = proj_sig

    @property
    def local_fields(self):
        """A list of all local fields on the model."""
        return list(six.itervalues(self._fields))

    fields = local_fields

    @property
    def local_many_to_many(self):
        """A list of all local Many-to-Many fields on the model."""
        return list(six.itervalues(self._many_to_many))

    def setup_fields(self, model, stub=False):
        """Set up the fields listed in the model's signature.

        For each field in the model signature's list of fields, a field
        instance will be created and stored in :py:attr:`_fields` or
        :py:attr:`_many_to_many` (depending on the type of field).

        Some fields (for instance, a field representing a primary key) may
        also influence the attributes on this model.

        Args:
            model (cls):
                The model class owning this meta instance. This must be a
                subclass of :py:class:`django.db.models.Model`.

            stub (bool, optional):
                If provided, only a primary key will be set up. This is used
                internally when creating relationships between models and
                fields in order to prevent recursive relationships.
        """
        for field_name, field_sig in six.iteritems(self._model_sig['fields']):
            primary_key = field_sig.get('primary_key', False)

            if not stub or primary_key:
                field_type = field_sig.pop('field_type')
                field = create_field(self._proj_sig, field_name, field_type,
                                     field_sig, model)
                field_sig['field_type'] = field_type

                if type(field) is models.AutoField:
                    self.meta['has_auto_field'] = True
                    self.meta['auto_field'] = field

                if type(field) is models.ManyToManyField:
                    self._many_to_many[field.name] = field
                else:
                    self._fields[field.name] = field

                field.set_attributes_from_name(field_name)

                if primary_key:
                    self.pk = field

    def __getattr__(self, name):
        """Return an attribute from the meta class.

        This will look up the attribute from the correct location, depending
        on the attribute being accessed.

        Args:
            name (unicode):
                The attribute name.

        Returns:
            object:
            The attribute value.
        """
        if name == 'model_name':
            return self.object_name

        return self.meta[name]

    def get_field(self, name):
        """Return a field with the given name.

        Args:
            name (unicode):
                The name of the field.

        Returns:
            django.db.models.Field:
            The field with the given name.

        Raises:
            django.db.models.fields.FieldDoesNotExist:
                The field could not be found.
        """
        try:
            return self._fields[name]
        except KeyError:
            try:
                return self._many_to_many[name]
            except KeyError:
                raise FieldDoesNotExist('%s has no field named %r' %
                                        (self.object_name, name))

    def get_field_by_name(self, name):
        """Return information on a field with the given name.

        This is a stub that provides only basic functionality. It will
        return information for a field with the given name, with most
        data hard-coded.

        Args:
            name (unicode):
                The name of the field.

        Returns:
            tuple:
            A tuple of information for the following:

            * The field instance (:py:class:`django.db.models.Field`)
            * The model (hard-coded as ``None``)
            * Whether this field is owned by this model (hard-coded as
              ``True``)
            * Whether this is for a many-to-many relationship (hard-coded as
             ``None``)

        Raises:
            django.db.models.fields.FieldDoesNotExist:
                The field could not be found.
        """
        return (self.get_field(name), None, True, None)

class MockModel(object):
    """A mock model.

    This replicates some of the state and functionality of a model for
    use when generating, reading, or mutating signatures.
    """

    def __init__(self, proj_sig, app_name, model_name, model_sig, stub=False,
                 db_name=None):
        """Initialize the model.

        Args:
            proj_sig (dict):
                The project's schema signature.

            app_name (unicode):
                The name of the Django app that owns the model.

            model_name (unicode):
                The name of the model.

            model_sig (dict):
                The model's schema signature.

            stub (bool, optional):
                Whether this is a stub model. This is used internally to
                create models that only contain a primary key field and no
                others, for use when dealing with circular relationships.

            db_name (unicode, optional):
                The name of the database where the model would be read from or
                written to.
        """
        self.app_name = app_name
        self.model_name = model_name
        self._meta = MockMeta(proj_sig, app_name, model_name, model_sig)
        self._meta.setup_fields(self, stub)
        self._state = ModelState(db_name)

    def __repr__(self):
        """Return a string representation of the model.

        Returns:
            unicode:
            A string representation of the model.
        """
        return '<MockModel for %s.%s>' % (self.app_name, self.model_name)

    def __hash__(self):
        """Return a hash of the model instance.

        This is used to allow the model instance to be used as a key in a
        dictionary.

        Django would return a hash of the primary key's value, but that's not
        necessary for our needs, and we don't have field values in most mock
        models.

        Returns:
            int:
            The hash of the model.
        """
        return hash(id(self))

    def __eq__(self, other):
        """Return whether two mock models are equal.

        Both are considered equal if they're both mock models with the same
        app name and model name.

        Args:
            other (MockModel):
                The other mock model to compare to.

        Returns:
            bool:
            ``True`` if both are equal. ``False` if they are not.
        """
        # For our purposes, we don't want to appear equal to "self".
        # Really, Django 1.2 should be checking if this is a string before
        # doing this comparison,
        return (isinstance(other, MockModel) and
                self.app_name == other.app_name and
                self.model_name == other.model_name)

class MockRelated(object):
    """A mock RelatedObject for relation fields.

    This replicates some of the state and functionality of
    :py:class:`django.db.models.related.RelatedObject`, used for generating
    signatures and applying mutations.
    """

    def __init__(self, related_model, model, field):
        """Initialize the object.

        Args:
            related_model (MockModel):
                The mock model on the other end of the relation.

            model (MockModel):
                The mock model on this end of the relation.

            field (django.db.models.Field):
                The field owning the relation.
        """
        self.parent_model = related_model
        self.model = model
        self.opts = model._meta
        self.field = field
        self.name = '%s:%s' % (model.app_name, model.model_name)
        self.var_name = model.model_name.lower()
<commit_msg>Keep the injected on_delete out of the field signature.<commit_after>"""Utilities for building mock database models and fields."""

from __future__ import unicode_literals

from django.db import models
from django.db.models.base import ModelState
from django.db.models.fields import FieldDoesNotExist
from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
from django.utils import six
from django.utils.encoding import force_bytes
from django.utils.functional import curry

from django_evolution.compat.datastructures import OrderedDict
from django_evolution.compat.models import (get_remote_field,
                                            get_remote_field_model)

def create_field(proj_sig, field_name, field_type, field_attrs, parent_model):
    """Create a Django field instance for the given signature data.

    This creates a field in a way that's compatible with a variety of versions
    of Django. It takes in data such as the field's name and attributes
    and creates an instance that can be used like any field found on a model.

    Args:
        field_name (unicode):
            The name of the field.

        field_type (cls):
            The class for the type of field being constructed. This must be a
            subclass of :py:class:`django.db.models.Field`.

        field_attrs (dict):
            Attributes to set on the field.

        parent_model (cls):
            The parent model that would own this field. This must be a
            subclass of :py:class:`django.db.models.Model`.

    Returns:
        django.db.models.Field:
        A new field instance matching the provided data.
    """
    # Convert to the standard string format for each version of Python, to
    # simulate what the format would be for the default name.
    field_name = str(field_name)

    # related_model isn't a valid field attribute, so it must be removed
    # prior to instantiating the field, but it must be restored
    # to keep the signature consistent.
    related_model = field_attrs.pop('related_model', None)

    if related_model:
        related_app_name, related_model_name = related_model.split('.')
        related_model_sig = proj_sig[related_app_name][related_model_name]
        to = MockModel(proj_sig, related_app_name, related_model_name,
                       related_model_sig, stub=True)

        has_on_delete = 'on_delete' in field_attrs

        if (issubclass(field_type, models.ForeignKey) and
            hasattr(models, 'CASCADE') and
            not has_on_delete):
            # Starting in Django 2.0, on_delete is a requirement for
            # ForeignKeys. If not provided in the signature, we want to
            # default this to CASCADE, which is the value that Django
            # previously defaulted to.
            field_attrs['on_delete'] = models.CASCADE

        field = field_type(to, name=field_name, **field_attrs)
        field_attrs['related_model'] = related_model

        if not has_on_delete:
            field_attrs.pop('on_delete', None)
    else:
        field = field_type(name=field_name, **field_attrs)

    if field_type is models.ManyToManyField and parent_model is not None:
        # Starting in Django 1.2, a ManyToManyField must have a through
        # model defined. This will be set internally to an auto-created
        # model if one isn't specified. We have to fake that model.
        through_model = field_attrs.get('through_model', None)
        through_model_sig = None

        if through_model:
            through_app_name, through_model_name = through_model.split('.')
            through_model_sig = proj_sig[through_app_name][through_model_name]
        elif hasattr(field, '_get_m2m_attr'):
            # Django >= 1.2
            remote_field = get_remote_field(field)
            remote_field_model = get_remote_field_model(remote_field)

            to = remote_field_model._meta.object_name.lower()

            if (remote_field_model == RECURSIVE_RELATIONSHIP_CONSTANT or
                to == parent_model._meta.object_name.lower()):
                from_ = 'from_%s' % to
                to = 'to_%s' % to
            else:
                from_ = parent_model._meta.object_name.lower()

            # This corresponds to the signature in
            # related.create_many_to_many_intermediary_model
            through_app_name = parent_model.app_name
            through_model_name = '%s_%s' % (parent_model._meta.object_name,
                                            field.name)
            through_model = '%s.%s' % (through_app_name, through_model_name)

            fields = OrderedDict()
            fields['id'] = {
                'field_type': models.AutoField,
                'primary_key': True,
            }

            fields[from_] = {
                'field_type': models.ForeignKey,
                'related_model': '%s.%s' % (parent_model.app_name,
                                            parent_model._meta.object_name),
                'related_name': '%s+' % through_model_name,
            }

            fields[to] = {
                'field_type': models.ForeignKey,
                'related_model': related_model,
                'related_name': '%s+' % through_model_name,
            }

            through_model_sig = {
                'meta': {
                    'db_table': field._get_m2m_db_table(parent_model._meta),
                    'managed': True,
                    'auto_created': True,
                    'app_label': through_app_name,
                    'unique_together': ((from_, to),),
                    'pk_column': 'id',
                },
                'fields': fields,
            }

            field.auto_created = True

        if through_model_sig:
            through = MockModel(proj_sig, through_app_name, through_model_name,
                                through_model_sig)
            get_remote_field(field).through = through

        field.m2m_db_table = curry(field._get_m2m_db_table, parent_model._meta)
        field.set_attributes_from_rel()

    field.set_attributes_from_name(field_name)

    # Needed in Django >= 1.7, for index building.
    field.model = parent_model

    return field

class MockMeta(object):
    """A mock of a models Options object, based on the model signature.

    This emulates the standard Meta class for a model, storing data and
    providing mock functions for setting up fields from a signature.
    """

    def __init__(self, proj_sig, app_name, model_name, model_sig):
        """Initialize the meta instance.

        Args:
            proj_sig (dict):
                The project's schema signature.

            app_name (unicode):
                The name of the Django application owning the model.

            model_name (unicode):
                The name of the model.

            model_sig (dict):
                The model's schema signature.
        """
        self.object_name = model_name
        self.app_label = app_name
        self.meta = {
            'order_with_respect_to': None,
            'has_auto_field': None,
            'db_tablespace': None,
            'swapped': False,
            'index_together': [],
            'indexes': [],
        }
        self.meta.update(model_sig['meta'])
        self._fields = OrderedDict()
        self._many_to_many = OrderedDict()
        self.abstract = False
        self.managed = True
        self.proxy = False
        self._model_sig = model_sig
        self._proj_sig = proj_sig

    @property
    def local_fields(self):
        """A list of all local fields on the model."""
        return list(six.itervalues(self._fields))

    fields = local_fields

    @property
    def local_many_to_many(self):
        """A list of all local Many-to-Many fields on the model."""
        return list(six.itervalues(self._many_to_many))

    def setup_fields(self, model, stub=False):
        """Set up the fields listed in the model's signature.

        For each field in the model signature's list of fields, a field
        instance will be created and stored in :py:attr:`_fields` or
        :py:attr:`_many_to_many` (depending on the type of field).

        Some fields (for instance, a field representing a primary key) may
        also influence the attributes on this model.

        Args:
            model (cls):
                The model class owning this meta instance. This must be a
                subclass of :py:class:`django.db.models.Model`.

            stub (bool, optional):
                If provided, only a primary key will be set up. This is used
                internally when creating relationships between models and
                fields in order to prevent recursive relationships.
        """
        for field_name, field_sig in six.iteritems(self._model_sig['fields']):
            primary_key = field_sig.get('primary_key', False)

            if not stub or primary_key:
                field_type = field_sig.pop('field_type')
                field = create_field(self._proj_sig, field_name, field_type,
                                     field_sig, model)
                field_sig['field_type'] = field_type

                if type(field) is models.AutoField:
                    self.meta['has_auto_field'] = True
                    self.meta['auto_field'] = field

                if type(field) is models.ManyToManyField:
                    self._many_to_many[field.name] = field
                else:
                    self._fields[field.name] = field

                field.set_attributes_from_name(field_name)

                if primary_key:
                    self.pk = field

    def __getattr__(self, name):
        """Return an attribute from the meta class.

        This will look up the attribute from the correct location, depending
        on the attribute being accessed.

        Args:
            name (unicode):
                The attribute name.

        Returns:
            object:
            The attribute value.
        """
        if name == 'model_name':
            return self.object_name

        return self.meta[name]

    def get_field(self, name):
        """Return a field with the given name.

        Args:
            name (unicode):
                The name of the field.

        Returns:
            django.db.models.Field:
            The field with the given name.

        Raises:
            django.db.models.fields.FieldDoesNotExist:
                The field could not be found.
        """
        try:
            return self._fields[name]
        except KeyError:
            try:
                return self._many_to_many[name]
            except KeyError:
                raise FieldDoesNotExist('%s has no field named %r' %
                                        (self.object_name, name))

    def get_field_by_name(self, name):
        """Return information on a field with the given name.

        This is a stub that provides only basic functionality. It will
        return information for a field with the given name, with most
        data hard-coded.

        Args:
            name (unicode):
                The name of the field.

        Returns:
            tuple:
            A tuple of information for the following:

            * The field instance (:py:class:`django.db.models.Field`)
            * The model (hard-coded as ``None``)
            * Whether this field is owned by this model (hard-coded as
              ``True``)
            * Whether this is for a many-to-many relationship (hard-coded as
             ``None``)

        Raises:
            django.db.models.fields.FieldDoesNotExist:
                The field could not be found.
        """
        return (self.get_field(name), None, True, None)

class MockModel(object):
    """A mock model.

    This replicates some of the state and functionality of a model for
    use when generating, reading, or mutating signatures.
    """

    def __init__(self, proj_sig, app_name, model_name, model_sig, stub=False,
                 db_name=None):
        """Initialize the model.

        Args:
            proj_sig (dict):
                The project's schema signature.

            app_name (unicode):
                The name of the Django app that owns the model.

            model_name (unicode):
                The name of the model.

            model_sig (dict):
                The model's schema signature.

            stub (bool, optional):
                Whether this is a stub model. This is used internally to
                create models that only contain a primary key field and no
                others, for use when dealing with circular relationships.

            db_name (unicode, optional):
                The name of the database where the model would be read from or
                written to.
        """
        self.app_name = app_name
        self.model_name = model_name
        self._meta = MockMeta(proj_sig, app_name, model_name, model_sig)
        self._meta.setup_fields(self, stub)
        self._state = ModelState(db_name)

    def __repr__(self):
        """Return a string representation of the model.

        Returns:
            unicode:
            A string representation of the model.
        """
        return '<MockModel for %s.%s>' % (self.app_name, self.model_name)

    def __hash__(self):
        """Return a hash of the model instance.

        This is used to allow the model instance to be used as a key in a
        dictionary.

        Django would return a hash of the primary key's value, but that's not
        necessary for our needs, and we don't have field values in most mock
        models.

        Returns:
            int:
            The hash of the model.
        """
        return hash(id(self))

    def __eq__(self, other):
        """Return whether two mock models are equal.

        Both are considered equal if they're both mock models with the same
        app name and model name.

        Args:
            other (MockModel):
                The other mock model to compare to.

        Returns:
            bool:
            ``True`` if both are equal. ``False` if they are not.
        """
        # For our purposes, we don't want to appear equal to "self".
        # Really, Django 1.2 should be checking if this is a string before
        # doing this comparison,
        return (isinstance(other, MockModel) and
                self.app_name == other.app_name and
                self.model_name == other.model_name)

class MockRelated(object):
    """A mock RelatedObject for relation fields.

    This replicates some of the state and functionality of
    :py:class:`django.db.models.related.RelatedObject`, used for generating
    signatures and applying mutations.
    """

    def __init__(self, related_model, model, field):
        """Initialize the object.

        Args:
            related_model (MockModel):
                The mock model on the other end of the relation.

            model (MockModel):
                The mock model on this end of the relation.

            field (django.db.models.Field):
                The field owning the relation.
        """
        self.parent_model = related_model
        self.model = model
        self.opts = model._meta
        self.field = field
        self.name = '%s:%s' % (model.app_name, model.model_name)
        self.var_name = model.model_name.lower()
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
import unittest

from django.test.client import Client
from django.core.urlresolvers import reverse

from .main import USER_AGENTS

class ViewIndexTest(unittest.TestCase):

    def setUp(self):
        self.client = Client()
        self.url = reverse('django_webp', args=('django_webp/python.png', ))

    def test_simple_request(self):
        """ should return an image converted to webp """
        for user_agent in USER_AGENTS:
            headers = { 'HTTP_USER_AGENT': user_agent }
            response = self.client.get(self.url, **headers)

            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.get('Content-Type'), 'image/webp')

    def test_404(self):
        """ Should raise a 404 """
        url = reverse('django_webp', args=('django_webp/not-exist.png', ))

        response = self.client.get(url)

        self.assertEqual(response.status_code, 404)

<commit_msg>Fixing import in test<commit_after># -*- coding: utf-8 -*-
import unittest

from django.test.client import Client
from django.core.urlresolvers import reverse

from django_webp.tests.main import USER_AGENTS

class ViewIndexTest(unittest.TestCase):

    def setUp(self):
        self.client = Client()
        self.url = reverse('django_webp', args=('django_webp/python.png', ))

    def test_simple_request(self):
        """ should return an image converted to webp """
        for user_agent in USER_AGENTS:
            headers = { 'HTTP_USER_AGENT': user_agent }
            response = self.client.get(self.url, **headers)

            self.assertEqual(response.status_code, 200)
            self.assertEqual(response.get('Content-Type'), 'image/webp')

    def test_404(self):
        """ Should raise a 404 """
        url = reverse('django_webp', args=('django_webp/not-exist.png', ))

        response = self.client.get(url)

        self.assertEqual(response.status_code, 404)

<|endoftext|>


<commit_before>from itertools import imap

from redish.utils import maybe_datetime
from redish.models import Model, Manager

from djangofeeds import conf
from djangofeeds.managers import DEFAULT_POST_LIMIT

class InconsistencyWarning(UserWarning):
    """An inconsistency with the data has been found."""

class Entry(Model):

    def post_save(self):
        self.sort_index.add(self.id, maybe_datetime(self.timestamp))
        self.guid_map[self.guid] = self.id

    def post_delete(self):
        del(self.guid_map[self.guid])
        self.sort_index.remove(self.id)

    @property
    def sort_index(self):
        return self.objects.get_sort_index(self["feed_url"])

    @property
    def guid_map(self):
        return self.objects.get_guid_map(self["feed_url"])

    def __repr__(self):
        if "guid" in self and "title" in self:
            return "<Entry: %s '%s'>" % (self.guid, self.title)
        return super(Entry, self).__repr__()

class Entries(Manager):
    db = "djangofeeds"
    model = Entry

    def all_by_order(self, feed_url, limit=DEFAULT_POST_LIMIT):
        posts = []
        index = self.get_sort_index(feed_url)
        for key in index.revrange(0, limit):
            try:
                posts.append(self.get(key))
            except KeyError:
                warnings.warn(
                        "Sort index for %s has member %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, key),
                        InconsistencyWarning)
                index.remove(key)

        return posts

    def update_or_create(self, feed_obj, **fields):
        fields["feed_url"] = feed_obj.feed_url
        fields["timestamp"] = fields["date_updated"]

        conf.FSCK_ON_UPDATE and self.fsck([feed_url])

        try:
            entry = self.get_by_guid(fields["feed_url"], fields["guid"])
            entry.update(fields)
            entry.save()
            return entry
        except KeyError:
            return self.create(**fields)

    def _verify_guidmap_consistency(self, feed_url, clean=True, full=False):
        # Verify that all pks in the guid map exists.
        guid_map = self.get_guid_map(feed_url)
        reverse = dict((pk, guid) for guid, pk in guid_map.items())
        pks = reverse.keys()
        postdata = self.api.mget(pks)
        issues = 0
        if any(post is None for post in postdata):
            for i, post in enumerate(postdata):
                if post is None:
                    warnings.warn(
                        "Guid map for %s has value %s for %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, pks[i], guid_map[pks[i]]),
                        InconsistencyWarning)
                    if clean:
                        del(guid_map[pks[i]])
                    issues += 1
        if full:
            # Verify that all entries in the system is in one or more
            # guid maps.
            seen = set()
            entrypks = set(self.keys("Entry:*"))
            for guidmap in imap(self.Dict, self.keys("*:guidmap")):
                seen.update(set(guidmap.values()))
            for missing in entrypks ^ seen:
                warnings.warn(
                    "Loose entry not in any guid map: %s (%s)" % (
                            missing, self[missing]),
                        InconsistencyWarning)
                if clean:
                    del(self[missing])
                issues += 1

        return issues

    def _verify_sort_index_consistency(self, feed_url, clean=True, full=False):
        # Verify that all pks in the sort index exists.
        index = self.get_sort_index(self, feed_url)
        pks = list(index)
        postdata = self.api.mget(pks)
        issues = 0
        if any(post is None for post in postdata):
            for i, post in enumerate(postdata):
                if post is None:
                    warnings.warn(
                        "Sort index for %s has member %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, pks[i]),
                        InconsistencyWarning)
                    if clean:
                        index.remove(pks[i])
                    issues += 1
        if full:
            # Verify that all entries in the system is in one or more
            # sort indices.
            seen = set()
            entrypks = set(self.keys("Entry:*"))
            for index in imap(self.SortedSet, self.iterkeys("*:sort")):
                seen.update(set(index))
            for missing in entrypks ^ seen:
                warnings.warn(
                    "Loose entry not in any sort index: %s (%s)" % (
                            missing, self[missing]),
                        InconsistencyWarning)
                if clean:
                    del(self[missing])
                issues += 1
        return issues

    def fsck(self, feed_urls=None, clean=True, full=False):
        if feed_urls is None:
            # Verify all existing feeds.
            _gkeys = set(k[:-8] for k in self.keys("*:guidmap"))
            _skeys = set(k[:-5] for k in self.keys("*:sort"))
            feed_urls = _gkeys | _skeys

        for feed_url in feed_urls:
            self.verify_guidmap_consistency(feed_url, clean, full)
            self.verify_sort_index_consistency(feed_url, clean, full)

    def get_by_guid(self, feed_url, guid):
        return self.get(self.get_guid_map(feed_url)[guid])

    def get_sort_index(self, feed_url):
        return self.SortedSet((feed_url, "sort"))

    def get_guid_map(self, feed_url):
        return self.Dict((feed_url, "guidmap"))

class RedisBackend(object):
    _entry = None

    def get_post_model(self):
        return Entries(host=conf.REDIS_POST_HOST,
                       port=conf.REDIS_POST_PORT,
                       db=conf.REDIS_POST_DB).Entry()

    def all_posts_by_order(self, feed, **kwargs):
        return self.Entry.objects.all_by_order(feed.feed_url, **kwargs)

    def get_post_count(self, feed):
        return len(self.Entry.objects.get_sort_index(feed.feed_url))

    @property
    def Entry(self):
        if self._entry is None:
            self._entry = self.get_post_model()
        return self._entry
<commit_msg> _verify_post_consistency: run by fsck(full=True)<commit_after>from itertools import imap
from datetime import datetime

from redish.utils import maybe_datetime
from redish.models import Model, Manager

from djangofeeds import conf
from djangofeeds.managers import DEFAULT_POST_LIMIT

class InconsistencyWarning(UserWarning):
    """An inconsistency with the data has been found."""

class Entry(Model):

    def post_save(self):
        self.sort_index.add(self.id, maybe_datetime(self.timestamp))
        self.guid_map[self.guid] = self.id

    def post_delete(self):
        del(self.guid_map[self.guid])
        self.sort_index.remove(self.id)

    @property
    def sort_index(self):
        return self.objects.get_sort_index(self["feed_url"])

    @property
    def guid_map(self):
        return self.objects.get_guid_map(self["feed_url"])

    def __repr__(self):
        if "guid" in self and "title" in self:
            return "<Entry: %s '%s'>" % (self.guid, self.title)
        return super(Entry, self).__repr__()

class Entries(Manager):
    db = "djangofeeds"
    model = Entry

    def all_by_order(self, feed_url, limit=DEFAULT_POST_LIMIT):
        posts = []
        index = self.get_sort_index(feed_url)
        for key in index.revrange(0, limit):
            try:
                posts.append(self.get(key))
            except KeyError:
                warnings.warn(
                        "Sort index for %s has member %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, key),
                        InconsistencyWarning)
                index.remove(key)

        return posts

    def update_or_create(self, feed_obj, **fields):
        fields["feed_url"] = feed_obj.feed_url
        fields["timestamp"] = fields["date_updated"]

        conf.FSCK_ON_UPDATE and self.fsck([feed_url], full=False)

        try:
            entry = self.get_by_guid(fields["feed_url"], fields["guid"])
            entry.update(fields)
            entry.save()
            return entry
        except KeyError:
            return self.create(**fields)

    def _verify_post_consistency(self, pk, post, clean=True):
        is_string = lambda s: isinstance(s, basestring)
        is_datetime = lambda s: isinstance(s, datetime)

        tests = {"feed_url": [is_string],
                 "guid": [is_string],
                 "date_updated": [is_datetime],
                 "content": [],
                 "title": []}

        for key, checks in tests.items():
            if key in post:
                value = post[key]
                if any(check(value) for check in checks):
                    failure = "invalid"
            else:
                failure = "missing"
                value = None

            if failure:
                warnings.warn(
                    "%s key for entry %s: %s (value: '%s')" % (
                        failure, pk, key, value),
                    InconsistencyWarning)
                if clean:
                    del(self[pk])

    def _verify_guidmap_consistency(self, feed_url, clean=True, full=False):
        # Verify that all pks in the guid map exists.
        guid_map = self.get_guid_map(feed_url)
        reverse = dict((pk, guid) for guid, pk in guid_map.items())
        pks = reverse.keys()
        postdata = self.api.mget(pks)
        issues = 0
        if any(post is None for post in postdata):
            for i, post in enumerate(postdata):
                if post is None:
                    warnings.warn(
                        "Guid map for %s has value %s for %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, pks[i], guid_map[pks[i]]),
                        InconsistencyWarning)
                    if clean:
                        del(guid_map[pks[i]])
                    issues += 1
        if full:
            # Verify that all entries in the system is in one or more
            # guid maps.
            seen = set()
            entrypks = set(self.keys("Entry:*"))
            for guidmap in imap(self.Dict, self.keys("*:guidmap")):
                seen.update(set(guidmap.values()))
            for missing in entrypks ^ seen:
                warnings.warn(
                    "Loose entry not in any guid map: %s (%s)" % (
                            missing, self[missing]),
                        InconsistencyWarning)
                if clean:
                    del(self[missing])
                issues += 1

        return issues

    def _verify_sort_index_consistency(self, feed_url, clean=True, full=False):
        # Verify that all pks in the sort index exists.
        index = self.get_sort_index(self, feed_url)
        pks = list(index)
        postdata = self.api.mget(pks)
        issues = 0
        if any(post is None for post in postdata):
            for i, post in enumerate(postdata):
                if post is None:
                    warnings.warn(
                        "Sort index for %s has member %s, but the "
                        "related key does not exist anymore." % (
                            feed_url, pks[i]),
                        InconsistencyWarning)
                    if clean:
                        index.remove(pks[i])
                    issues += 1
        if full:
            # Verify that all entries in the system is in one or more
            # sort indices.
            seen = set()
            entrypks = set(self.keys("Entry:*"))
            for index in imap(self.SortedSet, self.iterkeys("*:sort")):
                seen.update(set(index))
            for missing in entrypks ^ seen:
                warnings.warn(
                    "Loose entry not in any sort index: %s (%s)" % (
                            missing, self[missing]),
                        InconsistencyWarning)
                if clean:
                    del(self[missing])
                issues += 1
        return issues

    def fsck(self, feed_urls=None, clean=True, full=False):
        if feed_urls is None:
            # Verify all existing feeds.
            _gkeys = set(k[:-8] for k in self.keys("*:guidmap"))
            _skeys = set(k[:-5] for k in self.keys("*:sort"))
            feed_urls = _gkeys | _skeys

        for feed_url in feed_urls:
            self.verify_guidmap_consistency(feed_url, clean, full)
            self.verify_sort_index_consistency(feed_url, clean, full)

        if full:
            for pk, post in self.iteritems("Entry:*"):
                self._verify_post_consistency(pk, post, clean)

    def get_by_guid(self, feed_url, guid):
        return self.get(self.get_guid_map(feed_url)[guid])

    def get_sort_index(self, feed_url):
        return self.SortedSet((feed_url, "sort"))

    def get_guid_map(self, feed_url):
        return self.Dict((feed_url, "guidmap"))

class RedisBackend(object):
    _entry = None

    def get_post_model(self):
        return Entries(host=conf.REDIS_POST_HOST,
                       port=conf.REDIS_POST_PORT,
                       db=conf.REDIS_POST_DB).Entry()

    def all_posts_by_order(self, feed, **kwargs):
        return self.Entry.objects.all_by_order(feed.feed_url, **kwargs)

    def get_post_count(self, feed):
        return len(self.Entry.objects.get_sort_index(feed.feed_url))

    @property
    def Entry(self):
        if self._entry is None:
            self._entry = self.get_post_model()
        return self._entry
<|endoftext|>


<commit_before>import functools
import io

import six
import win32file
import win32pipe

cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0
cPIPE_READMODE_MESSAGE = 2

def check_closed(f):
    @functools.wraps(f)
    def wrapped(self, *args, **kwargs):
        if self._closed:
            raise RuntimeError(
                'Can not reuse socket after connection was closed.'
            )
        return f(self, *args, **kwargs)
    return wrapped

class NpipeSocket(object):
    """ Partial implementation of the socket API over windows named pipes.
        This implementation is only designed to be used as a client socket,
        and server-specific methods (bind, listen, accept...) are not
        implemented.
    """

    def __init__(self, handle=None):
        self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
        self._handle = handle
        self._closed = False

    def accept(self):
        raise NotImplementedError()

    def bind(self, address):
        raise NotImplementedError()

    def close(self):
        self._handle.Close()
        self._closed = True

    @check_closed
    def connect(self, address):
        win32pipe.WaitNamedPipe(address, self._timeout)
        handle = win32file.CreateFile(
            address,
            win32file.GENERIC_READ | win32file.GENERIC_WRITE,
            0,
            None,
            win32file.OPEN_EXISTING,
            cSECURITY_ANONYMOUS | cSECURITY_SQOS_PRESENT,
            0
        )
        self.flags = win32pipe.GetNamedPipeInfo(handle)[0]

        self._handle = handle
        self._address = address

    @check_closed
    def connect_ex(self, address):
        return self.connect(address)

    @check_closed
    def detach(self):
        self._closed = True
        return self._handle

    @check_closed
    def dup(self):
        return NpipeSocket(self._handle)

    @check_closed
    def fileno(self):
        return int(self._handle)

    def getpeername(self):
        return self._address

    def getsockname(self):
        return self._address

    def getsockopt(self, level, optname, buflen=None):
        raise NotImplementedError()

    def ioctl(self, control, option):
        raise NotImplementedError()

    def listen(self, backlog):
        raise NotImplementedError()

    def makefile(self, mode=None, bufsize=None):
        if mode.strip('b') != 'r':
            raise NotImplementedError()
        rawio = NpipeFileIOBase(self)
        if bufsize is None or bufsize <= 0:
            bufsize = io.DEFAULT_BUFFER_SIZE
        return io.BufferedReader(rawio, buffer_size=bufsize)

    @check_closed
    def recv(self, bufsize, flags=0):
        err, data = win32file.ReadFile(self._handle, bufsize)
        return data

    @check_closed
    def recvfrom(self, bufsize, flags=0):
        data = self.recv(bufsize, flags)
        return (data, self._address)

    @check_closed
    def recvfrom_into(self, buf, nbytes=0, flags=0):
        return self.recv_into(buf, nbytes, flags), self._address

    @check_closed
    def recv_into(self, buf, nbytes=0):
        if six.PY2:
            return self._recv_into_py2(buf, nbytes)

        readbuf = buf
        if not isinstance(buf, memoryview):
            readbuf = memoryview(buf)

        err, data = win32file.ReadFile(
            self._handle,
            readbuf[:nbytes] if nbytes else readbuf
        )
        return len(data)

    def _recv_into_py2(self, buf, nbytes):
        err, data = win32file.ReadFile(self._handle, nbytes or len(buf))
        n = len(data)
        buf[:n] = data
        return n

    @check_closed
    def send(self, string, flags=0):
        err, nbytes = win32file.WriteFile(self._handle, string)
        return nbytes

    @check_closed
    def sendall(self, string, flags=0):
        return self.send(string, flags)

    @check_closed
    def sendto(self, string, address):
        self.connect(address)
        return self.send(string)

    def setblocking(self, flag):
        if flag:
            return self.settimeout(None)
        return self.settimeout(0)

    def settimeout(self, value):
        if value is None:
            self._timeout = win32pipe.NMPWAIT_NOWAIT
        elif not isinstance(value, (float, int)) or value < 0:
            raise ValueError('Timeout value out of range')
        elif value == 0:
            self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
        else:
            self._timeout = value

    def gettimeout(self):
        return self._timeout

    def setsockopt(self, level, optname, value):
        raise NotImplementedError()

    @check_closed
    def shutdown(self, how):
        return self.close()

class NpipeFileIOBase(io.RawIOBase):
    def __init__(self, npipe_socket):
        self.sock = npipe_socket

    def close(self):
        super(NpipeFileIOBase, self).close()
        self.sock = None

    def fileno(self):
        return self.sock.fileno()

    def isatty(self):
        return False

    def readable(self):
        return True

    def readinto(self, buf):
        return self.sock.recv_into(buf)

    def seekable(self):
        return False

    def writable(self):
        return False
<commit_msg>Implement retry logic when the npipe open procedure fails with ERROR_PIPE_BUSY<commit_after>import functools
import io

import six
import win32file
import win32pipe

cERROR_PIPE_BUSY = 0xe7
cSECURITY_SQOS_PRESENT = 0x100000
cSECURITY_ANONYMOUS = 0

RETRY_WAIT_TIMEOUT = 10000

def check_closed(f):
    @functools.wraps(f)
    def wrapped(self, *args, **kwargs):
        if self._closed:
            raise RuntimeError(
                'Can not reuse socket after connection was closed.'
            )
        return f(self, *args, **kwargs)
    return wrapped

class NpipeSocket(object):
    """ Partial implementation of the socket API over windows named pipes.
        This implementation is only designed to be used as a client socket,
        and server-specific methods (bind, listen, accept...) are not
        implemented.
    """

    def __init__(self, handle=None):
        self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
        self._handle = handle
        self._closed = False

    def accept(self):
        raise NotImplementedError()

    def bind(self, address):
        raise NotImplementedError()

    def close(self):
        self._handle.Close()
        self._closed = True

    @check_closed
    def connect(self, address):
        win32pipe.WaitNamedPipe(address, self._timeout)
        try:
            handle = win32file.CreateFile(
                address,
                win32file.GENERIC_READ | win32file.GENERIC_WRITE,
                0,
                None,
                win32file.OPEN_EXISTING,
                cSECURITY_ANONYMOUS | cSECURITY_SQOS_PRESENT,
                0
            )
        except win32pipe.error as e:
            # See Remarks:
            # https://msdn.microsoft.com/en-us/library/aa365800.aspx
            if e.winerror == cERROR_PIPE_BUSY:
                # Another program or thread has grabbed our pipe instance
                # before we got to it. Wait for availability and attempt to
                # connect again.
                win32pipe.WaitNamedPipe(address, RETRY_WAIT_TIMEOUT)
                return self.connect(address)
            raise e

        self.flags = win32pipe.GetNamedPipeInfo(handle)[0]

        self._handle = handle
        self._address = address

    @check_closed
    def connect_ex(self, address):
        return self.connect(address)

    @check_closed
    def detach(self):
        self._closed = True
        return self._handle

    @check_closed
    def dup(self):
        return NpipeSocket(self._handle)

    @check_closed
    def fileno(self):
        return int(self._handle)

    def getpeername(self):
        return self._address

    def getsockname(self):
        return self._address

    def getsockopt(self, level, optname, buflen=None):
        raise NotImplementedError()

    def ioctl(self, control, option):
        raise NotImplementedError()

    def listen(self, backlog):
        raise NotImplementedError()

    def makefile(self, mode=None, bufsize=None):
        if mode.strip('b') != 'r':
            raise NotImplementedError()
        rawio = NpipeFileIOBase(self)
        if bufsize is None or bufsize <= 0:
            bufsize = io.DEFAULT_BUFFER_SIZE
        return io.BufferedReader(rawio, buffer_size=bufsize)

    @check_closed
    def recv(self, bufsize, flags=0):
        err, data = win32file.ReadFile(self._handle, bufsize)
        return data

    @check_closed
    def recvfrom(self, bufsize, flags=0):
        data = self.recv(bufsize, flags)
        return (data, self._address)

    @check_closed
    def recvfrom_into(self, buf, nbytes=0, flags=0):
        return self.recv_into(buf, nbytes, flags), self._address

    @check_closed
    def recv_into(self, buf, nbytes=0):
        if six.PY2:
            return self._recv_into_py2(buf, nbytes)

        readbuf = buf
        if not isinstance(buf, memoryview):
            readbuf = memoryview(buf)

        err, data = win32file.ReadFile(
            self._handle,
            readbuf[:nbytes] if nbytes else readbuf
        )
        return len(data)

    def _recv_into_py2(self, buf, nbytes):
        err, data = win32file.ReadFile(self._handle, nbytes or len(buf))
        n = len(data)
        buf[:n] = data
        return n

    @check_closed
    def send(self, string, flags=0):
        err, nbytes = win32file.WriteFile(self._handle, string)
        return nbytes

    @check_closed
    def sendall(self, string, flags=0):
        return self.send(string, flags)

    @check_closed
    def sendto(self, string, address):
        self.connect(address)
        return self.send(string)

    def setblocking(self, flag):
        if flag:
            return self.settimeout(None)
        return self.settimeout(0)

    def settimeout(self, value):
        if value is None:
            self._timeout = win32pipe.NMPWAIT_NOWAIT
        elif not isinstance(value, (float, int)) or value < 0:
            raise ValueError('Timeout value out of range')
        elif value == 0:
            self._timeout = win32pipe.NMPWAIT_USE_DEFAULT_WAIT
        else:
            self._timeout = value

    def gettimeout(self):
        return self._timeout

    def setsockopt(self, level, optname, value):
        raise NotImplementedError()

    @check_closed
    def shutdown(self, how):
        return self.close()

class NpipeFileIOBase(io.RawIOBase):
    def __init__(self, npipe_socket):
        self.sock = npipe_socket

    def close(self):
        super(NpipeFileIOBase, self).close()
        self.sock = None

    def fileno(self):
        return self.sock.fileno()

    def isatty(self):
        return False

    def readable(self):
        return True

    def readinto(self, buf):
        return self.sock.recv_into(buf)

    def seekable(self):
        return False

    def writable(self):
        return False
<|endoftext|>