Skip to content

API Reference

This page documents the public API of onedep_lib — the names exported from onedep_lib.__all__, plus DepositConfig.

Factories and facade

onedep_lib.dsp.deposit_init(email, users, country, config, experiment_type=None, em_subtype=None, coordinates=None, _base_dir=None, _api_client=None, _check_runner=None)

Create a new local deposition session.

Parameters:

Name Type Description Default
email str

Depositor e-mail address.

required
users list[str]

List of ORCID IDs granted access to this deposition.

required
country Country

Depositor country (use the Country enum).

required
config DepositConfig

Pre-built DepositConfig for the target deposition site.

required
experiment_type ExperimentType | None

Experiment type (can be set later via set_experiment_type).

None
em_subtype EMSubType | None

EM experiment subtype (can be set later via set_em_params).

None
coordinates bool | None

Whether coordinates are being deposited (can be set later).

None
_base_dir Path | None

Override session storage directory (for testing only).

None
_api_client ApiClient | None

Override API client (for testing only).

None
_check_runner CheckRunner | None

Override check runner (for testing only).

None

Returns:

Type Description
Deposition

A Deposition object representing the local session.

Source code in src/onedep_lib/dsp.py
def deposit_init(
    email: str,
    users: list[str],
    country: Country,
    config: DepositConfig,
    experiment_type: ExperimentType | None = None,
    em_subtype: EMSubType | None = None,
    coordinates: bool | None = None,
    _base_dir: Path | None = None,
    _api_client: ApiClient | None = None,
    _check_runner: CheckRunnerProtocol | None = None,
) -> Deposition:
    """Create a new local deposition session.

    Args:
        email: Depositor e-mail address.
        users: List of ORCID IDs granted access to this deposition.
        country: Depositor country (use the Country enum).
        config: Pre-built DepositConfig for the target deposition site.
        experiment_type: Experiment type (can be set later via set_experiment_type).
        em_subtype: EM experiment subtype (can be set later via set_em_params).
        coordinates: Whether coordinates are being deposited (can be set later).
        _base_dir: Override session storage directory (for testing only).
        _api_client: Override API client (for testing only).
        _check_runner: Override check runner (for testing only).

    Returns:
        A Deposition object representing the local session.
    """
    session_id = str(uuid.uuid4())
    base_dir = _base_dir or config.session_dir
    store: SessionStore = JsonSessionStore(session_id, base_dir=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)
        if config.fetch_local_schema
        else RemoteSchemaProvider(config.schema_base_url, config.schema_cache_dir)
    )
    session = LocalSession(
        session_id=session_id,
        email=email,
        users=users,
        country=country,
        experiment_type=experiment_type,
        created_at=datetime.now(tz=timezone.utc),
        em_subtype=em_subtype,
        coordinates=coordinates,
    )
    store.create_session(session)
    return Deposition(store=store, api_client=api_client, check_runner=check_runner)

onedep_lib.dsp.deposit_resume(session_id, config, _base_dir=None, _api_client=None, _check_runner=None)

Resume an existing local deposition session.

Parameters:

Name Type Description Default
session_id str

The session_id returned by a previous deposit_init() call.

required
config DepositConfig

Pre-built DepositConfig for the target deposition site.

required
_base_dir Path | None

Override session storage directory (for testing only).

None
_api_client ApiClient | None

Override API client (for testing only).

None
_check_runner CheckRunner | None

Override check runner (for testing only).

None

Returns:

Type Description
Deposition

A Deposition object for the existing session.

Raises:

Type Description
KeyError

If no session with the given session_id exists.

