Attributes

Base

class sheraf.attributes.Attribute(default=None, key=None, lazy=True, read_memoization=None, write_memoization=None, store_default_value=True)[source]

Bases: object

Base type of all attributes of a base model.

Parameters:
  • default (a callable object or a simple object) – The value this attribute will be initialized with. If it is a callable object, it will be called on initialization, else it will simply be copied. The callable object can take either no argument, or one argument that will be the parent model.

  • key – The key to identify the attribute in its parent persistent mapping.

  • lazy (bool) – If True, the objet carried by the attribute is created on the first read or write access. If False, it is created when the model object is created. Default is True.

  • read_memoization (bool) – Whether this attribute should be memoized on read. False by default.

  • write_memoization (bool) – Whether this attribute should be memoized on write. True by default.

When an attribute is memoized, its next reading will not result in a new database access. Attributes: - indexes: a dictionary of Indexes. The key with value None stands for this attribute’s name.

create(parent)[source]
Returns:

The attribute’s defined default value. If it is a callable, return the result of calling it instead.

default(func)[source]

Decorator for a callback to call to generate a default value for the attribute.

>>> class Cowboy(sheraf.Model):
...     table = "default_cowboys"
...     age = sheraf.IntegerAttribute()
...
...     @age.default
...     def default_age(self):
...         return 42
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create()
...     george.age
42
index(unique=False, key=None, index_keys_func=None, search_keys_func=None, values=None, search=None, mapping=None, primary=False, nullok=None, noneok=None)[source]

This method is a shortcut that inits a Index object on the current attribute. It takes the same arguments as Index except that by default:

  • the index name will be this attribute name;

  • the mapping parameter will be this attribute default_index_mapping parameter;

  • the index_keys_func parameter will be this attribute index_keys() method;

  • the search_keys_func parameter will be this attribute search_keys() method;

  • the noneok parameter will be this attribute noneok parameter

  • the nullok parameter will be this attribute nullok parameter

index_keys(value)[source]

The default transformation that will be applied when storing data if this attribute is indexed but the index() values_func parameter is not provided.

By default no transformation is applied, and the value parameter is returned in a set.

This method can be overload so a custom transformation is applied.

index_keys_func(*args, **kwargs)[source]

Shortcut for index_keys_func() for the index that has the same name than this attribute.

is_created(parent)[source]
Parameters:

parent – The owner of this attribute

Returns:

true if the object is effectively created

on_change(*args, **kwargs)[source]

Shortcut for on_creation(), on_edition() and on_deletion() at the same time.

on_creation(*args, **kwargs)[source]

Decorator for callbacks to call on an attribute creation. The callback will be executed before the attribute is created. If the callback yields, the part after the yield will be executed after the creation.

The callback will be passed the new value of the attribute.

The callback can be freely named.

>>> class Cowboy(sheraf.Model):
...     table = "old_cowboys_creation"
...     age = sheraf.IntegerAttribute()
...
...     @age.on_creation
...     def create_age(self, new):
...         print("New cowboy aged of", new)
...
>>> with sheraf.connection():
...     george = Cowboy.create(age=50)
New cowboy aged of 50
on_deletion(*args, **kwargs)[source]

Decorator for callbacks to call on an attribute deletion. The callback will be executed before the attribute is deleted. If the callback yields, the part after the yield will be executed after the deletion.

The callback will be passed the old value of the attribute.

The callback can be freely named.

>>> class Cowboy(sheraf.Model):
...     table = "old_cowboys_deletion"
...     age = sheraf.IntegerAttribute()
...
...     @age.on_deletion
...     def delete_age(self, old):
...         print("Deleting age of", 50)
...
>>> with sheraf.connection():
...     george = Cowboy.create(age=50)
...     del george.age
Deleting age of 50
on_edition(*args, **kwargs)[source]

Decorator for callbacks to call on an attribute edition. The callback will be executed before the attribute is edited. If the callback yields, the part after the yield will be executed after the update.

The callback will be passed the old and the new value of the attribute.

The callback can be freely named.

