def test_dashed(self): assert utils.dasherize('dashed-words') == 'dashed-words'
def test_underscore(self): assert utils.dasherize('under_score') == 'under-score'
def test_pascal(self): assert utils.dasherize('PascalCase') == 'pascal-case'
def test_camel(self): assert utils.dasherize('camelCase') == 'camel-case'
def test_word(self): assert utils.dasherize('word') == 'word'
def __init__(self, meta, name, data, bases): """ Initializes the options object and defaults configuration not specified. @param[in] meta Dictionary of the merged meta attributes. @param[in] name Name of the resource class this is being instantiataed for. """ #! Whether to allow display of debugging information #! to the client. self.debug = meta.get('debug') if self.debug is None: self.debug = False #! Whether to not actualize a resource from the described class. #! Abstract resources are meant as generic base classes. #! #! @note #! Abstract only counts if it is directly set on the resource. self.abstract = data.get('abstract') #! Name of the resource to use in URIs; defaults to the dasherized #! version of the camel cased class name (eg. SomethingHere becomes #! something-here). The defaulted version also strips a trailing #! Resource from the name (eg. SomethingHereResource still becomes #! something-here). self.name = meta.get('name') if self.name is None: # Generate a name for the resource if one is not provided. # PascalCaseThis => pascal-case-this dashed = utils.dasherize(name).strip() if dashed: # Strip off a trailing Resource as well. self.name = re.sub(r'-resource$', '', dashed) else: # Something went wrong; just use the class name self.name = name elif callable(self.name): # If name is callable; pass in the class name and use what # we got back. self.name = self.name(name) #! True if the resource is expected to operate asynchronously. #! #! The side-effect of setting this to True is that returning from #! `dispatch` (and by extension `get`, `post`, etc.) does not #! terminate the connection. You must invoke `response.close()` to #! terminate the connection. self.asynchronous = meta.get('asynchronous', False) #! Connectors to use to connect to the environment. #! #! This is a dictionary that maps hooks (keys) to the connector to use #! for the hook. #! #! There is only 1 hook available currently, 'http', and it is #! required. #! #! The available connectors are as follows: #! - http: #! > django #! > flask #! #! They may be used as follows: #! #! @code #! from armet import resources #! class Resource(resources.Resource): #! class Meta: #! connectors = {'http': 'django'} #! @endcode #! #! Connectors may also be specified as full paths to the connector #! module (if a connector is located somewhere other than #! armet.connectors) as follows: #! #! @code #! from armet import resources #! class Resource(resources.Resource): #! class Meta: #! connectors = {'http': 'some.other.place'} #! @endcode self.connectors = connectors = _merge(meta, 'connectors', bases, {}) if not connectors.get('http') and not self.abstract: raise ImproperlyConfigured('No valid HTTP connector was detected.') # Pull out the connectors and convert them into module references. for key in connectors: connector = connectors[key] if isinstance(connector, six.string_types): if connector in getattr(included_connectors, key): # Included shortname; prepend base. connectors[key] = 'armet.connectors.{}'.format(connector) #! Additional options to handle and merge into the meta object #! at the class object level. #! #! This should be a simple set/list/tuple of options. #! #! @code #! from armet import resources #! class Resource(resources.Resource): #! class Meta: #! options = {'color', 'plant'} #! #! @endcode self.options = options = _merge(meta, 'options', bases, {}) for name in options: # Pull out each option and stick it on the meta. setattr(self, name, meta.get(name)) #! Regular-expression patterns to apply to the request path #! and pull arguments and traversal out of it. #! #! @code #! from armet import resources #! class Resource(resources.Resource): #! class Meta: #! patterns = [ #! # Match nothing after the resource name. #! r'^$', #! #! # Match a slug after the resource name. #! r'^/(?P<slug>[^/]+)/?$', #! ] #! #! @endcode #! #! Named parameters get auto-attached to `self` on the resource #! instance. #! #! This may be a list of lists/tuples as well to indicate simple #! traversal. #! #! @code #! from armet import resources #! class Resource(resources.Resource): #! class Meta: #! patterns = [ #! # Match nothing and don't traverse. #! (None, r'^$'), #! #! # Match the word 'user' and traverse. #! # The remainder of the path is taken or #! # the named gruop, "path" (if the whole path was #! # matched) or the last-matched group (if none are #! # named "path") #! (UserResource, r'^/user'), #! ] #! #! @endcode self.patterns = meta.get('patterns', []) for index, pattern in enumerate(self.patterns): # Coerce simple form. if isinstance(pattern, six.string_types): pattern = (None, pattern) # Compile the expression. self.patterns[index] = (pattern[0], re.compile(pattern[1])) #! Trailing slash handling. #! The value indicates which URI is the canonical URI and the #! alternative URI is then made to redirect (with a 301) to the #! canonical URI. self.trailing_slash = meta.get('trailing_slash', True) #! List of allowed HTTP methods. #! If not provided and allowed_operations was provided instead #! the operations are appropriately mapped; else, the default #! configuration is provided. self.http_allowed_methods = meta.get('http_allowed_methods') if self.http_allowed_methods is None: self.http_allowed_methods = ( 'HEAD', 'OPTIONS', 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'LINK', 'UNLINK' ) #! List of allowed HTTP headers. #! This is used only to request or prevent CORS requests. self.http_allowed_headers = meta.get('http_allowed_headers') if self.http_allowed_headers is None: self.http_allowed_headers = ( 'Content-Type', 'Authorization', 'Accept', 'Origin' ) #! List of exposed HTTP headers. #! This is used only to show headers to the client. self.http_exposed_headers = meta.get('http_exposed_headers') if self.http_exposed_headers is None: self.http_exposed_headers = ( 'Content-Type', 'Authorization', 'Accept', 'Origin' ) #! List of allowed HTTP origins. #! This is used to request or prevent CORS requests. #! No CORS requests will be allowed, at-all, unless this #! property is set. #! NOTE: This can be set to '*' to indicate any origin. self.http_allowed_origins = meta.get('http_allowed_origins') if self.http_allowed_origins is None: self.http_allowed_origins = () #! Whether to use legacy redirects or not to inform the #! client the resource is available elsewhere. Legacy redirects #! require a combination of 301 and 307 in which 307 is not cacheable. #! Modern redirecting uses 308 and is in effect 307 with cacheing. #! Unfortunately unknown 3xx codes are treated as #! a 300 (Multiple choices) in which the user is supposed to chose #! the alternate link so the client is not supposed to auto follow #! redirects. Ensure all supported clients understand 308 before #! turning off legacy redirecting. #! As of 19 March 2013 only Firefox supports it since a year ago. self.legacy_redirect = meta.get('legacy_redirect', True) #! Mapping of serializers known by this resource. #! Values may either be a string reference to the serializer type #! or an serializer class object. self.serializers = serializers = meta.get('serializers') if not serializers: self.serializers = { 'json': 'armet.serializers.JSONSerializer', 'url': 'armet.serializers.URLSerializer' } # Expand the serializer name references. for name, serializer in six.iteritems(self.serializers): if isinstance(serializer, six.string_types): segments = serializer.split('.') module = '.'.join(segments[:-1]) module = import_module(module) self.serializers[name] = getattr(module, segments[-1]) #! List of allowed serializers of the understood serializers. self.allowed_serializers = meta.get('allowed_serializers') if not self.allowed_serializers: self.allowed_serializers = tuple(self.serializers.keys()) # Check to ensure that all allowed serializers are # understood serializers. for name in self.allowed_serializers: if name not in self.serializers: raise ImproperlyConfigured( 'The allowed serializer, {}, is not one of the ' 'understood serializers'.format(name)) #! Name of the default serializer of the list of #! understood serializers. self.default_serializer = meta.get('default_serializer') if not self.default_serializer: if 'json' in self.allowed_serializers: self.default_serializer = 'json' else: self.default_serializer = self.allowed_serializers[0] if self.default_serializer not in self.allowed_serializers: raise ImproperlyConfigured( 'The chosen default serializer, {}, is not one of the ' 'allowed serializers'.format(self.default_serializer)) #! Mapping of deserializers known by this resource. #! Values may either be a string reference to the deserializer type #! or an deserializer class object. self.deserializers = deserializers = meta.get('deserializers') if not deserializers: self.deserializers = { 'json': 'armet.deserializers.JSONDeserializer', 'url': 'armet.deserializers.URLDeserializer' } # Expand the deserializer name references. for name, deserializer in six.iteritems(self.deserializers): if isinstance(deserializer, six.string_types): segments = deserializer.split('.') module = '.'.join(segments[:-1]) module = import_module(module) self.deserializers[name] = getattr(module, segments[-1]) #! List of allowed deserializers of the understood deserializers. self.allowed_deserializers = meta.get('allowed_deserializers') if not self.allowed_deserializers: self.allowed_deserializers = tuple(self.deserializers.keys()) # Check to ensure that all allowed deserializers are # understood deserializers. for name in self.allowed_deserializers: if name not in self.deserializers: raise ImproperlyConfigured( 'The allowed deserializer, {}, is not one of the ' 'understood deserializers'.format(name)) #! List of authentication protocols to attempt in sequence #! to determine the authenticated user. self.authentication = meta.get('authentication') if self.authentication is None: # Default to a single instance of pass-through authentication. self.authentication = (authentication.Authentication(),) #! The authorization protocol to attempt to use #! to determine the if the user can access or is otherwise #! authorized. self.authorization = meta.get('authorization') if self.authorization is None: # Default is the pass-through authorization. self.authorization = authorization.Authorization()