Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .github/workflows/bar-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:

runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
python-version: [3.10.18, 3.11, 3.12, 3.13]

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,16 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}

# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,6 @@ dmypy.json
output/*
!output
!output/placeholder.txt

# Local sqlite mirrors generated from MySQL dumps
config/databases/*.db
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

[![Website Status](https://img.shields.io/website?url=http%3A%2F%2Fbar.utoronto.ca%2Fapi%2F)](http://bar.utoronto.ca/api/) ![GitHub repo size](https://img.shields.io/github/repo-size/BioAnalyticResource/BAR_API) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Documentation Status](https://readthedocs.org/projects/bar-api/badge/?version=latest)](https://bar-api.readthedocs.io/en/latest/?badge=latest)

This is the official repository for the Bio-Analytic Resource API. The API documentation can be found [here](https://bar-api.readthedocs.io/en/latest/).
This is the official repository for the Bio-Analytic Resource API. The API documentation can be found [here](https://bar-api.readthedocs.io/en/latest/).
2 changes: 2 additions & 0 deletions api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def create_app():
from api.resources.efp_image import efp_image
from api.resources.fastpheno import fastpheno
from api.resources.llama3 import llama3
from api.resources.efp_proxy import efp_proxy_ns

bar_api.add_namespace(gene_information)
bar_api.add_namespace(rnaseq_gene_expression)
Expand All @@ -78,6 +79,7 @@ def create_app():
bar_api.add_namespace(efp_image)
bar_api.add_namespace(fastpheno)
bar_api.add_namespace(llama3)
bar_api.add_namespace(efp_proxy_ns)
bar_api.init_app(bar_app)
return bar_app

Expand Down
15 changes: 0 additions & 15 deletions api/models/arabidopsis_ecotypes.py

This file was deleted.

12 changes: 0 additions & 12 deletions api/models/arachis.py

This file was deleted.

4 changes: 4 additions & 0 deletions api/models/bar_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Bridge file to maintain backward compatibility with imports
from api.utils.bar_utils import BARUtils

__all__ = ['BARUtils']
12 changes: 0 additions & 12 deletions api/models/cannabis.py

This file was deleted.

12 changes: 0 additions & 12 deletions api/models/dna_damage.py

This file was deleted.

88 changes: 88 additions & 0 deletions api/models/efp_dynamic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
Dynamic SQLAlchemy model generation for simple eFP databases.

This module provides runtime generation of SQLAlchemy ORM models from schema
definitions, enabling dynamic database access without hardcoded model classes.
"""

from __future__ import annotations

from typing import Dict

from sqlalchemy import Float, Integer, String, Text
from sqlalchemy.dialects.mysql import INTEGER

from api import db
from api.models.efp_schemas import SIMPLE_EFP_DATABASE_SCHEMAS


def _to_sqla_type(column_spec):
"""
Map a column specification dictionary to a SQLAlchemy column type.

Converts the simple type descriptors used in schema definitions to the
appropriate SQLAlchemy type objects for ORM model generation.

:param column_spec: Column specification with 'type', 'length', and 'unsigned' keys
:type column_spec: Dict[str, Any]
:return: SQLAlchemy column type (String, Integer, Float, or Text)
:rtype: sqlalchemy.types.TypeEngine
:raises ValueError: If column type is not one of: string, integer, float, text

Example::

col_spec = {"type": "string", "length": 24}
sqla_type = _to_sqla_type(col_spec) # Returns String(24)
"""
col_type = column_spec.get("type")
if col_type == "string":
return String(column_spec["length"])
if col_type == "integer":
if column_spec.get("unsigned"):
return INTEGER(unsigned=True)
return Integer
if col_type == "float":
return Float
if col_type == "text":
return Text
raise ValueError(f"Unsupported column type: {col_type}")


def _generate_model(bind_key: str, spec) -> db.Model:
"""
Build a concrete SQLAlchemy model class for the given schema specification.

Dynamically creates an ORM model with the specified table name, bind key,
and columns based on the schema definition. The generated model class can
be used like any Flask-SQLAlchemy model.

:param bind_key: Database bind key (e.g., 'cannabis', 'embryo')
:type bind_key: str
:param spec: Database schema specification from SIMPLE_EFP_DATABASE_SCHEMAS
:type spec: Dict[str, Any]
:return: Dynamically generated SQLAlchemy model class
:rtype: db.Model

Example::

schema = SIMPLE_EFP_DATABASE_SCHEMAS['cannabis']
CannabisModel = _generate_model('cannabis', schema)
# Returns class: CannabisSampleData(db.Model)
"""
attrs = {"__bind_key__": bind_key, "__tablename__": spec["table_name"]}

for column in spec["columns"]:
kwargs = {"nullable": column.get("nullable", True)}
if column.get("primary_key"):
kwargs["primary_key"] = True
attrs[column["name"]] = db.mapped_column(_to_sqla_type(column), **kwargs)

class_name = "".join([part.capitalize() for part in bind_key.split("_")]) + "SampleData"
return type(class_name, (db.Model,), attrs)


SIMPLE_EFP_SAMPLE_MODELS: Dict[str, db.Model] = {
db_name: _generate_model(db_name, spec) for db_name, spec in SIMPLE_EFP_DATABASE_SCHEMAS.items()
}

__all__ = ["SIMPLE_EFP_SAMPLE_MODELS"]
Loading