62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""What an adapter is, and how the runner finds one.
|
|
|
|
An adapter does I/O and parsing and nothing else: it yields records, and the
|
|
runner matches, dedups and writes them. Keeping the split means an adapter can be
|
|
tested against a captured payload with no database.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Iterator
|
|
|
|
|
|
class BaseAdapter:
|
|
"""Subclasses implement :meth:`fetch`.
|
|
|
|
The payload type is the service's own — a frame in tdas, an element set in
|
|
odms. That is the cargo, not the machine, so it is not defined here.
|
|
"""
|
|
|
|
def __init__(self, source) -> None:
|
|
self.source = source
|
|
|
|
@property
|
|
def config(self) -> dict:
|
|
return self.source.config or {}
|
|
|
|
def api_key(self) -> str | None:
|
|
"""This source's API key, or None.
|
|
|
|
An inline ``api_key`` in the config wins; otherwise ``api_key_secret``
|
|
names a Docker secret or env var. Production keeps the key out of the
|
|
database that way.
|
|
"""
|
|
direct = self.config.get("api_key")
|
|
if direct:
|
|
return str(direct).strip()
|
|
secret_name = self.config.get("api_key_secret")
|
|
if secret_name:
|
|
from yksa_kit.settings import read_secret
|
|
|
|
value = read_secret(str(secret_name))
|
|
return value.strip() if value else None
|
|
return None
|
|
|
|
def fetch(self) -> Iterator:
|
|
raise NotImplementedError
|
|
|
|
|
|
class AdapterRegistry(dict):
|
|
"""`kind` -> adapter class, with a decorator for declaring one.
|
|
|
|
A kind with no adapter is legitimate — tdas's `internal_push` is fed by an
|
|
API, odms's `internal_corrected` by a pipeline — and the runner records those
|
|
as skipped rather than failed.
|
|
"""
|
|
|
|
def register(self, kind: str):
|
|
def decorator(cls):
|
|
self[kind] = cls
|
|
return cls
|
|
|
|
return decorator
|