Skip to content
Snippets Groups Projects
Commit 18dedc99 authored by Pierre Chanial's avatar Pierre Chanial
Browse files

API GET dataset.

parent 0bcc0abc
No related branches found
No related tags found
1 merge request!10Resolve "API to get a dataset"
Pipeline #15496 canceled
"""Definitions of the endpoints related the datasets."""
import logging
from fastapi import APIRouter
from fastapi import APIRouter, HTTPException, status
from sqlalchemy import text
from sqlalchemy.engine import Connection
from ...helpers import begin_transaction
from ...schemas import Dataset
......@@ -12,13 +13,23 @@ router = APIRouter()
@router.get('/projects/{project}/datasets', summary='Lists the datasets of a project.')
def list_datasets(project: str) -> list[str]:
def list_datasets(project: str) -> list[Dataset]:
"""Lists the datasets belonging to a project."""
with begin_transaction(project) as conn:
stmt = text('SELECT schema_name FROM information_schema.schemata')
result = conn.execute(stmt)
schemas = result.scalars().all()
return [_ for _ in schemas if _filter_schema(_)]
schemas = result.scalars().all()
datasets = [
Dataset(
name=f'{project}.{_}',
description=_get_dataset_description_postgresql(conn, _),
)
for _ in schemas
if _filter_schema(_)
]
return datasets
def _filter_schema(schema: str) -> bool:
......@@ -30,9 +41,49 @@ def _filter_schema(schema: str) -> bool:
@router.post('/projects/{project}/datasets', summary='Creates a dataset in a project.')
def post_dataset(project: str, dataset: Dataset) -> None:
def post_dataset(project: str, dataset: Dataset) -> Dataset:
"""Creates a dataset in a project."""
with begin_transaction(project) as conn:
_, schema_id = dataset.name.split('.', 1)
stmt = text(f'CREATE SCHEMA {schema_id}')
*_, schema = dataset.name.split('.', 1)
stmt = text(f'CREATE SCHEMA {schema}')
conn.execute(stmt)
_set_dataset_description_postgresql(conn, schema, dataset.description)
return Dataset(name=f'{project}.{schema}', description=dataset.description)
@router.get(
'/projects/{project}/datasets/{dataset}', summary='Gets a dataset of a project.'
)
def get_dataset(project: str, dataset: str) -> Dataset:
"""Lists the datasets belonging to a project."""
with begin_transaction(project) as conn:
stmt = text(
f"""
SELECT
FROM information_schema.schemata
WHERE schema_name='{dataset}'"""
)
result = conn.execute(stmt).first()
if result is None:
msg = f"The dataset '{dataset}' does not exist in project '{project}'."
raise HTTPException(status.HTTP_404_NOT_FOUND, msg)
description = _get_dataset_description_postgresql(conn, dataset)
return Dataset(
name=f'{project}.{dataset}',
description=description,
)
def _get_dataset_description_postgresql(conn: Connection, dataset: str) -> str:
stmt = text(f"SELECT obj_description(CAST('{dataset}' AS regnamespace))")
return conn.execute(stmt).scalar_one()
def _set_dataset_description_postgresql(
conn: Connection, dataset: str, description: str
) -> None:
stmt = text(f"COMMENT ON SCHEMA {dataset} IS '{description}'")
conn.execute(stmt)
......@@ -5,6 +5,7 @@ An instance of this class can be used to connect the admin or a project database
import collections
from typing import Iterator
from fastapi import HTTPException, status
from sqlalchemy import create_engine
from sqlalchemy.engine import Engine
from sqlalchemy.future import select
......@@ -46,7 +47,12 @@ class ProjectEngines(collections.abc.Mapping):
def _create_project_engine(project_name: str) -> Engine:
"""Factory for the project engines."""
stmt = select(DBProject).where(DBProject.name == project_name)
project = AdminSession().execute(stmt).scalar_one()
project = AdminSession().execute(stmt).scalar_one_or_none()
if project is None:
raise HTTPException(
status.HTTP_404_NOT_FOUND,
f"The project '{project_name}' does not exist.",
)
uri = project.uri
return create_engine(uri, pool_pre_ping=True, future=True)
......
......@@ -6,7 +6,7 @@ from fastapi.testclient import TestClient
from app.main import app
@pytest.fixture(scope='module')
@pytest.fixture(scope='session')
def client() -> Generator[TestClient, None, None]:
with TestClient(app) as c:
yield c
from typing import Generator
import pytest
from fastapi.testclient import TestClient
from app.schemas import Project
from .helpers import stage_project
@pytest.fixture(scope='package')
def project(client: TestClient) -> Generator[Project, None, None]:
yield stage_project(client)
from fastapi.testclient import TestClient
from app.config import settings
from app.schemas import Dataset, Project
from ...helpers import uid
def stage_project(client: TestClient) -> Project:
"""Creates a project in the project database."""
project = Project(
name=uid('project'),
description=uid() + '♩♪♫♬♭♮♯🎼',
)
response = client.post(f'{settings.API_V0_STR}/projects', json=project.dict())
assert response.status_code == 200
return Project(**response.json())
def stage_dataset(client: TestClient, project: str) -> Dataset:
"""Creates a dataset in the project database."""
dataset = Dataset(
name=uid('dataset'),
description=uid() + '🍮🐉α𝒷𝓒ᗪeŦ🎯☹',
)
response = client.post(
f'{settings.API_V0_STR}/projects/{project}/datasets', json=dataset.dict()
)
assert response.status_code == 200
return Dataset(**response.json())
from fastapi.testclient import TestClient
from app.config import settings
from app.schemas import Dataset, Project
from ...helpers import uid
from .helpers import stage_dataset
def test_create_dataset_success(client: TestClient, project: Project) -> None:
dataset_name = uid('dataset')
dataset = Dataset(name=dataset_name, description=uid() + '🍮🐉α𝒷𝓒ᗪeŦ🎯☹')
canonical_name = f'{project.name}.{dataset_name}'
response = client.post(
f'{settings.API_V0_STR}/projects/{project.name}/datasets', json=dataset.dict()
)
assert response.status_code == 200, response.json()['detail']
dataset_post = Dataset(**response.json())
assert dataset_post.name == canonical_name
dataset.name = canonical_name
assert dataset_post == dataset
def test_create_dataset_failure(client: TestClient) -> None:
dataset = Dataset(name=uid('dataset'))
response = client.post(
f'{settings.API_V0_STR}/projects/UNKNOWN/datasets', json=dataset.dict()
)
assert response.status_code == 404
def test_get_dataset_success(client: TestClient, project: Project) -> None:
dataset = stage_dataset(client, project.name)
dataset_name = dataset.name.split('.')[1]
response = client.get(
f'{settings.API_V0_STR}/projects/{project.name}/datasets/{dataset_name}'
)
assert response.status_code == 200
dataset_get = Dataset(**response.json())
assert dataset_get == dataset
def test_get_dataset_failure(client: TestClient, project: Project) -> None:
response = client.get(
f'{settings.API_V0_STR}/projects/{project.name}/datasets/UNKNOWN'
)
assert response.status_code == 404
detail = response.json()['detail']
assert "The dataset 'UNKNOWN' does not exist in project" in detail
def test_list_datasets(client: TestClient, project: Project) -> None:
dataset = stage_dataset(client, project.name)
response = client.get(f'{settings.API_V0_STR}/projects/{project.name}/datasets')
assert response.status_code == 200
serialized_datasets = response.json()
assert dataset.name in (_['name'] for _ in serialized_datasets)
......@@ -3,5 +3,5 @@ from uuid import uuid4
def uid(arg: str = '') -> str:
if arg:
arg += '-'
return arg + str(uuid4())
arg += '_'
return arg + str(uuid4()).replace('-', '_')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment