#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""Model and Argument."""
from __future__ import print_function, division, unicode_literals
import abc
import inspect
import six
from .types import Nested
from .exception import ConvertError, ArgumentMissError, ArgumentInvalidError
@six.add_metaclass(abc.ABCMeta)
[docs]class BaseAdapter(object):
"""To implement your own adapter, you need inherit from
:class:`~parameter.model.BaseAdapter`.
There two methods must be overwirtten:
- ``get_argument``: Returns a single value
- ``get_arguments``: Returns a sequence of values.
Example::
from parameter.model import BaseAdapter
class DemoAdapter(BaseAdapter):
"demo adapter"
def __init__(self, arguments):
self.arguments = arguments
def get_argument(self, name, default):
return self.arguments.get(name, default)
def get_arguments(self, name):
return self.arguments.getlist(name)
If you want your adapter to support nested, you need to override
the ``spawn`` method, this method use the given value to return an new
instance of the current adapter.
::
from parameter.model import BaseAdapter
class DemoAdapter(BaseAdapter):
# see above
def spawn(self, arguments):
return DemoAdapter(arguments)
"""
@abc.abstractmethod
[docs] def get_argument(self, name, default, *args, **kwargs):
"""Returns the argument's value via ``name``.
:param name: The name of the argument.
:param default: The default value.
:raises: :class:`~parameter.exception.ArgumentMissError`
:raises: :class:`~parameter.exception.ArgumentInvalidError`
"""
@abc.abstractmethod
[docs] def get_arguments(self, name, *args, **kwargs):
"""Returns the argument's values via ``name``.
:param name: The name of the argument.
:raises: :class:`~parameter.exception.ArgumentMissError`
:raises: :class:`~parameter.exception.ArgumentInvalidError`
"""
@staticmethod
[docs] def spawn(val):
"""Use the new value to spawn an new adapter of this adapter.
"""
raise NotImplementedError()
[docs]class Argument(object):
"""Represents a parameter in HTTP request."""
_DEFAULT = [] # type: list
[docs] def __init__(self, type_, default=_DEFAULT, alias=None, multiple=False,
miss_message=None, invalid_message=None):
"""Initialize
:param type_:
The parameter's type, indicated using an instance which subclasses
:class:`parameter.types.BaseType`.
:type type_: :class:`parameter.types.BaseType`.
:param default: The default value.
:param alias:
The alias name of this argument as represented in the HTTP request.
:param multiple: This argument have multiple values.
:param miss_message:
The message of
:class:`~parameter.exception.ArgumentMissError`
:param invalid_message:
The message of
:class:`~parameter.exception.ArgumentInvalidError`
"""
self.name = None
self.alias = alias
self.type_ = type_() if inspect.isclass(type_) else type_
self.default = default
self.multiple = multiple
self.miss_message = miss_message
self.invalid_message = invalid_message
@classmethod
[docs] def is_init_default(cls, value):
"""Returns ``True`` if the ``value`` is the initial default."""
return value is cls._DEFAULT
[docs] def convert(self, value):
"""Check and convert the value to the specified type.
:raises: :class:`~parameter.exception.ArgumentMissError`
:raises: :class:`~parameter.exception.ArgumentInvalidError`
"""
if self.is_init_default(value):
raise ArgumentMissError(self.miss_message, self.name)
try:
return self.type_.convert(value)
except ConvertError as e:
raise ArgumentInvalidError(self.invalid_message, self.name, e)
class ModelMeta(type):
def __new__(cls, name, base, __dict__):
arguments = []
for attr, val in __dict__.items():
if not isinstance(val, Argument):
continue
# If not specify alias for this argument, set argument's name
# to current attribute name.
if val.alias is None:
val.name = attr
else:
# Otherwise set to the alias that user specified
val.name = val.alias
arguments.append((attr, val))
__dict__["_meta_arguments"] = arguments
for key, _ in arguments:
__dict__.pop(key)
return type.__new__(cls, name, base, __dict__)
@six.add_metaclass(ModelMeta)
class Model(object):
_meta_arguments = None # type: list
def __init__(self, adapter):
"""Initialize
:param adapter:
The adapter to get argument,
indicate using an instance which subclasses
:class:`BaseAdapter`
:type adapter: :class:`BaseAdapter`
"""
self.adapter = adapter
self._arguments = {}
self._init()
def _attempt_construct_adapter(self, arg, val):
"""Attempt construct an instance which subclasses :class:`~BaseAdapter`
if the type of arg's is :class:`~parameter.types.Nested`.
"""
if isinstance(arg.type_, Nested):
return self.adapter.spawn(val)
return val
def _init(self):
"""Initialize arguments"""
for attr, arg in self._meta_arguments:
if arg.multiple:
val = self.adapter.get_arguments(arg.name)
val = [arg.convert(self._attempt_construct_adapter(arg, v))
for v in val]
else:
val = self.adapter.get_argument(arg.name, arg.default)
val = arg.convert(self._attempt_construct_adapter(arg, val))
self._arguments[attr] = val
def __getattr__(self, key):
return self._arguments[key]
def __repr__(self):
args = " ".join("<%s: %s>" % (attr, arg.type_.__class__.__name__)
for attr, arg in self._meta_arguments)
return "<%s [%s]>" % (self.__class__.__name__, args)