@@ -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. | |||
def __init__(self, engine, created_by_ref): | |||
orm.Base.metadata.create_all(engine) | |||
#orm.Base.metadata.create_all(engine) | |||
self._engine = engine | |||
self._ses = sessionmaker(engine) | |||
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): | |||
hostuuid = _makeuuid(hostuuid) | |||
@@ -19,6 +19,7 @@ setup( | |||
long_description=open('README.md').read(), | |||
python_requires='>=3.8', | |||
install_requires=[ | |||
'alembic', | |||
'base58', | |||
'cryptography', | |||
'databases[sqlite]', | |||