Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
###############
# BASIC SETUP #
###############

# Storage configuration -- see "Configuration" -> "Object Storage" in the docs
ANYVLM_STORAGE_URI=postgresql://anyvlm:anyvlm-pw@localhost:5435/anyvlm

############
# OPTIONAL #
############

## Testing - see "Contributing" -> "Testing" in the docs
ANYVLM_TEST_STORAGE_URI=postgresql://anyvlm_test:anyvlm-test-pw@localhost:5436/anyvlm_test
1 change: 1 addition & 0 deletions .github/workflows/python-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
run: uv run pytest
env:
ANYVLM_ANYVAR_TEST_STORAGE_URI: postgresql://postgres:postgres@localhost:5432/postgres
ANYVLM_TEST_STORAGE_URI: postgresql://postgres:postgres@localhost:5432/postgres
lint:
name: lint
runs-on: ubuntu-latest
Expand Down
11 changes: 11 additions & 0 deletions compose.test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
anyvlm_test_db:
image: postgres:17
tmpfs:
- /var/lib/postgresql/data
ports:
- 127.0.0.1:5436:5432
environment:
- POSTGRES_DB=anyvlm_test
- POSTGRES_USER=anyvlm_test
- POSTGRES_PASSWORD=anyvlm-test-pw
15 changes: 15 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
services:
anyvlm_db:
image: postgres:17
volumes:
- anyvlm_vol:/var/lib/postgresql/data
ports:
- 127.0.0.1:5435:5432
environment:
- POSTGRES_DB=anyvlm
- POSTGRES_USER=anyvlm
- POSTGRES_PASSWORD=anyvlm-pw

volumes:
anyvlm_vol:
external: true
20 changes: 20 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#

# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build

# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

.PHONY: help Makefile

# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
62 changes: 62 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information

project = "anyvlm"
author = "GenomicMedLab"
html_title = "AnyVLM"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = [
"sphinx_rtd_theme",
"sphinx.ext.autodoc",
"sphinx_autodoc_typehints",
"sphinx.ext.linkcode",
"sphinx_copybutton",
"sphinx.ext.autosummary",
"sphinx_github_changelog",
]

templates_path = ["_templates"]
exclude_patterns = []

# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

html_theme = "sphinx_rtd_theme"
html_theme_options = {
"collapse_navigation": False,
}

# -- autodoc things ----------------------------------------------------------
import os
import sys

sys.path.insert(0, os.path.abspath("../../"))
autodoc_preserve_defaults = True

# -- get version -------------------------------------------------------------
from anyvlm import __version__ # noqa: E402

version = release = __version__


# -- linkcode ----------------------------------------------------------------
def linkcode_resolve(domain, info):
if domain != "py":
return None
if not info["module"]:
return None
filename = info["module"].replace(".", "/")
return f"https://github.com/genomicmedlab/anyvlm/blob/main/src/{filename}.py"


# -- code block style --------------------------------------------------------
pygments_style = "default"
pygements_dark_style = "monokai"
31 changes: 31 additions & 0 deletions docs/source/configuration/docker_compose.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Configuring Docker Compose
!!!!!!!!!!!!!!!!!!!!!!!!!!

This page describes how to use the provided ``compose.yaml`` file to start AnyVLM alongside its dependencies, and highlights some configuration options you can customize for your environment.

Overview
--------

The compose file defines one main service:

* ``anyvlm_db`` - PostgreSQL database for AnyVLM

It also defines a Docker volume:

* ``anyvlm_vol`` - storage for the AnyVLM PostgreSQL data directory

This volume is declared as ``external: true``, so it must exist before
you run ``docker compose up``. For example:

.. code-block:: bash

docker volume create anyvlm_vol

Running the stack
-----------------

After creating the external volumes and configuring any optional environment variables, you can start the stack with:

.. code-block:: bash

docker compose up
4 changes: 4 additions & 0 deletions docs/source/configuration/dotenv_example.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Example .env File
!!!!!!!!!!!!!!!!!

.. literalinclude:: ../../../.env.example
16 changes: 16 additions & 0 deletions docs/source/configuration/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Configuration
!!!!!!!!!!!!!

This section details AnyVLM configuration. It is broken down into the following subsections:

* :doc:`Object Storage <storage>`: define database connection, alter table names, and set parameters for bulk processing
* :doc:`Example .env file <dotenv_example>`: use a ``.env`` file to declare environment variables when running REST API service
* :doc:`Docker Compose <docker_compose>`: edit the provided Docker Compose file to tailor it to your needs

.. toctree::
:maxdepth: 2
:hidden:

Storage<storage>
Example .env file<dotenv_example>
Docker Compose<docker_compose>
16 changes: 16 additions & 0 deletions docs/source/configuration/storage.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Object Storage Configuration
!!!!!!!!!!!!!!!!!!!!!!!!!!!!

