diff --git a/interfaces/fiveg_core_gnb/interface/v0/schema.py b/interfaces/fiveg_core_gnb/interface/v0/schema.py index 52f838823..22430bf16 100644 --- a/interfaces/fiveg_core_gnb/interface/v0/schema.py +++ b/interfaces/fiveg_core_gnb/interface/v0/schema.py @@ -1,11 +1,7 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_core_gnb` relation interface. -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema - Examples: - ProviderSchema: + Provider: unit: app: { "tac": 1, @@ -18,7 +14,7 @@ } ], } - RequirerSchema: + Requirer: unit: app: { "gnb-name": "gnb001", @@ -27,7 +23,6 @@ from dataclasses import dataclass -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field @@ -60,7 +55,7 @@ class PLMNConfig: ) -class FivegCoreGnbProviderAppData(BaseModel): +class ProviderAppData(BaseModel): tac: int = Field( description="Tracking Area Code", examples=[1], @@ -70,19 +65,11 @@ class FivegCoreGnbProviderAppData(BaseModel): plmns: list[PLMNConfig] -class FivegCoreGnbRequirerAppData(BaseModel): +class RequirerAppData(BaseModel): gnb_name: str = Field( alias="gnb-name", description="Unique identifier of the CU/gnB.", examples=["gnb001"] ) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of the fiveg_core_gnb interface.""" - - app: FivegCoreGnbProviderAppData - - -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of the fiveg_core_gnb interface.""" - - app: FivegCoreGnbRequirerAppData +ProviderUnitData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_f1/interface/v0/schema.py b/interfaces/fiveg_f1/interface/v0/schema.py index 8ced40526..d58a22017 100644 --- a/interfaces/fiveg_f1/interface/v0/schema.py +++ b/interfaces/fiveg_f1/interface/v0/schema.py @@ -1,9 +1,7 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_f1` interface. -It exposes two interface_tester.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: { "f1_ip_address": "192.168.70.132", @@ -18,7 +16,7 @@ } ], } - RequirerSchema: + Requirer: unit: app: { "f1_port": 2153 @@ -27,7 +25,6 @@ from dataclasses import dataclass -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field, IPvAnyAddress, conlist @@ -60,7 +57,7 @@ class PLMNConfig: ) -class FivegF1ProviderAppData(BaseModel): +class ProviderAppData(BaseModel): f1_ip_address: IPvAnyAddress = Field( description="IPv4 address of the network interface used for F1 traffic", examples=["192.168.70.132"], @@ -75,17 +72,9 @@ class FivegF1ProviderAppData(BaseModel): plmns: conlist(PLMNConfig, min_length=1) -class FivegF1RequirerAppData(BaseModel): +class RequirerAppData(BaseModel): f1_port: int = Field(description="Number of the port used for F1 traffic", examples=[2153]) -class ProviderSchema(DataBagSchema): - """Provider schema for fiveg_f1.""" - - app: FivegF1ProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for fiveg_f1.""" - - app: FivegF1RequirerAppData +ProviderUnitData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_gnb_identity/interface/v0/schema.py b/interfaces/fiveg_gnb_identity/interface/v0/schema.py index e1c280a4c..785480a46 100644 --- a/interfaces/fiveg_gnb_identity/interface/v0/schema.py +++ b/interfaces/fiveg_gnb_identity/interface/v0/schema.py @@ -1,35 +1,25 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_gnb_identity` relation interface. -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema - Examples: - ProviderSchema: + Provider: unit: app: { "gnb_name": "gnb001", "tac": 1 } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field -class FivegGnbIdentityProviderAppData(BaseModel): +class ProviderAppData(BaseModel): gnb_name: str = Field(description="Name of the gnB.", examples=["gnb001"]) tac: int = Field(description="Tracking Area Code", examples=[1]) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of the fiveg_gnb_identity interface.""" - - app: FivegGnbIdentityProviderAppData - - -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of the fiveg_gnb_identity interface.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_n2/interface/v0/schema.py b/interfaces/fiveg_n2/interface/v0/schema.py index 4558c5c68..6c2b10f5c 100644 --- a/interfaces/fiveg_n2/interface/v0/schema.py +++ b/interfaces/fiveg_n2/interface/v0/schema.py @@ -1,25 +1,22 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_n2` interface. -It exposes two interface_tester.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: { "amf_ip_address": "192.168.70.132", "amf_hostname": "amf", "amf_port": 38412 } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field, IPvAnyAddress -class FivegN2ProviderAppData(BaseModel): +class ProviderAppData(BaseModel): amf_ip_address: IPvAnyAddress = Field( description="IP Address to reach the AMF's N2 interface.", examples=["192.168.70.132"] ) @@ -29,11 +26,6 @@ class FivegN2ProviderAppData(BaseModel): amf_port: int = Field(description="Port to reach the AMF's N2 interface.", examples=[38412]) -class ProviderSchema(DataBagSchema): - """Provider schema for fiveg_n2.""" - - app: FivegN2ProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for fiveg_n2.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_n3/interface/v0/schema.py b/interfaces/fiveg_n3/interface/v0/schema.py index ea5a279b8..c70eb5632 100644 --- a/interfaces/fiveg_n3/interface/v0/schema.py +++ b/interfaces/fiveg_n3/interface/v0/schema.py @@ -1,31 +1,23 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_n3` interface. -It exposes two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: { "upf_ip_address": "1.2.3.4" } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel -class FivegN3ProviderAppData(BaseModel): +class ProviderAppData(BaseModel): upf_ip_address: str -class ProviderSchema(DataBagSchema): - """Provider schema for fiveg_n3.""" - - app: FivegN3ProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for fiveg_n3.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_n4/interface/v0/schema.py b/interfaces/fiveg_n4/interface/v0/schema.py index 1667f26ec..2e30a8edb 100644 --- a/interfaces/fiveg_n4/interface/v0/schema.py +++ b/interfaces/fiveg_n4/interface/v0/schema.py @@ -1,28 +1,23 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_n4` interface. -It exposes two `interfaces.schema_base.DataBagSchema` subclasses called: -- `ProviderSchema` -- `RequirerSchema` - Examples: - ProviderSchema: + Provider: unit: app: { "upf_hostname": "upf.uplane-cloud.canonical.com", "upf_port": 8805 } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field -class FivegN4ProviderAppData(BaseModel): +class ProviderAppData(BaseModel): upf_hostname: str = Field( description="Name of the host exposing the UPF's N4 interface.", examples=["upf.uplane-cloud.canonical.com"], @@ -32,11 +27,6 @@ class FivegN4ProviderAppData(BaseModel): ) -class ProviderSchema(DataBagSchema): - """Provider schema for fiveg_n4.""" - - app: FivegN4ProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for fiveg_n4.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_nrf/interface/v0/schema.py b/interfaces/fiveg_nrf/interface/v0/schema.py index 9975f57c0..5782f2ee8 100644 --- a/interfaces/fiveg_nrf/interface/v0/schema.py +++ b/interfaces/fiveg_nrf/interface/v0/schema.py @@ -1,31 +1,23 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_nrf` interface. -It exposes two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: {"url": "https://nrf-example.com:1234"} - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import AnyHttpUrl, BaseModel, Field -class MyProviderAppData(BaseModel): +class ProviderAppData(BaseModel): url: AnyHttpUrl = Field( description="url to reach the NRF.", examples=["https://nrf-example.com:1234"] ) -class ProviderSchema(DataBagSchema): - """Provider schema for fiveg_nrf.""" - - app: MyProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for fiveg_nrf.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/fiveg_rfsim/interface/v0/schema.py b/interfaces/fiveg_rfsim/interface/v0/schema.py index 4bb36c39f..060b70867 100644 --- a/interfaces/fiveg_rfsim/interface/v0/schema.py +++ b/interfaces/fiveg_rfsim/interface/v0/schema.py @@ -1,25 +1,22 @@ """This file defines the schemas for the provider and requirer sides of the `fiveg_rfsim` interface. -It exposes two interface_tester.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: { "rfsim_address": "192.168.70.130", "sst": 1, "sd": 1, } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field -class FivegRFSIMProviderAppData(BaseModel): +class ProviderAppData(BaseModel): rfsim_address: str = Field(description="RF simulator service ip", examples=["192.168.70.130"]) sst: int = Field( description="Slice/Service Type", @@ -36,11 +33,6 @@ class FivegRFSIMProviderAppData(BaseModel): ) -class ProviderSchema(DataBagSchema): - """Provider schema for the fiveg_rfsim interface.""" - - app: FivegRFSIMProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for the fiveg_rfsim interface.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/ingress/interface/v1/tests/.disable b/interfaces/ingress/interface/v1/tests/.disable deleted file mode 100644 index e69de29bb..000000000 diff --git a/interfaces/ingress/interface/v1/tests/test_provider.py b/interfaces/ingress/interface/v1/tests/test_provider.py deleted file mode 100644 index 20fd6d1fc..000000000 --- a/interfaces/ingress/interface/v1/tests/test_provider.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2023 Canonical -# See LICENSE file for licensing details. - -from interface_tester import Tester -from scenario import Relation, State -from scenario.context import CharmEvents - - -def test_no_data_on_created(): - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="ingress", - interface="ingress", - ) - ], - ) - ) - state_out = t.run("ingress-relation-created") - t.assert_relation_data_empty() - - -def test_no_data_on_joined(): - # nothing happens on joined: databags are empty - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="ingress", - interface="ingress", - ) - ], - ) - ) - state_out = t.run("ingress-relation-joined") - t.assert_relation_data_empty() - - -def test_data_published_on_changed_remote_valid(): - ingress = Relation( - endpoint='ingress', - interface='ingress', - remote_app_data={ - 'host': '"0.0.0.42"', - 'model': '"bar"', - 'name': '"remote/0"', - 'port': '42', - }, - ) - t = Tester(State(leader=True, relations=[ingress])) - state_out = t.run(CharmEvents.relation_changed(ingress)) - t.assert_schema_valid() - - -def test_no_data_published_on_changed_remote_invalid(): - # on changed, if the remote side has sent INvalid data: local side didn't publish anything either. - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint='ingress', - interface='ingress', - remote_app_data={'host': '0.0.0.42', 'bubble': "10", 'rubble': "foo"}, - ) - ], - ) - ) - state_out = t.run("ingress-relation-changed") - t.assert_relation_data_empty() diff --git a/interfaces/ingress/interface/v2/schema.py b/interfaces/ingress/interface/v2/schema.py index 2848e8f48..362042b47 100644 --- a/interfaces/ingress/interface/v2/schema.py +++ b/interfaces/ingress/interface/v2/schema.py @@ -2,18 +2,14 @@ # See LICENSE file for licensing details. """This file defines the schemas for the provider and requirer sides of the ingress interface. -It exposes two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema - Examples: - ProviderSchema: + Provider: unit: app: {"ingress": {"url": "http://foo.bar:80/model_name-app_name"} } - RequirerSchema: + Requirer: unit: { "name": "app-name", "host": "hostname" @@ -24,36 +20,25 @@ } """ -from interface_tester.schema_base import DataBagSchema -from pydantic import AnyHttpUrl, BaseModel, Field, Json +from pydantic import AnyHttpUrl, BaseModel, Field class Url(BaseModel): url: AnyHttpUrl -class MyProviderData(BaseModel): - ingress: Json[Url] - - -class ProviderSchema(DataBagSchema): - """Provider schema for Ingress.""" - - app: MyProviderData - +class ProviderAppData(BaseModel): + ingress: Url -class IngressRequirerAppData(BaseModel): - model: Json[str] = Field(description="The model the application is in.") - port: Json[int] = Field(description="The port the unit wishes to be exposed. Stringified int.") - name: Json[str] = Field(description="The name of the application requesting ingress.") +class RequirerAppData(BaseModel): + model: str = Field(description="The model the application is in.") + port: int = Field(description="The port the unit wishes to be exposed. Stringified int.") + name: str = Field(description="The name of the application requesting ingress.") -class IngressRequirerUnitData(BaseModel): - host: Json[str] = Field(description="Unit hostname to be exposed.") +class RequirerUnitData(BaseModel): + host: str = Field(description="Unit hostname to be exposed.") -class RequirerSchema(DataBagSchema): - """Requirer schema for Ingress.""" - app: IngressRequirerAppData - unit: IngressRequirerUnitData +ProviderUnitData = None diff --git a/interfaces/ingress/interface/v2/tests/.disable b/interfaces/ingress/interface/v2/tests/.disable deleted file mode 100644 index e69de29bb..000000000 diff --git a/interfaces/ingress/interface/v2/tests/test_provider.py b/interfaces/ingress/interface/v2/tests/test_provider.py deleted file mode 100644 index 23ea7cbcd..000000000 --- a/interfaces/ingress/interface/v2/tests/test_provider.py +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2023 Canonical -# See LICENSE file for licensing details. - -from interface_tester import Tester -from scenario import Relation, State -from scenario.context import CharmEvents - - -def test_no_data_on_created(): - # nothing happens on created: databags are empty - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="ingress", - interface="ingress", - ) - ], - ) - ) - state_out = t.run("ingress-relation-created") - t.assert_relation_data_empty() - - -def test_no_data_on_joined(): - # nothing happens on joined: databags are empty - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="ingress", - interface="ingress", - ) - ], - ) - ) - state_out = t.run("ingress-relation-joined") - t.assert_relation_data_empty() - - -def test_data_published_on_changed_remote_valid(): - relation = Relation( - endpoint='ingress', - interface='ingress', - remote_app_name='remote', - remote_app_data={ - 'model': '"bar"', - 'port': '42', - 'name': '"remote"', - }, - remote_units_data={ - 0: { - 'host': '"0.0.0.42"', - } - }, - ) - t = Tester(State(relations=[relation])) - state_out = t.run(CharmEvents.relation_changed(relation)) - t.assert_schema_valid() - - -def test_data_published_on_changed_remote_invalid_json(): - # on changed, if the remote side has sent invalid json: local side didn't publish anything either. - ingress = Relation( - endpoint='ingress', - interface='ingress', - remote_app_data={ - 'model': 'bar', - 'port': '42', - 'name': 'true', - }, - remote_units_data={ - 0: { - 'host': '0.0.0.42', - } - }, - ) - t = Tester(State(leader=True, relations=[ingress])) - state_out = t.run(CharmEvents.relation_changed(ingress)) - t.assert_relation_data_empty() - - -def test_data_published_on_changed_remote_invalid(): - # on changed, if the remote side has sent invalid data: local side didn't publish anything either. - ingress = Relation( - endpoint='ingress', - interface='ingress', - remote_app_data={'model': '"bar"', 'port': '42', 'name': '"true"'}, - remote_units_data={ - 0: { - 'bubble': 'blabla', - } - }, - ) - t = Tester(State(leader=True, relations=[ingress])) - state_out = t.run(CharmEvents.relation_changed(ingress)) - t.assert_relation_data_empty() diff --git a/interfaces/ingress/ruff.toml b/interfaces/ingress/ruff.toml index 5c03a4a4a..88c6ebb59 100644 --- a/interfaces/ingress/ruff.toml +++ b/interfaces/ingress/ruff.toml @@ -10,10 +10,3 @@ quote-style = "preserve" "E501", # line too long "N805", # first argument of a method should be named self ] -"./interface/v*/tests/*.py" = [ - "CPY", # copyright - "D", # docs - "S", # security - "E501", # line too long - "F841", # assignment to unused variable -] diff --git a/interfaces/ip_router/interface/v0/schema.py b/interfaces/ip_router/interface/v0/schema.py index 3539033d4..f70807add 100644 --- a/interfaces/ip_router/interface/v0/schema.py +++ b/interfaces/ip_router/interface/v0/schema.py @@ -1,9 +1,7 @@ """This file defines the schemas for the provider and requirer sides of the `ip_router` interface. -It exposes two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema + Examples: - ProviderSchema: + Provider: unit: app: { "networks": [ @@ -28,7 +26,7 @@ } ] } - RequirerSchema: + Requirer: unit: app: { "networks": [ @@ -46,7 +44,6 @@ } """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, IPvAnyAddress, IPvAnyNetwork @@ -61,21 +58,13 @@ class IPNetwork(BaseModel): routes: list[Route] | None -class IPRouterProviderAppData(BaseModel): +class ProviderAppData(BaseModel): networks: list[IPNetwork] -class IPRouterRequirerAppData(BaseModel): +class RequirerAppData(BaseModel): networks: list[IPNetwork] -class ProviderSchema(DataBagSchema): - """Provider schema for ip_router.""" - - app: IPRouterProviderAppData - - -class RequirerSchema(DataBagSchema): - """Requirer schema for ip_router.""" - - app: IPRouterRequirerAppData +ProviderUnitData = None +RequirerUnitData = None diff --git a/interfaces/sdcore_config/interface/v0/schema.py b/interfaces/sdcore_config/interface/v0/schema.py index ab9a83ab3..68afc47b9 100644 --- a/interfaces/sdcore_config/interface/v0/schema.py +++ b/interfaces/sdcore_config/interface/v0/schema.py @@ -1,36 +1,26 @@ """This file defines the schemas for the provider and requirer sides of the `sdcore_config` relation interface. -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema - Examples: - ProviderSchema: + Provider: unit: app: { "webui_url": "sdcore-webui-k8s:9876", } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field -class SdcoreConfigProviderAppData(BaseModel): +class ProviderAppData(BaseModel): webui_url: str = Field( description="GRPC address of the Webui including Webui hostname and a fixed GRPC port.", examples=["sdcore-webui-k8s:9876"], ) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of the sdcore_config interface.""" - - app: SdcoreConfigProviderAppData - - -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of the sdcore_config interface.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/sdcore_management/interface/v0/schema.py b/interfaces/sdcore_management/interface/v0/schema.py index 0650d6f43..2c74a3c39 100644 --- a/interfaces/sdcore_management/interface/v0/schema.py +++ b/interfaces/sdcore_management/interface/v0/schema.py @@ -1,36 +1,26 @@ """This file defines the schemas for the provider and requirer sides of the `sdcore_management` relation interface. -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema - Examples: - ProviderSchema: + Provider: unit: app: { "management_endpoint": "http://1.2.3.4:1234", } - RequirerSchema: + Requirer: unit: app: """ -from interface_tester.schema_base import DataBagSchema from pydantic import BaseModel, Field, HttpUrl -class SdcoreManagementProviderAppData(BaseModel): +class ProviderAppData(BaseModel): management_url: HttpUrl = Field( description="The endpoint to use to manage SD-Core network.", examples=["http://1.2.3.4:1234"], ) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of the sdcore_management interface.""" - - app: SdcoreManagementProviderAppData - - -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of the sdcore_management interface.""" +ProviderUnitData = None +RequirerAppData = None +RequirerUnitData = None diff --git a/interfaces/spark_service_account/interface/v0/schema.py b/interfaces/spark_service_account/interface/v0/schema.py index f1938a420..3fd3d045b 100644 --- a/interfaces/spark_service_account/interface/v0/schema.py +++ b/interfaces/spark_service_account/interface/v0/schema.py @@ -1,15 +1,30 @@ -"""This file defines the schemas for the provider and requirer sides of this relation interface. +"""This file defines the schemas for the provider and requirer sides of this relation interface.""" -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema -""" +import json +from typing import Any -from interface_tester.schema_base import DataBagSchema -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator -class SparkServiceAccountProviderAppData(BaseModel): +class _BareStringDatabag(BaseModel): + """Base class for databag models that don't strictly JSON encode all entries.""" + + @staticmethod + def __juju_decoder__(value: str) -> str: + """Pass Juju's string through unmodified to be decoded by individual field validators.""" + return value + + @staticmethod + def __juju_encoder__(value: str | None) -> str: + """Convert `None` to "", erasing the value; Ops will error on a non-string.""" + return "" if value is None else value + + +class ProviderAppData(_BareStringDatabag): + """The provider's application databag.""" + + model_config = ConfigDict(strict=True, populate_by_name=True) + service_account: str = Field( alias="service-account", description="The name of the service account to be created and the namespace in" @@ -29,7 +44,11 @@ class SparkServiceAccountProviderAppData(BaseModel): ) -class SparkServiceAccountRequirerAppData(BaseModel): +class RequirerAppData(_BareStringDatabag): + """The requirer's application databag.""" + + model_config = ConfigDict(strict=True, populate_by_name=True) + service_account: str = Field( alias="service-account", description="The name of the service account to be created and the namespace in" @@ -46,21 +65,27 @@ class SparkServiceAccountRequirerAppData(BaseModel): title="Requested secrets", ) - skip_creation: bool = Field( + skip_creation: str = Field( alias="skip-creation", description="Define whether the providing charm should skip the creation of the" " service account requested.", + examples=["false", "true"], title="Skip creation", ) + @field_validator("requested_secrets", mode="before") + @classmethod + def _load_json(cls, value: Any) -> Any: + if not isinstance(value, str): + return value # __init__ argument was already deserialized. + return json.loads(value) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of this interface.""" - - app: SparkServiceAccountProviderAppData - + @field_serializer("requested_secrets") + def _dump_json(self, value: object) -> str | None: + if value is None: + return None + return json.dumps(value) -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of this interface.""" - app: SparkServiceAccountRequirerAppData +ProviderUnitData = None +RequirerUnitData = None diff --git a/interfaces/spark_service_account/interface/v0/tests/.disable b/interfaces/spark_service_account/interface/v0/tests/.disable deleted file mode 100644 index e69de29bb..000000000 diff --git a/interfaces/spark_service_account/interface/v0/tests/test_provider.py b/interfaces/spark_service_account/interface/v0/tests/test_provider.py deleted file mode 100644 index c19b4feb9..000000000 --- a/interfaces/spark_service_account/interface/v0/tests/test_provider.py +++ /dev/null @@ -1,44 +0,0 @@ -from interface_tester import Tester -from scenario import Relation, State - - -def test_nothing_happens_if_remote_empty(): - # GIVEN that the remote end has not published anything on databag - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="spark-service-account", - interface="spark_service_account", - ) - ], - ) - ) - - # WHEN this charm receives a relation-joined event - state_out = t.run("spark-service-account-relation-joined") - - # THEN no data is published to the (local) databags - t.assert_relation_data_empty() - - -def test_data_written_happy_path(): - # GIVEN that the remote end has requested a service account in the right format - t = Tester( - State( - leader=True, - relations=[ - Relation( - endpoint="spark-service-account", - interface="spark_service_account", - remote_app_data={"service-account": "namespace:sa-name"}, - ) - ], - ) - ) - # WHEN this charm receives a relation-changed event - state_out = t.run("spark-service-account-relation-changed") - - # THEN the schema is satisfied (this charm published all required fields) - t.assert_schema_valid() diff --git a/interfaces/spark_service_account/ruff.toml b/interfaces/spark_service_account/ruff.toml index efbd8c5cc..79b043b3f 100644 --- a/interfaces/spark_service_account/ruff.toml +++ b/interfaces/spark_service_account/ruff.toml @@ -9,10 +9,3 @@ quote-style = "preserve" "D", # docs "E501", # line too long ] -"./interface/v*/tests/*.py" = [ - "CPY", # copyright - "D", # docs - "S", # security - "E501", # line too long - "F841", # assignment to unused variable -] diff --git a/interfaces/zookeeper/interface/v0/schema.py b/interfaces/zookeeper/interface/v0/schema.py index 9791d5979..29de92964 100644 --- a/interfaces/zookeeper/interface/v0/schema.py +++ b/interfaces/zookeeper/interface/v0/schema.py @@ -1,22 +1,32 @@ -"""This file defines the schemas for the provider and requirer sides of the zookeeper_client interface. +"""This file defines the schemas for the provider and requirer sides of the zookeeper_client interface.""" -It must expose two interfaces.schema_base.DataBagSchema subclasses called: -- ProviderSchema -- RequirerSchema -""" +import json +from typing import Any, TypeAlias -from typing import TypeAlias - -from interface_tester.schema_base import DataBagSchema -from pydantic import ( - BaseModel, - Field, -) +from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator Endpoints: TypeAlias = str -class ZooKeeperProviderAppData(BaseModel): +class _BareStringDatabag(BaseModel): + """Base class for databag models that don't strictly JSON encode all entries.""" + + @staticmethod + def __juju_decoder__(value: str) -> str: + """Pass Juju's string through unmodified to be decoded by individual field validators.""" + return value + + @staticmethod + def __juju_encoder__(value: str | None) -> str: + """Convert `None` to "", erasing the value; Ops will error on a non-string.""" + return "" if value is None else value + + +class ProviderAppData(_BareStringDatabag): + """The provider's application databag.""" + + model_config = ConfigDict(strict=True, populate_by_name=True) + database: str = Field( description="The parent chroot zNode granted to the requirer", examples=["/myappB"], @@ -37,7 +47,7 @@ class ZooKeeperProviderAppData(BaseModel): ) secret_tls: str | None = Field( - None, + default=None, alias="secret-tls", description="The name of the TLS secret to use. Leaving this empty will configure a client with TLS disabled. The secret contains [tls].", examples=["secret://59060ecc-0495-4a80-8006-5f1fc13fd783/cjqub7fubg2s77p3niog"], @@ -45,7 +55,11 @@ class ZooKeeperProviderAppData(BaseModel): ) -class ZooKeeperRequirerAppData(BaseModel): +class RequirerAppData(_BareStringDatabag): + """The requirer's application databag.""" + + model_config = ConfigDict(strict=True, populate_by_name=True) + database: str = Field( description="The parent chroot zNode requested by the requirer", examples=["/myappA"], @@ -53,7 +67,7 @@ class ZooKeeperRequirerAppData(BaseModel): ) extra_user_roles: str | None = Field( - None, + default=None, alias="extra-user-roles", description="ACL string representation for the parent chroot", examples=["cdrwa"], @@ -67,14 +81,19 @@ class ZooKeeperRequirerAppData(BaseModel): title="Requested secrets", ) + @field_validator("requested_secrets", mode="before") + @classmethod + def _load_json(cls, value: Any) -> Any: + if not isinstance(value, str): + return value # __init__ argument was already deserialized. + return json.loads(value) -class ProviderSchema(DataBagSchema): - """The schema for the provider side of this interface.""" - - app: ZooKeeperProviderAppData - + @field_serializer("requested_secrets") + def _dump_json(self, value: object) -> str | None: + if value is None: + return None + return json.dumps(value) -class RequirerSchema(DataBagSchema): - """The schema for the requirer side of this interface.""" - app: ZooKeeperRequirerAppData +ProviderUnitData = None +RequirerUnitData = None