Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/mx-1649 prepare preventive and subtractive rule editing #330

Merged
merged 17 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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