File format¶
charlatan only supports YAML at time of writing.
Fixtures are defined in a YAML file. Here is its general structure:
toaster:
fields:
brand: Flying
number_of_toasts: 2
toasts: [!rel toast1, !rel toast2]
bought: !now -1y
model: Toaster
toaster_already_in_db:
id: 10
model: Toaster
toast1:
fields:
bread: brioche
model: .toast:Toast
toast2:
fields:
bread: campagne
model: .toast:Toast
user:
fields:
name: Michel Audiard
toaster: !rel toaster
model: toaster.models.user:User
post_creation:
has_used_toaster: true
In this example:
toaster
,toast1
andtoast2
are the fixture keys.model
is where to get the model. Both relative and absolute addressing are supportedfields
are provided as argument when instantiating the class:Toaster(**fields)
.!rel
lets you create relationships by pointing to another fixture key.!now
lets you enter timestamps. It supports basic operations (adding/subtracting days, months, years). It is evaluated when the fixture is instantiated.!epoch_now
generates epoch timestamps and supports the same operations as!now
.
Note
Inside fields
, !now
is supported only as a first level list item,
or as a dictionary value.
Defining a fixture¶
A fixture has an identifier (in the example above, toaster
is one of
the fixture identifiers), as well as the following configuration:
fields
: a dictionary for which keys are attribute, and values are their valuesmodel
gives information about how to retrieve the modelpost_creation
lets you have some attribute values be assigned after instantiation.
Inheritance¶
Fixtures can inherit from other fixtures.
first:
fields:
foo: bar
# Everything is inherited from first.
second:
inherit_from: first
# You can add fields without removing existing ones
third:
inherit_from: first
fields:
# foo: bar is implied by inheritance
toaster: toasted
# You can also overwrite the model.
fourth:
inherit_from: first
model: collections:Counter
# You can also overwrite both.
fifth:
inherit_from: second
fields:
toaster: toasted
model: collections:Counter
>>> import pprint
>>> from charlatan import FixturesManager
>>> manager = FixturesManager()
>>> manager.load("docs/examples/fixtures_inheritance.yaml")
>>> manager.get_fixture("first")
{'foo': 'bar'}
>>> manager.get_fixture("second")
{'foo': 'bar'}
>>> pprint.pprint(manager.get_fixture("third"))
{'foo': 'bar', 'toaster': 'toasted'}
>>> fourth = manager.get_fixture("fourth")
>>> fourth
Counter({'foo': 'bar'})
>>> fourth.__class__.__name__
'Counter'
>>> fifth = manager.get_fixture("fifth")
>>> fifth
Counter({'toaster': 'toasted', 'foo': 'bar'})
>>> fifth.__class__.__name__
'Counter'
If your fields are dict, then the first-level key will override everything,
unless you use deep_inherit
:
toaster:
fields:
toasts:
toast1:
type: brioche
price: 10
weight: 20
toaster2:
inherit_from: toaster
deep_inherit: true
fields:
toasts:
toast1:
type: bread
# Because of deep_inherit, the following fields are implied:
# price: 10
# weight: 20
Example test:
from charlatan import FixturesManager
def test_deep_inherit():
manager = FixturesManager()
manager.load('./charlatan/tests/example/data/deep_inherit.yaml')
toaster2 = manager.get_fixture('toaster2')
assert toaster2['toasts']['toast1']['price'] == 10
assert toaster2['toasts']['toast1']['weight'] == 20
New in version 0.4.5: You can use deep_inherit
to trigger nested inheritance for dicts.
New in version 0.2.4: Fixtures can now inherits from other fixtures.
Having dictionaries as fixtures¶
If you don’t specify the model, the content of fields
will be returned as
is. This is useful if you want to enter a dictionary or a list directly.
fixture_name:
fields:
foo: bar
fixture_list:
fields:
- "foo"
- "bar"
>>> manager = FixturesManager()
>>> manager.load("docs/examples/fixtures_dict.yaml")
>>> manager.get_fixture("fixture_name")
{'foo': 'bar'}
>>> manager.get_fixture("fixture_list")
['foo', 'bar']
New in version 0.2.4: Empty models are allowed so that dict ands lists can be used as fixtures.
Getting an already existing fixture from the database¶
You can also get a fixture directly from the database (it uses sqlalchemy
):
in this case, you just need to specify the model
and an id
.
toaster_already_in_db:
id: 10
model: Toaster
Dependencies¶
If a fixture depends on some side effect of another fixture, you can mark
that dependency (and, necessarily, ordering) by using the depend_on
section.
fixture1:
fields:
- name: "foo"
fixture2:
depend_on:
- fixture1
fields:
- name: "bar"
post_creation:
- some_descriptor_that_depend_on_fixture1: true
New in version 0.2.7.
Post creation¶
Example:
user:
fields:
name: Michel Audiard
model: User
post_creation:
has_used_toaster: true
# Note that rel are allowed in post_creation
new_toaster: !rel blue_toaster
For a given fixture, post_creation
lets you change some attributes after
instantiation. Here’s the pseudo-code:
instance = ObjectClass(**fields)
for k, v in post_creation:
setattr(instance, k, v)
New in version 0.2.0: It is now possible to use rel
in post_creation.
Linking to other objects¶
Example:
toaster:
model: Toaster
fields:
color: red
user:
model: User
fields:
# You can link to another fixture
toasters:
- !rel toaster
toaster_colors:
# You can also link to a specific attribute
fields:
color: !rel toaster.color
To link to another object defined in the configuration file, use !rel
. You
can link to another objet (e.g. !rel toaster
) or to another object’s
attribute (e.g. !rel toaster.color
).
>>> manager = FixturesManager()
>>> manager.load("docs/examples/relationships.yaml",
... models_package="charlatan.tests.fixtures.simple_models")
>>> manager.get_fixture("user").toasters
[<Toaster 'red'>]
>>> manager.get_fixture("toaster_colors")
{'color': 'red'}
You can also link to specific attributes of collection’s item (see Collections of Fixtures for more information about collections).
toaster_colors_list:
fields: ['red']
# Let's define a collection
toasters:
model: Toaster
objects:
red:
color: red
toaster_from_collection:
inherit_from: toaster
fields:
# You can link a specific attribute of a collection's item.
color: !rel toasters.red.color
>>> manager.get_fixture("toaster_from_collection")
<Toaster 'red'>
New in version 0.2.0: It is now possible to link to another object’ attribute.
Collections of Fixtures¶
Charlatan also provides more efficient way to define variations of fixtures.
The basic idea is to define the model and the default fields, then use the
objects
key to define related fixtures. There’s two ways to define those
fixtures in the objects
key:
- Use a list. You will then be able to access those fixtures via their index,
e.g.
toaster.0
for the first item. - Use a dict. The key will be the name of the fixture, the value a dict of
fields. You can access them via their namespace: e.g.
toaster.blue
.
You can also install all of them by installing the name of the collection.
toasters:
model: charlatan.tests.fixtures.simple_models:Toaster
# Those are the default for all fixtures
fields:
slots: 5
# You can have named fixtures in the collection. Note the use of dict.
objects:
green: # This fixture can be accessed via toaster.green
color: green
blue:
color: blue
anonymous_toasters:
inherit_from: toasters
# Here we define unamed fixtures. Note that we use a list instead of a dict.
objects:
# You access the first fixture via anonymous_toaster.0
-
color: yellow
-
color: black
# Those collections can be used as is in relationships.
collection:
fields:
# Since we defined the toasters collection as a dict, things's value will
# be a dict as well
things: !rel toasters
users:
model: charlatan.tests.fixtures.simple_models:User
objects:
1:
toasters: !rel anonymous_toasters
2:
# You can also link to specific relationships using the namespace
toasters: [!rel toasters.green]
3:
toasters: [!rel anonymous_toasters.0]
Here’s how you would use this fixture file to access specific fixtures:
>>> manager = FixturesManager()
>>> manager.load("docs/examples/collection.yaml")
>>> manager.get_fixture("toasters.green")
<Toaster 'green'>
>>> manager.get_fixture("anonymous_toasters.0")
<Toaster 'yellow'>
You can also access the whole collection:
>>> pprint.pprint(manager.get_fixture("toasters"))
{'blue': <Toaster 'blue'>, 'green': <Toaster 'green'>}
>>> manager.get_fixture("anonymous_toasters")
[<Toaster 'yellow'>, <Toaster 'black'>]
Like any fixture, this collection can be linked to in a relationship using the
!rel
keyword in an intuitive way.
>>> pprint.pprint(manager.get_fixture("collection"))
{'things': {'blue': <Toaster 'blue'>, 'green': <Toaster 'green'>}}
>>> user1 = manager.get_fixture("users.1")
>>> user1.toasters
[<Toaster 'yellow'>, <Toaster 'black'>]
>>> manager.get_fixture("users.2").toasters
[<Toaster 'green'>]
>>> manager.get_fixture("users.3").toasters
[<Toaster 'yellow'>]
Changed in version 0.3.4: Access to unnamed fixture by using a .{index}
notation instead of
_{index}
.
New in version 0.3.4: You can now have list of named fixtures.
New in version 0.2.8: It is now possible to retrieve lists of fixtures and link to them with
!rel
Loading Fixtures from Multiple Files¶
Loading fixtures from multiple files works similarly to loading collections. In
this case, every fixture in a single file is preceded by a namespace taken from
the name of that file. Relationships between fixtures in different files
specified using the !rel
keyword may be specified by prefixing the desired
target fixture with its file namespace.
toaster:
model: Toaster
fields:
color: red
user:
model: User
fields:
# You can link to another fixture
toasters:
- !rel toaster
toaster_colors:
# You can also link to a specific attribute
fields:
color: !rel toaster.color
toaster_colors_list:
fields: ['red']
# Let's define a collection
toasters:
model: Toaster
objects:
red:
color: red
toaster_from_collection:
inherit_from: toaster
fields:
# You can link a specific attribute of a collection's item.
color: !rel toasters.red.color
toaster:
model: Toaster
fields:
color: !rel relationships.toaster.color
>>> manager = FixturesManager()
>>> manager.load(["docs/examples/relationships.yaml",
... "docs/examples/files.yaml"],
... models_package="charlatan.tests.fixtures.simple_models")
>>> manager.get_fixture("files.toaster")
<Toaster 'red'>
New in version 0.3.7: It is now possible to load multiple fixtures files with FixturesManager
Datetime and timestamps¶
Use !now
, which returns timezone-aware datetime. You can use modifiers, for
instance:
!now +1y
returns the current datetime plus one year!now +5m
returns the current datetime plus five months!now -10d
returns the current datetime minus ten days!now +15M
(note the case) returns the current datetime plus 15 minutes!now -30s
returns the current datetime minus 30 seconds
For naive datetime (see the definition in Python’s datetime module documentation), use
!now_naive
. It also supports deltas.
For Unix timestamps (seconds since the epoch) you can use !epoch_now
:
!epoch_now +1d
returns the current datetime plus one year in seconds since the epoch!epoch_now_in_ms
returns the current timestamp in milliseconds
All the same time deltas work.
New in version 0.4.6: !epoch_now_in_ms
was added.
New in version 0.4.4: !now_naive
was added.
New in version 0.2.9: It is now possible to use times in seconds since the epoch
Unicode Strings¶
New in version 0.3.5.
In python 2 strings are not, by default, loaded as unicode. To load all the strings from the yaml files as unicode strings, pass the option use_unicode as True when you instantiate your fixture manager.