>>> class Cowboy(sheraf.Model):
...     table = "old_cowboys_edition"
...     age = sheraf.IntegerAttribute()
...
...     @age.on_edition
...     def update_age(self, new, old):
...         print("I was", old, "years old")
...         yield
...         print("Now I am", new, "years old")
...
>>> with sheraf.connection():
...     george = Cowboy.create(age=50)
...     george.age = 51
I was 50 years old
Now I am 51 years old
search_keys(value)[source]

The default transformation that will be applied when searching for data if this attribute is indexed but the index() search_keys_func parameter is not provided.

By default this calls values().

This method can be overload so a custom transformation is applied.

search_keys_func(*args, **kwargs)[source]

Shortcut for search_keys_func() for the index that has the same name than this attribute.

update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

Simple attributes

class sheraf.attributes.simples.BooleanAttribute(**kwargs)[source]

Bases: TypedAttribute

Store a bool object.

type

alias of bool

class sheraf.attributes.simples.DateAttribute(default=None, **kwargs)[source]

Bases: IntegerAttribute

Stores a datetime.date object.

class sheraf.attributes.simples.DateTimeAttribute(default=None, key=None, lazy=True, read_memoization=None, write_memoization=None, store_default_value=True)[source]

Bases: Attribute

Store a datetime.datetime object.

class sheraf.attributes.simples.FloatAttribute(**kwargs)[source]

Bases: TypedAttribute

Stores a float object.

type

alias of float

class sheraf.attributes.simples.IntegerAttribute(**kwargs)[source]

Bases: TypedAttribute

Stores an int object.

type

alias of int

class sheraf.attributes.simples.SimpleAttribute(default=None, key=None, lazy=True, read_memoization=None, write_memoization=None, store_default_value=True)[source]

Bases: Attribute

Store a primitive data.

The value can be a bool, str, int, float.

class sheraf.attributes.simples.StringAttribute(**kwargs)[source]

Bases: TypedAttribute

Stores a str object.

type

alias of str

class sheraf.attributes.simples.StringUUIDAttribute(default=None, key=None, lazy=True, read_memoization=None, write_memoization=None, store_default_value=True)[source]

Bases: UUIDAttribute

Stores an uuid.UUID but data is handled as a string.

class sheraf.attributes.simples.TimeAttribute(default=None, **kwargs)[source]

Bases: IntegerAttribute

Stores a datetime.time object.

class sheraf.attributes.simples.TypedAttribute(**kwargs)[source]

Bases: Attribute

Store a persistent dict of primitive data.

Keys and values can be str, int, float.

If the value set is None, it will not be casted.

type

alias of object

class sheraf.attributes.simples.UUIDAttribute(default=None, key=None, lazy=True, read_memoization=None, write_memoization=None, store_default_value=True)[source]

Bases: Attribute

Stores an uuid.UUID.

index_keys(value)[source]

The default transformation that will be applied when storing data if this attribute is indexed but the index() values_func parameter is not provided.

By default no transformation is applied, and the value parameter is returned in a set.

This method can be overload so a custom transformation is applied.

class sheraf.attributes.password.PasswordAttribute(**kwargs)[source]

Bases: Attribute

Stores crypted password in the database. Once a password has been crypted, it cannot be read back in plain text, however, comparisions are still possible. Under the hood, the python crypt() method is used.

The arguments passed to this attribute are passed to crypt.mksalt().

>>> class Cowboy(sheraf.Model):
...     table = "cautious_cowboy"
...     email = sheraf.StringAttribute()
...     password = sheraf.PasswordAttribute()
...
>>> with sheraf.connection(commit=True):  
...     cowboy = Cowboy.create(email="george@abitbol.com", password="$up3r$3cur3")
...     assert cowboy.password == "$up3r$3cur3"
...     str(cowboy.password)
'$6$vQzraMQc3G9Mf/zy$IfPSCPO81IzvGwo6AYoS6K6fen552B22DDz.ARwoxmvyFJ9He7.wVLIWbw0RWEIw/oGUblY9YbGvhwUQbtYEV.'
class sheraf.attributes.enum.EnumAttribute(enum, attribute=None, **kwargs)[source]

