From 692f3ffd9b5d353ad00effd294f8398a277341a9 Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Fri, 14 Aug 2026 23:32:44 -0400 Subject: [PATCH 1/6] Added alembic for database migrations --- .github/pull_request_template.md | 1 + README.md | 48 +++ alembic.ini | 126 +++++++ bbconf/alembic/README | 1 + bbconf/alembic/__init__.py | 0 bbconf/alembic/env.py | 77 ++++ bbconf/alembic/script.py.mako | 28 ++ .../8b0b706d0827_initial_migration.py | 352 ++++++++++++++++++ bbconf/alembic/versions/__init__.py | 0 bbconf/bbagent.py | 1 + bbconf/config_parser/bedbaseconfig.py | 1 + bbconf/config_parser/models.py | 1 + bbconf/db_utils.py | 33 ++ pyproject.toml | 4 + 14 files changed, 673 insertions(+) create mode 100644 alembic.ini create mode 100644 bbconf/alembic/README create mode 100644 bbconf/alembic/__init__.py create mode 100644 bbconf/alembic/env.py create mode 100644 bbconf/alembic/script.py.mako create mode 100644 bbconf/alembic/versions/8b0b706d0827_initial_migration.py create mode 100644 bbconf/alembic/versions/__init__.py diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a24b8bf0..163df722 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,5 +3,6 @@ ## TODO: +- [ ] ❗ If this PR includes a new database schema migration, following steps are completed: [Database Version Migration](https://pep.databio.org/pephub/developer/pepdbagent/database_version_migration/) - [ ] Version of pepdbagent updated in `__version__.py` file - [ ] Changelog updated \ No newline at end of file diff --git a/README.md b/README.md index df71db09..6caff043 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,51 @@ agent.objects # Generic object/file operations stats = agent.get_stats() print(stats.bedfiles_number, stats.bedsets_number) ``` + +## Database migrations + +`bbconf` uses [Alembic](https://alembic.sqlalchemy.org/) to version the database +schema. The migration scripts live in `bbconf/alembic`, and `alembic.ini` (repo +root) is used for local CLI work. The first (baseline) revision is +`8b0b706d0827`; it reproduces exactly the schema that `Base.metadata.create_all()` +builds, including the `pg_trgm` extension and the trigram / partial / expression +indexes. + +### Creating a new revision + +After changing the models in `bbconf/db_utils.py`: + +```bash +alembic revision --autogenerate -m "Describe your change" +``` + +Review the generated file. Alembic cannot autogenerate a few constructs used by +bbconf — the `pg_trgm` extension and expression-based indexes may need a manual +`op.execute(...)` — so always check the diff before committing. + +### Applying migrations + +```bash +alembic upgrade head # upgrade to the latest revision +alembic downgrade -1 # roll back one revision +alembic current # show the DB's current revision +``` + +### Running migrations automatically + +To upgrade the database to `head` automatically when `bbconf` starts, set +`run_migrations: true` under the `database` section of the config file: + +```yaml +database: + host: localhost + port: 5432 + user: postgres + password: docker + database: bedbase + run_migrations: true +``` + +> **Note:** enable this only after the database has been stamped/upgraded to a +> known revision. Turning it on against an un-stamped existing database will fail +> on startup, because the baseline revision creates tables that already exist. diff --git a/alembic.ini b/alembic.ini new file mode 100644 index 00000000..625b44aa --- /dev/null +++ b/alembic.ini @@ -0,0 +1,126 @@ +# A generic, single database configuration. +# +# This file is used only for local development / CLI work +# (e.g. `alembic revision --autogenerate`, `alembic upgrade head`). +# At runtime, bbconf builds the Alembic config programmatically in +# `BaseEngine.run_db_migration()` and does NOT read this file. + +[alembic] +# path to migration scripts +# Use forward slashes (/) also on windows to provide an os agnostic path +script_location = ./bbconf/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>=3.9 or backports.zoneinfo library and tzdata library. +# Any required deps can installed by adding `alembic[tz]` to the pip requirements +# string value is passed to ZoneInfo() +# 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 = newline +# +# Use os.pathsep. Default configuration used for new projects. +version_path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# Local development connection string. Override with `-x` or edit as needed. +# Runtime migrations use the URL built from the bbconf config instead. +sqlalchemy.url = postgresql+psycopg://postgres:docker@localhost:5432/bedbase + + +[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 + +# lint with attempts to fix using "ruff" - use the exec runner, execute a binary +# hooks = ruff +# ruff.type = exec +# ruff.executable = %(here)s/.venv/bin/ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +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 diff --git a/bbconf/alembic/README b/bbconf/alembic/README new file mode 100644 index 00000000..2500aa1b --- /dev/null +++ b/bbconf/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. diff --git a/bbconf/alembic/__init__.py b/bbconf/alembic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bbconf/alembic/env.py b/bbconf/alembic/env.py new file mode 100644 index 00000000..e2b9fecb --- /dev/null +++ b/bbconf/alembic/env.py @@ -0,0 +1,77 @@ +from logging.config import fileConfig + +from alembic import context +from sqlalchemy import engine_from_config, pool + +# 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. +# Importing from bbconf.db_utils also registers the custom @compiles types +# (BIGSERIAL, JSON->JSONB, ARRAY) and the pg_trgm extension DDL event, so the +# metadata compiles to exactly the same DDL that create_all() produces. +from bbconf.db_utils import Base + +target_metadata = 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. + + """ + 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() diff --git a/bbconf/alembic/script.py.mako b/bbconf/alembic/script.py.mako new file mode 100644 index 00000000..51a73aa6 --- /dev/null +++ b/bbconf/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/bbconf/alembic/versions/8b0b706d0827_initial_migration.py b/bbconf/alembic/versions/8b0b706d0827_initial_migration.py new file mode 100644 index 00000000..f8fae368 --- /dev/null +++ b/bbconf/alembic/versions/8b0b706d0827_initial_migration.py @@ -0,0 +1,352 @@ +"""Initial migration + +Revision ID: 8b0b706d0827 +Revises: +Create Date: 2026-08-14 23:09:22.903899 + +""" +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '8b0b706d0827' +down_revision: Union[str, None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + # pg_trgm backs the trigram GIN indexes on `bedsets` (ix_bedsets_name_trgm / + # ix_bedsets_description_trgm). In the ORM this is created by a before_create + # DDL event on the bedsets table; autogenerate does not emit it, so it is + # added here by hand. Must run before those indexes are created. + op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm") + op.create_table('bed_snapshots', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('file_path', sa.String(), nullable=False, comment='S3 object key, relative to the bucket root'), + sa.Column('file_type', sa.String(), nullable=False, comment='metadata | bedsets | bedset_membership | manifest'), + sa.Column('creation_date', sa.TIMESTAMP(timezone=True), nullable=False, comment='Build date of the export'), + sa.Column('record_count', sa.Integer(), nullable=True, comment='Rows actually written to the file'), + sa.Column('file_size', sa.Integer(), nullable=True, comment='Size of the file in bytes'), + sa.Column('checksum', sa.String(), nullable=True, comment='SHA256 of the file'), + sa.Column('schema_version', sa.Integer(), nullable=True, comment='Export schema version'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_bed_snapshots_id'), 'bed_snapshots', ['id'], unique=False) + op.create_table('bedsets', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=False, comment='Name of the bedset'), + sa.Column('description', sa.String(), nullable=True, comment='Description of the bedset'), + sa.Column('summary', sa.String(), nullable=True, comment='Summary of the bedset'), + sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('last_update_date', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('md5sum', sa.String(), nullable=True, comment='MD5 sum of the bedset'), + sa.Column('bedset_means', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Mean values of the bedset'), + sa.Column('bedset_standard_deviation', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Median values of the bedset'), + sa.Column('bedfile_count', sa.Integer(), nullable=False, comment='Number of bedfiles in the bedset (denormalized count)'), + sa.Column('author', sa.String(), nullable=True, comment='Author of the bedset'), + sa.Column('source', sa.String(), nullable=True, comment='Source of the bedset'), + sa.Column('processed', sa.Boolean(), nullable=False, comment='Whether the bedset was processed'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('ix_bedsets_description_trgm', 'bedsets', ['description'], unique=False, postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) + op.create_index(op.f('ix_bedsets_id'), 'bedsets', ['id'], unique=False) + op.create_index('ix_bedsets_name_trgm', 'bedsets', ['name'], unique=False, postgresql_using='gin', postgresql_ops={'name': 'gin_trgm_ops'}) + op.create_index('ix_bedsets_unprocessed', 'bedsets', ['id'], unique=False, postgresql_where=sa.text('processed = false')) + op.create_table('geo_gse_status', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('gse', sa.String(), nullable=False, comment='GSE number'), + sa.Column('status', sa.String(), nullable=False, comment='Status of the GEO project'), + sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('number_of_files', sa.Integer(), nullable=False, comment='Number of files'), + sa.Column('number_of_success', sa.Integer(), nullable=False, comment='Number of success'), + sa.Column('number_of_skips', sa.Integer(), nullable=False, comment='Number of skips'), + sa.Column('number_of_fails', sa.Integer(), nullable=False, comment='Number of fails'), + sa.Column('error', sa.String(), nullable=True, comment='Error message'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('gse') + ) + op.create_index(op.f('ix_geo_gse_status_id'), 'geo_gse_status', ['id'], unique=False) + op.create_table('licenses', + sa.Column('id', sa.String(), nullable=False), + sa.Column('shorthand', sa.String(), nullable=True, comment='License shorthand'), + sa.Column('label', sa.String(), nullable=False, comment='License label'), + sa.Column('description', sa.String(), nullable=False, comment='License description'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_licenses_id'), 'licenses', ['id'], unique=False) + op.create_table('reference_genomes', + sa.Column('digest', sa.String(), nullable=False), + sa.Column('alias', sa.String(), nullable=False, comment='Name of the reference genome'), + sa.PrimaryKeyConstraint('digest') + ) + op.create_index(op.f('ix_reference_genomes_digest'), 'reference_genomes', ['digest'], unique=False) + op.create_table('usage_files', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('file_path', sa.String(), nullable=False, comment='Path to the file'), + sa.Column('count', sa.Integer(), nullable=False, comment='Number of downloads'), + sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), + sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_usage_files_id'), 'usage_files', ['id'], unique=False) + op.create_table('usage_search', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('query', sa.String(), nullable=False, comment='Search query'), + sa.Column('type', sa.String(), nullable=False, comment='Type of the search. Bed/Bedset'), + sa.Column('count', sa.Integer(), nullable=False, comment='Number of searches'), + sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), + sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_usage_search_id'), 'usage_search', ['id'], unique=False) + op.create_table('bed', + sa.Column('id', sa.String(), nullable=False), + sa.Column('name', sa.String(), nullable=True), + sa.Column('genome_alias', sa.String(), nullable=True), + sa.Column('genome_digest', sa.String(), nullable=True), + sa.Column('description', sa.String(), nullable=True), + sa.Column('bed_compliance', sa.String(), nullable=False), + sa.Column('data_format', sa.String(), nullable=False), + sa.Column('compliant_columns', sa.Integer(), nullable=False), + sa.Column('non_compliant_columns', sa.Integer(), nullable=False), + sa.Column('header', sa.String(), nullable=True, comment='Header of the bed file, it if was provided.'), + sa.Column('indexed', sa.Boolean(), nullable=False, comment='Whether sample was added to qdrant'), + sa.Column('file_indexed', sa.Boolean(), nullable=False, comment='Whether file was tokenized and added to the vector database'), + sa.Column('pephub', sa.Boolean(), nullable=False, comment='Whether sample was added to pephub'), + sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('last_update_date', sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column('is_universe', sa.Boolean(), nullable=True), + sa.Column('license_id', sa.String(), nullable=True), + sa.Column('processed', sa.Boolean(), nullable=False, comment='Whether the bed file was processed'), + sa.ForeignKeyConstraint(['license_id'], ['licenses.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index('genome_alias_index', 'bed', ['genome_alias'], unique=False, postgresql_with={'deduplicate_items': 'true'}) + op.create_index(op.f('ix_bed_id'), 'bed', ['id'], unique=False) + op.create_index(op.f('ix_bed_license_id'), 'bed', ['license_id'], unique=False) + op.create_index('ix_bed_not_file_indexed', 'bed', ['id'], unique=False, postgresql_where=sa.text('file_indexed = false')) + op.create_index('ix_bed_not_indexed', 'bed', ['id'], unique=False, postgresql_where=sa.text('indexed = false')) + op.create_index('ix_bed_submission_date', 'bed', [sa.literal_column('submission_date DESC'), sa.literal_column('id')], unique=False) + op.create_index('ix_bed_unprocessed', 'bed', ['id'], unique=False, postgresql_where=sa.text('processed = false')) + op.create_table('geo_gsm_status', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('gse_status_id', sa.Integer(), nullable=False), + sa.Column('gsm', sa.String(), nullable=False, comment='GSM number'), + sa.Column('sample_name', sa.String(), nullable=False), + sa.Column('status', sa.String(), nullable=False, comment='Status of the GEO sample'), + sa.Column('error', sa.String(), nullable=True, comment='Error message'), + sa.Column('source_submission_date', sa.TIMESTAMP(timezone=True), nullable=True, comment='Submission date of the source'), + sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column('bed_id', sa.String(), nullable=True, comment='Bed identifier'), + sa.Column('file_size', sa.BigInteger(), nullable=False, comment='Size of the file'), + sa.Column('genome', sa.String(), nullable=True, comment='Genome'), + sa.ForeignKeyConstraint(['gse_status_id'], ['geo_gse_status.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_geo_gsm_status_bed_id'), 'geo_gsm_status', ['bed_id'], unique=False) + op.create_index(op.f('ix_geo_gsm_status_gse_status_id'), 'geo_gsm_status', ['gse_status_id'], unique=False) + op.create_index(op.f('ix_geo_gsm_status_id'), 'geo_gsm_status', ['id'], unique=False) + op.create_table('usage_bedset_meta', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('bedset_id', sa.String(), nullable=True), + sa.Column('count', sa.Integer(), nullable=False, comment='Number of visits'), + sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), + sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), + sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_usage_bedset_meta_bedset_id'), 'usage_bedset_meta', ['bedset_id'], unique=False) + op.create_index(op.f('ix_usage_bedset_meta_id'), 'usage_bedset_meta', ['id'], unique=False) + op.create_table('bed_metadata', + sa.Column('species_name', sa.String(), nullable=False, comment='Organism name'), + sa.Column('species_id', sa.String(), nullable=True, comment='Organism taxon id'), + sa.Column('genotype', sa.String(), nullable=True, comment='Genotype of the sample'), + sa.Column('phenotype', sa.String(), nullable=True, comment='Phenotype of the sample'), + sa.Column('cell_type', sa.String(), nullable=True, comment='Specific kind of cell with distinct characteristics found in an organism. e.g. Neurons, Hepatocytes, Adipocytes'), + sa.Column('cell_line', sa.String(), nullable=True, comment='Population of cells derived from a single cell and cultured in the lab for extended use, e.g. HeLa, HepG2, k562'), + sa.Column('tissue', sa.String(), nullable=True, comment='Tissue type'), + sa.Column('library_source', sa.String(), nullable=True, comment='Library source (e.g. genomic, transcriptomic)'), + sa.Column('assay', sa.String(), nullable=True, comment='Experimental protocol (e.g. ChIP-seq)'), + sa.Column('antibody', sa.String(), nullable=True, comment='Antibody used in the assay'), + sa.Column('target', sa.String(), nullable=True, comment='Target of the assay (e.g. H3K4me3)'), + sa.Column('treatment', sa.String(), nullable=True, comment='Treatment of the sample (e.g. drug treatment)'), + sa.Column('original_file_name', sa.String(), nullable=True, comment='Original file name'), + sa.Column('global_sample_id', postgresql.ARRAY(sa.String()), nullable=True, comment='Global sample identifier. e.g. GSM000'), + sa.Column('global_experiment_id', postgresql.ARRAY(sa.String()), nullable=True, comment='Global experiment identifier. e.g. GSE000'), + sa.Column('id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_bed_metadata_id'), 'bed_metadata', ['id'], unique=False) + op.create_table('bed_stats', + sa.Column('id', sa.String(), nullable=False), + sa.Column('number_of_regions', sa.Float(), nullable=True), + sa.Column('gc_content', sa.Float(), nullable=True), + sa.Column('median_tss_dist', sa.Float(), nullable=True), + sa.Column('mean_region_width', sa.Float(), nullable=True), + sa.Column('exon_frequency', sa.Float(), nullable=True), + sa.Column('intron_frequency', sa.Float(), nullable=True), + sa.Column('promoterprox_frequency', sa.Float(), nullable=True), + sa.Column('intergenic_frequency', sa.Float(), nullable=True), + sa.Column('promotercore_frequency', sa.Float(), nullable=True), + sa.Column('fiveutr_frequency', sa.Float(), nullable=True), + sa.Column('threeutr_frequency', sa.Float(), nullable=True), + sa.Column('fiveutr_percentage', sa.Float(), nullable=True), + sa.Column('threeutr_percentage', sa.Float(), nullable=True), + sa.Column('promoterprox_percentage', sa.Float(), nullable=True), + sa.Column('exon_percentage', sa.Float(), nullable=True), + sa.Column('intron_percentage', sa.Float(), nullable=True), + sa.Column('intergenic_percentage', sa.Float(), nullable=True), + sa.Column('promotercore_percentage', sa.Float(), nullable=True), + sa.Column('tssdist', sa.Float(), nullable=True), + sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_bed_stats_id'), 'bed_stats', ['id'], unique=False) + op.create_index('ix_bed_stats_missing_regions', 'bed_stats', ['id'], unique=False, postgresql_where=sa.text('number_of_regions IS NULL')) + op.create_table('bedfile_bedset_relation', + sa.Column('bedset_id', sa.String(), nullable=False), + sa.Column('bedfile_id', sa.String(), nullable=False), + sa.ForeignKeyConstraint(['bedfile_id'], ['bed.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('bedset_id', 'bedfile_id') + ) + op.create_index(op.f('ix_bedfile_bedset_relation_bedfile_id'), 'bedfile_bedset_relation', ['bedfile_id'], unique=False) + op.create_table('files', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(), nullable=False, comment='Name of the file, e.g. bed, bigBed'), + sa.Column('file_digest', sa.String(), nullable=True, comment='Digest of the file. Mainly used for bed file.'), + sa.Column('title', sa.String(), nullable=True), + sa.Column('type', sa.String(), nullable=False, comment='Type of the object, e.g. file, plot, ...'), + sa.Column('path', sa.String(), nullable=False), + sa.Column('path_thumbnail', sa.String(), nullable=True, comment='Thumbnail path of the file'), + sa.Column('description', sa.String(), nullable=True), + sa.Column('size', sa.Integer(), nullable=True, comment='Size of the file'), + sa.Column('bedfile_id', sa.String(), nullable=True), + sa.Column('bedset_id', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['bedfile_id'], ['bed.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name', 'bedfile_id'), + sa.UniqueConstraint('name', 'bedset_id') + ) + op.create_index(op.f('ix_files_bedfile_id'), 'files', ['bedfile_id'], unique=False) + op.create_index(op.f('ix_files_bedset_id'), 'files', ['bedset_id'], unique=False) + op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False) + op.create_table('genome_ref_stats', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('bed_id', sa.String(), nullable=False), + sa.Column('provided_genome', sa.String(), nullable=False), + sa.Column('compared_genome', sa.String(), nullable=False, comment='Compared Genome'), + sa.Column('genome_digest', sa.String(), nullable=False), + sa.Column('xs', sa.Float(), nullable=True), + sa.Column('oobr', sa.Float(), nullable=True), + sa.Column('sequence_fit', sa.Float(), nullable=True), + sa.Column('assigned_points', sa.Integer(), nullable=False), + sa.Column('tier_ranking', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['genome_digest'], ['reference_genomes.digest'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('bed_id', 'compared_genome') + ) + op.create_index(op.f('ix_genome_ref_stats_bed_id'), 'genome_ref_stats', ['bed_id'], unique=False) + op.create_index(op.f('ix_genome_ref_stats_id'), 'genome_ref_stats', ['id'], unique=False) + op.create_table('universes', + sa.Column('id', sa.String(), nullable=False), + sa.Column('method', sa.String(), nullable=True, comment='Method used to create the universe'), + sa.Column('bedset_id', sa.String(), nullable=True), + sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_universes_bedset_id'), 'universes', ['bedset_id'], unique=False) + op.create_index(op.f('ix_universes_id'), 'universes', ['id'], unique=False) + op.create_table('usage_bed_meta', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('bed_id', sa.String(), nullable=True), + sa.Column('count', sa.Integer(), nullable=False, comment='Number of visits'), + sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), + sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), + sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_index(op.f('ix_usage_bed_meta_bed_id'), 'usage_bed_meta', ['bed_id'], unique=False) + op.create_index(op.f('ix_usage_bed_meta_id'), 'usage_bed_meta', ['id'], unique=False) + op.create_table('tokenized_bed', + sa.Column('bed_id', sa.String(), nullable=False), + sa.Column('universe_id', sa.String(), nullable=False), + sa.Column('path', sa.String(), nullable=False, comment='Path to the tokenized bed file'), + sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['universe_id'], ['universes.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('bed_id', 'universe_id') + ) + op.create_index(op.f('ix_tokenized_bed_bed_id'), 'tokenized_bed', ['bed_id'], unique=False) + op.create_index(op.f('ix_tokenized_bed_universe_id'), 'tokenized_bed', ['universe_id'], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index(op.f('ix_tokenized_bed_universe_id'), table_name='tokenized_bed') + op.drop_index(op.f('ix_tokenized_bed_bed_id'), table_name='tokenized_bed') + op.drop_table('tokenized_bed') + op.drop_index(op.f('ix_usage_bed_meta_id'), table_name='usage_bed_meta') + op.drop_index(op.f('ix_usage_bed_meta_bed_id'), table_name='usage_bed_meta') + op.drop_table('usage_bed_meta') + op.drop_index(op.f('ix_universes_id'), table_name='universes') + op.drop_index(op.f('ix_universes_bedset_id'), table_name='universes') + op.drop_table('universes') + op.drop_index(op.f('ix_genome_ref_stats_id'), table_name='genome_ref_stats') + op.drop_index(op.f('ix_genome_ref_stats_bed_id'), table_name='genome_ref_stats') + op.drop_table('genome_ref_stats') + op.drop_index(op.f('ix_files_id'), table_name='files') + op.drop_index(op.f('ix_files_bedset_id'), table_name='files') + op.drop_index(op.f('ix_files_bedfile_id'), table_name='files') + op.drop_table('files') + op.drop_index(op.f('ix_bedfile_bedset_relation_bedfile_id'), table_name='bedfile_bedset_relation') + op.drop_table('bedfile_bedset_relation') + op.drop_index('ix_bed_stats_missing_regions', table_name='bed_stats', postgresql_where=sa.text('number_of_regions IS NULL')) + op.drop_index(op.f('ix_bed_stats_id'), table_name='bed_stats') + op.drop_table('bed_stats') + op.drop_index(op.f('ix_bed_metadata_id'), table_name='bed_metadata') + op.drop_table('bed_metadata') + op.drop_index(op.f('ix_usage_bedset_meta_id'), table_name='usage_bedset_meta') + op.drop_index(op.f('ix_usage_bedset_meta_bedset_id'), table_name='usage_bedset_meta') + op.drop_table('usage_bedset_meta') + op.drop_index(op.f('ix_geo_gsm_status_id'), table_name='geo_gsm_status') + op.drop_index(op.f('ix_geo_gsm_status_gse_status_id'), table_name='geo_gsm_status') + op.drop_index(op.f('ix_geo_gsm_status_bed_id'), table_name='geo_gsm_status') + op.drop_table('geo_gsm_status') + op.drop_index('ix_bed_unprocessed', table_name='bed', postgresql_where=sa.text('processed = false')) + op.drop_index('ix_bed_submission_date', table_name='bed') + op.drop_index('ix_bed_not_indexed', table_name='bed', postgresql_where=sa.text('indexed = false')) + op.drop_index('ix_bed_not_file_indexed', table_name='bed', postgresql_where=sa.text('file_indexed = false')) + op.drop_index(op.f('ix_bed_license_id'), table_name='bed') + op.drop_index(op.f('ix_bed_id'), table_name='bed') + op.drop_index('genome_alias_index', table_name='bed', postgresql_with={'deduplicate_items': 'true'}) + op.drop_table('bed') + op.drop_index(op.f('ix_usage_search_id'), table_name='usage_search') + op.drop_table('usage_search') + op.drop_index(op.f('ix_usage_files_id'), table_name='usage_files') + op.drop_table('usage_files') + op.drop_index(op.f('ix_reference_genomes_digest'), table_name='reference_genomes') + op.drop_table('reference_genomes') + op.drop_index(op.f('ix_licenses_id'), table_name='licenses') + op.drop_table('licenses') + op.drop_index(op.f('ix_geo_gse_status_id'), table_name='geo_gse_status') + op.drop_table('geo_gse_status') + op.drop_index('ix_bedsets_unprocessed', table_name='bedsets', postgresql_where=sa.text('processed = false')) + op.drop_index('ix_bedsets_name_trgm', table_name='bedsets', postgresql_using='gin', postgresql_ops={'name': 'gin_trgm_ops'}) + op.drop_index(op.f('ix_bedsets_id'), table_name='bedsets') + op.drop_index('ix_bedsets_description_trgm', table_name='bedsets', postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) + op.drop_table('bedsets') + op.drop_index(op.f('ix_bed_snapshots_id'), table_name='bed_snapshots') + op.drop_table('bed_snapshots') + # ### end Alembic commands ### diff --git a/bbconf/alembic/versions/__init__.py b/bbconf/alembic/versions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/bbconf/bbagent.py b/bbconf/bbagent.py index 41acaf4e..1cbcfcb8 100644 --- a/bbconf/bbagent.py +++ b/bbconf/bbagent.py @@ -50,6 +50,7 @@ def __init__( self, config: Path | str, init_ml: bool = True, + migrate_db: bool = False, ): """ Initialize connection to the pep_db database. You can use the basic connection parameters diff --git a/bbconf/config_parser/bedbaseconfig.py b/bbconf/config_parser/bedbaseconfig.py index ef9db9e6..9c5c5562 100644 --- a/bbconf/config_parser/bedbaseconfig.py +++ b/bbconf/config_parser/bedbaseconfig.py @@ -227,6 +227,7 @@ def _init_db_engine(self) -> BaseEngine: user=self._config.database.user, password=self._config.database.password, drivername=f"{self._config.database.dialect}+{self._config.database.driver}", + run_migrations=self._config.database.run_migrations, ) def _init_qdrant_client(self) -> QdrantClient: diff --git a/bbconf/config_parser/models.py b/bbconf/config_parser/models.py index 8f1d37ec..002c9536 100644 --- a/bbconf/config_parser/models.py +++ b/bbconf/config_parser/models.py @@ -35,6 +35,7 @@ class ConfigDB(BaseModel): database: str = DEFAULT_DB_NAME dialect: str = DEFAULT_DB_DIALECT driver: str | None = DEFAULT_DB_DRIVER + run_migrations: bool = False model_config = ConfigDict(extra="forbid") diff --git a/bbconf/db_utils.py b/bbconf/db_utils.py index 816115a8..7e3d21e1 100644 --- a/bbconf/db_utils.py +++ b/bbconf/db_utils.py @@ -1,8 +1,11 @@ import datetime import logging +import os from typing import Optional import pandas as pd +from alembic import command +from alembic.config import Config from sqlalchemy import ( DDL, TIMESTAMP, @@ -723,6 +726,7 @@ def __init__( drivername: str = POSTGRES_DIALECT, dsn: str | None = None, echo: bool = False, + run_migrations: bool = False, ): """ Initialize connection to the bedbase database. You can use the basic connection parameters @@ -737,6 +741,9 @@ def __init__( drivername: Driver used in connection. dsn: Libpq connection string using the dsn parameter (e.g. 'postgresql://user_name:password@host_name:port/db_name'). + run_migrations: Upgrade the database to the latest Alembic revision + (``head``) before connecting. Safe on an already-migrated or + pre-existing database (the initial revision is idempotent). """ if not dsn: dsn = URL.create( @@ -748,6 +755,13 @@ def __init__( drivername=drivername, ) + if run_migrations: + if isinstance(dsn, str): + migration_url = dsn + else: + migration_url = dsn.render_as_string(hide_password=False) + self.run_db_migration(migration_url) + self._engine = create_engine(dsn, echo=echo) self.create_schema(self._engine) self.check_db_connection() @@ -789,6 +803,25 @@ def delete_schema(self, engine=None) -> None: Base.metadata.drop_all(engine) return None + def run_db_migration(self, database_url: str) -> None: + """ + Upgrade the database to the latest Alembic revision (``head``). + + The Alembic config is built programmatically so the package does not + depend on the repo-root ``alembic.ini`` at runtime. + + Args: + database_url: SQLAlchemy connection URL (with password) to migrate. + """ + script_location = os.path.join(os.path.dirname(__file__), "alembic") + + alembic_cfg = Config() + alembic_cfg.set_main_option("script_location", script_location) + alembic_cfg.set_main_option("sqlalchemy.url", database_url) + + _LOGGER.info("Running database migrations to the latest revision...") + command.upgrade(alembic_cfg, "head") + def session_execute(self, statement: Select) -> Result: """ Execute statement using sqlalchemy statement. diff --git a/pyproject.toml b/pyproject.toml index ac1e4685..0ff9c045 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ dependencies = [ "qdrant_client >= 1.16.1", "setuptools < 70.0.0", "cachetools >= 4.2.4", + "alembic >= 1.19.1", ] [project.urls] @@ -75,3 +76,6 @@ exclude = ["manual_testing.py"] [tool.ruff.lint.isort] known-first-party = ["bbconf"] + +[tool.ruff.lint.per-file-ignores] +"bbconf/alembic/env.py" = ["E402"] From 340d56afe1c4c912a1ff166865fc0aa96a9c45c1 Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Fri, 14 Aug 2026 23:33:33 -0400 Subject: [PATCH 2/6] deleted unused arguemnt --- bbconf/bbagent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bbconf/bbagent.py b/bbconf/bbagent.py index 1cbcfcb8..41acaf4e 100644 --- a/bbconf/bbagent.py +++ b/bbconf/bbagent.py @@ -50,7 +50,6 @@ def __init__( self, config: Path | str, init_ml: bool = True, - migrate_db: bool = False, ): """ Initialize connection to the pep_db database. You can use the basic connection parameters From 37d87f52ebab0f96c5c4c64165656c81e31f4bd9 Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Sat, 15 Aug 2026 00:25:49 -0400 Subject: [PATCH 3/6] Small tweaks --- .github/pull_request_template.md | 2 +- README.md | 3 +++ alembic.ini | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 163df722..fdc90a74 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,6 +3,6 @@ ## TODO: -- [ ] ❗ If this PR includes a new database schema migration, following steps are completed: [Database Version Migration](https://pep.databio.org/pephub/developer/pepdbagent/database_version_migration/) +- [ ] ❗ If this PR includes a new database schema migration, following steps are completed: (README)[README.md] - [ ] Version of pepdbagent updated in `__version__.py` file - [ ] Changelog updated \ No newline at end of file diff --git a/README.md b/README.md index 6caff043..6211ac19 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,9 @@ root) is used for local CLI work. The first (baseline) revision is builds, including the `pg_trgm` extension and the trigram / partial / expression indexes. +To update schema for desirable database, use different database url in `alembic.ini`, +otherwise run test database + ### Creating a new revision After changing the models in `bbconf/db_utils.py`: diff --git a/alembic.ini b/alembic.ini index 625b44aa..5f4c58b6 100644 --- a/alembic.ini +++ b/alembic.ini @@ -70,6 +70,8 @@ version_path_separator = os # Local development connection string. Override with `-x` or edit as needed. # Runtime migrations use the URL built from the bbconf config instead. + +### !!!! Change this code to desirable database!!!! sqlalchemy.url = postgresql+psycopg://postgres:docker@localhost:5432/bedbase From c3fd35f54b211c1213d01cec82ab7b6ca2138a29 Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Sat, 15 Aug 2026 23:31:53 -0400 Subject: [PATCH 4/6] Linting and test fix --- .../8b0b706d0827_initial_migration.py | 1028 ++++++++++++----- bbconf/bbagent.py | 38 +- bbconf/models/base_models.py | 2 +- bbconf/modules/snapshots.py | 8 +- 4 files changed, 742 insertions(+), 334 deletions(-) diff --git a/bbconf/alembic/versions/8b0b706d0827_initial_migration.py b/bbconf/alembic/versions/8b0b706d0827_initial_migration.py index f8fae368..24f76169 100644 --- a/bbconf/alembic/versions/8b0b706d0827_initial_migration.py +++ b/bbconf/alembic/versions/8b0b706d0827_initial_migration.py @@ -1,10 +1,11 @@ """Initial migration Revision ID: 8b0b706d0827 -Revises: +Revises: Create Date: 2026-08-14 23:09:22.903899 """ + from typing import Sequence, Union import sqlalchemy as sa @@ -12,7 +13,7 @@ from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. -revision: str = '8b0b706d0827' +revision: str = "8b0b706d0827" down_revision: Union[str, None] = None branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None @@ -26,327 +27,716 @@ def upgrade() -> None: # DDL event on the bedsets table; autogenerate does not emit it, so it is # added here by hand. Must run before those indexes are created. op.execute("CREATE EXTENSION IF NOT EXISTS pg_trgm") - op.create_table('bed_snapshots', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('file_path', sa.String(), nullable=False, comment='S3 object key, relative to the bucket root'), - sa.Column('file_type', sa.String(), nullable=False, comment='metadata | bedsets | bedset_membership | manifest'), - sa.Column('creation_date', sa.TIMESTAMP(timezone=True), nullable=False, comment='Build date of the export'), - sa.Column('record_count', sa.Integer(), nullable=True, comment='Rows actually written to the file'), - sa.Column('file_size', sa.Integer(), nullable=True, comment='Size of the file in bytes'), - sa.Column('checksum', sa.String(), nullable=True, comment='SHA256 of the file'), - sa.Column('schema_version', sa.Integer(), nullable=True, comment='Export schema version'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_bed_snapshots_id'), 'bed_snapshots', ['id'], unique=False) - op.create_table('bedsets', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=False, comment='Name of the bedset'), - sa.Column('description', sa.String(), nullable=True, comment='Description of the bedset'), - sa.Column('summary', sa.String(), nullable=True, comment='Summary of the bedset'), - sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('last_update_date', sa.TIMESTAMP(timezone=True), nullable=True), - sa.Column('md5sum', sa.String(), nullable=True, comment='MD5 sum of the bedset'), - sa.Column('bedset_means', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Mean values of the bedset'), - sa.Column('bedset_standard_deviation', postgresql.JSON(astext_type=sa.Text()), nullable=True, comment='Median values of the bedset'), - sa.Column('bedfile_count', sa.Integer(), nullable=False, comment='Number of bedfiles in the bedset (denormalized count)'), - sa.Column('author', sa.String(), nullable=True, comment='Author of the bedset'), - sa.Column('source', sa.String(), nullable=True, comment='Source of the bedset'), - sa.Column('processed', sa.Boolean(), nullable=False, comment='Whether the bedset was processed'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index('ix_bedsets_description_trgm', 'bedsets', ['description'], unique=False, postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) - op.create_index(op.f('ix_bedsets_id'), 'bedsets', ['id'], unique=False) - op.create_index('ix_bedsets_name_trgm', 'bedsets', ['name'], unique=False, postgresql_using='gin', postgresql_ops={'name': 'gin_trgm_ops'}) - op.create_index('ix_bedsets_unprocessed', 'bedsets', ['id'], unique=False, postgresql_where=sa.text('processed = false')) - op.create_table('geo_gse_status', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('gse', sa.String(), nullable=False, comment='GSE number'), - sa.Column('status', sa.String(), nullable=False, comment='Status of the GEO project'), - sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('number_of_files', sa.Integer(), nullable=False, comment='Number of files'), - sa.Column('number_of_success', sa.Integer(), nullable=False, comment='Number of success'), - sa.Column('number_of_skips', sa.Integer(), nullable=False, comment='Number of skips'), - sa.Column('number_of_fails', sa.Integer(), nullable=False, comment='Number of fails'), - sa.Column('error', sa.String(), nullable=True, comment='Error message'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('gse') - ) - op.create_index(op.f('ix_geo_gse_status_id'), 'geo_gse_status', ['id'], unique=False) - op.create_table('licenses', - sa.Column('id', sa.String(), nullable=False), - sa.Column('shorthand', sa.String(), nullable=True, comment='License shorthand'), - sa.Column('label', sa.String(), nullable=False, comment='License label'), - sa.Column('description', sa.String(), nullable=False, comment='License description'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_licenses_id'), 'licenses', ['id'], unique=False) - op.create_table('reference_genomes', - sa.Column('digest', sa.String(), nullable=False), - sa.Column('alias', sa.String(), nullable=False, comment='Name of the reference genome'), - sa.PrimaryKeyConstraint('digest') - ) - op.create_index(op.f('ix_reference_genomes_digest'), 'reference_genomes', ['digest'], unique=False) - op.create_table('usage_files', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('file_path', sa.String(), nullable=False, comment='Path to the file'), - sa.Column('count', sa.Integer(), nullable=False, comment='Number of downloads'), - sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), - sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_usage_files_id'), 'usage_files', ['id'], unique=False) - op.create_table('usage_search', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('query', sa.String(), nullable=False, comment='Search query'), - sa.Column('type', sa.String(), nullable=False, comment='Type of the search. Bed/Bedset'), - sa.Column('count', sa.Integer(), nullable=False, comment='Number of searches'), - sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), - sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_usage_search_id'), 'usage_search', ['id'], unique=False) - op.create_table('bed', - sa.Column('id', sa.String(), nullable=False), - sa.Column('name', sa.String(), nullable=True), - sa.Column('genome_alias', sa.String(), nullable=True), - sa.Column('genome_digest', sa.String(), nullable=True), - sa.Column('description', sa.String(), nullable=True), - sa.Column('bed_compliance', sa.String(), nullable=False), - sa.Column('data_format', sa.String(), nullable=False), - sa.Column('compliant_columns', sa.Integer(), nullable=False), - sa.Column('non_compliant_columns', sa.Integer(), nullable=False), - sa.Column('header', sa.String(), nullable=True, comment='Header of the bed file, it if was provided.'), - sa.Column('indexed', sa.Boolean(), nullable=False, comment='Whether sample was added to qdrant'), - sa.Column('file_indexed', sa.Boolean(), nullable=False, comment='Whether file was tokenized and added to the vector database'), - sa.Column('pephub', sa.Boolean(), nullable=False, comment='Whether sample was added to pephub'), - sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('last_update_date', sa.TIMESTAMP(timezone=True), nullable=True), - sa.Column('is_universe', sa.Boolean(), nullable=True), - sa.Column('license_id', sa.String(), nullable=True), - sa.Column('processed', sa.Boolean(), nullable=False, comment='Whether the bed file was processed'), - sa.ForeignKeyConstraint(['license_id'], ['licenses.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index('genome_alias_index', 'bed', ['genome_alias'], unique=False, postgresql_with={'deduplicate_items': 'true'}) - op.create_index(op.f('ix_bed_id'), 'bed', ['id'], unique=False) - op.create_index(op.f('ix_bed_license_id'), 'bed', ['license_id'], unique=False) - op.create_index('ix_bed_not_file_indexed', 'bed', ['id'], unique=False, postgresql_where=sa.text('file_indexed = false')) - op.create_index('ix_bed_not_indexed', 'bed', ['id'], unique=False, postgresql_where=sa.text('indexed = false')) - op.create_index('ix_bed_submission_date', 'bed', [sa.literal_column('submission_date DESC'), sa.literal_column('id')], unique=False) - op.create_index('ix_bed_unprocessed', 'bed', ['id'], unique=False, postgresql_where=sa.text('processed = false')) - op.create_table('geo_gsm_status', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('gse_status_id', sa.Integer(), nullable=False), - sa.Column('gsm', sa.String(), nullable=False, comment='GSM number'), - sa.Column('sample_name', sa.String(), nullable=False), - sa.Column('status', sa.String(), nullable=False, comment='Status of the GEO sample'), - sa.Column('error', sa.String(), nullable=True, comment='Error message'), - sa.Column('source_submission_date', sa.TIMESTAMP(timezone=True), nullable=True, comment='Submission date of the source'), - sa.Column('submission_date', sa.TIMESTAMP(timezone=True), nullable=False), - sa.Column('bed_id', sa.String(), nullable=True, comment='Bed identifier'), - sa.Column('file_size', sa.BigInteger(), nullable=False, comment='Size of the file'), - sa.Column('genome', sa.String(), nullable=True, comment='Genome'), - sa.ForeignKeyConstraint(['gse_status_id'], ['geo_gse_status.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_geo_gsm_status_bed_id'), 'geo_gsm_status', ['bed_id'], unique=False) - op.create_index(op.f('ix_geo_gsm_status_gse_status_id'), 'geo_gsm_status', ['gse_status_id'], unique=False) - op.create_index(op.f('ix_geo_gsm_status_id'), 'geo_gsm_status', ['id'], unique=False) - op.create_table('usage_bedset_meta', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('bedset_id', sa.String(), nullable=True), - sa.Column('count', sa.Integer(), nullable=False, comment='Number of visits'), - sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), - sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), - sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_usage_bedset_meta_bedset_id'), 'usage_bedset_meta', ['bedset_id'], unique=False) - op.create_index(op.f('ix_usage_bedset_meta_id'), 'usage_bedset_meta', ['id'], unique=False) - op.create_table('bed_metadata', - sa.Column('species_name', sa.String(), nullable=False, comment='Organism name'), - sa.Column('species_id', sa.String(), nullable=True, comment='Organism taxon id'), - sa.Column('genotype', sa.String(), nullable=True, comment='Genotype of the sample'), - sa.Column('phenotype', sa.String(), nullable=True, comment='Phenotype of the sample'), - sa.Column('cell_type', sa.String(), nullable=True, comment='Specific kind of cell with distinct characteristics found in an organism. e.g. Neurons, Hepatocytes, Adipocytes'), - sa.Column('cell_line', sa.String(), nullable=True, comment='Population of cells derived from a single cell and cultured in the lab for extended use, e.g. HeLa, HepG2, k562'), - sa.Column('tissue', sa.String(), nullable=True, comment='Tissue type'), - sa.Column('library_source', sa.String(), nullable=True, comment='Library source (e.g. genomic, transcriptomic)'), - sa.Column('assay', sa.String(), nullable=True, comment='Experimental protocol (e.g. ChIP-seq)'), - sa.Column('antibody', sa.String(), nullable=True, comment='Antibody used in the assay'), - sa.Column('target', sa.String(), nullable=True, comment='Target of the assay (e.g. H3K4me3)'), - sa.Column('treatment', sa.String(), nullable=True, comment='Treatment of the sample (e.g. drug treatment)'), - sa.Column('original_file_name', sa.String(), nullable=True, comment='Original file name'), - sa.Column('global_sample_id', postgresql.ARRAY(sa.String()), nullable=True, comment='Global sample identifier. e.g. GSM000'), - sa.Column('global_experiment_id', postgresql.ARRAY(sa.String()), nullable=True, comment='Global experiment identifier. e.g. GSE000'), - sa.Column('id', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_bed_metadata_id'), 'bed_metadata', ['id'], unique=False) - op.create_table('bed_stats', - sa.Column('id', sa.String(), nullable=False), - sa.Column('number_of_regions', sa.Float(), nullable=True), - sa.Column('gc_content', sa.Float(), nullable=True), - sa.Column('median_tss_dist', sa.Float(), nullable=True), - sa.Column('mean_region_width', sa.Float(), nullable=True), - sa.Column('exon_frequency', sa.Float(), nullable=True), - sa.Column('intron_frequency', sa.Float(), nullable=True), - sa.Column('promoterprox_frequency', sa.Float(), nullable=True), - sa.Column('intergenic_frequency', sa.Float(), nullable=True), - sa.Column('promotercore_frequency', sa.Float(), nullable=True), - sa.Column('fiveutr_frequency', sa.Float(), nullable=True), - sa.Column('threeutr_frequency', sa.Float(), nullable=True), - sa.Column('fiveutr_percentage', sa.Float(), nullable=True), - sa.Column('threeutr_percentage', sa.Float(), nullable=True), - sa.Column('promoterprox_percentage', sa.Float(), nullable=True), - sa.Column('exon_percentage', sa.Float(), nullable=True), - sa.Column('intron_percentage', sa.Float(), nullable=True), - sa.Column('intergenic_percentage', sa.Float(), nullable=True), - sa.Column('promotercore_percentage', sa.Float(), nullable=True), - sa.Column('tssdist', sa.Float(), nullable=True), - sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_bed_stats_id'), 'bed_stats', ['id'], unique=False) - op.create_index('ix_bed_stats_missing_regions', 'bed_stats', ['id'], unique=False, postgresql_where=sa.text('number_of_regions IS NULL')) - op.create_table('bedfile_bedset_relation', - sa.Column('bedset_id', sa.String(), nullable=False), - sa.Column('bedfile_id', sa.String(), nullable=False), - sa.ForeignKeyConstraint(['bedfile_id'], ['bed.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('bedset_id', 'bedfile_id') - ) - op.create_index(op.f('ix_bedfile_bedset_relation_bedfile_id'), 'bedfile_bedset_relation', ['bedfile_id'], unique=False) - op.create_table('files', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(), nullable=False, comment='Name of the file, e.g. bed, bigBed'), - sa.Column('file_digest', sa.String(), nullable=True, comment='Digest of the file. Mainly used for bed file.'), - sa.Column('title', sa.String(), nullable=True), - sa.Column('type', sa.String(), nullable=False, comment='Type of the object, e.g. file, plot, ...'), - sa.Column('path', sa.String(), nullable=False), - sa.Column('path_thumbnail', sa.String(), nullable=True, comment='Thumbnail path of the file'), - sa.Column('description', sa.String(), nullable=True), - sa.Column('size', sa.Integer(), nullable=True, comment='Size of the file'), - sa.Column('bedfile_id', sa.String(), nullable=True), - sa.Column('bedset_id', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['bedfile_id'], ['bed.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name', 'bedfile_id'), - sa.UniqueConstraint('name', 'bedset_id') - ) - op.create_index(op.f('ix_files_bedfile_id'), 'files', ['bedfile_id'], unique=False) - op.create_index(op.f('ix_files_bedset_id'), 'files', ['bedset_id'], unique=False) - op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False) - op.create_table('genome_ref_stats', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('bed_id', sa.String(), nullable=False), - sa.Column('provided_genome', sa.String(), nullable=False), - sa.Column('compared_genome', sa.String(), nullable=False, comment='Compared Genome'), - sa.Column('genome_digest', sa.String(), nullable=False), - sa.Column('xs', sa.Float(), nullable=True), - sa.Column('oobr', sa.Float(), nullable=True), - sa.Column('sequence_fit', sa.Float(), nullable=True), - sa.Column('assigned_points', sa.Integer(), nullable=False), - sa.Column('tier_ranking', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['genome_digest'], ['reference_genomes.digest'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('bed_id', 'compared_genome') - ) - op.create_index(op.f('ix_genome_ref_stats_bed_id'), 'genome_ref_stats', ['bed_id'], unique=False) - op.create_index(op.f('ix_genome_ref_stats_id'), 'genome_ref_stats', ['id'], unique=False) - op.create_table('universes', - sa.Column('id', sa.String(), nullable=False), - sa.Column('method', sa.String(), nullable=True, comment='Method used to create the universe'), - sa.Column('bedset_id', sa.String(), nullable=True), - sa.ForeignKeyConstraint(['bedset_id'], ['bedsets.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['id'], ['bed.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_universes_bedset_id'), 'universes', ['bedset_id'], unique=False) - op.create_index(op.f('ix_universes_id'), 'universes', ['id'], unique=False) - op.create_table('usage_bed_meta', - sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), - sa.Column('bed_id', sa.String(), nullable=True), - sa.Column('count', sa.Integer(), nullable=False, comment='Number of visits'), - sa.Column('date_from', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date from'), - sa.Column('date_to', sa.TIMESTAMP(timezone=True), nullable=False, comment='Date to'), - sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_index(op.f('ix_usage_bed_meta_bed_id'), 'usage_bed_meta', ['bed_id'], unique=False) - op.create_index(op.f('ix_usage_bed_meta_id'), 'usage_bed_meta', ['id'], unique=False) - op.create_table('tokenized_bed', - sa.Column('bed_id', sa.String(), nullable=False), - sa.Column('universe_id', sa.String(), nullable=False), - sa.Column('path', sa.String(), nullable=False, comment='Path to the tokenized bed file'), - sa.ForeignKeyConstraint(['bed_id'], ['bed.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['universe_id'], ['universes.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('bed_id', 'universe_id') - ) - op.create_index(op.f('ix_tokenized_bed_bed_id'), 'tokenized_bed', ['bed_id'], unique=False) - op.create_index(op.f('ix_tokenized_bed_universe_id'), 'tokenized_bed', ['universe_id'], unique=False) + op.create_table( + "bed_snapshots", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column( + "file_path", + sa.String(), + nullable=False, + comment="S3 object key, relative to the bucket root", + ), + sa.Column( + "file_type", + sa.String(), + nullable=False, + comment="metadata | bedsets | bedset_membership | manifest", + ), + sa.Column( + "creation_date", + sa.TIMESTAMP(timezone=True), + nullable=False, + comment="Build date of the export", + ), + sa.Column( + "record_count", + sa.Integer(), + nullable=True, + comment="Rows actually written to the file", + ), + sa.Column( + "file_size", + sa.Integer(), + nullable=True, + comment="Size of the file in bytes", + ), + sa.Column("checksum", sa.String(), nullable=True, comment="SHA256 of the file"), + sa.Column( + "schema_version", + sa.Integer(), + nullable=True, + comment="Export schema version", + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_bed_snapshots_id"), "bed_snapshots", ["id"], unique=False) + op.create_table( + "bedsets", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=False, comment="Name of the bedset"), + sa.Column( + "description", + sa.String(), + nullable=True, + comment="Description of the bedset", + ), + sa.Column( + "summary", sa.String(), nullable=True, comment="Summary of the bedset" + ), + sa.Column("submission_date", sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column("last_update_date", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column( + "md5sum", sa.String(), nullable=True, comment="MD5 sum of the bedset" + ), + sa.Column( + "bedset_means", + postgresql.JSON(astext_type=sa.Text()), + nullable=True, + comment="Mean values of the bedset", + ), + sa.Column( + "bedset_standard_deviation", + postgresql.JSON(astext_type=sa.Text()), + nullable=True, + comment="Median values of the bedset", + ), + sa.Column( + "bedfile_count", + sa.Integer(), + nullable=False, + comment="Number of bedfiles in the bedset (denormalized count)", + ), + sa.Column("author", sa.String(), nullable=True, comment="Author of the bedset"), + sa.Column("source", sa.String(), nullable=True, comment="Source of the bedset"), + sa.Column( + "processed", + sa.Boolean(), + nullable=False, + comment="Whether the bedset was processed", + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "ix_bedsets_description_trgm", + "bedsets", + ["description"], + unique=False, + postgresql_using="gin", + postgresql_ops={"description": "gin_trgm_ops"}, + ) + op.create_index(op.f("ix_bedsets_id"), "bedsets", ["id"], unique=False) + op.create_index( + "ix_bedsets_name_trgm", + "bedsets", + ["name"], + unique=False, + postgresql_using="gin", + postgresql_ops={"name": "gin_trgm_ops"}, + ) + op.create_index( + "ix_bedsets_unprocessed", + "bedsets", + ["id"], + unique=False, + postgresql_where=sa.text("processed = false"), + ) + op.create_table( + "geo_gse_status", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("gse", sa.String(), nullable=False, comment="GSE number"), + sa.Column( + "status", sa.String(), nullable=False, comment="Status of the GEO project" + ), + sa.Column("submission_date", sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column( + "number_of_files", sa.Integer(), nullable=False, comment="Number of files" + ), + sa.Column( + "number_of_success", + sa.Integer(), + nullable=False, + comment="Number of success", + ), + sa.Column( + "number_of_skips", sa.Integer(), nullable=False, comment="Number of skips" + ), + sa.Column( + "number_of_fails", sa.Integer(), nullable=False, comment="Number of fails" + ), + sa.Column("error", sa.String(), nullable=True, comment="Error message"), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("gse"), + ) + op.create_index( + op.f("ix_geo_gse_status_id"), "geo_gse_status", ["id"], unique=False + ) + op.create_table( + "licenses", + sa.Column("id", sa.String(), nullable=False), + sa.Column("shorthand", sa.String(), nullable=True, comment="License shorthand"), + sa.Column("label", sa.String(), nullable=False, comment="License label"), + sa.Column( + "description", sa.String(), nullable=False, comment="License description" + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_licenses_id"), "licenses", ["id"], unique=False) + op.create_table( + "reference_genomes", + sa.Column("digest", sa.String(), nullable=False), + sa.Column( + "alias", sa.String(), nullable=False, comment="Name of the reference genome" + ), + sa.PrimaryKeyConstraint("digest"), + ) + op.create_index( + op.f("ix_reference_genomes_digest"), + "reference_genomes", + ["digest"], + unique=False, + ) + op.create_table( + "usage_files", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("file_path", sa.String(), nullable=False, comment="Path to the file"), + sa.Column("count", sa.Integer(), nullable=False, comment="Number of downloads"), + sa.Column( + "date_from", + sa.TIMESTAMP(timezone=True), + nullable=False, + comment="Date from", + ), + sa.Column( + "date_to", sa.TIMESTAMP(timezone=True), nullable=False, comment="Date to" + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_usage_files_id"), "usage_files", ["id"], unique=False) + op.create_table( + "usage_search", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("query", sa.String(), nullable=False, comment="Search query"), + sa.Column( + "type", + sa.String(), + nullable=False, + comment="Type of the search. Bed/Bedset", + ), + sa.Column("count", sa.Integer(), nullable=False, comment="Number of searches"), + sa.Column( + "date_from", + sa.TIMESTAMP(timezone=True), + nullable=False, + comment="Date from", + ), + sa.Column( + "date_to", sa.TIMESTAMP(timezone=True), nullable=False, comment="Date to" + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_usage_search_id"), "usage_search", ["id"], unique=False) + op.create_table( + "bed", + sa.Column("id", sa.String(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("genome_alias", sa.String(), nullable=True), + sa.Column("genome_digest", sa.String(), nullable=True), + sa.Column("description", sa.String(), nullable=True), + sa.Column("bed_compliance", sa.String(), nullable=False), + sa.Column("data_format", sa.String(), nullable=False), + sa.Column("compliant_columns", sa.Integer(), nullable=False), + sa.Column("non_compliant_columns", sa.Integer(), nullable=False), + sa.Column( + "header", + sa.String(), + nullable=True, + comment="Header of the bed file, it if was provided.", + ), + sa.Column( + "indexed", + sa.Boolean(), + nullable=False, + comment="Whether sample was added to qdrant", + ), + sa.Column( + "file_indexed", + sa.Boolean(), + nullable=False, + comment="Whether file was tokenized and added to the vector database", + ), + sa.Column( + "pephub", + sa.Boolean(), + nullable=False, + comment="Whether sample was added to pephub", + ), + sa.Column("submission_date", sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column("last_update_date", sa.TIMESTAMP(timezone=True), nullable=True), + sa.Column("is_universe", sa.Boolean(), nullable=True), + sa.Column("license_id", sa.String(), nullable=True), + sa.Column( + "processed", + sa.Boolean(), + nullable=False, + comment="Whether the bed file was processed", + ), + sa.ForeignKeyConstraint(["license_id"], ["licenses.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + "genome_alias_index", + "bed", + ["genome_alias"], + unique=False, + postgresql_with={"deduplicate_items": "true"}, + ) + op.create_index(op.f("ix_bed_id"), "bed", ["id"], unique=False) + op.create_index(op.f("ix_bed_license_id"), "bed", ["license_id"], unique=False) + op.create_index( + "ix_bed_not_file_indexed", + "bed", + ["id"], + unique=False, + postgresql_where=sa.text("file_indexed = false"), + ) + op.create_index( + "ix_bed_not_indexed", + "bed", + ["id"], + unique=False, + postgresql_where=sa.text("indexed = false"), + ) + op.create_index( + "ix_bed_submission_date", + "bed", + [sa.literal_column("submission_date DESC"), sa.literal_column("id")], + unique=False, + ) + op.create_index( + "ix_bed_unprocessed", + "bed", + ["id"], + unique=False, + postgresql_where=sa.text("processed = false"), + ) + op.create_table( + "geo_gsm_status", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("gse_status_id", sa.Integer(), nullable=False), + sa.Column("gsm", sa.String(), nullable=False, comment="GSM number"), + sa.Column("sample_name", sa.String(), nullable=False), + sa.Column( + "status", sa.String(), nullable=False, comment="Status of the GEO sample" + ), + sa.Column("error", sa.String(), nullable=True, comment="Error message"), + sa.Column( + "source_submission_date", + sa.TIMESTAMP(timezone=True), + nullable=True, + comment="Submission date of the source", + ), + sa.Column("submission_date", sa.TIMESTAMP(timezone=True), nullable=False), + sa.Column("bed_id", sa.String(), nullable=True, comment="Bed identifier"), + sa.Column( + "file_size", sa.BigInteger(), nullable=False, comment="Size of the file" + ), + sa.Column("genome", sa.String(), nullable=True, comment="Genome"), + sa.ForeignKeyConstraint( + ["gse_status_id"], ["geo_gse_status.id"], ondelete="CASCADE" + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_geo_gsm_status_bed_id"), "geo_gsm_status", ["bed_id"], unique=False + ) + op.create_index( + op.f("ix_geo_gsm_status_gse_status_id"), + "geo_gsm_status", + ["gse_status_id"], + unique=False, + ) + op.create_index( + op.f("ix_geo_gsm_status_id"), "geo_gsm_status", ["id"], unique=False + ) + op.create_table( + "usage_bedset_meta", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("bedset_id", sa.String(), nullable=True), + sa.Column("count", sa.Integer(), nullable=False, comment="Number of visits"), + sa.Column( + "date_from", + sa.TIMESTAMP(timezone=True), + nullable=False, + comment="Date from", + ), + sa.Column( + "date_to", sa.TIMESTAMP(timezone=True), nullable=False, comment="Date to" + ), + sa.ForeignKeyConstraint(["bedset_id"], ["bedsets.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_usage_bedset_meta_bedset_id"), + "usage_bedset_meta", + ["bedset_id"], + unique=False, + ) + op.create_index( + op.f("ix_usage_bedset_meta_id"), "usage_bedset_meta", ["id"], unique=False + ) + op.create_table( + "bed_metadata", + sa.Column("species_name", sa.String(), nullable=False, comment="Organism name"), + sa.Column( + "species_id", sa.String(), nullable=True, comment="Organism taxon id" + ), + sa.Column( + "genotype", sa.String(), nullable=True, comment="Genotype of the sample" + ), + sa.Column( + "phenotype", sa.String(), nullable=True, comment="Phenotype of the sample" + ), + sa.Column( + "cell_type", + sa.String(), + nullable=True, + comment="Specific kind of cell with distinct characteristics found in an organism. e.g. Neurons, Hepatocytes, Adipocytes", + ), + sa.Column( + "cell_line", + sa.String(), + nullable=True, + comment="Population of cells derived from a single cell and cultured in the lab for extended use, e.g. HeLa, HepG2, k562", + ), + sa.Column("tissue", sa.String(), nullable=True, comment="Tissue type"), + sa.Column( + "library_source", + sa.String(), + nullable=True, + comment="Library source (e.g. genomic, transcriptomic)", + ), + sa.Column( + "assay", + sa.String(), + nullable=True, + comment="Experimental protocol (e.g. ChIP-seq)", + ), + sa.Column( + "antibody", sa.String(), nullable=True, comment="Antibody used in the assay" + ), + sa.Column( + "target", + sa.String(), + nullable=True, + comment="Target of the assay (e.g. H3K4me3)", + ), + sa.Column( + "treatment", + sa.String(), + nullable=True, + comment="Treatment of the sample (e.g. drug treatment)", + ), + sa.Column( + "original_file_name", + sa.String(), + nullable=True, + comment="Original file name", + ), + sa.Column( + "global_sample_id", + postgresql.ARRAY(sa.String()), + nullable=True, + comment="Global sample identifier. e.g. GSM000", + ), + sa.Column( + "global_experiment_id", + postgresql.ARRAY(sa.String()), + nullable=True, + comment="Global experiment identifier. e.g. GSE000", + ), + sa.Column("id", sa.String(), nullable=False), + sa.ForeignKeyConstraint(["id"], ["bed.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_bed_metadata_id"), "bed_metadata", ["id"], unique=False) + op.create_table( + "bed_stats", + sa.Column("id", sa.String(), nullable=False), + sa.Column("number_of_regions", sa.Float(), nullable=True), + sa.Column("gc_content", sa.Float(), nullable=True), + sa.Column("median_tss_dist", sa.Float(), nullable=True), + sa.Column("mean_region_width", sa.Float(), nullable=True), + sa.Column("exon_frequency", sa.Float(), nullable=True), + sa.Column("intron_frequency", sa.Float(), nullable=True), + sa.Column("promoterprox_frequency", sa.Float(), nullable=True), + sa.Column("intergenic_frequency", sa.Float(), nullable=True), + sa.Column("promotercore_frequency", sa.Float(), nullable=True), + sa.Column("fiveutr_frequency", sa.Float(), nullable=True), + sa.Column("threeutr_frequency", sa.Float(), nullable=True), + sa.Column("fiveutr_percentage", sa.Float(), nullable=True), + sa.Column("threeutr_percentage", sa.Float(), nullable=True), + sa.Column("promoterprox_percentage", sa.Float(), nullable=True), + sa.Column("exon_percentage", sa.Float(), nullable=True), + sa.Column("intron_percentage", sa.Float(), nullable=True), + sa.Column("intergenic_percentage", sa.Float(), nullable=True), + sa.Column("promotercore_percentage", sa.Float(), nullable=True), + sa.Column("tssdist", sa.Float(), nullable=True), + sa.ForeignKeyConstraint(["id"], ["bed.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index(op.f("ix_bed_stats_id"), "bed_stats", ["id"], unique=False) + op.create_index( + "ix_bed_stats_missing_regions", + "bed_stats", + ["id"], + unique=False, + postgresql_where=sa.text("number_of_regions IS NULL"), + ) + op.create_table( + "bedfile_bedset_relation", + sa.Column("bedset_id", sa.String(), nullable=False), + sa.Column("bedfile_id", sa.String(), nullable=False), + sa.ForeignKeyConstraint(["bedfile_id"], ["bed.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["bedset_id"], ["bedsets.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("bedset_id", "bedfile_id"), + ) + op.create_index( + op.f("ix_bedfile_bedset_relation_bedfile_id"), + "bedfile_bedset_relation", + ["bedfile_id"], + unique=False, + ) + op.create_table( + "files", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column( + "name", + sa.String(), + nullable=False, + comment="Name of the file, e.g. bed, bigBed", + ), + sa.Column( + "file_digest", + sa.String(), + nullable=True, + comment="Digest of the file. Mainly used for bed file.", + ), + sa.Column("title", sa.String(), nullable=True), + sa.Column( + "type", + sa.String(), + nullable=False, + comment="Type of the object, e.g. file, plot, ...", + ), + sa.Column("path", sa.String(), nullable=False), + sa.Column( + "path_thumbnail", + sa.String(), + nullable=True, + comment="Thumbnail path of the file", + ), + sa.Column("description", sa.String(), nullable=True), + sa.Column("size", sa.Integer(), nullable=True, comment="Size of the file"), + sa.Column("bedfile_id", sa.String(), nullable=True), + sa.Column("bedset_id", sa.String(), nullable=True), + sa.ForeignKeyConstraint(["bedfile_id"], ["bed.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["bedset_id"], ["bedsets.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name", "bedfile_id"), + sa.UniqueConstraint("name", "bedset_id"), + ) + op.create_index(op.f("ix_files_bedfile_id"), "files", ["bedfile_id"], unique=False) + op.create_index(op.f("ix_files_bedset_id"), "files", ["bedset_id"], unique=False) + op.create_index(op.f("ix_files_id"), "files", ["id"], unique=False) + op.create_table( + "genome_ref_stats", + sa.Column("id", sa.Integer(), nullable=False), + sa.Column("bed_id", sa.String(), nullable=False), + sa.Column("provided_genome", sa.String(), nullable=False), + sa.Column( + "compared_genome", sa.String(), nullable=False, comment="Compared Genome" + ), + sa.Column("genome_digest", sa.String(), nullable=False), + sa.Column("xs", sa.Float(), nullable=True), + sa.Column("oobr", sa.Float(), nullable=True), + sa.Column("sequence_fit", sa.Float(), nullable=True), + sa.Column("assigned_points", sa.Integer(), nullable=False), + sa.Column("tier_ranking", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(["bed_id"], ["bed.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint( + ["genome_digest"], ["reference_genomes.digest"], ondelete="CASCADE" + ), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("bed_id", "compared_genome"), + ) + op.create_index( + op.f("ix_genome_ref_stats_bed_id"), "genome_ref_stats", ["bed_id"], unique=False + ) + op.create_index( + op.f("ix_genome_ref_stats_id"), "genome_ref_stats", ["id"], unique=False + ) + op.create_table( + "universes", + sa.Column("id", sa.String(), nullable=False), + sa.Column( + "method", + sa.String(), + nullable=True, + comment="Method used to create the universe", + ), + sa.Column("bedset_id", sa.String(), nullable=True), + sa.ForeignKeyConstraint(["bedset_id"], ["bedsets.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["id"], ["bed.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_universes_bedset_id"), "universes", ["bedset_id"], unique=False + ) + op.create_index(op.f("ix_universes_id"), "universes", ["id"], unique=False) + op.create_table( + "usage_bed_meta", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("bed_id", sa.String(), nullable=True), + sa.Column("count", sa.Integer(), nullable=False, comment="Number of visits"), + sa.Column( + "date_from", + sa.TIMESTAMP(timezone=True), + nullable=False, + comment="Date from", + ), + sa.Column( + "date_to", sa.TIMESTAMP(timezone=True), nullable=False, comment="Date to" + ), + sa.ForeignKeyConstraint(["bed_id"], ["bed.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) + op.create_index( + op.f("ix_usage_bed_meta_bed_id"), "usage_bed_meta", ["bed_id"], unique=False + ) + op.create_index( + op.f("ix_usage_bed_meta_id"), "usage_bed_meta", ["id"], unique=False + ) + op.create_table( + "tokenized_bed", + sa.Column("bed_id", sa.String(), nullable=False), + sa.Column("universe_id", sa.String(), nullable=False), + sa.Column( + "path", + sa.String(), + nullable=False, + comment="Path to the tokenized bed file", + ), + sa.ForeignKeyConstraint(["bed_id"], ["bed.id"], ondelete="CASCADE"), + sa.ForeignKeyConstraint(["universe_id"], ["universes.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("bed_id", "universe_id"), + ) + op.create_index( + op.f("ix_tokenized_bed_bed_id"), "tokenized_bed", ["bed_id"], unique=False + ) + op.create_index( + op.f("ix_tokenized_bed_universe_id"), + "tokenized_bed", + ["universe_id"], + unique=False, + ) # ### end Alembic commands ### def downgrade() -> None: """Downgrade schema.""" # ### commands auto generated by Alembic - please adjust! ### - op.drop_index(op.f('ix_tokenized_bed_universe_id'), table_name='tokenized_bed') - op.drop_index(op.f('ix_tokenized_bed_bed_id'), table_name='tokenized_bed') - op.drop_table('tokenized_bed') - op.drop_index(op.f('ix_usage_bed_meta_id'), table_name='usage_bed_meta') - op.drop_index(op.f('ix_usage_bed_meta_bed_id'), table_name='usage_bed_meta') - op.drop_table('usage_bed_meta') - op.drop_index(op.f('ix_universes_id'), table_name='universes') - op.drop_index(op.f('ix_universes_bedset_id'), table_name='universes') - op.drop_table('universes') - op.drop_index(op.f('ix_genome_ref_stats_id'), table_name='genome_ref_stats') - op.drop_index(op.f('ix_genome_ref_stats_bed_id'), table_name='genome_ref_stats') - op.drop_table('genome_ref_stats') - op.drop_index(op.f('ix_files_id'), table_name='files') - op.drop_index(op.f('ix_files_bedset_id'), table_name='files') - op.drop_index(op.f('ix_files_bedfile_id'), table_name='files') - op.drop_table('files') - op.drop_index(op.f('ix_bedfile_bedset_relation_bedfile_id'), table_name='bedfile_bedset_relation') - op.drop_table('bedfile_bedset_relation') - op.drop_index('ix_bed_stats_missing_regions', table_name='bed_stats', postgresql_where=sa.text('number_of_regions IS NULL')) - op.drop_index(op.f('ix_bed_stats_id'), table_name='bed_stats') - op.drop_table('bed_stats') - op.drop_index(op.f('ix_bed_metadata_id'), table_name='bed_metadata') - op.drop_table('bed_metadata') - op.drop_index(op.f('ix_usage_bedset_meta_id'), table_name='usage_bedset_meta') - op.drop_index(op.f('ix_usage_bedset_meta_bedset_id'), table_name='usage_bedset_meta') - op.drop_table('usage_bedset_meta') - op.drop_index(op.f('ix_geo_gsm_status_id'), table_name='geo_gsm_status') - op.drop_index(op.f('ix_geo_gsm_status_gse_status_id'), table_name='geo_gsm_status') - op.drop_index(op.f('ix_geo_gsm_status_bed_id'), table_name='geo_gsm_status') - op.drop_table('geo_gsm_status') - op.drop_index('ix_bed_unprocessed', table_name='bed', postgresql_where=sa.text('processed = false')) - op.drop_index('ix_bed_submission_date', table_name='bed') - op.drop_index('ix_bed_not_indexed', table_name='bed', postgresql_where=sa.text('indexed = false')) - op.drop_index('ix_bed_not_file_indexed', table_name='bed', postgresql_where=sa.text('file_indexed = false')) - op.drop_index(op.f('ix_bed_license_id'), table_name='bed') - op.drop_index(op.f('ix_bed_id'), table_name='bed') - op.drop_index('genome_alias_index', table_name='bed', postgresql_with={'deduplicate_items': 'true'}) - op.drop_table('bed') - op.drop_index(op.f('ix_usage_search_id'), table_name='usage_search') - op.drop_table('usage_search') - op.drop_index(op.f('ix_usage_files_id'), table_name='usage_files') - op.drop_table('usage_files') - op.drop_index(op.f('ix_reference_genomes_digest'), table_name='reference_genomes') - op.drop_table('reference_genomes') - op.drop_index(op.f('ix_licenses_id'), table_name='licenses') - op.drop_table('licenses') - op.drop_index(op.f('ix_geo_gse_status_id'), table_name='geo_gse_status') - op.drop_table('geo_gse_status') - op.drop_index('ix_bedsets_unprocessed', table_name='bedsets', postgresql_where=sa.text('processed = false')) - op.drop_index('ix_bedsets_name_trgm', table_name='bedsets', postgresql_using='gin', postgresql_ops={'name': 'gin_trgm_ops'}) - op.drop_index(op.f('ix_bedsets_id'), table_name='bedsets') - op.drop_index('ix_bedsets_description_trgm', table_name='bedsets', postgresql_using='gin', postgresql_ops={'description': 'gin_trgm_ops'}) - op.drop_table('bedsets') - op.drop_index(op.f('ix_bed_snapshots_id'), table_name='bed_snapshots') - op.drop_table('bed_snapshots') + op.drop_index(op.f("ix_tokenized_bed_universe_id"), table_name="tokenized_bed") + op.drop_index(op.f("ix_tokenized_bed_bed_id"), table_name="tokenized_bed") + op.drop_table("tokenized_bed") + op.drop_index(op.f("ix_usage_bed_meta_id"), table_name="usage_bed_meta") + op.drop_index(op.f("ix_usage_bed_meta_bed_id"), table_name="usage_bed_meta") + op.drop_table("usage_bed_meta") + op.drop_index(op.f("ix_universes_id"), table_name="universes") + op.drop_index(op.f("ix_universes_bedset_id"), table_name="universes") + op.drop_table("universes") + op.drop_index(op.f("ix_genome_ref_stats_id"), table_name="genome_ref_stats") + op.drop_index(op.f("ix_genome_ref_stats_bed_id"), table_name="genome_ref_stats") + op.drop_table("genome_ref_stats") + op.drop_index(op.f("ix_files_id"), table_name="files") + op.drop_index(op.f("ix_files_bedset_id"), table_name="files") + op.drop_index(op.f("ix_files_bedfile_id"), table_name="files") + op.drop_table("files") + op.drop_index( + op.f("ix_bedfile_bedset_relation_bedfile_id"), + table_name="bedfile_bedset_relation", + ) + op.drop_table("bedfile_bedset_relation") + op.drop_index( + "ix_bed_stats_missing_regions", + table_name="bed_stats", + postgresql_where=sa.text("number_of_regions IS NULL"), + ) + op.drop_index(op.f("ix_bed_stats_id"), table_name="bed_stats") + op.drop_table("bed_stats") + op.drop_index(op.f("ix_bed_metadata_id"), table_name="bed_metadata") + op.drop_table("bed_metadata") + op.drop_index(op.f("ix_usage_bedset_meta_id"), table_name="usage_bedset_meta") + op.drop_index( + op.f("ix_usage_bedset_meta_bedset_id"), table_name="usage_bedset_meta" + ) + op.drop_table("usage_bedset_meta") + op.drop_index(op.f("ix_geo_gsm_status_id"), table_name="geo_gsm_status") + op.drop_index(op.f("ix_geo_gsm_status_gse_status_id"), table_name="geo_gsm_status") + op.drop_index(op.f("ix_geo_gsm_status_bed_id"), table_name="geo_gsm_status") + op.drop_table("geo_gsm_status") + op.drop_index( + "ix_bed_unprocessed", + table_name="bed", + postgresql_where=sa.text("processed = false"), + ) + op.drop_index("ix_bed_submission_date", table_name="bed") + op.drop_index( + "ix_bed_not_indexed", + table_name="bed", + postgresql_where=sa.text("indexed = false"), + ) + op.drop_index( + "ix_bed_not_file_indexed", + table_name="bed", + postgresql_where=sa.text("file_indexed = false"), + ) + op.drop_index(op.f("ix_bed_license_id"), table_name="bed") + op.drop_index(op.f("ix_bed_id"), table_name="bed") + op.drop_index( + "genome_alias_index", + table_name="bed", + postgresql_with={"deduplicate_items": "true"}, + ) + op.drop_table("bed") + op.drop_index(op.f("ix_usage_search_id"), table_name="usage_search") + op.drop_table("usage_search") + op.drop_index(op.f("ix_usage_files_id"), table_name="usage_files") + op.drop_table("usage_files") + op.drop_index(op.f("ix_reference_genomes_digest"), table_name="reference_genomes") + op.drop_table("reference_genomes") + op.drop_index(op.f("ix_licenses_id"), table_name="licenses") + op.drop_table("licenses") + op.drop_index(op.f("ix_geo_gse_status_id"), table_name="geo_gse_status") + op.drop_table("geo_gse_status") + op.drop_index( + "ix_bedsets_unprocessed", + table_name="bedsets", + postgresql_where=sa.text("processed = false"), + ) + op.drop_index( + "ix_bedsets_name_trgm", + table_name="bedsets", + postgresql_using="gin", + postgresql_ops={"name": "gin_trgm_ops"}, + ) + op.drop_index(op.f("ix_bedsets_id"), table_name="bedsets") + op.drop_index( + "ix_bedsets_description_trgm", + table_name="bedsets", + postgresql_using="gin", + postgresql_ops={"description": "gin_trgm_ops"}, + ) + op.drop_table("bedsets") + op.drop_index(op.f("ix_bed_snapshots_id"), table_name="bed_snapshots") + op.drop_table("bed_snapshots") # ### end Alembic commands ### diff --git a/bbconf/bbagent.py b/bbconf/bbagent.py index 41acaf4e..571e2ff2 100644 --- a/bbconf/bbagent.py +++ b/bbconf/bbagent.py @@ -740,8 +740,14 @@ def _bin_number_of_regions(self, number_of_regions: list) -> BinValues: return BinValues( bins=n_region_bin_edges, counts=n_region_counts, - mean=round(statistics.mean(number_of_regions), 2), - median=round(statistics.median(number_of_regions), 2), + mean=round(statistics.mean(number_of_regions), 2) + if number_of_regions + else 0, + median=( + round(statistics.median(number_of_regions), 2) + if number_of_regions + else 0 + ), ) def _bin_mean_region_width(self, mean_region_widths: list) -> BinValues: @@ -771,8 +777,16 @@ def _bin_mean_region_width(self, mean_region_widths: list) -> BinValues: return BinValues( bins=mean_reg_width_bin_edges, counts=mean_reg_width_counts, - mean=round(statistics.mean(mean_region_widths), 2), - median=round(statistics.median(mean_region_widths), 2), + mean=( + round(statistics.mean(mean_region_widths), 2) + if mean_region_widths + else 0 + ), + median=( + round(statistics.median(mean_region_widths), 2) + if mean_region_widths + else 0 + ), ) def _bin_file_size(self, list_file_size: list) -> BinValues: @@ -803,8 +817,16 @@ def _bin_file_size(self, list_file_size: list) -> BinValues: return BinValues( bins=file_size_bin_edges, counts=file_size_counts, - mean=round(statistics.mean(filtered_list_file_size), 2), - median=round(statistics.median(filtered_list_file_size), 2), + mean=( + round(statistics.mean(filtered_list_file_size), 2) + if filtered_list_file_size + else 0 + ), + median=( + round(statistics.median(filtered_list_file_size), 2) + if filtered_list_file_size + else 0 + ), ) def _get_geo_stats(self, sa_session: Session) -> GEOStatistics: @@ -857,8 +879,8 @@ def _get_geo_stats(self, sa_session: Session) -> GEOStatistics: file_sizes=BinValues( bins=list(file_size_bin_edges), counts=file_size_counts.astype(int).tolist(), - mean=round(statistics.mean(file_sizes), 2), - median=round(statistics.median(file_sizes), 2), + mean=round(statistics.mean(file_sizes), 2) if file_sizes else 0, + median=round(statistics.median(file_sizes), 2) if file_sizes else 0, ), ) diff --git a/bbconf/models/base_models.py b/bbconf/models/base_models.py index a465c814..76a38f9d 100644 --- a/bbconf/models/base_models.py +++ b/bbconf/models/base_models.py @@ -130,4 +130,4 @@ class BedSnapshotResult(BaseModel): class BedSnapshotListResult(BaseModel): count: int - results: list[BedSnapshotResult] \ No newline at end of file + results: list[BedSnapshotResult] diff --git a/bbconf/modules/snapshots.py b/bbconf/modules/snapshots.py index ba488d11..49e7ee2c 100644 --- a/bbconf/modules/snapshots.py +++ b/bbconf/modules/snapshots.py @@ -148,9 +148,7 @@ def list( count_statement = select(func.count()).select_from(BedSnapshot) if file_type is not None: statement = statement.where(BedSnapshot.file_type == file_type) - count_statement = count_statement.where( - BedSnapshot.file_type == file_type - ) + count_statement = count_statement.where(BedSnapshot.file_type == file_type) statement = statement.order_by( BedSnapshot.creation_date.desc(), BedSnapshot.id.desc() ) @@ -188,9 +186,7 @@ def get_by_filename(self, filename: str) -> BedSnapshotResult: rows = session.scalars( select(BedSnapshot) .where(BedSnapshot.file_path.like(f"%{filename}")) - .order_by( - BedSnapshot.creation_date.desc(), BedSnapshot.id.desc() - ) + .order_by(BedSnapshot.creation_date.desc(), BedSnapshot.id.desc()) ).all() for row in rows: if os.path.basename(row.file_path) == filename: From 0b48d4e1d5739ce48089bb3f171aa43d2f2cb8cc Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Sun, 16 Aug 2026 13:57:48 -0400 Subject: [PATCH 5/6] Removed phc from bedbase --- .github/workflows/run-pytest.yml | 1 + bbconf/config_parser/bedbaseconfig.py | 30 ------ bbconf/config_parser/const.py | 4 - bbconf/config_parser/models.py | 10 -- bbconf/config_parser/utils.py | 3 +- bbconf/db_utils.py | 3 - bbconf/modules/bedfiles.py | 131 ++++---------------------- bbconf/modules/bedsets.py | 70 +------------- pyproject.toml | 1 - tests/config_test.yaml | 4 - tests/conftest.py | 8 -- tests/test_bedfile.py | 17 +--- tests/test_universes.py | 2 +- 13 files changed, 23 insertions(+), 261 deletions(-) diff --git a/.github/workflows/run-pytest.yml b/.github/workflows/run-pytest.yml index 3abcaa95..d2d6c3ce 100644 --- a/.github/workflows/run-pytest.yml +++ b/.github/workflows/run-pytest.yml @@ -15,6 +15,7 @@ jobs: python-version: ["3.10", "3.13"] os: [ubuntu-latest] # can't use macOS when using service containers or container jobs runs-on: ${{ matrix.os }} + services: postgres: image: postgres diff --git a/bbconf/config_parser/bedbaseconfig.py b/bbconf/config_parser/bedbaseconfig.py index 9c5c5562..fea22738 100644 --- a/bbconf/config_parser/bedbaseconfig.py +++ b/bbconf/config_parser/bedbaseconfig.py @@ -20,7 +20,6 @@ from geniml.search.backends import BiVectorBackend, QdrantBackend from geniml.search.interfaces import BiVectorSearchInterface from geniml.search.query2vec import BED2Vec -from pephubclient import PEPHubClient from qdrant_client import QdrantClient, models from sentence_transformers import SparseEncoder from umap import UMAP @@ -115,7 +114,6 @@ def __init__(self, config: Path | str, init_ml: bool = True): self.umap_encoder: UMAP | None = None self.sparse_encoder = None - self._phc = self._init_pephubclient() self._boto3_client = self._init_boto3_client() @staticmethod @@ -165,16 +163,6 @@ def db_engine(self) -> BaseEngine: """ return self._db_engine - @property - def phc(self) -> PEPHubClient: - """ - Get PEPHub client. - - Returns: - PEPHub client. - """ - return self._phc - @property def boto3_client(self) -> boto3.client: """ @@ -683,24 +671,6 @@ def delete_files_s3(self, files: list[FileModel]) -> None: self.delete_s3(file.path_thumbnail) return None - @staticmethod - def _init_pephubclient() -> PEPHubClient | None: - """ - Create Pephub client object using credentials provided in config file. - - Returns: - PephubClient. - """ - - # try: - # _LOGGER.info("Initializing PEPHub client...") - # return PEPHubClient() - # except Exception as e: - # _LOGGER.error(f"Error in creating PephubClient object: {e}") - # warnings.warn(f"Error in creating PephubClient object: {e}", UserWarning) - # return None - return None - def get_prefixed_uri(self, postfix: str, access_id: str) -> str: """ Return uri with correct prefix (schema). diff --git a/bbconf/config_parser/const.py b/bbconf/config_parser/const.py index 61aad4ec..91877ef2 100644 --- a/bbconf/config_parser/const.py +++ b/bbconf/config_parser/const.py @@ -17,10 +17,6 @@ DEFAULT_SPARSE_MODEL = "prithivida/Splade_PP_en_v2" DEFAULT_REGION2_VEC_MODEL = "databio/r2v_encoder-ChIP-atlas-hg38" -DEFAULT_PEPHUB_NAMESPACE = "databio" -DEFAULT_PEPHUB_NAME = "bedbase_all" -DEFAULT_PEPHUB_TAG = "default" - DEFAULT_S3_BUCKET = "bedbase" diff --git a/bbconf/config_parser/models.py b/bbconf/config_parser/models.py index 002c9536..c2f5ca7e 100644 --- a/bbconf/config_parser/models.py +++ b/bbconf/config_parser/models.py @@ -9,9 +9,6 @@ DEFAULT_DB_DRIVER, DEFAULT_DB_NAME, DEFAULT_DB_PORT, - DEFAULT_PEPHUB_NAME, - DEFAULT_PEPHUB_NAMESPACE, - DEFAULT_PEPHUB_TAG, DEFAULT_QDRANT_BIVEC_COLLECTION_NAME, DEFAULT_QDRANT_FILE_COLLECTION_NAME, DEFAULT_QDRANT_HYBRID_COLLECTION_NAME, @@ -121,12 +118,6 @@ def modify_access(self) -> bool: return False -class ConfigPepHubClient(BaseModel): - namespace: str | None = DEFAULT_PEPHUB_NAMESPACE - name: str | None = DEFAULT_PEPHUB_NAME - tag: str | None = DEFAULT_PEPHUB_TAG - - class ConfigFile(BaseModel): database: ConfigDB qdrant: ConfigQdrant = None @@ -134,7 +125,6 @@ class ConfigFile(BaseModel): path: ConfigPath access_methods: AccessMethods = None s3: ConfigS3 = None - phc: ConfigPepHubClient = None model_config = ConfigDict(extra="allow") diff --git a/bbconf/config_parser/utils.py b/bbconf/config_parser/utils.py index cba2e364..46f0a7f1 100644 --- a/bbconf/config_parser/utils.py +++ b/bbconf/config_parser/utils.py @@ -1,7 +1,6 @@ import logging import yacman -from pephubclient.helpers import MessageHandler as m from pydantic_core._pydantic_core import ValidationError from bbconf.config_parser.models import ConfigFile @@ -56,6 +55,6 @@ def config_analyzer(config_path: str) -> bool: ) return False - m.print_success("Configuration file is valid! ") + _LOGGER.info("Configuration file is valid!") return True diff --git a/bbconf/db_utils.py b/bbconf/db_utils.py index 7e3d21e1..e49423c6 100644 --- a/bbconf/db_utils.py +++ b/bbconf/db_utils.py @@ -111,9 +111,6 @@ class Bed(Base): default=False, comment="Whether file was tokenized and added to the vector database", ) - pephub: Mapped[bool] = mapped_column( - default=False, comment="Whether sample was added to pephub" - ) submission_date: Mapped[datetime.datetime] = mapped_column( default=deliver_update_date diff --git a/bbconf/modules/bedfiles.py b/bbconf/modules/bedfiles.py index f4382f2b..e21c1c93 100644 --- a/bbconf/modules/bedfiles.py +++ b/bbconf/modules/bedfiles.py @@ -6,7 +6,6 @@ from geniml.bbclient import BBClient from geniml.search.backends import QdrantBackend from gtars.models import RegionSet as GRegionSet -from pephubclient.exceptions import ResponseError from pydantic import BaseModel from qdrant_client import models from qdrant_client.http.exceptions import UnexpectedResponse @@ -100,7 +99,7 @@ def get(self, identifier: str, full: bool = False) -> BedMetadataAll: Args: identifier: Bed file identifier. - full: If True, return full metadata, including statistics, files, and raw metadata from pephub. + full: If True, return full metadata, including statistics and files. Returns: BED file metadata. @@ -126,8 +125,7 @@ def _build_metadata(self, bed_object: Bed, full: bool = False) -> BedMetadataAll Args: bed_object: Bed ORM object to build metadata from. - full: If True, return full metadata, including statistics, files, - and raw metadata from pephub. + full: If True, return full metadata, including statistics and files. Returns: BED file metadata. @@ -195,21 +193,8 @@ def _build_metadata(self, bed_object: Bed, full: bool = False) -> BedMetadataAll universe_meta = None bed_bedsets = [] - try: - if full: - bed_metadata = BedPEPHubRestrict( - **self.config.phc.sample.get( - namespace=self.config.config.phc.namespace, - name=self.config.config.phc.name, - tag=self.config.config.phc.tag, - sample_name=identifier, - ) - ) - else: - bed_metadata = None - except Exception as e: - _LOGGER.warning(f"Could not retrieve metadata from pephub. Error: {e}") - bed_metadata = None + # Raw metadata used to come from PEPHub, which is no longer used. + bed_metadata = None return BedMetadataAll( id=bed_object.id, @@ -395,17 +380,8 @@ def get_raw_metadata(self, identifier: str) -> BedPEPHub: Returns: BED file raw metadata. """ - try: - bed_metadata = self.config.phc.sample.get( - namespace=self.config.config.phc.namespace, - name=self.config.config.phc.name, - tag=self.config.config.phc.tag, - sample_name=identifier, - ) - except Exception as e: - _LOGGER.warning(f"Could not retrieve metadata from pephub. Error: {e}") - bed_metadata = {} - return BedPEPHubRestrict(**bed_metadata) + # Raw metadata used to come from PEPHub, which is no longer used. + return BedPEPHubRestrict() def get_classification(self, identifier: str) -> BedClassification: """ @@ -608,7 +584,7 @@ def add( Args: identifier: Bed file identifier. stats: Bed file results {statistics, plots, files, metadata}. - metadata: Bed file metadata (will be saved in pephub). + metadata: Bed file metadata. plots: Bed file plots. files: Bed file files. classification: Bed file classification. @@ -616,11 +592,11 @@ def add( license_id: Bed file license id (default: 'DUO:0000042'). Full list of licenses: https://raw.githubusercontent.com/EBISPOT/DUO/master/duo.csv upload_qdrant: Add bed file to qdrant indexes. - upload_pephub: Add bed file to pephub. + upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload files to s3. local_path: Local path to the output files. overwrite: Overwrite bed file if it already exists. - nofail: Do not raise an error for error in pephub/s3/qdrant or record exists and not overwrite. + nofail: Do not raise an error for error in s3/qdrant or record exists and not overwrite. processed: True if bedfile was processed and statistics and plots were calculated. Returns: @@ -673,22 +649,7 @@ def add( classification = BedClassification(**classification) if upload_pephub: - pephub_metadata = BedPEPHub(**metadata) - try: - self.upload_pephub( - identifier, - pephub_metadata.model_dump(exclude=set("input_file")), - overwrite, - ) - except Exception as e: - _LOGGER.warning( - f"Could not upload to pephub. Error: {e}. nofail: {nofail}" - ) - upload_pephub = False - if not nofail: - raise e - else: - _LOGGER.info("upload_pephub set to false. Skipping pephub..") + _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") if upload_qdrant: if classification.genome_alias == "hg38": @@ -724,7 +685,7 @@ def add( description=bed_metadata.description, license_id=license_id, indexed=upload_qdrant, - pephub=upload_pephub, + pephub=False, processed=processed, ) session.add(new_bed) @@ -809,18 +770,18 @@ def update( Args: identifier: Bed file identifier. stats: Bed file results {statistics, plots, files, metadata}. - metadata: Bed file metadata (will be saved in pephub). + metadata: Bed file metadata. plots: Bed file plots. files: Bed file files. classification: Bed file classification. ref_validation: Reference validation data. RefGenValidModel. license_id: Bed file license id (default: 'DUO:0000042'). upload_qdrant: Add bed file to qdrant indexes. - upload_pephub: Add bed file to pephub. + upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload files to s3. local_path: Local path to the output files. overwrite: Overwrite bed file if it already exists. - nofail: Do not raise an error for error in pephub/s3/qdrant or record exists and not overwrite. + nofail: Do not raise an error for error in s3/qdrant or record exists and not overwrite. processed: True if bedfile was processed and statistics and plots were calculated. Returns: @@ -844,18 +805,8 @@ def update( bed_metadata = StandardMeta(**metadata if metadata else {}) classification = BedClassification(**classification if classification else {}) - if upload_pephub and metadata: - metadata = BedPEPHub(**metadata) - try: - self.update_pephub(identifier, metadata.model_dump(), overwrite) - except Exception as e: - _LOGGER.warning( - f"Could not upload to pephub. Error: {e}. nofail: {nofail}" - ) - if not nofail: - raise e - else: - _LOGGER.info("upload_pephub set to false. Skipping pephub..") + if upload_pephub: + _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") if upload_qdrant: if classification.genome_alias == "hg38": @@ -1190,65 +1141,15 @@ def delete(self, identifier: str) -> None: bed_object = session.scalar(statement) files = [FileModel(**k.__dict__) for k in bed_object.files] - delete_pephub = bed_object.pephub delete_qdrant = bed_object.indexed session.delete(bed_object) session.commit() - if delete_pephub: - self.delete_pephub_sample(identifier) if delete_qdrant: self.delete_qdrant_point(identifier) self.config.delete_files_s3(files) - def upload_pephub(self, identifier: str, metadata: dict, overwrite: bool = False): - if not metadata: - _LOGGER.warning("No metadata provided. Skipping pephub upload..") - return False - self.config.phc.sample.create( - namespace=self.config.config.phc.namespace, - name=self.config.config.phc.name, - tag=self.config.config.phc.tag, - sample_name=identifier, - sample_dict=metadata, - overwrite=overwrite, - ) - - def update_pephub( - self, identifier: str, metadata: dict, overwrite: bool = False - ) -> None: - try: - if not metadata: - _LOGGER.warning("No metadata provided. Skipping pephub upload..") - return None - self.config.phc.sample.update( - namespace=self.config.config.phc.namespace, - name=self.config.config.phc.name, - tag=self.config.config.phc.tag, - sample_name=identifier, - sample_dict=metadata, - ) - except ResponseError as e: - _LOGGER.warning(f"Could not update pephub. Error: {e}") - - def delete_pephub_sample(self, identifier: str): - """ - Delete sample from pephub. - - Args: - identifier: Bed file identifier. - """ - try: - self.config.phc.sample.remove( - namespace=self.config.config.phc.namespace, - name=self.config.config.phc.name, - tag=self.config.config.phc.tag, - sample_name=identifier, - ) - except ResponseError as e: - _LOGGER.warning(f"Could not delete from pephub. Error: {e}") - def upload_file_qdrant( self, bed_id: str, diff --git a/bbconf/modules/bedsets.py b/bbconf/modules/bedsets.py index 0713c0ad..c44598ea 100644 --- a/bbconf/modules/bedsets.py +++ b/bbconf/modules/bedsets.py @@ -324,7 +324,7 @@ def create( statistics: Calculate statistics for bedset. annotation: Bedset annotation (author, source). plots: Dictionary with plots. - upload_pephub: Upload bedset to pephub (create view in pephub). + upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload bedset to s3. local_path: Local path to the output files. no_fail: Do not raise an error if bedset already exists. @@ -358,12 +358,7 @@ def create( annotation = {} if upload_pephub: - try: - self._create_pephub_view(identifier, description, bedid_list, no_fail) - except Exception as e: - _LOGGER.error(f"Failed to create view in pephub: {e}") - if not no_fail: - raise e + _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") if no_fail: bedid_list = list(set(bedid_list)) @@ -462,42 +457,6 @@ def _calculate_statistics(self, bed_ids: list[str]) -> BedSetStats: _LOGGER.info("Bedset statistics were calculated successfully") return bedset_stats - # def _create_pephub_view( - # self, - # bedset_id: str, - # description: str = None, - # bed_ids: list = None, - # nofail: bool = False, - # ) -> None: - # """ - # Create view in pephub for bedset. - # - # Args: - # bedset_id: Bedset identifier. - # description: Bedset description. - # bed_ids: List of bed file identifiers. - # nofail: Do not raise an error if sample not found. - # - # Returns: - # None. - # """ - # - # _LOGGER.info(f"Creating view in pephub for bedset '{bedset_id}'") - # try: - # self.config.phc.view.create( - # namespace=self.config.config.phc.namespace, - # name=self.config.config.phc.name, - # tag=self.config.config.phc.tag, - # view_name=bedset_id, - # # description=description, - # sample_list=bed_ids, - # ) - # except Exception as e: - # _LOGGER.error(f"Failed to create view in pephub: {e}") - # if not nofail: - # raise e - # return None - def get_ids_list( self, query: str | None = None, limit: int = 10, offset: int = 0 ) -> BedSetListResult: @@ -623,31 +582,6 @@ def delete(self, identifier: str) -> None: if files: self.config.delete_files_s3(files) - # def delete_phc_view(self, identifier: str, nofail: bool = False) -> None: - # """ - # Delete view in pephub. - # - # Args: - # identifier: Bedset identifier. - # nofail: Do not raise an error if view not found. - # - # Returns: - # None. - # """ - # _LOGGER.info(f"Deleting view in pephub for bedset '{identifier}'") - # try: - # self.config.phc.view.delete( - # namespace=self.config.config.phc.namespace, - # name=self.config.config.phc.name, - # tag=self.config.config.phc.tag, - # view_name=identifier, - # ) - # except Exception as e: - # _LOGGER.error(f"Failed to delete view in pephub: {e}") - # if not nofail: - # raise e - # return None - def exists(self, identifier: str) -> bool: """ Check if bedset exists in the database. diff --git a/pyproject.toml b/pyproject.toml index 0ff9c045..b32405a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,6 @@ dependencies = [ "pydantic >= 2.9.0", "botocore >= 1.34.0, < 1.36.0", "boto3 >= 1.34.54, < 1.36.0", - "pephubclient >= 0.4.5", "sqlalchemy_schemadisplay", "zarr < 3.0.0", "pyyaml >= 6.0.1", diff --git a/tests/config_test.yaml b/tests/config_test.yaml index ef8069eb..41c866c8 100644 --- a/tests/config_test.yaml +++ b/tests/config_test.yaml @@ -17,10 +17,6 @@ qdrant: s3: bucket: bedbase endpoint_url: "None" -phc: - namespace: bedbase - name: bedbase - tag: test access_methods: http: type: "https" diff --git a/tests/conftest.py b/tests/conftest.py index 85d0b01b..1b841e26 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -125,11 +125,3 @@ def example_dict(): @pytest.fixture def load_test_data(): get_bbagent().config.db_engine() - - -@pytest.fixture() -def mocked_phc(mocker): - mocker.patch( - "pephubclient.modules.sample.PEPHubSample.get", - return_value={"sample_name": BED_TEST_ID, "other_metadata": "other_metadata_1"}, - ) diff --git a/tests/test_bedfile.py b/tests/test_bedfile.py index daf5f4ea..f0b88bb0 100644 --- a/tests/test_bedfile.py +++ b/tests/test_bedfile.py @@ -52,16 +52,13 @@ def test_add_nofail(self, bbagent_obj, example_dict, mocker): bbagent_obj.bed.add(**example_dict) assert bbagent_obj.bed.exists(example_dict["identifier"]) - def test_get_all(self, bbagent_obj, mocked_phc): + def test_get_all(self, bbagent_obj): with ContextManagerDBTesting(config=bbagent_obj.config, add_data=True): return_result = bbagent_obj.bed.get(BED_TEST_ID, full=True) assert return_result is not None assert return_result.files is not None assert return_result.plots is not None - # TODO: PEPhub is disabled - # assert return_result.raw_metadata is not None - assert return_result.genome_alias == "hg38" assert return_result.stats.number_of_regions == 1 @@ -69,7 +66,7 @@ def test_get_all(self, bbagent_obj, mocked_phc): assert return_result.plots.chrombins is not None assert return_result.license_id == DEFAULT_LICENSE - def test_get_all_bedsets_bedfile_count(self, bbagent_obj, mocked_phc): + def test_get_all_bedsets_bedfile_count(self, bbagent_obj): with ContextManagerDBTesting( config=bbagent_obj.config, add_data=True, bedset=True ): @@ -92,16 +89,6 @@ def test_get_all_not_found(self, bbagent_obj): assert return_result.genome_alias == "hg38" assert return_result.id == BED_TEST_ID - @pytest.mark.skip( - "Skipped, because PHC is disabled" - ) # TODO: should we disable PHC everywhere? - def test_get_raw_metadata(self, bbagent_obj, mocked_phc): - with ContextManagerDBTesting(config=bbagent_obj.config, add_data=True): - return_result = bbagent_obj.bed.get_raw_metadata(BED_TEST_ID) - - assert return_result is not None - assert return_result.sample_name == BED_TEST_ID - def test_get_stats(self, bbagent_obj): with ContextManagerDBTesting(config=bbagent_obj.config, add_data=True): return_result = bbagent_obj.bed.get_stats(BED_TEST_ID) diff --git a/tests/test_universes.py b/tests/test_universes.py index 366b3770..15b8a45e 100644 --- a/tests/test_universes.py +++ b/tests/test_universes.py @@ -65,6 +65,6 @@ def test_add_get_tokenized(self, bbagent_obj, mocker): assert zarr_mock.called assert f"s3://bedbase/{saved_path}" == zarr_path - def test_get_tokenized(self, bbagent_obj, mocked_phc): + def test_get_tokenized(self, bbagent_obj): # how to test it? ... From 9872e9bff3709552834a93f24e19b512cba74b51 Mon Sep 17 00:00:00 2001 From: khoroshevskyi Date: Sun, 16 Aug 2026 14:39:39 -0400 Subject: [PATCH 6/6] removed leftovers of pephubclient --- bbconf/models/bed_models.py | 32 -------------------------------- bbconf/modules/bedfiles.py | 29 ----------------------------- bbconf/modules/bedsets.py | 5 ----- tests/conftest.py | 1 - tests/test_bedfile.py | 1 - tests/test_bedset.py | 1 - 6 files changed, 69 deletions(-) diff --git a/bbconf/models/bed_models.py b/bbconf/models/bed_models.py index 4d66b7f1..8580b9ba 100644 --- a/bbconf/models/bed_models.py +++ b/bbconf/models/bed_models.py @@ -77,33 +77,6 @@ class BedStatsModel(BaseModel): model_config = ConfigDict(extra="ignore", populate_by_name=True) -class BedPEPHub(BaseModel): - sample_name: str | None = "" - genome: str | None = "" - organism: str | None = "" - species_id: str | None = "" - cell_type: str | None = "" - cell_line: str | None = "" - assay: str | None = Field("", description="Experimental protocol (e.g. ChIP-seq)") - library_source: str | None = Field( - "", description="Library source (e.g. genomic, transcriptomic)" - ) - genotype: str | None = Field("", description="Genotype of the sample") - target: str | None = Field("", description="Target of the assay (e.g. H3K4me3)") - antibody: str | None = Field("", description="Antibody used in the assay") - treatment: str | None = Field( - "", description="Treatment of the sample (e.g. drug treatment)" - ) - tissue: str | None = Field("", description="Tissue type") - global_sample_id: str | None = Field("", description="Global sample identifier") - global_experiment_id: str | None = Field( - "", description="Global experiment identifier" - ) - description: str | None = Field("", description="Description of the sample") - - model_config = ConfigDict(extra="allow", populate_by_name=True) - - class StandardMeta(BaseModel): """ Standardized Bed file metadata @@ -165,10 +138,6 @@ def ensure_list(cls, v: str | list[str]) -> list[str]: raise ValueError("values must be a string or a list of strings") -class BedPEPHubRestrict(BedPEPHub): - model_config = ConfigDict(extra="ignore") - - class BedMetadataBasic(BedClassification): id: str name: str | None = "" @@ -198,7 +167,6 @@ class BedMetadataAll(BedMetadataBasic): plots: BedPlots | None = None files: BedFiles | None = None universe_metadata: UniverseMetadata | None = None - raw_metadata: BedPEPHub | BedPEPHubRestrict | None = None bedsets: list[BedSetMinimal] | None = None diff --git a/bbconf/modules/bedfiles.py b/bbconf/modules/bedfiles.py index e21c1c93..7a594f46 100644 --- a/bbconf/modules/bedfiles.py +++ b/bbconf/modules/bedfiles.py @@ -51,8 +51,6 @@ BedListSearchResult, BedMetadataAll, BedMetadataBasic, - BedPEPHub, - BedPEPHubRestrict, BedPlots, BedSetMinimal, BedStatsModel, @@ -193,9 +191,6 @@ def _build_metadata(self, bed_object: Bed, full: bool = False) -> BedMetadataAll universe_meta = None bed_bedsets = [] - # Raw metadata used to come from PEPHub, which is no longer used. - bed_metadata = None - return BedMetadataAll( id=bed_object.id, name=bed_object.name, @@ -205,7 +200,6 @@ def _build_metadata(self, bed_object: Bed, full: bool = False) -> BedMetadataAll description=bed_object.description, submission_date=bed_object.submission_date, last_update_date=bed_object.last_update_date, - raw_metadata=bed_metadata, genome_alias=bed_object.genome_alias, genome_digest=bed_object.genome_digest, bed_compliance=bed_object.bed_compliance, @@ -370,19 +364,6 @@ def get_files(self, identifier: str) -> BedFiles: ) return bed_files - def get_raw_metadata(self, identifier: str) -> BedPEPHub: - """ - Get file metadata by identifier. - - Args: - identifier: Bed file identifier. - - Returns: - BED file raw metadata. - """ - # Raw metadata used to come from PEPHub, which is no longer used. - return BedPEPHubRestrict() - def get_classification(self, identifier: str) -> BedClassification: """ Get file classification by identifier. @@ -571,7 +552,6 @@ def add( ref_validation: dict[str, BaseModel] | None = None, license_id: str = DEFAULT_LICENSE, upload_qdrant: bool = False, - upload_pephub: bool = False, upload_s3: bool = False, local_path: str = None, overwrite: bool = False, @@ -592,7 +572,6 @@ def add( license_id: Bed file license id (default: 'DUO:0000042'). Full list of licenses: https://raw.githubusercontent.com/EBISPOT/DUO/master/duo.csv upload_qdrant: Add bed file to qdrant indexes. - upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload files to s3. local_path: Local path to the output files. overwrite: Overwrite bed file if it already exists. @@ -648,8 +627,6 @@ def add( bed_metadata = StandardMeta(**metadata) classification = BedClassification(**classification) - if upload_pephub: - _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") if upload_qdrant: if classification.genome_alias == "hg38": @@ -685,7 +662,6 @@ def add( description=bed_metadata.description, license_id=license_id, indexed=upload_qdrant, - pephub=False, processed=processed, ) session.add(new_bed) @@ -757,7 +733,6 @@ def update( ref_validation: dict[str, BaseModel] | None = None, license_id: str = DEFAULT_LICENSE, upload_qdrant: bool = False, - upload_pephub: bool = False, upload_s3: bool = True, local_path: str = None, overwrite: bool = False, @@ -777,7 +752,6 @@ def update( ref_validation: Reference validation data. RefGenValidModel. license_id: Bed file license id (default: 'DUO:0000042'). upload_qdrant: Add bed file to qdrant indexes. - upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload files to s3. local_path: Local path to the output files. overwrite: Overwrite bed file if it already exists. @@ -805,9 +779,6 @@ def update( bed_metadata = StandardMeta(**metadata if metadata else {}) classification = BedClassification(**classification if classification else {}) - if upload_pephub: - _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") - if upload_qdrant: if classification.genome_alias == "hg38": _LOGGER.info(f"Uploading bed file to qdrant.. [{identifier}]") diff --git a/bbconf/modules/bedsets.py b/bbconf/modules/bedsets.py index c44598ea..facfe1e9 100644 --- a/bbconf/modules/bedsets.py +++ b/bbconf/modules/bedsets.py @@ -306,7 +306,6 @@ def create( statistics: bool = False, annotation: dict | None = None, plots: dict | None = None, - upload_pephub: bool = False, upload_s3: bool = False, local_path: str = "", no_fail: bool = False, @@ -324,7 +323,6 @@ def create( statistics: Calculate statistics for bedset. annotation: Bedset annotation (author, source). plots: Dictionary with plots. - upload_pephub: Deprecated and ignored. PEPHub upload is no longer supported. upload_s3: Upload bedset to s3. local_path: Local path to the output files. no_fail: Do not raise an error if bedset already exists. @@ -357,9 +355,6 @@ def create( if not isinstance(annotation, dict): annotation = {} - if upload_pephub: - _LOGGER.info("PEPHub upload is no longer supported. Skipping pephub..") - if no_fail: bedid_list = list(set(bedid_list)) diff --git a/tests/conftest.py b/tests/conftest.py index 1b841e26..e0ebba4b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -114,7 +114,6 @@ def example_dict(): files=files, classification=classification, upload_qdrant=False, - upload_pephub=False, upload_s3=True, local_path=DATA_PATH, overwrite=False, diff --git a/tests/test_bedfile.py b/tests/test_bedfile.py index f0b88bb0..db5cadf8 100644 --- a/tests/test_bedfile.py +++ b/tests/test_bedfile.py @@ -83,7 +83,6 @@ def test_get_all_not_found(self, bbagent_obj): assert return_result is not None assert return_result.files is None assert return_result.plots is None - assert return_result.raw_metadata is None assert return_result.stats is None assert return_result.genome_alias == "hg38" diff --git a/tests/test_bedset.py b/tests/test_bedset.py index 71bde676..1f7977fd 100644 --- a/tests/test_bedset.py +++ b/tests/test_bedset.py @@ -53,7 +53,6 @@ def test_crate_bedset_all(self, bbagent_obj, mocker): }, statistics=True, upload_s3=True, - upload_pephub=False, no_fail=True, ) with Session(bbagent_obj.config.db_engine.engine) as session: