<commit_before>"""Cornice Swagger 2.0 documentor"""
import re
import six

import colander
from cornice.validators import colander_validator, colander_body_validator

import cornice_swagger.util
from cornice_swagger.converters import convert_schema, convert_parameter

class CorniceSwaggerException(Exception):
    """Raised when cornice services have structural problems to be converted."""

class CorniceSwagger(object):
    """Handles the creation of a swagger document from a cornice application."""

    def __init__(self, services, def_ref_depth=0, param_ref=False, resp_ref=False):
        """
        :param services:
            List of cornice services to document. You may use
            cornice.service.get_services() to get it.
        :param def_ref_depth:
            How depth swagger object schemas should be split into
            swaggger definitions with JSON pointers. Default (0) is no split.
            You may use negative values to split everything.
        :param param_ref:
            Defines if swagger parameters should be put inline on the operation
            or on the parameters section and referenced by JSON pointers.
            Default is inline.
        :param resp_ref:
            Defines if swagger responses should be put inline on the operation
            or on the responses section and referenced by JSON pointers.
            Default is inline.
        """

        self.services = services

        self.definitions = DefinitionHandler(ref=def_ref_depth)
        self.parameters = ParameterHandler(self.definitions,
                                           ref=param_ref)
        self.responses = ResponseHandler(self.definitions,
                                         ref=resp_ref)

    def __call__(self, title, version, base_path='/', info={}, swagger={}, **kwargs):
        """
        Generate a Swagger 2.0 documentation. Keyword arguments may be used
        to provide additional information to build methods as such ignores.

        :param title:
            The name presented on the swagger document.
        :param version:
            The version of the API presented on the swagger document.
        :param base_path:
            The path that all requests to the API must refer to.
        :param info:
            Swagger info field.
        :param swagger:
            Extra fields that should be provided on the swagger documentation.
        """

        info.update(title=title, version=version)
        swagger.update(swagger='2.0', info=info, basePath=base_path)

        paths, tags = self._build_paths(**kwargs)
        if paths:
            swagger['paths'] = paths
        if tags:
            swagger['tags'] = tags

        definitions = self.definitions.definition_registry
        if definitions:
            swagger['definitions'] = definitions

        parameters = self.parameters.parameter_registry
        if parameters:
            swagger['parameters'] = parameters

        responses = self.responses.response_registry
        if responses:
            swagger['responses'] = responses

        return swagger

    def _build_paths(self, ignore_methods=['head', 'options'], ignore_ctypes=[],
                     default_tags=None, **kwargs):
        """
        Build the Swagger "paths" and "tags" attributes from cornice service
        definitions.

        :param ignore_methods:
            List of service methods that should NOT be presented on the
            documentation. You may use this to remove methods that are not
            essential on the API documentation. Default ignores HEAD ans OPTIONS.
        :param ignore_ctypes:
            List of service content-types that should NOT be presented on
            the documentation. You may use this when a Cornice service definition
            has multiple view definitions for a same method, which is not supported
            on Swagger.
        :param default_tags:
            Provide a default list of tags or a callable that takes a cornice
            service and returns a list of Swagger tags to be used if a tag is
            not provided by the view.
        """

        paths = {}
        tags = []

        for service in self.services:
            path = self._extract_path_from_service(service, **kwargs)

            for method_name, view, args in service.definitions:

                if method_name.lower() in ignore_methods:
                    continue

                op = self._extract_operation_from_view(view, args, **kwargs)

                if any(ctype in op.get('consumes', []) for ctype in ignore_ctypes):
                    continue

                # XXX: Swagger doesn't support different schemas for for a same method
                # with different ctypes as cornice. If this happens, you may ignore one
                # content-type from the documentation otherwise we raise an Exception
                # Related to https://github.com/OAI/OpenAPI-Specification/issues/146
                previous_definition = path.get(method_name.lower())
                if previous_definition:
                    raise CorniceSwaggerException(("Swagger doesn't support multiple "
                                                   "views for a same method. You may "
                                                   "ignore one."))

                # If tag not defined and a default tag is provided
                if 'tags' not in op and default_tags:
                    if callable(default_tags):
                        default_tags = default_tags(service)
                    op['tags'] = default_tags

                # Check if tags was correctly defined as a list
                if not isinstance(op.get('tags', []), list):
                    raise CorniceSwaggerException('Tags should be defined as a list.')

                # Add method tags to root tags
                for tag in op.get('tags', []):
                    if tag not in [t['name'] for t in tags]:
                        root_tag = {'name': tag}
                        tags.append(root_tag)

                path[method_name.lower()] = op
            paths[service.path] = path

        return paths, tags

    def _extract_path_from_service(self, service, **kwargs):
        """
        Extract path object and its parameters from service definitions.

        :param service:
            Cornice service to extract information from.

        :rtype: dict
            Path definition.
        """

        path = {}

        # Extract path parameters
        parameters = self.parameters.from_path(service.path)
        if parameters:
            path['parameters'] = parameters

        return path

    def _extract_operation_from_view(self, view, args={},
                                     summary_docstrings=False, **kwargs):
        """
        Extract swagger operation details from colander view definitions.

        :param view:
            View to extract information from.
        :param args:
            Arguments from the view decorator.
        :param summary_docstrings:
            Enable extracting operation summaries from view docstrings.

        :rtype: dict
            Operation definition.
        """

        op = {
            'responses': {
                'default': {
                    'description': 'UNDOCUMENTED RESPONSE'
                }
            },
        }

        # If 'produces' are not defined in the view, try get from renderers
        renderer = args.get('renderer', '')

        if "json" in renderer:  # allows for "json" or "simplejson"
            produces = ['application/json']
        elif renderer == 'xml':
            produces = ['text/xml']
        else:
            produces = None

        if produces:
            op.setdefault('produces', produces)

        # Get explicit accepted content-types
        consumes = args.get('content_type')

        # A single content-type is provided, so wrap it in a list
        if isinstance(consumes, six.string_types):
            consumes = [consumes]

        if consumes:
            op['consumes'] = list(consumes)

        # Get parameters from view schema
        if 'schema' in args:
            validators = args.get('validators')
            parameters = self.parameters.from_schema(args['schema'], validators)
            if parameters:
                op['parameters'] = parameters

        # Get summary from docstring
        if isinstance(view, six.string_types):
            if 'klass' in args:
                ob = args['klass']
                view_ = getattr(ob, view.lower())
                docstring = cornice_swagger.util.trim(view_.__doc__)
        else:
            docstring = cornice_swagger.util.trim(view.__doc__)

        if docstring and summary_docstrings:
            op['summary'] = docstring

        # Get response definitions
        if 'response_schemas' in args:
            op['responses'] = self.responses.from_schema_mapping(args['response_schemas'])

        # Get response tags
        if 'tags' in args:
            op['tags'] = args['tags']

        return op

class DefinitionHandler(object):
    """Handles Swagger object definitions provided by cornice as colander schemas."""

    json_pointer = '#/definitions/'

    def __init__(self, ref=0):
        """
        :param ref:
            The depth that should be used by self.ref when calling self.from_schema.
        """

        self.definition_registry = {}
        self.ref = ref

    def from_schema(self, schema_node, base_name=None):
        """
        Creates a Swagger definition from a colander schema.

        :param schema_node:
            Colander schema to be transformed into a Swagger definition.
        :param base_name:
            Schema alternative title.

        :rtype: dict
            Swagger schema.
        """
        return self._ref_recursive(convert_schema(schema_node), self.ref, base_name)

    def _ref_recursive(self, schema, depth, base_name=None):
        """
        Dismantle nested swagger schemas into several definitions using JSON pointers.
        Note: This can be dangerous since definition titles must be unique.

        :param schema:
            Base swagger schema.
        :param depth:
            How many levels of the swagger object schemas should be split into
            swaggger definitions with JSON pointers. Default (0) is no split.
            You may use negative values to split everything.
        :param base_name:
            If schema doesn't have a name, the caller may provide it to be
            used as reference.

        :rtype: dict
            JSON pointer to the root definition schema,
            or the original definition if depth is zero.
        """

        if depth == 0:
            return schema

        if schema['type'] != 'object':
            return schema

        name = base_name or schema['title']

        pointer = self.json_pointer + name
        for child_name, child in schema.get('properties', {}).items():
            schema['properties'][child_name] = self._ref_recursive(child, depth-1)

        self.definition_registry[name] = schema

        return {'$ref': pointer}