Bases: Attribute

Takes an Enum and an optional Attribute, filters the data with the Enum and serialize it with the methods of the Attribute.

>>> import enum
...
>>> class Cowboy(sheraf.Model):
...     table = "enum_cowboys"
...
...     class Status(enum.IntEnum):
...         FARMER = 0
...         COWBOY = 1
...         SHERIF = 2
...
...     status = sheraf.EnumAttribute(Status, sheraf.IntegerAttribute())
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create(status=Cowboy.Status.SHERIF)
...
...     assert george.status == 2
...     assert george.status == Cowboy.Status.SHERIF

Note

For every value FOOBAR defined in the enum, the attributes defines an accessor is_foobar.

>>> with sheraf.connection(commit=True):
...     assert george.status.is_sherif
...     assert not george.status.is_farmer
class sheraf.attributes.counter.CounterAttribute(default=0, **kwargs)[source]

Bases: IntegerAttribute

CounterAttribute is very like SimpleAttribute with concurrency-proof operations.

It acts as a drop-in replacement for SimpleAttribute with increment and decrement methods that automatically solve conflicts. It supports all mathematical operations, but only solves conflicts using increment and decrement. Every other destructive operation are sensible to conflicts.

>>> class MyModel(sheraf.Model):
...     table = "mymodel"
...     # You can just replace SimpleAttribute or IntegerAttribute with CounterAttribute
...     counter = sheraf.CounterAttribute() # initialized with 0
...
>>> with sheraf.connection(commit=True):
...     m = MyModel.create()
...     m.counter.increment(1)
...     m.counter.decrement(1)

Here is how to produce a conflict case, and how CounterAttributes behaves to solve it:

>>> with sheraf.connection(commit=True):
...     sheraf.Database.get().nestable = True
...     m1 = MyModel.read(m.id)
...
...     with sheraf.connection(commit=True):
...         m2 = MyModel.read(m.id)
...         m2.counter.decrement(10)
...
...     m1.counter.increment(100)
...
>>> with sheraf.connection():
...     m3 = MyModel.read(m.id)
...     assert 90 == m3.counter

The conflict resolution understands that a transaction is adding 100 and another transaction is substracting 10 to the counter at the same time, and finally adds 90. The formula used is: new_state_a + new_state_b - old_state where old_state is 0 as it was the previous value registered in the database, and new_state_a and new_state_b are the conflicting new values (here -10 and 100).

Using += and -= operators would have raised a conflicts. increment or decrement must be called explicitely.

>>> with sheraf.connection(commit=True):
...     sheraf.Database.get().nestable = True
...     m1 = MyModel.read(m.id)
...
...     with sheraf.connection(commit=True):
...         m2 = MyModel.read(m.id)
...         m2.counter -= 10
...
...     m1.counter += 100
Traceback (most recent call last):
    ...
ZODB.POSException.ConflictError: database conflict error ...

Regular assignments and operations on the counter also raise conflicts for automatic conflict resolution.

>>> with sheraf.connection(commit=True):
...     sheraf.Database.get().nestable = True
...     m1 = MyModel.read(m.id)
...
...     with sheraf.connection(commit=True):
...         m2 = MyModel.read(m.id)
...         m2.counter = 10
...
...     m1.counter.increment(100)
Traceback (most recent call last):
    ...
ZODB.POSException.ConflictError: database conflict error ...

Collection attributes

Collection attributes are attributes that behave like native python collections such as dict, list or set. They usually combine different objects:

  • A persistent_type that is the persistent data structure that will store the data. For instance ListAttribute usually uses sheraf.types.SmallList or LargeList.

  • An accessor_type that helps handling the persistent_type with an interface similar to the native type it refers. For instance ListAttribute uses ListAccessor that behaves like the python list.

  • An optional attribute that helps the accessor_type serialize and deserialize the data stored in the persistent_type. This allows pairing collections with other kinds of attributes. For example you can easilly handle Blob lists or dictionaries of InlineModelAttributes.

