From 9f6f35efc12626b9d38f0be551d32d339ae913f1 Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:16:52 +0200 Subject: [PATCH 1/6] feat: init fastapi app --- .gitignore | 2 + .python-version | 1 + README.md | 22 +++++++- oc4ids_datastore_api/__init__.py | 0 oc4ids_datastore_api/main.py | 9 +++ pyproject.toml | 12 ++++ requirements_dev.txt | 97 ++++++++++++++++++++++++++++++++ 7 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .python-version create mode 100644 oc4ids_datastore_api/__init__.py create mode 100644 oc4ids_datastore_api/main.py create mode 100644 pyproject.toml create mode 100644 requirements_dev.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..033df5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.venv +__pycache__ diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/README.md b/README.md index 93db623..b596741 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# oc4ids-datastore-api \ No newline at end of file +# OC4IDS Datastore API + +## Local Development + +### Prerequisites + +- Python 3.12 + +### Install Python requirements + +``` +python -m venv .venv +source .venv/bin/activate +pip install -r requirements_dev.txt +``` + +### Run app + +``` +fastapi dev oc4ids_datastore_api/main.py +``` diff --git a/oc4ids_datastore_api/__init__.py b/oc4ids_datastore_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/oc4ids_datastore_api/main.py b/oc4ids_datastore_api/main.py new file mode 100644 index 0000000..cdc7f32 --- /dev/null +++ b/oc4ids_datastore_api/main.py @@ -0,0 +1,9 @@ +from fastapi import FastAPI + + +app = FastAPI() + + +@app.get("/") +def index(): + return "Hello, World!" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..5da0d14 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "oc4ids-datastore-api" +description = "OC4IDS Datastore API" +version = "0.1.0" +readme = "README.md" +dependencies = [ + "fastapi[standard]" +] diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 0000000..bc71bf7 --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,97 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --extra=dev --output-file=requirements_dev.txt pyproject.toml +# +annotated-types==0.7.0 + # via pydantic +anyio==4.8.0 + # via + # httpx + # starlette + # watchfiles +certifi==2025.1.31 + # via + # httpcore + # httpx +click==8.1.8 + # via + # rich-toolkit + # typer + # uvicorn +dnspython==2.7.0 + # via email-validator +email-validator==2.2.0 + # via fastapi +fastapi[standard]==0.115.8 + # via oc4ids-datastore-api (pyproject.toml) +fastapi-cli[standard]==0.0.7 + # via fastapi +h11==0.14.0 + # via + # httpcore + # uvicorn +httpcore==1.0.7 + # via httpx +httptools==0.6.4 + # via uvicorn +httpx==0.28.1 + # via fastapi +idna==3.10 + # via + # anyio + # email-validator + # httpx +jinja2==3.1.5 + # via fastapi +markdown-it-py==3.0.0 + # via rich +markupsafe==3.0.2 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +pydantic==2.10.6 + # via fastapi +pydantic-core==2.27.2 + # via pydantic +pygments==2.19.1 + # via rich +python-dotenv==1.0.1 + # via uvicorn +python-multipart==0.0.20 + # via fastapi +pyyaml==6.0.2 + # via uvicorn +rich==13.9.4 + # via + # rich-toolkit + # typer +rich-toolkit==0.13.2 + # via fastapi-cli +shellingham==1.5.4 + # via typer +sniffio==1.3.1 + # via anyio +starlette==0.45.3 + # via fastapi +typer==0.15.1 + # via fastapi-cli +typing-extensions==4.12.2 + # via + # anyio + # fastapi + # pydantic + # pydantic-core + # rich-toolkit + # typer +uvicorn[standard]==0.34.0 + # via + # fastapi + # fastapi-cli +uvloop==0.21.0 + # via uvicorn +watchfiles==1.0.4 + # via uvicorn +websockets==14.2 + # via uvicorn From bb7f1c34fa1a1f4f058367ab5c6c5e2ae618a8ee Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:53:42 +0200 Subject: [PATCH 2/6] feat: add linting and type checking --- README.md | 9 +++++++++ oc4ids_datastore_api/main.py | 3 +-- pyproject.toml | 18 ++++++++++++++++++ requirements_dev.txt | 30 ++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b596741..38a9ff5 100644 --- a/README.md +++ b/README.md @@ -19,3 +19,12 @@ pip install -r requirements_dev.txt ``` fastapi dev oc4ids_datastore_api/main.py ``` + +### Run linting and type checking + +``` +black oc4ids_datastore_api/ +isort oc4ids_datastore_api/ +flake8 oc4ids_datastore_api/ +mypy oc4ids_datastore_api/ +``` diff --git a/oc4ids_datastore_api/main.py b/oc4ids_datastore_api/main.py index cdc7f32..a223e95 100644 --- a/oc4ids_datastore_api/main.py +++ b/oc4ids_datastore_api/main.py @@ -1,9 +1,8 @@ from fastapi import FastAPI - app = FastAPI() @app.get("/") -def index(): +def index() -> str: return "Hello, World!" diff --git a/pyproject.toml b/pyproject.toml index 5da0d14..25be13f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,3 +10,21 @@ readme = "README.md" dependencies = [ "fastapi[standard]" ] + +[project.optional-dependencies] +dev = [ + "black", + "isort", + "flake8", + "Flake8-pyproject", + "mypy", +] + +[tool.isort] +profile = "black" + +[tool.flake8] +max-line-length = 88 + +[tool.mypy] +strict = true diff --git a/requirements_dev.txt b/requirements_dev.txt index bc71bf7..03e7214 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -11,12 +11,15 @@ anyio==4.8.0 # httpx # starlette # watchfiles +black==25.1.0 + # via oc4ids-datastore-api (pyproject.toml) certifi==2025.1.31 # via # httpcore # httpx click==8.1.8 # via + # black # rich-toolkit # typer # uvicorn @@ -28,6 +31,12 @@ fastapi[standard]==0.115.8 # via oc4ids-datastore-api (pyproject.toml) fastapi-cli[standard]==0.0.7 # via fastapi +flake8==7.1.1 + # via + # flake8-pyproject + # oc4ids-datastore-api (pyproject.toml) +flake8-pyproject==1.2.3 + # via oc4ids-datastore-api (pyproject.toml) h11==0.14.0 # via # httpcore @@ -43,18 +52,38 @@ idna==3.10 # anyio # email-validator # httpx +isort==6.0.0 + # via oc4ids-datastore-api (pyproject.toml) jinja2==3.1.5 # via fastapi markdown-it-py==3.0.0 # via rich markupsafe==3.0.2 # via jinja2 +mccabe==0.7.0 + # via flake8 mdurl==0.1.2 # via markdown-it-py +mypy==1.15.0 + # via oc4ids-datastore-api (pyproject.toml) +mypy-extensions==1.0.0 + # via + # black + # mypy +packaging==24.2 + # via black +pathspec==0.12.1 + # via black +platformdirs==4.3.6 + # via black +pycodestyle==2.12.1 + # via flake8 pydantic==2.10.6 # via fastapi pydantic-core==2.27.2 # via pydantic +pyflakes==3.2.0 + # via flake8 pygments==2.19.1 # via rich python-dotenv==1.0.1 @@ -81,6 +110,7 @@ typing-extensions==4.12.2 # via # anyio # fastapi + # mypy # pydantic # pydantic-core # rich-toolkit From f3cecded6961aeec614c2be7f2bb04b3d0b00ede Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:43:49 +0200 Subject: [PATCH 3/6] feat: implement get_datasets endpoint --- README.md | 10 ++++++++++ oc4ids_datastore_api/database.py | 13 +++++++++++++ oc4ids_datastore_api/main.py | 9 ++++++--- oc4ids_datastore_api/models.py | 17 +++++++++++++++++ oc4ids_datastore_api/schemas.py | 25 +++++++++++++++++++++++++ oc4ids_datastore_api/services.py | 23 +++++++++++++++++++++++ pyproject.toml | 4 +++- requirements_dev.txt | 11 ++++++++++- 8 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 oc4ids_datastore_api/database.py create mode 100644 oc4ids_datastore_api/models.py create mode 100644 oc4ids_datastore_api/schemas.py create mode 100644 oc4ids_datastore_api/services.py diff --git a/README.md b/README.md index 38a9ff5..43a39c2 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,22 @@ source .venv/bin/activate pip install -r requirements_dev.txt ``` +### Set database enrivonment variable + +``` +export DATABASE_URL="postgresql://oc4ids_datastore_read_only@localhost/oc4ids_datastore" +``` + ### Run app ``` fastapi dev oc4ids_datastore_api/main.py ``` +### View the OpenAPI schema + +While the app is running, go to `http://127.0.0.1:8000/docs/` + ### Run linting and type checking ``` diff --git a/oc4ids_datastore_api/database.py b/oc4ids_datastore_api/database.py new file mode 100644 index 0000000..196dc41 --- /dev/null +++ b/oc4ids_datastore_api/database.py @@ -0,0 +1,13 @@ +import os +from typing import Sequence + +from sqlmodel import Session, create_engine, select + +from oc4ids_datastore_api.models import DatasetSQLModel + +engine = create_engine(os.environ["DATABASE_URL"]) + + +def fetch_all_datasets() -> Sequence[DatasetSQLModel]: + with Session(engine) as session: + return session.exec(select(DatasetSQLModel)).all() diff --git a/oc4ids_datastore_api/main.py b/oc4ids_datastore_api/main.py index a223e95..3425f8e 100644 --- a/oc4ids_datastore_api/main.py +++ b/oc4ids_datastore_api/main.py @@ -1,8 +1,11 @@ from fastapi import FastAPI +from oc4ids_datastore_api.schemas import Dataset +from oc4ids_datastore_api.services import get_all_datasets + app = FastAPI() -@app.get("/") -def index() -> str: - return "Hello, World!" +@app.get("/datasets") +def get_datasets() -> list[Dataset]: + return get_all_datasets() diff --git a/oc4ids_datastore_api/models.py b/oc4ids_datastore_api/models.py new file mode 100644 index 0000000..1386b74 --- /dev/null +++ b/oc4ids_datastore_api/models.py @@ -0,0 +1,17 @@ +import datetime + +from sqlmodel import Field, SQLModel + + +class DatasetSQLModel(SQLModel, table=True): + __tablename__ = "dataset" + + dataset_id: str = Field(primary_key=True) + source_url: str + publisher_name: str + license_url: str | None + license_name: str | None + json_url: str | None + csv_url: str | None + xlsx_url: str | None + updated_at: datetime.datetime diff --git a/oc4ids_datastore_api/schemas.py b/oc4ids_datastore_api/schemas.py new file mode 100644 index 0000000..0486d12 --- /dev/null +++ b/oc4ids_datastore_api/schemas.py @@ -0,0 +1,25 @@ +import datetime + +from pydantic import BaseModel + + +class Publisher(BaseModel): + name: str + + +class License(BaseModel): + url: str | None + name: str | None + + +class Download(BaseModel): + format: str + url: str + + +class Dataset(BaseModel): + loaded_at: datetime.datetime + source_url: str + publisher: Publisher + license: License + downloads: list[Download] diff --git a/oc4ids_datastore_api/services.py b/oc4ids_datastore_api/services.py new file mode 100644 index 0000000..b5200c0 --- /dev/null +++ b/oc4ids_datastore_api/services.py @@ -0,0 +1,23 @@ +from oc4ids_datastore_api.database import fetch_all_datasets +from oc4ids_datastore_api.models import DatasetSQLModel +from oc4ids_datastore_api.schemas import Dataset, Download, License, Publisher + + +def _transform_dataset(dataset: DatasetSQLModel) -> Dataset: + downloads = [ + Download(format="json", url=(dataset.json_url or "")), + Download(format="csv", url=(dataset.csv_url or "")), + Download(format="xlsx", url=(dataset.xlsx_url or "")), + ] + return Dataset( + loaded_at=dataset.updated_at, + source_url=dataset.source_url, + publisher=Publisher(name=dataset.publisher_name), + license=License(url=dataset.license_url, name=dataset.license_name), + downloads=downloads, + ) + + +def get_all_datasets() -> list[Dataset]: + datasets = fetch_all_datasets() + return [_transform_dataset(dataset) for dataset in datasets] diff --git a/pyproject.toml b/pyproject.toml index 25be13f..783c65f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,9 @@ description = "OC4IDS Datastore API" version = "0.1.0" readme = "README.md" dependencies = [ - "fastapi[standard]" + "fastapi[standard]", + "psycopg2", + "sqlmodel", ] [project.optional-dependencies] diff --git a/requirements_dev.txt b/requirements_dev.txt index 03e7214..1a4c1ae 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -76,10 +76,14 @@ pathspec==0.12.1 # via black platformdirs==4.3.6 # via black +psycopg2==2.9.10 + # via oc4ids-datastore-api (pyproject.toml) pycodestyle==2.12.1 # via flake8 pydantic==2.10.6 - # via fastapi + # via + # fastapi + # sqlmodel pydantic-core==2.27.2 # via pydantic pyflakes==3.2.0 @@ -102,6 +106,10 @@ shellingham==1.5.4 # via typer sniffio==1.3.1 # via anyio +sqlalchemy==2.0.37 + # via sqlmodel +sqlmodel==0.0.22 + # via oc4ids-datastore-api (pyproject.toml) starlette==0.45.3 # via fastapi typer==0.15.1 @@ -114,6 +122,7 @@ typing-extensions==4.12.2 # pydantic # pydantic-core # rich-toolkit + # sqlalchemy # typer uvicorn[standard]==0.34.0 # via From b26b5a64a9e80e8322c07484d2bd06893727f100 Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:21:50 +0200 Subject: [PATCH 4/6] feat: don't include download formats with no URL --- README.md | 8 ++-- oc4ids_datastore_api/services.py | 9 ++-- pyproject.toml | 6 +++ requirements_dev.txt | 14 +++++- tests/test_services.py | 74 ++++++++++++++++++++++++++++++++ 5 files changed, 103 insertions(+), 8 deletions(-) create mode 100644 tests/test_services.py diff --git a/README.md b/README.md index 43a39c2..100d2fb 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ While the app is running, go to `http://127.0.0.1:8000/docs/` ### Run linting and type checking ``` -black oc4ids_datastore_api/ -isort oc4ids_datastore_api/ -flake8 oc4ids_datastore_api/ -mypy oc4ids_datastore_api/ +black oc4ids_datastore_api/ tests/ +isort oc4ids_datastore_api/ tests/ +flake8 oc4ids_datastore_api/ tests/ +mypy oc4ids_datastore_api/ tests/ ``` diff --git a/oc4ids_datastore_api/services.py b/oc4ids_datastore_api/services.py index b5200c0..76a96e0 100644 --- a/oc4ids_datastore_api/services.py +++ b/oc4ids_datastore_api/services.py @@ -4,10 +4,13 @@ def _transform_dataset(dataset: DatasetSQLModel) -> Dataset: + download_urls = { + "json": dataset.json_url, + "csv": dataset.csv_url, + "xlsx": dataset.xlsx_url, + } downloads = [ - Download(format="json", url=(dataset.json_url or "")), - Download(format="csv", url=(dataset.csv_url or "")), - Download(format="xlsx", url=(dataset.xlsx_url or "")), + Download(format=format, url=url) for format, url in download_urls.items() if url ] return Dataset( loaded_at=dataset.updated_at, diff --git a/pyproject.toml b/pyproject.toml index 783c65f..8e174ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,8 @@ dev = [ "flake8", "Flake8-pyproject", "mypy", + "pytest", + "pytest-mock", ] [tool.isort] @@ -30,3 +32,7 @@ max-line-length = 88 [tool.mypy] strict = true + +[tool.pytest.ini_options] +log_cli = true +log_cli_level = "INFO" diff --git a/requirements_dev.txt b/requirements_dev.txt index 1a4c1ae..770111a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -52,6 +52,8 @@ idna==3.10 # anyio # email-validator # httpx +iniconfig==2.0.0 + # via pytest isort==6.0.0 # via oc4ids-datastore-api (pyproject.toml) jinja2==3.1.5 @@ -71,11 +73,15 @@ mypy-extensions==1.0.0 # black # mypy packaging==24.2 - # via black + # via + # black + # pytest pathspec==0.12.1 # via black platformdirs==4.3.6 # via black +pluggy==1.5.0 + # via pytest psycopg2==2.9.10 # via oc4ids-datastore-api (pyproject.toml) pycodestyle==2.12.1 @@ -90,6 +96,12 @@ pyflakes==3.2.0 # via flake8 pygments==2.19.1 # via rich +pytest==8.3.4 + # via + # oc4ids-datastore-api (pyproject.toml) + # pytest-mock +pytest-mock==3.14.0 + # via oc4ids-datastore-api (pyproject.toml) python-dotenv==1.0.1 # via uvicorn python-multipart==0.0.20 diff --git a/tests/test_services.py b/tests/test_services.py new file mode 100644 index 0000000..4e290e6 --- /dev/null +++ b/tests/test_services.py @@ -0,0 +1,74 @@ +import datetime + +from pytest_mock import MockerFixture + +from oc4ids_datastore_api.models import DatasetSQLModel +from oc4ids_datastore_api.schemas import Dataset, Download, License, Publisher +from oc4ids_datastore_api.services import get_all_datasets + + +def test_get_all_datasets(mocker: MockerFixture) -> None: + patch_fetch_all_datasets = mocker.patch( + "oc4ids_datastore_api.services.fetch_all_datasets" + ) + now = datetime.datetime.now() + dataset_sql_model = DatasetSQLModel( + dataset_id="test_dataset", + source_url="https://test-dataset.json", + publisher_name="test_publisher", + license_url="https://license.com", + license_name="License", + json_url="https://downloads/test_dataset.json", + csv_url="https://downloads/test_dataset.csv", + xlsx_url="https://downloads/test_dataset.xlsx", + updated_at=now, + ) + patch_fetch_all_datasets.return_value = [dataset_sql_model] + + datasets = get_all_datasets() + + expected_dataset = Dataset( + loaded_at=now, + source_url="https://test-dataset.json", + publisher=Publisher(name="test_publisher"), + license=License(name="License", url="https://license.com"), + downloads=[ + Download(format="json", url="https://downloads/test_dataset.json"), + Download(format="csv", url="https://downloads/test_dataset.csv"), + Download(format="xlsx", url="https://downloads/test_dataset.xlsx"), + ], + ) + assert datasets == [expected_dataset] + + +def test_get_all_datasets_missing_download_formats(mocker: MockerFixture) -> None: + patch_fetch_all_datasets = mocker.patch( + "oc4ids_datastore_api.services.fetch_all_datasets" + ) + now = datetime.datetime.now() + dataset_sql_model = DatasetSQLModel( + dataset_id="test_dataset", + source_url="https://test-dataset.json", + publisher_name="test_publisher", + license_url="https://license.com", + license_name="License", + json_url="https://downloads/test_dataset.json", + csv_url=None, + xlsx_url=None, + updated_at=now, + ) + patch_fetch_all_datasets.return_value = [dataset_sql_model] + + datasets = get_all_datasets() + + expected_dataset = Dataset( + loaded_at=now, + source_url="https://test-dataset.json", + publisher=Publisher(name="test_publisher"), + license=License(name="License", url="https://license.com"), + downloads=[ + Download(format="json", url="https://downloads/test_dataset.json"), + ], + ) + + assert datasets == [expected_dataset] From ac0bcfccf854ed3f98b53d69708813a3b97e6198 Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:23:18 +0200 Subject: [PATCH 5/6] ci: run linting and testing in CI --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f2ac72 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI +on: [push] + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dev requirements + run: pip install -r requirements_dev.txt + - name: Install local package + run: pip install . + - name: Check black + run: black --check oc4ids_datastore_api/ tests/ + - name: Check isort + run: isort --check-only oc4ids_datastore_api/ tests/ + - name: Check flake8 + run: flake8 oc4ids_datastore_api/ tests/ + - name: Check mypy + run: mypy oc4ids_datastore_api/ tests/ + - name: Run tests + run: pytest From 8088d96742c2555942de4d19e40a35a1e5936a1e Mon Sep 17 00:00:00 2001 From: Tilly Woodfield <22456167+tillywoodfield@users.noreply.github.com> Date: Thu, 6 Feb 2025 16:28:08 +0200 Subject: [PATCH 6/6] fix: delay reading environment variable to enable local development without database --- README.md | 2 ++ oc4ids_datastore_api/database.py | 12 ++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 100d2fb..8510221 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ pip install -r requirements_dev.txt ### Set database enrivonment variable +With a read-only user, set the path to the already existing database, which is created by [oc4ids-datastore-pipeline](https://github.com/OpenDataServices/oc4ids-datastore-pipeline). + ``` export DATABASE_URL="postgresql://oc4ids_datastore_read_only@localhost/oc4ids_datastore" ``` diff --git a/oc4ids_datastore_api/database.py b/oc4ids_datastore_api/database.py index 196dc41..9e2445c 100644 --- a/oc4ids_datastore_api/database.py +++ b/oc4ids_datastore_api/database.py @@ -1,13 +1,21 @@ import os from typing import Sequence +from sqlalchemy import Engine from sqlmodel import Session, create_engine, select from oc4ids_datastore_api.models import DatasetSQLModel -engine = create_engine(os.environ["DATABASE_URL"]) +_engine = None + + +def get_engine() -> Engine: + global _engine + if _engine is None: + _engine = create_engine(os.environ["DATABASE_URL"]) + return _engine def fetch_all_datasets() -> Sequence[DatasetSQLModel]: - with Session(engine) as session: + with Session(get_engine()) as session: return session.exec(select(DatasetSQLModel)).all()