class ParameterHandler(object):
    """Handles swagger parameter definitions."""

    json_pointer = '#/parameters/'

    def __init__(self, definition_handler=DefinitionHandler(), ref=False):
        """
        :param definition_handler:
            Callable that handles swagger definition schemas.
        :param ref:
            Specifies the ref value when calling from_xxx methods.
        """

        self.parameter_registry = {}

        self.definitions = definition_handler
        self.ref = ref

    def from_schema(self, schema_node, validators=[]):
        """
        Creates a list of Swagger params from a colander request schema.

        :param schema_node:
            Request schema to be transformed into Swagger.
        :param validators:
            Validators used in colander with the schema.
        :rtype: list
            List of Swagger parameters.
        """

        params = []

        if not isinstance(schema_node, colander.Schema):
            schema_node = schema_node()

        if colander_validator in validators:
            for param_schema in schema_node.children:
                location = param_schema.name
                if location is 'body':
                    name = param_schema.__class__.__name__
                    param = convert_parameter(location,
                                              param_schema,
                                              self.definitions.from_schema)
                    param['name'] = name
                    if self.ref:
                        param = self._ref(param)
                    params.append(param)

                elif location in ('path', 'headers', 'querystring'):
                    for node_schema in param_schema.children:
                        param = convert_parameter(location,
                                                  node_schema,
                                                  self.definitions.from_schema)
                        if self.ref:
                            param = self._ref(param)
                        params.append(param)

        elif colander_body_validator in validators:
            name = schema_node.__class__.__name__
            param = convert_parameter('body', schema_node)
            param['name'] = name
            if self.ref:
                param = self._ref(param, schema_node.__class__.__name__)
            params.append(param)

        return params

    def from_path(self, path):
        """
        Create a list of Swagger path params from a cornice service path.

        :type path: string
        :rtype: list
        """

        param_names = re.findall('\{(.*?)\}', path)
        params = []
        for name in param_names:
            param_schema = colander.SchemaNode(colander.String(), name=name)
            param = convert_parameter('path', param_schema)
            if self.ref:
                param = self._ref(param)
            params.append(param)

        return params

    def _ref(self, param, base_name=None):
        """
        Store a parameter schema and return a reference to it.

        :param schema:
            Swagger parameter definition.
        :param base_name:
            Name that should be used for the reference.

        :rtype: dict
            JSON pointer to the original parameter definition.
        """

        name = base_name or param.get('title', '') or param.get('name', '')

        pointer = self.json_pointer + name
        self.parameter_registry[name] = param

        return {'$ref': pointer}

class ResponseHandler(object):
    """Handles swagger response definitions."""

    json_pointer = '#/responses/'

    def __init__(self, definition_handler=DefinitionHandler(), ref=False):
        """
        :param definition_handler:
            Callable that handles swagger definition schemas.
        :param ref:
            Specifies the ref value when calling from_xxx methods.
        """

        self.response_registry = {}

        self.definitions = definition_handler
        self.ref = ref

    def from_schema_mapping(self, schema_mapping):
        """
        Creates a Swagger response object from a dict of response schemas.

        :param schema_mapping:
            Dict with entries matching ``{status_code: response_schema}``.
        :rtype: dict
            Response schema.
        """

        responses = {}

        for status, response_schema in schema_mapping.items():

            response = {}
            if response_schema.description:
                response['description'] = response_schema.description
            else:
                raise CorniceSwaggerException('Responses must have a description.')

            for field_schema in response_schema.children:
                location = field_schema.name
                if location == 'body':
                    field_schema.title = field_schema.__class__.__name__
                    response['schema'] = self.definitions.from_schema(field_schema)
                elif location == 'header':
                    headers = convert_schema(field_schema)
                    if 'properties' in headers:
                        response['headers'] = headers['properties']

            pointer = response_schema.__class__.__name__
            if self.ref:
                response = self._ref(response, pointer)
            responses[status] = response

        return responses

    def _ref(self, resp, base_name=None):
        """
        Store a response schema and return a reference to it.

        :param schema:
            Swagger response definition.
        :param base_name:
            Name that should be used for the reference.

        :rtype: dict
            JSON pointer to the original response definition.
        """

        name = base_name or resp.get('title', '') or resp.get('name', '')

        pointer = self.json_pointer + name
        self.response_registry[name] = resp

        return {'$ref': pointer}

def generate_swagger_spec(services, title, version, **kwargs):
    """Utility to turn cornice web services into a Swagger-readable file.
    """

    def get_tags_from_path(service):
        return [service.path.split("/")[1]]

    swag = CorniceSwagger(services, def_ref_depth=-1, param_ref=0)
    doc = swag(title, version, ignores=('head'),
               default_tags=get_tags_from_path,
               summary_docstrings=True, **kwargs)
    doc.update(**kwargs)

    return doc
<commit_msg>minor fixes in tag generation using default callables<commit_after>"""Cornice Swagger 2.0 documentor"""
import re
import six

import colander
from cornice.validators import colander_validator, colander_body_validator

import cornice_swagger.util
from cornice_swagger.converters import convert_schema, convert_parameter

class CorniceSwaggerException(Exception):
    """Raised when cornice services have structural problems to be converted."""

class CorniceSwagger(object):
    """Handles the creation of a swagger document from a cornice application."""

    def __init__(self, services, def_ref_depth=0, param_ref=False, resp_ref=False):
        """
        :param services:
            List of cornice services to document. You may use
            cornice.service.get_services() to get it.
        :param def_ref_depth:
            How depth swagger object schemas should be split into
            swaggger definitions with JSON pointers. Default (0) is no split.
            You may use negative values to split everything.
        :param param_ref:
            Defines if swagger parameters should be put inline on the operation
            or on the parameters section and referenced by JSON pointers.
            Default is inline.
        :param resp_ref:
            Defines if swagger responses should be put inline on the operation
            or on the responses section and referenced by JSON pointers.
            Default is inline.
        """

        self.services = services

        self.definitions = DefinitionHandler(ref=def_ref_depth)
        self.parameters = ParameterHandler(self.definitions,
                                           ref=param_ref)
        self.responses = ResponseHandler(self.definitions,
                                         ref=resp_ref)

    def __call__(self, title, version, base_path='/', info={}, swagger={}, **kwargs):
        """
        Generate a Swagger 2.0 documentation. Keyword arguments may be used
        to provide additional information to build methods as such ignores.

        :param title:
            The name presented on the swagger document.
        :param version:
            The version of the API presented on the swagger document.
        :param base_path:
            The path that all requests to the API must refer to.
        :param info:
            Swagger info field.
        :param swagger:
            Extra fields that should be provided on the swagger documentation.
        """

        info.update(title=title, version=version)
        swagger.update(swagger='2.0', info=info, basePath=base_path)

        paths, tags = self._build_paths(**kwargs)
        if paths:
            swagger['paths'] = paths
        if tags:
            swagger['tags'] = tags

        definitions = self.definitions.definition_registry
        if definitions:
            swagger['definitions'] = definitions

        parameters = self.parameters.parameter_registry
        if parameters:
            swagger['parameters'] = parameters

        responses = self.responses.response_registry
        if responses:
            swagger['responses'] = responses

        return swagger

    def _build_paths(self, ignore_methods=['head', 'options'], ignore_ctypes=[],
                     default_tags=None, **kwargs):
        """
        Build the Swagger "paths" and "tags" attributes from cornice service
        definitions.

        :param ignore_methods:
            List of service methods that should NOT be presented on the
            documentation. You may use this to remove methods that are not
            essential on the API documentation. Default ignores HEAD ans OPTIONS.
        :param ignore_ctypes:
            List of service content-types that should NOT be presented on
            the documentation. You may use this when a Cornice service definition
            has multiple view definitions for a same method, which is not supported
            on Swagger.
        :param default_tags:
            Provide a default list of tags or a callable that takes a cornice
            service and returns a list of Swagger tags to be used if a tag is
            not provided by the view.
        """

        paths = {}
        tags = []

        for service in self.services:
            path = self._extract_path_from_service(service, **kwargs)

            for method_name, view, args in service.definitions:

                if method_name.lower() in ignore_methods:
                    continue

                op = self._extract_operation_from_view(view, args, **kwargs)

                if any(ctype in op.get('consumes', []) for ctype in ignore_ctypes):
                    continue

                # XXX: Swagger doesn't support different schemas for for a same method
                # with different ctypes as cornice. If this happens, you may ignore one
                # content-type from the documentation otherwise we raise an Exception
                # Related to https://github.com/OAI/OpenAPI-Specification/issues/146
                previous_definition = path.get(method_name.lower())
                if previous_definition:
                    raise CorniceSwaggerException(("Swagger doesn't support multiple "
                                                   "views for a same method. You may "
                                                   "ignore one."))

                # If tag not defined and a default tag is provided
                if 'tags' not in op and default_tags:
                    if callable(default_tags):
                        op['tags'] = default_tags(service)
                    else:
                        op['tags'] = default_tags

                # Check if tags was correctly defined as a list
                if not isinstance(op.get('tags', []), list):
                    raise CorniceSwaggerException('Tags should be defined as a list.')

                # Add method tags to root tags
                for tag in op.get('tags', []):
                    if tag not in [t['name'] for t in tags]:
                        root_tag = {'name': tag}
                        tags.append(root_tag)

                path[method_name.lower()] = op
            paths[service.path] = path

        return paths, tags

    def _extract_path_from_service(self, service, **kwargs):
        """
        Extract path object and its parameters from service definitions.

        :param service:
            Cornice service to extract information from.

        :rtype: dict
            Path definition.
        """

        path = {}

        # Extract path parameters
        parameters = self.parameters.from_path(service.path)
        if parameters:
            path['parameters'] = parameters

        return path

    def _extract_operation_from_view(self, view, args={},
                                     summary_docstrings=False, **kwargs):
        """
        Extract swagger operation details from colander view definitions.

        :param view:
            View to extract information from.
        :param args:
            Arguments from the view decorator.
        :param summary_docstrings:
            Enable extracting operation summaries from view docstrings.

        :rtype: dict
            Operation definition.
        """

        op = {
            'responses': {
                'default': {
                    'description': 'UNDOCUMENTED RESPONSE'
                }
            },
        }

        # If 'produces' are not defined in the view, try get from renderers
        renderer = args.get('renderer', '')

        if "json" in renderer:  # allows for "json" or "simplejson"
            produces = ['application/json']
        elif renderer == 'xml':
            produces = ['text/xml']
        else:
            produces = None

        if produces:
            op.setdefault('produces', produces)

        # Get explicit accepted content-types
        consumes = args.get('content_type')

        # A single content-type is provided, so wrap it in a list
        if isinstance(consumes, six.string_types):
            consumes = [consumes]

        if consumes:
            op['consumes'] = list(consumes)

        # Get parameters from view schema
        if 'schema' in args:
            validators = args.get('validators')
            parameters = self.parameters.from_schema(args['schema'], validators)
            if parameters:
                op['parameters'] = parameters

        # Get summary from docstring
        if isinstance(view, six.string_types):
            if 'klass' in args:
                ob = args['klass']
                view_ = getattr(ob, view.lower())
                docstring = cornice_swagger.util.trim(view_.__doc__)
        else:
            docstring = cornice_swagger.util.trim(view.__doc__)

        if docstring and summary_docstrings:
            op['summary'] = docstring

        # Get response definitions
        if 'response_schemas' in args:
            op['responses'] = self.responses.from_schema_mapping(args['response_schemas'])

        # Get response tags
        if 'tags' in args:
            op['tags'] = args['tags']

        return op