>>> class Horse(sheraf.InlineModel):
...     table = "horse"
...     name = sheraf.SimpleAttribute()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     favorite_numbers = sheraf.SmallListAttribute(
...         sheraf.IntegerAttribute(),
...     )
...     horses = sheraf.LargeDictAttribute(
...         sheraf.InlineModelAttribute(Horse),
...     )
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create(
...         name="George Abitbol",
...         favorite_numbers=[1, 13, 21, 34],
...         horses = {
...             "first": {"name": "Jolly Jumper"},
...             "second": {"name": "Polly Pumper"},
...         },
...     )
...
...     assert 21 in george.favorite_numbers
...     assert "Jolly Jumper" == george.horses["first"].name

You can also nest collections as you like, and play for instance with DictAttribute or ListAttribute.

>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     dice_results = sheraf.LargeDictAttribute(
...         sheraf.SmallListAttribute(
...             sheraf.IntegerAttribute()
...         )
...     )
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create(
...         name="George Abitbol",
...         dice_results={
...             "monday": [2, 6, 4],
...             "tuesday": [1, 1, 3],
...         }
...     )
...     assert 6 == george.dice_results["monday"][1]
class sheraf.attributes.collections.DictAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.DictAttributeAccessor'>, **kwargs)[source]

Bases: Attribute

Attribute mimicking the behavior of dict.

>>> class Gun(sheraf.InlineModel):
...     nb_amno = sheraf.IntegerAttribute()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     guns = sheraf.LargeDictAttribute(
...         sheraf.InlineModelAttribute(Gun)
...     )
...
>>> with sheraf.connection(commit=True):
...    george = Cowboy.create(
...        name="George Abitbol",
...        guns={
...            "rita": {"nb_amno": 6},
...            "carlotta": {"nb_amno": 5},
...        }
...    )
...
...    assert george.guns["rita"].nb_amno == 6
...    for gun in george.guns.values():
...        assert gun.nb_amno >= 5
update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

class sheraf.attributes.collections.LargeDictAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.DictAttributeAccessor'>, **kwargs)[source]

Bases: DictAttribute

Shortcut for DictAttribute(persistent_type=LargeDict)

persistent_type

alias of LargeDict

class sheraf.attributes.collections.LargeListAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.ListAttributeAccessor'>, **kwargs)[source]

Bases: ListAttribute

Shortcut for ListAttribute(persistent_type=LargeList).

persistent_type

alias of LargeList

class sheraf.attributes.collections.ListAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.ListAttributeAccessor'>, **kwargs)[source]

Bases: Attribute

Attribute mimicking the behavior of list.

>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     faxes = sheraf.LargeListAttribute(
...         sheraf.BlobAttribute()
...     )
...
>>> with sheraf.connection():
...     george = Cowboy.create(
...         name="George Abitbol",
...         faxes=[
...             sheraf.Blob.create(filename="peter1.txt", data=b"Can you give me my pin's back please?"),
...         ],
...     )
...
...     george.faxes.append(sheraf.Blob.create(filename="peter2.txt", data=b"Hey! Did you receive my last fax?"))
...     assert "peter1.txt" == george.faxes[0].original_name
...     assert b"fax" in george.faxes[1].data
...
index_keys(list_)[source]

By default, every items in a ListAttribute is indexed.

>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     favorite_colors = sheraf.LargeListAttribute(
...         sheraf.StringAttribute()
...     ).index()
...
>>> with sheraf.connection():
...     george = Cowboy.create(favorite_colors=[
...         "red", "green", "blue",
...     ])
...     assert george in Cowboy.search(favorite_colors="red")
...     assert george in Cowboy.search(favorite_colors="blue")
...     assert george not in Cowboy.search(favorite_colors="yellow")

Warning

A current limitation is that indexed lists won’t update their indexes if they are edited through their accessor. For the indexes to be updated, the whole list must be re-assigned.

