experimentum.Storage package¶
Subpackages¶
- experimentum.Storage.Migrations package
- Submodules
- experimentum.Storage.Migrations.Blueprint module
- experimentum.Storage.Migrations.Column module
- experimentum.Storage.Migrations.ForeignKey module
- experimentum.Storage.Migrations.Migration module
- experimentum.Storage.Migrations.Migrator module
- experimentum.Storage.Migrations.Schema module
- Module contents
- experimentum.Storage.SQLAlchemy 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: - cls (AbstractRepository) – Repository to map
- store (AbstractStore) – Storage that is used
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.
-
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.