Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54178ee56c | ||
|
|
10e7597612 | ||
|
|
142ccbc587 | ||
|
|
f75ad48d78 |
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-core"
|
name = "govoplan-core"
|
||||||
version = "0.1.18"
|
version = "0.1.21"
|
||||||
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
|
description = "Reusable GovOPlaN platform core, access, tenancy, and RBAC components."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal, Protocol, runtime_checkable
|
from typing import Literal, Protocol, runtime_checkable
|
||||||
|
|
||||||
|
from govoplan_core.core.access import PrincipalRef
|
||||||
from govoplan_core.core.external_references import (
|
from govoplan_core.core.external_references import (
|
||||||
SOURCE_AUTHORITY_MODES,
|
SOURCE_AUTHORITY_MODES,
|
||||||
SourceAuthorityMode,
|
SourceAuthorityMode,
|
||||||
@@ -24,6 +25,7 @@ CAPABILITY_DATASOURCE_LIFECYCLE = "datasources.lifecycle"
|
|||||||
CAPABILITY_DATASOURCE_PUBLICATION = "datasources.publication"
|
CAPABILITY_DATASOURCE_PUBLICATION = "datasources.publication"
|
||||||
CAPABILITY_DATASOURCE_ORIGINS = "connectors.datasourceOrigins"
|
CAPABILITY_DATASOURCE_ORIGINS = "connectors.datasourceOrigins"
|
||||||
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS = "datasources.artifactBackends"
|
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS = "datasources.artifactBackends"
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY = "policy.datasourceVisibility"
|
||||||
|
|
||||||
DatasourceMode = Literal["live", "cached", "static"]
|
DatasourceMode = Literal["live", "cached", "static"]
|
||||||
DatasourceKind = Literal[
|
DatasourceKind = Literal[
|
||||||
@@ -38,6 +40,7 @@ DatasourceKind = Literal[
|
|||||||
]
|
]
|
||||||
DatasourceShape = Literal["tabular", "document", "binary", "directory", "stream"]
|
DatasourceShape = Literal["tabular", "document", "binary", "directory", "stream"]
|
||||||
DatasourceConsistency = Literal["current", "live", "frozen"]
|
DatasourceConsistency = Literal["current", "live", "frozen"]
|
||||||
|
DatasourceVisibilityAction = Literal["discover", "read"]
|
||||||
DatasourcePublicationStatus = Literal[
|
DatasourcePublicationStatus = Literal[
|
||||||
"published",
|
"published",
|
||||||
"published_with_warnings",
|
"published_with_warnings",
|
||||||
@@ -70,6 +73,7 @@ class DatasourceField:
|
|||||||
name: str
|
name: str
|
||||||
data_type: str
|
data_type: str
|
||||||
nullable: bool = True
|
nullable: bool = True
|
||||||
|
classification: str = "internal"
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -90,6 +94,8 @@ class DatasourceGovernance:
|
|||||||
classification: str = "internal"
|
classification: str = "internal"
|
||||||
privacy_profile_ref: str | None = None
|
privacy_profile_ref: str | None = None
|
||||||
retention_policy_ref: str | None = None
|
retention_policy_ref: str | None = None
|
||||||
|
access_policy_ref: str | None = None
|
||||||
|
visibility_policy: Mapping[str, object] = field(default_factory=dict)
|
||||||
hold_refs: tuple[str, ...] = ()
|
hold_refs: tuple[str, ...] = ()
|
||||||
publication_state: str = "draft"
|
publication_state: str = "draft"
|
||||||
transfer_agreement_ref: str | None = None
|
transfer_agreement_ref: str | None = None
|
||||||
@@ -160,6 +166,12 @@ class DatasourceGovernance:
|
|||||||
retention_policy_ref=_optional_governance_text(
|
retention_policy_ref=_optional_governance_text(
|
||||||
source.get("retention_policy_ref")
|
source.get("retention_policy_ref")
|
||||||
),
|
),
|
||||||
|
access_policy_ref=_optional_governance_text(
|
||||||
|
source.get("access_policy_ref")
|
||||||
|
),
|
||||||
|
visibility_policy=_governance_mapping(
|
||||||
|
source.get("visibility_policy")
|
||||||
|
),
|
||||||
hold_refs=_governance_texts(source.get("hold_refs")),
|
hold_refs=_governance_texts(source.get("hold_refs")),
|
||||||
publication_state=str(source.get("publication_state") or "draft"),
|
publication_state=str(source.get("publication_state") or "draft"),
|
||||||
transfer_agreement_ref=_optional_governance_text(
|
transfer_agreement_ref=_optional_governance_text(
|
||||||
@@ -191,6 +203,8 @@ class DatasourceGovernance:
|
|||||||
"classification": self.classification,
|
"classification": self.classification,
|
||||||
"privacy_profile_ref": self.privacy_profile_ref,
|
"privacy_profile_ref": self.privacy_profile_ref,
|
||||||
"retention_policy_ref": self.retention_policy_ref,
|
"retention_policy_ref": self.retention_policy_ref,
|
||||||
|
"access_policy_ref": self.access_policy_ref,
|
||||||
|
"visibility_policy": dict(self.visibility_policy),
|
||||||
"hold_refs": list(self.hold_refs),
|
"hold_refs": list(self.hold_refs),
|
||||||
"publication_state": self.publication_state,
|
"publication_state": self.publication_state,
|
||||||
"transfer_agreement_ref": self.transfer_agreement_ref,
|
"transfer_agreement_ref": self.transfer_agreement_ref,
|
||||||
@@ -203,6 +217,39 @@ class DatasourceGovernance:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DatasourceVisibilityPolicyRequest:
|
||||||
|
tenant_id: str
|
||||||
|
datasource_ref: str
|
||||||
|
principal: PrincipalRef
|
||||||
|
action: DatasourceVisibilityAction
|
||||||
|
classification: str = "internal"
|
||||||
|
policy_ref: str | None = None
|
||||||
|
consistency: DatasourceConsistency = "current"
|
||||||
|
materialization_ref: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class DatasourceVisibilityPolicyDecision:
|
||||||
|
allowed: bool
|
||||||
|
reason: str | None = None
|
||||||
|
policies: tuple[Mapping[str, object], ...] = ()
|
||||||
|
decision_ref: str | None = None
|
||||||
|
provenance: Mapping[str, object] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class DatasourceVisibilityPolicyProvider(Protocol):
|
||||||
|
"""Optionally tighten Datasources-owned local visibility policy."""
|
||||||
|
|
||||||
|
def decide_datasource_visibility(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
*,
|
||||||
|
request: DatasourceVisibilityPolicyRequest,
|
||||||
|
) -> DatasourceVisibilityPolicyDecision: ...
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
class DatasourceDescriptor:
|
class DatasourceDescriptor:
|
||||||
ref: str
|
ref: str
|
||||||
@@ -659,6 +706,13 @@ def datasource_origins(registry: object | None) -> DatasourceOriginProvider | No
|
|||||||
return capability if isinstance(capability, DatasourceOriginProvider) else None
|
return capability if isinstance(capability, DatasourceOriginProvider) else None
|
||||||
|
|
||||||
|
|
||||||
|
def datasource_visibility_policy_provider(
|
||||||
|
registry: object | None,
|
||||||
|
) -> DatasourceVisibilityPolicyProvider | None:
|
||||||
|
capability = _capability(registry, CAPABILITY_POLICY_DATASOURCE_VISIBILITY)
|
||||||
|
return capability if isinstance(capability, DatasourceVisibilityPolicyProvider) else None
|
||||||
|
|
||||||
|
|
||||||
def _capability(registry: object | None, name: str) -> object | None:
|
def _capability(registry: object | None, name: str) -> object | None:
|
||||||
if (
|
if (
|
||||||
registry is None
|
registry is None
|
||||||
@@ -695,6 +749,7 @@ __all__ = [
|
|||||||
"CAPABILITY_DATASOURCE_LIFECYCLE",
|
"CAPABILITY_DATASOURCE_LIFECYCLE",
|
||||||
"CAPABILITY_DATASOURCE_ORIGINS",
|
"CAPABILITY_DATASOURCE_ORIGINS",
|
||||||
"CAPABILITY_DATASOURCE_PUBLICATION",
|
"CAPABILITY_DATASOURCE_PUBLICATION",
|
||||||
|
"CAPABILITY_POLICY_DATASOURCE_VISIBILITY",
|
||||||
"DatasourceAccessError",
|
"DatasourceAccessError",
|
||||||
"DatasourceArtifactReference",
|
"DatasourceArtifactReference",
|
||||||
"DatasourceArtifactBackend",
|
"DatasourceArtifactBackend",
|
||||||
@@ -725,9 +780,14 @@ __all__ = [
|
|||||||
"DatasourceStageInput",
|
"DatasourceStageInput",
|
||||||
"DatasourceUnavailableError",
|
"DatasourceUnavailableError",
|
||||||
"DatasourceValidationError",
|
"DatasourceValidationError",
|
||||||
|
"DatasourceVisibilityAction",
|
||||||
|
"DatasourceVisibilityPolicyDecision",
|
||||||
|
"DatasourceVisibilityPolicyProvider",
|
||||||
|
"DatasourceVisibilityPolicyRequest",
|
||||||
"datasource_catalogue",
|
"datasource_catalogue",
|
||||||
"datasource_artifact_backend_provider",
|
"datasource_artifact_backend_provider",
|
||||||
"datasource_lifecycle",
|
"datasource_lifecycle",
|
||||||
"datasource_origins",
|
"datasource_origins",
|
||||||
"datasource_publication",
|
"datasource_publication",
|
||||||
|
"datasource_visibility_policy_provider",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
from typing import Protocol, runtime_checkable
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
from govoplan_core.core.access import ResourceAccessExplanationProvider
|
from govoplan_core.core.access import ResourceAccessExplanationProvider
|
||||||
@@ -10,6 +11,48 @@ from govoplan_core.core.access import ResourceAccessExplanationProvider
|
|||||||
CAPABILITY_FILES_ACCESS = "files.access"
|
CAPABILITY_FILES_ACCESS = "files.access"
|
||||||
CAPABILITY_FILES_ARTIFACT_STORE = "files.artifact_store"
|
CAPABILITY_FILES_ARTIFACT_STORE = "files.artifact_store"
|
||||||
CAPABILITY_FILES_POSTBOX_REFERENCES = "files.postbox_references"
|
CAPABILITY_FILES_POSTBOX_REFERENCES = "files.postbox_references"
|
||||||
|
CAPABILITY_FILES_TABULAR_CONTENT = "files.tabular_content"
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileError(ValueError):
|
||||||
|
"""Stable base error for exact-version managed tabular file access."""
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileNotFoundError(ManagedTabularFileError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileAccessError(ManagedTabularFileError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileUnavailableError(ManagedTabularFileError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileValidationError(ManagedTabularFileError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class ManagedTabularFile:
|
||||||
|
"""Authorized metadata for one immutable managed file version."""
|
||||||
|
|
||||||
|
file_asset_id: str
|
||||||
|
file_version_id: str
|
||||||
|
filename: str
|
||||||
|
display_path: str
|
||||||
|
content_type: str | None
|
||||||
|
size_bytes: int
|
||||||
|
sha256: str
|
||||||
|
updated_at: datetime | None = None
|
||||||
|
current_version: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class ManagedTabularFileContent:
|
||||||
|
file: ManagedTabularFile
|
||||||
|
payload: bytes
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -91,6 +134,39 @@ class PostboxFileReferenceProvider(Protocol):
|
|||||||
) -> tuple[PostboxFileReferenceRef, ...]: ...
|
) -> tuple[PostboxFileReferenceRef, ...]: ...
|
||||||
|
|
||||||
|
|
||||||
|
@runtime_checkable
|
||||||
|
class ManagedTabularFileProvider(Protocol):
|
||||||
|
"""List and open authorized CSV/XLSX content without exposing Files internals."""
|
||||||
|
|
||||||
|
def list_tabular_files(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
query: str = "",
|
||||||
|
limit: int = 100,
|
||||||
|
) -> tuple[ManagedTabularFile, ...]: ...
|
||||||
|
|
||||||
|
def get_tabular_file(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
file_asset_id: str,
|
||||||
|
file_version_id: str | None = None,
|
||||||
|
) -> ManagedTabularFile | None: ...
|
||||||
|
|
||||||
|
def read_tabular_file(
|
||||||
|
self,
|
||||||
|
session: object,
|
||||||
|
principal: object,
|
||||||
|
*,
|
||||||
|
file_asset_id: str,
|
||||||
|
file_version_id: str,
|
||||||
|
max_bytes: int,
|
||||||
|
) -> ManagedTabularFileContent: ...
|
||||||
|
|
||||||
|
|
||||||
def postbox_file_reference_provider(
|
def postbox_file_reference_provider(
|
||||||
registry: object | None,
|
registry: object | None,
|
||||||
) -> PostboxFileReferenceProvider | None:
|
) -> PostboxFileReferenceProvider | None:
|
||||||
@@ -107,3 +183,46 @@ def postbox_file_reference_provider(
|
|||||||
"PostboxFileReferenceProvider"
|
"PostboxFileReferenceProvider"
|
||||||
)
|
)
|
||||||
return provider
|
return provider
|
||||||
|
|
||||||
|
|
||||||
|
def managed_tabular_file_provider(
|
||||||
|
registry: object | None,
|
||||||
|
) -> ManagedTabularFileProvider | None:
|
||||||
|
if (
|
||||||
|
registry is None
|
||||||
|
or not hasattr(registry, "has_capability")
|
||||||
|
or not registry.has_capability(CAPABILITY_FILES_TABULAR_CONTENT)
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
provider = registry.require_capability(CAPABILITY_FILES_TABULAR_CONTENT)
|
||||||
|
if not isinstance(provider, ManagedTabularFileProvider):
|
||||||
|
raise TypeError(
|
||||||
|
"files.tabular_content provider does not implement "
|
||||||
|
"ManagedTabularFileProvider"
|
||||||
|
)
|
||||||
|
return provider
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"CAPABILITY_FILES_ACCESS",
|
||||||
|
"CAPABILITY_FILES_ARTIFACT_STORE",
|
||||||
|
"CAPABILITY_FILES_POSTBOX_REFERENCES",
|
||||||
|
"CAPABILITY_FILES_TABULAR_CONTENT",
|
||||||
|
"FileAccessProvider",
|
||||||
|
"ManagedArtifactRef",
|
||||||
|
"ManagedArtifactStore",
|
||||||
|
"ManagedArtifactWriteRequest",
|
||||||
|
"ManagedTabularFile",
|
||||||
|
"ManagedTabularFileAccessError",
|
||||||
|
"ManagedTabularFileContent",
|
||||||
|
"ManagedTabularFileError",
|
||||||
|
"ManagedTabularFileNotFoundError",
|
||||||
|
"ManagedTabularFileProvider",
|
||||||
|
"ManagedTabularFileUnavailableError",
|
||||||
|
"ManagedTabularFileValidationError",
|
||||||
|
"PostboxFileReferenceProvider",
|
||||||
|
"PostboxFileReferenceRef",
|
||||||
|
"PostboxFileReferenceRequest",
|
||||||
|
"managed_tabular_file_provider",
|
||||||
|
"postbox_file_reference_provider",
|
||||||
|
]
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from govoplan_core.core.datasources import (
|
|||||||
CAPABILITY_DATASOURCE_LIFECYCLE,
|
CAPABILITY_DATASOURCE_LIFECYCLE,
|
||||||
CAPABILITY_DATASOURCE_ORIGINS,
|
CAPABILITY_DATASOURCE_ORIGINS,
|
||||||
CAPABILITY_DATASOURCE_PUBLICATION,
|
CAPABILITY_DATASOURCE_PUBLICATION,
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY,
|
||||||
DatasourceCatalogueProvider,
|
DatasourceCatalogueProvider,
|
||||||
DatasourceArtifactBackendProvider,
|
DatasourceArtifactBackendProvider,
|
||||||
DatasourceDescriptor,
|
DatasourceDescriptor,
|
||||||
@@ -21,11 +22,14 @@ from govoplan_core.core.datasources import (
|
|||||||
DatasourcePublicationResult,
|
DatasourcePublicationResult,
|
||||||
DatasourceReadResult,
|
DatasourceReadResult,
|
||||||
DatasourceStage,
|
DatasourceStage,
|
||||||
|
DatasourceVisibilityPolicyDecision,
|
||||||
|
DatasourceVisibilityPolicyProvider,
|
||||||
datasource_catalogue,
|
datasource_catalogue,
|
||||||
datasource_artifact_backend_provider,
|
datasource_artifact_backend_provider,
|
||||||
datasource_lifecycle,
|
datasource_lifecycle,
|
||||||
datasource_origins,
|
datasource_origins,
|
||||||
datasource_publication,
|
datasource_publication,
|
||||||
|
datasource_visibility_policy_provider,
|
||||||
)
|
)
|
||||||
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
||||||
from govoplan_core.core.registry import PlatformRegistry
|
from govoplan_core.core.registry import PlatformRegistry
|
||||||
@@ -161,6 +165,10 @@ class _Provider:
|
|||||||
def artifact_backends(self):
|
def artifact_backends(self):
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
|
def decide_datasource_visibility(self, session, *, request):
|
||||||
|
del session, request
|
||||||
|
return DatasourceVisibilityPolicyDecision(allowed=True)
|
||||||
|
|
||||||
|
|
||||||
class DatasourceContractTests(unittest.TestCase):
|
class DatasourceContractTests(unittest.TestCase):
|
||||||
def test_capabilities_are_runtime_checkable_and_resolved_without_modules(self) -> None:
|
def test_capabilities_are_runtime_checkable_and_resolved_without_modules(self) -> None:
|
||||||
@@ -170,6 +178,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
self.assertIsInstance(provider, DatasourcePublicationProvider)
|
self.assertIsInstance(provider, DatasourcePublicationProvider)
|
||||||
self.assertIsInstance(provider, DatasourceOriginProvider)
|
self.assertIsInstance(provider, DatasourceOriginProvider)
|
||||||
self.assertIsInstance(provider, DatasourceArtifactBackendProvider)
|
self.assertIsInstance(provider, DatasourceArtifactBackendProvider)
|
||||||
|
self.assertIsInstance(provider, DatasourceVisibilityPolicyProvider)
|
||||||
registry = PlatformRegistry()
|
registry = PlatformRegistry()
|
||||||
registry.register(
|
registry.register(
|
||||||
ModuleManifest(
|
ModuleManifest(
|
||||||
@@ -182,6 +191,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
CAPABILITY_DATASOURCE_PUBLICATION: lambda context: provider,
|
CAPABILITY_DATASOURCE_PUBLICATION: lambda context: provider,
|
||||||
CAPABILITY_DATASOURCE_ORIGINS: lambda context: provider,
|
CAPABILITY_DATASOURCE_ORIGINS: lambda context: provider,
|
||||||
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS: lambda context: provider,
|
CAPABILITY_DATASOURCE_ARTIFACT_BACKENDS: lambda context: provider,
|
||||||
|
CAPABILITY_POLICY_DATASOURCE_VISIBILITY: lambda context: provider,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -192,6 +202,7 @@ class DatasourceContractTests(unittest.TestCase):
|
|||||||
self.assertIs(provider, datasource_publication(registry))
|
self.assertIs(provider, datasource_publication(registry))
|
||||||
self.assertIs(provider, datasource_origins(registry))
|
self.assertIs(provider, datasource_origins(registry))
|
||||||
self.assertIs(provider, datasource_artifact_backend_provider(registry))
|
self.assertIs(provider, datasource_artifact_backend_provider(registry))
|
||||||
|
self.assertIs(provider, datasource_visibility_policy_provider(registry))
|
||||||
self.assertIsNone(datasource_catalogue(PlatformRegistry()))
|
self.assertIsNone(datasource_catalogue(PlatformRegistry()))
|
||||||
|
|
||||||
def test_descriptor_distinguishes_mode_kind_shape_and_materialization(self) -> None:
|
def test_descriptor_distinguishes_mode_kind_shape_and_materialization(self) -> None:
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
|
||||||
|
from govoplan_core.core.files import (
|
||||||
|
CAPABILITY_FILES_TABULAR_CONTENT,
|
||||||
|
ManagedTabularFile,
|
||||||
|
ManagedTabularFileContent,
|
||||||
|
ManagedTabularFileProvider,
|
||||||
|
managed_tabular_file_provider,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _Provider:
|
||||||
|
def list_tabular_files(self, session, principal, *, query="", limit=100):
|
||||||
|
del session, principal, query, limit
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def get_tabular_file(
|
||||||
|
self,
|
||||||
|
session,
|
||||||
|
principal,
|
||||||
|
*,
|
||||||
|
file_asset_id,
|
||||||
|
file_version_id=None,
|
||||||
|
):
|
||||||
|
del session, principal, file_asset_id, file_version_id
|
||||||
|
return None
|
||||||
|
|
||||||
|
def read_tabular_file(
|
||||||
|
self,
|
||||||
|
session,
|
||||||
|
principal,
|
||||||
|
*,
|
||||||
|
file_asset_id,
|
||||||
|
file_version_id,
|
||||||
|
max_bytes,
|
||||||
|
):
|
||||||
|
del session, principal, file_asset_id, file_version_id, max_bytes
|
||||||
|
file = ManagedTabularFile(
|
||||||
|
file_asset_id="asset-1",
|
||||||
|
file_version_id="version-1",
|
||||||
|
filename="source.csv",
|
||||||
|
display_path="Imports/source.csv",
|
||||||
|
content_type="text/csv",
|
||||||
|
size_bytes=8,
|
||||||
|
sha256="a" * 64,
|
||||||
|
updated_at=datetime(2026, 8, 21, tzinfo=UTC),
|
||||||
|
)
|
||||||
|
return ManagedTabularFileContent(file=file, payload=b"id\n1\n")
|
||||||
|
|
||||||
|
|
||||||
|
class _Registry:
|
||||||
|
def __init__(self, provider):
|
||||||
|
self.provider = provider
|
||||||
|
|
||||||
|
def has_capability(self, name):
|
||||||
|
return name == CAPABILITY_FILES_TABULAR_CONTENT
|
||||||
|
|
||||||
|
def require_capability(self, name):
|
||||||
|
if not self.has_capability(name):
|
||||||
|
raise KeyError(name)
|
||||||
|
return self.provider
|
||||||
|
|
||||||
|
|
||||||
|
class ManagedTabularFileContractTests(unittest.TestCase):
|
||||||
|
def test_runtime_protocol_and_registry_resolution(self) -> None:
|
||||||
|
provider = _Provider()
|
||||||
|
|
||||||
|
self.assertIsInstance(provider, ManagedTabularFileProvider)
|
||||||
|
self.assertIs(provider, managed_tabular_file_provider(_Registry(provider)))
|
||||||
|
self.assertIsNone(managed_tabular_file_provider(None))
|
||||||
|
|
||||||
|
def test_exact_version_content_retains_safe_metadata(self) -> None:
|
||||||
|
result = _Provider().read_tabular_file(
|
||||||
|
object(),
|
||||||
|
object(),
|
||||||
|
file_asset_id="asset-1",
|
||||||
|
file_version_id="version-1",
|
||||||
|
max_bytes=100,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual("version-1", result.file.file_version_id)
|
||||||
|
self.assertEqual("a" * 64, result.file.sha256)
|
||||||
|
self.assertEqual(b"id\n1\n", result.payload)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -10,6 +10,7 @@ from fastapi import APIRouter, Depends, FastAPI
|
|||||||
from fastapi.testclient import TestClient
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
from govoplan_core.auth import ApiPrincipal, get_api_principal
|
from govoplan_core.auth import ApiPrincipal, get_api_principal
|
||||||
|
from govoplan_core.admin.models import SystemSettings
|
||||||
from govoplan_core.celery_app import _run_tenant_worker_batches
|
from govoplan_core.celery_app import _run_tenant_worker_batches
|
||||||
from govoplan_core.core.access import PrincipalRef
|
from govoplan_core.core.access import PrincipalRef
|
||||||
from govoplan_core.core.lifecycle import require_module_active
|
from govoplan_core.core.lifecycle import require_module_active
|
||||||
@@ -26,6 +27,7 @@ from govoplan_core.core.module_entitlements import (
|
|||||||
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
from govoplan_core.core.modules import ModuleContext, ModuleManifest
|
||||||
from govoplan_core.core.registry import PlatformRegistry
|
from govoplan_core.core.registry import PlatformRegistry
|
||||||
from govoplan_core.db.session import configure_database, get_database
|
from govoplan_core.db.session import configure_database, get_database
|
||||||
|
from govoplan_core.db.base import Base
|
||||||
from govoplan_core.server.platform import create_platform_router
|
from govoplan_core.server.platform import create_platform_router
|
||||||
from govoplan_core.tenancy.scope import Tenant, create_scope_tables
|
from govoplan_core.tenancy.scope import Tenant, create_scope_tables
|
||||||
|
|
||||||
@@ -257,6 +259,10 @@ class TenantModuleEntitlementRouteTests(unittest.TestCase):
|
|||||||
root = Path(tempfile.mkdtemp(prefix="govoplan-entitlement-test-"))
|
root = Path(tempfile.mkdtemp(prefix="govoplan-entitlement-test-"))
|
||||||
configure_database(f"sqlite:///{root / 'test.db'}")
|
configure_database(f"sqlite:///{root / 'test.db'}")
|
||||||
create_scope_tables(get_database().engine)
|
create_scope_tables(get_database().engine)
|
||||||
|
Base.metadata.create_all(
|
||||||
|
bind=get_database().engine,
|
||||||
|
tables=[SystemSettings.__table__],
|
||||||
|
)
|
||||||
self.manifests = (
|
self.manifests = (
|
||||||
ModuleManifest(id="access", name="Access", version="test"),
|
ModuleManifest(id="access", name="Access", version="test"),
|
||||||
ModuleManifest(
|
ModuleManifest(
|
||||||
|
|||||||
Generated
+5
-5
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "file:../../govoplan-access/webui",
|
"@govoplan/access-webui": "file:../../govoplan-access/webui",
|
||||||
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
|
"@govoplan/addresses-webui": "file:../../govoplan-addresses/webui",
|
||||||
@@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-access/webui": {
|
"../../govoplan-access/webui": {
|
||||||
"name": "@govoplan/access-webui",
|
"name": "@govoplan/access-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2"
|
"typescript": "^5.7.2"
|
||||||
},
|
},
|
||||||
@@ -288,7 +288,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-datasources/webui": {
|
"../../govoplan-datasources/webui": {
|
||||||
"name": "@govoplan/datasources-webui",
|
"name": "@govoplan/datasources-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.20",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
@@ -547,7 +547,7 @@
|
|||||||
},
|
},
|
||||||
"../../govoplan-policy/webui": {
|
"../../govoplan-policy/webui": {
|
||||||
"name": "@govoplan/policy-webui",
|
"name": "@govoplan/policy-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.18",
|
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.19",
|
||||||
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
||||||
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.18",
|
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.18",
|
||||||
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.18",
|
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.18",
|
||||||
@@ -753,8 +753,8 @@
|
|||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/@govoplan/access-webui": {
|
"node_modules/@govoplan/access-webui": {
|
||||||
"version": "0.1.18",
|
"version": "0.1.19",
|
||||||
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#44799b15e56cb1ace679689783be51aac7bee4c3",
|
"resolved": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#2d1b1e356ecb8726219d4a502db78e61f435a88f",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.18",
|
"@govoplan/core-webui": "^0.1.18",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/core-webui",
|
"name": "@govoplan/core-webui",
|
||||||
"version": "0.1.18",
|
"version": "0.1.21",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
"preview": "vite preview --host 127.0.0.1 --port 4173"
|
"preview": "vite preview --host 127.0.0.1 --port 4173"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.18",
|
"@govoplan/access-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git#v0.1.19",
|
||||||
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
"@govoplan/admin-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git#v0.1.18",
|
||||||
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.18",
|
"@govoplan/audit-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git#v0.1.18",
|
||||||
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.18",
|
"@govoplan/calendar-webui": "git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git#v0.1.18",
|
||||||
|
|||||||
Reference in New Issue
Block a user