class DefinitionHandler(object):
    """Handles Swagger object definitions provided by cornice as colander schemas."""

    json_pointer = '#/definitions/'

    def __init__(self, ref=0):
        """
        :param ref:
            The depth that should be used by self.ref when calling self.from_schema.
        """

        self.definition_registry = {}
        self.ref = ref

    def from_schema(self, schema_node, base_name=None):
        """
        Creates a Swagger definition from a colander schema.

        :param schema_node:
            Colander schema to be transformed into a Swagger definition.
        :param base_name:
            Schema alternative title.

        :rtype: dict
            Swagger schema.
        """
        return self._ref_recursive(convert_schema(schema_node), self.ref, base_name)

    def _ref_recursive(self, schema, depth, base_name=None):
        """
        Dismantle nested swagger schemas into several definitions using JSON pointers.
        Note: This can be dangerous since definition titles must be unique.

        :param schema:
            Base swagger schema.
        :param depth:
            How many levels of the swagger object schemas should be split into
            swaggger definitions with JSON pointers. Default (0) is no split.
            You may use negative values to split everything.
        :param base_name:
            If schema doesn't have a name, the caller may provide it to be
            used as reference.

        :rtype: dict
            JSON pointer to the root definition schema,
            or the original definition if depth is zero.
        """

        if depth == 0:
            return schema

        if schema['type'] != 'object':
            return schema

        name = base_name or schema['title']

        pointer = self.json_pointer + name
        for child_name, child in schema.get('properties', {}).items():
            schema['properties'][child_name] = self._ref_recursive(child, depth-1)

        self.definition_registry[name] = schema

        return {'$ref': pointer}

class ParameterHandler(object):
    """Handles swagger parameter definitions."""

    json_pointer = '#/parameters/'

    def __init__(self, definition_handler=DefinitionHandler(), ref=False):
        """
        :param definition_handler:
            Callable that handles swagger definition schemas.
        :param ref:
            Specifies the ref value when calling from_xxx methods.
        """

        self.parameter_registry = {}

        self.definitions = definition_handler
        self.ref = ref

    def from_schema(self, schema_node, validators=[]):
        """
        Creates a list of Swagger params from a colander request schema.

        :param schema_node:
            Request schema to be transformed into Swagger.
        :param validators:
            Validators used in colander with the schema.
        :rtype: list
            List of Swagger parameters.
        """

        params = []

        if not isinstance(schema_node, colander.Schema):
            schema_node = schema_node()

        if colander_validator in validators:
            for param_schema in schema_node.children:
                location = param_schema.name
                if location is 'body':
                    name = param_schema.__class__.__name__
                    param = convert_parameter(location,
                                              param_schema,
                                              self.definitions.from_schema)
                    param['name'] = name
                    if self.ref:
                        param = self._ref(param)
                    params.append(param)

                elif location in ('path', 'headers', 'querystring'):
                    for node_schema in param_schema.children:
                        param = convert_parameter(location,
                                                  node_schema,
                                                  self.definitions.from_schema)
                        if self.ref:
                            param = self._ref(param)
                        params.append(param)

        elif colander_body_validator in validators:
            name = schema_node.__class__.__name__
            param = convert_parameter('body', schema_node)
            param['name'] = name
            if self.ref:
                param = self._ref(param, schema_node.__class__.__name__)
            params.append(param)

        return params

    def from_path(self, path):
        """
        Create a list of Swagger path params from a cornice service path.

        :type path: string
        :rtype: list
        """

        param_names = re.findall('\{(.*?)\}', path)
        params = []
        for name in param_names:
            param_schema = colander.SchemaNode(colander.String(), name=name)
            param = convert_parameter('path', param_schema)
            if self.ref:
                param = self._ref(param)
            params.append(param)

        return params

    def _ref(self, param, base_name=None):
        """
        Store a parameter schema and return a reference to it.

        :param schema:
            Swagger parameter definition.
        :param base_name:
            Name that should be used for the reference.

        :rtype: dict
            JSON pointer to the original parameter definition.
        """

        name = base_name or param.get('title', '') or param.get('name', '')

        pointer = self.json_pointer + name
        self.parameter_registry[name] = param

        return {'$ref': pointer}

class ResponseHandler(object):
    """Handles swagger response definitions."""

    json_pointer = '#/responses/'

    def __init__(self, definition_handler=DefinitionHandler(), ref=False):
        """
        :param definition_handler:
            Callable that handles swagger definition schemas.
        :param ref:
            Specifies the ref value when calling from_xxx methods.
        """

        self.response_registry = {}

        self.definitions = definition_handler
        self.ref = ref

    def from_schema_mapping(self, schema_mapping):
        """
        Creates a Swagger response object from a dict of response schemas.

        :param schema_mapping:
            Dict with entries matching ``{status_code: response_schema}``.
        :rtype: dict
            Response schema.
        """

        responses = {}

        for status, response_schema in schema_mapping.items():

            response = {}
            if response_schema.description:
                response['description'] = response_schema.description
            else:
                raise CorniceSwaggerException('Responses must have a description.')

            for field_schema in response_schema.children:
                location = field_schema.name
                if location == 'body':
                    field_schema.title = field_schema.__class__.__name__
                    response['schema'] = self.definitions.from_schema(field_schema)
                elif location == 'header':
                    headers = convert_schema(field_schema)
                    if 'properties' in headers:
                        response['headers'] = headers['properties']

            pointer = response_schema.__class__.__name__
            if self.ref:
                response = self._ref(response, pointer)
            responses[status] = response

        return responses

    def _ref(self, resp, base_name=None):
        """
        Store a response schema and return a reference to it.

        :param schema:
            Swagger response definition.
        :param base_name:
            Name that should be used for the reference.

        :rtype: dict
            JSON pointer to the original response definition.
        """

        name = base_name or resp.get('title', '') or resp.get('name', '')

        pointer = self.json_pointer + name
        self.response_registry[name] = resp

        return {'$ref': pointer}

def generate_swagger_spec(services, title, version, **kwargs):
    """Utility to turn cornice web services into a Swagger-readable file.
    """

    def get_tags_from_path(service):
        return [service.path.split("/")[1]]

    swag = CorniceSwagger(services, def_ref_depth=-1, param_ref=0)
    doc = swag(title, version, ignores=('head'),
               default_tags=get_tags_from_path,
               summary_docstrings=True, **kwargs)
    doc.update(**kwargs)

    return doc
<|endoftext|>


<commit_before>#!/usr/bin/env python
#
# Copyright 2013 (c) Lei Xu <eddyxu@gmail.com>

import coverage

def test_exclude_pattern():
    """Test using regular expression for exclusion rules.
    """
    args = coverage.create_args('-E /abc.*\.txt'.split())
    assert coverage.is_excluded_path(args, '/abcd.txt')
    assert not coverage.is_excluded_path(args, '/cd.txt')
<commit_msg> * Use relative import for python 3<commit_after>#!/usr/bin/env python
#
# Copyright 2013 (c) Lei Xu <eddyxu@gmail.com>

from . import coverage

def test_exclude_pattern():
    """Test using regular expression for exclusion rules.
    """
    args = coverage.create_args('-E /abc.*\.txt'.split())
    assert coverage.is_excluded_path(args, '/abcd.txt')
    assert not coverage.is_excluded_path(args, '/cd.txt')
<|endoftext|>


<commit_before>#Wall_Follow.py