>>> with sheraf.connection(): 
...     george.favorite_colors.append("purple")
...     george in Cowboy.search(favorite_colors="purple")
False
>>> with sheraf.connection(): 
...     george.favorite_colors = list(george.favorite_colors) + ["brown"]
...     george in Cowboy.search(favorite_colors="brown")
True
search_keys(value)[source]

The default transformation that will be applied when searching for data if this attribute is indexed but the index() search_keys_func parameter is not provided.

By default this calls values().

This method can be overload so a custom transformation is applied.

update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

class sheraf.attributes.collections.SetAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.SetAttributeAccessor'>, **kwargs)[source]

Bases: TypedAttribute

Attribute mimicking the behavior of set.

>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     favorite_numbers = sheraf.SetAttribute(
...         sheraf.IntegerAttribute()
...     )
...
>>> with sheraf.connection(commit=True):
...    george = Cowboy.create(
...        name="George Abitbol",
...        favorite_numbers={1, 8, 13}
...    )
...
...    assert 13 in george.favorite_numbers
...    george.favorite_numbers.add(8)
...    assert {1, 8, 13} == set(george.favorite_numbers)
index_keys(set_)[source]

By default, every items in a SetAttribute is indexed.

>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     favorite_colors = sheraf.SetAttribute(
...         sheraf.StringAttribute()
...     ).index()
...
>>> with sheraf.connection():
...     george = Cowboy.create(favorite_colors={
...         "red", "green", "blue",
...     })
...     assert george in Cowboy.search(favorite_colors="red")
...     assert george in Cowboy.search(favorite_colors="blue")
...     assert george not in Cowboy.search(favorite_colors="yellow")

Warning

A current limitation is that indexed sets won’t update their indexes if they are edited through their accessor. For the indexes to be updated, the whole set must be re-assigned.

>>> with sheraf.connection(): 
...     george.favorite_colors.add("purple")
...     george in Cowboy.search(favorite_colors="purple")
False
>>> with sheraf.connection(): 
...     george.favorite_colors = set(george.favorite_colors) | {"brown"}
...     george in Cowboy.search(favorite_colors="brown")
True
search_keys(value)[source]

The default transformation that will be applied when searching for data if this attribute is indexed but the index() search_keys_func parameter is not provided.

By default this calls values().

This method can be overload so a custom transformation is applied.

update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

class sheraf.attributes.collections.SmallDictAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.DictAttributeAccessor'>, **kwargs)[source]

Bases: DictAttribute

Shortcut for DictAttribute(persistent_type=SmallDict)

persistent_type

alias of SmallDict

class sheraf.attributes.collections.SmallListAttribute(attribute=None, persistent_type=None, accessor_type=<class 'sheraf.attributes.collections.ListAttributeAccessor'>, **kwargs)[source]

Bases: ListAttribute

Shortcut for ListAttribute(persistent_type=SmallList).

persistent_type

alias of PersistentList

Model attributes

class sheraf.attributes.models.AttributeLoader(attribute=None, **kwargs)[source]

Bases: ModelLoader

class sheraf.attributes.models.IndexedModelAttribute(*args, **kwargs)[source]

Bases: ModelLoader, Attribute

ModelAttribute behaves like a classic model, including the indexation capabilities. The child attribute mapping and all the index mappings are is stored in the parent mapping.

Parameters:

model (AttributeModel) – The model type to store.

Note

The AttributeModel must have a primary index.

>>> class Horse(sheraf.AttributeModel):
...     name = sheraf.StringAttribute().index(primary=True)
...     size = sheraf.IntegerAttribute().index()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy_indexer"
...     name = sheraf.StringAttribute()
...     horses = sheraf.IndexedModelAttribute(Horse)
...
>>> with sheraf.connection(commit=True):
...     george = Cowboy.create(name="George Abitbol")
...     jolly = george.horses.create(name="Jolly Jumper", size=32)
...
...     assert jolly == george.horses.read("Jolly Jumper")
...     assert jolly in george.horses.search(size=32)
index_keys(model)[source]

The default transformation that will be applied when storing data if this attribute is indexed but the index() values_func parameter is not provided.

By default no transformation is applied, and the value parameter is returned in a set.

