From 8c1784e8c1eac65f66b4a1ecc4b8b0ddd5de9327 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Wed, 3 Apr 2013 01:17:26 +0200 Subject: Pylint. --- .pylintrc | 240 +++++++++++++++++++++++++++++++++++++++++++++ Makefile | 18 +++- factory/__init__.py | 28 +++--- factory/base.py | 123 +++++++---------------- factory/compat.py | 10 +- factory/containers.py | 7 +- factory/declarations.py | 39 ++------ factory/helpers.py | 123 +++++++++++++++++++++++ tests/test_declarations.py | 3 +- 9 files changed, 445 insertions(+), 146 deletions(-) create mode 100644 .pylintrc create mode 100644 factory/helpers.py diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..e9f43c9 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,240 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook='import os, sys; sys.path.append(os.getcwd())' + +# Profiled execution. +profile=no + +# Add to the black list. It should be a base name, not a +# path. You may set this option multiple times. +ignore= + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). +#disable=C0103,C0111,C0302,E1002,E1101,E1102,E1103,I0011,I0013,R0201,R0801,R0901,R0902,R0903,R0904,R0912,R0914,R0915,R0921,R0923,W0108,W0212,W0232,W0141,W0142,W0401,W0613,R0924 +disable=C0103,C0111,I0011,R0201,R0903,R0922,W0142,W0212,W0232,W0613 +# see http://www.logilab.org/card/pylintfeatures + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html +output-format=text + +# Include message's id in output +include-ids=yes + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Add a comment according to your evaluation note. This is used by the global +# evaluation report (RP0004). +comment=no + + +[BASIC] + +# Required attributes for module, separated by a comma +required-attributes= + +# List of builtins function names that should not be used, separated by a comma +bad-functions=map,filter,apply,input + +# Regular expression which should only match correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression which should only match correct module level names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression which should only match correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression which should only match correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct instance attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match correct list comprehension / +# generator expression variable names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Regular expression which should only match functions or classes name which do +# not require a docstring +no-docstring-rgx=__.*__ + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=80 + +# Maximum number of lines in a module +max-module-lines=1200 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of classes names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). +ignored-classes=SQLObject + +# When zope mode is activated, add a predefined set of Zope acquired attributes +# to generated-members. +zope=no + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E0201 when accessed. +generated-members=REQUEST,acl_users,aq_parent + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the beginning of the name of dummy variables +# (i.e. not used). +dummy-variables-rgx=_|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + + +[CLASSES] + +# List of interface methods to ignore, separated by a comma. This is used for +# instance to not check methods defines in Zope's Interface base class. +ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=8 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branchs=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=10 + +# Maximum number of attributes for a class (see R0902). +max-attributes=10 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub,string,TERMIOS,Bastion,rexec + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= diff --git a/Makefile b/Makefile index f9c27c0..274ee32 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,8 @@ +PACKAGE=factory +TESTS_DIR=tests +DOC_DIR=docs + + all: default @@ -11,14 +16,17 @@ clean: test: python -W default setup.py test +pylint: + pylint --rcfile=.pylintrc --report=no $(PACKAGE)/ + coverage: coverage erase - coverage run "--include=factory/*.py,tests/*.py" --branch setup.py test - coverage report "--include=factory/*.py,tests/*.py" - coverage html "--include=factory/*.py,tests/*.py" + coverage run "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" --branch setup.py test + coverage report "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" + coverage html "--include=$(PACKAGE)/*.py,$(TESTS_DIR)/*.py" doc: - $(MAKE) -C docs html + $(MAKE) -C $(DOC_DIR) html -.PHONY: all default clean coverage doc test +.PHONY: all default clean coverage doc pylint test diff --git a/factory/__init__.py b/factory/__init__.py index adcf9c9..beb422e 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -28,19 +28,6 @@ from .base import ( StubFactory, DjangoModelFactory, - build, - create, - stub, - generate, - simple_generate, - make_factory, - - build_batch, - create_batch, - stub_batch, - generate_batch, - simple_generate_batch, - BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, @@ -58,6 +45,21 @@ from .declarations import ( PostGeneration, PostGenerationMethodCall, RelatedFactory, +) + +from .helpers import ( + build, + create, + stub, + generate, + simple_generate, + make_factory, + + build_batch, + create_batch, + stub_batch, + generate_batch, + simple_generate_batch, lazy_attribute, iterator, diff --git a/factory/base.py b/factory/base.py index 71c2eb1..13a0623 100644 --- a/factory/base.py +++ b/factory/base.py @@ -20,10 +20,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -import re -import sys -import warnings - from . import containers # Strategies @@ -68,7 +64,7 @@ class FactoryMetaClass(type): """Factory metaclass for handling ordered declarations.""" def __call__(cls, **kwargs): - """Override the default Factory() syntax to call the default build strategy. + """Override the default Factory() syntax to call the default strategy. Returns an instance of the associated class. """ @@ -80,10 +76,11 @@ class FactoryMetaClass(type): elif cls.FACTORY_STRATEGY == STUB_STRATEGY: return cls.stub(**kwargs) else: - raise UnknownStrategy('Unknown FACTORY_STRATEGY: {0}'.format(cls.FACTORY_STRATEGY)) + raise UnknownStrategy('Unknown FACTORY_STRATEGY: {0}'.format( + cls.FACTORY_STRATEGY)) @classmethod - def _discover_associated_class(cls, class_name, attrs, inherited=None): + def _discover_associated_class(mcs, class_name, attrs, inherited=None): """Try to find the class associated with this factory. In order, the following tests will be performed: @@ -104,8 +101,6 @@ class FactoryMetaClass(type): AssociatedClassError: If we were unable to associate this factory to a class. """ - own_associated_class = None - if FACTORY_CLASS_DECLARATION in attrs: return attrs[FACTORY_CLASS_DECLARATION] @@ -120,7 +115,7 @@ class FactoryMetaClass(type): class_name) @classmethod - def _extract_declarations(cls, bases, attributes): + def _extract_declarations(mcs, bases, attributes): """Extract declarations from a class definition. Args: @@ -141,7 +136,8 @@ class FactoryMetaClass(type): postgen_declarations.update_with_public( getattr(base, CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS, {})) # Import all 'public' attributes (avoid those starting with _) - declarations.update_with_public(getattr(base, CLASS_ATTRIBUTE_DECLARATIONS, {})) + declarations.update_with_public( + getattr(base, CLASS_ATTRIBUTE_DECLARATIONS, {})) # Import attributes from the class definition attributes = postgen_declarations.update_with_public(attributes) @@ -154,7 +150,7 @@ class FactoryMetaClass(type): return attributes - def __new__(cls, class_name, bases, attrs, extra_attrs=None): + def __new__(mcs, class_name, bases, attrs, extra_attrs=None): """Record attributes as a pattern for later instance construction. This is called when a new Factory subclass is defined; it will collect @@ -174,7 +170,8 @@ class FactoryMetaClass(type): """ parent_factories = get_factory_bases(bases) if not parent_factories: - return super(FactoryMetaClass, cls).__new__(cls, class_name, bases, attrs) + return super(FactoryMetaClass, mcs).__new__( + mcs, class_name, bases, attrs) is_abstract = attrs.pop('ABSTRACT_FACTORY', False) extra_attrs = {} @@ -185,7 +182,7 @@ class FactoryMetaClass(type): inherited_associated_class = getattr(base, CLASS_ATTRIBUTE_ASSOCIATED_CLASS, None) - associated_class = cls._discover_associated_class(class_name, attrs, + associated_class = mcs._discover_associated_class(class_name, attrs, inherited_associated_class) # If inheriting the factory from a parent, keep a link to it. @@ -193,22 +190,23 @@ class FactoryMetaClass(type): if associated_class == inherited_associated_class: attrs['_base_factory'] = base - # The CLASS_ATTRIBUTE_ASSOCIATED_CLASS must *not* be taken into account - # when parsing the declared attributes of the new class. + # The CLASS_ATTRIBUTE_ASSOCIATED_CLASS must *not* be taken into + # account when parsing the declared attributes of the new class. extra_attrs = {CLASS_ATTRIBUTE_ASSOCIATED_CLASS: associated_class} # Extract pre- and post-generation declarations - attributes = cls._extract_declarations(parent_factories, attrs) + attributes = mcs._extract_declarations(parent_factories, attrs) # Add extra args if provided. if extra_attrs: attributes.update(extra_attrs) - return super(FactoryMetaClass, cls).__new__(cls, class_name, bases, attributes) + return super(FactoryMetaClass, mcs).__new__( + mcs, class_name, bases, attributes) - def __str__(self): - return '<%s for %s>' % (self.__name__, - getattr(self, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__) + def __str__(cls): + return '<%s for %s>' % (cls.__name__, + getattr(cls, CLASS_ATTRIBUTE_ASSOCIATED_CLASS).__name__) # Factory base classes @@ -216,6 +214,7 @@ class FactoryMetaClass(type): class BaseFactory(object): """Factory base support for sequences, attributes and stubs.""" + # Backwards compatibility UnknownStrategy = UnknownStrategy UnsupportedStrategy = UnsupportedStrategy @@ -231,6 +230,9 @@ class BaseFactory(object): # class. _base_factory = None + # Holds the target class, once resolved. + _associated_class = None + # List of arguments that should be passed as *args instead of **kwargs FACTORY_ARG_PARAMETERS = () @@ -329,7 +331,8 @@ class BaseFactory(object): attrs (dict): attributes to use for generating the object """ # Extract declarations used for post-generation - postgen_declarations = getattr(cls, CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS) + postgen_declarations = getattr(cls, + CLASS_ATTRIBUTE_POSTGEN_DECLARATIONS) postgen_attributes = {} for name, decl in sorted(postgen_declarations.items()): postgen_attributes[name] = decl.extract(name, attrs) @@ -341,7 +344,8 @@ class BaseFactory(object): results = {} for name, decl in sorted(postgen_declarations.items()): extracted, extracted_kwargs = postgen_attributes[name] - results[name] = decl.call(obj, create, extracted, **extracted_kwargs) + results[name] = decl.call(obj, create, extracted, + **extracted_kwargs) cls._after_postgeneration(obj, create, results) @@ -527,7 +531,7 @@ Factory = FactoryMetaClass('Factory', (BaseFactory,), { # Backwards compatibility -Factory.AssociatedClassError = AssociatedClassError +Factory.AssociatedClassError = AssociatedClassError # pylint: disable=W0201 class StubFactory(Factory): @@ -547,7 +551,7 @@ class StubFactory(Factory): class DjangoModelFactory(Factory): """Factory for Django models. - This makes sure that the 'sequence' field of created objects is an unused id. + This makes sure that the 'sequence' field of created objects is a new id. Possible improvement: define a new 'attribute' type, AutoField, which would handle those for non-numerical primary keys. @@ -559,7 +563,7 @@ class DjangoModelFactory(Factory): @classmethod def _get_manager(cls, target_class): try: - return target_class._default_manager + return target_class._default_manager # pylint: disable=W0212 except AttributeError: return target_class.objects @@ -567,7 +571,8 @@ class DjangoModelFactory(Factory): def _setup_next_sequence(cls): """Compute the next available PK, based on the 'pk' database field.""" - manager = cls._get_manager(cls._associated_class) + model = cls._associated_class # pylint: disable=E1101 + manager = cls._get_manager(model) try: return 1 + manager.values_list('pk', flat=True @@ -590,7 +595,7 @@ class DjangoModelFactory(Factory): key_fields[field] = kwargs.pop(field) key_fields['defaults'] = kwargs - obj, created = manager.get_or_create(*args, **key_fields) + obj, _created = manager.get_or_create(*args, **key_fields) return obj @classmethod @@ -610,68 +615,6 @@ class MogoFactory(Factory): return target_class.new(*args, **kwargs) -def make_factory(klass, **kwargs): - """Create a new, simple factory for the given class.""" - factory_name = '%sFactory' % klass.__name__ - kwargs[FACTORY_CLASS_DECLARATION] = klass - base_class = kwargs.pop('FACTORY_CLASS', Factory) - - factory_class = type(Factory).__new__(type(Factory), factory_name, (base_class,), kwargs) - factory_class.__name__ = '%sFactory' % klass.__name__ - factory_class.__doc__ = 'Auto-generated factory for class %s' % klass - return factory_class - - -def build(klass, **kwargs): - """Create a factory for the given class, and build an instance.""" - return make_factory(klass, **kwargs).build() - - -def build_batch(klass, size, **kwargs): - """Create a factory for the given class, and build a batch of instances.""" - return make_factory(klass, **kwargs).build_batch(size) - - -def create(klass, **kwargs): - """Create a factory for the given class, and create an instance.""" - return make_factory(klass, **kwargs).create() - - -def create_batch(klass, size, **kwargs): - """Create a factory for the given class, and create a batch of instances.""" - return make_factory(klass, **kwargs).create_batch(size) - - -def stub(klass, **kwargs): - """Create a factory for the given class, and stub an instance.""" - return make_factory(klass, **kwargs).stub() - - -def stub_batch(klass, size, **kwargs): - """Create a factory for the given class, and stub a batch of instances.""" - return make_factory(klass, **kwargs).stub_batch(size) - - -def generate(klass, strategy, **kwargs): - """Create a factory for the given class, and generate an instance.""" - return make_factory(klass, **kwargs).generate(strategy) - - -def generate_batch(klass, strategy, size, **kwargs): - """Create a factory for the given class, and generate instances.""" - return make_factory(klass, **kwargs).generate_batch(strategy, size) - - -def simple_generate(klass, create, **kwargs): - """Create a factory for the given class, and simple_generate an instance.""" - return make_factory(klass, **kwargs).simple_generate(create) - - -def simple_generate_batch(klass, create, size, **kwargs): - """Create a factory for the given class, and simple_generate instances.""" - return make_factory(klass, **kwargs).simple_generate_batch(create, size) - - def use_strategy(new_strategy): """Force the use of a different strategy. diff --git a/factory/compat.py b/factory/compat.py index a924de0..84f31b7 100644 --- a/factory/compat.py +++ b/factory/compat.py @@ -25,9 +25,11 @@ import sys -is_python2 = (sys.version_info[0] == 2) +PY2 = (sys.version_info[0] == 2) -if is_python2: - string_types = (str, unicode) +if PY2: + def is_string(obj): + return isinstance(obj, (str, unicode)) else: - string_types = (str,) + def is_string(obj): + return isinstance(obj, str) diff --git a/factory/containers.py b/factory/containers.py index dc3a457..e02f9f9 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -228,9 +228,12 @@ class AttributeBuilder(object): self._containers = extra.pop('__containers', ()) self._attrs = factory.declarations(extra) - attrs_with_subfields = [k for k, v in self._attrs.items() if self.has_subfields(v)] + attrs_with_subfields = [ + k for k, v in self._attrs.items() + if self.has_subfields(v)] - self._subfields = utils.multi_extract_dict(attrs_with_subfields, self._attrs) + self._subfields = utils.multi_extract_dict( + attrs_with_subfields, self._attrs) def has_subfields(self, value): return isinstance(value, declarations.SubFactory) diff --git a/factory/declarations.py b/factory/declarations.py index 15d8d5b..3d76960 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -21,9 +21,7 @@ # THE SOFTWARE. -import collections import itertools -import warnings from . import compat from . import utils @@ -179,7 +177,7 @@ class Sequence(OrderedDeclaration): type (function): A function converting an integer into the expected kind of counter for the 'function' attribute. """ - def __init__(self, function, type=int): + def __init__(self, function, type=int): # pylint: disable=W0622 super(Sequence, self).__init__() self.function = function self.type = type @@ -318,7 +316,7 @@ class SubFactory(ParameteredAttribute): self.factory_module = self.factory_name = '' else: # Must be a string - if not isinstance(factory, compat.string_types) or '.' not in factory: + if not (compat.is_string(factory) and '.' in factory): raise ValueError( "The argument of a SubFactory must be either a class " "or the fully qualified path to a Factory class; got " @@ -330,7 +328,8 @@ class SubFactory(ParameteredAttribute): """Retrieve the wrapped factory.Factory subclass.""" if self.factory is None: # Must be a module path - self.factory = utils.import_object(self.factory_module, self.factory_name) + self.factory = utils.import_object( + self.factory_module, self.factory_name) return self.factory def generate(self, create, params): @@ -390,10 +389,6 @@ class PostGeneration(PostGenerationDeclaration): return self.function(obj, create, extracted, **kwargs) -def post_generation(fun): - return PostGeneration(fun) - - class RelatedFactory(PostGenerationDeclaration): """Calls a factory once the object has been generated. @@ -414,7 +409,7 @@ class RelatedFactory(PostGenerationDeclaration): self.factory_module = self.factory_name = '' else: # Must be a string - if not isinstance(factory, compat.string_types) or '.' not in factory: + if not (compat.is_string(factory) and '.' in factory): raise ValueError( "The argument of a SubFactory must be either a class " "or the fully qualified path to a Factory class; got " @@ -426,7 +421,8 @@ class RelatedFactory(PostGenerationDeclaration): """Retrieve the wrapped factory.Factory subclass.""" if self.factory is None: # Must be a module path - self.factory = utils.import_object(self.factory_module, self.factory_name) + self.factory = utils.import_object( + self.factory_module, self.factory_name) return self.factory def call(self, obj, create, extracted=None, **kwargs): @@ -450,7 +446,7 @@ class PostGenerationMethodCall(PostGenerationDeclaration): Example: class UserFactory(factory.Factory): ... - password = factory.PostGenerationMethodCall('set_password', password='') + password = factory.PostGenerationMethodCall('set_pass', password='') """ def __init__(self, method_name, *args, **kwargs): super(PostGenerationMethodCall, self).__init__() @@ -472,22 +468,3 @@ class PostGenerationMethodCall(PostGenerationDeclaration): passed_kwargs.update(kwargs) method = getattr(obj, self.method_name) method(*passed_args, **passed_kwargs) - - -# Decorators... in case lambdas don't cut it - -def lazy_attribute(func): - return LazyAttribute(func) - -def iterator(func): - """Turn a generator function into an iterator attribute.""" - return Iterator(func()) - -def sequence(func): - return Sequence(func) - -def lazy_attribute_sequence(func): - return LazyAttributeSequence(func) - -def container_attribute(func): - return ContainerAttribute(func, strict=False) diff --git a/factory/helpers.py b/factory/helpers.py new file mode 100644 index 0000000..8f0d161 --- /dev/null +++ b/factory/helpers.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2010 Mark Sandstrom +# Copyright (c) 2011-2013 Raphaƫl Barrois +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + + +"""Simple wrappers around Factory class definition.""" + + +from . import base +from . import declarations + + +def make_factory(klass, **kwargs): + """Create a new, simple factory for the given class.""" + factory_name = '%sFactory' % klass.__name__ + kwargs[base.FACTORY_CLASS_DECLARATION] = klass + base_class = kwargs.pop('FACTORY_CLASS', base.Factory) + + factory_class = type(base.Factory).__new__( + type(base.Factory), factory_name, (base_class,), kwargs) + factory_class.__name__ = '%sFactory' % klass.__name__ + factory_class.__doc__ = 'Auto-generated factory for class %s' % klass + return factory_class + + +def build(klass, **kwargs): + """Create a factory for the given class, and build an instance.""" + return make_factory(klass, **kwargs).build() + + +def build_batch(klass, size, **kwargs): + """Create a factory for the given class, and build a batch of instances.""" + return make_factory(klass, **kwargs).build_batch(size) + + +def create(klass, **kwargs): + """Create a factory for the given class, and create an instance.""" + return make_factory(klass, **kwargs).create() + + +def create_batch(klass, size, **kwargs): + """Create a factory for the given class, and create a batch of instances.""" + return make_factory(klass, **kwargs).create_batch(size) + + +def stub(klass, **kwargs): + """Create a factory for the given class, and stub an instance.""" + return make_factory(klass, **kwargs).stub() + + +def stub_batch(klass, size, **kwargs): + """Create a factory for the given class, and stub a batch of instances.""" + return make_factory(klass, **kwargs).stub_batch(size) + + +def generate(klass, strategy, **kwargs): + """Create a factory for the given class, and generate an instance.""" + return make_factory(klass, **kwargs).generate(strategy) + + +def generate_batch(klass, strategy, size, **kwargs): + """Create a factory for the given class, and generate instances.""" + return make_factory(klass, **kwargs).generate_batch(strategy, size) + + +# We're reusing 'create' as a keyword. +# pylint: disable=W0621 + + +def simple_generate(klass, create, **kwargs): + """Create a factory for the given class, and simple_generate an instance.""" + return make_factory(klass, **kwargs).simple_generate(create) + + +def simple_generate_batch(klass, create, size, **kwargs): + """Create a factory for the given class, and simple_generate instances.""" + return make_factory(klass, **kwargs).simple_generate_batch(create, size) + + +# pylint: enable=W0621 + + +def lazy_attribute(func): + return declarations.LazyAttribute(func) + + +def iterator(func): + """Turn a generator function into an iterator attribute.""" + return declarations.Iterator(func()) + + +def sequence(func): + return declarations.Sequence(func) + + +def lazy_attribute_sequence(func): + return declarations.LazyAttributeSequence(func) + + +def container_attribute(func): + return declarations.ContainerAttribute(func, strict=False) + + +def post_generation(fun): + return declarations.PostGeneration(fun) diff --git a/tests/test_declarations.py b/tests/test_declarations.py index 7b9b0af..4c08dfa 100644 --- a/tests/test_declarations.py +++ b/tests/test_declarations.py @@ -25,6 +25,7 @@ import itertools import warnings from factory import declarations +from factory import helpers from .compat import mock, unittest from . import tools @@ -124,7 +125,7 @@ class PostGenerationDeclarationTestCase(unittest.TestCase): def test_decorator_simple(self): call_params = [] - @declarations.post_generation + @helpers.post_generation def foo(*args, **kwargs): call_params.append(args) call_params.append(kwargs) -- cgit v1.2.3