Source code in src/onedep_lib/dsp.py
def deposit_resume(
    session_id: str,
    config: DepositConfig,
    _base_dir: Path | None = None,
    _api_client: ApiClient | None = None,
    _check_runner: CheckRunnerProtocol | None = None,
) -> Deposition:
    """Resume an existing local deposition session.

    Args:
        session_id: The session_id returned by a previous deposit_init() call.
        config: Pre-built DepositConfig for the target deposition site.
        _base_dir: Override session storage directory (for testing only).
        _api_client: Override API client (for testing only).
        _check_runner: Override check runner (for testing only).

    Returns:
        A Deposition object for the existing session.

    Raises:
        KeyError: If no session with the given session_id exists.
    """
    base_dir = _base_dir or config.session_dir
    store: SessionStore = JsonSessionStore(session_id, base_dir=base_dir)
    session = store.get_session()  # raises KeyError if not found
    client_config = _config_for_hostname(config, session.site_base_url) if session.site_base_url else config
    api_client: ApiClient = _api_client or HttpApiClient(client_config, auth_provider=TokenStore(client_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)

onedep_lib.dsp.list_sessions(base_dir=None)

Return all local sessions with their registered files, newest first.

Source code in src/onedep_lib/dsp.py
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()
    _base = base_dir or config.session_dir
    if not _base.exists():
        return []

    results: list[tuple[LocalSession, list[LocalFile]]] = []
    for entry in sorted(_base.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True):
        json_path = entry / "session.json"
        if not json_path.exists():
            continue
        try:
            store = JsonSessionStore(entry.name, base_dir=_base)
            session = store.get_session()
            files = store.get_all_files()
            store.close()
            results.append((session, files))
        except Exception:  # noqa: BLE001
            continue

    return results

onedep_lib.dsp.check_auth_key(config)

Return True if the configured credentials are valid, False otherwise.

Source code in src/onedep_lib/dsp.py
def check_auth_key(config: DepositConfig) -> bool:
    """Return True if the configured credentials are valid, False otherwise."""
    try:
        api_client = HttpApiClient(config, auth_provider=TokenStore(config))
        api_client.get_all_depositions()
        return True
    except Exception:  # noqa: BLE001
        return False

onedep_lib.dsp.Deposition

Local deposition session. Created via deposit_init() or deposit_resume().

Source code in src/onedep_lib/dsp.py
class Deposition:
    """Local deposition session. Created via deposit_init() or deposit_resume()."""

    def __init__(
        self,
        store: SessionStore,
        api_client: ApiClient,
        check_runner: CheckRunnerProtocol,
    ) -> None:
        self._store = store
        self._api_client = api_client
        self._check_runner = check_runner
        self._session = store.get_session()

    @property
    def session_id(self) -> str:
        """Unique ID of the local session."""
        return self._session.session_id

    @property
    def remote_dep_id(self) -> str | None:
        """Remote deposition ID, populated after deposit() is called."""
        return self._session.remote_dep_id

    @property
    def site_base_url(self) -> str | None:
        """Remote deposition site root, populated after deposit() is called."""
        return self._session.site_base_url

    @property
    def site_url(self) -> str | None:
        """Remote deposition site URL, populated after deposit() is called."""
        return self._session.site_url

    def set_experiment_type(self, experiment_type: ExperimentType) -> None:
        """Set or update the experiment type for this deposition."""
        self._store.update_experiment_type(experiment_type)
        self._session.experiment_type = experiment_type

    def set_em_params(
        self,
        em_subtype: EMSubType | None = None,
        coordinates: bool | None = None,
    ) -> None:
        """Set EM-specific parameters for this deposition."""
        self._store.update_em_params(em_subtype, coordinates)
        self._session.em_subtype = em_subtype
        self._session.coordinates = coordinates

    def add_file(self, file_path: str, file_type: FileType) -> str:
        """Register a local file for this deposition.

        Returns:
            A file_id (UUID string) to reference this file in check methods.

        Raises:
            FileNotFoundError: If file_path does not exist.
        """
        path = Path(file_path)
        if not path.exists():
            raise FileNotFoundError(f"File not found: {file_path}")
        stat = path.stat()
        file_mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
        md5 = _md5_of_file(path)
        file_id = str(uuid.uuid4())
        local_file = LocalFile(
            file_id=file_id,
            session_id=self._session.session_id,
            file_path=str(path.resolve()),
            file_type=file_type,
            md5=md5,
            file_mtime=file_mtime,
        )
        self._store.add_file(local_file)
        return file_id

    def has_file(self, file_path: str) -> bool:
        """Check if file has already been registered for this deposition."""
        return self._store.has_file(file_path)

    def remove_file(self, file_id: str) -> None:
        """Remove a file from this local session by its file_id."""
        self._store.remove_file(file_id)

    def set_voxel_values(
        self,
        file_id: str,
        spacing_x: float,
        spacing_y: float,
        spacing_z: float,
        contour: float,
    ) -> None:
        """Set voxel spacing and contour level for a map file."""
        self._store.set_voxel_values(file_id, spacing_x, spacing_y, spacing_z, contour)

    def check_required_files(self) -> CheckReport:
        """Check that the session contains all required files for the experiment type."""
        files = self._store.get_all_files()
        return self._check_runner.check_required_files(files, self._session.experiment_type, self._session.em_subtype)

    def check_mmcif_file(self, file_id: str) -> CheckReport:
        """Check that the file identified by file_id is a valid mmCIF."""
        file = self._store.get_file(file_id)
        return self._check_runner.check_mmcif_file(file)

    def check_mmcif_category(self, file_id: str, category: str) -> CheckReport:
        """Check that the mmCIF file contains the given category."""
        file = self._store.get_file(file_id)
        return self._check_runner.check_mmcif_category(file, category)

    def check_mmcif_field(self, file_id: str, category: str, field: str) -> CheckReport:
        """Check that the mmCIF file contains the given field in the given category."""
        file = self._store.get_file(file_id)
        return self._check_runner.check_mmcif_field(file, category, field)

    def check_file_type(self, file_id: str, file_type: FileType) -> CheckReport:
        """Check that the file matches the expected FileType."""
        file = self._store.get_file(file_id)
        return self._check_runner.check_file_type(file, file_type)

    def deposit(self) -> str:
        """Submit this deposition to the OneDep API.

        Creates a remote deposition, uploads all registered files, and triggers
        processing. Returns immediately without waiting for processing to finish.

        Returns:
            The remote deposition ID (e.g. "D_8000000001").

        Raises:
            ValueError: If experiment_type has not been set.
            ApiError: If any API call fails.
        """
        if self._session.experiment_type is None:
            raise ValueError(
                "experiment_type must be set before calling deposit(). "
                "Use set_experiment_type() or pass experiment_type to deposit_init()."
            )
        if self._session.remote_dep_id is None:
            experiment = Experiment(
                exp_type=self._session.experiment_type,
                coordinates=self._session.coordinates if self._session.coordinates is not None else True,
                subtype=self._session.em_subtype,
            )
            remote_dep = self._api_client.create_deposition(
                email=self._session.email,
                users=self._session.users,
                country=self._session.country,
                experiments=[experiment],
            )
            dep_id = remote_dep.dep_id
            site_base_url = remote_dep.site_base_url or getattr(self._api_client, "site_base_url", None)
            self._store.set_remote_dep_id(
                dep_id,
                site_url=remote_dep.site_url,
                site_base_url=site_base_url,
            )
            self._session.remote_dep_id = dep_id
            self._session.site_base_url = site_base_url
            self._session.site_url = remote_dep.site_url
        else:
            dep_id = self._session.remote_dep_id

        for file in self._store.get_all_files():
            deposited = self._api_client.upload_file(dep_id, file.file_path, file.file_type)
            if file.voxel:
                v = file.voxel
                self._api_client.update_metadata(
                    dep_id,
                    deposited.file_id,
                    spacing_x=v["spacing_x"],
                    spacing_y=v["spacing_y"],
                    spacing_z=v["spacing_z"],
                    contour=v["contour"],
                    description="",
                )

        self._api_client.process(dep_id)
        return dep_id

    def get_status(self) -> DepositStatus | DepositError:
        """Return the current processing status of the remote deposition.

        Raises:
            RuntimeError: If deposit() has not been called yet.
        """
        if self._session.remote_dep_id is None:
            raise RuntimeError(
                "deposit() has not been called yet for this session. "
                "Call deposit() first to obtain a remote deposition ID."
            )
        return self._api_client.get_status(self._session.remote_dep_id)

    def get_experiment_file_types(self) -> list[FileType]:
        """Return the accepted file types for the current experiment type."""
        session = self._session
        if not session or not session.experiment_type:
            return []
        exptype = session.experiment_type.value
        if exptype is None:
            return []
        subschemas = CheckRunner.subschemas
        if exptype not in subschemas:
            return []
        provider = LocalSchemaProvider(DepositConfig().local_schema_cache_dir)
        schema = provider.get_schema(exptype)
        filetypes = schema.get("enum", [])
        return filetypes

    def close(self) -> None:
        """Close the underlying session store connection."""
        self._store.close()

    def __enter__(self) -> Deposition:
        return self

    def __exit__(self, *args: object) -> None:
        self.close()

remote_dep_id property

Remote deposition ID, populated after deposit() is called.

session_id property

Unique ID of the local session.

site_base_url property

Remote deposition site root, populated after deposit() is called.

site_url property

Remote deposition site URL, populated after deposit() is called.

add_file(file_path, file_type)

Register a local file for this deposition.

Returns:

Type Description
str

A file_id (UUID string) to reference this file in check methods.

Raises:

Type Description
FileNotFoundError

If file_path does not exist.

Source code in src/onedep_lib/dsp.py
def add_file(self, file_path: str, file_type: FileType) -> str:
    """Register a local file for this deposition.

    Returns:
        A file_id (UUID string) to reference this file in check methods.

    Raises:
        FileNotFoundError: If file_path does not exist.
    """
    path = Path(file_path)
    if not path.exists():
        raise FileNotFoundError(f"File not found: {file_path}")
    stat = path.stat()
    file_mtime = datetime.fromtimestamp(stat.st_mtime, tz=timezone.utc)
    md5 = _md5_of_file(path)
    file_id = str(uuid.uuid4())
    local_file = LocalFile(
        file_id=file_id,
        session_id=self._session.session_id,
        file_path=str(path.resolve()),
        file_type=file_type,
        md5=md5,
        file_mtime=file_mtime,
    )
    self._store.add_file(local_file)
    return file_id

check_file_type(file_id, file_type)

Check that the file matches the expected FileType.

Source code in src/onedep_lib/dsp.py
def check_file_type(self, file_id: str, file_type: FileType) -> CheckReport:
    """Check that the file matches the expected FileType."""
    file = self._store.get_file(file_id)
    return self._check_runner.check_file_type(file, file_type)

check_mmcif_category(file_id, category)

Check that the mmCIF file contains the given category.

Source code in src/onedep_lib/dsp.py
def check_mmcif_category(self, file_id: str, category: str) -> CheckReport:
    """Check that the mmCIF file contains the given category."""
    file = self._store.get_file(file_id)
    return self._check_runner.check_mmcif_category(file, category)

check_mmcif_field(file_id, category, field)

Check that the mmCIF file contains the given field in the given category.

Source code in src/onedep_lib/dsp.py
def check_mmcif_field(self, file_id: str, category: str, field: str) -> CheckReport:
    """Check that the mmCIF file contains the given field in the given category."""
    file = self._store.get_file(file_id)
    return self._check_runner.check_mmcif_field(file, category, field)

check_mmcif_file(file_id)

Check that the file identified by file_id is a valid mmCIF.

Source code in src/onedep_lib/dsp.py
def check_mmcif_file(self, file_id: str) -> CheckReport:
    """Check that the file identified by file_id is a valid mmCIF."""
    file = self._store.get_file(file_id)
    return self._check_runner.check_mmcif_file(file)

check_required_files()

Check that the session contains all required files for the experiment type.

Source code in src/onedep_lib/dsp.py
def check_required_files(self) -> CheckReport:
    """Check that the session contains all required files for the experiment type."""
    files = self._store.get_all_files()
    return self._check_runner.check_required_files(files, self._session.experiment_type, self._session.em_subtype)

close()

Close the underlying session store connection.

Source code in src/onedep_lib/dsp.py
def close(self) -> None:
    """Close the underlying session store connection."""
    self._store.close()

deposit()

Submit this deposition to the OneDep API.

Creates a remote deposition, uploads all registered files, and triggers processing. Returns immediately without waiting for processing to finish.

Returns:

Type Description
str

The remote deposition ID (e.g. "D_8000000001").

Raises:

Type Description
ValueError

If experiment_type has not been set.

ApiError

If any API call fails.

Source code in src/onedep_lib/dsp.py
def deposit(self) -> str:
    """Submit this deposition to the OneDep API.

    Creates a remote deposition, uploads all registered files, and triggers
    processing. Returns immediately without waiting for processing to finish.

    Returns:
        The remote deposition ID (e.g. "D_8000000001").

    Raises:
        ValueError: If experiment_type has not been set.
        ApiError: If any API call fails.
    """
    if self._session.experiment_type is None:
        raise ValueError(
            "experiment_type must be set before calling deposit(). "
            "Use set_experiment_type() or pass experiment_type to deposit_init()."
        )
    if self._session.remote_dep_id is None:
        experiment = Experiment(
            exp_type=self._session.experiment_type,
            coordinates=self._session.coordinates if self._session.coordinates is not None else True,
            subtype=self._session.em_subtype,
        )
        remote_dep = self._api_client.create_deposition(
            email=self._session.email,
            users=self._session.users,
            country=self._session.country,
            experiments=[experiment],
        )
        dep_id = remote_dep.dep_id
        site_base_url = remote_dep.site_base_url or getattr(self._api_client, "site_base_url", None)
        self._store.set_remote_dep_id(
            dep_id,
            site_url=remote_dep.site_url,
            site_base_url=site_base_url,
        )
        self._session.remote_dep_id = dep_id
        self._session.site_base_url = site_base_url
        self._session.site_url = remote_dep.site_url
    else:
        dep_id = self._session.remote_dep_id

    for file in self._store.get_all_files():
        deposited = self._api_client.upload_file(dep_id, file.file_path, file.file_type)
        if file.voxel:
            v = file.voxel
            self._api_client.update_metadata(
                dep_id,
                deposited.file_id,
                spacing_x=v["spacing_x"],
                spacing_y=v["spacing_y"],
                spacing_z=v["spacing_z"],
                contour=v["contour"],
                description="",
            )

    self._api_client.process(dep_id)
    return dep_id

get_experiment_file_types()

Return the accepted file types for the current experiment type.

Source code in src/onedep_lib/dsp.py
def get_experiment_file_types(self) -> list[FileType]:
    """Return the accepted file types for the current experiment type."""
    session = self._session
    if not session or not session.experiment_type:
        return []
    exptype = session.experiment_type.value
    if exptype is None:
        return []
    subschemas = CheckRunner.subschemas
    if exptype not in subschemas:
        return []
    provider = LocalSchemaProvider(DepositConfig().local_schema_cache_dir)
    schema = provider.get_schema(exptype)
    filetypes = schema.get("enum", [])
    return filetypes

get_status()

Return the current processing status of the remote deposition.

Raises:

Type Description
RuntimeError

If deposit() has not been called yet.

Source code in src/onedep_lib/dsp.py
def get_status(self) -> DepositStatus | DepositError:
    """Return the current processing status of the remote deposition.

    Raises:
        RuntimeError: If deposit() has not been called yet.
    """
    if self._session.remote_dep_id is None:
        raise RuntimeError(
            "deposit() has not been called yet for this session. "
            "Call deposit() first to obtain a remote deposition ID."
        )
    return self._api_client.get_status(self._session.remote_dep_id)

has_file(file_path)

Check if file has already been registered for this deposition.

Source code in src/onedep_lib/dsp.py
def has_file(self, file_path: str) -> bool:
    """Check if file has already been registered for this deposition."""
    return self._store.has_file(file_path)

remove_file(file_id)

Remove a file from this local session by its file_id.

Source code in src/onedep_lib/dsp.py
def remove_file(self, file_id: str) -> None:
    """Remove a file from this local session by its file_id."""
    self._store.remove_file(file_id)

set_em_params(em_subtype=None, coordinates=None)

Set EM-specific parameters for this deposition.

Source code in src/onedep_lib/dsp.py
def set_em_params(
    self,
    em_subtype: EMSubType | None = None,
    coordinates: bool | None = None,
) -> None:
    """Set EM-specific parameters for this deposition."""
    self._store.update_em_params(em_subtype, coordinates)
    self._session.em_subtype = em_subtype
    self._session.coordinates = coordinates

set_experiment_type(experiment_type)

Set or update the experiment type for this deposition.

Source code in src/onedep_lib/dsp.py
def set_experiment_type(self, experiment_type: ExperimentType) -> None:
    """Set or update the experiment type for this deposition."""
    self._store.update_experiment_type(experiment_type)
    self._session.experiment_type = experiment_type

set_voxel_values(file_id, spacing_x, spacing_y, spacing_z, contour)

Set voxel spacing and contour level for a map file.

Source code in src/onedep_lib/dsp.py
def set_voxel_values(
    self,
    file_id: str,
    spacing_x: float,
    spacing_y: float,
    spacing_z: float,
    contour: float,
) -> None:
    """Set voxel spacing and contour level for a map file."""
    self._store.set_voxel_values(file_id, spacing_x, spacing_y, spacing_z, contour)

Configuration

onedep_lib.config.DepositConfig dataclass

Resolved configuration for talking to a OneDep deposition site.

Built by :meth:load, which resolves fields from three sources in order of increasing priority: the on-disk config file (config_path), environment variables, and keyword argument overrides. See load for the full resolution order, including how access_token and refresh_token are populated from the [auths.<fqdn>] section.

Attributes:

Name Type Description
access_token str | None

Current OAuth access token, or None if not authenticated.

refresh_token str | None

Current OAuth refresh token, or None if not authenticated.

hostname str

Base URL of the OneDep deposition site to talk to.

ssl_verify bool

Whether to verify TLS certificates on API requests.

redirect bool

Whether to follow site-redirect responses from the API.

allowed_redirect_domain str

Domain suffix that redirect targets must belong to.

fetch_local_schema bool

Whether to use the bundled local JSON schemas instead of fetching them from schema_base_url.

local_schema_cache_dir Path

Directory containing the bundled local JSON schemas.

schema_base_url str

Base URL to fetch remote JSON schemas from.

schema_cache_dir Path

Local directory used to cache remote JSON schemas.

session_dir Path

Local directory used to store deposition session state.

config_path Path

Path to the TOML config file read and written by this class.

Source code in src/onedep_lib/config.py
@dataclass
class DepositConfig:
    """Resolved configuration for talking to a OneDep deposition site.

    Built by :meth:`load`, which resolves fields from three sources in order
    of increasing priority: the on-disk config file (``config_path``),
    environment variables, and keyword argument overrides. See ``load`` for
    the full resolution order, including how ``access_token`` and
    ``refresh_token`` are populated from the ``[auths.<fqdn>]`` section.

    Attributes:
        access_token: Current OAuth access token, or None if not authenticated.
        refresh_token: Current OAuth refresh token, or None if not authenticated.
        hostname: Base URL of the OneDep deposition site to talk to.
        ssl_verify: Whether to verify TLS certificates on API requests.
        redirect: Whether to follow site-redirect responses from the API.
        allowed_redirect_domain: Domain suffix that redirect targets must belong to.
        fetch_local_schema: Whether to use the bundled local JSON schemas instead
            of fetching them from schema_base_url.
        local_schema_cache_dir: Directory containing the bundled local JSON schemas.
        schema_base_url: Base URL to fetch remote JSON schemas from.
        schema_cache_dir: Local directory used to cache remote JSON schemas.
        session_dir: Local directory used to store deposition session state.
        config_path: Path to the TOML config file read and written by this class.
    """

    access_token: str | None = None
    refresh_token: str | None = None
    hostname: str = "https://deposit.wwpdb.org/deposition"
    ssl_verify: bool = True
    redirect: bool = True
    allowed_redirect_domain: str = "wwpdb.org"
    fetch_local_schema: bool = True
    local_schema_cache_dir: Path = field(default_factory=lambda: Path(__file__).parent / "schemas" / "json")
    schema_base_url: str = "https://schemas.wwpdb.org/nextdep"
    schema_cache_dir: Path = field(default_factory=lambda: Path.home() / ".onedep" / "schemas")
    session_dir: Path = field(default_factory=lambda: Path.home() / ".onedep" / "sessions")
    config_path: Path = field(default_factory=lambda: Path.home() / ".config" / "onedep" / "config.toml")

    @staticmethod
    def _load_toml_file(path: Path) -> dict:
        if not path.exists():
            return {}
        try:
            with path.open("rb") as fp:
                return tomllib.load(fp)
        except tomllib.TOMLDecodeError as exc:
            raise ConfigError(f"Failed to parse {path}: {exc}") from exc

    @classmethod
    def load(cls, **overrides: object) -> DepositConfig:
        """Resolve a DepositConfig from the config file, environment, and overrides.

        Resolution order (lowest to highest priority):

        1. The TOML config file at ``config_path`` (default
           ``~/.config/onedep/config.toml``), read from its ``[default]`` section.
        2. Environment variables (``ONEDEP_ACCESS_TOKEN``, ``ONEDEP_REFRESH_TOKEN``,
           ``ONEDEP_HOSTNAME``, ``ONEDEP_SSL_VERIFY``, ``ONEDEP_REDIRECT``,
           ``ONEDEP_SCHEMA_URL``).
        3. Keyword arguments passed to this method.

        After resolving ``hostname``, also reads the matching ``[auths.<fqdn>]``
        section from the config file and populates ``access_token`` and
        ``refresh_token`` from it -- unless either token was explicitly set
        via an environment variable (``ONEDEP_ACCESS_TOKEN`` or
        ``ONEDEP_REFRESH_TOKEN``) or passed as a keyword argument, in which
        case file-based token loading is skipped entirely.

        Args:
            **overrides: Field values that take priority over the config file
                and environment variables. Pass config_path to read from a
                non-default config file location.

        Returns:
            A fully resolved DepositConfig.

        Raises:
            ConfigError: If the config file contains invalid TOML, an invalid
                boolean environment variable value, or malformed token data
                in an [auths.<fqdn>] section.
        """
        valid_fields = {f.name for f in fields(cls)}
        path_fields = {"local_schema_cache_dir", "schema_cache_dir", "session_dir", "config_path"}
        merged: dict[str, object] = {}

        config_path_override = overrides.pop("config_path", None)
        config_file = (
            Path(config_path_override)  # type: ignore[arg-type]
            if config_path_override is not None
            else DepositConfig().config_path
        )
        merged["config_path"] = config_file

        raw = cls._load_toml_file(config_file)
        section = raw.get("default", {})
        for key, value in section.items():
            if key in valid_fields and key not in ("config_path", "access_token", "refresh_token"):
                if key == "hostname" and value == "":
                    continue
                merged[key] = value

        for env_var, (field_name, coerce) in _ENV_MAP.items():
            raw_val = os.environ.get(env_var)
            if raw_val is not None:
                value = coerce(raw_val)
                if field_name == "hostname" and value == "":
                    continue
                merged[field_name] = value

        for key, value in overrides.items():
            if key in valid_fields:
                merged[key] = value

        # Load tokens from [auths.<fqdn>] unless explicitly overridden via kwargs
        # If either token was explicitly overridden, skip file-based token loading entirely.
        if "access_token" not in merged and "refresh_token" not in merged:
            hostname_val = str(merged.get("hostname", next(f.default for f in fields(cls) if f.name == "hostname")))
            fqdn_key = _hostname_to_fqdn_key(hostname_val)
            if fqdn_key:
                entry = raw.get("auths", {}).get(fqdn_key)
                if isinstance(entry, dict):
                    acc = entry.get("access_token")
                    ref = entry.get("refresh_token")
                    if acc is not None or ref is not None:
                        if acc is not None and not isinstance(acc, str):
                            raise ConfigError(f"Malformed token data in [auths.{fqdn_key}]")
                        if not isinstance(ref, str):
                            raise ConfigError(f"Malformed token data in [auths.{fqdn_key}]")
                        merged["access_token"] = acc
                        merged["refresh_token"] = ref

        for key in path_fields:
            if key in merged and not isinstance(merged[key], Path):
                merged[key] = Path(merged[key])  # type: ignore[arg-type]

        return cls(**merged)  # type: ignore[arg-type]

    def _read_toml(self) -> dict:
        return DepositConfig._load_toml_file(self.config_path)

    def _write_toml(self, data: dict) -> None:
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        tmp = self.config_path.with_suffix(".toml.tmp")
        try:
            tmp.write_text(tomli_w.dumps(data), encoding="utf-8")
            os.replace(tmp, self.config_path)
        except ConfigError:
            tmp.unlink(missing_ok=True)
            raise
        except Exception as exc:
            tmp.unlink(missing_ok=True)
            raise ConfigError(f"Failed to write {self.config_path}: {exc}") from exc

    def read_auth_entry(self, key: str) -> dict | None:
        data = self._read_toml()
        auths = data.get("auths", {})
        if not isinstance(auths, dict):
            raise ConfigError("Malformed [auths] section in config.toml")
        entry = auths.get(key)
        if entry is None:
            return None
        if not isinstance(entry, dict):
            raise ConfigError(f"Malformed [auths.{key}] entry in config.toml")
        return entry

    def read_auth_entries(self) -> dict[str, dict]:
        data = self._read_toml()
        auths = data.get("auths", {})
        if not isinstance(auths, dict):
            raise ConfigError("Malformed [auths] section in config.toml")
        entries: dict[str, dict] = {}
        for key, entry in auths.items():
            if not isinstance(entry, dict):
                raise ConfigError(f"Malformed [auths.{key}] entry in config.toml")
            entries[key] = dict(entry)
        return entries

    def write_auth_entry(self, key: str, entry: dict) -> None:
        data = self._read_toml()
        auths = data.setdefault("auths", {})
        if not isinstance(auths, dict):
            raise ConfigError("Malformed [auths] section in config.toml")
        auths[key] = entry
        self._write_toml(data)

    def delete_auth_entry(self, key: str) -> None:
        data = self._read_toml()
        auths = data.get("auths")
        if not isinstance(auths, dict) or key not in auths:
            return
        del auths[key]
        self._write_toml(data)

load(**overrides) classmethod

Resolve a DepositConfig from the config file, environment, and overrides.

Resolution order (lowest to highest priority):

  1. The TOML config file at config_path (default ~/.config/onedep/config.toml), read from its [default] section.
  2. Environment variables (ONEDEP_ACCESS_TOKEN, ONEDEP_REFRESH_TOKEN, ONEDEP_HOSTNAME, ONEDEP_SSL_VERIFY, ONEDEP_REDIRECT, ONEDEP_SCHEMA_URL).
  3. Keyword arguments passed to this method.

After resolving hostname, also reads the matching [auths.<fqdn>] section from the config file and populates access_token and refresh_token from it -- unless either token was explicitly set via an environment variable (ONEDEP_ACCESS_TOKEN or ONEDEP_REFRESH_TOKEN) or passed as a keyword argument, in which case file-based token loading is skipped entirely.

Parameters:

Name Type Description Default
**overrides object

Field values that take priority over the config file and environment variables. Pass config_path to read from a non-default config file location.

{}

Returns:

Type Description
DepositConfig

A fully resolved DepositConfig.

Raises:

Type Description
ConfigError

If the config file contains invalid TOML, an invalid boolean environment variable value, or malformed token data in an [auths.] section.

Source code in src/onedep_lib/config.py
@classmethod
def load(cls, **overrides: object) -> DepositConfig:
    """Resolve a DepositConfig from the config file, environment, and overrides.

    Resolution order (lowest to highest priority):

    1. The TOML config file at ``config_path`` (default
       ``~/.config/onedep/config.toml``), read from its ``[default]`` section.
    2. Environment variables (``ONEDEP_ACCESS_TOKEN``, ``ONEDEP_REFRESH_TOKEN``,
       ``ONEDEP_HOSTNAME``, ``ONEDEP_SSL_VERIFY``, ``ONEDEP_REDIRECT``,
       ``ONEDEP_SCHEMA_URL``).
    3. Keyword arguments passed to this method.

    After resolving ``hostname``, also reads the matching ``[auths.<fqdn>]``
    section from the config file and populates ``access_token`` and
    ``refresh_token`` from it -- unless either token was explicitly set
    via an environment variable (``ONEDEP_ACCESS_TOKEN`` or
    ``ONEDEP_REFRESH_TOKEN``) or passed as a keyword argument, in which
    case file-based token loading is skipped entirely.

    Args:
        **overrides: Field values that take priority over the config file
            and environment variables. Pass config_path to read from a
            non-default config file location.

    Returns:
        A fully resolved DepositConfig.

    Raises:
        ConfigError: If the config file contains invalid TOML, an invalid
            boolean environment variable value, or malformed token data
            in an [auths.<fqdn>] section.
    """
    valid_fields = {f.name for f in fields(cls)}
    path_fields = {"local_schema_cache_dir", "schema_cache_dir", "session_dir", "config_path"}
    merged: dict[str, object] = {}

    config_path_override = overrides.pop("config_path", None)
    config_file = (
        Path(config_path_override)  # type: ignore[arg-type]
        if config_path_override is not None
        else DepositConfig().config_path
    )
    merged["config_path"] = config_file

    raw = cls._load_toml_file(config_file)
    section = raw.get("default", {})
    for key, value in section.items():
        if key in valid_fields and key not in ("config_path", "access_token", "refresh_token"):
            if key == "hostname" and value == "":
                continue
            merged[key] = value

    for env_var, (field_name, coerce) in _ENV_MAP.items():
        raw_val = os.environ.get(env_var)
        if raw_val is not None:
            value = coerce(raw_val)
            if field_name == "hostname" and value == "":
                continue
            merged[field_name] = value

    for key, value in overrides.items():
        if key in valid_fields:
            merged[key] = value

    # Load tokens from [auths.<fqdn>] unless explicitly overridden via kwargs
    # If either token was explicitly overridden, skip file-based token loading entirely.
    if "access_token" not in merged and "refresh_token" not in merged:
        hostname_val = str(merged.get("hostname", next(f.default for f in fields(cls) if f.name == "hostname")))
        fqdn_key = _hostname_to_fqdn_key(hostname_val)
        if fqdn_key:
            entry = raw.get("auths", {}).get(fqdn_key)
            if isinstance(entry, dict):
                acc = entry.get("access_token")
                ref = entry.get("refresh_token")
                if acc is not None or ref is not None:
                    if acc is not None and not isinstance(acc, str):
                        raise ConfigError(f"Malformed token data in [auths.{fqdn_key}]")
                    if not isinstance(ref, str):
                        raise ConfigError(f"Malformed token data in [auths.{fqdn_key}]")
                    merged["access_token"] = acc
                    merged["refresh_token"] = ref

    for key in path_fields:
        if key in merged and not isinstance(merged[key], Path):
            merged[key] = Path(merged[key])  # type: ignore[arg-type]

    return cls(**merged)  # type: ignore[arg-type]

Check result types

onedep_lib.checks.report.CheckReport dataclass

Result of running one or more checks against a file or session.

Attributes:

Name Type Description
source str

Identifier of what was checked (a file_id, or "session" for session-level checks like check_required_files).

issues list[CheckIssue]

All issues found, of any severity.

Source code in src/onedep_lib/checks/report.py
@dataclass
class CheckReport:
    """Result of running one or more checks against a file or session.

    Attributes:
        source: Identifier of what was checked (a file_id, or "session" for
            session-level checks like check_required_files).
        issues: All issues found, of any severity.
    """

    source: str
    issues: list[CheckIssue] = field(default_factory=list)

    @property
    def ok(self) -> bool:
        """True if issues contains no ERROR or FATAL severity issue."""
        return not any(i.severity in (CheckSeverity.ERROR, CheckSeverity.FATAL) for i in self.issues)

    def errors(self) -> list[CheckIssue]:
        """Return only the issues with ERROR or FATAL severity."""
        return [i for i in self.issues if i.severity in (CheckSeverity.ERROR, CheckSeverity.FATAL)]

    def warnings(self) -> list[CheckIssue]:
        """Return only the issues with WARNING severity."""
        return [i for i in self.issues if i.severity == CheckSeverity.WARNING]

ok property

True if issues contains no ERROR or FATAL severity issue.

errors()

Return only the issues with ERROR or FATAL severity.

Source code in src/onedep_lib/checks/report.py
def errors(self) -> list[CheckIssue]:
    """Return only the issues with ERROR or FATAL severity."""
    return [i for i in self.issues if i.severity in (CheckSeverity.ERROR, CheckSeverity.FATAL)]

warnings()

Return only the issues with WARNING severity.

Source code in src/onedep_lib/checks/report.py
def warnings(self) -> list[CheckIssue]:
    """Return only the issues with WARNING severity."""
    return [i for i in self.issues if i.severity == CheckSeverity.WARNING]

onedep_lib.checks.report.CheckIssue dataclass

A single finding produced by a check.

For example, a missing required file or an invalid mmCIF field.

Source code in src/onedep_lib/checks/report.py
@dataclass(frozen=True)
class CheckIssue:
    """A single finding produced by a check.

    For example, a missing required file or an invalid mmCIF field.
    """

    severity: CheckSeverity
    code: str
    message: str
    location: CifLocation = field(default_factory=CifLocation)
    expected: Any = None
    actual: Any = None

    def __post_init__(self) -> None:
        if not isinstance(self.severity, CheckSeverity):
            object.__setattr__(self, "severity", CheckSeverity(self.severity))

onedep_lib.checks.report.CheckSeverity

Bases: str, Enum

Severity level of a single CheckIssue, from least to most severe.

Source code in src/onedep_lib/checks/report.py
class CheckSeverity(str, Enum):
    """Severity level of a single CheckIssue, from least to most severe."""

    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    FATAL = "fatal"

onedep_lib.checks.report.CifLocation dataclass

Location of a check issue within an mmCIF file, when known.

All fields are optional; any subset may be populated depending on what the originating check was able to determine.

Source code in src/onedep_lib/checks/report.py
@dataclass(frozen=True)
class CifLocation:
    """Location of a check issue within an mmCIF file, when known.

    All fields are optional; any subset may be populated depending on what
    the originating check was able to determine.
    """

    data_block: str | None = None
    category: str | None = None
    item: str | None = None
    row: int | None = None
    line: int | None = None
    column: int | None = None

Domain enums

onedep_lib.enums.Country

Bases: Enum

Source code in src/onedep_lib/enums.py
class Country(enum.Enum):
    AFGHANISTAN = "Afghanistan"
    ALAND = "Aland Islands"
    ALBANIA = "Albania"
    ALGERIA = "Algeria"
    AMERICAN_SAMOA = "American Samoa"
    ANDORRA = "Andorra"
    ANGOLA = "Angola"
    ANGUILLA = "Anguilla"
    ANTARCTICA = "Antarctica"
    ANTIGUA_BARBUDA = "Antigua And Barbuda"
    ARGENTINA = "Argentina"
    ARMENIA = "Armenia"
    ARUBA = "Aruba"
    AUSTRALIA = "Australia"
    AUSTRIA = "Austria"
    AZERBAIJAN = "Azerbaijan"
    BAHAMAS = "Bahamas"
    BAHRAIN = "Bahrain"
    BANGLADESH = "Bangladesh"
    BARBADOS = "Barbados"
    BELARUS = "Belarus"
    BELGIUM = "Belgium"
    BELIZE = "Belize"
    BENIN = "Benin"
    BERMUDA = "Bermuda"
    BHUTAN = "Bhutan"
    BOLIVIA = "Bolivia, Plurinational State Of"
    BONAIRE = "Bonaire, Sint Eustatius And Saba"
    BOSNIA_HERZEGOVINA = "Bosnia And Herzegovina"
    BOTSWANA = "Botswana"
    BOUVET = "Bouvet Island"
    BRAZIL = "Brazil"
    BRITISH_INDIAN_OCEAN = "British Indian Ocean Territory"
    BRUNEI = "Brunei Darussalam"
    BULGARIA = "Bulgaria"
    BURKINA_FASO = "Burkina Faso"
    BURUNDI = "Burundi"
    CAMBODIA = "Cambodia"
    CAMEROON = "Cameroon"
    CANADA = "Canada"
    CAPE_VERDE = "Cape Verde"
    CAR = "Central African Republic"
    CAYMAN = "Cayman Islands"
    CHAD = "Chad"
    CHILE = "Chile"
    CHINA = "China"
    CHRISTMAS = "Christmas Island"
    COCOS = "Cocos (Keeling) Islands"
    COLOMBIA = "Colombia"
    COMOROS = "Comoros"
    CONGO = "Congo"
    COOK = "Cook Islands"
    COSTA_RICA = "Costa Rica"
    CROATIA = "Croatia"
    CUBA = "Cuba"
    CURACAO = "Curacao"
    CYPRUS = "Cyprus"
    CZECH_REPUBLIC = "Czech Republic"
    DENMARK = "Denmark"
    DJIBOUTI = "Djibouti"
    DOMINICA = "Dominica"
    DOMINICAN_REPUBLIC = "Dominican Republic"
    DRC = "Congo, The Democratic Republic Of The"
    ECUADOR = "Ecuador"
    EGYPT = "Egypt"
    EL_SALVADOR = "El Salvador"
    EQUATORIAL_GUINEA = "Equatorial Guinea"
    ERITREA = "Eritrea"
    ESTONIA = "Estonia"
    ETHIOPIA = "Ethiopia"
    FAROE = "Faroe Islands"
    FIJI = "Fiji"
    FINLAND = "Finland"
    FRANCE = "France"
    FRENCH_GUIANA = "French Guiana"
    FRENCH_POLYNESIA = "French Polynesia"
    FRENCH_SOUTHERN = "French Southern Territories"
    GABON = "Gabon"
    GAMBIA = "Gambia"
    GEORGIA = "Georgia"
    GERMANY = "Germany"
    GHANA = "Ghana"
    GIBRALTAR = "Gibraltar"
    GREECE = "Greece"
    GREENLAND = "Greenland"
    GRENADA = "Grenada"
    GUADELOUPE = "Guadeloupe"
    GUAM = "Guam"
    GUATEMALA = "Guatemala"
    GUERNSEY = "Guernsey"
    GUINEA = "Guinea"
    GUINEA_BISSAU = "Guinea-Bissau"
    GUYANA = "Guyana"
    HAITI = "Haiti"
    HEARD_MCDONALD = "Heard Island And Mcdonald Islands"
    HONDURAS = "Honduras"
    HONG_KONG = "Hong Kong"
    HUNGARY = "Hungary"
    ICELAND = "Iceland"
    INDIA = "India"
    INDONESIA = "Indonesia"
    IRAN = "Iran, Islamic Republic Of"
    IRAQ = "Iraq"
    IRELAND = "Ireland"
    ISLE_OF_MAN = "Isle Of Man"
    ISRAEL = "Israel"
    ITALY = "Italy"
    IVORY_COAST = "Cote D'Ivoire"
    JAMAICA = "Jamaica"
    JAPAN = "Japan"
    JERSEY = "Jersey"
    JORDAN = "Jordan"
    KAZAKHSTAN = "Kazakhstan"
    KENYA = "Kenya"
    KIRIBATI = "Kiribati"
    KUWAIT = "Kuwait"
    KYRGYZSTAN = "Kyrgyzstan"
    LAOS = "Lao People'S Democratic Republic"
    LATVIA = "Latvia"
    LEBANON = "Lebanon"
    LESOTHO = "Lesotho"
    LIBERIA = "Liberia"
    LIBYA = "Libya"
    LIECHTENSTEIN = "Liechtenstein"
    LITHUANIA = "Lithuania"
    LUXEMBOURG = "Luxembourg"
    MACAO = "Macao"
    MACEDONIA = "Macedonia"
    MADAGASCAR = "Madagascar"
    MALAWI = "Malawi"
    MALAYSIA = "Malaysia"
    MALDIVES = "Maldives"
    MALI = "Mali"
    MALTA = "Malta"
    MALVINAS = "Falkland Islands (Malvinas)"
    MARSHALL = "Marshall Islands"
    MARTINIQUE = "Martinique"
    MAURITANIA = "Mauritania"
    MAURITIUS = "Mauritius"
    MAYOTTE = "Mayotte"
    MEXICO = "Mexico"
    MICRONESIA = "Micronesia, Federated States Of"
    MOLDOVA = "Moldova, Republic Of"
    MONACO = "Monaco"
    MONGOLIA = "Mongolia"
    MONTENEGRO = "Montenegro"
    MONTSERRAT = "Montserrat"
    MOROCCO = "Morocco"
    MOZAMBIQUE = "Mozambique"
    MYANMAR = "Myanmar"
    NAMIBIA = "Namibia"
    NAURU = "Nauru"
    NEPAL = "Nepal"
    NETHERLANDS = "Netherlands"
    NEW_CALEDONIA = "New Caledonia"
    NEW_ZEALAND = "New Zealand"
    NICARAGUA = "Nicaragua"
    NIGER = "Niger"
    NIGERIA = "Nigeria"
    NIUE = "Niue"
    NORFOLK = "Norfolk Island"
    NORTH_KOREA = "Korea, Democratic People'S Republic Of"
    NORTHERN_MARIANA = "Northern Mariana Islands"
    NORWAY = "Norway"
    OMAN = "Oman"
    PAKISTAN = "Pakistan"
    PALAU = "Palau"
    PALESTINIAN = "Palestinian Territory"
    PANAMA = "Panama"
    PAPUA_NEW_GUINEA = "Papua New Guinea"
    PARAGUAY = "Paraguay"
    PERU = "Peru"
    PHILIPPINES = "Philippines"
    PITCAIRN = "Pitcairn"
    POLAND = "Poland"
    PORTUGAL = "Portugal"
    PUERTO_RICO = "Puerto Rico"
    QATAR = "Qatar"
    REUNION = "Reunion"
    ROMANIA = "Romania"
    RUSSIA = "Russian Federation"
    RWANDA = "Rwanda"
    SAINT_BARTHELEMY = "Saint Barthelemy"
    SAINT_HELENA = "Saint Helena, Ascension And Tristan Da Cunha"
    SAINT_KITTS = "Saint Kitts And Nevis"
    SAINT_LUCIA = "Saint Lucia"
    SAINT_MARTIN = "Saint Martin (French Part)"
    SAINT_PIERRE = "Saint Pierre And Miquelon"
    SAINT_VINCENT = "Saint Vincent And The Grenadines"
    SAMOA = "Samoa"
    SAN_MARINO = "San Marino"
    SAO_TOME_PRINCIPE = "Sao Tome And Principe"
    SAUDI_ARABIA = "Saudi Arabia"
    SENEGAL = "Senegal"
    SERBIA = "Serbia"
    SEYCHELLES = "Seychelles"
    SIERRA_LEONE = "Sierra Leone"
    SINGAPORE = "Singapore"
    SINT_MAARTEN = "Sint Maarten (Dutch Part)"
    SLOVAKIA = "Slovakia"
    SLOVENIA = "Slovenia"
    SOLOMON = "Solomon Islands"
    SOMALIA = "Somalia"
    SOUTH_AFRICA = "South Africa"
    SOUTH_GEORGIA = "South Georgia And The South Sandwich Islands"
    SOUTH_KOREA = "Korea, Republic Of"
    SOUTH_SUDAN = "South Sudan"
    SPAIN = "Spain"
    SRI_LANKA = "Sri Lanka"
    SUDAN = "Sudan"
    SURINAME = "Suriname"
    SVALBARD = "Svalbard And Jan Mayen"
    SWAZILAND = "Swaziland"
    SWEDEN = "Sweden"
    SWITZERLAND = "Switzerland"
    SYRIA = "Syrian Arab Republic"
    TAIWAN = "Taiwan"
    TAJIKISTAN = "Tajikistan"
    TANZANIA = "Tanzania, United Republic Of"
    THAILAND = "Thailand"
    TIMOR_LESTE = "Timor-Leste"
    TOGO = "Togo"
    TOKELAU = "Tokelau"
    TONGA = "Tonga"
    TRINIDAD_TOBAGO = "Trinidad And Tobago"
    TUNISIA = "Tunisia"
    TURKEY = "Turkey"
    TURKMENISTAN = "Turkmenistan"
    TURKS_CAICOS = "Turks And Caicos Islands"
    TUVALU = "Tuvalu"
    UAE = "United Arab Emirates"
    UGANDA = "Uganda"
    UK = "United Kingdom"
    UKRAINE = "Ukraine"
    URUGUAY = "Uruguay"
    USA = "United States"
    USA_ISLANDS = "United States Minor Outlying Islands"
    UZBEKISTAN = "Uzbekistan"
    VANUATU = "Vanuatu"
    VATICAN = "Holy See (Vatican City State)"
    VENEZUELA = "Venezuela, Bolivarian Republic Of"
    VIETNAM = "Viet Nam"
    VIRGIN_BRITISH = "Virgin Islands, British"
    VIRGIN_USA = "Virgin Islands, U.S."
    WALLIS_FUTUNA = "Wallis And Futuna"
    WESTERN_SAHARA = "Western Sahara"
    YEMEN = "Yemen"
    ZAMBIA = "Zambia"
    ZIMBABWE = "Zimbabwe"

onedep_lib.enums.EMSubType

Bases: Enum

Source code in src/onedep_lib/enums.py
class EMSubType(enum.Enum):
    HELICAL = "helical"
    SPA = "single"
    SUBTOMOGRAM = "subtomogram"
    TOMOGRAPHY = "tomography"

onedep_lib.enums.ExperimentType

Bases: Enum

Source code in src/onedep_lib/enums.py
class ExperimentType(enum.Enum):
    XRAY = "xray"
    FIBER = "fiber"
    NEUTRON = "neutron"
    EM = "em"
    EC = "ec"
    NMR = "nmr"
    SSNMR = "ssnmr"

onedep_lib.enums.FileType

Bases: Enum

Source code in src/onedep_lib/enums.py
class FileType(enum.Enum):
    LAYER = "layer-lines"
    FSC_XML = "fsc-xml"
    FSC_HALF_MAP_MASK = "fsc-half-map-mask"
    FSC_MAP_MODEL_MASK = "fsc-map-model-mask"
    MAP_MODEL_FSC = "map-model-fsc"
    PDB_COORD = "co-pdb"
    MMCIF_COORD = "co-cif"
    MD_CIF = "md-cif"
    EM_MAP = "vo-map"
    EM_RAW_MAP = "raw-map"
    EM_ADDITIONAL_MAP = "add-map"
    EM_HALF_MAP = "half-map"
    EM_MASK = "mask-map"
    EM_ALIGNMENT_MASK = "alignment-mask-map"
    EM_SEGMENTATION_MAP = "segmentation-map"
    EM_FOCUS_REFINEMENT_ADDITIONAL_MAP = "focus-refinement-additional-map"
    EM_FOCUSED_REFINEMENT_MASK = "focused-refinement-mask-map"
    EM_3D_CLASSIFICATION_ADDITIONAL_MAP = "3d-classification-additional-map"
    ENTRY_IMAGE = "img-emdb"
    CRYSTAL_STRUC_FACTORS = "xs-cif"
    CRYSTAL_MTZ = "xs-mtz"
    CRYSTAL_PARAMETER = "xa-par"
    CRYSTAL_TOPOLOGY = "xa-top"
    VIRUS_MATRIX = "xa-mat"
    NMR_ACS = "nm-shi"
    NMR_RESTRAINT_AMBER = "nm-res-amb"
    NMR_TOPOLOGY_AMBER = "nm-aux-amb"
    NMR_RESTRAINT_ARIA = "nm-res-ari"
    NMR_RESTRAINT_BIOSYM = "nm-res-bio"
    NMR_RESTRAINT_CHARMM = "nm-res-cha"
    NMR_TOPOLOGY_CHARMM = "nm-aux-cha"
    NMR_RESTRAINT_CNS = "nm-res-cns"
    NMR_RESTRAINT_CYANA = "nm-res-cya"
    NMR_RESTRAINT_DYNAMO = "nm-res-dyn"
    NMR_RESTRAINT_PALES = "nm-res-dyn"
    NMR_RESTRAINT_TALOS = "nm-res-dyn"
    NMR_RESTRAINT_GROMACS = "nm-res-gro"
    NMR_TOPOLOGY_GROMACS = "nm-aux-gro"
    NMR_RESTRAINT_ISD = "nm-res-isd"
    NMR_RESTRAINT_ROSETTA = "nm-res-ros"
    NMR_RESTRAINT_SCHRODINGER = "nm-res-sch"
    NMR_RESTRAINT_SYBYL = "nm-res-syb"
    NMR_RESTRAINT_XPLOR = "nm-res-xpl"
    NMR_RESTRAINT_OTHER = "nm-res-oth"
    NMR_SPECTRAL_PEAK = "nm-pea-any"
    NMR_UNIFIED_NEF = "nm-uni-nef"
    NMR_UNIFIED_STAR = "nm-uni-str"

API response models

onedep_lib.apis.deposit.models.DepositStatus dataclass

Source code in src/onedep_lib/apis/deposit/models.py
@dataclass
class DepositStatus:
    status: str
    action: str
    step: str
    details: str
    date: datetime

    def __post_init__(self) -> None:
        self.status = str(self.status)
        self.action = str(self.action)
        self.step = str(self.step)
        self.details = str(self.details)
        if isinstance(self.date, str):
            self.date = datetime.fromisoformat(self.date)

onedep_lib.apis.deposit.models.DepositError dataclass

Source code in src/onedep_lib/apis/deposit/models.py
@dataclass
class DepositError:
    code: str
    message: str
    extras: str | None = None

    def __post_init__(self) -> None:
        self.code = str(self.code)
        self.message = str(self.message)

onedep_lib.apis.deposit.enums.Status

Bases: Enum

Source code in src/onedep_lib/apis/deposit/enums.py
class Status(enum.Enum):
    # Values are never used for parsing; the API sends names (e.g. "DEP").
    # Parse with Status["DEP"], not Status("1").
    DEP = enum.auto()
    PROC = enum.auto()
    AUTH = enum.auto()
    REPL = enum.auto()
    AUCO = enum.auto()
    AUXS = enum.auto()
    AUXU = enum.auto()
    HOLD = enum.auto()
    HPUB = enum.auto()
    OBS = enum.auto()
    POLC = enum.auto()
    REL = enum.auto()
    REUP = enum.auto()
    WAIT = enum.auto()
    WDRN = enum.auto()

Exceptions

onedep_lib.exceptions.OneDepError

Bases: Exception

Base exception for all onedep_lib errors.

Source code in src/onedep_lib/exceptions.py
class OneDepError(Exception):
    """Base exception for all onedep_lib errors."""

onedep_lib.exceptions.ApiError

Bases: OneDepError

HTTP error from the OneDep API.

status_code is the status the API responded with. It is None when no response was received at all -- see ApiUnreachableError.

Source code in src/onedep_lib/exceptions.py
class ApiError(OneDepError):
    """HTTP error from the OneDep API.

    ``status_code`` is the status the API responded with. It is None when no
    response was received at all -- see ApiUnreachableError.
    """

    def __init__(self, message: str, status_code: int | None) -> None:
        super().__init__(message)
        self.status_code = status_code

onedep_lib.exceptions.ApiUnreachableError

Bases: ApiError

The OneDep API could not be reached.

Raised for transport-level failures -- DNS failure, connection refused, TLS error, timeout -- where no HTTP response was received, so there is no status code to report and status_code is None.

Subclasses ApiError so that existing except ApiError handlers keep catching these. Catch it specifically to tell "the server was never reached" apart from a response the server actually sent, such as a genuine 401/403. Callers that need to decide whether the user's credentials are bad must make that distinction: an offline client is not an unauthorized one.

Source code in src/onedep_lib/exceptions.py
class ApiUnreachableError(ApiError):
    """The OneDep API could not be reached.

    Raised for transport-level failures -- DNS failure, connection refused, TLS
    error, timeout -- where no HTTP response was received, so there is no status
    code to report and ``status_code`` is None.

    Subclasses ApiError so that existing ``except ApiError`` handlers keep
    catching these. Catch it specifically to tell "the server was never reached"
    apart from a response the server actually sent, such as a genuine 401/403.
    Callers that need to decide whether the user's credentials are bad must make
    that distinction: an offline client is not an unauthorized one.
    """

    def __init__(self, message: str = "Failed to access the API") -> None:
        super().__init__(message, None)

onedep_lib.exceptions.DepositApiException = ApiError module-attribute

Authentication

onedep_lib.auths.token.TokenStore

Manages the OneDep access/refresh token lifecycle for a DepositConfig.

Tokens are persisted in the [auths.<fqdn>] section of the config file, keyed by the FQDN derived from the active hostname. Access tokens are short-lived JWTs (30-minute TTL); refresh tokens are long-lived opaque strings (30-day TTL) that rotate on every use.

Source code in src/onedep_lib/auths/token.py
class TokenStore:
    """Manages the OneDep access/refresh token lifecycle for a DepositConfig.

    Tokens are persisted in the ``[auths.<fqdn>]`` section of the config
    file, keyed by the FQDN derived from the active hostname. Access tokens
    are short-lived JWTs (30-minute TTL); refresh tokens are long-lived
    opaque strings (30-day TTL) that rotate on every use.
    """

    def __init__(self, config: DepositConfig) -> None:
        self._config = config
        self._entries = self._load_auth_entries()
        active_key = self._fqdn_key()
        if self._config.refresh_token is not None:
            entry = {"refresh_token": self._config.refresh_token}
            if self._config.access_token is not None:
                entry["access_token"] = self._config.access_token
            self._entries[active_key] = entry

    def store_tokens(self, access_token: str, refresh_token: str) -> None:
        """Persist a token pair for the config's current hostname.

        Writes both tokens to the [auths.<fqdn>] section of the config file
        and updates the in-memory config fields.

        Args:
            access_token: The access token to store.
            refresh_token: The refresh token to store.
        """
        self._store_tokens_for_key(self._fqdn_key(), access_token, refresh_token)

    def get_access_token(self) -> str:
        """Return a valid access token, refreshing it first if necessary.

        Returns:
            A non-expired access token for the config's current hostname.

        Raises:
            AuthError: If no refresh token is stored, or refresh fails
                because the refresh token is expired, revoked, or invalid.
            ApiUnreachableError: If a refresh is needed and the request cannot
                reach the server.
            ApiError: If a refresh is needed and the server returns an
                unexpected error response.
        """
        entry = self._read_entry()
        token = entry.get("access_token")
        if token is None or self._is_expired(token):
            return self.refresh()
        return token

    def refresh(self) -> str:
        """Exchange the stored refresh token for a new token pair.

        The rotated refresh token replaces the old one; refresh token
        rotation is mandatory on every call.

        Returns:
            The new access token.

        Raises:
            AuthError: If no refresh token is stored, or the server rejects
                the refresh token as expired, revoked, or invalid.
            ApiUnreachableError: If the refresh request cannot reach the server.
            ApiError: If the server returns an unexpected error response.
        """
        entry = self._read_entry()
        access_token, refresh_token = self._request_refresh(self._config.hostname, entry["refresh_token"])
        self.store_tokens(access_token, refresh_token)
        return access_token

    def activate_site(self, site_base_url: str) -> str:
        """Switch the active hostname to a redirected deposition site.

        If credentials already exist for site_base_url, refreshes them
        against that site. Otherwise exchanges the current refresh token for
        a token pair scoped to that site via its /auth/tokens/exchange
        endpoint. Updates config.hostname to site_base_url on success.

        Args:
            site_base_url: The deposition site root URL to activate.

        Returns:
            A valid access token for site_base_url.

        Raises:
            ConfigError: If site_base_url cannot be converted to a valid FQDN key.
            AuthError: If the exchange/refresh request is rejected.
            ApiUnreachableError: If the request cannot reach the server.
            ApiError: If the server returns an unexpected error response.
        """
        key = _hostname_to_fqdn_key(site_base_url)
        if not key:
            raise ConfigError(f"Invalid hostname for token storage: {site_base_url!r}")
        self._entries = self._load_auth_entries() | self._entries
        entry = self._entries.get(key)
        if entry is None:
            entry = self._read_entry()
            access_token, refresh_token = self._request_exchange(site_base_url, entry["refresh_token"])
        else:
            access_token, refresh_token = self._request_refresh(site_base_url, entry["refresh_token"])
        self._config.hostname = site_base_url
        self._store_tokens_for_key(key, access_token, refresh_token)
        return access_token

    def revoke(self) -> None:
        """Revoke the current refresh token on the server and clear it locally.

        Posts the current refresh token to the server's revoke endpoint.
        On success (204 No Content), removes the [auths.<fqdn>] entry from
        the config file and clears the in-memory token fields via
        clear_tokens(). After revocation, get_access_token() raises
        AuthError until new tokens are stored.

        Raises:
            AuthError: If the server rejects the revoke request (401/403).
            ApiUnreachableError: If the request cannot reach the server.
            ApiError: If the server returns an unexpected error response.
        """
        entry = self._read_entry()
        access_token = self.get_access_token()
        try:
            response = requests.post(
                self._url(_REVOKE_PATH),
                headers={"Authorization": f"Bearer {access_token}"},
                json={"refresh_token": entry["refresh_token"]},
                verify=self._config.ssl_verify,
                timeout=30,
            )
        except requests.RequestException as exc:
            raise ApiUnreachableError(f"Token revoke failed: {exc}") from exc

        if response.status_code in (401, 403):
            raise AuthError("Token revoke was rejected; credentials are expired, revoked, or invalid.")
        if response.status_code != 204:
            raise ApiError(f"Token revoke failed with status {response.status_code}", response.status_code)
        self.clear_tokens()

    def clear_tokens(self) -> None:
        """Clear stored tokens locally without contacting the server.

        Removes the in-memory access/refresh tokens and deletes the
        [auths.<fqdn>] entry for the config's current hostname from the
        config file.
        """
        self._config.access_token = None
        self._config.refresh_token = None
        key = self._fqdn_key()
        self._config.delete_auth_entry(key)
        self._entries.pop(key, None)

    def _load_auth_entries(self) -> dict[str, dict[str, str]]:
        raw_entries = self._config.read_auth_entries()
        entries: dict[str, dict[str, str]] = {}
        for key, entry in raw_entries.items():
            access_token = entry.get("access_token")
            refresh_token = entry.get("refresh_token")
            if access_token is not None and not isinstance(access_token, str):
                raise ConfigError(f"Malformed token data in [auths.{key}]")
            if refresh_token is not None and not isinstance(refresh_token, str):
                raise ConfigError(f"Malformed token data in [auths.{key}]")
            if refresh_token is None:
                continue
            values = {"refresh_token": refresh_token}
            if access_token is not None:
                values["access_token"] = access_token
            entries[key] = values
        return entries

    def _store_tokens_for_key(self, key: str, access_token: str, refresh_token: str) -> None:
        self._config.write_auth_entry(
            key,
            {"access_token": access_token, "refresh_token": refresh_token},
        )
        self._entries[key] = {"access_token": access_token, "refresh_token": refresh_token}
        self._config.access_token = access_token
        self._config.refresh_token = refresh_token

    def _request_refresh(self, hostname: str, refresh_token: str) -> tuple[str, str]:
        return self._request_token_pair(hostname, _REFRESH_PATH, refresh_token, "refresh")

    def _request_exchange(self, hostname: str, refresh_token: str) -> tuple[str, str]:
        return self._request_token_pair(hostname, _EXCHANGE_PATH, refresh_token, "exchange")

    def _request_token_pair(
        self,
        hostname: str,
        path: str,
        refresh_token: str,
        operation: str,
    ) -> tuple[str, str]:
        try:
            response = requests.post(
                self._url_for(hostname, path),
                json={"refresh_token": refresh_token},
                verify=self._config.ssl_verify,
                timeout=30,
            )
        except requests.RequestException as exc:
            raise ApiUnreachableError(f"Token {operation} failed: {exc}") from exc

        if response.status_code in (401, 403):
            raise AuthError("Refresh token is expired, revoked, or invalid; generate and paste a new token pair.")

        try:
            response.raise_for_status()
            body = response.json()
        except Exception as exc:
            raise ApiError(f"Token {operation} failed: {exc}", response.status_code) from exc

        access_token = body.get("access_token")
        refresh_token_out = body.get("refresh_token")
        if not isinstance(access_token, str) or not isinstance(refresh_token_out, str):
            raise ApiError(
                f"Token {operation} response missing access_token or refresh_token",
                response.status_code,
            )
        return access_token, refresh_token_out

    def _read_entry(self) -> dict[str, str]:
        key = self._fqdn_key()
        entry = self._entries.get(key)
        config_entry = self._entry_from_config()
        if config_entry is not None and (
            entry is None or config_entry.get("refresh_token") != entry.get("refresh_token")
        ):
            entry = config_entry
            self._entries[key] = entry
        if entry is None:
            access_token = self._config.access_token
            refresh_token = self._config.refresh_token
            if refresh_token is None:
                raise AuthError("No refresh token stored. Paste a refresh token first.")
            entry = {"refresh_token": refresh_token}
            if access_token is not None:
                entry["access_token"] = access_token
            self._entries[key] = entry
        if entry.get("refresh_token") is None:
            raise AuthError("No refresh token stored. Paste a refresh token first.")
        return dict(entry)

    def _entry_from_config(self) -> dict[str, str] | None:
        refresh_token = self._config.refresh_token
        if refresh_token is None:
            return None
        entry = {"refresh_token": refresh_token}
        if self._config.access_token is not None:
            entry["access_token"] = self._config.access_token
        return entry

    def _fqdn_key(self) -> str:
        key = _hostname_to_fqdn_key(self._config.hostname)
        if not key:
            raise ConfigError(f"Invalid hostname for token storage: {self._config.hostname!r}")
        return key

    def _url(self, path: str) -> str:
        return self._url_for(self._config.hostname, path)

    def _url_for(self, hostname: str, path: str) -> str:
        base = hostname.rstrip("/") + "/"
        return urljoin(base, path)

    def _is_expired(self, token: str) -> bool:
        try:
            payload = pyjwt.decode(
                token,
                options={"verify_signature": False},
                algorithms=["HS256", "RS256", "none"],
            )
            exp = payload.get("exp")
            return exp is None or not isinstance(exp, (int, float)) or exp < time.time() + 60
        except Exception:
            return True

activate_site(site_base_url)

Switch the active hostname to a redirected deposition site.

If credentials already exist for site_base_url, refreshes them against that site. Otherwise exchanges the current refresh token for a token pair scoped to that site via its /auth/tokens/exchange endpoint. Updates config.hostname to site_base_url on success.

Parameters:

Name Type Description Default
site_base_url str

The deposition site root URL to activate.

required

Returns:

Type Description
str

A valid access token for site_base_url.

Raises:

Type Description
ConfigError

If site_base_url cannot be converted to a valid FQDN key.

AuthError

If the exchange/refresh request is rejected.

ApiUnreachableError

If the request cannot reach the server.

ApiError

If the server returns an unexpected error response.

Source code in src/onedep_lib/auths/token.py
def activate_site(self, site_base_url: str) -> str:
    """Switch the active hostname to a redirected deposition site.

    If credentials already exist for site_base_url, refreshes them
    against that site. Otherwise exchanges the current refresh token for
    a token pair scoped to that site via its /auth/tokens/exchange
    endpoint. Updates config.hostname to site_base_url on success.

    Args:
        site_base_url: The deposition site root URL to activate.

    Returns:
        A valid access token for site_base_url.

    Raises:
        ConfigError: If site_base_url cannot be converted to a valid FQDN key.
        AuthError: If the exchange/refresh request is rejected.
        ApiUnreachableError: If the request cannot reach the server.
        ApiError: If the server returns an unexpected error response.
    """
    key = _hostname_to_fqdn_key(site_base_url)
    if not key:
        raise ConfigError(f"Invalid hostname for token storage: {site_base_url!r}")
    self._entries = self._load_auth_entries() | self._entries
    entry = self._entries.get(key)
    if entry is None:
        entry = self._read_entry()
        access_token, refresh_token = self._request_exchange(site_base_url, entry["refresh_token"])
    else:
        access_token, refresh_token = self._request_refresh(site_base_url, entry["refresh_token"])
    self._config.hostname = site_base_url
    self._store_tokens_for_key(key, access_token, refresh_token)
    return access_token

clear_tokens()

Clear stored tokens locally without contacting the server.

Removes the in-memory access/refresh tokens and deletes the [auths.] entry for the config's current hostname from the config file.

Source code in src/onedep_lib/auths/token.py
def clear_tokens(self) -> None:
    """Clear stored tokens locally without contacting the server.

    Removes the in-memory access/refresh tokens and deletes the
    [auths.<fqdn>] entry for the config's current hostname from the
    config file.
    """
    self._config.access_token = None
    self._config.refresh_token = None
    key = self._fqdn_key()
    self._config.delete_auth_entry(key)
    self._entries.pop(key, None)

get_access_token()

Return a valid access token, refreshing it first if necessary.

Returns:

Type Description
str

A non-expired access token for the config's current hostname.

Raises:

Type Description
AuthError

If no refresh token is stored, or refresh fails because the refresh token is expired, revoked, or invalid.

ApiUnreachableError

If a refresh is needed and the request cannot reach the server.

ApiError

If a refresh is needed and the server returns an unexpected error response.

Source code in src/onedep_lib/auths/token.py
def get_access_token(self) -> str:
    """Return a valid access token, refreshing it first if necessary.

    Returns:
        A non-expired access token for the config's current hostname.

    Raises:
        AuthError: If no refresh token is stored, or refresh fails
            because the refresh token is expired, revoked, or invalid.
        ApiUnreachableError: If a refresh is needed and the request cannot
            reach the server.
        ApiError: If a refresh is needed and the server returns an
            unexpected error response.
    """
    entry = self._read_entry()
    token = entry.get("access_token")
    if token is None or self._is_expired(token):
        return self.refresh()
    return token

refresh()

Exchange the stored refresh token for a new token pair.

The rotated refresh token replaces the old one; refresh token rotation is mandatory on every call.

Returns:

Type Description
str

The new access token.

Raises:

Type Description
AuthError

If no refresh token is stored, or the server rejects the refresh token as expired, revoked, or invalid.

ApiUnreachableError

If the refresh request cannot reach the server.

ApiError

If the server returns an unexpected error response.

Source code in src/onedep_lib/auths/token.py
def refresh(self) -> str:
    """Exchange the stored refresh token for a new token pair.

    The rotated refresh token replaces the old one; refresh token
    rotation is mandatory on every call.

    Returns:
        The new access token.

    Raises:
        AuthError: If no refresh token is stored, or the server rejects
            the refresh token as expired, revoked, or invalid.
        ApiUnreachableError: If the refresh request cannot reach the server.
        ApiError: If the server returns an unexpected error response.
    """
    entry = self._read_entry()
    access_token, refresh_token = self._request_refresh(self._config.hostname, entry["refresh_token"])
    self.store_tokens(access_token, refresh_token)
    return access_token

revoke()

Revoke the current refresh token on the server and clear it locally.

Posts the current refresh token to the server's revoke endpoint. On success (204 No Content), removes the [auths.] entry from the config file and clears the in-memory token fields via clear_tokens(). After revocation, get_access_token() raises AuthError until new tokens are stored.

Raises:

Type Description
AuthError

If the server rejects the revoke request (401/403).

ApiUnreachableError

If the request cannot reach the server.

ApiError

If the server returns an unexpected error response.

Source code in src/onedep_lib/auths/token.py
def revoke(self) -> None:
    """Revoke the current refresh token on the server and clear it locally.

    Posts the current refresh token to the server's revoke endpoint.
    On success (204 No Content), removes the [auths.<fqdn>] entry from
    the config file and clears the in-memory token fields via
    clear_tokens(). After revocation, get_access_token() raises
    AuthError until new tokens are stored.

    Raises:
        AuthError: If the server rejects the revoke request (401/403).
        ApiUnreachableError: If the request cannot reach the server.
        ApiError: If the server returns an unexpected error response.
    """
    entry = self._read_entry()
    access_token = self.get_access_token()
    try:
        response = requests.post(
            self._url(_REVOKE_PATH),
            headers={"Authorization": f"Bearer {access_token}"},
            json={"refresh_token": entry["refresh_token"]},
            verify=self._config.ssl_verify,
            timeout=30,
        )
    except requests.RequestException as exc:
        raise ApiUnreachableError(f"Token revoke failed: {exc}") from exc

    if response.status_code in (401, 403):
        raise AuthError("Token revoke was rejected; credentials are expired, revoked, or invalid.")
    if response.status_code != 204:
        raise ApiError(f"Token revoke failed with status {response.status_code}", response.status_code)
    self.clear_tokens()

store_tokens(access_token, refresh_token)

Persist a token pair for the config's current hostname.

Writes both tokens to the [auths.] section of the config file and updates the in-memory config fields.

Parameters:

Name Type Description Default
access_token str

The access token to store.

required
refresh_token str

The refresh token to store.

required
Source code in src/onedep_lib/auths/token.py
def store_tokens(self, access_token: str, refresh_token: str) -> None:
    """Persist a token pair for the config's current hostname.

    Writes both tokens to the [auths.<fqdn>] section of the config file
    and updates the in-memory config fields.

    Args:
        access_token: The access token to store.
        refresh_token: The refresh token to store.
    """
    self._store_tokens_for_key(self._fqdn_key(), access_token, refresh_token)

onedep_lib.auths.types.AuthProvider

Bases: Protocol

Source code in src/onedep_lib/auths/types.py
class AuthProvider(Protocol):
    def store_tokens(self, access_token: str, refresh_token: str) -> None:
        """Persist a manually supplied token pair."""
        ...

    def get_access_token(self) -> str:
        """Return a valid access token, refreshing and persisting rotation when needed."""
        ...

    def refresh(self) -> str:
        """Refresh tokens explicitly and return the new access token."""
        ...

    def revoke(self) -> None:
        """Revoke the current refresh token and clear local storage on success."""
        ...

    def clear_tokens(self) -> None:
        """Clear local token storage without contacting the server."""
        ...

clear_tokens()

Clear local token storage without contacting the server.

Source code in src/onedep_lib/auths/types.py
def clear_tokens(self) -> None:
    """Clear local token storage without contacting the server."""
    ...

get_access_token()

Return a valid access token, refreshing and persisting rotation when needed.

Source code in src/onedep_lib/auths/types.py
def get_access_token(self) -> str:
    """Return a valid access token, refreshing and persisting rotation when needed."""
    ...

refresh()

Refresh tokens explicitly and return the new access token.

Source code in src/onedep_lib/auths/types.py
def refresh(self) -> str:
    """Refresh tokens explicitly and return the new access token."""
    ...

revoke()

Revoke the current refresh token and clear local storage on success.

Source code in src/onedep_lib/auths/types.py
def revoke(self) -> None:
    """Revoke the current refresh token and clear local storage on success."""
    ...

store_tokens(access_token, refresh_token)

Persist a manually supplied token pair.

Source code in src/onedep_lib/auths/types.py
7
8
9
def store_tokens(self, access_token: str, refresh_token: str) -> None:
    """Persist a manually supplied token pair."""
    ...

Protocols

onedep_lib.apis.deposit.types.ApiClient

Bases: Protocol

Source code in src/onedep_lib/apis/deposit/types.py
class ApiClient(Protocol):
    def create_deposition(
        self,
        email: str,
        users: list[str],
        country: Country,
        experiments: list[Experiment],
        password: str = "",
    ) -> WwPDBDeposition: ...

    def get_all_depositions(self) -> list[WwPDBDeposition]: ...

    def get_deposition(self, dep_id: str) -> WwPDBDeposition: ...

    def upload_file(
        self,
        dep_id: str,
        file_path: str,
        file_type: FileType,
        overwrite: bool = False,
    ) -> DepositedFile: ...

    def update_metadata(
        self,
        dep_id: str,
        file_id: int,
        spacing_x: float,
        spacing_y: float,
        spacing_z: float,
        contour: float,
        description: str,
    ) -> DepositedFile: ...

    def get_files(self, dep_id: str) -> list[DepositedFile]: ...

    def remove_file(self, dep_id: str, file_id: int) -> bool: ...

    def get_status(self, dep_id: str) -> Union[DepositStatus, DepositError]: ...

    def process(self, dep_id: str) -> Union[DepositStatus, DepositError]: ...