This method can be overload so a custom transformation is applied.

index_table_default

alias of SmallDict

search_keys(query)[source]

The default transformation that will be applied when searching for data if this attribute is indexed but the index() search_keys_func parameter is not provided.

By default this calls values().

This method can be overload so a custom transformation is applied.

class sheraf.attributes.models.InlineModelAttribute(model=None, **kwargs)[source]

Bases: ModelLoader, Attribute

ModelAttribute behaves like a basic model (i.e. have no indexation capability). The child attribute mapping is stored in the parent mapping.

Parameters:

model (InlineModel) – The model type to store.

>>> class Horse(sheraf.InlineModel):
...     name = sheraf.StringAttribute()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy_inliner"
...     name = sheraf.StringAttribute()
...     horse = sheraf.InlineModelAttribute(Horse)
...
>>> with sheraf.connection(commit=True):
...     jolly = Horse.create(name="Jolly Jumper")
...     george = Cowboy.create(name="George", horse=jolly)
...     george.horse.name
'Jolly Jumper'
default_mapping

alias of SmallDict

update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

class sheraf.attributes.models.ModelAttribute(model=None, **kwargs)[source]

Bases: ModelLoader, Attribute

This attribute references another Model.

Parameters:

model (Model or list of Model) – The model type to store.

>>> class Horse(sheraf.Model):
...     table = "horse"
...     name = sheraf.SimpleAttribute()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     mount = sheraf.ModelAttribute(Horse)
...
>>> with sheraf.connection(commit=True):
...     jolly = Horse.create(name="Jolly Jumper")
...     george = Cowboy.create(name="George Abitbol", mount=jolly)
...
...     george.mount.name
'Jolly Jumper'

The referenced model can be dynamically created if its structure is passed through as a dict:

>>> with sheraf.connection(commit=True):
...     peter = Cowboy.create(name="Peter", mount={"name": "Polly Pumper"})
...     assert isinstance(peter.mount, Horse)
...     peter.mount.name
'Polly Pumper'

When the referenced model is deleted, the value of the attribute becomes None.

>>> with sheraf.connection(commit=True):
...     george = Cowboy.read(george.id)
...     jolly.delete()
...     assert george.mount is None

Several model classes can be used, but this will be more memory consuming in the database.

>>> class Pony(sheraf.Model):
...     table = "pony"
...     name = sheraf.SimpleAttribute()
...
>>> class Cowboy(sheraf.Model):
...     table = "cowboy"
...     name = sheraf.SimpleAttribute()
...     mount = sheraf.ModelAttribute((Horse, Pony))
...
>>> with sheraf.connection(commit=True):
...     superpony = Pony.create(name="Superpony")
...     peter = Cowboy.create(name="Peter", mount=superpony)

When several models are set, the first one is considered to be the default model. The default model is used when there is a doubt on the read data, or in the case of model creation with a dict.

index_keys(model)[source]

By default ModelAttribute are indexed on their identifier.

update(old_value, new_value, addition=True, edition=True, deletion=False, replacement=False)[source]

Updates the value of the attribute.

Parameters:
  • old_value – The previous value the attribute had.

  • new_value – The new value that the attribute should be updated with.

  • addition – On collections, adds elements to the attribute if they are present in new_value and not in old_value, defaults to True.

  • edition – On collections, edits the attribute element if present on both old_value and new_value, defaults to True.

  • deletion – On collections, removes an element if present in old_value and absent from new_value, default to False.

  • replacement – Replace sub-collections or sub-models instead of editing them in-place, defaults to False.

class sheraf.attributes.models.ModelLoader(model=None, **kwargs)[source]

Bases: object

Loads models from the base in a cache.

Inherited by most model types (Model[Attribute|List|Set|(Large)Dict])

class sheraf.attributes.models.ReverseModelAttribute(model, attribute, **kwargs)[source]

Bases: AttributeLoader, Attribute

Inverse reference to a ModelAttribute.

Parameters:

The referenced attribute must be indexed.