Storage Connection
==================

Use the ``ANYVLM_STORAGE_URI`` environment variable to pass a `libpq connection string <https://www.postgresql.org/docs/current/libpq.html>`_ to the PostgreSQL connection constructor.

.. list-table::
:widths: 30 70
:header-rows: 1

* - Environment Variable
- Default Value
* - ``ANYVLM_STORAGE_URI``
- ``"postgresql://postgres@localhost:5432/anyvlm"``
9 changes: 9 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
AnyVLM
!!!!!!

.. toctree::
:maxdepth: 2
:caption: Contents
:hidden:

Configuration<configuration/index>
10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ dev = [
"fastapi[standard]",
"seqrepo-rest-service", # for generating SeqRepo-based tests fixtures
]
docs = [
"sphinx==8.2.3",
"sphinx_rtd_theme==3.0.2",
"sphinx-autodoc-typehints==3.2.0",
"sphinx-autobuild==2024.10.3",
"sphinx-copybutton==0.5.2",
"sphinxext-opengraph==0.10.0",
"sphinx-github-changelog==1.7.1",
]

[project.urls]
Homepage = "https://github.com/genomicmedlab/anyvlm"
Expand Down Expand Up @@ -75,6 +84,7 @@ pythonpath = ["src"]
[tool.ruff]
src = ["src"]
include = ["src/**/*.py", "tests/**/*.py"]
exclude = ["docs/source/"]

[tool.ruff.lint]
select = ["ALL"]
Expand Down
7 changes: 7 additions & 0 deletions src/anyvlm/storage/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Provide tools and implementations of AnyVLM storage backends."""

from .base_storage import Storage

DEFAULT_STORAGE_URI = "postgresql://postgres@localhost:5432/anyvlm"

__all__ = ["DEFAULT_STORAGE_URI", "Storage"]
36 changes: 36 additions & 0 deletions src/anyvlm/storage/base_storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Provide base storage implementation."""

from abc import ABC, abstractmethod

from ga4gh.va_spec.base import CohortAlleleFrequencyStudyResult


class StorageError(Exception):
"""Base AnyLM storage error."""


class IncompleteVAObjectError(StorageError):
"""Raise if provided VA object is missing fully-materialized properties required for storage"""


class Storage(ABC):
"""Abstract base class for interacting with storage backends."""

@abstractmethod
def __init__(self, *args, **kwargs) -> None:
"""Initialize the storage backend."""

@abstractmethod
def close(self) -> None:
"""Close the storage backend."""

@abstractmethod
def wipe_db(self) -> None:
"""Wipe all data from the storage backend."""

@abstractmethod
def add_allele_frequency(self, caf: CohortAlleleFrequencyStudyResult) -> None:
"""Add allele frequency data to the database. Will skip conflicts.

:param caf: Cohort allele frequency study result object to insert into the DB
"""
48 changes: 48 additions & 0 deletions src/anyvlm/storage/mapper_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Central registry for all object mappers."""

from types import MappingProxyType
from typing import TypeVar

from ga4gh.va_spec.base import CohortAlleleFrequencyStudyResult

from anyvlm.storage import orm
from anyvlm.storage.mappers import AlleleFrequencyMapper, BaseMapper

T = TypeVar("T")


class MapperRegistry:
"""Central registry for all object mappers."""

va_model_to_db_mapping: MappingProxyType = MappingProxyType(
{CohortAlleleFrequencyStudyResult: orm.AlleleFrequencyData}
)

_mappers: MappingProxyType[type, BaseMapper] = MappingProxyType(
{orm.AlleleFrequencyData: AlleleFrequencyMapper()}
)

def get_mapper(self, entity_type: type[T]) -> BaseMapper:
"""Get mapper for the given entity type."""
mapper = self._mappers.get(entity_type)
if mapper is None:
raise ValueError(f"No mapper registered for type: {entity_type}")
return mapper

def from_db_entity(self, db_entity): # noqa: ANN201, ANN001
"""Convert any DB entity to its corresponding VA-Spec model."""
mapper = self.get_mapper(type(db_entity))
return mapper.from_db_entity(db_entity)

def to_db_entity(self, va_model) -> orm.Base: # noqa: ANN001
"""Convert any VA-Spec model to its corresponding DB entity."""
db_type = self.va_model_to_db_mapping.get(type(va_model))
if db_type is None:
raise ValueError(f"No DB entity type mapped for VA model: {type(va_model)}")

mapper = self.get_mapper(db_type)
return mapper.to_db_entity(va_model)


# Global registry instance
mapper_registry = MapperRegistry()
Loading