generated from coulomb/repo-seed
697 lines
24 KiB
Python
697 lines
24 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
from decimal import Decimal
|
|
from typing import Any, Literal
|
|
|
|
from .pricing_models import PricingModel
|
|
|
|
PublicationArtifactKind = Literal["product", "meter", "price", "commitment", "configuration"]
|
|
ArtifactMappingStatus = Literal["exact", "approximate", "unsupported"]
|
|
PublicationOperationKind = Literal["create", "update", "noop", "retire", "rollback"]
|
|
DriftSeverity = Literal["info", "warn", "error"]
|
|
|
|
|
|
def _serialize_value(value: Any) -> Any:
|
|
if isinstance(value, Decimal):
|
|
return str(value)
|
|
if hasattr(value, "__dataclass_fields__"):
|
|
return {
|
|
key: _serialize_value(getattr(value, key))
|
|
for key in value.__dataclass_fields__
|
|
}
|
|
if isinstance(value, tuple):
|
|
return [_serialize_value(item) for item in value]
|
|
if isinstance(value, list):
|
|
return [_serialize_value(item) for item in value]
|
|
if isinstance(value, dict):
|
|
return {key: _serialize_value(item) for key, item in value.items()}
|
|
return value
|
|
|
|
|
|
def _payload_signature(payload: Any) -> str:
|
|
return json.dumps(_serialize_value(payload), sort_keys=True)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CatalogProduct:
|
|
id: str
|
|
name: str
|
|
description: str
|
|
currency: str
|
|
lifecycle_phase: str
|
|
active_pricing_model_id: str | None = None
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishableProduct:
|
|
key: str
|
|
product_id: str
|
|
name: str
|
|
description: str
|
|
currency: str
|
|
lifecycle_phase: str
|
|
active: bool
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishableMeter:
|
|
key: str
|
|
meter_id: str
|
|
name: str
|
|
event_name: str
|
|
unit: str
|
|
aggregation: str = "sum"
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishablePrice:
|
|
key: str
|
|
price_id: str
|
|
product_key: str
|
|
component_id: str
|
|
component_kind: str
|
|
label: str
|
|
currency: str
|
|
billing_treatment: str
|
|
cadence: str | None = None
|
|
amount: Decimal | None = None
|
|
unit_price: Decimal | None = None
|
|
included_units: Decimal | None = None
|
|
meter_key: str | None = None
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishableCommitment:
|
|
key: str
|
|
commitment_id: str
|
|
kind: str
|
|
value: str
|
|
unit: str | None = None
|
|
description: str = ""
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishableConfiguration:
|
|
key: str
|
|
configuration_id: str
|
|
product_key: str
|
|
model_id: str
|
|
model_name: str
|
|
segment: str | None
|
|
price_keys: tuple[str, ...]
|
|
commitment_keys: tuple[str, ...]
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublicationBundle:
|
|
bundle_id: str
|
|
model_id: str
|
|
model_name: str
|
|
product: PublishableProduct
|
|
meters: tuple[PublishableMeter, ...]
|
|
prices: tuple[PublishablePrice, ...]
|
|
commitments: tuple[PublishableCommitment, ...]
|
|
configurations: tuple[PublishableConfiguration, ...]
|
|
provider_hints: dict[str, Any] = field(default_factory=dict)
|
|
notes: tuple[str, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProviderMappedArtifact:
|
|
provider: str
|
|
source_key: str
|
|
source_kind: PublicationArtifactKind
|
|
provider_id: str
|
|
provider_object_type: str
|
|
mapping_status: ArtifactMappingStatus
|
|
payload: dict[str, Any]
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
notes: tuple[str, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProviderPublicationPackage:
|
|
provider: str
|
|
bundle_id: str
|
|
model_id: str
|
|
model_name: str
|
|
artifacts: tuple[ProviderMappedArtifact, ...]
|
|
notes: tuple[str, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DriftFinding:
|
|
provider_id: str
|
|
provider_object_type: str
|
|
severity: DriftSeverity
|
|
summary: str
|
|
expected: dict[str, Any] = field(default_factory=dict)
|
|
actual: dict[str, Any] = field(default_factory=dict)
|
|
suggested_action: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublicationOperation:
|
|
kind: PublicationOperationKind
|
|
provider_id: str
|
|
provider_object_type: str
|
|
source_key: str | None
|
|
source_kind: PublicationArtifactKind | None
|
|
mapping_status: ArtifactMappingStatus | None
|
|
summary: str
|
|
desired_payload: dict[str, Any] = field(default_factory=dict)
|
|
current_payload: dict[str, Any] = field(default_factory=dict)
|
|
desired_metadata: dict[str, Any] = field(default_factory=dict)
|
|
current_metadata: dict[str, Any] = field(default_factory=dict)
|
|
desired_notes: tuple[str, ...] = ()
|
|
current_notes: tuple[str, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublishedProviderArtifact:
|
|
provider: str
|
|
source_key: str
|
|
source_kind: PublicationArtifactKind
|
|
provider_id: str
|
|
provider_object_type: str
|
|
mapping_status: ArtifactMappingStatus
|
|
payload: dict[str, Any]
|
|
metadata: dict[str, Any] = field(default_factory=dict)
|
|
notes: tuple[str, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublicationRevision:
|
|
revision_id: str
|
|
model_id: str
|
|
model_name: str
|
|
summary: str
|
|
operations: tuple[PublicationOperation, ...]
|
|
snapshot: tuple[PublishedProviderArtifact, ...]
|
|
replaced_revision_id: str | None = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ProviderPublicationState:
|
|
provider: str
|
|
active_revision_id: str | None = None
|
|
active_model_id: str | None = None
|
|
artifacts: tuple[PublishedProviderArtifact, ...] = ()
|
|
revisions: tuple[PublicationRevision, ...] = ()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublicationPlan:
|
|
provider: str
|
|
bundle_id: str
|
|
model_id: str
|
|
model_name: str
|
|
operations: tuple[PublicationOperation, ...]
|
|
drift: tuple[DriftFinding, ...]
|
|
unsupported_artifacts: tuple[ProviderMappedArtifact, ...]
|
|
summary: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PublicationApplyResult:
|
|
plan: PublicationPlan
|
|
revision: PublicationRevision
|
|
state: ProviderPublicationState
|
|
summary: str
|
|
|
|
|
|
def build_publication_bundle(
|
|
product: CatalogProduct,
|
|
model: PricingModel,
|
|
*,
|
|
configuration_id: str | None = None,
|
|
segment: str | None = None,
|
|
) -> PublicationBundle:
|
|
product_key = f"product:{product.id}"
|
|
publishable_product = PublishableProduct(
|
|
key=product_key,
|
|
product_id=product.id,
|
|
name=product.name,
|
|
description=product.description,
|
|
currency=product.currency,
|
|
lifecycle_phase=product.lifecycle_phase,
|
|
active=model.status != "retired",
|
|
metadata={
|
|
**product.metadata,
|
|
"adaptive_pricing_model_id": model.id,
|
|
"adaptive_pricing_model_status": model.status,
|
|
},
|
|
)
|
|
|
|
meters: list[PublishableMeter] = []
|
|
prices: list[PublishablePrice] = []
|
|
for component in model.charge_components:
|
|
meter_key: str | None = None
|
|
if component.kind == "usage" and component.meter:
|
|
meter_key = f"meter:{model.id}:{component.id}"
|
|
meters.append(
|
|
PublishableMeter(
|
|
key=meter_key,
|
|
meter_id=component.meter,
|
|
name=component.label or component.meter,
|
|
event_name=component.meter,
|
|
unit=component.unit or "usage_unit",
|
|
metadata={
|
|
"adaptive_pricing_model_id": model.id,
|
|
"source_component_id": component.id,
|
|
},
|
|
)
|
|
)
|
|
|
|
prices.append(
|
|
PublishablePrice(
|
|
key=f"price:{model.id}:{component.id}",
|
|
price_id=f"{model.id}:{component.id}",
|
|
product_key=product_key,
|
|
component_id=component.id,
|
|
component_kind=component.kind,
|
|
label=component.label or component.id,
|
|
currency=model.currency,
|
|
billing_treatment=component.billing_treatment or "recurring",
|
|
cadence=component.cadence or (
|
|
model.access_fee_cadence if component.kind in {"access", "support", "discount", "risk_adjustment"} else None
|
|
),
|
|
amount=component.amount,
|
|
unit_price=component.unit_price,
|
|
included_units=component.included_units,
|
|
meter_key=meter_key,
|
|
metadata={
|
|
**component.metadata,
|
|
"adaptive_pricing_model_id": model.id,
|
|
"source_component_id": component.id,
|
|
"source_component_kind": component.kind,
|
|
},
|
|
)
|
|
)
|
|
|
|
commitments = tuple(
|
|
PublishableCommitment(
|
|
key=f"commitment:{model.id}:{commitment.id}",
|
|
commitment_id=commitment.id,
|
|
kind=commitment.kind,
|
|
value=commitment.value,
|
|
unit=commitment.unit,
|
|
description=commitment.description,
|
|
metadata={"adaptive_pricing_model_id": model.id},
|
|
)
|
|
for commitment in model.commitments
|
|
)
|
|
|
|
configuration = PublishableConfiguration(
|
|
key=f"configuration:{configuration_id or model.id}",
|
|
configuration_id=configuration_id or model.id,
|
|
product_key=product_key,
|
|
model_id=model.id,
|
|
model_name=model.name,
|
|
segment=segment,
|
|
price_keys=tuple(price.key for price in prices),
|
|
commitment_keys=tuple(commitment.key for commitment in commitments),
|
|
metadata={
|
|
"adaptive_pricing_model_id": model.id,
|
|
"lifecycle_phase": model.lifecycle_phase,
|
|
},
|
|
)
|
|
|
|
notes = (
|
|
"Publication bundles preserve the internal pricing model as the source of truth.",
|
|
"Provider mappings may mark artifacts exact, approximate, or unsupported without mutating the bundle.",
|
|
)
|
|
|
|
return PublicationBundle(
|
|
bundle_id=f"bundle:{model.id}",
|
|
model_id=model.id,
|
|
model_name=model.name,
|
|
product=publishable_product,
|
|
meters=tuple(meters),
|
|
prices=tuple(prices),
|
|
commitments=commitments,
|
|
configurations=(configuration,),
|
|
provider_hints=model.provider_hints,
|
|
notes=notes,
|
|
)
|
|
|
|
|
|
def _published_artifact(mapped: ProviderMappedArtifact) -> PublishedProviderArtifact:
|
|
return PublishedProviderArtifact(
|
|
provider=mapped.provider,
|
|
source_key=mapped.source_key,
|
|
source_kind=mapped.source_kind,
|
|
provider_id=mapped.provider_id,
|
|
provider_object_type=mapped.provider_object_type,
|
|
mapping_status=mapped.mapping_status,
|
|
payload=mapped.payload,
|
|
metadata=mapped.metadata,
|
|
notes=mapped.notes,
|
|
)
|
|
|
|
|
|
def _artifact_changed(
|
|
desired: ProviderMappedArtifact,
|
|
current: PublishedProviderArtifact,
|
|
) -> bool:
|
|
return any(
|
|
[
|
|
desired.mapping_status != current.mapping_status,
|
|
desired.provider_object_type != current.provider_object_type,
|
|
_payload_signature(desired.payload) != _payload_signature(current.payload),
|
|
_payload_signature(desired.metadata) != _payload_signature(current.metadata),
|
|
desired.notes != current.notes,
|
|
]
|
|
)
|
|
|
|
|
|
def _diff_summary(
|
|
desired: ProviderMappedArtifact,
|
|
current: PublishedProviderArtifact,
|
|
) -> DriftFinding:
|
|
return DriftFinding(
|
|
provider_id=desired.provider_id,
|
|
provider_object_type=desired.provider_object_type,
|
|
severity="warn",
|
|
summary="Provider shadow state differs from desired pricing definition.",
|
|
expected={
|
|
"payload": desired.payload,
|
|
"metadata": desired.metadata,
|
|
"mapping_status": desired.mapping_status,
|
|
"notes": list(desired.notes),
|
|
},
|
|
actual={
|
|
"payload": current.payload,
|
|
"metadata": current.metadata,
|
|
"mapping_status": current.mapping_status,
|
|
"notes": list(current.notes),
|
|
},
|
|
suggested_action="Publish the desired definition again or reconcile the provider-side drift.",
|
|
)
|
|
|
|
|
|
def plan_publication(
|
|
package: ProviderPublicationPackage,
|
|
current_state: ProviderPublicationState | None = None,
|
|
) -> PublicationPlan:
|
|
current_state = current_state or ProviderPublicationState(provider=package.provider)
|
|
current_index = {artifact.provider_id: artifact for artifact in current_state.artifacts}
|
|
desired_publishable = tuple(
|
|
artifact for artifact in package.artifacts if artifact.mapping_status != "unsupported"
|
|
)
|
|
unsupported = tuple(
|
|
artifact for artifact in package.artifacts if artifact.mapping_status == "unsupported"
|
|
)
|
|
|
|
operations: list[PublicationOperation] = []
|
|
drift: list[DriftFinding] = []
|
|
desired_ids = {artifact.provider_id for artifact in desired_publishable}
|
|
|
|
for artifact in desired_publishable:
|
|
current = current_index.get(artifact.provider_id)
|
|
if current is None:
|
|
operations.append(
|
|
PublicationOperation(
|
|
kind="create",
|
|
provider_id=artifact.provider_id,
|
|
provider_object_type=artifact.provider_object_type,
|
|
source_key=artifact.source_key,
|
|
source_kind=artifact.source_kind,
|
|
mapping_status=artifact.mapping_status,
|
|
summary="Create provider artifact from canonical pricing definition.",
|
|
desired_payload=artifact.payload,
|
|
desired_metadata=artifact.metadata,
|
|
desired_notes=artifact.notes,
|
|
)
|
|
)
|
|
continue
|
|
|
|
if _artifact_changed(artifact, current):
|
|
operations.append(
|
|
PublicationOperation(
|
|
kind="update",
|
|
provider_id=artifact.provider_id,
|
|
provider_object_type=artifact.provider_object_type,
|
|
source_key=artifact.source_key,
|
|
source_kind=artifact.source_kind,
|
|
mapping_status=artifact.mapping_status,
|
|
summary="Update provider artifact to match canonical pricing definition.",
|
|
desired_payload=artifact.payload,
|
|
current_payload=current.payload,
|
|
desired_metadata=artifact.metadata,
|
|
current_metadata=current.metadata,
|
|
desired_notes=artifact.notes,
|
|
current_notes=current.notes,
|
|
)
|
|
)
|
|
drift.append(_diff_summary(artifact, current))
|
|
continue
|
|
|
|
operations.append(
|
|
PublicationOperation(
|
|
kind="noop",
|
|
provider_id=artifact.provider_id,
|
|
provider_object_type=artifact.provider_object_type,
|
|
source_key=artifact.source_key,
|
|
source_kind=artifact.source_kind,
|
|
mapping_status=artifact.mapping_status,
|
|
summary="Provider artifact already matches canonical pricing definition.",
|
|
desired_payload=artifact.payload,
|
|
current_payload=current.payload,
|
|
desired_metadata=artifact.metadata,
|
|
current_metadata=current.metadata,
|
|
desired_notes=artifact.notes,
|
|
current_notes=current.notes,
|
|
)
|
|
)
|
|
|
|
for artifact in current_state.artifacts:
|
|
if artifact.provider_id in desired_ids:
|
|
continue
|
|
operations.append(
|
|
PublicationOperation(
|
|
kind="retire",
|
|
provider_id=artifact.provider_id,
|
|
provider_object_type=artifact.provider_object_type,
|
|
source_key=artifact.source_key,
|
|
source_kind=artifact.source_kind,
|
|
mapping_status=artifact.mapping_status,
|
|
summary="Retire managed provider artifact no longer present in the desired pricing definition.",
|
|
current_payload=artifact.payload,
|
|
current_metadata=artifact.metadata,
|
|
current_notes=artifact.notes,
|
|
)
|
|
)
|
|
drift.append(
|
|
DriftFinding(
|
|
provider_id=artifact.provider_id,
|
|
provider_object_type=artifact.provider_object_type,
|
|
severity="warn",
|
|
summary="Managed provider artifact exists in shadow state but not in the desired pricing definition.",
|
|
actual={
|
|
"payload": artifact.payload,
|
|
"metadata": artifact.metadata,
|
|
"mapping_status": artifact.mapping_status,
|
|
"notes": list(artifact.notes),
|
|
},
|
|
suggested_action="Retire the artifact or republish the desired model.",
|
|
)
|
|
)
|
|
|
|
summary = (
|
|
f"{package.provider}: {sum(op.kind == 'create' for op in operations)} create, "
|
|
f"{sum(op.kind == 'update' for op in operations)} update, "
|
|
f"{sum(op.kind == 'retire' for op in operations)} retire, "
|
|
f"{sum(op.kind == 'noop' for op in operations)} noop, "
|
|
f"{len(unsupported)} unsupported."
|
|
)
|
|
|
|
return PublicationPlan(
|
|
provider=package.provider,
|
|
bundle_id=package.bundle_id,
|
|
model_id=package.model_id,
|
|
model_name=package.model_name,
|
|
operations=tuple(operations),
|
|
drift=tuple(drift),
|
|
unsupported_artifacts=unsupported,
|
|
summary=summary,
|
|
)
|
|
|
|
|
|
def _next_revision_id(state: ProviderPublicationState) -> str:
|
|
return f"{state.provider}-rev-{len(state.revisions) + 1:04d}"
|
|
|
|
|
|
def _next_active_model_id(artifacts: list[PublishedProviderArtifact]) -> str | None:
|
|
product = next((artifact for artifact in artifacts if artifact.source_kind == "product"), None)
|
|
if product is None:
|
|
return None
|
|
if product.metadata.get("model_id"):
|
|
return str(product.metadata["model_id"])
|
|
payload_metadata = product.payload.get("metadata", {})
|
|
if payload_metadata.get("adaptive_pricing_model_id"):
|
|
return str(payload_metadata["adaptive_pricing_model_id"])
|
|
return None
|
|
|
|
|
|
def apply_publication(
|
|
package: ProviderPublicationPackage,
|
|
current_state: ProviderPublicationState | None = None,
|
|
) -> PublicationApplyResult:
|
|
current_state = current_state or ProviderPublicationState(provider=package.provider)
|
|
plan = plan_publication(package, current_state)
|
|
desired_index = {
|
|
artifact.provider_id: artifact
|
|
for artifact in package.artifacts
|
|
if artifact.mapping_status != "unsupported"
|
|
}
|
|
artifact_index = {artifact.provider_id: artifact for artifact in current_state.artifacts}
|
|
|
|
for operation in plan.operations:
|
|
if operation.kind in {"create", "update", "noop"}:
|
|
artifact_index[operation.provider_id] = _published_artifact(
|
|
desired_index[operation.provider_id]
|
|
)
|
|
elif operation.kind == "retire":
|
|
artifact_index.pop(operation.provider_id, None)
|
|
|
|
snapshot = tuple(sorted(artifact_index.values(), key=lambda artifact: artifact.provider_id))
|
|
revision = PublicationRevision(
|
|
revision_id=_next_revision_id(current_state),
|
|
model_id=package.model_id,
|
|
model_name=package.model_name,
|
|
summary=plan.summary,
|
|
operations=plan.operations,
|
|
snapshot=snapshot,
|
|
replaced_revision_id=current_state.active_revision_id,
|
|
)
|
|
state = ProviderPublicationState(
|
|
provider=package.provider,
|
|
active_revision_id=revision.revision_id,
|
|
active_model_id=_next_active_model_id(list(snapshot)),
|
|
artifacts=snapshot,
|
|
revisions=current_state.revisions + (revision,),
|
|
)
|
|
return PublicationApplyResult(
|
|
plan=plan,
|
|
revision=revision,
|
|
state=state,
|
|
summary=plan.summary,
|
|
)
|
|
|
|
|
|
def rollback_publication(
|
|
state: ProviderPublicationState,
|
|
revision_id: str,
|
|
) -> PublicationApplyResult:
|
|
revision = next((item for item in state.revisions if item.revision_id == revision_id), None)
|
|
if revision is None:
|
|
raise ValueError(f"unknown revision_id '{revision_id}'")
|
|
|
|
rollback_operation = PublicationOperation(
|
|
kind="rollback",
|
|
provider_id=revision.revision_id,
|
|
provider_object_type="revision",
|
|
source_key=None,
|
|
source_kind=None,
|
|
mapping_status=None,
|
|
summary=f"Rollback provider shadow state to {revision.revision_id}.",
|
|
)
|
|
new_revision = PublicationRevision(
|
|
revision_id=_next_revision_id(state),
|
|
model_id=revision.model_id,
|
|
model_name=revision.model_name,
|
|
summary=f"Rolled back provider shadow state to {revision.revision_id}.",
|
|
operations=(rollback_operation,),
|
|
snapshot=revision.snapshot,
|
|
replaced_revision_id=state.active_revision_id,
|
|
)
|
|
new_state = ProviderPublicationState(
|
|
provider=state.provider,
|
|
active_revision_id=new_revision.revision_id,
|
|
active_model_id=revision.model_id,
|
|
artifacts=revision.snapshot,
|
|
revisions=state.revisions + (new_revision,),
|
|
)
|
|
return PublicationApplyResult(
|
|
plan=PublicationPlan(
|
|
provider=state.provider,
|
|
bundle_id=f"rollback:{revision.revision_id}",
|
|
model_id=revision.model_id,
|
|
model_name=revision.model_name,
|
|
operations=(rollback_operation,),
|
|
drift=(),
|
|
unsupported_artifacts=(),
|
|
summary=new_revision.summary,
|
|
),
|
|
revision=new_revision,
|
|
state=new_state,
|
|
summary=new_revision.summary,
|
|
)
|
|
|
|
|
|
def provider_state_to_dict(state: ProviderPublicationState) -> dict[str, Any]:
|
|
return _serialize_value(state)
|
|
|
|
|
|
def _operation_from_dict(raw: dict[str, Any]) -> PublicationOperation:
|
|
return PublicationOperation(
|
|
kind=raw["kind"],
|
|
provider_id=raw["provider_id"],
|
|
provider_object_type=raw["provider_object_type"],
|
|
source_key=raw.get("source_key"),
|
|
source_kind=raw.get("source_kind"),
|
|
mapping_status=raw.get("mapping_status"),
|
|
summary=raw["summary"],
|
|
desired_payload=dict(raw.get("desired_payload", {})),
|
|
current_payload=dict(raw.get("current_payload", {})),
|
|
desired_metadata=dict(raw.get("desired_metadata", {})),
|
|
current_metadata=dict(raw.get("current_metadata", {})),
|
|
desired_notes=tuple(raw.get("desired_notes", [])),
|
|
current_notes=tuple(raw.get("current_notes", [])),
|
|
)
|
|
|
|
|
|
def _artifact_from_dict(raw: dict[str, Any]) -> PublishedProviderArtifact:
|
|
return PublishedProviderArtifact(
|
|
provider=raw["provider"],
|
|
source_key=raw["source_key"],
|
|
source_kind=raw["source_kind"],
|
|
provider_id=raw["provider_id"],
|
|
provider_object_type=raw["provider_object_type"],
|
|
mapping_status=raw["mapping_status"],
|
|
payload=dict(raw.get("payload", {})),
|
|
metadata=dict(raw.get("metadata", {})),
|
|
notes=tuple(raw.get("notes", [])),
|
|
)
|
|
|
|
|
|
def _revision_from_dict(raw: dict[str, Any]) -> PublicationRevision:
|
|
return PublicationRevision(
|
|
revision_id=raw["revision_id"],
|
|
model_id=raw["model_id"],
|
|
model_name=raw["model_name"],
|
|
summary=raw["summary"],
|
|
operations=tuple(_operation_from_dict(item) for item in raw.get("operations", [])),
|
|
snapshot=tuple(_artifact_from_dict(item) for item in raw.get("snapshot", [])),
|
|
replaced_revision_id=raw.get("replaced_revision_id"),
|
|
)
|
|
|
|
|
|
def provider_state_from_dict(raw: dict[str, Any]) -> ProviderPublicationState:
|
|
return ProviderPublicationState(
|
|
provider=raw["provider"],
|
|
active_revision_id=raw.get("active_revision_id"),
|
|
active_model_id=raw.get("active_model_id"),
|
|
artifacts=tuple(_artifact_from_dict(item) for item in raw.get("artifacts", [])),
|
|
revisions=tuple(_revision_from_dict(item) for item in raw.get("revisions", [])),
|
|
)
|