class WallFollow(self):
	## Initialize Wall Following
	#
    # @param left   A Motor representing the left motor.
    # @param right  A Motor representing the right motor.
    # @param timer  A Timer for moderating data taking.
	def __init__(self,left,right,timer):
		self.leftmotor = left
		self.rightmotor = right
		self.timer = timer
		self.timer.reset()

		# Number of values to record
        self.recordLen = 10
        # Record of values from youngest to oldest.
        self.record = []

        # Tweak values as needed
        self.kp = 1.0
        self.ki = 0.1
        self.kd = 0.5

	# Make module to give theta from LRIR inputs
	# TODO

	## Given value from LRIR sensors, makes bot move forward
    #
    # @param theta A number proportional to the angle between the wall and
    # 				where the bot is facing
    # @param speed  A value from -255 to 255 that corresponds to the general
    #               speed of the robot.
	def Follow_Wall(self, theta, speed = 0):
		if self.timer.millis() > 100:
			self.timer.reset(0)

			# error value
            err = 0 - theta

            # Integrate over the last several timesteps.
            self.record.insert(0, err)
            if len(self.record) > self.recordLen:
                self.record.pop()

            # Take the derivative over recorded history.
            deriv = self.record[0] - self.record[-1] if len(self.record) > 1 else 0

            power = self.kp * err + self.ki * sum(self.record) + kd * deriv

            self.leftMotor.write ((speed - power) > 0, min(abs(speed - power), 255))
            self.rightMotor.write((speed + power) > 0, min(abs(speed + power), 255))

    ## Reinitialize this class to start taking data over.
    def reset(self):
        self.timer.reset()
        self.record = []

<commit_msg>made wall follow simpler<commit_after>#Wall_Follow.py

class WallFollow():

    ## Initialize a WallFollow object.
    #
    # @param left   A Motor representing the left motor.
    # @param right  A Motor representing the right motor.
    # @param timer  A Timer for moderating data taking.
    def __init__(self, left, right, timer):
        self.leftMotor = left
        self.rightMotor = right
        self.timer = timer
        self.timer.reset()

        # Number of values to record
        self.recordLen = 10
        # Record of values from youngest to oldest.
        self.record = []

        # Tweak values as needed
        self.kp = 1.0
        self.ki = 0.1
        self.kd = 0.5

    ## Given a distance value from LRIR0 make bot move to be 14 cm from wall.
    #
    # @param distance a number proportional to the distance between the LRIR sensor and a wall
    #
    # @param speed  A value from -255 to 255 that corresponds to the general
    #               speed of the robot.

    def move_to_target(self, distance, speed = 0):
        if self.timer.millis() > 100:
            self.timer.reset()

            # error value
            # 20 from hypotenuse of 45,45,90 triangle with 14.14 long sides
            err = 20 - distance

            # Integrate over the last several timesteps.
            self.record.insert(0, err)
            if len(self.record) > self.recordLen:
                self.record.pop()

            # Take the derivative over recorded history.
            deriv = self.record[0] - self.record[-1] if len(self.record) > 1 else 0

            power = self.kp * err + self.ki * sum(self.record) + kd * deriv

            self.leftMotor.write ((speed - power) > 0, min(abs(speed - power), 255))
            self.rightMotor.write((speed + power) > 0, min(abs(speed + power), 255))

    ## Reinitialize this class to start taking data over.
    def reset(self):
        self.timer.reset()
        self.record = []

<|endoftext|>


<commit_before>"""
Panel SEs (:mod:`structMan.ses.panel`)
======================================

.. currentmodule:: structMan.ses.panel

"""
import numpy as np

from ses import SE2D

from structMan.sol200 import (DRESP1, DCONSTR, DEQATN, DRESP2, DESVAR,
                              DVPREL1, DVPREL2)

import structMan.sol200.output_codes as output_codes_SOL200

class Panel(SE2D):
    """Panel

    Attributes
    ----------

    """
    def __init__(self, name, eids, model=None):
        super(Panel, self).__init__(name, eids, model)
        # geometric parameters
        self.radius1 = None
        self.radius2 = None
        self.a = None
        self.b = None
        self.t = None
        self.xaxis = 'stringer'
        # material properties
        # ...
        self.is_isotropic = True
        # optimization constraints
        self.all_constraints = ['vonMises']
        self.constraints = {'vonMises': 1}

        # finding corner nodes
        # - assuming that they are those that share only one inner element
        # - radius calculated assuming the panel has a common center
        if self.elements is not None:
            nodes = []
            for element in self.elements:
                for node in element.nodes:
                    nodes.append(node)
            self.nodes = set(nodes)
            ccoords = np.array([n.xyz for n in self.nodes])
            xs = ccoords[:, 0]
            ys = ccoords[:, 1]
            zs = ccoords[:, 2]
            rs = (ys**2 + zs**2)**0.5
            thetas = np.arctan2(zs, ys)
            self.a = xs.max() - xs.min()
            self.b = (thetas.max() - thetas.min())*rs.mean()

            # retrieving panel thickness and material properties
            self.t = self.elements[0].pid.t

    def create_dvars(self):
        if self.dvars_created:
            return
        self.dvars_created = True
        pid = self.pid
        ptype = self.ptype

        if ptype == 'PSHELL':
            self.add_dtable('PANE', self.E)
            self.add_dtable('PANnu', self.nu)
            dvar_t = DESVAR('PANt', self.t, self.t_lb, self.t_ub)
            self.add_dvar(dvar_t)
            dvprel = DVPREL1('PSHELL', pid=pid, pname='T', dvids=[dvar_t.id],
                             coeffs=[1.])
            self.add_dvprel(dvprel)
        else:
            raise NotImplementedError('%s not supported!' % ptype)

    def constrain_vonMises(self, Fcy, average=False):
        """Add a von Mises stress constraint

        Parameters
        ----------
        Fcy : float
            The stress threshold that will be compared to the von Mises stress
            for this constraint.
        average : bool, optional
            If False the center element is chosen, otherwise ...
            #TODO not implemented

        """
        self.create_dvars()
        eid = self.get_central_element().eid

        dcid = self.constraints['vonMises']
        OUTC = output_codes_SOL200.OUTC

        atta = OUTC['STRESS']['CQUAD4']['von Mises or maximum shear at Z1']
        dresp1 = DRESP1('PANZ1VM', 'STRESS', 'ELEM', None, atta=atta,
                        attb=None, atti=eid)
        self.add_dresp(dresp1)
        self.add_constraint(dcid, dresp1, None, Fcy)

        atta = OUTC['STRESS']['CQUAD4']['von Mises or maximum shear at Z2']
        dresp1 = DRESP1('PANZ2VM', 'STRESS', 'ELEM', None, atta=atta,
                        attb=None, atti=eid)
        self.add_dresp(dresp1)
        self.add_constraint(dcid, dresp1, None, Fcy)

<commit_msg>DEV: added buckling constraint option<commit_after>"""
Panel SEs (:mod:`structMan.ses.panel`)
======================================

.. currentmodule:: structMan.ses.panel

"""
import numpy as np

from ses import SE2D

from structMan.sol200 import (DRESP1, DCONSTR, DEQATN, DRESP2, DRESP3, DESVAR,
                              DVPREL1, DVPREL2)

import structMan.sol200.output_codes as output_codes_SOL200

