Skip to content

Commit

Permalink
feature/mx-1649 prepare preventive and subtractive rule editing (#330)
Browse files Browse the repository at this point in the history
### PR Context

- some fixes and additions for
robert-koch-institut/mex-editor#192
- `mex/common/fields.py` is a port of
[`mex/backend/fields.py`](https://github.com/robert-koch-institut/mex-backend/blob/0.22.0/mex/backend/fields.py)
- because it is needed by the editor too
- prevent pydantic 2.10, because it broke tests:
#341

### Added

- add vocabulary and temporal unions and lookups to `mex.common.types`
- add `mex.common.fields` with field type by class name lookups

### Changes

- set default empty rules to all of the rule-set models
- pin pydantic to sub 2.10 (for now) because of breaking changes

### Fixed

- switch HTTP method for preview endpoint to `POST`
- add optional values to variadic values for distribution models
- make `endpointDescription` optional for variadic access platform
models

---------

Signed-off-by: Nicolas Drebenstedt <[email protected]>
  • Loading branch information
cutoffthetop authored Nov 29, 2024
1 parent 8b837e3 commit b1bb746
Show file tree
Hide file tree
Showing 28 changed files with 433 additions and 149 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ default_language_version:
python: python3.11
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.7.3
rev: v0.8.0
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- add vocabulary and temporal unions and lookups to `mex.common.types`
- add `mex.common.fields` with field type by class name lookups

### Changes

- wikidata helper now optionally accepts wikidata primary source
- set default empty rules to all of the rule-set models
- pin pydantic to sub 2.10 (for now) because of breaking changes

### Deprecated

### Removed

### Fixed

- switch HTTP method for preview endpoint to `POST`
- add optional values to variadic values for distribution models
- make `endpointDescription` optional for variadic access platform models

### Security

## [0.41.0] - 2024-11-18
Expand Down
2 changes: 1 addition & 1 deletion mex/common/backend_api/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def preview_merged_item(
A single merged item
"""
response = self.request(
method="GET",
method="POST",
endpoint=f"preview-item/{stable_target_id}",
payload=rule_set,
)
Expand Down
2 changes: 1 addition & 1 deletion mex/common/connector/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from mex.common.connector.http import HTTPConnector

__all__ = (
"CONNECTOR_STORE",
"BaseConnector",
"HTTPConnector",
"CONNECTOR_STORE",
)
137 changes: 137 additions & 0 deletions mex/common/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
from mex.common.models import (
ADDITIVE_MODEL_CLASSES_BY_NAME,
EXTRACTED_MODEL_CLASSES_BY_NAME,
MERGED_MODEL_CLASSES_BY_NAME,
PREVENTIVE_MODEL_CLASSES_BY_NAME,
SUBTRACTIVE_MODEL_CLASSES_BY_NAME,
)
from mex.common.types import (
MERGED_IDENTIFIER_CLASSES,
TEMPORAL_ENTITIES,
VOCABULARY_ENUMS,
Email,
Link,
LiteralStringType,
Text,
)
from mex.common.utils import contains_only_types, group_fields_by_class_name

# all models classes
ALL_MODEL_CLASSES_BY_NAME = {
**ADDITIVE_MODEL_CLASSES_BY_NAME,
**EXTRACTED_MODEL_CLASSES_BY_NAME,
**MERGED_MODEL_CLASSES_BY_NAME,
**PREVENTIVE_MODEL_CLASSES_BY_NAME,
**SUBTRACTIVE_MODEL_CLASSES_BY_NAME,
}

# fields that are immutable and can only be set once
FROZEN_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: field_info.frozen is True,
)

# static fields that are set once on class-level to a literal type
LITERAL_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: isinstance(field_info.annotation, LiteralStringType),
)

# fields typed as merged identifiers containing references to merged items
REFERENCE_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, *MERGED_IDENTIFIER_CLASSES),
)

# nested fields that contain `Text` objects
TEXT_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, Text),
)

# nested fields that contain `Link` objects
LINK_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, Link),
)

# fields annotated as `Email` type
EMAIL_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, Email),
)

# fields annotated as `int` type
INTEGER_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, int),
)

# fields annotated as `str` type
STRING_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, str),
)

# fields annotated as any temporal type
TEMPORAL_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, *TEMPORAL_ENTITIES),
)

# fields annotated as any vocabulary enum
VOCABULARY_FIELDS_BY_CLASS_NAME = group_fields_by_class_name(
ALL_MODEL_CLASSES_BY_NAME,
lambda field_info: contains_only_types(field_info, *VOCABULARY_ENUMS),
)

# fields with changeable values that are not nested objects or merged item references
MUTABLE_FIELDS_BY_CLASS_NAME = {
name: sorted(
{
field_name
for field_name in cls.get_all_fields()
if field_name
not in (
*FROZEN_FIELDS_BY_CLASS_NAME[name],
*REFERENCE_FIELDS_BY_CLASS_NAME[name],
*TEXT_FIELDS_BY_CLASS_NAME[name],
*LINK_FIELDS_BY_CLASS_NAME[name],
)
}
)
for name, cls in ALL_MODEL_CLASSES_BY_NAME.items()
}

# fields with mergeable values that are neither literal nor frozen
MERGEABLE_FIELDS_BY_CLASS_NAME = {
name: sorted(
{
field_name
for field_name in cls.model_fields
if field_name
not in (
*FROZEN_FIELDS_BY_CLASS_NAME[name],
*LITERAL_FIELDS_BY_CLASS_NAME[name],
)
}
)
for name, cls in ALL_MODEL_CLASSES_BY_NAME.items()
}

# fields with values that should be set once but are neither literal nor references
FINAL_FIELDS_BY_CLASS_NAME = {
name: sorted(
{
field_name
for field_name in cls.get_all_fields()
if field_name in FROZEN_FIELDS_BY_CLASS_NAME[name]
and field_name
not in (
*LITERAL_FIELDS_BY_CLASS_NAME[name],
*REFERENCE_FIELDS_BY_CLASS_NAME[name],
)
}
)
for name, cls in ALL_MODEL_CLASSES_BY_NAME.items()
}
2 changes: 1 addition & 1 deletion mex/common/identity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

__all__ = (
"BaseProvider",
"get_provider",
"Identity",
"get_provider",
"register_provider",
)
54 changes: 27 additions & 27 deletions mex/common/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,33 @@
)

__all__ = (
"ADDITIVE_MODEL_CLASSES",
"ADDITIVE_MODEL_CLASSES_BY_NAME",
"BASE_MODEL_CLASSES",
"BASE_MODEL_CLASSES_BY_NAME",
"EXTRACTED_MODEL_CLASSES",
"EXTRACTED_MODEL_CLASSES_BY_NAME",
"FILTER_MODEL_BY_EXTRACTED_CLASS_NAME",
"MAPPING_MODEL_BY_EXTRACTED_CLASS_NAME",
"MERGED_MODEL_CLASSES",
"MERGED_MODEL_CLASSES_BY_NAME",
"MEX_PRIMARY_SOURCE_IDENTIFIER",
"MEX_PRIMARY_SOURCE_IDENTIFIER_IN_PRIMARY_SOURCE",
"MEX_PRIMARY_SOURCE_STABLE_TARGET_ID",
"PREVENTIVE_MODEL_CLASSES",
"PREVENTIVE_MODEL_CLASSES_BY_NAME",
"RULE_MODEL_CLASSES",
"RULE_MODEL_CLASSES_BY_NAME",
"RULE_SET_REQUEST_CLASSES",
"RULE_SET_REQUEST_CLASSES_BY_NAME",
"RULE_SET_RESPONSE_CLASSES",
"RULE_SET_RESPONSE_CLASSES_BY_NAME",
"SUBTRACTIVE_MODEL_CLASSES",
"SUBTRACTIVE_MODEL_CLASSES_BY_NAME",
"AccessPlatformRuleSetRequest",
"AccessPlatformRuleSetResponse",
"ActivityRuleSetRequest",
"ActivityRuleSetResponse",
"ADDITIVE_MODEL_CLASSES_BY_NAME",
"ADDITIVE_MODEL_CLASSES",
"AdditiveAccessPlatform",
"AdditiveActivity",
"AdditiveBibliographicResource",
Expand All @@ -242,8 +263,6 @@
"AnyRuleSetRequest",
"AnyRuleSetResponse",
"AnySubtractiveModel",
"BASE_MODEL_CLASSES_BY_NAME",
"BASE_MODEL_CLASSES",
"BaseAccessPlatform",
"BaseActivity",
"BaseBibliographicResource",
Expand All @@ -264,8 +283,6 @@
"ContactPointRuleSetResponse",
"DistributionRuleSetRequest",
"DistributionRuleSetResponse",
"EXTRACTED_MODEL_CLASSES_BY_NAME",
"EXTRACTED_MODEL_CLASSES",
"ExtractedAccessPlatform",
"ExtractedActivity",
"ExtractedBibliographicResource",
Expand All @@ -281,13 +298,7 @@
"ExtractedResource",
"ExtractedVariable",
"ExtractedVariableGroup",
"FILTER_MODEL_BY_EXTRACTED_CLASS_NAME",
"generate_entity_filter_schema",
"generate_mapping_schema",
"GenericFieldInfo",
"MAPPING_MODEL_BY_EXTRACTED_CLASS_NAME",
"MERGED_MODEL_CLASSES_BY_NAME",
"MERGED_MODEL_CLASSES",
"MergedAccessPlatform",
"MergedActivity",
"MergedBibliographicResource",
Expand All @@ -303,17 +314,12 @@
"MergedResource",
"MergedVariable",
"MergedVariableGroup",
"MEX_PRIMARY_SOURCE_IDENTIFIER_IN_PRIMARY_SOURCE",
"MEX_PRIMARY_SOURCE_IDENTIFIER",
"MEX_PRIMARY_SOURCE_STABLE_TARGET_ID",
"OrganizationalUnitRuleSetRequest",
"OrganizationalUnitRuleSetResponse",
"OrganizationRuleSetRequest",
"OrganizationRuleSetResponse",
"OrganizationalUnitRuleSetRequest",
"OrganizationalUnitRuleSetResponse",
"PersonRuleSetRequest",
"PersonRuleSetResponse",
"PREVENTIVE_MODEL_CLASSES_BY_NAME",
"PREVENTIVE_MODEL_CLASSES",
"PreventiveAccessPlatform",
"PreventiveActivity",
"PreventiveBibliographicResource",
Expand All @@ -332,14 +338,6 @@
"PrimarySourceRuleSetResponse",
"ResourceRuleSetRequest",
"ResourceRuleSetResponse",
"RULE_MODEL_CLASSES_BY_NAME",
"RULE_MODEL_CLASSES",
"RULE_SET_REQUEST_CLASSES_BY_NAME",
"RULE_SET_REQUEST_CLASSES",
"RULE_SET_RESPONSE_CLASSES_BY_NAME",
"RULE_SET_RESPONSE_CLASSES",
"SUBTRACTIVE_MODEL_CLASSES_BY_NAME",
"SUBTRACTIVE_MODEL_CLASSES",
"SubtractiveAccessPlatform",
"SubtractiveActivity",
"SubtractiveBibliographicResource",
Expand All @@ -358,6 +356,8 @@
"VariableGroupRuleSetResponse",
"VariableRuleSetRequest",
"VariableRuleSetResponse",
"generate_entity_filter_schema",
"generate_mapping_schema",
)

MEX_PRIMARY_SOURCE_IDENTIFIER = ExtractedPrimarySourceIdentifier("00000000000001")
Expand Down
8 changes: 4 additions & 4 deletions mex/common/models/access_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class _SparseValues(_Stem):


class _VariadicValues(_Stem):
endpointDescription: list[Link]
endpointDescription: list[Link] = []
endpointType: list[APIType] = []
endpointURL: list[Link] = []
technicalAccessibility: list[TechnicalAccessibility] = []
Expand Down Expand Up @@ -141,9 +141,9 @@ class PreventiveAccessPlatform(_Stem, PreventiveRule):


class _BaseRuleSet(_Stem, RuleSet):
additive: AdditiveAccessPlatform
subtractive: SubtractiveAccessPlatform
preventive: PreventiveAccessPlatform
additive: AdditiveAccessPlatform = AdditiveAccessPlatform()
subtractive: SubtractiveAccessPlatform = SubtractiveAccessPlatform()
preventive: PreventiveAccessPlatform = PreventiveAccessPlatform()


class AccessPlatformRuleSetRequest(_BaseRuleSet):
Expand Down
6 changes: 3 additions & 3 deletions mex/common/models/activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,9 @@ class PreventiveActivity(_Stem, PreventiveRule):


class _BaseRuleSet(_Stem, RuleSet):
additive: AdditiveActivity
subtractive: SubtractiveActivity
preventive: PreventiveActivity
additive: AdditiveActivity = AdditiveActivity()
subtractive: SubtractiveActivity = SubtractiveActivity()
preventive: PreventiveActivity = PreventiveActivity()


class ActivityRuleSetRequest(_BaseRuleSet):
Expand Down
7 changes: 7 additions & 0 deletions mex/common/models/base/rules.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import TYPE_CHECKING

from mex.common.models.base.entity import BaseEntity


Expand All @@ -15,3 +17,8 @@ class PreventiveRule(BaseEntity):

class RuleSet(BaseEntity):
"""Base class for a set of an additive, subtractive and preventive rule."""

if TYPE_CHECKING: # pragma: no cover
additive: AdditiveRule
subtractive: SubtractiveRule
preventive: PreventiveRule
6 changes: 3 additions & 3 deletions mex/common/models/bibliographic_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,9 @@ class PreventiveBibliographicResource(_Stem, PreventiveRule):


class _BaseRuleSet(_Stem, RuleSet):
additive: AdditiveBibliographicResource
subtractive: SubtractiveBibliographicResource
preventive: PreventiveBibliographicResource
additive: AdditiveBibliographicResource = AdditiveBibliographicResource()
subtractive: SubtractiveBibliographicResource = SubtractiveBibliographicResource()
preventive: PreventiveBibliographicResource = PreventiveBibliographicResource()


class BibliographicResourceRuleSetRequest(_BaseRuleSet):
Expand Down
6 changes: 3 additions & 3 deletions mex/common/models/consent.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,9 @@ class PreventiveConsent(_Stem, PreventiveRule):


class _BaseRuleSet(_Stem, RuleSet):
additive: AdditiveConsent
subtractive: SubtractiveConsent
preventive: PreventiveConsent
additive: AdditiveConsent = AdditiveConsent()
subtractive: SubtractiveConsent = SubtractiveConsent()
preventive: PreventiveConsent = PreventiveConsent()


class ConsentRuleSetRequest(_BaseRuleSet):
Expand Down
Loading

0 comments on commit b1bb746

Please sign in to comment.