Skip to content

Commit

Permalink
- Removed presets from factory (with the benefit of hindsight presets…
Browse files Browse the repository at this point in the history
… created more problems than they solved).

- Added a `document` property to makers which is set to the target document when assemble or finish is called using the `target` context method (blueprints now use this).
  • Loading branch information
anthonyjb committed Aug 16, 2016
1 parent 120fa8e commit d8a7a96
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 274 deletions.
24 changes: 9 additions & 15 deletions mongoframes/factory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,6 @@ class Factory:
`Frame` classes to modify the insert behaviour for factories.
"""

def __init__(self, presets=None):
# A list of presets for the factory
self._presets = presets or []

# Read-only properties

@property
def presets(self):
return self._presets

# Public methods

def assemble(self, blueprint, quota):
Expand All @@ -54,7 +44,7 @@ def assemble(self, blueprint, quota):
# Assemble the documents
documents = []
for i in range(0, int(quota)):
documents.append(blueprint.assemble(self.presets))
documents.append(blueprint.assemble())

return documents

Expand All @@ -67,7 +57,7 @@ def finish(self, blueprint, documents):
# Finish the documents
finished = []
for document in documents:
finished.append(blueprint.finish(document, self.presets))
finished.append(blueprint.finish(document))

return finished

Expand All @@ -80,10 +70,14 @@ def populate(self, blueprint, documents):
# Convert the documents to frame instances
frames = []
for document in documents:
[frame_document, meta_document] = document
# Separate out any meta fields
meta_document = {}
for field_name in blueprint._meta_fields:
meta_document[field_name] = document[field_name]
document.pop(field_name)

# Initialize the frame
frame = blueprint.get_frame_cls()(frame_document)
frame = blueprint.get_frame_cls()(document)

# Apply any meta fields
for key, value in meta_document.items():
Expand Down Expand Up @@ -113,4 +107,4 @@ def reassemble(self, blueprint, fields, documents):

# Reassemble the documents
for document in documents:
blueprint.reassemble(fields, document, self.presets)
blueprint.reassemble(fields, document)
94 changes: 21 additions & 73 deletions mongoframes/factory/blueprints.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from blinker import signal
from collections import OrderedDict

from mongoframes.factory.presets import Preset
from mongoframes.factory.makers import Maker

__all__ = ['Blueprint']
Expand All @@ -19,6 +19,10 @@ def __new__(meta, name, bases, dct):
# field the instruction refers to and the value is a `Maker` type for
# generating a value for that field.
dct['_instructions'] = dct.get('_instructions') or {}

# Ensure the instructions are an ordered dictionary
dct['_instructions'] = OrderedDict(dct['_instructions'])

for k, v in dct.items():
if isinstance(v, Maker):
dct['_instructions'][k] = v
Expand Down Expand Up @@ -68,107 +72,51 @@ def get_meta_fields(cls):
# Factory methods

@classmethod
def assemble(cls, presets=None):
"""Assemble a single document using the blueprint and presets"""
presets = presets or []

def assemble(cls):
"""Assemble a single document using the blueprint"""
document = {}

fields = cls._frame_cls.get_fields() | cls._meta_fields

for field_name in fields:

# Use a dedicated instruction if we have one
if field_name in cls._instructions:
maker = cls._instructions[field_name]
if maker:
document[field_name] = maker()
continue

# Check for a preset
preset = Preset.find(presets, field_name)
if preset:
document[field_name] = preset.maker()
continue

for field_name, maker in cls._instructions.items():
with maker.target(document):
document[field_name] = maker()
return document

@classmethod
def finish(cls, document, presets=None):
def finish(cls, document):
"""
Take a pre-assembled document and convert all dynamic values to static
values.
"""
presets = presets or []

document_copy = {}
meta_document = {}
for field_name, value in document.items():

# Use a dedicated instruction if we have one
if field_name in cls._instructions:
maker = cls._instructions[field_name]
if field_name in cls._meta_fields:
meta_document[field_name] = maker(value)
else:
document_copy[field_name] = maker(value)
continue

# Check for a preset
preset = Preset.find(presets, field_name)
if preset:
if field_name in cls._meta_fields:
meta_document[field_name] = preset.maker(value)
else:
document_copy[field_name] = preset.maker(value)
continue

return (document_copy, meta_document)
maker = cls._instructions[field_name]
with maker.target(document):
document_copy[field_name] = maker(value)
return document_copy

@classmethod
def reassemble(cls, fields, document, presets=None):
def reassemble(cls, fields, document):
"""
Take a pre-assembled document and reassemble the given set of fields
for it.
for it in place.
"""

# Filter the field list to just those we can set against the frame
fields = [f for f in fields \
if f in cls._frame_cls.get_fields() | cls._meta_fields]

for field_name in fields:

# Use a dedicated instruction if we have one
if field_name in cls._instructions:
for field_name in cls._instructions:
if field_name in fields:
maker = cls._instructions[field_name]
if maker:
with maker.target(document):
document[field_name] = maker()
continue

# Check for a preset
preset = Preset.find(presets, field_name)
if preset:
document[field_name] = preset.maker()
continue

@classmethod
def reset(cls, presets=None):
def reset(cls):
"""
Reset the blueprint. Blueprints are typically reset before being used to
assemble a quota of documents. Resetting a blueprint will in turn reset
all the makers for the blueprint allowing internal counters and a like
to be reset.
"""
presets = presets or []

# Reset instructions
for maker in cls._instructions.values():
maker.reset()

# Check for a preset
for preset in presets:
preset.maker.reset()

# Events

@classmethod
Expand Down
49 changes: 37 additions & 12 deletions mongoframes/factory/makers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import contextlib
import random

import faker
Expand All @@ -21,11 +22,21 @@ class Maker:
A base class for all Maker classes.
"""

