| @@ -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]', | ||||