class Panel(SE2D):
    """Panel

    This class should be used for cylindrical panels (i.e. fuselage panels).
    For plates please refer to :class:`.Plate`.

    For wing panels this class should also be adopted.

    Attributes
    ----------

    """
    def __init__(self, name, eids, model=None):
        super(Panel, self).__init__(name, eids, model)
        # geometric parameters
        self.radius1 = None
        self.radius2 = None
        self.a = None
        self.b = None
        self.t = None
        self.xaxis = 'stringer'
        # material properties
        # ...
        self.is_isotropic = True
        # optimization constraints
        self.all_constraints = ['vonMises', 'buckling']
        self.constraints = {'vonMises': 1,
                            'buckling': 1}

        # finding corner nodes
        # - assuming that they are those that share only one inner element
        # - radius calculated assuming the panel has a common center
        if self.elements is not None:
            nodes = []
            for element in self.elements:
                for node in element.nodes:
                    nodes.append(node)
            self.nodes = set(nodes)
            ccoords = np.array([n.xyz for n in self.nodes])
            xs = ccoords[:, 0]
            ys = ccoords[:, 1]
            zs = ccoords[:, 2]
            rs = (ys**2 + zs**2)**0.5
            thetas = np.arctan2(zs, ys)
            self.r = rs.mean()
            self.a = xs.max() - xs.min()
            self.b = (thetas.max() - thetas.min())*self.r

            # retrieving panel thickness and material properties
            self.t = self.elements[0].pid.t

    def create_dvars(self):
        if self.dvars_created:
            return
        self.dvars_created = True
        pid = self.pid
        ptype = self.ptype

        if ptype == 'PSHELL':
            dvar_t = DESVAR('PANt', self.t, self.t_lb, self.t_ub)
            self.add_dvar(dvar_t)
            dvprel = DVPREL1('PSHELL', pid=pid, pname='T', dvids=[dvar_t.id],
                             coeffs=[1.])
            self.add_dvprel(dvprel)
            self.add_dtable('PANr', self.r)
            self.add_dtable('PANa', self.a)
            self.add_dtable('PANb', self.b)
            self.add_dtable('PANE', self.E)
            self.add_dtable('PANnu', self.nu)
        else:
            raise NotImplementedError('%s not supported!' % ptype)

    def constrain_vonMises(self, Fcy, average=False):
        """Add a von Mises stress constraint

        Parameters
        ----------
        Fcy : float
            The stress threshold that will be compared to the von Mises stress
            for this constraint.
        average : bool, optional
            If False the center element is chosen, otherwise ...
            #TODO not implemented

        """
        self.create_dvars()
        eid = self.get_central_element().eid

        dcid = self.constraints['vonMises']
        OUTC = output_codes_SOL200.OUTC

        atta = OUTC['STRESS']['CQUAD4']['von Mises or maximum shear at Z1']
        dresp1 = DRESP1('PANZ1VM', 'STRESS', 'ELEM', None, atta=atta,
                        attb=None, atti=eid)
        self.add_dresp(dresp1)
        self.add_constraint(dcid, dresp1, None, Fcy)

        atta = OUTC['STRESS']['CQUAD4']['von Mises or maximum shear at Z2']
        dresp1 = DRESP1('PANZ2VM', 'STRESS', 'ELEM', None, atta=atta,
                        attb=None, atti=eid)
        self.add_dresp(dresp1)
        self.add_constraint(dcid, dresp1, None, Fcy)

    def constrain_buckling(self, method=1, ms=0.1):
        """Add a buckling constraint

        Parameters
        ----------
        method : int, optional
            Select one of the following methods for buckling calculation:

            - `1` : Bruhn's method using Equation C9.4:
                    - considers compressive and shear loads
                    - no plasticity correction has been implemented

        ms : float, optional
            Minimum margin of safety to be used as constraint.

        """
        OUTC = output_codes_SOL200.OUTC

        eid = self.get_central_element().eid

        # reading membrane force Nxx
        code_Nxx = OUTC['FORCE']['CQUAD4']['Membrane force x']
        dresp_Nxx = DRESP1('PANfNxx', 'FORCE', 'ELEM', region=None,
                           atta=code_Nxx, attb=None, atti=eid)
        self.add_dresp(dresp_Nxx)
        # reading membrane force Nxy
        code_Nxy = OUTC['FORCE']['CQUAD4']['Membrane force xy']
        dresp_Nxy = DRESP1('PANfNxy', 'FORCE', 'ELEM', region=None,
                           atta=code_Nxy, attb=None, atti=eid)
        self.add_dresp(dresp_Nxy)
        # calculating the margin of safety using an external subroutine
        dresp = DRESP3('PANBUCK1', 'PANBUCK', 'METHOD1')
        dresp.add_dvar(self.dvars['PANt'].id)
        dresp.add_dtable(self.dtables['PANr'][0])
        dresp.add_dtable(self.dtables['PANa'][0])
        dresp.add_dtable(self.dtables['PANb'][0])
        dresp.add_dtable(self.dtables['PANE'][0])
        dresp.add_dtable(self.dtables['PANnu'][0])
        dresp.add_dresp2(dresp_Nxx.id)
        dresp.add_dresp2(dresp_Nxy.id)
        self.add_dresp(dresp)

<|endoftext|>


<commit_before>__author__ = 'adria'

#!/usr/bin/python

import os.path