def __init__(self):

# The document the maker is assembling/finishing data for
self._document = None

def __call__(self, *args):
if args:
return self._finish(*args)
return self._assemble()

@property
def document(self):
# Return the target document
return self._document

def reset(self):
"""Reset the maker instance"""
pass
Expand All @@ -36,6 +47,12 @@ def _assemble(self):
def _finish(self, value):
return value

@contextlib.contextmanager
def target(self, document):
self._document = document
yield
self._document = None


class DictOf(Maker):
"""
Expand All @@ -44,6 +61,7 @@ class DictOf(Maker):
"""

def __init__(self, table):
super().__init__()

# The table of keyword arguments that will be used to generate the
# dictionary.
Expand Down Expand Up @@ -78,6 +96,7 @@ class Faker(Maker):
default_locale = 'en_US'

def __init__(self, provider, assembler=True, locale=None, **kwargs):
super().__init__()

# The provider that will be used to generate the value
self._provider = provider
Expand Down Expand Up @@ -121,6 +140,7 @@ class Lambda(Maker):
"""

def __init__(self, func, assembler=True, finisher=False):
super().__init__()

assert assembler or finisher, \
'Either `assembler` or `finisher` must be true for lambda'
Expand Down Expand Up @@ -151,6 +171,7 @@ class ListOf(Maker):
"""

def __init__(self, maker, quantity, reset_maker=False):
super().__init__()

# The maker used to generate each value in the list
self._maker = maker
Expand Down Expand Up @@ -184,6 +205,7 @@ class Reference(Maker):
"""

def __init__(self, frame_cls, field_name, value):
super().__init__()

# The `Frame` class that will be used to obtain the referenced document
self._frame_cls = frame_cls
Expand Down Expand Up @@ -224,6 +246,7 @@ class Static(Maker):
"""

def __init__(self, value, assembler=True):
super().__init__()

# The value to return
self._value = value
Expand All @@ -248,29 +271,30 @@ class SubFactory(Maker):
A maker that makes sub-documents.
"""

def __init__(self, blueprint, presets=None):
def __init__(self, blueprint):
super().__init__()

# The blueprint to produce
self._blueprint = blueprint

# A list of presets to apply with the blueprint
self._presets = presets or []

def reset(self):
"""Reset the associated blueprint and presets"""
self._blueprint.reset(self._presets)
"""Reset the blueprint for the sub-factory"""
self._blueprint.reset()

def _assemble(self):
return self._blueprint.assemble(self._presets)
return self._blueprint.assemble()

def _finish(self, value):
[frame_document, meta_document] = self._blueprint.finish(
value,
self._presets
)
document = self._blueprint.finish(value)

# Separate out any meta fields
meta_document = {}
for field_name in self._blueprint._meta_fields:
meta_document[field_name] = document[field_name]
document.pop(field_name)

# Initialize the sub-frame
sub_frame = self._blueprint.get_frame_cls()(frame_document)
sub_frame = self._blueprint.get_frame_cls()(document)

# Apply any meta fields
for key, value in meta_document.items():
Expand All @@ -290,6 +314,7 @@ def __init__(self,
assembler=True,
max_attempts=1000
):
super().__init__()

# The maker that will generate values
self._maker = maker
Expand Down
2 changes: 2 additions & 0 deletions mongoframes/factory/makers/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class DateBetween(Maker):
"""

def __init__(self, min_date, max_date):
super().__init__()

# The date range between which a date will be selected
self._min_date = min_date
self._max_date = max_date
Expand Down
1 change: 1 addition & 0 deletions mongoframes/factory/makers/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def __init__(self,
service_url='//fakeimg.pl',
service_formatter=None
):
super().__init__()

# The size of the image to generate
self._width = width
Expand Down
4 changes: 4 additions & 0 deletions mongoframes/factory/makers/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class Counter(Maker):
"""

def __init__(self, start_from=1, step=1):
super().__init__()

self._start_from = int(start_from)
self._step = step
self._counter = self._start_from
Expand All @@ -32,6 +34,8 @@ class Float(Maker):
"""

def __init__(self, min_value, max_value):
super().__init__()

self._min_value = min_value
self._max_value = max_value

Expand Down
Loading

0 comments on commit d8a7a96

Please sign in to comment.