diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 508d74c7..c96fbbda 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -35,7 +35,7 @@ jobs: python-version: 3.11 - name: Install requirements - run: make setup + run: make install - name: Run linters run: make linter diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f2ed16a..4ff025aa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ default_language_version: python: python3.11 repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.8 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -20,26 +20,16 @@ repos: - id: check-yaml name: yaml - repo: https://github.com/python-poetry/poetry - rev: 1.7.0 + rev: 1.7.1 hooks: - id: poetry-check name: poetry - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.7.0 - hooks: # configured according to mypy maintainer: https://github.com/python/mypy/issues/13916 - - id: mypy - name: mypy - files: ^mex/ - pass_filenames: false - args: [mex] - additional_dependencies: - - "backoff>=2.2.1,<3" - - "click>=8.1.7,<9" - - "pandas-stubs>=2.1.1,<3" - - "pydantic-settings>=2.1.0,<3" - - "pydantic>=2.5.1,<3" - - "pytest>=7.4.3,<8" - - "types-ldap3>=2.9.13.15,<3" - - "types-pytz>=2023.3.1.1,<2024" - - "types-requests>=2.31.0.10,<3" - - "types-setuptools>=68.2.0.1,<69" + - repo: local + hooks: + - id: mypy + name: mypy + entry: poetry run dmypy run --timeout 7200 -- mex + files: ^mex/ + language: system + pass_filenames: false + types: [python] diff --git a/README.md b/README.md index b318435d..7ff9ffc4 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,6 @@ MEx project will be released under the same license in the future. - update global dependencies in `requirements.txt` manually - update git hooks with `pre-commit autoupdate` -- update git hook additional dependencies manually - show outdated dependencies with `poetry show --outdated` - update dependencies in poetry using `poetry update --lock` - update github actions manually in `.github\workflows\default.yml` diff --git a/mex/common/ldap/models/person.py b/mex/common/ldap/models/person.py index 12784e73..ecef8e1b 100644 --- a/mex/common/ldap/models/person.py +++ b/mex/common/ldap/models/person.py @@ -7,14 +7,14 @@ class LDAPPerson(LDAPActor): """Model class for LDAP persons.""" - company: str | None = Field(None) - department: str | None = Field(None) - departmentNumber: str | None = Field(None) - displayName: str | None = Field(None) - employeeID: str = Field(...) - givenName: list[str] = Field(..., min_length=1) - ou: list[str] = Field([]) - sn: str = Field(...) + company: str | None = None + department: str | None = None + departmentNumber: str | None = None + displayName: str | None = None + employeeID: str + givenName: list[str] = Field(min_length=1) + ou: list[str] = [] + sn: str @classmethod def get_ldap_fields(cls) -> tuple[str, ...]: diff --git a/mex/common/models/access_platform.py b/mex/common/models/access_platform.py index fbc24e91..339f51f9 100644 --- a/mex/common/models/access_platform.py +++ b/mex/common/models/access_platform.py @@ -1,3 +1,5 @@ +from typing import Annotated + from pydantic import Field from mex.common.models.base import BaseModel @@ -5,27 +7,16 @@ from mex.common.models.merged_item import MergedItem from mex.common.types import ( AccessPlatformID, + APIType, ContactPointID, Link, OrganizationalUnitID, PersonID, + TechnicalAccessibility, Text, - VocabularyEnum, ) -class TechnicalAccessibility(VocabularyEnum): - """Technical accessibility within RKI and outside of RKI.""" - - __vocabulary__ = "technical-accessibility" - - -class APIType(VocabularyEnum): - """Technical standard or style of a network API.""" - - __vocabulary__ = "api-type" - - class BaseAccessPlatform(BaseModel): """A way of physically accessing the Resource for re-use.""" @@ -34,14 +25,15 @@ class BaseAccessPlatform(BaseModel): contact: list[OrganizationalUnitID | PersonID | ContactPointID] = [] description: list[Text] = [] endpointDescription: Link | None = None - endpointType: APIType | None = Field( - None, examples=["https://mex.rki.de/item/api-type-1"] - ) + endpointType: Annotated[ + APIType, Field(examples=["https://mex.rki.de/item/api-type-1"]) + ] | None = None endpointURL: Link | None = None landingPage: list[Link] = [] - technicalAccessibility: TechnicalAccessibility = Field( - ..., examples=["https://mex.rki.de/item/technical-accessibility-1"] - ) + technicalAccessibility: Annotated[ + TechnicalAccessibility, + Field(examples=["https://mex.rki.de/item/technical-accessibility-1"]), + ] title: list[Text] = [] unitInCharge: list[OrganizationalUnitID] = [] diff --git a/mex/common/models/activity.py b/mex/common/models/activity.py index 3aa9316f..f095aeeb 100644 --- a/mex/common/models/activity.py +++ b/mex/common/models/activity.py @@ -7,8 +7,8 @@ from mex.common.models.merged_item import MergedItem from mex.common.types import ( ActivityID, + ActivityType, ContactPointID, - Identifier, Link, OrganizationalUnitID, OrganizationID, @@ -16,16 +16,9 @@ Text, Theme, Timestamp, - VocabularyEnum, ) -class ActivityType(VocabularyEnum): - """The activity type.""" - - __vocabulary__ = "activity-type" - - class BaseActivity(BaseModel): """The context a resource was generated in. @@ -40,35 +33,21 @@ class BaseActivity(BaseModel): ] ] = [] alternativeTitle: list[Text] = [] - contact: list[ - Annotated[ - OrganizationalUnitID | PersonID | ContactPointID, - Field(examples=[Identifier.generate(seed=42)]), - ] - ] = Field( - ..., - min_length=1, - ) + contact: Annotated[ + list[OrganizationalUnitID | PersonID | ContactPointID,], Field(min_length=1) + ] documentation: list[Link] = [] end: list[ - Annotated[ - Timestamp, - Field( - examples=["2024-01-17", "2024", "2024-01"], - ), - ] + Annotated[Timestamp, Field(examples=["2024-01-17", "2024", "2024-01"])] ] = [] - externalAssociate: list[OrganizationalUnitID | PersonID] = [] + externalAssociate: list[OrganizationID | PersonID] = [] funderOrCommissioner: list[OrganizationID] = [] fundingProgram: list[str] = [] involvedPerson: list[PersonID] = [] involvedUnit: list[OrganizationalUnitID] = [] isPartOfActivity: list[ActivityID] = [] publication: list[Link] = [] - responsibleUnit: list[OrganizationalUnitID] = Field( - ..., - min_length=1, - ) + responsibleUnit: Annotated[list[OrganizationalUnitID], Field(min_length=1)] shortName: list[Text] = [] start: list[ Annotated[Timestamp, Field(examples=["2023-01-16", "2023", "2023-02"])] @@ -77,7 +56,7 @@ class BaseActivity(BaseModel): theme: list[ Annotated[Theme, Field(examples=["https://mex.rki.de/item/theme-1"])] ] = [] - title: list[Text] = Field(..., min_length=1) + title: Annotated[list[Text], Field(min_length=1)] website: list[Link] = [] diff --git a/mex/common/models/base.py b/mex/common/models/base.py index 4fa408e8..c68faf2f 100644 --- a/mex/common/models/base.py +++ b/mex/common/models/base.py @@ -2,17 +2,39 @@ import pickle # nosec from collections.abc import MutableMapping from functools import cache -from typing import TYPE_CHECKING, Any, Optional, TypeVar, Union, get_args, get_origin +from typing import TYPE_CHECKING, Annotated, Any, TypeVar, Union, get_args, get_origin -from pydantic import BaseModel as PydanticBaseModel +from pydantic import ( + BaseModel as PydanticBaseModel, +) from pydantic import ConfigDict, Field, TypeAdapter, ValidationError, model_validator from pydantic.fields import FieldInfo +from pydantic.json_schema import DEFAULT_REF_TEMPLATE, JsonSchemaMode, JsonSchemaValue +from pydantic.json_schema import ( + GenerateJsonSchema as PydanticJsonSchemaGenerator, +) from mex.common.types import Identifier RawModelDataT = TypeVar("RawModelDataT") +class JsonSchemaGenerator(PydanticJsonSchemaGenerator): + """Customization of the pydantic class for generating JSON schemas.""" + + def handle_ref_overrides(self, json_schema: JsonSchemaValue) -> JsonSchemaValue: + """Disable pydantic behavior to wrap top-level `$ref` keys in an `allOf`. + + For example, pydantic would convert + {"$ref": "#/$defs/APIType", "examples": ["api-type-1"]} + into + {"allOf": {"$ref": "#/$defs/APIType"}, "examples": ["api-type-1"]} + which is in fact recommended by JSON schema, but we need to disable this + to stay compatible with mex-editor and mex-model. + """ + return json_schema + + class BaseModel(PydanticBaseModel): """Common base class for all MEx model classes.""" @@ -27,6 +49,32 @@ class BaseModel(PydanticBaseModel): validate_assignment=True, ) + @classmethod + def model_json_schema( + cls, + by_alias: bool = True, + ref_template: str = DEFAULT_REF_TEMPLATE, + schema_generator: type[PydanticJsonSchemaGenerator] = JsonSchemaGenerator, + mode: JsonSchemaMode = "validation", + ) -> dict[str, Any]: + """Generates a JSON schema for a model class. + + Args: + by_alias: Whether to use attribute aliases or not. + ref_template: The reference template. + schema_generator: Overriding the logic used to generate the JSON schema + mode: The mode in which to generate the schema. + + Returns: + The JSON schema for the given model class. + """ + return super().model_json_schema( + by_alias=by_alias, + ref_template=ref_template, + schema_generator=schema_generator, + mode=mode, + ) + @classmethod @cache def _get_alias_lookup(cls) -> dict[str, str]: @@ -73,7 +121,7 @@ def _get_field_names_allowing_none(cls) -> list[str]: @classmethod def _convert_non_list_to_list( cls, name: str, field: FieldInfo, value: Any - ) -> Optional[list[Any]]: + ) -> list[Any] | None: """Convert a non-list value to a list value by wrapping it in a list.""" if value is None: if name in cls._get_field_names_allowing_none(): @@ -171,12 +219,13 @@ class MExModel(BaseModel): # also used as the foreign key for all fields containing references. stableTargetId: Any - identifier: Identifier = Field( - ..., - description=( - "A globally unique identifier for this item. Regardless of the entity-type " - "or whether this item was extracted, merged, etc. identifiers will be " - "assigned just once." + identifier: Annotated[ + Identifier, + Field( + description=( + "A globally unique identifier for this item. Regardless of the " + "entity-type or whether this item was extracted, merged, etc. " + "identifiers will be assigned just once." + ), ), - examples=[Identifier.generate(seed=42)], - ) + ] diff --git a/mex/common/models/contact_point.py b/mex/common/models/contact_point.py index de61d98c..5b315bd3 100644 --- a/mex/common/models/contact_point.py +++ b/mex/common/models/contact_point.py @@ -12,17 +12,7 @@ class BaseContactPoint(BaseModel): """A contact point - for example, an interdepartmental project.""" stableTargetId: ContactPointID - email: list[ - Annotated[ - Email, - Field( - examples=["info@rki.de"], - ), - ] - ] = Field( - ..., - min_length=1, - ) + email: Annotated[list[Email], Field(min_length=1)] class ExtractedContactPoint(BaseContactPoint, ExtractedData): diff --git a/mex/common/models/distribution.py b/mex/common/models/distribution.py index 91ff6010..15b32943 100644 --- a/mex/common/models/distribution.py +++ b/mex/common/models/distribution.py @@ -1,3 +1,5 @@ +from typing import Annotated + from pydantic import Field from mex.common.models.base import BaseModel @@ -7,28 +9,24 @@ AccessPlatformID, AccessRestriction, DistributionID, + License, Link, + MIMEType, + OrganizationID, PersonID, Timestamp, - VocabularyEnum, ) -class MIMEType(VocabularyEnum): - """The mime type.""" - - __vocabulary__ = "mime-type" - - class BaseDistribution(BaseModel): """A specific representation of a dataset.""" stableTargetId: DistributionID accessService: AccessPlatformID | None = None - accessRestriction: AccessRestriction = Field( - ..., - examples=["https://mex.rki.de/item/access-restriction-1"], - ) + accessRestriction: Annotated[ + AccessRestriction, + Field(examples=["https://mex.rki.de/item/access-restriction-1"]), + ] accessURL: Link | None = None author: list[PersonID] = [] contactPerson: list[PersonID] = [] @@ -36,22 +34,28 @@ class BaseDistribution(BaseModel): dataManager: list[PersonID] = [] downloadURL: Link | None = None issued: Timestamp - license: list[Link] = [] - mediaType: MIMEType | None = Field( - None, - examples=["https://mex.rki.de/item/mime-type-1"], - ) - modified: list[Timestamp] = [] + license: Annotated[ + License, Field(examples=["https://mex.rki.de/item/license-1"]) + ] | None = None + mediaType: Annotated[ + MIMEType, + Field( + examples=["https://mex.rki.de/item/mime-type-1"], + ), + ] | None = None + modified: Timestamp | None = None otherContributor: list[PersonID] = [] projectLeader: list[PersonID] = [] projectManager: list[PersonID] = [] - publisher: list[PersonID] = Field(..., min_length=1) + publisher: Annotated[list[OrganizationID], Field(min_length=1)] researcher: list[PersonID] = [] - title: str = Field( - ..., - examples=["theNameOfTheFile"], - min_length=1, - ) + title: Annotated[ + str, + Field( + examples=["theNameOfTheFile"], + min_length=1, + ), + ] class ExtractedDistribution(BaseDistribution, ExtractedData): diff --git a/mex/common/models/extracted_data.py b/mex/common/models/extracted_data.py index 5abaa8b9..485508c0 100644 --- a/mex/common/models/extracted_data.py +++ b/mex/common/models/extracted_data.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Annotated, Any from pydantic import Field, model_validator @@ -19,31 +19,34 @@ class BaseExtractedData(MExModel): correct type, e.g. `PersonID`. """ - hadPrimarySource: PrimarySourceID = Field( - ..., - description=( - "The stableTargetID of the primary source, that this item was extracted " - "from. This field is mandatory for all extracted items to aid with data " - "provenance. Extracted primary sources also have this field and are all " - "extracted from a primary source called MEx, which is its own primary " - "source and has the static stableTargetID: " - f"{MEX_PRIMARY_SOURCE_STABLE_TARGET_ID}" + hadPrimarySource: Annotated[ + PrimarySourceID, + Field( + description=( + "The stableTargetID of the primary source, that this item was " + "extracted from. This field is mandatory for all extracted items to " + "aid with data provenance. Extracted primary sources also have this " + "field and are all extracted from a primary source called MEx, which " + "is its own primary source and has the static stableTargetID: " + f"{MEX_PRIMARY_SOURCE_STABLE_TARGET_ID}" + ), ), - examples=[PrimarySourceID.generate(seed=42)], - ) - identifierInPrimarySource: str = Field( - ..., - description=( - "This is the identifier the original item had in its source system. " - "It is only unique amongst items coming from the same system, because " - "identifier formats are likely to overlap between systems. " - "The value for `identifierInPrimarySource` is therefore only unique in " - "composition with `hadPrimarySource`. MEx uses this composite key " - "to assign a stable and globally unique `identifier` to each item." + ] + identifierInPrimarySource: Annotated[ + str, + Field( + description=( + "This is the identifier the original item had in its source system. " + "It is only unique amongst items coming from the same system, because " + "identifier formats are likely to overlap between systems. " + "The value for `identifierInPrimarySource` is therefore only unique in " + "composition with `hadPrimarySource`. MEx uses this composite key " + "to assign a stable and globally unique `identifier` to each item." + ), + examples=["123456", "item-501", "D7/x4/zz.final3"], + min_length=1, ), - examples=["123456", "item-501", "D7/x4/zz.final3"], - min_length=1, - ) + ] def __str__(self) -> str: """Format this extracted data instance as a string for logging.""" diff --git a/mex/common/models/organization.py b/mex/common/models/organization.py index 1a4d64b1..3b522e84 100644 --- a/mex/common/models/organization.py +++ b/mex/common/models/organization.py @@ -22,6 +22,7 @@ class BaseOrganization(BaseModel): Field( pattern=r"^https://gepris\.dfg\.de/gepris/institution/[0-9]{1,64}$", examples=["https://gepris.dfg.de/gepris/institution/10179"], + json_schema_extra={"format": "uri"}, ), ] ] = [] @@ -31,6 +32,7 @@ class BaseOrganization(BaseModel): Field( pattern=r"^https://d\-nb\.info/gnd/[-X0-9]{3,10}$", examples=["https://d-nb.info/gnd/17690-4"], + json_schema_extra={"format": "uri"}, ), ] ] = [] @@ -40,16 +42,18 @@ class BaseOrganization(BaseModel): Field( pattern=r"^https://isni\.org/isni/[X0-9]{16}$", examples=["https://isni.org/isni/0000000109403744"], + json_schema_extra={"format": "uri"}, ), ] ] = [] - officialName: list[Text] = Field(..., min_length=1) + officialName: Annotated[list[Text], Field(min_length=1)] rorId: list[ Annotated[ str, Field( pattern=r"^https://ror\.org/[a-z0-9]{9}$", examples=["https://ror.org/01k5qnb77"], + json_schema_extra={"format": "uri"}, ), ] ] = [] @@ -60,16 +64,20 @@ class BaseOrganization(BaseModel): Field( pattern=r"^https://viaf\.org/viaf/[0-9]{2,22}$", examples=["https://viaf.org/viaf/123556639"], + json_schema_extra={"format": "uri"}, + ), + ] + ] = [] + wikidataId: list[ + Annotated[ + str, + Field( + examples=["http://www.wikidata.org/entity/Q679041"], + pattern=r"^https://www\.wikidata\.org/entity/[PQ0-9]{2,64}$", + json_schema_extra={"format": "uri"}, ), ] ] = [] - wikidataId: Annotated[ - str, - Field( - examples=["http://www.wikidata.org/entity/Q679041"], - pattern=r"^https://www\.wikidata\.org/entity/[PQ0-9]{2,64}$", - ), - ] | None = None class ExtractedOrganization(BaseOrganization, ExtractedData): diff --git a/mex/common/models/organizational_unit.py b/mex/common/models/organizational_unit.py index 8db28c5c..6c24269d 100644 --- a/mex/common/models/organizational_unit.py +++ b/mex/common/models/organizational_unit.py @@ -13,15 +13,8 @@ class BaseOrganizationalUnit(BaseModel): stableTargetId: OrganizationalUnitID alternativeName: list[Text] = [] - email: list[ - Annotated[ - Email, - Field( - examples=["info@rki.de"], - ), - ] - ] = [] - name: list[Text] = Field(..., min_length=1) + email: list[Email] = [] + name: Annotated[list[Text], Field(min_length=1)] parentUnit: OrganizationalUnitID | None = None shortName: list[Text] = [] unitOf: list[OrganizationID] = [] diff --git a/mex/common/models/person.py b/mex/common/models/person.py index ff24e8ae..4203c27b 100644 --- a/mex/common/models/person.py +++ b/mex/common/models/person.py @@ -13,14 +13,7 @@ class BasePerson(BaseModel): stableTargetId: PersonID affiliation: list[OrganizationID] = [] - email: list[ - Annotated[ - Email, - Field( - examples=["info@rki.de"], - ), - ] - ] = [] + email: list[Email] = [] familyName: list[ Annotated[ str, @@ -33,15 +26,15 @@ class BasePerson(BaseModel): Annotated[ str, Field( - examples=["Anna Schmidt", "P. Meier", "Wolf Maria Hermann"], + examples=["Juturna Felicitás", "M. Berhanu", "Keone Seong-Hyeon"], ), ] - ] + ] = [] givenName: list[ Annotated[ str, Field( - examples=["Wangari", "Marie Salomea", "May-Britt"], + examples=["Romāns", "Marie Salomea", "May-Britt"], ), ] ] = [] @@ -51,6 +44,7 @@ class BasePerson(BaseModel): Field( pattern=r"^https://isni\.org/isni/[X0-9]{16}$", examples=["https://isni.org/isni/0000000109403744"], + json_schema_extra={"format": "uri"}, ), ] ] = [] @@ -61,6 +55,7 @@ class BasePerson(BaseModel): Field( pattern=r"^https://orcid\.org/[-X0-9]{9,21}$", examples=["https://orcid.org/0000-0002-9079-593X"], + json_schema_extra={"format": "uri"}, ), ], ] = [] diff --git a/mex/common/models/primary_source.py b/mex/common/models/primary_source.py index d8491757..288a9be8 100644 --- a/mex/common/models/primary_source.py +++ b/mex/common/models/primary_source.py @@ -1,3 +1,5 @@ +from typing import Annotated + from pydantic import Field from mex.common.models.base import BaseModel @@ -24,10 +26,12 @@ class BasePrimarySource(BaseModel): locatedAt: list[Link] = [] title: list[Text] = [] unitInCharge: list[OrganizationalUnitID] = [] - version: str | None = Field( - None, - examples=["v1", "2023-01-16", "Schema 9"], - ) + version: Annotated[ + str, + Field( + examples=["v1", "2023-01-16", "Schema 9"], + ), + ] | None = None class ExtractedPrimarySource(BasePrimarySource, ExtractedData): diff --git a/mex/common/models/resource.py b/mex/common/models/resource.py index 2984ca26..7ac1e7e5 100644 --- a/mex/common/models/resource.py +++ b/mex/common/models/resource.py @@ -9,68 +9,39 @@ AccessPlatformID, AccessRestriction, ActivityID, + AnonymizationPseudonymization, ContactPointID, + DataProcessingState, DistributionID, + Frequency, + Language, + License, Link, OrganizationalUnitID, OrganizationID, PersonID, ResourceID, + ResourceTypeGeneral, Text, Theme, Timestamp, - VocabularyEnum, ) -class ResourceTypeGeneral(VocabularyEnum): - """The general type of a resource.""" - - __vocabulary__ = "resource-type-general" - - -class AnonymizationPseudonymization(VocabularyEnum): - """Whether the resource is anonymized/pseudonymized.""" - - __vocabulary__ = "anonymization-pseudonymization" - - -class DataProcessingState(VocabularyEnum): - """Type for state of data processing.""" - - __vocabulary__ = "data-processing-state" - - -class Frequency(VocabularyEnum): - """Frequency type.""" - - __vocabulary__ = "frequency" - - -class Language(VocabularyEnum): - """Language type.""" - - __vocabulary__ = "language" - - -class License(VocabularyEnum): - """License type.""" - - __vocabulary__ = "license" - - class BaseResource(BaseModel): """A defined piece or collection of information.""" stableTargetId: ResourceID accessPlatform: list[AccessPlatformID] = [] - accessRestriction: AccessRestriction = Field( - ..., - examples=["https://mex.rki.de/item/access-restriction-1"], - ) - accrualPeriodicity: Frequency | None = Field( - None, examples=["https://mex.rki.de/item/frequency-1"] - ) + accessRestriction: Annotated[ + AccessRestriction, + Field( + examples=["https://mex.rki.de/item/access-restriction-1"], + ), + ] + accrualPeriodicity: Annotated[ + Frequency, Field(examples=["https://mex.rki.de/item/frequency-1"]) + ] | None = None alternativeTitle: list[Text] = [] anonymizationPseudonymization: list[ Annotated[ @@ -80,12 +51,12 @@ class BaseResource(BaseModel): ), ] ] = [] - contact: list[OrganizationalUnitID | PersonID | ContactPointID] = Field( - ..., min_length=1 - ) + contact: Annotated[ + list[OrganizationalUnitID | PersonID | ContactPointID], Field(min_length=1) + ] contributingUnit: list[OrganizationalUnitID] = [] contributor: list[PersonID] = [] - created: list[Timestamp] = [] + created: Timestamp | None = None creator: list[PersonID] = [] description: list[Text] = [] distribution: list[DistributionID] = [] @@ -98,21 +69,23 @@ class BaseResource(BaseModel): language: list[ Annotated[Language, Field(examples=["https://mex.rki.de/item/language-1"])] ] = [] - license: list[ + license: Annotated[ + License, Field(examples=["https://mex.rki.de/item/license-1"]) + ] | None = None + loincId: list[str] = [] + meshId: list[ Annotated[ - License, + str, Field( - examples=["https://mex.rki.de/item/license-1"], + pattern=r"^http://id\.nlm\.nih\.gov/mesh/[A-Z0-9]{2,64}$", + examples=["http://id.nlm.nih.gov/mesh/D001604"], + json_schema_extra={"format": "uri"}, ), ] ] = [] - loincId: list[str] = [] - meshId: list[ - Annotated[str, Field(pattern=r"^http://id\.nlm\.nih\.gov/mesh/[A-Z0-9]{2,64}$")] - ] = [] method: list[Text] = [] methodDescription: list[Text] = [] - modified: list[Timestamp] = [] + modified: Timestamp | None = None publication: list[Link] = [] publisher: list[OrganizationID] = [] qualityInformation: list[Text] = [] @@ -128,15 +101,26 @@ class BaseResource(BaseModel): rights: list[Text] = [] sizeOfDataBasis: str | None = None spatial: list[Text] = [] - stateOfDataProcessing: DataProcessingState | None = Field( - None, examples=["https://mex.rki.de/item/data-processing-state-1"] - ) - temporal: list[Timestamp | str] = [] - theme: list[ - Annotated[Theme, Field(examples=["https://mex.rki.de/item/theme-1"])] - ] = Field(..., min_length=1) - title: list[Text] = Field(..., min_length=1) - unitInCharge: list[OrganizationalUnitID] = Field(..., min_length=1) + stateOfDataProcessing: list[ + Annotated[ + DataProcessingState, + Field( + examples=["https://mex.rki.de/item/data-processing-state-1"], + ), + ] + ] = [] + temporal: Timestamp | Annotated[ + str, + Field( + examples=["2022-01 bis 2022-03", "Sommer 2023", "nach 2013", "1998-2008"] + ), + ] | None = None + theme: Annotated[ + list[Annotated[Theme, Field(examples=["https://mex.rki.de/item/theme-1"])]], + Field(min_length=1), + ] + title: Annotated[list[Text], Field(min_length=1)] + unitInCharge: Annotated[list[OrganizationalUnitID], Field(min_length=1)] wasGeneratedBy: ActivityID | None = None diff --git a/mex/common/models/variable.py b/mex/common/models/variable.py index 2b7e1594..ce16a3c2 100644 --- a/mex/common/models/variable.py +++ b/mex/common/models/variable.py @@ -6,48 +6,46 @@ from mex.common.models.extracted_data import ExtractedData from mex.common.models.merged_item import MergedItem from mex.common.types import ( + DataType, ResourceID, Text, VariableGroupID, VariableID, - VocabularyEnum, ) -class DataType(VocabularyEnum): - """The type of the single piece of information within a datum.""" - - __vocabulary__ = "data-type" - - class BaseVariable(BaseModel): """A single piece of information within a resource.""" stableTargetId: VariableID belongsTo: list[VariableGroupID] = [] - codingSystem: str | None = Field( - None, - examples=["SF-36 Version 1"], - ) - dataType: DataType | None = Field( - None, - examples=["https://mex.rki.de/item/data-type-1"], - ) + codingSystem: Annotated[ + str, + Field( + examples=["SF-36 Version 1"], + ), + ] | None = None + dataType: Annotated[ + DataType, + Field( + examples=["https://mex.rki.de/item/data-type-1"], + ), + ] | None = None description: list[Text] = [] - label: list[ - Annotated[ - Text, - Field( - examples=[ - {"language": "de", "value": "Mehrere Treppenabsätze steigen"} - ], - ), - ] - ] = Field( - ..., - min_length=1, - ) - usedIn: list[ResourceID] = Field(..., min_length=1) + label: Annotated[ + list[ + Annotated[ + Text, + Field( + examples=[ + {"language": "de", "value": "Mehrere Treppenabsätze steigen"} + ], + ), + ] + ], + Field(min_length=1), + ] + usedIn: Annotated[list[ResourceID], Field(min_length=1)] valueSet: list[ Annotated[ str, @@ -59,9 +57,7 @@ class BaseVariable(BaseModel): ], ), ] - ] = Field( - [], - ) + ] = [] class ExtractedVariable(BaseVariable, ExtractedData): diff --git a/mex/common/models/variable_group.py b/mex/common/models/variable_group.py index 7fb25fe6..d5a2f09e 100644 --- a/mex/common/models/variable_group.py +++ b/mex/common/models/variable_group.py @@ -1,3 +1,5 @@ +from typing import Annotated + from pydantic import Field from mex.common.models.base import BaseModel @@ -10,8 +12,8 @@ class BaseVariableGroup(BaseModel): """The grouping of variables according to a certain aspect.""" stableTargetId: VariableGroupID - containedBy: list[ResourceID] = Field(..., min_length=1) - label: list[Text] = Field(..., min_length=1) + containedBy: Annotated[list[ResourceID], Field(min_length=1)] + label: Annotated[list[Text], Field(min_length=1)] class ExtractedVariableGroup(BaseVariableGroup, ExtractedData): diff --git a/mex/common/public_api/models.py b/mex/common/public_api/models.py index b665f6c4..2668b2b8 100644 --- a/mex/common/public_api/models.py +++ b/mex/common/public_api/models.py @@ -66,7 +66,7 @@ class PublicApiItem(PublicApiBaseModel): entityType: str itemId: UUID | None = Field(None, exclude=True) - businessId: str = Field(..., exclude=True) + businessId: str = Field(exclude=True) values: list[PublicApiField] @property @@ -96,7 +96,7 @@ class PublicApiItemWithoutValues(PublicApiBaseModel): entityType: str itemId: UUID | None = Field(None, exclude=True) - businessId: str = Field(..., exclude=True) + businessId: str = Field(exclude=True) @property def stableTargetId(self) -> Identifier: # noqa: N802 diff --git a/mex/common/testing/joker.py b/mex/common/testing/joker.py index 1536ead3..cd7be534 100644 --- a/mex/common/testing/joker.py +++ b/mex/common/testing/joker.py @@ -10,7 +10,7 @@ class Joker: assert value == {"predictable": 42, "timestamp": Joker()} """ - _repr_cache = None + _repr_cache: str | None = None def __eq__(self, obj: Any) -> bool: """Pretend to be equal to any other object.""" diff --git a/mex/common/transform.py b/mex/common/transform.py index a443b3b9..e4a59b1a 100644 --- a/mex/common/transform.py +++ b/mex/common/transform.py @@ -55,3 +55,21 @@ def dromedary_to_snake(string: str) -> str: for word in re.split(r"([A-Z]+(?![a-z])|[a-z]+|[A-Z][a-z]+)", string) if word.strip("_") ) + + +@cache +def dromedary_to_kebab(string: str) -> str: + """Convert the given string from `dromedaryCase` into `kebab-case`.""" + return "-".join( + word.lower() + for word in re.split(r"([A-Z]+(?![a-z])|[a-z]+|[A-Z][a-z]+)", string) + if word.strip("-") + ) + + +@cache +def kebab_to_camel(string: str) -> str: + """Convert the given string from `kebab-case` into `CamelCase`.""" + if len(tokens := re.split(r"\-+", string)) > 1: + return "".join(word.title() for word in tokens) + return string[:1].upper() + string[1:] diff --git a/mex/common/types/__init__.py b/mex/common/types/__init__.py index 9f8be4ed..2dbc6286 100644 --- a/mex/common/types/__init__.py +++ b/mex/common/types/__init__.py @@ -28,6 +28,17 @@ ) from mex.common.types.vocabulary import ( AccessRestriction, + ActivityType, + AnonymizationPseudonymization, + APIType, + DataProcessingState, + DataType, + Frequency, + Language, + License, + MIMEType, + ResourceTypeGeneral, + TechnicalAccessibility, Theme, VocabularyEnum, VocabularyLoader, @@ -38,23 +49,34 @@ "AccessPlatformID", "AccessRestriction", "ActivityID", + "ActivityType", + "AnonymizationPseudonymization", + "APIType", "AssetsPath", "CET", "ContactPointID", + "DataProcessingState", + "DataType", "DistributionID", "Email", + "Frequency", "Identifier", "IdentityProvider", + "Language", + "License", "Link", "LinkLanguage", + "MIMEType", "OrganizationalUnitID", "OrganizationID", "PathWrapper", "PersonID", "PrimarySourceID", "ResourceID", + "ResourceTypeGeneral", "Sink", "split_to_caps", + "TechnicalAccessibility", "Text", "TextLanguage", "Theme", diff --git a/mex/common/types/email.py b/mex/common/types/email.py index b21020a5..fd4416bd 100644 --- a/mex/common/types/email.py +++ b/mex/common/types/email.py @@ -4,7 +4,7 @@ from pydantic.json_schema import JsonSchemaValue from pydantic_core import core_schema -EMAIL_PATTERN = r".+@.+" +EMAIL_PATTERN = r"^[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+$" class Email(str): @@ -12,7 +12,7 @@ class Email(str): @classmethod def __get_pydantic_core_schema__(cls, _source: Type[Any]) -> core_schema.CoreSchema: - """Get pydanctic core schema.""" + """Get pydantic core schema.""" return core_schema.str_schema(pattern=EMAIL_PATTERN) @classmethod @@ -21,5 +21,7 @@ def __get_pydantic_json_schema__( ) -> JsonSchemaValue: """Add title and format.""" field_schema = handler(core_schema_) - field_schema.update(title=cls.__name__, format="email") + field_schema["title"] = cls.__name__ + field_schema["format"] = "email" + field_schema["examples"] = ["info@rki.de"] return field_schema diff --git a/mex/common/types/link.py b/mex/common/types/link.py index 7ef3d902..7f08c7bd 100644 --- a/mex/common/types/link.py +++ b/mex/common/types/link.py @@ -41,7 +41,6 @@ class Link(BaseModel): language: LinkLanguage | None = None title: str | None = None url: str = Field( - ..., pattern=r"^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?", min_length=1, examples=["https://hello-world.org", "file://S:/OE/MF4/Projekte/MEx"], @@ -56,14 +55,11 @@ def convert_markdown_to_link(cls, values: Any) -> dict[str, Any]: return values if isinstance(values, str): if match := re.match(r"\[(?P.*)\]\((?P<url>.*)\)", values): - url_dict = { + return { key: markdown_unescape(value) for key, value in match.groupdict().items() } - else: - url_dict = {"url": values} - - return url_dict + return {"url": values} raise ValueError(f"Allowed input types are dict and str, got {type(values)}") def __str__(self) -> str: diff --git a/mex/common/types/text.py b/mex/common/types/text.py index 3d55a0d6..99927521 100644 --- a/mex/common/types/text.py +++ b/mex/common/types/text.py @@ -26,7 +26,7 @@ class Text(BaseModel): Text(value="foo") == Text.model_validate("foo") """ - value: str = Field(..., min_length=1) + value: str = Field(min_length=1) language: TextLanguage | None = None @model_validator(mode="before") diff --git a/mex/common/types/timestamp.py b/mex/common/types/timestamp.py index e7c25d78..441994ac 100644 --- a/mex/common/types/timestamp.py +++ b/mex/common/types/timestamp.py @@ -167,6 +167,7 @@ def __get_pydantic_json_schema__( """Modify the schema to add the class name as title and examples.""" json_schema = handler(core_schema_) json_schema["title"] = cls.__name__ + json_schema["format"] = "date-time" json_schema["examples"] = [ "2011", "2019-03", @@ -232,7 +233,7 @@ def __eq__(self, other: Any) -> bool: try: other = self.validate(other) except TypeError: - return NotImplemented + return False return bool( self.date_time == other.date_time and self.precision == other.precision ) @@ -242,7 +243,7 @@ def __gt__(self, other: Any) -> bool: try: other = self.validate(other) except TypeError: - return NotImplemented + raise NotImplementedError() return bool(self.date_time > other.date_time) def __str__(self) -> str: diff --git a/mex/common/types/vocabulary.py b/mex/common/types/vocabulary.py index 13c4f8e9..f519e2f7 100644 --- a/mex/common/types/vocabulary.py +++ b/mex/common/types/vocabulary.py @@ -114,6 +114,72 @@ class AccessRestriction(VocabularyEnum): __vocabulary__ = "access-restriction" +class ActivityType(VocabularyEnum): + """The activity type.""" + + __vocabulary__ = "activity-type" + + +class AnonymizationPseudonymization(VocabularyEnum): + """Whether the resource is anonymized/pseudonymized.""" + + __vocabulary__ = "anonymization-pseudonymization" + + +class APIType(VocabularyEnum): + """Technical standard or style of a network API.""" + + __vocabulary__ = "api-type" + + +class DataProcessingState(VocabularyEnum): + """Type for state of data processing.""" + + __vocabulary__ = "data-processing-state" + + +class DataType(VocabularyEnum): + """The type of the single piece of information within a datum.""" + + __vocabulary__ = "data-type" + + +class Frequency(VocabularyEnum): + """Frequency type.""" + + __vocabulary__ = "frequency" + + +class Language(VocabularyEnum): + """Language type.""" + + __vocabulary__ = "language" + + +class License(VocabularyEnum): + """License type.""" + + __vocabulary__ = "license" + + +class MIMEType(VocabularyEnum): + """The mime type.""" + + __vocabulary__ = "mime-type" + + +class ResourceTypeGeneral(VocabularyEnum): + """The general type of a resource.""" + + __vocabulary__ = "resource-type-general" + + +class TechnicalAccessibility(VocabularyEnum): + """Technical accessibility within RKI and outside of RKI.""" + + __vocabulary__ = "technical-accessibility" + + class Theme(VocabularyEnum): """The theme type.""" diff --git a/poetry.lock b/poetry.lock index e1d7605f..52328b32 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "accessible-pygments" @@ -56,18 +56,15 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "babel" -version = "2.13.1" +version = "2.14.0" description = "Internationalization utilities" optional = false python-versions = ">=3.7" files = [ - {file = "Babel-2.13.1-py3-none-any.whl", hash = "sha256:7077a4984b02b6727ac10f1f7294484f737443d7e2e66c5e4380e41a3ae0b4ed"}, - {file = "Babel-2.13.1.tar.gz", hash = "sha256:33e0952d7dd6374af8dbf6768cc4ddf3ccfefc244f9986d4074704f2fbd18900"}, + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, ] -[package.dependencies] -setuptools = {version = "*", markers = "python_version >= \"3.12\""} - [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] @@ -102,29 +99,33 @@ lxml = ["lxml"] [[package]] name = "black" -version = "23.11.0" +version = "23.12.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.8" files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, + {file = "black-23.12.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:67f19562d367468ab59bd6c36a72b2c84bc2f16b59788690e02bbcb140a77175"}, + {file = "black-23.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bbd75d9f28a7283b7426160ca21c5bd640ca7cd8ef6630b4754b6df9e2da8462"}, + {file = "black-23.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:593596f699ca2dcbbbdfa59fcda7d8ad6604370c10228223cd6cf6ce1ce7ed7e"}, + {file = "black-23.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:12d5f10cce8dc27202e9a252acd1c9a426c83f95496c959406c96b785a92bb7d"}, + {file = "black-23.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e73c5e3d37e5a3513d16b33305713237a234396ae56769b839d7c40759b8a41c"}, + {file = "black-23.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ba09cae1657c4f8a8c9ff6cfd4a6baaf915bb4ef7d03acffe6a2f6585fa1bd01"}, + {file = "black-23.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace64c1a349c162d6da3cef91e3b0e78c4fc596ffde9413efa0525456148873d"}, + {file = "black-23.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:72db37a2266b16d256b3ea88b9affcdd5c41a74db551ec3dd4609a59c17d25bf"}, + {file = "black-23.12.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fdf6f23c83078a6c8da2442f4d4eeb19c28ac2a6416da7671b72f0295c4a697b"}, + {file = "black-23.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39dda060b9b395a6b7bf9c5db28ac87b3c3f48d4fdff470fa8a94ab8271da47e"}, + {file = "black-23.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7231670266ca5191a76cb838185d9be59cfa4f5dd401b7c1c70b993c58f6b1b5"}, + {file = "black-23.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:193946e634e80bfb3aec41830f5d7431f8dd5b20d11d89be14b84a97c6b8bc75"}, + {file = "black-23.12.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bcf91b01ddd91a2fed9a8006d7baa94ccefe7e518556470cf40213bd3d44bbbc"}, + {file = "black-23.12.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:996650a89fe5892714ea4ea87bc45e41a59a1e01675c42c433a35b490e5aa3f0"}, + {file = "black-23.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdbff34c487239a63d86db0c9385b27cdd68b1bfa4e706aa74bb94a435403672"}, + {file = "black-23.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:97af22278043a6a1272daca10a6f4d36c04dfa77e61cbaaf4482e08f3640e9f0"}, + {file = "black-23.12.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ead25c273adfad1095a8ad32afdb8304933efba56e3c1d31b0fee4143a1e424a"}, + {file = "black-23.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c71048345bdbced456cddf1622832276d98a710196b842407840ae8055ade6ee"}, + {file = "black-23.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a832b6e00eef2c13b3239d514ea3b7d5cc3eaa03d0474eedcbbda59441ba5d"}, + {file = "black-23.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:6a82a711d13e61840fb11a6dfecc7287f2424f1ca34765e70c909a35ffa7fb95"}, + {file = "black-23.12.0-py3-none-any.whl", hash = "sha256:a7c07db8200b5315dc07e331dda4d889a56f6bf4db6a9c2a526fa3166a81614f"}, + {file = "black-23.12.0.tar.gz", hash = "sha256:330a327b422aca0634ecd115985c1c7fd7bdb5b5a2ef8aa9888a82e2ebe9437a"}, ] [package.dependencies] @@ -136,7 +137,7 @@ platformdirs = ">=2" [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -277,63 +278,63 @@ files = [ [[package]] name = "coverage" -version = "7.3.2" +version = "7.3.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"}, - {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"}, - {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"}, - {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"}, - {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"}, - {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"}, - {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"}, - {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"}, - {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"}, - {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"}, - {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"}, - {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"}, - {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"}, - {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"}, - {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"}, - {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"}, - {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"}, - {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"}, - {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"}, - {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"}, - {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"}, - {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"}, - {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"}, - {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"}, - {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"}, - {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"}, - {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"}, - {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"}, + {file = "coverage-7.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d874434e0cb7b90f7af2b6e3309b0733cde8ec1476eb47db148ed7deeb2a9494"}, + {file = "coverage-7.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee6621dccce8af666b8c4651f9f43467bfbf409607c604b840b78f4ff3619aeb"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1367aa411afb4431ab58fd7ee102adb2665894d047c490649e86219327183134"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f0f8f0c497eb9c9f18f21de0750c8d8b4b9c7000b43996a094290b59d0e7523"}, + {file = "coverage-7.3.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db0338c4b0951d93d547e0ff8d8ea340fecf5885f5b00b23be5aa99549e14cfd"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d31650d313bd90d027f4be7663dfa2241079edd780b56ac416b56eebe0a21aab"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9437a4074b43c177c92c96d051957592afd85ba00d3e92002c8ef45ee75df438"}, + {file = "coverage-7.3.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9e17d9cb06c13b4f2ef570355fa45797d10f19ca71395910b249e3f77942a837"}, + {file = "coverage-7.3.3-cp310-cp310-win32.whl", hash = "sha256:eee5e741b43ea1b49d98ab6e40f7e299e97715af2488d1c77a90de4a663a86e2"}, + {file = "coverage-7.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:593efa42160c15c59ee9b66c5f27a453ed3968718e6e58431cdfb2d50d5ad284"}, + {file = "coverage-7.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c944cf1775235c0857829c275c777a2c3e33032e544bcef614036f337ac37bb"}, + {file = "coverage-7.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eda7f6e92358ac9e1717ce1f0377ed2b9320cea070906ece4e5c11d172a45a39"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c854c1d2c7d3e47f7120b560d1a30c1ca221e207439608d27bc4d08fd4aeae8"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:222b038f08a7ebed1e4e78ccf3c09a1ca4ac3da16de983e66520973443b546bc"}, + {file = "coverage-7.3.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff4800783d85bff132f2cc7d007426ec698cdce08c3062c8d501ad3f4ea3d16c"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fc200cec654311ca2c3f5ab3ce2220521b3d4732f68e1b1e79bef8fcfc1f2b97"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:307aecb65bb77cbfebf2eb6e12009e9034d050c6c69d8a5f3f737b329f4f15fb"}, + {file = "coverage-7.3.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ffb0eacbadb705c0a6969b0adf468f126b064f3362411df95f6d4f31c40d31c1"}, + {file = "coverage-7.3.3-cp311-cp311-win32.whl", hash = "sha256:79c32f875fd7c0ed8d642b221cf81feba98183d2ff14d1f37a1bbce6b0347d9f"}, + {file = "coverage-7.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:243576944f7c1a1205e5cd658533a50eba662c74f9be4c050d51c69bd4532936"}, + {file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"}, + {file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"}, + {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"}, + {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"}, + {file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"}, + {file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"}, + {file = "coverage-7.3.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbd8a5fe6c893de21a3c6835071ec116d79334fbdf641743332e442a3466f7ea"}, + {file = "coverage-7.3.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:50c472c1916540f8b2deef10cdc736cd2b3d1464d3945e4da0333862270dcb15"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e9223a18f51d00d3ce239c39fc41410489ec7a248a84fab443fbb39c943616c"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f501e36ac428c1b334c41e196ff6bd550c0353c7314716e80055b1f0a32ba394"}, + {file = "coverage-7.3.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:475de8213ed95a6b6283056d180b2442eee38d5948d735cd3d3b52b86dd65b92"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:afdcc10c01d0db217fc0a64f58c7edd635b8f27787fea0a3054b856a6dff8717"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fff0b2f249ac642fd735f009b8363c2b46cf406d3caec00e4deeb79b5ff39b40"}, + {file = "coverage-7.3.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a1f76cfc122c9e0f62dbe0460ec9cc7696fc9a0293931a33b8870f78cf83a327"}, + {file = "coverage-7.3.3-cp38-cp38-win32.whl", hash = "sha256:757453848c18d7ab5d5b5f1827293d580f156f1c2c8cef45bfc21f37d8681069"}, + {file = "coverage-7.3.3-cp38-cp38-win_amd64.whl", hash = "sha256:ad2453b852a1316c8a103c9c970db8fbc262f4f6b930aa6c606df9b2766eee06"}, + {file = "coverage-7.3.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b15e03b8ee6a908db48eccf4e4e42397f146ab1e91c6324da44197a45cb9132"}, + {file = "coverage-7.3.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89400aa1752e09f666cc48708eaa171eef0ebe3d5f74044b614729231763ae69"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c59a3e59fb95e6d72e71dc915e6d7fa568863fad0a80b33bc7b82d6e9f844973"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ede881c7618f9cf93e2df0421ee127afdfd267d1b5d0c59bcea771cf160ea4a"}, + {file = "coverage-7.3.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3bfd2c2f0e5384276e12b14882bf2c7621f97c35320c3e7132c156ce18436a1"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7f3bad1a9313401ff2964e411ab7d57fb700a2d5478b727e13f156c8f89774a0"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:65d716b736f16e250435473c5ca01285d73c29f20097decdbb12571d5dfb2c94"}, + {file = "coverage-7.3.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a702e66483b1fe602717020a0e90506e759c84a71dbc1616dd55d29d86a9b91f"}, + {file = "coverage-7.3.3-cp39-cp39-win32.whl", hash = "sha256:7fbf3f5756e7955174a31fb579307d69ffca91ad163467ed123858ce0f3fd4aa"}, + {file = "coverage-7.3.3-cp39-cp39-win_amd64.whl", hash = "sha256:cad9afc1644b979211989ec3ff7d82110b2ed52995c2f7263e7841c846a75348"}, + {file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"}, + {file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"}, ] [package.extras] @@ -595,6 +596,21 @@ files = [ [package.dependencies] traitlets = "*" +[[package]] +name = "mex-model" +version = "2.2.0" +description = "RKI MEx metadata model." +optional = false +python-versions = "*" +files = [] +develop = false + +[package.source] +type = "git" +url = "https://github.com/robert-koch-institut/mex-model.git" +reference = "2.2.0" +resolved_reference = "d8ef77aa5a5f3a696e5698969ffa0dae46d5b785" + [[package]] name = "mypy" version = "1.7.1" @@ -710,36 +726,36 @@ files = [ [[package]] name = "pandas" -version = "2.1.3" +version = "2.1.4" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"}, - {file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"}, - {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"}, - {file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"}, - {file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"}, - {file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"}, - {file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"}, - {file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"}, - {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"}, - {file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"}, - {file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"}, - {file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"}, - {file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"}, - {file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"}, - {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"}, - {file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"}, - {file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"}, - {file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"}, - {file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"}, - {file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"}, - {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"}, - {file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"}, - {file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"}, - {file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"}, - {file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdec823dc6ec53f7a6339a0e34c68b144a7a1fd28d80c260534c39c62c5bf8c9"}, + {file = "pandas-2.1.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:294d96cfaf28d688f30c918a765ea2ae2e0e71d3536754f4b6de0ea4a496d034"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b728fb8deba8905b319f96447a27033969f3ea1fea09d07d296c9030ab2ed1d"}, + {file = "pandas-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00028e6737c594feac3c2df15636d73ace46b8314d236100b57ed7e4b9ebe8d9"}, + {file = "pandas-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:426dc0f1b187523c4db06f96fb5c8d1a845e259c99bda74f7de97bd8a3bb3139"}, + {file = "pandas-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:f237e6ca6421265643608813ce9793610ad09b40154a3344a088159590469e46"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b7d852d16c270e4331f6f59b3e9aa23f935f5c4b0ed2d0bc77637a8890a5d092"}, + {file = "pandas-2.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bd7d5f2f54f78164b3d7a40f33bf79a74cdee72c31affec86bfcabe7e0789821"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa6e92e639da0d6e2017d9ccff563222f4eb31e4b2c3cf32a2a392fc3103c0d"}, + {file = "pandas-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d797591b6846b9db79e65dc2d0d48e61f7db8d10b2a9480b4e3faaddc421a171"}, + {file = "pandas-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2d3e7b00f703aea3945995ee63375c61b2e6aa5aa7871c5d622870e5e137623"}, + {file = "pandas-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:dc9bf7ade01143cddc0074aa6995edd05323974e6e40d9dbde081021ded8510e"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:482d5076e1791777e1571f2e2d789e940dedd927325cc3cb6d0800c6304082f6"}, + {file = "pandas-2.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8a706cfe7955c4ca59af8c7a0517370eafbd98593155b48f10f9811da440248b"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0513a132a15977b4a5b89aabd304647919bc2169eac4c8536afb29c07c23540"}, + {file = "pandas-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9f17f2b6fc076b2a0078862547595d66244db0f41bf79fc5f64a5c4d635bead"}, + {file = "pandas-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:45d63d2a9b1b37fa6c84a68ba2422dc9ed018bdaa668c7f47566a01188ceeec1"}, + {file = "pandas-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f69b0c9bb174a2342818d3e2778584e18c740d56857fc5cdb944ec8bbe4082cf"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3f06bda01a143020bad20f7a85dd5f4a1600112145f126bc9e3e42077c24ef34"}, + {file = "pandas-2.1.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab5796839eb1fd62a39eec2916d3e979ec3130509930fea17fe6f81e18108f6a"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edbaf9e8d3a63a9276d707b4d25930a262341bca9874fcb22eff5e3da5394732"}, + {file = "pandas-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ebfd771110b50055712b3b711b51bee5d50135429364d0498e1213a7adc2be8"}, + {file = "pandas-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ea107e0be2aba1da619cc6ba3f999b2bfc9669a83554b1904ce3dd9507f0860"}, + {file = "pandas-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:d65148b14788b3758daf57bf42725caa536575da2b64df9964c563b015230984"}, + {file = "pandas-2.1.4.tar.gz", hash = "sha256:fcb68203c833cc735321512e13861358079a96c174a61f5116a1de89c58c0ef7"}, ] [package.dependencies] @@ -777,13 +793,13 @@ xml = ["lxml (>=4.8.0)"] [[package]] name = "pandas-stubs" -version = "2.1.1.230928" +version = "2.1.4.231218" description = "Type annotations for pandas" optional = false python-versions = ">=3.9" files = [ - {file = "pandas_stubs-2.1.1.230928-py3-none-any.whl", hash = "sha256:992d97159e054ca3175ebe8321ac5616cf6502dd8218b03bb0eaf3c4f6939037"}, - {file = "pandas_stubs-2.1.1.230928.tar.gz", hash = "sha256:ce1691c71c5d67b8f332da87763f7f54650f46895d99964d588c3a5d79e2cacc"}, + {file = "pandas_stubs-2.1.4.231218-py3-none-any.whl", hash = "sha256:9c9a8db37b83ff4ff9f672644099abc624ed407aa46d9dcb5f305de9925b3d29"}, + {file = "pandas_stubs-2.1.4.231218.tar.gz", hash = "sha256:f0dd07b3bb2935ddcff9c7b7ba9076cf3529b968a0dee96fab53f5f8747974f7"}, ] [package.dependencies] @@ -807,13 +823,13 @@ testing = ["docopt", "pytest (<6.0.0)"] [[package]] name = "pathspec" -version = "0.11.2" +version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, - {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] [[package]] @@ -862,13 +878,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prompt-toolkit" -version = "3.0.41" +version = "3.0.43" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.41-py3-none-any.whl", hash = "sha256:f36fe301fafb7470e86aaf90f036eef600a3210be4decf461a5b1ca8403d3cb2"}, - {file = "prompt_toolkit-3.0.41.tar.gz", hash = "sha256:941367d97fc815548822aa26c2a269fdc4eb21e9ec05fc5d447cf09bad5d75f0"}, + {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, + {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, ] [package.dependencies] @@ -1203,46 +1219,30 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.1.7" +version = "0.1.8" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7f80496854fdc65b6659c271d2c26e90d4d401e6a4a31908e7e334fab4645aac"}, - {file = "ruff-0.1.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1ea109bdb23c2a4413f397ebd8ac32cb498bee234d4191ae1a310af760e5d287"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0c2de9dd9daf5e07624c24add25c3a490dbf74b0e9bca4145c632457b3b42a"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:69a4bed13bc1d5dabf3902522b5a2aadfebe28226c6269694283c3b0cecb45fd"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de02ca331f2143195a712983a57137c5ec0f10acc4aa81f7c1f86519e52b92a1"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:45b38c3f8788a65e6a2cab02e0f7adfa88872696839d9882c13b7e2f35d64c5f"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c64cb67b2025b1ac6d58e5ffca8f7b3f7fd921f35e78198411237e4f0db8e73"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dcc6bb2f4df59cb5b4b40ff14be7d57012179d69c6565c1da0d1f013d29951b"}, - {file = "ruff-0.1.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2bb4bb6bbe921f6b4f5b6fdd8d8468c940731cb9406f274ae8c5ed7a78c478"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:276a89bcb149b3d8c1b11d91aa81898fe698900ed553a08129b38d9d6570e717"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:90c958fe950735041f1c80d21b42184f1072cc3975d05e736e8d66fc377119ea"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6b05e3b123f93bb4146a761b7a7d57af8cb7384ccb2502d29d736eaade0db519"}, - {file = "ruff-0.1.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:290ecab680dce94affebefe0bbca2322a6277e83d4f29234627e0f8f6b4fa9ce"}, - {file = "ruff-0.1.7-py3-none-win32.whl", hash = "sha256:416dfd0bd45d1a2baa3b1b07b1b9758e7d993c256d3e51dc6e03a5e7901c7d80"}, - {file = "ruff-0.1.7-py3-none-win_amd64.whl", hash = "sha256:4af95fd1d3b001fc41325064336db36e3d27d2004cdb6d21fd617d45a172dd96"}, - {file = "ruff-0.1.7-py3-none-win_arm64.whl", hash = "sha256:0683b7bfbb95e6df3c7c04fe9d78f631f8e8ba4868dfc932d43d690698057e2e"}, - {file = "ruff-0.1.7.tar.gz", hash = "sha256:dffd699d07abf54833e5f6cc50b85a6ff043715da8788c4a79bcd4ab4734d306"}, -] - -[[package]] -name = "setuptools" -version = "69.0.2" -description = "Easily download, build, install, upgrade, and uninstall Python packages" -optional = false -python-versions = ">=3.8" -files = [ - {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, - {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"}, + {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"}, + {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"}, + {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"}, + {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"}, + {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"}, + {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"}, + {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"}, ] -[package.extras] -docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "six" version = "1.16.0" @@ -1312,22 +1312,22 @@ test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] [[package]] name = "sphinx-book-theme" -version = "1.0.1" +version = "1.1.0" description = "A clean book theme for scientific explanations and documentation with Sphinx" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "sphinx_book_theme-1.0.1-py3-none-any.whl", hash = "sha256:d15f8248b3718a9a6be0ba617a32d1591f9fa39c614469bface777ba06a73b75"}, - {file = "sphinx_book_theme-1.0.1.tar.gz", hash = "sha256:927b399a6906be067e49c11ef1a87472f1b1964075c9eea30fb82c64b20aedee"}, + {file = "sphinx_book_theme-1.1.0-py3-none-any.whl", hash = "sha256:088bc69d65fab8446adb8691ed61687f71bf7504c9740af68bc78cf936a26112"}, + {file = "sphinx_book_theme-1.1.0.tar.gz", hash = "sha256:ad4f92998e53e24751ecd0978d3eb79fdaa59692f005b1b286ecdd6146ebc9c1"}, ] [package.dependencies] -pydata-sphinx-theme = ">=0.13.3" -sphinx = ">=4,<7" +pydata-sphinx-theme = ">=0.14" +sphinx = ">=5" [package.extras] code-style = ["pre-commit"] -doc = ["ablog", "docutils (==0.17.1)", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs (<=3.4.0)", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] +doc = ["ablog", "folium", "ipywidgets", "matplotlib", "myst-nb", "nbclient", "numpy", "numpydoc", "pandas", "plotly", "sphinx-copybutton", "sphinx-design", "sphinx-examples", "sphinx-tabs", "sphinx-thebe", "sphinx-togglebutton", "sphinxcontrib-bibtex", "sphinxcontrib-youtube", "sphinxext-opengraph"] test = ["beautifulsoup4", "coverage", "myst-nb", "pytest", "pytest-cov", "pytest-regressions", "sphinx_thebe"] [[package]] @@ -1520,24 +1520,24 @@ urllib3 = ">=2" [[package]] name = "types-setuptools" -version = "68.2.0.2" +version = "69.0.0.0" description = "Typing stubs for setuptools" optional = false python-versions = ">=3.7" files = [ - {file = "types-setuptools-68.2.0.2.tar.gz", hash = "sha256:09efc380ad5c7f78e30bca1546f706469568cf26084cfab73ecf83dea1d28446"}, - {file = "types_setuptools-68.2.0.2-py3-none-any.whl", hash = "sha256:d5b5ff568ea2474eb573dcb783def7dadfd9b1ff638bb653b3c7051ce5aeb6d1"}, + {file = "types-setuptools-69.0.0.0.tar.gz", hash = "sha256:b0a06219f628c6527b2f8ce770a4f47550e00d3e8c3ad83e2dc31bc6e6eda95d"}, + {file = "types_setuptools-69.0.0.0-py3-none-any.whl", hash = "sha256:8c86195bae2ad81e6dea900a570fe9d64a59dbce2b11cc63c046b03246ea77bf"}, ] [[package]] name = "typing-extensions" -version = "4.8.0" +version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, - {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, + {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, + {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] [[package]] @@ -1581,4 +1581,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "849ce044de49aa58ccbeba1a7724435ba32658dbe5bf41274a3f518162072098" +content-hash = "a63b14dd514e25b7d0cd96e9a812a870ec4e26bc15d4831761cb46ab3b5a61b5" diff --git a/pyproject.toml b/pyproject.toml index 938789ab..7c389691 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "mex-common" -version = "0.17.0" +version = "0.18.0" description = "RKI MEx common library." authors = ["RKI MEx Team <mex@rki.de>"] readme = "README.md" @@ -15,25 +15,26 @@ click = "^8.1.7" langdetect = "^1.0.9" ldap3 = "^2.9.1" numpy = "^1.26.2" -pandas = "^2.1.3" -pydantic = "^2.5.1" +pandas = "^2.1.4" +pydantic = "^2.5.2" pydantic-settings = "^2.1.0" requests = "^2.31.0" [tool.poetry.group.dev.dependencies] black = "^23.11.0" ipdb = "^0.13.13" -mypy = "^1.7.0" -pandas-stubs = "^2.1.1" +mex-model = { git = "https://github.com/robert-koch-institut/mex-model.git", rev = "2.2.0"} +mypy = "^1.7.1" +pandas-stubs = "^2.1.4" pytest = "^7.4.3" pytest-cov = "^4.1.0" -ruff = "^0.1.6" +ruff = "^0.1.8" sphinx = "^6.2.1" -sphinx-book-theme = "^1.0.1" -types-ldap3 = "^2.9.13.15" -types-pytz = "^2023.3.1.1" -types-requests = "^2.31.0.10" -types-setuptools = "^68.2.0.1" +sphinx-book-theme = "^1.1.0" +types-ldap3 = "^2.9.13" +types-pytz = "^2023.3.1" +types-requests = "^2.31.0" +types-setuptools = "^69.0.0" [tool.ipdb] context = 5 @@ -59,7 +60,7 @@ addopts = [ "--cov", "--no-cov-on-fail", "--cov-report=term-missing:skip-covered", - "--cov-fail-under=90", + "--cov-fail-under=95", "--cov-branch", "--pdbcls=IPython.terminal.debugger:TerminalPdb" ] diff --git a/requirements.txt b/requirements.txt index 0413bde6..2e89804d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -poetry==1.7.0 +poetry==1.7.1 pre-commit==3.5.0 -wheel==0.41.3 +wheel==0.42.0 diff --git a/tests/models/test_schema.py b/tests/models/test_schema.py new file mode 100644 index 00000000..0410cf5a --- /dev/null +++ b/tests/models/test_schema.py @@ -0,0 +1,170 @@ +import json +import re +from copy import deepcopy +from importlib.resources import files +from itertools import zip_longest +from typing import Any, Callable + +import pytest + +from mex.common.models import EXTRACTED_MODEL_CLASSES_BY_NAME +from mex.common.transform import dromedary_to_kebab +from mex.common.types.identifier import MEX_ID_PATTERN + +SPECIFIED_SCHEMA_PATH = files("mex.model").joinpath("entities") + +GENERATED_SCHEMAS = dict( + sorted( + { + name.removeprefix("Extracted"): model.model_json_schema( + ref_template="/schema/fields/{model}" + ) + for name, model in EXTRACTED_MODEL_CLASSES_BY_NAME.items() + }.items() + ) +) +SPECIFIED_SCHEMAS = dict( + sorted( + { + schema["title"].replace(" ", ""): schema + for file_name in SPECIFIED_SCHEMA_PATH.glob("*.json") + if (schema := json.load(open(file_name, encoding="utf-8"))) + and not schema["title"].startswith("Concept") + }.items() + ) +) +ENTITY_TYPES_AND_FIELD_NAMES_BY_FQN = { + f"{entity_type}.{field_name}": (entity_type, field_name) + for entity_type, schema in SPECIFIED_SCHEMAS.items() + for field_name in schema["properties"] +} + + +def test_entity_types_match_spec() -> None: + assert list(GENERATED_SCHEMAS) == list(SPECIFIED_SCHEMAS) + + +@pytest.mark.parametrize( + ("generated", "specified"), + zip_longest(GENERATED_SCHEMAS.values(), SPECIFIED_SCHEMAS.values()), + ids=GENERATED_SCHEMAS, +) +def test_field_names_match_spec( + generated: dict[str, Any], specified: dict[str, Any] +) -> None: + assert set(generated["properties"]) == set(specified["properties"]) + + +@pytest.mark.parametrize( + ("generated", "specified"), + zip_longest(GENERATED_SCHEMAS.values(), SPECIFIED_SCHEMAS.values()), + ids=GENERATED_SCHEMAS, +) +def test_required_fields_match_spec( + generated: dict[str, Any], specified: dict[str, Any] +) -> None: + assert set(generated["required"]) == set(specified["required"]) + + +def deduplicate_dicts(dct: dict[str, Any], key: str) -> None: + # take a set of dicts and deduplicate them by dumping/loading to json + dct[key] = [json.loads(s) for s in dict.fromkeys(json.dumps(d) for d in dct[key])] + + +def dissolve_single_item_lists(dct: dict[str, Any], key: str) -> None: + # if a list in a dict value has just one item, dissolve it into the parent dict + if len(dct[key]) == 1 and isinstance(dct[key][0], dict): + dct.update(dct.pop(key)[0]) + + +def sub_only_text(repl: Callable[[str], str], string: str) -> str: + # substitute only the textual parts of a string, e.g. leave slashes alone + return re.sub(r"([a-zA-Z_-]+)", lambda m: repl(m.group(0)), string) + + +def prepare_field(field: str, obj: list[Any] | dict[str, Any]) -> None: + # prepare each item in a list (in-place) + if isinstance(obj, list): + for item in obj: + prepare_field(field, item) + obj[:] = [item for item in obj if item] + return + + # discard annotations that we can safely ignore + # (these have no use-case and no implementation plans yet) + obj.pop("sameAs", None) # only in spec + obj.pop("subPropertyOf", None) # only in spec + obj.pop("description", None) # only in model (mostly implementation hints) + + # pop annotations that we don't compare directly but use for other comparisons + title = obj.pop("title", "") # only in model (autogenerated by pydantic) + use_scheme = obj.pop("useScheme", "") # only in spec (needed to select vocabulary) + vocabulary = use_scheme.removeprefix("https://mex.rki.de/item/") # vocabulary name + + # ignore differences between dates and datetimes + # (we only have `Timestamp` as a date-time implementation, but no type for `date`, + # but we might/should add that in the future) + if obj.get("format") in ("date", "date-time"): + obj.pop("examples", None) + obj.pop("pattern", None) + obj["format"] = "date-time" + + # align reference paths + # (the paths to referenced vocabularies and types differ between the models + # and the specification, so we need to make sure they match before comparing) + if obj.get("pattern") == MEX_ID_PATTERN: + obj.pop("pattern") + obj.pop("type") + if field in ("identifier", "stableTargetId"): + obj["$ref"] = "/schema/fields/identifier" + else: + obj["$ref"] = f"/schema/entities/{title.removesuffix('ID')}#/identifier" + + # align concept/enum annotations + # (spec uses `useScheme` to specify vocabularies and models use enums) + if obj.get("$ref") == "/schema/entities/concept#/identifier": + obj["$ref"] = f"/schema/fields/{vocabulary}" + + # make sure all refs have paths in kebab-case + # (the models use the class names, whereas the spec uses kebab-case URLs) + if "$ref" in obj: + obj["$ref"] = sub_only_text(dromedary_to_kebab, obj["$ref"]) + + # recurse into the field definitions for array items + if obj.get("type") == "array": + prepare_field(field, obj["items"]) + + for quantifier in {"anyOf", "allOf"} & set(obj): + # prepare choices + prepare_field(field, obj[quantifier]) + # deduplicate items, used for date/times + deduplicate_dicts(obj, quantifier) + # collapse non-choices + dissolve_single_item_lists(obj, quantifier) + + +@pytest.mark.parametrize( + ("entity_type", "field_name"), + ENTITY_TYPES_AND_FIELD_NAMES_BY_FQN.values(), + ids=ENTITY_TYPES_AND_FIELD_NAMES_BY_FQN.keys(), +) +def test_field_defs_match_spec(entity_type: str, field_name: str) -> None: + specified_properties = SPECIFIED_SCHEMAS[entity_type]["properties"] + generated_properties = GENERATED_SCHEMAS[entity_type]["properties"] + specified = deepcopy(specified_properties[field_name]) + generated = deepcopy(generated_properties[field_name]) + + prepare_field(field_name, specified) + prepare_field(field_name, generated) + + assert ( + generated == specified + ), f""" +{entity_type}.{field_name} + +specified: +{json.dumps(specified_properties[field_name], indent=4, sort_keys=True)} + +generated: +{json.dumps(generated_properties[field_name], indent=4, sort_keys=True)} +""" diff --git a/tests/public_api/test_connector.py b/tests/public_api/test_connector.py index bab905ae..45d5e8eb 100644 --- a/tests/public_api/test_connector.py +++ b/tests/public_api/test_connector.py @@ -133,6 +133,7 @@ def test_post_models_mocked( assert payload == expected_payload +@pytest.mark.skip(reason="public api is being deprecated") @pytest.mark.integration def test_search_model_that_does_not_exist() -> None: random_id = Identifier.generate() @@ -318,6 +319,7 @@ def test_get_all_items_mocked( assert items == mex_metadata_items_response +@pytest.mark.skip(reason="public api is being deprecated") @pytest.mark.integration def test_get_all_items() -> None: connector = PublicApiConnector.get() diff --git a/tests/public_api/test_extract.py b/tests/public_api/test_extract.py index 00f3d9bc..39b134b5 100644 --- a/tests/public_api/test_extract.py +++ b/tests/public_api/test_extract.py @@ -10,6 +10,7 @@ from mex.common.public_api.models import PublicApiMetadataItemsResponse +@pytest.mark.skip(reason="public api is being deprecated") @pytest.mark.integration def test_extract_mex_person_items() -> None: mex_persons = list(extract_mex_person_items()) @@ -38,8 +39,7 @@ def __init__(self: PublicApiConnector) -> None: assert mex_persons == mex_metadata_items_response.items[2:4] * 2 -@pytest.mark.integration -def test_extract_mex_person_items_limit_reached( +def test_extract_mex_person_items_mocked_limit_reached( mex_metadata_items_response: PublicApiMetadataItemsResponse, monkeypatch: MonkeyPatch, ) -> None: diff --git a/tests/sinks/test_public_api.py b/tests/sinks/test_public_api.py index 698f22c6..ceac5787 100644 --- a/tests/sinks/test_public_api.py +++ b/tests/sinks/test_public_api.py @@ -48,6 +48,7 @@ def __init__(self: PublicApiConnector) -> None: delete_model.assert_called_once_with(extracted_person) +@pytest.mark.skip(reason="public api is being deprecated") @pytest.mark.integration def test_public_api_post_and_purge_roundtrip( extracted_primary_sources: dict[str, ExtractedPrimarySource] diff --git a/tests/test_transform.py b/tests/test_transform.py index 6db462a4..7cc443d0 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -9,7 +9,13 @@ from pydantic import BaseModel as PydanticModel from pydantic import Field, SecretStr -from mex.common.transform import MExEncoder, dromedary_to_snake, snake_to_dromedary +from mex.common.transform import ( + MExEncoder, + dromedary_to_kebab, + dromedary_to_snake, + kebab_to_camel, + snake_to_dromedary, +) from mex.common.types import Identifier, Timestamp @@ -93,3 +99,49 @@ def test_snake_to_dromedary(string: str, expected: str) -> None: def test_dromedary_to_snake(string: str, expected: str) -> None: result = dromedary_to_snake(string) assert result == expected + + +@pytest.mark.parametrize( + ("string", "expected"), + [ + ("", ""), + ("word", "word"), + ("already-kebab", "already-kebab"), + ("Such-AWeird-MIXEDCase", "such-a-weird-mixed-case"), + ("ABWordCDEWordFG", "ab-word-cde-word-fg"), + ("multipleWordsInAString", "multiple-words-in-a-string"), + ], + ids=[ + "empty", + "single word", + "already kebab", + "mixed case", + "caps words", + "multiple words", + ], +) +def test_dromedary_to_kebab(string: str, expected: str) -> None: + result = dromedary_to_kebab(string) + assert result == expected + + +@pytest.mark.parametrize( + ("string", "expected"), + [ + ("", ""), + ("word", "Word"), + ("AlreadyCamel", "AlreadyCamel"), + ("Mixed-CASE", "MixedCase"), + ("multiple-words-in-a-string", "MultipleWordsInAString"), + ], + ids=[ + "empty", + "single word", + "already camel", + "mixed case", + "multiple words", + ], +) +def test_kebab_to_camel(string: str, expected: str) -> None: + result = kebab_to_camel(string) + assert result == expected diff --git a/tests/types/test_timestamp.py b/tests/types/test_timestamp.py index 0dbfa21f..56c29ea9 100644 --- a/tests/types/test_timestamp.py +++ b/tests/types/test_timestamp.py @@ -1,7 +1,8 @@ -from datetime import datetime +from datetime import date, datetime from typing import Any import pytest +from pytz import timezone from mex.common.types import CET, UTC, Timestamp @@ -60,15 +61,29 @@ def test_timestamp_validation_errors(value: Any, message: str) -> None: "1999-01-20T21:00:00Z", ), ( - (datetime(2020, 3, 22),), + ("2016-06-10T21:42:24.76073899Z",), {}, - "2020-03-21T23:00:00Z", + "2016-06-10T21:42:24Z", + ), + ( + (date(2020, 3, 22),), + {}, + "2020-03-22", ), ( (datetime(2020, 3, 22, 14, 30, 58),), {}, "2020-03-22T13:30:58Z", ), + ( + ( + datetime( + 2020, 3, 22, 14, 30, 58, tzinfo=timezone("America/Los_Angeles") + ), + ), + {}, + "2020-03-22T22:23:58Z", + ), ( (Timestamp(2004, 11),), {}, @@ -86,8 +101,10 @@ def test_timestamp_validation_errors(value: Any, message: str) -> None: "date string", "time string", "padded time", + "nano seconds", "date", "datetime", + "pacific time", "timestamp", ], ) @@ -96,3 +113,29 @@ def test_timestamp_parsing( ) -> None: timestamp = Timestamp(*args, **kwargs) assert str(timestamp) == expected + + +def test_timestamp_eq() -> None: + assert Timestamp(2004) == Timestamp("2004") + assert Timestamp(2004, 11) == Timestamp(2004, 11) + assert Timestamp(2004, 11, 2) == "2004-11-02" + assert Timestamp(2020, 3, 22, 14, 30, 58, 0) == datetime(2020, 3, 22, 14, 30, 58, 0) + assert Timestamp(2005) != object() + + +def test_timestamp_gt() -> None: + assert Timestamp(2004) > Timestamp("2003") + assert Timestamp(2004, 11) < "2013-10-02" + assert Timestamp(2004, 11) <= Timestamp(2004, 12) + assert Timestamp(2020, 3, 22, 14, 30, 58) >= datetime(2020, 3, 22, 14, 29) + + with pytest.raises(NotImplementedError): + assert Timestamp(2005) > object() + + +def test_timestamp_str() -> None: + assert str(Timestamp(2004, 11, 26)) == "2004-11-26" + + +def test_timestamp_repr() -> None: + assert repr(Timestamp(2018, 3, 2, 13, 0, 1)) == 'Timestamp("2018-03-02T12:00:01Z")' diff --git a/tests/wikidata/test_transform.py b/tests/wikidata/test_transform.py index e5d176dd..7b34aa77 100644 --- a/tests/wikidata/test_transform.py +++ b/tests/wikidata/test_transform.py @@ -48,7 +48,7 @@ def test_transform_wikidata_organization_to_organization( "rorId": ["https://ror.org/05vs9tj88", "https://ror.org/044kkbh92"], "shortName": [], "viafId": ["https://viaf.org/viaf/129013645"], - "wikidataId": "https://www.wikidata.org/entity/Q26678", + "wikidataId": ["https://www.wikidata.org/entity/Q26678"], } with open(TESTDATA_DIR / "items_details.json", "r", encoding="utf-8") as f: