experimentum.Storage package

Submodules

experimentum.Storage.AbstractRepository module

Interface for using the Repository Design Pattern.

Instead of using the AbstractStore (or any of its implementations like Store) as a God Class the Repository Design Pattern allows you to follow the Single Responsibility Rule.

A Repository acts like an in-memory domain object collection, that connects the domain and data mapping layers. Objects can be easily added to and removed from the Repository, due to the mapping code of the Repository which will ensure that the right operations are executed behind the scenes.

Apart from acting as a bridge between the data and the domain layer, the Repository also provides some useful methods for storing, updating, deleting and querying data.

Examples

Lets assume that you are using the default experimentum data store Store. With Repository there is already an implementation which will handle the mapping between the Repository objects and the SQLAlchemy ORM which is used by the Store.

For simplicity we will assume that your database already contains a User and Address table (for more information on how to specify your domain layer, see: Migrations).

Data Layer

Each user has a name, fullname and a password field and can have multiple adresses. Each address has an email field:

from experimentum.Storage import AbstractRepository

class AddressRepository(AbstractRepository.implementation):
    __table__ = 'Address'

    def __init__(self, email):
        self.email = email


class UserRepository(AbstractRepository.implementation):
    __table__ = 'User'

    __relationships__ = {
        'addresses': [AddressRepository]
    }

    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password

In the __init__ method of the repository you specify the fields of the table. The __table__ attribute contains the name of the table, while the optional __relationships__ attribute specifies any relationships of the data. The key is the attribute under which the data will be accessible, ie:

print(user.addresses)  # will print all addresses of a specific user

Actions

There are several methods for querying data, like find(), get(), first(), all():

# Find a user by its id
user = UserRepository.find(2)  # get user with ID 2
print(user.id, user.name, user.fullname, user.addresses)

# Get many users by a condition (here where name != 'John')
users = UserRepository.get(where=['name', '!=', 'John'])
for user in users:
    print(user.id, user.name, user.fullname, user.addresses)

# Get the first user which satisfies a certain condition
user = UserRepository.first(where=['name', 'John'])
print(user.id, user.name, user.fullname, user.addresses)

# Get all users
users = UserRepository.all()
for user in users:
    print(user.id, user.name, user.fullname, user.addresses)

The Repository class also provides several methods for storing, updating, and deleting data, like create(), update() delete():

# Create a new user from a data dictionary
user = UserRepository.from_dict({
    'name': 'Hello',
    'fullname': 'World',
    'password': '1234',
    # Turns the entries into AddressRepository instances and adds them to the user repo
    # so that they get saved with the correct user id
    'addresses': [
        {'email': 'hello@world.com'},
        {'email': 'foo@world.com'},
    ]
})
user.create()  # create the new user

# Update a user
user = UserRepository.find(2)
user.fullname = 'Doe'
user.name = 'John'
user.update()

# Delete a user
user = UserRepository.find(1)
user.delete()

Events

A Repository provides several events, allowing you to hook into the following points in a repositorie’s lifecycle: before_insert(), after_insert(), before_update(), after_update().

Events allow you to easily execute code each time after/before a specific repository object is saved or updated in the data store.

class AddressRepository(AbstractRepository.implementation):
    # ...

    def after_insert(self):
        print('Gets called each time after an address is saved to the database.')
        print(self.id, self.email)  # Object has access to the inserted id

Loading

In order for the framework to map all the repositories it has to load them via the RepositoryLoader class. For the RepositoryLoader class to be able to find the repositories, the files have be in your repository folder and end with Repository.py. The repository files should contain a repository class with the same name as the file name.

class experimentum.Storage.AbstractRepository.AbstractRepository(**attributes)

Bases: object

Interface for using the Repository Design Pattern.

Instead of using the AbstractStore as a God Class the Repository Design Pattern allows you to follow the Single Responsibility Rule.

store

Store that is used for mapping domain and data layer.

Type:AbstractStore
implementation

Concrete repo implementation which should be used.

Type:AbstractRepository
__table__

Name of the table the repository refers to.

