@@ -0,0 +1,105 @@ | |||||
# A generic, single database configuration. | |||||
[alembic] | |||||
# path to migration scripts | |||||
script_location = alembic | |||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s | |||||
# Uncomment the line below if you want the files to be prepended with date and time | |||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file | |||||
# for all available tokens | |||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s | |||||
# sys.path path, will be prepended to sys.path if present. | |||||
# defaults to the current working directory. | |||||
prepend_sys_path = . | |||||
# timezone to use when rendering the date within the migration file | |||||
# as well as the filename. | |||||
# If specified, requires the python-dateutil library that can be | |||||
# installed by adding `alembic[tz]` to the pip requirements | |||||
# string value is passed to dateutil.tz.gettz() | |||||
# leave blank for localtime | |||||
# timezone = | |||||
# max length of characters to apply to the | |||||
# "slug" field | |||||
# truncate_slug_length = 40 | |||||
# set to 'true' to run the environment during | |||||
# the 'revision' command, regardless of autogenerate | |||||
# revision_environment = false | |||||
# set to 'true' to allow .pyc and .pyo files without | |||||
# a source .py file to be detected as revisions in the | |||||
# versions/ directory | |||||
# sourceless = false | |||||
# version location specification; This defaults | |||||
# to alembic/versions. When using multiple version | |||||
# directories, initial revisions must be specified with --version-path. | |||||
# The path separator used here should be the separator specified by "version_path_separator" below. | |||||
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions | |||||
# version path separator; As mentioned above, this is the character used to split | |||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. | |||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. | |||||
# Valid values for version_path_separator are: | |||||
# | |||||
# version_path_separator = : | |||||
# version_path_separator = ; | |||||
# version_path_separator = space | |||||
version_path_separator = os # Use os.pathsep. Default configuration used for new projects. | |||||
# the output encoding used when revision files | |||||
# are written from script.py.mako | |||||
# output_encoding = utf-8 | |||||
sqlalchemy.url = sqlite+pysqlite:///:memory: | |||||
[post_write_hooks] | |||||
# post_write_hooks defines scripts or Python functions that are run | |||||
# on newly generated revision scripts. See the documentation for further | |||||
# detail and examples | |||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint | |||||
# hooks = black | |||||
# black.type = console_scripts | |||||
# black.entrypoint = black | |||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME | |||||
# Logging configuration | |||||
[loggers] | |||||
keys = root,sqlalchemy,alembic | |||||
[handlers] | |||||
keys = console | |||||
[formatters] | |||||
keys = generic | |||||
[logger_root] | |||||
level = WARN | |||||
handlers = console | |||||
qualname = | |||||
[logger_sqlalchemy] | |||||
level = WARN | |||||
handlers = | |||||
qualname = sqlalchemy.engine | |||||
[logger_alembic] | |||||
level = INFO | |||||
handlers = | |||||
qualname = alembic | |||||
[handler_console] | |||||
class = StreamHandler | |||||
args = (sys.stderr,) | |||||
level = NOTSET | |||||
formatter = generic | |||||
[formatter_generic] | |||||
format = %(levelname)-5.5s [%(name)s] %(message)s | |||||
datefmt = %H:%M:%S |
@@ -0,0 +1,41 @@ | |||||
New Version | |||||
----------- | |||||
To create a revision, update the ORM in medashare/orm.py. | |||||
Then run: | |||||
``` | |||||
alembic revision --autogenerate -m 'what you did' | |||||
``` | |||||
This will create a version file. Edit the version file to support the | |||||
migration (such as populating new columns). | |||||
Once things are tested as good, commit everything. | |||||
Differences from template | |||||
------------------------- | |||||
There are a couple difference, first the `alembic.ini` file | |||||
was updated: | |||||
``` | |||||
sqlalchemy.url = sqlite+pysqlite:///:memory: | |||||
``` | |||||
This lets the `--autogenerate` work properly. | |||||
The second is that the `env.py:run_migrations_online` was updated with: | |||||
``` | |||||
if 'engine' in config.attributes: | |||||
connectable = config.attributes['engine'] | |||||
else: | |||||
``` | |||||
so that the engine could be passed in directly as opposed to | |||||
opening the file again, or passing in the url. | |||||
TODO | |||||
---- | |||||
Figure out how to automatically add the `medashare.orm` import to | |||||
the script. The template implies that there is a way, but don't know | |||||
where the config comes from. |
@@ -0,0 +1,83 @@ | |||||
from logging.config import fileConfig | |||||
from sqlalchemy import engine_from_config | |||||
from sqlalchemy import pool | |||||
from alembic import context | |||||
# this is the Alembic Config object, which provides | |||||
# access to the values within the .ini file in use. | |||||
config = context.config | |||||
# Interpret the config file for Python logging. | |||||
# This line sets up loggers basically. | |||||
if config.config_file_name is not None: | |||||
fileConfig(config.config_file_name) | |||||
# add your model's MetaData object here | |||||
# for 'autogenerate' support | |||||
# from myapp import mymodel | |||||
# target_metadata = mymodel.Base.metadata | |||||
import medashare.orm | |||||
target_metadata = medashare.orm.Base.metadata | |||||
# other values from the config, defined by the needs of env.py, | |||||
# can be acquired: | |||||
# my_important_option = config.get_main_option("my_important_option") | |||||
# ... etc. | |||||
def run_migrations_offline() -> None: | |||||
"""Run migrations in 'offline' mode. | |||||
This configures the context with just a URL | |||||
and not an Engine, though an Engine is acceptable | |||||
here as well. By skipping the Engine creation | |||||
we don't even need a DBAPI to be available. | |||||
Calls to context.execute() here emit the given string to the | |||||
script output. | |||||
""" | |||||
url = config.get_main_option("sqlalchemy.url") | |||||
context.configure( | |||||
url=url, | |||||
target_metadata=target_metadata, | |||||
literal_binds=True, | |||||
dialect_opts={"paramstyle": "named"}, | |||||
) | |||||
with context.begin_transaction(): | |||||
context.run_migrations() | |||||
def run_migrations_online() -> None: | |||||
"""Run migrations in 'online' mode. | |||||
In this scenario we need to create an Engine | |||||
and associate a connection with the context. | |||||
""" | |||||
if 'engine' in config.attributes: | |||||
connectable = config.attributes['engine'] | |||||
else: | |||||
connectable = engine_from_config( | |||||
config.get_section(config.config_ini_section), | |||||
prefix="sqlalchemy.", | |||||
poolclass=pool.NullPool, | |||||
) | |||||
with connectable.connect() as connection: | |||||
context.configure( | |||||
connection=connection, target_metadata=target_metadata | |||||
) | |||||
with context.begin_transaction(): | |||||
context.run_migrations() | |||||
if context.is_offline_mode(): | |||||
run_migrations_offline() | |||||
else: | |||||
run_migrations_online() |
@@ -0,0 +1,24 @@ | |||||
"""${message} | |||||
Revision ID: ${up_revision} | |||||
Revises: ${down_revision | comma,n} | |||||
Create Date: ${create_date} | |||||
""" | |||||
from alembic import op | |||||
import sqlalchemy as sa | |||||
${imports if imports else ""} | |||||
# revision identifiers, used by Alembic. | |||||
revision = ${repr(up_revision)} | |||||
down_revision = ${repr(down_revision)} | |||||
branch_labels = ${repr(branch_labels)} | |||||
depends_on = ${repr(depends_on)} | |||||
def upgrade() -> None: | |||||
${upgrades if upgrades else "pass"} | |||||
def downgrade() -> None: | |||||
${downgrades if downgrades else "pass"} |
@@ -0,0 +1,63 @@ | |||||
"""initial db schema | |||||
Revision ID: afad01589b76 | |||||
Revises: | |||||
Create Date: 2022-09-09 17:08:06.132506 | |||||
""" | |||||
from alembic import op | |||||
import sqlalchemy as sa | |||||
import medashare.orm | |||||
# revision identifiers, used by Alembic. | |||||
revision = 'afad01589b76' | |||||
down_revision = None | |||||
branch_labels = None | |||||
depends_on = None | |||||
def upgrade() -> None: | |||||
# ### commands auto generated by Alembic - please adjust! ### | |||||
op.create_table('dummy', | |||||
sa.Column('id', sa.Integer(), nullable=False), | |||||
sa.PrimaryKeyConstraint('id') | |||||
) | |||||
op.create_table('hash_index', | |||||
sa.Column('hash', sa.String(), nullable=False), | |||||
sa.Column('uuid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.PrimaryKeyConstraint('hash', 'uuid') | |||||
) | |||||
op.create_table('hostmapping', | |||||
sa.Column('hostid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.Column('objid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.PrimaryKeyConstraint('hostid', 'objid') | |||||
) | |||||
op.create_table('hosttable', | |||||
sa.Column('hostid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.Column('objid', medashare.orm.UUID(length=32), nullable=True), | |||||
sa.PrimaryKeyConstraint('hostid') | |||||
) | |||||
op.create_table('metadata_objects', | |||||
sa.Column('uuid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.Column('modified', sa.DateTime(), nullable=True), | |||||
sa.Column('data', medashare.orm.MDBaseType(), nullable=True), | |||||
sa.PrimaryKeyConstraint('uuid') | |||||
) | |||||
op.create_table('uuidv5_index', | |||||
sa.Column('uuid', medashare.orm.UUID(length=32), nullable=False), | |||||
sa.Column('objid', medashare.orm.UUID(length=32), nullable=True), | |||||
sa.PrimaryKeyConstraint('uuid') | |||||
) | |||||
# ### end Alembic commands ### | |||||
def downgrade() -> None: | |||||
# ### commands auto generated by Alembic - please adjust! ### | |||||
op.drop_table('uuidv5_index') | |||||
op.drop_table('metadata_objects') | |||||
op.drop_table('hosttable') | |||||
op.drop_table('hostmapping') | |||||
op.drop_table('hash_index') | |||||
op.drop_table('dummy') | |||||
# ### end Alembic commands ### |
@@ -284,13 +284,35 @@ class ObjectStore(object): | |||||
# looking up the UUIDv5 for FileObjects. | # looking up the UUIDv5 for FileObjects. | ||||
def __init__(self, engine, created_by_ref): | def __init__(self, engine, created_by_ref): | ||||
orm.Base.metadata.create_all(engine) | |||||
#orm.Base.metadata.create_all(engine) | |||||
self._engine = engine | self._engine = engine | ||||
self._ses = sessionmaker(engine) | self._ses = sessionmaker(engine) | ||||
self._created_by_ref = created_by_ref | self._created_by_ref = created_by_ref | ||||
self._handle_migration() | |||||
def _handle_migration(self): | |||||
'''Handle migrating the database to a newer version.''' | |||||
# running commands directly: | |||||
# pydoc3 alembic.config.Config | |||||
# pydoc3 alembic.commands | |||||
# inspecting the scripts directly: | |||||
# alembic/script/base.py:61 | |||||
from alembic import command | |||||
from alembic.config import Config | |||||
config = Config() | |||||
config.set_main_option("script_location", "medashare:alembic") | |||||
with self._engine.begin() as connection: | |||||
config.attributes['engine'] = self._engine | |||||
command.upgrade(config, 'head') | |||||
def get_host(self, hostuuid): | def get_host(self, hostuuid): | ||||
hostuuid = _makeuuid(hostuuid) | hostuuid = _makeuuid(hostuuid) | ||||
@@ -19,6 +19,7 @@ setup( | |||||
long_description=open('README.md').read(), | long_description=open('README.md').read(), | ||||
python_requires='>=3.8', | python_requires='>=3.8', | ||||
install_requires=[ | install_requires=[ | ||||
'alembic', | |||||
'base58', | 'base58', | ||||
'cryptography', | 'cryptography', | ||||
'databases[sqlite]', | 'databases[sqlite]', | ||||