POPPy is built around SQLAlchemy , an Object-Relational Mapper (ORM) well known by Python developers.
The model integration needs some code to work, located in every __init__.py of
the models/
directory of each plugin. This code is automatically generated
when you call the command poppy create plugin
.
If for some reason you do not have the code, you can find it in the POPPyCore
source code at POPPyCore/poppy/core/management/templates/plugin_template/plugin_namespace/plugin_name/models/__init__.py-tpl
First, create a file in your plugin’s model directory and add these imports:
from poppy.pop.models.non_null_column import NonNullColumn
from poppy.core.db.base import Base
Then, you can create your model class inheriting from the base class, as you would any SQLAlchemy model:
class Dictionary(Base):
id_dictionary = NonNullColumn(INTEGER(), primary_key=True, unique=True)
word = NonNullColumn(String(16), descr='The word', unique=True, comment='Must be an english word')
__tablename__ = 'dictionary'
In this class you will define every column of your table including their
type (either general or dialect specific types). If you want to generate
your database documentation automatically (see chapter TBC), you should use
poppy’s custom column class NonNullColumn
instead of
sqlalchemy.Column
.
The fields description
and comment
are used to generate the
documentation.
Note
Do not use double quotes in your description or comment, but only escaped single quotes.
You might want to use SQLAlchemy to interface an already existing secondary database. In this case, you don’t need to detail every column of the table. You only need to create a class that inherits from Base and DeferredReflection, and give the table name. You don’t even need a primary key.
from sqlalchemy.ext.declarative import DeferredReflection
class PacketType(DeferredReflection, Base):
__tablename__ = "packettype"
The migration generation is integrated in the POPPy framework through calls to alembic. It is able to generate distinct migrations for every plugin.
python manage.py db makemigrations tuto.texter
You can use any argument of the alembic revision command as follows
python manage.py db makemigrations tuto.texter --message='First tuto.texter migration' --depends_on='poppy.pop' --head=poppy.pop
Alembic migration generation is not perfect and you have to manually review and correct the migrations that alembic produces. Furthermore, alembic is not yet fully integrated to the POPPy framework and some modifications has to be done in order for the migration to be registered by the framework. See (POPPy improvements) chapter.
First add this import
from poppy.pop.alembic.helpers import create_table, execute, user, drop_table
Then, replace every op.create_table
with the poppy wrapper function
create_table
. It is needed to grant access to the pipeline user and log
what is happening. Do the same with drop_table
.
At this point you can also edit the alembic revision identifiers, for example
add a branch label for convenience and the migration this one depends on
(remember you can do all of this with the command arguments)
Finally, you should check the migration for any mistake that alembic may have made. See this alembic documentation page to know what alembic can and cannot detect.
Note
You can manage your branches as explained in the alembic documentation about branches
You can call alembic commands through the pipeline with this command, so you don’t have to worry about users and databases or even alembic configuration:
python manage.py db alembic ...
You can either run migrations one by one by giving their branch label:
$ python manage.py db upgrade poppy.pop
$ python manage.py db upgrade tuto.texter
or all in one go with:
$ python manage.py db upgrade heads
If you want to use postgres schemas, you need to specify it in the models, in the
__table_args__
field:
class Dictionary(Base):
...
__table_args__ = {'
schema': 'my_schema'
}
Then, when editing the first migration you need to manually add the creation and deletion of the schema :
from poppy.pop.alembic.helpers import create_schema, execute, drop_schema
def upgrade():
create_schema('my_schema')
...
def downgrade():
drop_schema('my_schema', cascade=True)
Using this command :
poppy gen_doc your.plugin
You can generate a reST document containing the description of every database table of the given plugin. There is one document per plugin. The document is generated Here is an example of rst code generated and what it looks like once compiled.
The table dictionary
~~~~~~~~~~~~~~~~~~~~
The "dictionary" table contains the dictionary
.. csv-table:: dictionary
:header: "Column name", "Data type", "Description", "Priority", "Comment"
"id_dictionary", "INTEGER", "Primary key", "PK", ""
"identifier", "INTEGER", "Identifier of the word", "NN", "Must be unique. "
"word", "VARCHAR(16)", "The word", "NN", "Must be unique. Must be an english word."
The tuple of columns (identifier,word) must be unique.
The “dictionary” table contains the dictionary
Column name | Data type | Description | Priority | Comment |
---|---|---|---|---|
id_dictionary | INTEGER | Primary key | PK | |
identifier | INTEGER | Identifier of the word | NN | Must be unique. |
word | VARCHAR(16) | The word | NN | Must be unique. Must be an english word. |
The tuple of columns (identifier,word) must be unique.
Note
The code responsible for the documentation generation is located in
poppy.core.management.commands in the function gen_doc()