From f023e5a477668b8374a75c78e87d946b21a27f15 Mon Sep 17 00:00:00 2001 From: Alexey Kotlyarov Date: Tue, 8 Dec 2015 12:57:24 +1100 Subject: Don't leave AttributeBuilder in an inconsistent state on exceptions When one of the LazyValues raises an exception, don't leave its name in __pending stack of the AttributeBuilder, preventing evaluation of any other LazyValues. --- factory/containers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'factory') diff --git a/factory/containers.py b/factory/containers.py index 0ae354b..ec33ca1 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -102,8 +102,10 @@ class LazyStub(object): val = self.__attrs[name] if isinstance(val, LazyValue): self.__pending.append(name) - val = val.evaluate(self, self.__containers) - last = self.__pending.pop() + try: + val = val.evaluate(self, self.__containers) + finally: + last = self.__pending.pop() assert name == last self.__values[name] = val return val -- cgit v1.2.3 From f2c075c40fd331b7d26a9db72aad249b2165eac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Cauwelier?= Date: Fri, 12 Feb 2016 17:31:04 +0100 Subject: factory: LazyFunction to just call a function in the simplest case No need to wrap it in a lambda to strip the object argument from LazyAttribute or the sequence argument from Sequence. --- factory/__init__.py | 1 + factory/declarations.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'factory') diff --git a/factory/__init__.py b/factory/__init__.py index c8bc396..1fa581b 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -43,6 +43,7 @@ from .base import ( from .faker import Faker from .declarations import ( + LazyFunction, LazyAttribute, Iterator, Sequence, diff --git a/factory/declarations.py b/factory/declarations.py index f0dbfe5..9ab7462 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -57,6 +57,23 @@ class OrderedDeclaration(object): raise NotImplementedError('This is an abstract method') +class LazyFunction(OrderedDeclaration): + """Simplest OrderedDeclaration computed by calling the given function. + + Attributes: + function (function): a function without arguments and + returning the computed value. + """ + + def __init__(self, function, *args, **kwargs): + super(LazyFunction, self).__init__(*args, **kwargs) + self.function = function + + def evaluate(self, sequence, obj, create, extra=None, containers=()): + logger.debug("LazyFunction: Evaluating %r on %r", self.function, obj) + return self.function() + + class LazyAttribute(OrderedDeclaration): """Specific OrderedDeclaration computed using a lambda. -- cgit v1.2.3 From efc8a7e873aaab5170e1dc2477aaf92c6fb59fc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Mon, 15 Feb 2016 00:00:03 +0100 Subject: fuzzy: Minor cleanup in BaseFuzzyDateTime The ``_now()`` method wasn't declared on the base class, only in its subclasses. --- factory/fuzzy.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'factory') diff --git a/factory/fuzzy.py b/factory/fuzzy.py index a7e834c..71d1884 100644 --- a/factory/fuzzy.py +++ b/factory/fuzzy.py @@ -217,6 +217,9 @@ class BaseFuzzyDateTime(BaseFuzzyAttribute): """%s boundaries should have start <= end, got %r > %r""" % ( self.__class__.__name__, start_dt, end_dt)) + def _now(self): + raise NotImplementedError() + def __init__(self, start_dt, end_dt=None, force_year=None, force_month=None, force_day=None, force_hour=None, force_minute=None, force_second=None, -- cgit v1.2.3 From eea28cce1544021f3d152782c9932a20402d6240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sat, 2 Apr 2016 16:11:58 +0200 Subject: Refactor: move error defs to a dedicated module. --- factory/__init__.py | 7 +++++-- factory/base.py | 31 ++++++++----------------------- factory/containers.py | 8 ++------ factory/errors.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 31 deletions(-) create mode 100644 factory/errors.py (limited to 'factory') diff --git a/factory/__init__.py b/factory/__init__.py index 1fa581b..ccd71cd 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -32,14 +32,17 @@ from .base import ( ListFactory, StubFactory, - FactoryError, - BUILD_STRATEGY, CREATE_STRATEGY, STUB_STRATEGY, use_strategy, ) + +from .errors import ( + FactoryError, +) + from .faker import Faker from .declarations import ( diff --git a/factory/base.py b/factory/base.py index 0f2af59..1ddb742 100644 --- a/factory/base.py +++ b/factory/base.py @@ -24,6 +24,7 @@ import logging from . import containers from . import declarations +from . import errors from . import utils logger = logging.getLogger('factory.generate') @@ -35,22 +36,6 @@ STUB_STRATEGY = 'stub' -class FactoryError(Exception): - """Any exception raised by factory_boy.""" - - -class AssociatedClassError(FactoryError): - """Exception for Factory subclasses lacking Meta.model.""" - - -class UnknownStrategy(FactoryError): - """Raised when a factory uses an unknown strategy.""" - - -class UnsupportedStrategy(FactoryError): - """Raised when trying to use a strategy on an incompatible Factory.""" - - # Factory metaclasses def get_factory_bases(bases): @@ -82,7 +67,7 @@ class FactoryMetaClass(type): elif cls._meta.strategy == STUB_STRATEGY: return cls.stub(**kwargs) else: - raise UnknownStrategy('Unknown Meta.strategy: {0}'.format( + raise errors.UnknownStrategy('Unknown Meta.strategy: {0}'.format( cls._meta.strategy)) def __new__(mcs, class_name, bases, attrs): @@ -296,12 +281,12 @@ class BaseFactory(object): """Factory base support for sequences, attributes and stubs.""" # Backwards compatibility - UnknownStrategy = UnknownStrategy - UnsupportedStrategy = UnsupportedStrategy + UnknownStrategy = errors.UnknownStrategy + UnsupportedStrategy = errors.UnsupportedStrategy def __new__(cls, *args, **kwargs): """Would be called if trying to instantiate the class.""" - raise FactoryError('You cannot instantiate BaseFactory') + raise errors.FactoryError('You cannot instantiate BaseFactory') _meta = FactoryOptions() @@ -477,7 +462,7 @@ class BaseFactory(object): attrs (dict): attributes to use for generating the object """ if cls._meta.abstract: - raise FactoryError( + raise errors.FactoryError( "Cannot generate instances of abstract factory %(f)s; " "Ensure %(f)s.Meta.model is set and %(f)s.Meta.abstract " "is either not set or False." % dict(f=cls.__name__)) @@ -680,7 +665,7 @@ Factory = FactoryMetaClass('Factory', (BaseFactory,), { # Backwards compatibility -Factory.AssociatedClassError = AssociatedClassError # pylint: disable=W0201 +Factory.AssociatedClassError = errors.AssociatedClassError # pylint: disable=W0201 class StubFactory(Factory): @@ -695,7 +680,7 @@ class StubFactory(Factory): @classmethod def create(cls, **kwargs): - raise UnsupportedStrategy() + raise errors.UnsupportedStrategy() class BaseDictFactory(Factory): diff --git a/factory/containers.py b/factory/containers.py index 0ae354b..c591988 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -25,13 +25,10 @@ import logging logger = logging.getLogger(__name__) from . import declarations +from . import errors from . import utils -class CyclicDefinitionError(Exception): - """Raised when cyclic definition were found.""" - - class LazyStub(object): """A generic container that only allows getting attributes. @@ -93,7 +90,7 @@ class LazyStub(object): attributes being computed. """ if name in self.__pending: - raise CyclicDefinitionError( + raise errors.CyclicDefinitionError( "Cyclic lazy attribute definition for %s; cycle found in %r." % (name, self.__pending)) elif name in self.__values: @@ -112,7 +109,6 @@ class LazyStub(object): "The parameter %s is unknown. Evaluated attributes are %r, " "definitions are %r." % (name, self.__values, self.__attrs)) - def __setattr__(self, name, value): """Prevent setting attributes once __init__ is done.""" if not self.__initialized: diff --git a/factory/errors.py b/factory/errors.py new file mode 100644 index 0000000..79d85f4 --- /dev/null +++ b/factory/errors.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2011-2015 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. + + +class FactoryError(Exception): + """Any exception raised by factory_boy.""" + + +class AssociatedClassError(FactoryError): + """Exception for Factory subclasses lacking Meta.model.""" + + +class UnknownStrategy(FactoryError): + """Raised when a factory uses an unknown strategy.""" + + +class UnsupportedStrategy(FactoryError): + """Raised when trying to use a strategy on an incompatible Factory.""" + + +class CyclicDefinitionError(FactoryError): + """Raised when a cyclical declaration occurs.""" + + -- cgit v1.2.3 From c77962de7dd7206ccab85b44da173832acbf5921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sat, 2 Apr 2016 16:13:34 +0200 Subject: Add a new Params section to factories. This handles parameters that alter the declarations of a factory. A few technical notes: - A parameter's outcome may alter other parameters - In order to fix that, we perform a (simple) cyclic definition detection at class declaration time. - Parameters may only be either naked values or ComplexParameter subclasses - Parameters are never passed to the underlying class --- factory/base.py | 44 +++++++++++++++++++- factory/containers.py | 108 ++++++++++++++++++++++++++++++++++++++++++++---- factory/declarations.py | 33 +++++++++++++++ factory/utils.py | 4 +- 4 files changed, 177 insertions(+), 12 deletions(-) (limited to 'factory') diff --git a/factory/base.py b/factory/base.py index 1ddb742..282e3b1 100644 --- a/factory/base.py +++ b/factory/base.py @@ -20,6 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. +import collections import logging from . import containers @@ -92,6 +93,7 @@ class FactoryMetaClass(type): base_factory = None attrs_meta = attrs.pop('Meta', None) + attrs_params = attrs.pop('Params', None) base_meta = resolve_attribute('_meta', bases) options_class = resolve_attribute('_options_class', bases, FactoryOptions) @@ -106,6 +108,7 @@ class FactoryMetaClass(type): meta=attrs_meta, base_meta=base_meta, base_factory=base_factory, + params=attrs_params, ) return new_class @@ -148,6 +151,8 @@ class FactoryOptions(object): self.base_factory = None self.declarations = {} self.postgen_declarations = {} + self.parameters = {} + self.parameters_dependencies = {} def _build_default_options(self): """"Provide the default value for all allowed fields. @@ -186,7 +191,7 @@ class FactoryOptions(object): % (self.factory, ','.join(sorted(meta_attrs.keys())))) def contribute_to_class(self, factory, - meta=None, base_meta=None, base_factory=None): + meta=None, base_meta=None, base_factory=None, params=None): self.factory = factory self.base_factory = base_factory @@ -204,6 +209,7 @@ class FactoryOptions(object): continue self.declarations.update(parent._meta.declarations) self.postgen_declarations.update(parent._meta.postgen_declarations) + self.parameters.update(parent._meta.parameters) for k, v in vars(self.factory).items(): if self._is_declaration(k, v): @@ -211,6 +217,13 @@ class FactoryOptions(object): if self._is_postgen_declaration(k, v): self.postgen_declarations[k] = v + if params is not None: + for k, v in vars(params).items(): + if not k.startswith('_'): + self.parameters[k] = v + + self.parameters_dependencies = self._compute_parameter_dependencies(self.parameters) + def _get_counter_reference(self): """Identify which factory should be used for a shared counter.""" @@ -242,6 +255,32 @@ class FactoryOptions(object): """Captures instances of PostGenerationDeclaration.""" return isinstance(value, declarations.PostGenerationDeclaration) + def _compute_parameter_dependencies(self, parameters): + """Find out in what order parameters should be called.""" + # Warning: parameters only provide reverse dependencies; we reverse them into standard dependencies. + # deep_revdeps: set of fields a field depend indirectly upon + deep_revdeps = collections.defaultdict(set) + # Actual, direct dependencies + deps = collections.defaultdict(set) + + for name, parameter in parameters.items(): + if isinstance(parameter, declarations.ComplexParameter): + field_revdeps = parameter.get_revdeps(parameters) + if not field_revdeps: + continue + deep_revdeps[name] = set.union(*(deep_revdeps[dep] for dep in field_revdeps)) + deep_revdeps[name] |= set(field_revdeps) + for dep in field_revdeps: + deps[dep].add(name) + + # Check for cyclical dependencies + cyclic = [name for name, field_deps in deep_revdeps.items() if name in field_deps] + if cyclic: + raise errors.CyclicDefinitionError( + "Cyclic definition detected on %s' Params around %s" + % (self.factory, ', '.join(cyclic))) + return deps + def __str__(self): return "<%s for %s>" % (self.__class__.__name__, self.factory.__class__.__name__) @@ -439,6 +478,9 @@ class BaseFactory(object): # Remove 'hidden' arguments. for arg in cls._meta.exclude: del kwargs[arg] + # Remove parameters, if defined + for arg in cls._meta.parameters: + kwargs.pop(arg, None) # Extract *args from **kwargs args = tuple(kwargs.pop(key) for key in cls._meta.inline_args) diff --git a/factory/containers.py b/factory/containers.py index c591988..d3f39c4 100644 --- a/factory/containers.py +++ b/factory/containers.py @@ -117,6 +117,69 @@ class LazyStub(object): raise AttributeError('Setting of object attributes is not allowed') +class DeclarationStack(object): + """An ordered stack of declarations. + + This is intended to handle declaration precedence among different mutating layers. + """ + def __init__(self, ordering): + self.ordering = ordering + self.layers = dict((name, {}) for name in self.ordering) + + def __getitem__(self, key): + return self.layers[key] + + def __setitem__(self, key, value): + assert key in self.ordering + self.layers[key] = value + + def current(self): + """Retrieve the current, flattened declarations dict.""" + result = {} + for layer in self.ordering: + result.update(self.layers[layer]) + return result + + +class ParameterResolver(object): + """Resolve a factory's parameter declarations.""" + def __init__(self, parameters, deps): + self.parameters = parameters + self.deps = deps + self.declaration_stack = None + + self.resolved = set() + + def resolve_one(self, name): + """Compute one field is needed, taking dependencies into accounts.""" + if name in self.resolved: + return + + for dep in self.deps.get(name, ()): + self.resolve_one(dep) + + self.compute(name) + self.resolved.add(name) + + def compute(self, name): + """Actually compute the value for a given name.""" + value = self.parameters[name] + if isinstance(value, declarations.ComplexParameter): + overrides = value.compute(name, self.declaration_stack.current()) + else: + overrides = {name: value} + self.declaration_stack['overrides'].update(overrides) + + def resolve(self, declaration_stack): + """Resolve parameters for a given declaration stack. + + Modifies the stack in-place. + """ + self.declaration_stack = declaration_stack + for name in self.parameters: + self.resolve_one(name) + + class LazyValue(object): """Some kind of "lazy evaluating" object.""" @@ -125,7 +188,7 @@ class LazyValue(object): raise NotImplementedError("This is an abstract method.") -class OrderedDeclarationWrapper(LazyValue): +class DeclarationWrapper(LazyValue): """Lazy wrapper around an OrderedDeclaration. Attributes: @@ -136,7 +199,7 @@ class OrderedDeclarationWrapper(LazyValue): """ def __init__(self, declaration, sequence, create, extra=None, **kwargs): - super(OrderedDeclarationWrapper, self).__init__(**kwargs) + super(DeclarationWrapper, self).__init__(**kwargs) self.declaration = declaration self.sequence = sequence self.create = create @@ -166,7 +229,7 @@ class AttributeBuilder(object): Attributes: factory (base.Factory): the Factory for which attributes are being built - _attrs (DeclarationDict): the attribute declarations for the factory + _declarations (DeclarationDict): the attribute declarations for the factory _subfields (dict): dict mapping an attribute name to a dict of overridden default values for the related SubFactory. """ @@ -179,20 +242,47 @@ class AttributeBuilder(object): self.factory = factory self._containers = extra.pop('__containers', ()) - self._attrs = factory.declarations(extra) + + initial_declarations = dict(factory._meta.declarations) self._log_ctx = log_ctx - initial_declarations = factory.declarations({}) + # Parameters + # ---------- + self._declarations = self.merge_declarations(initial_declarations, extra) + + # Subfields + # --------- + attrs_with_subfields = [ k for k, v in initial_declarations.items() - if self.has_subfields(v)] + if self.has_subfields(v) + ] + # Extract subfields; THIS MODIFIES self._declarations. self._subfields = utils.multi_extract_dict( - attrs_with_subfields, self._attrs) + attrs_with_subfields, self._declarations) def has_subfields(self, value): return isinstance(value, declarations.ParameteredAttribute) + def merge_declarations(self, initial, extra): + """Compute the final declarations, taking into account paramter-based overrides.""" + # Precedence order: + # - Start with class-level declarations + # - Add overrides from parameters + # - Finally, use callsite-level declarations & values + declaration_stack = DeclarationStack(['initial', 'overrides', 'extra']) + declaration_stack['initial'] = initial.copy() + declaration_stack['extra'] = extra.copy() + + # Actually compute the final stack + resolver = ParameterResolver( + parameters=self.factory._meta.parameters, + deps=self.factory._meta.parameters_dependencies, + ) + resolver.resolve(declaration_stack) + return declaration_stack.current() + def build(self, create, force_sequence=None): """Build a dictionary of attributes. @@ -210,9 +300,9 @@ class AttributeBuilder(object): # Parse attribute declarations, wrapping SubFactory and # OrderedDeclaration. wrapped_attrs = {} - for k, v in self._attrs.items(): + for k, v in self._declarations.items(): if isinstance(v, declarations.OrderedDeclaration): - v = OrderedDeclarationWrapper(v, + v = DeclarationWrapper(v, sequence=sequence, create=create, extra=self._subfields.get(k, {}), diff --git a/factory/declarations.py b/factory/declarations.py index 9ab7462..ad1f72f 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -440,6 +440,39 @@ class List(SubFactory): **params) +# Parameters +# ========== + + +class ComplexParameter(object): + """A complex parameter, to be used in a Factory.Params section. + + Must implement: + - A "compute" function, performing the actual declaration override + - Optionally, a get_revdeps() function (to compute other parameters it may alter) + """ + + def compute(self, field_name, declarations): + """Compute the overrides for this parameter. + + Args: + - field_name (str): the field this parameter is installed at + - declarations (dict): the global factory declarations + + Returns: + dict: the declarations to override + """ + raise NotImplementedError() + + def get_revdeps(self, parameters): + """Retrieve the list of other parameters modified by this one.""" + return [] + + +# Post-generation +# =============== + + class ExtractionContext(object): """Private class holding all required context from extraction to postgen.""" def __init__(self, value=None, did_extract=False, extra=None, for_field=''): diff --git a/factory/utils.py b/factory/utils.py index 15dba0a..cfae4ec 100644 --- a/factory/utils.py +++ b/factory/utils.py @@ -35,7 +35,7 @@ def extract_dict(prefix, kwargs, pop=True, exclude=()): Args: prefix (str): the prefix to use for lookups - kwargs (dict): the dict from which values should be extracted + kwargs (dict): the dict from which values should be extracted; WILL BE MODIFIED. pop (bool): whether to use pop (True) or get (False) exclude (iterable): list of prefixed keys that shouldn't be extracted @@ -68,7 +68,7 @@ def multi_extract_dict(prefixes, kwargs, pop=True, exclude=()): Args: prefixes (str list): the prefixes to use for lookups - kwargs (dict): the dict from which values should be extracted + kwargs (dict): the dict from which values should be extracted; WILL BE MODIFIED. pop (bool): whether to use pop (True) or get (False) exclude (iterable): list of prefixed keys that shouldn't be extracted -- cgit v1.2.3 From 03c40fd80707ad4837523a07cdf3f82564ab0259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Sat, 2 Apr 2016 16:14:06 +0200 Subject: Add Traits (Closes #251). Based on a boolean flag, those will alter the definitions of the current factory, taking precedence over pre-defined behavior but overridden by callsite-level arguments. --- factory/__init__.py | 1 + factory/declarations.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) (limited to 'factory') diff --git a/factory/__init__.py b/factory/__init__.py index ccd71cd..ad9da80 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -52,6 +52,7 @@ from .declarations import ( Sequence, LazyAttributeSequence, SelfAttribute, + Trait, ContainerAttribute, SubFactory, Dict, diff --git a/factory/declarations.py b/factory/declarations.py index ad1f72f..895f2ac 100644 --- a/factory/declarations.py +++ b/factory/declarations.py @@ -469,6 +469,22 @@ class ComplexParameter(object): return [] +class Trait(ComplexParameter): + """The simplest complex parameter, it enables a bunch of new declarations based on a boolean flag.""" + def __init__(self, **overrides): + self.overrides = overrides + + def compute(self, field_name, declarations): + if declarations.get(field_name): + return self.overrides + else: + return {} + + def get_revdeps(self, parameters): + """This might alter fields it's injecting.""" + return [param for param in parameters if param in self.overrides] + + # Post-generation # =============== -- cgit v1.2.3 From d9098a809db170f8ffc52efe3e5c6e7f48aa2893 Mon Sep 17 00:00:00 2001 From: Rich Rauenzahn Date: Tue, 12 Apr 2016 14:57:28 -0700 Subject: Add custom error message when django_get_or_create is missing an input. --- factory/django.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'factory') diff --git a/factory/django.py b/factory/django.py index b3c508c..43434c2 100644 --- a/factory/django.py +++ b/factory/django.py @@ -29,6 +29,8 @@ import types import logging import functools +from . import errors + """factory_boy extensions for use with the Django framework.""" try: @@ -157,6 +159,11 @@ class DjangoModelFactory(base.Factory): key_fields = {} for field in cls._meta.django_get_or_create: + if field not in kwargs: + raise errors.FactoryError( + "django_get_or_create - " + "Unable to find initialization value for '%s' in factory %s" % + (field, cls.__name__)) key_fields[field] = kwargs.pop(field) key_fields['defaults'] = kwargs -- cgit v1.2.3 From f1ed74e06dfb6851bc691ebfd8135c875154ad50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Barrois?= Date: Tue, 19 Apr 2016 22:54:36 +0200 Subject: Release version 2.7.0 --- factory/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'factory') diff --git a/factory/__init__.py b/factory/__init__.py index ad9da80..421e1ab 100644 --- a/factory/__init__.py +++ b/factory/__init__.py @@ -20,7 +20,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -__version__ = '2.6.1' +__version__ = '2.7.0' __author__ = 'Raphaël Barrois ' -- cgit v1.2.3