Contents
MEMENTO¶
Anyblok mainly depends on:
- Python 3.2+
- SQLAlchemy
- Alembic
Blok¶
A blok is a collection of source code files. These files are loaded in the registry
only if the blok state is installed
.
To declare a blok you have to:
Declare a Python package:
The name of the module is not really significant --> Just create an ``__init__.py`` file
Declare a blok class in the
__init__.py
of the Python package:from anyblok.blok import Blok class MyBlok(Blok): """ Short description of the blok """ ... version = '1.0.0'
Here are the available attributes for the blok:
Attribute | Description |
---|---|
__doc__ |
Short description of the blok (in the docstring) |
version |
the version of the blok (required because no value by default) |
autoinstall |
boolean, if True this blok is automatically
installed |
priority |
installation order of the blok to installation |
readme |
Path of the ‘readme’ file of the blok, by default
README.rst |
required |
List of the required dependancies for install |
optional |
List of the optional dependencies, their are installed if they are found |
conflicting |
List the blok which are not be installed to install this blok |
conditionnal |
If the bloks of this list ares installed the this blok will be automaticly installed |
And the methods that define blok behaviours:
Method | Description |
---|---|
import_declaration_module |
classmethod , call to import all python
module which declare object from blok. |
reload_declaration_module |
classmethod , call to reload the import
all the python module which declare object |
update |
Action to do when the blok is being
install or updated. This method has one
argument Since version 0.20.0 the
|
uninstall |
Action to do when the blok is being uninstalled |
load |
Action to do when the server starts |
pre_migration |
Action to do when the blok is being
installed or updated to make some specific
migration, before auto migration.
This method has one argument
Since version 0.20.0 the
|
post_migration |
Action to do when the blok is being
installed or updated to make some specific
migration, after auto migration.
This method has one argument
Since version 0.20.0 the
|
And some facility:
Method | Description |
---|---|
import_file |
facility to import data |
Note
The version 0.2.0 change the import and reload of the module python
Declare the entry point in the
setup.py
:from setuptools import setup setup( ... entry_points={ 'bloks': [ 'web=anyblok_web_server.bloks.web:Web', ], }, ... )
Note
The version 0.4.0, required all the declaration of the bloks on the entry point bloks
Declaration¶
In AnyBlok, everything is a declaration (Model, Mixin, …) and you have to
import the Declarations
class:
from anyblok.declarations import Declarations
The Declarations
has two main methods
Method name | Description |
---|---|
register |
Add the declaration in the registry This method can be used as:
|
unregister |
Remove an existing declaration from the registry. This method is only used as a function: from ... import Foo
unregister(``Declarations.type``, cls_=Foo)
|
Note
Declarations.type
must be replaced by:
- Model
- …
Declarations.type
defines the behaviour of the register
and
unregister
methods
Model¶
A Model is an AnyBlok class referenced in the registry. The registry is
hierarchical. The model Foo
is accessed by registry.Foo
and the model
Foo.Bar
is accessed by registry.Foo.Bar
.
To declare a Model you must use register
:
from anyblok.declarations import Declarations
register = Declarations.register
Model = Declarations.Model
@register(Model):
class Foo:
pass
The name of the model is defined by the name of the class (here Foo
).
The namespace of Foo
is defined by the hierarchy under Model
. In this
example, Foo
is in Model
, you can access at Foo
by Model.Foo
.
Warning
Model.Foo
is not the Foo
Model. It is an avatar of Foo
only
used for the declaration.
If you define the Bar
model, under the Foo
model, you should write:
@register(Model.Foo)
class Bar:
""" Description of the model """
pass
Note
The description is used by the model System.Model to describe the model
The declaration name of Bar
is Model.Foo.Bar
. The namespace of
Bar
in the registry is Foo.Bar
. The namespace of Foo
in the
registry is Foo
:
Foo = registry.Foo
Bar = registry.Foo.Bar
Some models have a table in the database. The name of the table is by default the
namespace in lowercase with .
replaced with .
.
Note
The registry is accessible only in the method of the models:
@register(Model)
class Foo:
def myMethod(self):
registry = self.registry
Foo = registry.Foo
The main goal of AnyBlok is not only to add models in the registry, but also to easily overload these models. The declaration stores the Python class in the registry. If one model already exist then the second declaration of this model overloads the first model:
@register(Model)
class Foo:
x = 1
@register(Model)
class Foo:
x = 2
------------------------------------------
Foo = registry.Foo
assert Foo.x == 2
Here are the parameters of the register
method for Model
:
Param | Description |
---|---|
cls_ | Define the real class if register is used as a
function not as a decorator |
name_ | Overload the name of the class: @register(Model, name_='Bar')
class Foo:
pass
Declarations.Bar
|
tablename | Overload the name of the table: @register(Model, tablename='my_table')
class Foo:
pass
|
is_sql_view | Boolean flag, which indicateis if the model is based on a SQL view. Deprecated use factory |
factory | Factory class to build the Model class.
Default : anyblok.model.factory.ModelFactory |
tablename | Define the real name of the table. By default the table name is the registry name without the declaration type, and with ‘.’ replaced with ‘_’. This attribute is also used to map an existing table declared by a previous Model. Allowed values:
|
Warning
Model can only inherit simple python class, Mixin or Model.
Non SQL Model¶
This is the default model. This model has no tables. It is used to organize the registry or for specific process.:
#register(Model)
class Foo:
pass
SQL Model¶
A SQL Model
is a simple Model
with Column
or RelationShip
. For
each model, one table will be created.:
@register(Model)
class Foo:
# SQL Model with mapped with the table ``foo``
id = Integer(primary_key=True)
# id is a column on the table ``foo``
Warning
Each SQL Model have to have got one or more primary key
In the case or you need to add some configuration in the SQLAlchemy class attrinute:
- __table_args__
- __mapper_args__
you can use the next class methods
method | description |
---|---|
define_table_args | Add options for SQLAlchemy table build:
@classmethod
def define_table_args(cls, table_args, properties):
# table_args: tuple of the known
# __table_args\_\_
# properties: properties of the assembled model
# columns, registry name
return my_tuple_value
|
define_mapper_args | Add options for SQLAlchemy mappers build:
@classmethod
def define_mapper_args(cls, mapper_args,
properties):
# table_args: dict of the known
# __mapper_args\_\_
# properties: properties of the assembled model
# columns, registry name
return my_dict_value
|
Note
New in 0.4.0
View Model¶
A View Model
as SQL Model
. Need the declaration of Column
and / or
RelationShip
. In the register
the param factory
must be
anyblok.model.factory.ViewFactory
and the View Model
must define the
sqlalchemy_view_declaration
classmethod.:
from anyblok.model.factory import ViewFactory
@register(Model, factory=ViewFactory)
class Foo:
id = Integer(primary_key=True)
name = String()
@classmethod
def sqlalchemy_view_declaration(cls):
from sqlalchemy.sql import select
Model = cls.registry.System.Model
return select([Model.id.label('id'), Model.name.label('name')])
sqlalchemy_view_declaration
must return a select query corresponding to the
request of the SQL view.
Column¶
To declare a Column
in a model, add a column on the table of the model.:
from anyblok.declarations import Declarations
from anyblok.column import Integer, String
@Declarations.register(Declaration.Model)
class MyModel:
id = Integer(primary_key=True)
name = String()
Note
Since the version 0.4.0 the Columns
are not Declarations
List of the column type:
DateTime
: use datetime.datetime, with pytz for the timezoneDecimal
: use decimal.DecimalFloat
Time
: use datetime.timeBigInteger
Boolean
Date
: use datetime.dateInteger
Interval
: use datetime.timedeltaLargeBinary
String
Text
Selection
Json
Sequence
Color
: use colour.ColorPassword
: use sqlalchemy_utils.types.password.PasswordUUID
: use uuidURL
: use furl.furlPhoneNumber
: use sqlalchemy_utils.PhoneNumberCountry
: use pycountry
All the columns have the following optional parameters:
Parameter | Description |
---|---|
label | Label of the column, If None the label is the name of column capitalized |
default | define a default value for this column. ..warning: The default value depends of the column type
..note: Put the name of a classmethod to call it
|
index | boolean flag to define whether the column is indexed |
nullable | Defines if the column must be filled or not |
primary_key | Boolean flag to define if the column is a primary key or not |
unique | Boolean flag to define if the column value must be unique or not |
foreign_key | Define a foreign key on this column to another column of another model: @register(Model)
class Foo:
id = Integer(primary_key=True)
@register(Model)
class Bar:
id = Integer(primary_key=True)
foo = Integer(foreign_key=Model.Foo.use('id'))
If the foo = Integer(foreign_key='Model.Foo=>id'))
|
db_column_name | String to define the real column name in the table, different from the model attribute name |
encrypt_key | Crypt the column in the database. can take the values:
..warning: The python package cryptography must be installed
|
Other attribute for String
:
Param | Description |
---|---|
size |
Column size in the table |
Other attribute for Selection
:
Param | Description |
---|---|
size |
column size in the table |
selections |
dict or dict.items to give the available key with
the associate label |
Other attribute for Sequence
:
Param | Description |
---|---|
size |
column size in the table |
code |
code of the sequence |
formater |
formater of the sequence |
Other attribute for Color
:
Param | Description |
---|---|
size |
column max size in the table |
Other attribute for Password
:
Param | Description |
---|---|
size |
password max size in the table |
crypt_context |
see the option for the python lib passlib |
..warning:
The Password column can be found with the query meth:
Other attribute for UUID
:
Param | Description |
---|---|
binary |
Stores a UUID in the database natively when it can and falls back to a BINARY(16) or a CHAR(32) |
Other attribute for DateTime
:
Param | Description |
---|---|
auto_update |
Boolean (default: False) if True the value will be update when the session is flushed |
default_timezone |
timezone or timezone’s name, define the timezone to on naive datetime. Warning The datetime with another timezone don’t change and keep their own timezone tokyo_tz = pytz.timezone('Asia/Tokyo')
@register(Model)
class Bar:
foo = DateTime(default_timezone=tokyo_tz)
//
foo = DateTime(default_timezone='Asia/Tokyo')
|
Other attribute for PhoneNumber
:
Param | Description |
---|---|
region |
Default region to save phone number (FR) |
max_length |
max size of the column in the database (20) |
RelationShip¶
To declare a RelationShip
in a model, add a RelationShip on the table of
the model.:
from anyblok.declarations import Declarations
from anyblok.column import Integer
from anyblok.relationship import Many2One
@Declarations.register(Declaration.Model)
class MyModel:
id = Integer(primary_key=True)
@Declarations.register(Declaration.Model)
class MyModel2:
id = Integer(primary_key=True)
mymodel = Many2One(model=Declaration.Model.MyModel)
Note
Since the version 0.4.0 the RelationShip
don’t come from Declarations
List of the RelationShip type:
One2One
Many2One
One2Many
Many2Many
Parameters of a RelationShip
:
Param | Description |
---|---|
label |
The label of the column |
model |
The remote model |
remote_columns |
The column name on the remote model, if no remote columns are defined the remote column will be the primary column of the remote model |
Parameters of the One2One
field:
Param | Description |
---|---|
column_names |
Name of the local column. If the column doesn’t exist then this column will be created. If no column name then the name will be ‘M2O name’ + ‘_’ + ‘name of the remote column’ |
nullable |
Indicates if the column name is nullable or not |
backref |
Remote One2One link with the column name |
unique |
Add unique constraint on the created column(s) |
index |
Add index constraint on the created column(s) |
primary_key |
The created column(s) are primary key |
Parameters of the Many2One
field:
Parameter | Description |
---|---|
column_names |
Name of the local column. If the column doesn’t exist then this column will be created. If no column name then the name will be ‘M2O name’ + ‘_’ + ‘name of the remote column’ |
nullable |
Indicate if the column name is nullable or not |
unique |
Add unique constraint on the created column(s) |
index |
Add index constraint on the created column(s) |
primary_key |
The created column(s) are primary key |
one2many |
Opposite One2Many link with this Many2one |
foreign_key_options |
take a dict with the option for create the foreign key |
Many2One(model=The.Model, nullable=True,
foreign_key_options={'ondelete': cascade})
Parameters of the One2Many
field:
Parameter | Description |
---|---|
primaryjoin |
Join condition between the relationship and the remote column |
many2one |
Opposite Many2One link with this One2Many |
Warning
In the case where two or more foreign keys is found to the same primary key,
then the primary join become a or
between them. You must considere this
field as a readonly field, because SQLAlchemy will change the both foreign key
Parameters of the Many2Many
field:
Parameter | Description |
---|---|
join_table |
many2many intermediate table between both models |
join_model |
many2many intermediate table compute from a Model, This attribute is used to build a rich Many2Many Warning An exception is raised if the table come from join_table and join_model are different |
m2m_remote_columns |
Column name in the join table which have got the foreign key to the remote model |
local_columns |
Name of the local column which holds the foreign key to the join table. If the column does not exist then this column will be created. If no column name then the name will be ‘tablename’ + ‘_’ + name of the relationship |
m2m_local_columns |
Column name in the join table which holds the foreign key to the model |
many2many |
Opposite Many2Many link with this relationship |
compute_join |
Force to compute secondaryjoin and primaryjoin In the most case this is forbidden because it is dangeourous, The only case where the compute is required, is when the model_join have more than one primary key to the main model for rich Many2Many Note In the case where the both model are the same this option is forced |
Note
Since 0.4.0, when the relationnal table is created by AnyBlok, the m2m_columns becomme foreign keys
Field¶
To declare a Field
in a model, add a Field on the Model, this is not a
SQL column.:
from anyblok.declarations import Declarations
from anyblok.field import Function
from anyblok.column import Integer
@Declarations.register(Declaration.Model)
class MyModel:
id = Integer(primary_key=True)
first_name = String()
last_name = String()
name = Function(fget='fget', fset='fset', fdel='fdel', fexpr='fexpr')
def fget(self):
return '{0} {1}'.format(self.first_name, self.last_name)
def fset(self, value):
self.first_name, self.last_name = value.split(' ', 1)
def fdel(self):
self.first_name = self.last_name = None
@classmethod
def fexpr(cls):
return func.concat(cls.first_name, ' ', cls.last_name)
List of the Field
type:
Function
JsonRelated
Parameters for Field.Function
Parameter | Description |
---|---|
fget |
name of the method to call to get the value of field: def fget(self):
return '{0} {1}'.format(self.first_name,
self.last_name)
|
fset |
name of the method to call to set the value of field: def fset(self):
self.first_name, self.last_name = value.split(' ',
1)
|
fdel |
name of the method to call to del the value of field: def fdel(self):
self.first_name = self.last_name = None
|
fexp |
name of the class method to call to filter on the field: @classmethod
def fexp(self):
return func.concat(cls.first_name, ' ',
cls.last_name)
|
Parameters for Field.JsonRelated
Define setter, getter for a key in Column.Json, it is a helper to do an alias of specific entry in a Column.Json.
Parameter | Description |
---|---|
json_column |
name of the json column in the Model |
keys |
list of string, represent the path in json to store and get the value |
get_adapter |
method to convert the date after get it. This value can be the name of a method on the model |
set_adapter |
method to convert the date before store it. This value can be the name of a method on the model |
Mixin¶
A Mixin looks like a Model, but has no tables. A Mixin adds behaviour to a Model with Python inheritance:
@register(Mixin)
class MyMixin:
def foo():
pass
@register(Model)
class MyModel(Mixin.MyMixin):
pass
----------------------------------
assert hasattr(registry.MyModel, 'foo')
If you inherit a mixin, all the models previously using the base mixin also benefit from the overload:
@register(Mixin)
class MyMixin:
pass
@register(Model)
class MyModel(Mixin.MyMixin):
pass
@register(Mixin)
class MyMixin:
def foo():
pass
----------------------------------
assert hasattr(registry.MyModel, 'foo')
SQL View¶
An SQL view is a model, with the argument factory=anyblok.model.factory.ViewFactory
in the
register. and the classmethod sqlalchemy_view_declaration
:
from anyblok.model.factory import ViewFactory
@register(Model)
class T1:
id = Integer(primary_key=True)
code = String()
val = Integer()
@register(Model)
class T2:
id = Integer(primary_key=True)
code = String()
val = Integer()
@register(Model, factory=ViewFactory)
class TestView:
code = String(primary_key=True)
val1 = Integer()
val2 = Integer()
@classmethod
def sqlalchemy_view_declaration(cls):
""" This method must return the query of the view """
T1 = cls.registry.T1
T2 = cls.registry.T2
query = select([T1.code.label('code'),
T1.val.label('val1'),
T2.val.label('val2')])
return query.where(T1.code == T2.code)
Core¶
Core
is a low level set of declarations for all the Models of AnyBlok. Core
adds
general behaviour to the application.
Warning
Core can not inherit Model, Mixin, Core, or other declaration type.
Base¶
Add a behaviour in all the Models, Each Model inherits Base. For instance, the
fire
method of the event come from Core.Base
.
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class Base:
pass
SqlBase¶
Only the Models with Field
, Column
, RelationShip
inherits Core.SqlBase
.
For instance, the insert
method only makes sense for the Model
with a table.
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class SqlBase:
pass
SqlViewBase¶
Like SqlBase
, only the SqlView
inherits this Core
class.
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class SqlViewBase:
pass
Query¶
Overloads the SQLAlchemy Query
class.
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class Query
pass
Session¶
Overloads the SQLAlchemy Session
class.
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class Session
pass
InstrumentedList¶
from anyblok import Declarations
@Declarations.register(Declarations.Core)
class InstrumentedList
pass
InstrumentedList
is the class returned by the Query for all the list result
like:
- query.all()
- relationship list (Many2Many, One2Many)
Adds some features like getting a specific property or calling a method on all the elements of the list:
MyModel.query().all().foo(bar)
Sharing a table between more than one model¶
SQLAlchemy allows two methods to share a table between two or more mapping class:
Inherit an SQL Model in a non-SQL Model:
@register(Model) class Test: id = Integer(primary_key=True) name = String() @register(Model) class Test2(Model.Test): pass ---------------------------------------- t1 = Test1.insert(name='foo') assert Test2.query().filter(Test2.id == t1.id, Test2.name == t1.name).count() == 1
- Share the
__table__
. AnyBlok cannot give the table at the declaration, because the table does not exist yet. But during the assembly, if the table exists and the model has the name of this table, AnyBlok directly links the table. To define the table you must use the named argument
tablename
in theregister
@register(Model) class Test: id = Integer(primary_key=True) name = String() @register(Model, tablename=Model.Test) class Test2: id = Integer(primary_key=True) name = String() ---------------------------------------- t1 = Test1.insert(name='foo') assert Test2.query().filter(Test2.id == t1.id, Test2.name == t1.name).count() == 1
Warning
There are no checks on the existing columns.
- Share the
Sharing a view between more than one model¶
Sharing a view between two Models is the merge between:
- Creating a View Model
- Sharing the same table between more than one model.
Warning
For the view you must redined the column in the Model corresponding to the view with inheritance or simple Share by tablename
Specific behaviour¶
AnyBlok implements some facilities to help developers
Column encryption¶
You can encrypt some columns to protect them. The python package cryptography must be installed:
pip install cryptography
Use the encrypt_key attribute on the column to define the key of cryptography:
@register(Model)
class MyModel:
# define the specific encrypt_key
encrypt_column_1 = String(encrypt_key='SecretKey')
# Use the default encrypt_key
encrypt_column_2 = String(encrypt_key=Configuration.get('default_encrypt_key')
encrypt_column_3 = String(encrypt_key=True)
# Use the class method to get encrypt_key
encrypt_column_1 = String(encrypt_key='get_encrypt_key')
@classmethod
def get_encrypt_key(cls):
return 'SecretKey'
The encryption works for any Columns.
Environment¶
The Environment contains non persistent contextual variables. By
default, it is stored in the current Thread
object, but that
is amendable (see Define a new environment type).
Use the current environment¶
The environment can be used from whereever in the code.
Generic use¶
To get or set variable in environment, you must import the
EnvironmentManager
:
from anyblok.environment import EnvironmentManager
Set a variable:
EnvironmentManager.set('my variable name', some_value)
Get a variable:
EnvironmentManager.get('my variable name', default=some_default)
Use from a Model
¶
A class-level attribute is present on all Model classes to access the Environment variables conveniently.
To grab the EnvironmentManager from a Model
method, just use
self.Env
. For a classmethod, that would be as in:
@classmethod
def myclsmeth(cls):
env = cls.Env
(...)
Then, it’s easy to get and set variables. Here’s an example from a Model instance method:
self.Env.set('my variable name', some_value)
self.Env.get('my variable name', default=some_default_value)
Note
the Env
attribute is actually set in
registry.registry_base
, which is a class dynamically
generated at registry creation, and of which all assembled
classes stored in the registry inherit.
Define a new environment type¶
If you do not want to stock the environment in the Thread
, you must
implement a new type of environment.
This type is a simple class which have theses class methods:
- scoped_function_for_session
- setter
- getter
MyEnvironmentClass:
@classmethod
def scoped_function_for_session(cls):
...
@classmethod
def setter(cls, key, value):
...
@classmethod
def getter(cls, key, default):
...
return value
Declare your class as the Environment class:
EnvironmentManager.define_environment_cls(MyEnvironmentClass)
The classmethod scoped_function_for_session
is passed at SQLAlchemy
scoped_session
function see
Cache¶
The cache allows to call a method more than once without having any difference in the result. But the cache must also depend on the registry database and the model. The cache of anyblok can be put on a Model, a Core or a Mixin method. If the cache is on a Core or a Mixin then the usecase depends on the registry name of the assembled model.
Use cache
or classmethod_cache
to apply a cache on a method:
from anyblok.declarations import cache, classmethod_cache
Warning
cache
depend of the instance, if you want add a cache for
any instance you must use classmethod_cache
Cache the method of a Model:
@register(Model)
class Foo:
@classmethod_cache()
def bar(cls):
import random
return random.random()
-----------------------------------------
assert Foo.bar() == Foo.bar()
Cache the method coming from a Mixin:
@register(Mixin)
class MFoo:
@classmethod_cache()
def bar(cls):
import random
return random.random()
@register(Model)
class Foo(Mixin.MFoo):
pass
@register(Model)
class Foo2(Mixin.MFoo):
pass
-----------------------------------------
assert Foo.bar() == Foo.bar()
assert Foo2.bar() == Foo2.bar()
assert Foo.bar() != Foo2.bar()
Cache the method coming from a Mixin:
@register(Core)
class Base
@classmethod_cache()
def bar(cls):
import random
return random.random()
@register(Model)
class Foo:
pass
@register(Model)
class Foo2:
pass
-----------------------------------------
assert Foo.bar() == Foo.bar()
assert Foo2.bar() == Foo2.bar()
assert Foo.bar() != Foo2.bar()
Event¶
Simple implementation of a synchronous event
for AnyBlok or SQLAlchemy:
@register(Model)
class Event:
pass
@register(Model)
class Test:
x = 0
@listen(Model.Event, 'fireevent')
def my_event(cls, a=1, b=1):
cls.x = a * b
---------------------------------------------
registry.Event.fire('fireevent', a=2)
assert registry.Test.x == 2
Note
The decorated method is seen as a classmethod
This API gives:
- a decorator
listen
which binds the decorated method to the event. fire
method with the following parameters (Only for AnyBlok event):event
: string name of the event*args
: positionnal arguments to pass att the decorated method**kwargs
: named argument to pass at the decorated method
It is possible to overload an existing event listener, just by overloading the decorated method:
@register(Model)
class Test:
@classmethod
def my_event(cls, **kwarg):
res = super(Test, cls).my_event(**kwargs)
return res * 2
---------------------------------------------
registry.Event.fire('fireevent', a=2)
assert registry.Test.x == 4
Warning
The overload does not take the listen
decorator but the
classmethod decorator, because the method name is already seen as an
event listener
Some of the Attribute events of the Mapper events are implemented. See the SQLAlchemy ORM Events http://docs.sqlalchemy.org/en/latest/orm/events.html#orm-events
You may also add a classmethod with the name event type + '_orm_event'
. The event will be automaticly
create with on the Model and the event type without arguments:
@register(Model)
class Test:
x = 0
@classmethod
def after_insert_orm_event(cls, mapper, connection, target):
# call when a new instance of Test is added in the session
pass
@listen('Model.Test', 'after_insert')
def another_orm_event(cls, mapper, connection, target):
# it is the same effect as ``after_insert_orm_event``,
# it is call after the add of a new instance in the session
Hybrid method¶
Facility to create an SQLAlchemy hybrid method. See this page: http://docs.sqlalchemy.org/en/latest/orm/extensions/hybrid.html#module-sqlalchemy.ext.hybrid
AnyBlok allows to define a hybrid_method which can be overloaded, because the real sqlalchemy decorator is applied after assembling in the last overload of the decorated method:
from anyblok.declarations import hybrid_method
@register(Model)
class Test:
@hybrid_method
def my_hybrid_method(self):
return ...
Pre-commit hook¶
It is possible to call specific classmethods just before the commit of the session:
@register(Model)
class Test:
id = Integer(primary_key=True)
val = Integer(default=0)
@classmethod
def method2call_just_before_the_commit(cls, *a, **kw):
pass
-----------------------------------------------------
registry.Test.precommit_hook('method2call_just_before_the_commit', *a, **kw)
Post-commit hook¶
It is possible to call specific classmethods just after the commit of the session:
@register(Model)
class Test:
id = Integer(primary_key=True)
val = Integer(default=0)
@classmethod
def method2call_just_after_the_commit(cls, *a, **kw):
pass
-----------------------------------------------------
registry.Test.postcommit_hook('method2call_just_after_the_commit', *a, **kw)
Aliased¶
Facility to create an SQL alias for the SQL query by the ORM:
select * from my_table the_table_alias.
This facility is given by SQLAlchemy, and anyblok adds this functionnality directly in the Model:
BlokAliased = registry.System.Blok.aliased()
Note
See this page:
http://docs.sqlalchemy.org/en/latest/orm/query.html#sqlalchemy.orm.aliased
to know the parameters of the aliased
method
Warning
The first arg is already passed by AnyBlok
Warning
Only this method give the registry into the alias, don’t import sqlalchemy.orm.aliased
Get the registry¶
You can get a Model by the registry in any method of Models:
Model = self.registry.System.Model
assert Model.__registry_name__ == 'Model.System.Model'
Get the current environment¶
The current environment is saved in the main thread. You can add a value to the current Environment:
self.Env.set('My var', 'one value')
You can get a value from the current Environment:
myvalue = self.Env.get('My var', defaul="My default value")
Note
The environment is as a dict the value can be an instance of any type
Initialize some data by entry point¶
the entry point anyblok.init
allow to define function, ìnit_function
in this example:
setup(
...
entry_points={
'anyblok.init': [
'my_function=path:init_function',
],
},
)
In the path the init_function must be defined:
def init_function(unittest=False):
...
..warning:
Use unittest parameter to defined if the function must be call
or not
Make easily ReadOnly model¶
In somme case you want that your model is:
readonly: No modification, No deletion:
@register(...) class MyModel(Mixin.ReadOnly): ...
forbid modification: No modification but can delete:
@register(...) class MyModel(Mixin.ForbidUpdate): ...
forbid deletion: No deletion but can modify:
@register(...) class MyModel(Mixin.ForbidDelete): ...
Plugin¶
Plugin is used for the low level, it is not use in the bloks, because the model can be overload by the declaration.
Define a new plugin¶
A plugin can be a class or a function:
class MyPlugin:
pass
Add the plugin definition in the configuration:
@Configuration.add('plugins')
def add_plugins(self, group)
group.add_argument('--my-option', dest='plugin_name',
type=AnyBlokPlugin,
default='path:MyPlugin')
Use the plugin:
plugin = Configuration.get('plugin_name')
anyblok.model.plugin¶
This a hook to add new feature in Model, this is already use for:
- hybrid_method
- table and mapper args
- event
- Sqlalchemy event
- cache / classmethod_cache
Start by implementing the plugin (see
ModelPluginBase
):
from anyblok.model.plugins import ModelPluginBase
class MyPlugin(ModelPluginBase):
...
Then, declare it in setup.py
:
setup(
...
entry_points={
...
'anyblok.model.plugin': [
'myplugin=path:MyPlugin',
],
...
},
...
)
anyblok.model.factory¶
This factory is used to:
- give the core classes need to build the model
- build the model
Start by implementing the factory (see
BaseFactory
):
from anyblok.model.factory import BaseFactory
class MyFactory(BaseFactory):
def insert_core_bases(self, bases, properties):
...
def build_model(self, modelname, bases, properties):
...
In your bloks you can use your factory:
@register(Model, factory=MyFactory)
class MyModel:
...