>>> class Cowboy(sheraf.Model):  
...     table = "reverse_cowboys"
...     name = sheraf.StringAttribute()
...     horse = sheraf.ModelAttribute("Horse").index()
...
>>> class Horse(sheraf.Model):  
...     table = "reverse_horses"
...     name = sheraf.StringAttribute()
...     cowboy = sheraf.ReverseModelAttribute("Cowboy", "horse")
...
>>> with sheraf.connection():  
...     george = Cowboy.create(name="George")
...     horse = Horse.create(name="Jolly", cowboy=george)
...     george.horse.name
"Jolly"

Collection attributes are also supported:

>>> class Cowboy(sheraf.Model):  
...     table = "reverse_multicowboys"
...     name = sheraf.StringAttribute()
...     horses = sheraf.LargeListAttribute(ModelAttribute("Horse").index())
...
>>> class Horse(sheraf.Model):  
...     table = "reverse_multihorses"
...     name = sheraf.StringAttribute()
...     cowboy = sheraf.ReverseModelAttribute("Cowboy", "horses")
...
>>> with sheraf.connection():  
...     george = Cowboy.create(name="George")
...     jolly = Horse.create(name="Jolly", cowboy=george)
...     polly = Horse.create(name="Polly", cowboy=george)
...     george.horses[0].name
...     george.horses[1].name
"Jolly"
"Polly"

File attributes

class sheraf.attributes.blobs.Blob(**kwargs)[source]

Bases: InlineModel

Blob objects wrap the raw data from regular files.

ZODB ZODB.blob.Blob are used to store the data.

classmethod create(data=None, filename=None, stream=None, **kwargs)[source]
Parameters:
  • data – the data to store in the blob

  • filename – the name of the original file

  • stream – If data is not set, data will be read from the stream stream.

  • kwargs – optional attributes to set

Returns:

A sheraf object wrapping a ZODB.blob.Blob object, or None if this is an empty and unnamed file.

property data

The file binary data.

delete()[source]

Delete the object from the base.

edit(value, addition=True, edition=True, deletion=False, replacement=False)[source]

Take a dictionary and a set of options, and try to applies the dictionary values to the instance structure.

Parameters:
  • value – The dictionary containing the values. The dictionary elements that do not match the instance attributes will be ignored.

  • addition – If True, elements present in value and absent from the instance attributes will be added.

  • edition – If True, elements present in both value and the instance will be updated.

  • deletion – If True, elements present in the instance and absent from value will be deleted.

  • replacement – Like edition, but create a new element instead of updating one.

  • strict – If strict is True, every keys in value must be sheraf attributes of the current model. Default is False.

property file_extension

The original filename extension.

property filename

The name of the blob file.

open()[source]

Opens the stored blob file.

property original_name

The original filename.

class sheraf.attributes.blobs.BlobAttribute(model=<class 'sheraf.attributes.blobs.Blob'>, **kwargs)[source]

Bases: InlineModelAttribute

This attribute stores binary files. It is mainly an interface that handles a Blob object.

class sheraf.attributes.files.FileAttribute(file_object_class=<class 'sheraf.attributes.files.FileObject'>, **kwargs)[source]

Bases: Attribute

This attribute stores a file on disk.

Index

class sheraf.attributes.index.Index(*attributes, unique=False, key=None, index_keys_func=None, search_keys_func=None, values=None, search=None, mapping=None, primary=False, nullok=None, noneok=None, auto=True)[source]

Bases: object

Indexes should be either created as IndexedModel class parameters, or with the attributes index() method.

