Skip to content
Draft
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
15 changes: 14 additions & 1 deletion src/onedep_lib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,28 @@
from onedep_lib.auths.token import TokenStore
from onedep_lib.auths.types import AuthProvider
from onedep_lib.checks.report import CheckIssue, CheckReport, CheckSeverity, CifLocation
from onedep_lib.dsp import Deposition, deposit_init, deposit_resume, list_sessions
from onedep_lib.dsp import (
Deposition,
deposit_init,
deposit_resume,
get_session,
get_session_metadata,
list_session_metadata,
list_sessions,
)
from onedep_lib.enums import Country, EMSubType, ExperimentType, FileType
from onedep_lib.exceptions import ApiError, DepositApiException, OneDepError
from onedep_lib.session.models import SessionSummary

__all__ = [
# factories / facade
"deposit_init",
"deposit_resume",
"get_session",
"get_session_metadata",
"list_session_metadata",
"list_sessions",
"SessionSummary",
"Deposition",
# check result types
"CheckReport",
Expand Down
79 changes: 68 additions & 11 deletions src/onedep_lib/dsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from onedep_lib.apis.deposit.client import HttpApiClient
from onedep_lib.apis.deposit.models import DepositError, DepositStatus, Experiment
from onedep_lib.apis.deposit.types import ApiClient
from onedep_lib.auths.token import TokenStore
from onedep_lib.checks.report import CheckReport
from onedep_lib.checks.runner import CheckRunner
from onedep_lib.checks.types import CheckRunner as CheckRunnerProtocol
Expand All @@ -16,9 +17,8 @@
from onedep_lib.schemas.local import LocalSchemaProvider
from onedep_lib.schemas.remote import RemoteSchemaProvider
from onedep_lib.session.json_store import JsonSessionStore
from onedep_lib.session.models import LocalFile, LocalSession
from onedep_lib.session.models import LocalFile, LocalSession, SessionSummary
from onedep_lib.session.types import SessionStore
from onedep_lib.auths.token import TokenStore


def _md5_of_file(path: Path, chunk_size: int = 1 << 20) -> str:
Expand All @@ -29,6 +29,21 @@ def _md5_of_file(path: Path, chunk_size: int = 1 << 20) -> str:
return h.hexdigest()


def _session_base_dir(config: DepositConfig, base_dir: Path | None = None) -> Path:
return Path(base_dir or config.session_dir)


def _summarize_session(session: LocalSession, files: list[LocalFile]) -> SessionSummary:
return SessionSummary(
session_id=session.session_id,
remote_dep_id=session.remote_dep_id,
experiment_type=session.experiment_type,
created_at=session.created_at,
updated_at=session.updated_at or session.created_at,
file_count=len(files),
)


def list_sessions(base_dir: Path | None = None) -> list[tuple[LocalSession, list[LocalFile]]]:
"""Return all local sessions with their registered files, newest first."""
config = DepositConfig.load()
Expand All @@ -53,6 +68,41 @@ def list_sessions(base_dir: Path | None = None) -> list[tuple[LocalSession, list
return results


def list_session_metadata(base_dir: Path | None = None) -> list[SessionSummary]:
"""Return metadata summaries for all local sessions, newest first."""
return [_summarize_session(session, files) for session, files in list_sessions(base_dir=base_dir)]
Comment thread
Copilot marked this conversation as resolved.


def get_session(session_id: str, base_dir: Path | None = None) -> tuple[LocalSession, list[LocalFile]]:
"""Return one local session and its registered files.

Args:
session_id: Local session UUID.
base_dir: Optional session storage directory. Defaults to the configured
OneDep Lib session directory.

Raises:
KeyError: If no local session with the given session_id exists.
"""
config = DepositConfig.load()
_base = base_dir or config.session_dir
json_path = _base / session_id / "session.json"
if not json_path.exists():
raise KeyError(f"No session found for {session_id!r}")

store = JsonSessionStore(session_id, base_dir=_base)
try:
return store.get_session(), store.get_all_files()
finally:
store.close()


def get_session_metadata(session_id: str, base_dir: Path | None = None) -> SessionSummary:
"""Return a metadata summary for one local session."""
session, files = get_session(session_id, base_dir=base_dir)
return _summarize_session(session, files)


def deposit_init(
email: str,
users: list[str],
Expand All @@ -61,7 +111,7 @@ def deposit_init(
em_subtype: EMSubType | None = None,
coordinates: bool | None = None,
config: DepositConfig | None = None,
_base_dir: Path | None = None,
base_dir: Path | None = None,
_api_client: ApiClient | None = None,
_check_runner: CheckRunnerProtocol | None = None,
) -> Deposition:
Expand All @@ -75,7 +125,8 @@ def deposit_init(
em_subtype: EM experiment subtype (can be set later via set_em_params).
coordinates: Whether coordinates are being deposited (can be set later).
config: Optional pre-built DepositConfig; loaded from default sources if None.
_base_dir: Override session storage directory (for testing only).
base_dir: Optional session storage directory. Defaults to the configured
OneDep Lib session directory.
_api_client: Override API client (for testing only).
_check_runner: Override check runner (for testing only).

Expand All @@ -84,8 +135,8 @@ def deposit_init(
"""
config = config or DepositConfig.load()
session_id = str(uuid.uuid4())
base_dir = _base_dir or config.session_dir
store: SessionStore = JsonSessionStore(session_id, base_dir=base_dir)
session_base_dir = _session_base_dir(config, base_dir=base_dir)
store: SessionStore = JsonSessionStore(session_id, base_dir=session_base_dir)
api_client: ApiClient = _api_client or HttpApiClient(config, auth_provider=TokenStore(config))
check_runner: CheckRunnerProtocol = _check_runner or CheckRunner(
LocalSchemaProvider(config.local_schema_cache_dir)
Expand All @@ -99,6 +150,7 @@ def deposit_init(
country=country,
experiment_type=experiment_type,
created_at=datetime.now(),
updated_at=datetime.now(),
em_subtype=em_subtype,
coordinates=coordinates,
)
Expand All @@ -109,7 +161,7 @@ def deposit_init(
def deposit_resume(
session_id: str,
config: DepositConfig | None = None,
_base_dir: Path | None = None,
base_dir: Path | None = None,
_api_client: ApiClient | None = None,
_check_runner: CheckRunnerProtocol | None = None,
) -> Deposition:
Expand All @@ -118,7 +170,8 @@ def deposit_resume(
Args:
session_id: The session_id returned by a previous deposit_init() call.
config: Optional pre-built DepositConfig; loaded from default sources if None.
_base_dir: Override session storage directory (for testing only).
base_dir: Optional session storage directory. Defaults to the configured
OneDep Lib session directory.
_api_client: Override API client (for testing only).
_check_runner: Override check runner (for testing only).

Expand All @@ -129,16 +182,20 @@ def deposit_resume(
KeyError: If no session with the given session_id exists.
"""
config = config or DepositConfig.load()
base_dir = _base_dir or config.session_dir
store: SessionStore = JsonSessionStore(session_id, base_dir=base_dir)
session_base_dir = _session_base_dir(config, base_dir=base_dir)
store: SessionStore = JsonSessionStore(session_id, base_dir=session_base_dir)
store.get_session() # raises KeyError if not found
Comment on lines 184 to 187

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Alex, try the following after line 194 (session_base_dir = ...)

    if session_id not in (s.session_id for s, _ in list_sessions(base_dir=session_base_dir)):
        raise KeyError(f"Session with id {session_id} not found.")

api_client: ApiClient = _api_client or HttpApiClient(config, auth_provider=TokenStore(config))
check_runner: CheckRunnerProtocol = _check_runner or CheckRunner(
LocalSchemaProvider(config.local_schema_cache_dir)
if config.fetch_local_schema
else RemoteSchemaProvider(config.schema_base_url, config.schema_cache_dir)
)
return Deposition(store=store, api_client=api_client, check_runner=check_runner)
return Deposition(
store=store,
api_client=api_client,
check_runner=check_runner,
)


class Deposition:
Expand Down
11 changes: 11 additions & 0 deletions src/onedep_lib/session/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ class LocalSession:
country: Country
experiment_type: ExperimentType | None
created_at: datetime
updated_at: datetime | None = None
remote_dep_id: str | None = None
em_subtype: EMSubType | None = None
coordinates: bool | None = None


@dataclass
class SessionSummary:
session_id: str
remote_dep_id: str | None = None
experiment_type: ExperimentType | None = None
created_at: datetime | None = None
updated_at: datetime | None = None
file_count: int = 0
66 changes: 60 additions & 6 deletions tests/unit/test_deposition_facade.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import pytest
from pathlib import Path

from onedep_lib.checks.report import CheckReport
from onedep_lib.dsp import deposit_init, deposit_resume
from onedep_lib.dsp import (
deposit_init,
deposit_resume,
get_session,
get_session_metadata,
list_session_metadata,
)
from onedep_lib.enums import Country, ExperimentType, FileType
from tests.unit.apis.deposit.test_stub_api_client import StubApiClient

Expand Down Expand Up @@ -37,7 +43,7 @@ def dep(tmp_path, stub_api):
users=["0000-0001-2345-6789"],
country=Country.USA,
experiment_type=ExperimentType.XRAY,
_base_dir=tmp_path,
base_dir=tmp_path,
_api_client=stub_api,
_check_runner=StubCheckRunner(),
)
Expand Down Expand Up @@ -78,7 +84,7 @@ def test_deposit_without_experiment_type_raises(tmp_path):
email="test@example.com",
users=[],
country=Country.USA,
_base_dir=tmp_path,
base_dir=tmp_path,
_api_client=StubApiClient(),
_check_runner=StubCheckRunner(),
)
Expand Down Expand Up @@ -109,21 +115,69 @@ def test_deposit_resume_restores_session(dep, tmp_path, stub_api):
dep.close()
resumed = deposit_resume(
session_id,
_base_dir=tmp_path,
base_dir=tmp_path,
_api_client=stub_api,
_check_runner=StubCheckRunner(),
)
assert resumed.session_id == session_id
resumed.close()


def test_deposit_resume_accepts_public_base_dir(tmp_path, stub_api):
dep = deposit_init(
email="test@example.com",
users=[],
country=Country.USA,
experiment_type=ExperimentType.XRAY,
base_dir=tmp_path,
_api_client=stub_api,
_check_runner=StubCheckRunner(),
)
session_id = dep.session_id
dep.close()

resumed = deposit_resume(
session_id,
base_dir=tmp_path,
_api_client=stub_api,
_check_runner=StubCheckRunner(),
)
assert resumed.session_id == session_id
resumed.close()


def test_session_metadata_entry_points(dep, tmp_path):
test_file = tmp_path / "coords.cif"
test_file.write_text("data_test")
file_id = dep.add_file(str(test_file), FileType.MMCIF_COORD)
session_id = dep.session_id
dep.close()

session, files = get_session(session_id, base_dir=tmp_path)
assert session.session_id == session_id
assert session.email == "test@example.com"
assert [file.file_id for file in files] == [file_id]

summary = get_session_metadata(session_id, base_dir=tmp_path)
assert summary.session_id == session_id
assert summary.experiment_type == ExperimentType.XRAY
assert summary.file_count == 1
assert summary.created_at is not None
assert summary.updated_at is not None

summaries = list_session_metadata(base_dir=tmp_path)
assert len(summaries) == 1
assert summaries[0].session_id == session_id
assert summaries[0].file_count == 1


def test_context_manager(tmp_path, stub_api):
with deposit_init(
email="test@example.com",
users=[],
country=Country.USA,
experiment_type=ExperimentType.XRAY,
_base_dir=tmp_path,
base_dir=tmp_path,
_api_client=stub_api,
_check_runner=StubCheckRunner(),
) as dep:
Expand Down