class DataBase:
    def __init__(self, rutaUsers="usuaris.txt", rutaComptes="comptes.txt"):
        self.rutaUsers = rutaUsers
        self.rutaComptes = rutaComptes

    def creaUsers(self):
        """Creates users file"""
        if self.comprovaUsers():
            print("Users' file already exists!")
        else:
            open(self.rutaUsers, 'w').close()

    def creaComptes(self):
        """Creates accounts file"""
        if self.comprovaComptes():
            print("Accounts file already exists!")
        else:
            open(self.rutaComptes, 'w').close()

    def comprovaUsers(self):
        """Comprova que existeixi el fitxer on es guarden els usuaris"""
        result = False
        if os.path.isfile(self.rutaUsers):
            result = True
        return result

    def comprovaComptes(self):
        """Comprova que existeixi el fitxer on es guarden les comptes bancaries"""
        result = False
        if os.path.isfile(self.rutaComptes):
            result = True
        return result

    def llegeixUsers(self):
        """Llegeix tot el fitxer d'usuaris, els retorna en una string"""
        resultat = ""
        if self.comprovaUsers():
            with open(self.rutaUsers, 'r') as f:
                resultat = f.read()
        else:
            print("Error! no s'ha trobat el fitxer d'usuaris")
            #llençar una excepcio
        return resultat

    def llistaUsers(self):
        """Agafa els usuaris (amb el seu dni, nom i cognoms) i els retorna en una llista"""
        llista = []
        text = self.llegeixUsers()
        llistacomes = text.split("\n")
        for linia in llistacomes:
            llista.append(linia.split(","))
        llista.pop()
        #fer alguna cosa si no troba usuaris?
        return llista

    def llegeixComptes(self):
        """Llegeix tot el fitxer de comptes"""
        resultat = ""
        if self.comprovaComptes():
            with open(self.rutaComptes, 'r') as f:
                resultat = f.read()
        else:
            print("Error! no s'ha trobat el fitxer de comptes")
            #llençar una excepcio
        return resultat

    def afegeixUsuari(self, dni, nombre, apellidos):
        """Afegeix un usuari al fitxer d'usuaris sense comprovar que existeixi"""
        added = False
        if self.comprovaUsers():
            with open(self.rutaUsers, 'a') as f:
                f.write(dni+","+nombre+","+apellidos+"\n")
                added = True
        else:
            print("Error! no s'ha trobat el fitxer d'usuaris")
        return added

    def esborraUsuari(self, dni):
        """Esborra un usuari al fitxer d'usuaris sense comprovar que existeixi"""
        if self.comprovaUsers():
            file = open(self.rutaUsers, 'r')
            llista = file.readlines()
            file.close()
            trobat = False
            with open(self.rutaUsers, 'w') as file:
                for linia in llista:
                    print(linia.split(",")[0])
                    print("---")
                    if linia.split(",")[0] != dni:
                        file.write(linia)
                    else:
                        trobat = True
            if (not trobat):
                print("No s'ha trobat l'usuari!")
                #error
        else:
            print("Error! no s'ha trobat el fitxer<commit_msg>petits canvis en dataBase<commit_after>__author__ = 'adria'

#!/usr/bin/python

import os.path

class DataBase:
    def __init__(self, rutaUsers="../database/usuaris.txt", rutaComptes="../database/comptes.txt"):
        self.rutaUsers = rutaUsers
        self.rutaComptes = rutaComptes

    def creaUsers(self):
        """Creates users file"""
        if self.comprovaUsers():
            print("Users' file already exists!")
        else:
            open(self.rutaUsers, 'w').close()

    def creaComptes(self):
        """Creates accounts file"""
        if self.comprovaComptes():
            print("Accounts file already exists!")
        else:
            open(self.rutaComptes, 'w').close()

    def comprovaUsers(self):
        """Comprova que existeixi el fitxer on es guarden els usuaris"""
        result = False
        if os.path.isfile(self.rutaUsers):
            result = True
        return result

    def comprovaComptes(self):
        """Comprova que existeixi el fitxer on es guarden les comptes bancaries"""
        result = False
        if os.path.isfile(self.rutaComptes):
            result = True
        return result

    def llegeixUsers(self):
        """Llegeix tot el fitxer d'usuaris, els retorna en una string"""
        resultat = ""
        if self.comprovaUsers():
            with open(self.rutaUsers, 'r') as f:
                resultat = f.read()
        else:
            print("Error! no s'ha trobat el fitxer d'usuaris")
            #llençar una excepcio
        return resultat

    def llistaUsers(self):
        """Agafa els usuaris (amb el seu dni, nom i cognoms) i els retorna en una llista"""
        llista = []
        text = self.llegeixUsers()
        llistacomes = text.split("\n")
        for linia in llistacomes:
            llista.append(linia.split(","))
        llista.pop()
        #fer alguna cosa si no troba usuaris?
        return llista

    def llegeixComptes(self):
        """Llegeix tot el fitxer de comptes"""
        resultat = ""
        if self.comprovaComptes():
            with open(self.rutaComptes, 'r') as f:
                resultat = f.read()
        else:
            print("Error! no s'ha trobat el fitxer de comptes")
            #llençar una excepcio
        return resultat

    def afegeixUsuari(self, dni, nombre, apellidos):
        """Afegeix un usuari al fitxer d'usuaris sense comprovar que existeixi"""
        added = False
        if self.comprovaUsers():
            with open(self.rutaUsers, 'a') as f:
                f.write(dni+","+nombre+","+apellidos+"\n")
                added = True
        else:
            print("Error! no s'ha trobat el fitxer d'usuaris")
        return added

    def esborraUsuari(self, dni):
        """Esborra un usuari al fitxer d'usuaris sense comprovar que existeixi"""
        if self.comprovaUsers():
            file = open(self.rutaUsers, 'r')
            llista = file.readlines()
            file.close()
            trobat = False
            with open(self.rutaUsers, 'w') as file:
                for linia in llista:
                    print(linia.split(",")[0])
                    print("---")
                    if linia.split(",")[0] != dni:
                        file.write(linia)
                    else:
                        trobat = True
            if (not trobat):
                print("No s'ha trobat l'usuari!")
                #error
        else:
            print("Error! no s'ha trobat el fitxer")<|endoftext|>


<commit_before>import pygame

class Player(pygame.sprite.Sprite):
    SPEED = 300
    def __init__(self, *groups):
        super().__init__(*groups)
        self.image = pygame.image.load('frog.gif')
        self.rect = pygame.rect.Rect((320, 240), self.image.get_size())

    def update(self, dt):
        key = pygame.key.get_pressed()
        if key[pygame.K_LEFT]:
            self.rect.x -= self.SPEED * dt
        if key[pygame.K_RIGHT]:
            self.rect.x += self.SPEED * dt
        if key[pygame.K_UP]:
            self.rect.y -= self.SPEED * dt
        if key[pygame.K_DOWN]:
            self.rect.y += self.SPEED * dt

class Game():
    def main(self, screen):
        clock = pygame.time.Clock()

        background = pygame.image.load('Castle.gif')
        background = pygame.transform.scale(background, (640, 480))
        sprites = pygame.sprite.Group()
        self.player = Player(sprites)
        while True:
            dt = clock.tick(30) / 1000

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    return

            sprites.update(dt)
            screen.blit(background, (0,0))
            sprites.draw(screen)
            pygame.display.flip()

if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode((640, 480))
    game = Game()
    game.main(screen)<commit_msg>Make the screen size a constant.<commit_after>import pygame

SCREEN_SIZE = (640, 480)

class Player(pygame.sprite.Sprite):
    SPEED = 300
    def __init__(self, *groups):
        super().__init__(*groups)
        self.image = pygame.image.load('frog.gif')
        self.rect = pygame.rect.Rect((320, 240), self.image.get_size())

    def update(self, dt):
        key = pygame.key.get_pressed()
        if key[pygame.K_LEFT]:
            self.rect.x -= self.SPEED * dt
        if key[pygame.K_RIGHT]:
            self.rect.x += self.SPEED * dt
        if key[pygame.K_UP]:
            self.rect.y -= self.SPEED * dt
        if key[pygame.K_DOWN]:
            self.rect.y += self.SPEED * dt

class Game():
    def main(self, screen):
        clock = pygame.time.Clock()

        background = pygame.image.load('Castle.gif')
        background = pygame.transform.scale(background, SCREEN_SIZE)
        sprites = pygame.sprite.Group()
        self.player = Player(sprites)
        while True:
            dt = clock.tick(30) / 1000

            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    return
                if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
                    return

            sprites.update(dt)
            screen.blit(background, (0,0))
            sprites.draw(screen)
            pygame.display.flip()

if __name__ == '__main__':
    pygame.init()
    screen = pygame.display.set_mode(SCREEN_SIZE)
    game = Game()
    game.main(screen)<|endoftext|>


<commit_before>"""Small utilities."""

from collections.abc import Sequence

from pyspark import SparkContext
from sympy import (sympify, Symbol, Expr, SympifyError, count_ops,
                   default_sort_key)

#
# SymPy utilities
# ---------------
#

def ensure_sympify(obj, role='', expected_type=None):
    """Sympify the given object with checking and error reporting.

    This is a shallow wrapper over SymPy sympify function to have error
    reporting in consistent style and an optional type checking.
    """

    header = 'Invalid {}: '.format(role)

    try:
        sympified = sympify(obj)
    except SympifyError as exc:
        raise TypeError(header, obj, 'failed to be simpified', exc.args)

    if expected_type is None or isinstance(sympified, expected_type):
        return sympified
    else:
        raise TypeError(header, sympified, 'expecting', expected_type)

def ensure_symb(obj, role=''):
    """Sympify the given object into a symbol."""
    return ensure_sympify(obj, role, Symbol)

def ensure_expr(obj, role=''):
    """Sympify the given object into an expression."""
    return ensure_sympify(obj, role, Expr)

def sympy_key(expr):
    """Get the key for ordering SymPy expressions.

    This function assumes that the given expression is already sympified.
    """

    return count_ops(expr), default_sort_key(expr)

def is_higher(obj, priority):
    """Test if the object has higher operation priority.

    When the given object does not have defined priority, it is considered
    lower.
    """

    return getattr(obj, '_op_priority', priority - 1) > priority

#
# Spark utilities
# ---------------
#

class BCastVar:
    """Automatically broadcast variables.

    This class is a shallow encapsulation of a variable and its broadcast
    into the spark context.  The variable can be redistributed automatically
    after any change.

    """

    __slots__ = [
        '_ctx',
        '_var',
        '_bcast'
    ]

    def __init__(self, ctx: SparkContext, var):
        """Initialize the broadcast variable."""
        self._ctx = ctx
        self._var = var
        self._bcast = None

    @property
    def var(self):
        """Get the variable to mutate."""
        self._bcast = None
        return self._var

    @property
    def ro(self):
        """Get the variable, read-only.

        Note that this function only prevents the redistribution of the
        variable.  It cannot force the variable not be mutated.
        """
        return self._var

    @property
    def bcast(self):
        """Get the broadcast variable."""
        if self._bcast is None:
            self._bcast = self._ctx.broadcast(self._var)
        return self._bcast

#
# Misc utilities
# --------------
#

def ensure_pair(obj, role):
    """Ensures that the given object is a pair."""
    if not (isinstance(obj, Sequence) and len(obj) == 2):
        raise TypeError('Invalid {}: '.format(role), obj, 'expecting pair')
    return obj
<commit_msg>Add class and metaclass for concrete symbols<commit_after>"""Small utilities."""

from collections.abc import Sequence

from pyspark import SparkContext
from sympy import (
    sympify, Symbol, Expr, SympifyError, count_ops,
    default_sort_key, AtomicExpr, Integer, S
)
from sympy.core.assumptions import ManagedProperties

#
# SymPy utilities
# ---------------
#

def ensure_sympify(obj, role='', expected_type=None):
    """Sympify the given object with checking and error reporting.

    This is a shallow wrapper over SymPy sympify function to have error
    reporting in consistent style and an optional type checking.
    """

    header = 'Invalid {}: '.format(role)

    try:
        sympified = sympify(obj)
    except SympifyError as exc:
        raise TypeError(header, obj, 'failed to be simpified', exc.args)

    if expected_type is None or isinstance(sympified, expected_type):
        return sympified
    else:
        raise TypeError(header, sympified, 'expecting', expected_type)

def ensure_symb(obj, role=''):
    """Sympify the given object into a symbol."""
    return ensure_sympify(obj, role, Symbol)

def ensure_expr(obj, role=''):
    """Sympify the given object into an expression."""
    return ensure_sympify(obj, role, Expr)

def sympy_key(expr):
    """Get the key for ordering SymPy expressions.

    This function assumes that the given expression is already sympified.
    """

    return count_ops(expr), default_sort_key(expr)

def is_higher(obj, priority):
    """Test if the object has higher operation priority.

    When the given object does not have defined priority, it is considered
    lower.
    """

    return getattr(obj, '_op_priority', priority - 1) > priority

class _ConcrSymbsMeta(ManagedProperties):
    """The meta class for concrete symbols.

    The primary purpose of this metaclass is to set the concrete singleton
    values from the symbols set in the class body.
    """

    SYMBS_INPUT = '_symbs_'

    def __new__(mcs, name, bases, attrs):
        """Create the new concrete symbols class."""

        cls = super().__new__(mcs, name, bases, attrs)

        if not hasattr(cls, mcs.SYMBS_INPUT):
            raise AttributeError('Cannot find attribute ' + mcs.SYMBS_INPUT)

        symbs = getattr(cls, mcs.SYMBS_INPUT)
        if symbs is None:
            # Base class.
            return cls

        if not isinstance(symbs, Sequence):
            raise ValueError('Invalid symbols', symbs, 'expecting a sequence')
        for i in symbs:
            invalid = not isinstance(i, Sequence) or len(i) != 2 or any(
                not isinstance(j, str) for j in i
            )
            if invalid:
                raise ValueError(
                    'Invalid symbol', i,
                    'expecting pairs of identifier and LaTeX form.'
                )
        if len(symbs) < 2:
            raise ValueError(
                'Invalid symbols ', symbs, 'expecting multiple of them'
            )

        for i, v in enumerate(symbs):
            obj = cls(i)
            setattr(cls, v[0], obj)
            continue

        return cls

class ConcrSymbs(AtomicExpr, metaclass=_ConcrSymbsMeta):
    """Base class for concrete symbols.

    Subclasses can set `_symbs_` inside the class body to be a sequence of
    string pairs.  Then attributes named after the first field of the pairs will
    be created, with the LaTeX form controlled by the second pair.

    The resulted values are valid SymPy expressions.  They are ordered according
    to their order in the given sequence.

    """

    _symbs_ = None

    _VAL_FIELD = '_val_index'
    __slots__ = [_VAL_FIELD]

    def __init__(self, val_index):
        """Initialize the concrete symbol object.
        """
        if self._symbs_ is None:
            raise ValueError('Base ConcrSymbs class cannot be instantiated')
        setattr(self, self._VAL_FIELD, val_index)

    @property
    def args(self):
        """The argument for SymPy."""
        return Integer(getattr(self, self._VAL_FIELD)),

    def __str__(self):
        """Get the string representation of the symbol."""
        return self._symbs_[getattr(self, self._VAL_FIELD)][0]

    def __repr__(self):
        """Get the machine readable string representation."""
        return '.'.join([type(self).__name__, str(self)])

    _op_priority = 20.0

    def __eq__(self, other):
        """Test two values for equality."""
        return isinstance(other, type(self)) and self.args == other.args

    def __hash__(self):
        """Hash the concrete symbol object."""
        return hash(repr(self))

    def __lt__(self, other):
        """Test two values for less than order.

        The order will be based on the order given in the class.
        """
        return self.args < other.args

    def __gt__(self, other):
        """Test two values for greater than."""
        return self.args > other.args

    def __sub__(self, other):
        """Subtract the current value with another.

        This method is mainly to be able to work together with the Kronecker
        delta class from SymPy.
        """

        if not isinstance(other, type(self)):
            raise ValueError(
                'Invalid operation for ', (self, other),
                'concrete symbols can only be subtracted for the same type'
            )

        return self.args[0] - other.args[0]

    def sort_key(self, order=None):
        return (
            self.class_key(),
            (1, tuple(i.sort_key() for i in self.args)),
            S.One.sort_key(), S.One
        )

    def _latex(self, _):
        """Print itself as LaTeX code."""
        return self._symbs_[self.args[0]][1]

#
# Spark utilities
# ---------------
#

class BCastVar:
    """Automatically broadcast variables.

    This class is a shallow encapsulation of a variable and its broadcast
    into the spark context.  The variable can be redistributed automatically
    after any change.

    """

    __slots__ = [
        '_ctx',
        '_var',
        '_bcast'
    ]

    def __init__(self, ctx: SparkContext, var):
        """Initialize the broadcast variable."""
        self._ctx = ctx
        self._var = var
        self._bcast = None

    @property
    def var(self):
        """Get the variable to mutate."""
        self._bcast = None
        return self._var

    @property
    def ro(self):
        """Get the variable, read-only.

        Note that this function only prevents the redistribution of the
        variable.  It cannot force the variable not be mutated.
        """
        return self._var

    @property
    def bcast(self):
        """Get the broadcast variable."""
        if self._bcast is None:
            self._bcast = self._ctx.broadcast(self._var)
        return self._bcast

#
# Misc utilities
# --------------
#

def ensure_pair(obj, role):
    """Ensures that the given object is a pair."""
    if not (isinstance(obj, Sequence) and len(obj) == 2):
        raise TypeError('Invalid {}: '.format(role), obj, 'expecting pair')
    return obj
<|endoftext|>


<commit_before># Copyright 2011-2013 Colin Scott
# Copyright 2011-2013 Andreas Wundsam
# Copyright 2012-2013 Sam Whitlock
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from collections import defaultdict, namedtuple
from sts.fingerprints.messages import *
import sts.replay_event
from pox.lib.revent import Event, EventMixin
from sts.util.convenience import base64_encode
import logging
log = logging.getLogger("openflow_buffer")

class PendingMessage(Event):
  def __init__(self, pending_message, b64_packet, send_event=False):
    # TODO(cs): boolean flag is ugly. Should use subclasses, but EventMixin
    # doesn't support addListener() on super/subclasses.
    super(PendingMessage, self).__init__()
    self.pending_message = pending_message
    self.b64_packet = b64_packet
    self.send_event = send_event

# TODO(cs): move me to another file?
class OpenFlowBuffer(EventMixin):
  '''
  Models asynchrony: chooses when switches get to process packets from
  controllers. Buffers packets until they are pulled off the buffer and chosen
  by god (control_flow.py) to be processed.
  '''

  # Packet class matches that should be let through automatically if
  # self.allow_whitelisted_packets is True.
  whitelisted_packet_classes = [("class", "ofp_packet_out", ("data", ("class", "lldp", None))),
                                ("class", "ofp_packet_in",  ("data", ("class", "lldp", None))),
                                ("class", "lldp", None),
                                ("class", "ofp_echo_request", None),
                                ("class", "ofp_echo_reply", None)]

  @staticmethod
  def in_whitelist(packet_fingerprint):
    for match in OpenFlowBuffer.whitelisted_packet_classes:
      if packet_fingerprint.check_match(match):
        return True
    return False

  _eventMixin_events = set([PendingMessage])

  def __init__(self):
    # keep around a queue for each switch of pending openflow messages waiting to
    # arrive at the switches.
    # { pending receive -> [(connection, pending ofp)_1, (connection, pending ofp)_2, ...] }
    self.pendingreceive2conn_messages = defaultdict(list)
    # { pending send -> [(connection, pending ofp)_1, (connection, pending ofp)_2, ...] }
    self.pendingsend2conn_messages = defaultdict(list)
    self._delegate_input_logger = None
    self.pass_through_whitelisted_packets = False

  def set_pass_through(self):
    ''' Cause all message receipts to pass through immediately without being
    buffered'''
    self.passed_through_events = []
    self.addListener(PendingMessage, self._pass_through_handler)

  def unset_pass_through(self):
    '''Unset pass through mode, and return any events that were passed through
    since pass through mode was set'''
    self.removeListener(self._pass_through_handler)
    passed_events = self.passed_through_events
    self.passed_through_events = []
    return passed_events

  def _pass_through_handler(self, message_event):
    ''' handler for pass-through mode '''
    pending_message = message_event.pending_message
    # Pass through
    self.schedule(pending_message)
    # Record
    if message_event.send_event:
      replay_event_class = sts.replay_event.ControlMessageSend
    else:
      replay_event_class = sts.replay_event.ControlMessageReceive

    replay_event = replay_event_class(pending_message.dpid,
                                      pending_message.controller_id,
                                      pending_message.fingerprint,
                                      b64_packet=message_event.b64_packet)
    if self._delegate_input_logger is not None:
      # TODO(cs): set event.round somehow?
      self._delegate_input_logger.log_input_event(replay_event)
    else:
      self.passed_through_events.append(replay_event)

  def set_pass_through(self, input_logger=None):
    ''' Cause all message receipts to pass through immediately without being
    buffered'''
    self.passed_through_events = []
    self._delegate_input_logger = input_logger
    self.addListener(PendingMessage, self._pass_through_handler)

  def unset_pass_through(self):
    '''Unset pass through mode, and return any events that were passed through
    since pass through mode was set'''
    self.removeListener(self._pass_through_handler)
    passed_events = self.passed_through_events
    self.passed_through_events = []
    return passed_events

  def message_receipt_waiting(self, pending_message):
    '''
    Return whether the pending message receive is available
    '''
    return pending_message in self.pendingreceive2conn_messages

  def message_send_waiting(self, pending_message):
    '''
    Return whether the pending send is available
    '''
    return pending_message in self.pendingsend2conn_messages

  def schedule(self, pending_message):
    '''
    Cause the switch to process the pending message associated with
    the fingerprint and controller connection.
    '''
    # TODO(cs): test whether this actually works! not sure about namedtuples..
    receive = type(pending_message) == PendingReceive
    if receive:
      if not self.message_receipt_waiting(pending_message):
        raise ValueError("No such pending message %s" % pending_message)
      multiset = self.pendingreceive2conn_messages
    else:
      if not self.message_send_waiting(pending_message):
        raise ValueError("No such pending message %s" % pending_message)
      multiset = self.pendingsend2conn_messages
    (forwarder, message) = multiset[pending_message].pop(0)
    # Avoid memory leak:
    if multiset[pending_message] == []:
      del multiset[pending_message]
    if receive:
      forwarder.allow_message_receipt(message)
    else:
      forwarder.allow_message_send(message)
    return message

  # TODO(cs): make this a factory method that returns DeferredOFConnection objects
  # with bound openflow_buffer.insert() method. (much cleaner API + separation of concerns)
  def insert_pending_receipt(self, dpid, controller_id, ofp_message, conn):
    ''' Called by DeferredOFConnection to insert messages into our buffer '''
    fingerprint = OFFingerprint.from_pkt(ofp_message)
    if self.pass_through_whitelisted_packets and self.in_whitelist(fingerprint):
      conn.allow_message_receipt(ofp_message)
      return
    conn_message = (conn, ofp_message)
    pending_receive = PendingReceive(dpid, controller_id, fingerprint)
    self.pendingreceive2conn_messages[pending_receive].append(conn_message)
    b64_packet = base64_encode(ofp_message)
    self.raiseEventNoErrors(PendingMessage(pending_receive, b64_packet))
    return pending_receive

  # TODO(cs): make this a factory method that returns DeferredOFConnection objects
  # with bound openflow_buffer.insert() method. (much cleaner API + separation of concerns)
  def insert_pending_send(self, dpid, controller_id, ofp_message, conn):
    ''' Called by DeferredOFConnection to insert messages into our buffer '''
    fingerprint = OFFingerprint.from_pkt(ofp_message)
    if self.pass_through_whitelisted_packets and self.in_whitelist(fingerprint):
      conn.allow_message_send(ofp_message)
      return
    conn_message = (conn, ofp_message)
    pending_send = PendingSend(dpid, controller_id, fingerprint)
    self.pendingsend2conn_messages[pending_send].append(conn_message)
    b64_packet = base64_encode(ofp_message)
    self.raiseEventNoErrors(PendingMessage(pending_send, b64_packet, send_event=True))
    return pending_send

  def pending_receives(self):
    ''' Return the message receipts which are waiting to be scheduled '''
    return self.pendingreceive2conn_messages.keys()

  def pending_sends(self):
    ''' Return the message sends which are waiting to be scheduled '''
    return self.pendingsend2conn_messages.keys()

  def flush(self):
    ''' Garbage collect any previous pending messages '''
    num_pending_messages = (len(self.pendingreceive2conn_messages) +
                            len(self.pendingsend2conn_messages))
    if num_pending_messages > 0:
      log.info("Flushing %d pending messages" % num_pending_messages)
    self.pendingreceive2conn_messages = defaultdict(list)
    self.pendingsend2conn_messages = defaultdict(list)

PendingReceive = namedtuple('PendingReceive', ['dpid', 'controller_id', 'fingerprint'])
PendingSend = namedtuple('PendingSend', ['dpid', 'controller_id', 'fingerprint'])

<commit_msg>Fix circular dependency between replay_event and openflow_buffer. Thanks to Ahmed.<commit_after># Copyright 2011-2013 Colin Scott
# Copyright 2011-2013 Andreas Wundsam
# Copyright 2012-2013 Sam Whitlock
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from collections import defaultdict, namedtuple
from sts.fingerprints.messages import *
from pox.lib.revent import Event, EventMixin
from sts.util.convenience import base64_encode
import logging
log = logging.getLogger("openflow_buffer")

class PendingMessage(Event):
  def __init__(self, pending_message, b64_packet, send_event=False):
    # TODO(cs): boolean flag is ugly. Should use subclasses, but EventMixin
    # doesn't support addListener() on super/subclasses.
    super(PendingMessage, self).__init__()
    self.pending_message = pending_message
    self.b64_packet = b64_packet
    self.send_event = send_event

# TODO(cs): move me to another file?
class OpenFlowBuffer(EventMixin):
  '''
  Models asynchrony: chooses when switches get to process packets from
  controllers. Buffers packets until they are pulled off the buffer and chosen
  by god (control_flow.py) to be processed.
  '''

  # Packet class matches that should be let through automatically if
  # self.allow_whitelisted_packets is True.
  whitelisted_packet_classes = [("class", "ofp_packet_out", ("data", ("class", "lldp", None))),
                                ("class", "ofp_packet_in",  ("data", ("class", "lldp", None))),
                                ("class", "lldp", None),
                                ("class", "ofp_echo_request", None),
                                ("class", "ofp_echo_reply", None)]

  @staticmethod
  def in_whitelist(packet_fingerprint):
    for match in OpenFlowBuffer.whitelisted_packet_classes:
      if packet_fingerprint.check_match(match):
        return True
    return False

  _eventMixin_events = set([PendingMessage])

  def __init__(self):
    # keep around a queue for each switch of pending openflow messages waiting to
    # arrive at the switches.
    # { pending receive -> [(connection, pending ofp)_1, (connection, pending ofp)_2, ...] }
    self.pendingreceive2conn_messages = defaultdict(list)
    # { pending send -> [(connection, pending ofp)_1, (connection, pending ofp)_2, ...] }
    self.pendingsend2conn_messages = defaultdict(list)
    self._delegate_input_logger = None
    self.pass_through_whitelisted_packets = False

  def set_pass_through(self):
    ''' Cause all message receipts to pass through immediately without being
    buffered'''
    self.passed_through_events = []
    self.addListener(PendingMessage, self._pass_through_handler)

  def unset_pass_through(self):
    '''Unset pass through mode, and return any events that were passed through
    since pass through mode was set'''
    self.removeListener(self._pass_through_handler)
    passed_events = self.passed_through_events
    self.passed_through_events = []
    return passed_events

  def _pass_through_handler(self, message_event):
    ''' handler for pass-through mode '''
    # TODO(cs): figure out a better way to resolve circular dependency
    import sts.replay_event
    pending_message = message_event.pending_message
    # Pass through
    self.schedule(pending_message)
    # Record
    if message_event.send_event:
      replay_event_class = sts.replay_event.ControlMessageSend
    else:
      replay_event_class = sts.replay_event.ControlMessageReceive

    replay_event = replay_event_class(pending_message.dpid,
                                      pending_message.controller_id,
                                      pending_message.fingerprint,
                                      b64_packet=message_event.b64_packet)
    if self._delegate_input_logger is not None:
      # TODO(cs): set event.round somehow?
      self._delegate_input_logger.log_input_event(replay_event)
    else:
      self.passed_through_events.append(replay_event)

  def set_pass_through(self, input_logger=None):
    ''' Cause all message receipts to pass through immediately without being
    buffered'''
    self.passed_through_events = []
    self._delegate_input_logger = input_logger
    self.addListener(PendingMessage, self._pass_through_handler)

  def unset_pass_through(self):
    '''Unset pass through mode, and return any events that were passed through
    since pass through mode was set'''
    self.removeListener(self._pass_through_handler)
    passed_events = self.passed_through_events
    self.passed_through_events = []
    return passed_events

  def message_receipt_waiting(self, pending_message):
    '''
    Return whether the pending message receive is available
    '''
    return pending_message in self.pendingreceive2conn_messages

  def message_send_waiting(self, pending_message):
    '''
    Return whether the pending send is available
    '''
    return pending_message in self.pendingsend2conn_messages

  def schedule(self, pending_message):
    '''
    Cause the switch to process the pending message associated with
    the fingerprint and controller connection.
    '''
    # TODO(cs): test whether this actually works! not sure about namedtuples..
    receive = type(pending_message) == PendingReceive
    if receive:
      if not self.message_receipt_waiting(pending_message):
        raise ValueError("No such pending message %s" % pending_message)
      multiset = self.pendingreceive2conn_messages
    else:
      if not self.message_send_waiting(pending_message):
        raise ValueError("No such pending message %s" % pending_message)
      multiset = self.pendingsend2conn_messages
    (forwarder, message) = multiset[pending_message].pop(0)
    # Avoid memory leak:
    if multiset[pending_message] == []:
      del multiset[pending_message]
    if receive:
      forwarder.allow_message_receipt(message)
    else:
      forwarder.allow_message_send(message)
    return message

  # TODO(cs): make this a factory method that returns DeferredOFConnection objects
  # with bound openflow_buffer.insert() method. (much cleaner API + separation of concerns)
  def insert_pending_receipt(self, dpid, controller_id, ofp_message, conn):
    ''' Called by DeferredOFConnection to insert messages into our buffer '''
    fingerprint = OFFingerprint.from_pkt(ofp_message)
    if self.pass_through_whitelisted_packets and self.in_whitelist(fingerprint):
      conn.allow_message_receipt(ofp_message)
      return
    conn_message = (conn, ofp_message)
    pending_receive = PendingReceive(dpid, controller_id, fingerprint)
    self.pendingreceive2conn_messages[pending_receive].append(conn_message)
    b64_packet = base64_encode(ofp_message)
    self.raiseEventNoErrors(PendingMessage(pending_receive, b64_packet))
    return pending_receive

  # TODO(cs): make this a factory method that returns DeferredOFConnection objects
  # with bound openflow_buffer.insert() method. (much cleaner API + separation of concerns)
  def insert_pending_send(self, dpid, controller_id, ofp_message, conn):
    ''' Called by DeferredOFConnection to insert messages into our buffer '''
    fingerprint = OFFingerprint.from_pkt(ofp_message)
    if self.pass_through_whitelisted_packets and self.in_whitelist(fingerprint):
      conn.allow_message_send(ofp_message)
      return
    conn_message = (conn, ofp_message)
    pending_send = PendingSend(dpid, controller_id, fingerprint)
    self.pendingsend2conn_messages[pending_send].append(conn_message)
    b64_packet = base64_encode(ofp_message)
    self.raiseEventNoErrors(PendingMessage(pending_send, b64_packet, send_event=True))
    return pending_send

  def pending_receives(self):
    ''' Return the message receipts which are waiting to be scheduled '''
    return self.pendingreceive2conn_messages.keys()

  def pending_sends(self):
    ''' Return the message sends which are waiting to be scheduled '''
    return self.pendingsend2conn_messages.keys()

  def flush(self):
    ''' Garbage collect any previous pending messages '''
    num_pending_messages = (len(self.pendingreceive2conn_messages) +
                            len(self.pendingsend2conn_messages))
    if num_pending_messages > 0:
      log.info("Flushing %d pending messages" % num_pending_messages)
    self.pendingreceive2conn_messages = defaultdict(list)
    self.pendingsend2conn_messages = defaultdict(list)

PendingReceive = namedtuple('PendingReceive', ['dpid', 'controller_id', 'fingerprint'])
PendingSend = namedtuple('PendingSend', ['dpid', 'controller_id', 'fingerprint'])

<|endoftext|>