Type:str
__relationship__

Any Relationships the data has.

Type:dict
after_insert()

Event that gets called after insert statement is executed.

Returns:AbstractRepository
after_update()

Event that gets called after update statement is executed.

Returns:AbstractRepository
classmethod all()

Get all entries for this specific repository from your data store.

Raises:NotImplementedError – if method is not implemented yet.
Returns:List of all entires
Return type:list
before_insert()

Event that gets called before insert statement is executed.

Returns:AbstractRepository
before_update()

Event that gets called before update statement is executed.

Returns:AbstractRepository
create()

Save the repository content in your data store.

Raises:NotImplementedError – if method is not implemented yet.
delete()

Delete the repository content from your data store.

Raises:NotImplementedError – if method is not implemented yet.
classmethod find(id)

Find an entry of this repository based on its id.

Parameters:id (int) – ID to search for.
Raises:NotImplementedError – if method is not implemented yet.
Returns:Item which the concrete id
Return type:AbstractRepository
classmethod first(where=None)

Get first entry which satisfy a specific condition from your data store.

Parameters:where (list, optional) – Defaults to None. Where Condition
Raises:NotImplementedError – if method is not implemented yet.
Returns:Item which satisfies the condition.
Return type:AbstractRepository
classmethod from_dict(data)

Create a new Repository instance based on a dictionary entry.

Parameters:data (dict) – Repository Data
Returns:Repository instance filled with data.
Return type:AbstractRepository
classmethod get(where=None)

Get all entries which satisfy a specific condition from your data store.

Parameters:where (list, optional) – Defaults to None. Where Condition
Raises:NotImplementedError – if method is not implemented yet.
Returns:List of items which satisfy the condition.
Return type:list
implemantation = None
static mapping(cls, store)

Map data store content to repository classes.

Example: Map a User Table in the data store to the UserRepository, which reflects the attributes of the User Table schema.

Parameters:
Raises:

NotImplementedError – if method is not implemented

store = None
to_json()

Return JSON representation of repository fields and values.

Returns:JSON representration
Return type:str
update()

Update the repository content in your data store.

Raises:NotImplementedError – if method is not implemented yet.
class experimentum.Storage.AbstractRepository.RepositoryLoader(app, implementation, store)

Bases: object

Load and map all the repositories it can find and cache them.

app

Main App class.

Type:App
implementation

Concrete repo implementation which should be used.

Type:AbstractRepository
store

Data store which is used

Type:AbstractStore
get(repository)

Return class of a loaded repository if it exists.

Parameters:repository (str) – Name of repository class.
Returns:Repository class
Return type:AbstractRepository
load()

Load all repositories from the repo folder and try to map them to the store.

experimentum.Storage.AbstractStore module

Interface of the data store to make it possible to switch between different implementations.

class experimentum.Storage.AbstractStore.AbstractStore

Bases: object

Contains the interface between the framework and the concrete Database implementation.

alter(blueprint)

Alter the schema for a table.

Parameters:blueprint (Blueprint) – Table Blueprint
Raises:NotImplementedError – if method is not implemented by derived class.
create(blueprint)

Create a new Table.

Parameters:blueprint (Blueprint) – The Blueprint to create the table
Raises:NotImplementedError – if method is not implemented by derived class.
drop(name, checkfirst=False)

Drop a table.

Parameters:name (str) – Name of the table
Raises:NotImplementedError – if method is not implemented by derived class.
has_column(table, column)

Check if a table has a specific column.

Parameters:
  • table (str) – Name of the table
  • column (str) – Name of the column
Raises:

NotImplementedError – if method is not implemented by derived class.

Returns:

boolean

has_table(table)

Check if the data store has a specific table.

Parameters:table (str) – Name of the Table
Raises:NotImplementedError – if method is not implemented by derived class.
Returns:boolean
rename(old, new)

Rename a table.

Parameters:
  • old (str) – Old table name
  • new (str) – New table name
Raises:

NotImplementedError – if method is not implemented by derived class.

Module contents

Import classes for easier importing by other packages/modules.