Parameters:
  • attributes – The attributes being indexed. They can be either Attribute or strings representing the attributes names in the model.

  • key – The key the index will use. By default, it takes the name it has as a IndexedModel attribute. If the index() is used, the key is the Attribute name.

  • unique (bool) – If the index is unique, and two models have the same value for this model, a UniqueIndexException is raised when trying to write the second one. Automatically set to True if primary is True.

  • index_keys_func – A callable that takes the current attribute value and returns a single key, or a collection of keys, where the model instance will be indexed. The keys will be regenerated each time this attribute will be edited, and the index will be updated accordingly. It may take time if the generated collection is large. By default the attribute index_keys() method is applied.

  • search_keys_func – A callable that takes some raw data and returns a collection of keys to search in the index. By default, the search_keys_func() method is used.

  • mapping – The mapping object to be used to store the indexed values. By default the index_mapping class attribute is used. If you know that the keys of the index will always be a specific type, like integers, you might way to use this attribute to use specialized datastructures.

  • nullok – If True, None or empty values can be indexed. True by default.

  • noneok – Ignored in if nullok is True. Else, if noneok is True, None values can be indexed. False by default.”

  • auto – Defaults to True, enable the automatic index update. When set to False the index won’t be updated when the attributes are updated.

>>> class People(sheraf.Model):
...     table = "index_people"
...
...     # Simple indexing
...     name = sheraf.SimpleAttribute()
...     nameindex = sheraf.Index("name")
...
...     # Indexing with the .index() shortcut
...     size = sheraf.IntegerAttribute().index()
...
...     # Emails can only be owned once
...     email = sheraf.SimpleAttribute().index(unique=True)
...
...     # Indexing people by their decade
...     age = sheraf.SimpleAttribute().index(key="decade", index_keys_func=lambda age: {age // 10})
...
>>> with sheraf.connection(commit=True):
...     m = People.create(
...         name="George Abitbol",
...         size=180,
...         email="george@abitbol.com",
...         age=55,
...     )
...
>>> with sheraf.connection():
...     assert [m] == People.filter(nameindex="George Abitbol")
...     assert [m] == People.filter(size=180)
...     assert [m] == People.filter(decade=5)
...
>>> with sheraf.connection():
...     People.create(name="Peter", size=175, email="george@abitbol.com", age=35)
Traceback (most recent call last):
    ...
UniqueIndexException
index_keys_func(*args, **kwargs)[source]

This decorator sets a index_keys method, for one, several or all of the index attributes.

  • If no positionnal argument is passed, then this sets a default index_keys function for the index.

  • You can pass one or several attributes or attributes names to set a specific index_keys method for those attributes.

  • If you do set a specific index_keys method for one or several attributes, the decorated function will be given the attribute index_keys as positionnal arguments, in the order they were passed to the decorator.

The decorated function must return a single key, or a collection of keys, where the index will store the current model instance. Depending on the noneok and nullok Index parameters, None and falsy index keys might be ignored.

>>> class Cowboy(sheraf.Model):
...     table = "any_values_table"
...     first_name = sheraf.StringAttribute()
...     last_name = sheraf.StringAttribute()
...     surname = sheraf.StringAttribute()
...     name_parts = sheraf.Index(first_name, last_name, surname)
...
...     @name_parts.index_keys_func
...     def default_name_indexation(self, values):
...         return values.lower()
...
...     @name_parts.index_keys_func(first_name, "last_name")
...     def full_name(self, first, last):
...         return f"{first} {last}".lower()
...
>>> with sheraf.connection():
...     m = Cowboy.create(first_name="George", last_name="Abitbol", surname="Georgy")
...     assert m in Cowboy.search(name_parts="george abitbol")
...     assert m in Cowboy.search(name_parts="georgy")

Note

You can use index_keys() to check the index keys your custom function generates.

search_keys_func(*args, **kwargs)[source]

This decorator sets the search_keys method for the index. It should return a single key to search in the index, or a collection of keys to search in the index. If it returns an indexed collection, by default the model instances will be returned in the order the index keys will be iterated.

>>> class Model(sheraf.Model):
...     table = "any_search_table"
...     foo = sheraf.StringAttribute()
...     bar = sheraf.StringAttribute()
...     theindex = sheraf.Index(foo, bar)
...
...     @theindex.search_keys_func
...     def the_search_method(self, values):
...         return values.lower()
...
>>> with sheraf.connection():
...     m = Model.create(foo="foo", bar="bar")
...     assert m in Model.search(theindex="foo")
...     assert m in Model.search(theindex="BAR")

Note

You can use search_keys() to check the index keys your custom function generates.