mex.backend package

Subpackages

Submodules

mex.backend.exceptions module

exception mex.backend.exceptions.BackendError

Bases: MExError

Base backend error that offer details on underlying pydantic errors.

errors() list[ErrorDetails]

Details about underlying pydantic errors.

is_retryable() bool

Whether the error is retryable.

class mex.backend.exceptions.DebuggingInfo(*, errors: list[dict[str, Any]], scope: DebuggingScope)

Bases: BaseModel

Debugging information for error responses.

errors: list[dict[str, Any]]
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

scope: DebuggingScope
class mex.backend.exceptions.DebuggingScope(*, http_version: str, method: str, path: str, path_params: dict[str, Any], query_string: str, scheme: str)

Bases: BaseModel

Scope for debugging info of error responses.

http_version: str
method: str
model_config = {'extra': 'ignore'}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

path: str
path_params: dict[str, Any]
query_string: str
scheme: str
class mex.backend.exceptions.DetailedError(*args, **kwargs)

Bases: Protocol

Protocol for errors that offer details.

errors() list[ErrorDetails]

Details about each underlying error.

class mex.backend.exceptions.ErrorResponse(*, message: str, debug: DebuggingInfo)

Bases: BaseModel

Response model for user and system errors.

debug: DebuggingInfo
message: str
model_config = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

mex.backend.exceptions.handle_detailed_error(request: Request, exc: Exception) Response

Handle detailed errors and provide debugging info.

mex.backend.exceptions.handle_uncaught_exception(request: Request, exc: Exception) Response

Handle uncaught errors and provide debugging info.

mex.backend.fields module

mex.backend.logging module

mex.backend.main module

mex.backend.main.lifespan(_: FastAPI) AsyncIterator[None]

Async context manager to execute startup and teardown of the FastAPI app.

mex.backend.security module

mex.backend.security.check_header_for_authorization_method(api_key: Annotated[str | None, ~fastapi.params.Depends(dependency=<fastapi.security.api_key.APIKeyHeader object at 0x7ff4fce4e120>, use_cache=True, scope=None)] = None, credentials: HTTPBasicCredentials | None, ~fastapi.params.Depends(dependency=<fastapi.security.http.HTTPBasic object at 0x7ff4fce4e510>, use_cache=True, scope=None)] = None, user_agent: Annotated[str, Header(PydanticUndefined)]='n/a') None

Check authorization header for API key or credentials.

Raises:

HTTPException if both API key and credentials or none of them are in header.

Parameters:
  • api_key – the API key

  • credentials – username and password

  • user_agent – user-agent (in case of a web browser starts with “Mozilla/”)

mex.backend.security.has_read_access(api_key: Annotated[str | None, ~fastapi.params.Depends(dependency=<fastapi.security.api_key.APIKeyHeader object at 0x7ff4fce4e120>, use_cache=True, scope=None)] = None, credentials: HTTPBasicCredentials | None, ~fastapi.params.Depends(dependency=<fastapi.security.http.HTTPBasic object at 0x7ff4fce4e510>, use_cache=True, scope=None)] = None, user_agent: Annotated[str, Header(PydanticUndefined)]='n/a') None

Verify if api key or credentials have read access or write access.

Raises:

HTTPException if no header or provided APIKey/credentials have no read access.

Parameters:
  • api_key – the API key

  • credentials – username and password

  • user_agent – user-agent (in case of a web browser starts with “Mozilla/”)

Settings:

check credentials in backend_user_database or backend_api_key_database

mex.backend.security.has_write_access(api_key: Annotated[str | None, ~fastapi.params.Depends(dependency=<fastapi.security.api_key.APIKeyHeader object at 0x7ff4fce4e120>, use_cache=True, scope=None)] = None, credentials: HTTPBasicCredentials | None, ~fastapi.params.Depends(dependency=<fastapi.security.http.HTTPBasic object at 0x7ff4fce4e510>, use_cache=True, scope=None)] = None, user_agent: Annotated[str, Header(PydanticUndefined)]='n/a') None

Verify if provided api key or credentials have write access.

Raises:

HTTPException if no header or provided APIKey/credentials have no write access.

Parameters:
  • api_key – the API key

  • credentials – username and password

  • user_agent – user-agent (in case of a web browser starts with “Mozilla/”)

Settings:

check credentials in backend_user_database or backend_api_key_database

mex.backend.security.has_write_access_ldap(credentials: ~typing.Annotated[~fastapi.security.http.HTTPBasicCredentials, ~fastapi.params.Depends(dependency=<fastapi.security.http.HTTPBasic object at 0x7ff4fce4e510>, use_cache=True, scope=None)]) str

Verify if provided credentials have LDAP write access.

Raises:

HTTPException if credentials have no LDAP write access or are missing.

Parameters:

credentials – username and password

mex.backend.settings module

class mex.backend.settings.BackendSettings(_env_file: Path | str | Sequence[Path | str] | None = PosixPath('.'), _env_file_encoding: str | None = None, _env_nested_delimiter: str | None = None, _secrets_dir: str | Path | None = None, *, pdb: bool = False, MEX_SINK: list[Sink] = [Sink.NDJSON], MEX_ASSETS_DIR: Path = PosixPath('/home/runner/work/mex-backend/mex-backend/assets'), MEX_WORK_DIR: Path = PosixPath('/home/runner/work/mex-backend/mex-backend'), MEX_IDENTITY_PROVIDER: IdentityProvider = IdentityProvider.MEMORY, MEX_BACKEND_API_URL: HttpUrl = HttpUrl('http://localhost:8080/'), MEX_BACKEND_API_KEY: SecretStr = SecretStr('**********'), MEX_BACKEND_API_PARALLELIZATION: int = 1, MEX_BACKEND_API_CHUNK_SIZE: int = 25, MEX_VERIFY_SESSION: bool | AssetsPath = True, MEX_ORGANIGRAM_PATH: AssetsPath = AssetsPath('raw-data/organigram/organizational_units.json'), MEX_PRIMARY_SOURCES_PATH: AssetsPath = AssetsPath('raw-data/primary-sources/primary-sources.json'), MEX_LDAP_URL: SecretStr = SecretStr('**********'), MEX_LDAP_SEARCH_BASE: str = 'dc=ldapmock,dc=local', MEX_WIKI_API_URL: HttpUrl = HttpUrl('http://wikidata/'), MEX_WEB_USER_AGENT: str = 'rki/mex', MEX_ORCID_API_URL: HttpUrl = HttpUrl('https://orcid/'), MEX_BACKEND_HOST: Annotated[str, MinLen(min_length=1), MaxLen(max_length=250)] = 'localhost', MEX_BACKEND_PORT: Annotated[int, Gt(gt=0), Lt(lt=65536)] = 8080, MEX_BACKEND_ROOT_PATH: str = '', MEX_GRAPH_URL: str = 'neo4j://localhost:7687', MEX_GRAPH_NAME: str = 'neo4j', MEX_GRAPH_USER: SecretStr = SecretStr('**********'), MEX_GRAPH_PASSWORD: SecretStr = SecretStr('**********'), MEX_GRAPH_TX_TIMEOUT: int | float = 15.0, MEX_GRAPH_SESSION_TIMEOUT: int | float = 45.0, MEX_BACKEND_API_KEY_DATABASE: APIKeyDatabase = APIKeyDatabase(read=[], write=[]), MEX_BACKEND_API_USER_DATABASE: APIUserDatabase = APIUserDatabase(read={}, write={}), MEX_BACKEND_VALKEY_URL: SecretStr | None = None)

Bases: BaseSettings

Settings definition for the backend server.

backend_api_key_database: APIKeyDatabase
backend_host: str
backend_port: int
backend_root_path: str
backend_user_database: APIUserDatabase
graph_db: str
graph_password: SecretStr
graph_session_timeout: int | float
graph_tx_timeout: int | float
graph_url: str
graph_user: SecretStr
model_config = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': '.env', 'env_file_encoding': 'utf-8', 'env_ignore_empty': False, 'env_nested_delimiter': '__', 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': 'mex_', 'extra': 'ignore', 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'populate_by_name': True, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_assignment': True, 'validate_by_alias': True, 'validate_by_name': True, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

valkey_url: SecretStr | None

mex.backend.types module

class mex.backend.types.APIKey(secret_value: SecretType)

Bases: SecretStr

An API Key used for authenticating and authorizing a client.

class mex.backend.types.APIKeyDatabase(*, read: list[APIKey] = [], write: list[APIKey] = [])

Bases: BaseModel

A lookup from access level to list of allowed APIKeys.

model_config = {'extra': 'ignore', 'populate_by_name': True, 'str_max_length': 100000, 'str_min_length': 1, 'str_strip_whitespace': True, 'use_enum_values': True, 'validate_assignment': True, 'validate_by_alias': True, 'validate_by_name': True, 'validate_default': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

read: list[APIKey]
write: list[APIKey]
class mex.backend.types.APIUserDatabase(*, read: dict[str, APIUserPassword] = {}, write: dict[str, APIUserPassword] = {})

Bases: BaseModel

Database containing usernames and passwords for backend API.

model_config = {'extra': 'ignore', 'populate_by_name': True, 'str_max_length': 100000, 'str_min_length': 1, 'str_strip_whitespace': True, 'use_enum_values': True, 'validate_assignment': True, 'validate_by_alias': True, 'validate_by_name': True, 'validate_default': True}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

read: dict[str, APIUserPassword]
write: dict[str, APIUserPassword]
class mex.backend.types.APIUserPassword(secret_value: SecretType)

Bases: SecretStr

An API password used for basic authentication along with a username.

class mex.backend.types.AccessLevel(*values)

Bases: Enum

Enum of access level.

READ = 'read'
WRITE = 'write'
class mex.backend.types.DynamicStrEnum(name: str, bases: tuple[type], dct: EnumDict)

Bases: EnumType

Metaclass to dynamically populate an enumeration from a list of strings.

class mex.backend.types.ExtractedType(*values)

Bases: Enum

Enumeration of possible types for extracted items.

EXTRACTED_ACCESS_PLATFORM = 'ExtractedAccessPlatform'
EXTRACTED_ACTIVITY = 'ExtractedActivity'
EXTRACTED_BIBLIOGRAPHIC_RESOURCE = 'ExtractedBibliographicResource'
EXTRACTED_CONTACT_POINT = 'ExtractedContactPoint'
EXTRACTED_DISTRIBUTION = 'ExtractedDistribution'
EXTRACTED_ORGANIZATION = 'ExtractedOrganization'
EXTRACTED_ORGANIZATIONAL_UNIT = 'ExtractedOrganizationalUnit'
EXTRACTED_PERSON = 'ExtractedPerson'
EXTRACTED_PRIMARY_SOURCE = 'ExtractedPrimarySource'
EXTRACTED_RESOURCE = 'ExtractedResource'
EXTRACTED_VARIABLE = 'ExtractedVariable'
EXTRACTED_VARIABLE_GROUP = 'ExtractedVariableGroup'
class mex.backend.types.MergedType(*values)

Bases: Enum

Enumeration of possible types for merged items.

MERGED_ACCESS_PLATFORM = 'MergedAccessPlatform'
MERGED_ACTIVITY = 'MergedActivity'
MERGED_BIBLIOGRAPHIC_RESOURCE = 'MergedBibliographicResource'
MERGED_CONTACT_POINT = 'MergedContactPoint'
MERGED_DISTRIBUTION = 'MergedDistribution'
MERGED_ORGANIZATION = 'MergedOrganization'
MERGED_ORGANIZATIONAL_UNIT = 'MergedOrganizationalUnit'
MERGED_PERSON = 'MergedPerson'
MERGED_PRIMARY_SOURCE = 'MergedPrimarySource'
MERGED_RESOURCE = 'MergedResource'
MERGED_VARIABLE = 'MergedVariable'
MERGED_VARIABLE_GROUP = 'MergedVariableGroup'
class mex.backend.types.ReferenceFieldName(*values)

Bases: Enum

Enumeration of possible field names that contain references.

ACCESS_PLATFORM = 'accessPlatform'
ACCESS_SERVICE = 'accessService'
AFFILIATION = 'affiliation'
BELONGS_TO = 'belongsTo'
CONTACT = 'contact'
CONTAINED_BY = 'containedBy'
CONTRIBUTING_UNIT = 'contributingUnit'
CONTRIBUTOR = 'contributor'
CREATOR = 'creator'
DISTRIBUTION = 'distribution'
EDITOR = 'editor'
EDITOR_OF_SERIES = 'editorOfSeries'
EXTERNAL_ASSOCIATE = 'externalAssociate'
EXTERNAL_PARTNER = 'externalPartner'
FUNDER_OR_COMMISSIONER = 'funderOrCommissioner'
HAD_PRIMARY_SOURCE = 'hadPrimarySource'
HAS_DATA_SUBJECT = 'hasDataSubject'
INVOLVED_PERSON = 'involvedPerson'
INVOLVED_UNIT = 'involvedUnit'
IS_PART_OF = 'isPartOf'
IS_PART_OF_ACTIVITY = 'isPartOfActivity'
MEMBER_OF = 'memberOf'
PARENT_UNIT = 'parentUnit'
PUBLICATION = 'publication'
PUBLISHER = 'publisher'
RESPONSIBLE_UNIT = 'responsibleUnit'
STABLE_TARGET_ID = 'stableTargetId'
SUCCEEDS = 'succeeds'
UNIT_IN_CHARGE = 'unitInCharge'
UNIT_OF = 'unitOf'
USED_IN = 'usedIn'
WAS_GENERATED_BY = 'wasGeneratedBy'

Module contents