diff --git a/docs/api/model/map.rst b/docs/api/model/map.rst index 435f139..799eceb 100644 --- a/docs/api/model/map.rst +++ b/docs/api/model/map.rst @@ -9,4 +9,4 @@ Mapping definitions - :ref:`map`. .. automodule:: pysdmx.model.map - :members: MappingDefinition, ComponentMapper, MultipleComponentMapper, ValueMap, MultipleValueMap, ImplicitMapper, ValueSetter, DateMapper \ No newline at end of file + :members: StructureMap, ComponentMap, MultiComponentMap, ValueMap, MultiValueMap, ImplicitComponentMap, FixedValueMap, DatePatternMap \ No newline at end of file diff --git a/docs/howto/map.rst b/docs/howto/map.rst index 99e6c49..6c1cea6 100644 --- a/docs/howto/map.rst +++ b/docs/howto/map.rst @@ -132,7 +132,7 @@ this is the case for ``OPTION_TYPE``, ``TO``, and ``OI``. for m in mapping.implicit_maps: print(m) # Output example: - # ImplicitMapper(source='OPT_TYP', target='OPTION_TYPE') + # ImplicitComponentMap(source='OPT_TYP', target='OPTION_TYPE') As seen, the operation to be applied is fairly simple: @@ -151,7 +151,7 @@ Another fairly simple case is setting fixed values. This is the case for for m in mapping.fixed_value_maps: print(m) # Output example: - # ValueSetter(target='FREQ', value='M') + # FixedValueMap(target='FREQ', value='M') Mapping Codes """"""""""""" @@ -165,7 +165,7 @@ mappings can be retrieved via the ``component_maps`` property: for m in mapping.component_maps: print(m) # Output example: - # ComponentMapper( + # ComponentMap( # source='CONTRACT', # target='CONTRACT', # values=[ @@ -209,7 +209,7 @@ property: for m in rm.date_maps: print(m) # Output example: - # DateMapper(source='ACTIVITY_DATE', target='TIME_PERIOD', pattern='MM/dd/yyyy', frequency='M') + # PatternMap(source='ACTIVITY_DATE', target='TIME_PERIOD', pattern='MM/dd/yyyy', frequency='M') Here, we need to parse the date using the supplied pattern, ``MM/dd/yyyy``, i.e., dates like ``12/25/2013``. Once this is done, we need to format them to diff --git a/pyproject.toml b/pyproject.toml index 1a0907e..bb64518 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pysdmx" -version = "1.0.0-beta-1" +version = "1.0.0-beta-2" description = "Your opinionated Python SDMX library" authors = [ "Xavier Sosnovsky ", diff --git a/src/pysdmx/__init__.py b/src/pysdmx/__init__.py index 355b688..7f280f2 100644 --- a/src/pysdmx/__init__.py +++ b/src/pysdmx/__init__.py @@ -1,3 +1,3 @@ """Your opinionated Python SDMX library.""" -__version__ = "1.0.0-beta-1" +__version__ = "1.0.0-beta-2" diff --git a/src/pysdmx/fmr/__init__.py b/src/pysdmx/fmr/__init__.py index e47aeaf..83b2e60 100644 --- a/src/pysdmx/fmr/__init__.py +++ b/src/pysdmx/fmr/__init__.py @@ -15,10 +15,10 @@ ConceptScheme, DataflowInfo, Hierarchy, - MappingDefinition, MetadataReport, Organisation, Schema, + StructureMap, ValueMap, ) @@ -432,7 +432,7 @@ def get_mapping( agency: str, id: str, version: str = "+", - ) -> MappingDefinition: + ) -> StructureMap: """Get a mapping definition (aka structure map). Args: @@ -450,7 +450,7 @@ def get_mapping( def get_code_map( self, agency: str, id: str, version: str = "+" ) -> Sequence[ValueMap]: - """Get a mapping definition. + """Get a code map (aka representation map). Args: agency: The agency maintaining the representation map. @@ -741,7 +741,7 @@ async def get_mapping( agency: str, id: str, version: str = "+", - ) -> MappingDefinition: + ) -> StructureMap: """Get a mapping definition (aka structure map). Args: @@ -759,7 +759,7 @@ async def get_mapping( async def get_code_map( self, agency: str, id: str, version: str = "+" ) -> Sequence[ValueMap]: - """Get a representation map. + """Get a code map (aka representation map). Args: agency: The agency maintaining the representation map. diff --git a/src/pysdmx/fmr/fusion/map.py b/src/pysdmx/fmr/fusion/map.py index abbad19..ee2d74b 100644 --- a/src/pysdmx/fmr/fusion/map.py +++ b/src/pysdmx/fmr/fusion/map.py @@ -6,15 +6,16 @@ from msgspec import Struct +from pysdmx.fmr.fusion.core import FusionString from pysdmx.model import ( - ComponentMapper, - DateMapper, - ImplicitMapper, - MappingDefinition, - MultipleComponentMapper, - MultipleValueMap, + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + MultiValueMap, + StructureMap as SM, ValueMap, - ValueSetter, ) from pysdmx.util import find_by_urn @@ -46,10 +47,10 @@ def __get_dt(self, inp: str) -> dt: inp = inp[0:-1] return dt.fromisoformat(inp).replace(tzinfo=tz.utc) - def to_model(self, is_multi: bool) -> Union[MultipleValueMap, ValueMap]: + def to_model(self, is_multi: bool) -> Union[MultiValueMap, ValueMap]: """Returns the requested value maps.""" if is_multi: - return MultipleValueMap( + return MultiValueMap( [src.to_model() for src in self.source], self.target, self.__get_dt(self.validFrom) if self.validFrom else None, @@ -79,7 +80,7 @@ class FusionRepresentationMap( def to_model( self, is_multi: bool = False, - ) -> Sequence[Union[MultipleValueMap, ValueMap]]: + ) -> Sequence[Union[MultiValueMap, ValueMap]]: """Returns the requested value maps.""" return [rm.to_model(is_multi) for rm in self.mappedRelationships] @@ -93,22 +94,22 @@ class FusionComponentMap(Struct, frozen=True): def to_model( self, rms: Sequence[FusionRepresentationMap] - ) -> Union[ComponentMapper, MultipleComponentMapper, ImplicitMapper]: + ) -> Union[ComponentMap, MultiComponentMap, ImplicitComponentMap]: """Returns the requested component map.""" if self.representationMapRef: rm = find_by_urn(rms, self.representationMapRef) if len(self.sources) == 1 and len(self.targets) == 1: - return ComponentMapper( + return ComponentMap( self.sources[0], self.targets[0], rm.to_model(), ) else: - return MultipleComponentMapper( + return MultiComponentMap( self.sources, self.targets, rm.to_model(True) ) else: - return ImplicitMapper(self.sources[0], self.targets[0]) + return ImplicitComponentMap(self.sources[0], self.targets[0]) class FusionTimePatternMap(Struct, frozen=True): @@ -116,22 +117,38 @@ class FusionTimePatternMap(Struct, frozen=True): source: str target: str - freqId: str pattern: str + locale: str + freqId: Optional[str] = None + freqDim: Optional[str] = None + id: Optional[str] = None - def to_model(self) -> DateMapper: + def to_model(self) -> DatePatternMap: """Returns the requested date mapper.""" - return DateMapper( + freq = self.freqId if self.freqId else self.freqDim + typ = "fixed" if self.freqId else "variable" + return DatePatternMap( self.source, self.target, self.pattern, - self.freqId, + freq, # type: ignore[arg-type] + self.id, + self.locale, + typ, # type: ignore[arg-type] ) class FusionStructureMap(Struct, frozen=True): """Fusion-JSON payload for a structure map.""" + id: str + agencyId: str + version: str + source: str + target: str + names: Sequence[FusionString] = () + descriptions: Sequence[FusionString] = () + fixedInput: Dict[str, Any] = {} fixedOutput: Dict[str, Any] = {} timePatternMaps: Sequence[FusionTimePatternMap] = () componentMaps: Sequence[FusionComponentMap] = () @@ -139,21 +156,24 @@ class FusionStructureMap(Struct, frozen=True): def to_model( self, rms: Sequence[FusionRepresentationMap], - ) -> MappingDefinition: + ) -> SM: """Returns the requested mapping definition.""" - m1 = [tpm.to_model() for tpm in self.timePatternMaps] - m2 = [cm.to_model(rms) for cm in self.componentMaps] - m3 = [ValueSetter(k, v) for k, v in self.fixedOutput.items()] - m4 = [m for m in m2 if isinstance(m, ImplicitMapper)] - m5 = [m for m in m2 if isinstance(m, MultipleComponentMapper)] - m6 = [m for m in m2 if isinstance(m, ComponentMapper)] - - return MappingDefinition( - component_maps=m6, - date_maps=m1, - fixed_value_maps=m3, - implicit_maps=m4, - multiple_component_maps=m5, + m1 = tuple(tpm.to_model() for tpm in self.timePatternMaps) + m2 = tuple(cm.to_model(rms) for cm in self.componentMaps) + m3 = tuple(FixedValueMap(k, v) for k, v in self.fixedOutput.items()) + m4 = tuple( + FixedValueMap(k, v, "source") for k, v in self.fixedInput.items() + ) + + return SM( + self.id, + self.names[0].value, + self.agencyId, + self.source, + self.target, + m1 + m2 + m3 + m4, + self.descriptions[0].value if self.descriptions else None, + self.version, ) @@ -163,7 +183,7 @@ class FusionMappingMessage(Struct, frozen=True): StructureMap: Sequence[FusionStructureMap] RepresentationMap: Sequence[FusionRepresentationMap] = () - def to_model(self) -> MappingDefinition: + def to_model(self) -> SM: """Returns the requested mapping definition.""" return self.StructureMap[0].to_model(self.RepresentationMap) @@ -173,7 +193,7 @@ class FusionRepresentationMapMessage(Struct, frozen=True): RepresentationMap: Sequence[FusionRepresentationMap] - def to_model(self) -> MappingDefinition: + def to_model(self) -> SM: """Returns the requested mapping definition.""" out = self.RepresentationMap[0].to_model() return out # type: ignore[return-value] diff --git a/src/pysdmx/fmr/sdmx/map.py b/src/pysdmx/fmr/sdmx/map.py index 00a9b0e..4f4a138 100644 --- a/src/pysdmx/fmr/sdmx/map.py +++ b/src/pysdmx/fmr/sdmx/map.py @@ -7,14 +7,14 @@ from msgspec import Struct from pysdmx.model import ( - ComponentMapper, - DateMapper, - ImplicitMapper, - MappingDefinition, - MultipleComponentMapper, - MultipleValueMap, + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + MultiValueMap, + StructureMap, ValueMap, - ValueSetter, ) from pysdmx.util import find_by_urn @@ -44,10 +44,10 @@ class JsonRepresentationMapping(Struct, frozen=True): def __get_dt(self, inp: str) -> dt: return dt.fromisoformat(inp).replace(tzinfo=tz.utc) - def to_model(self, is_multi: bool) -> Union[MultipleValueMap, ValueMap]: + def to_model(self, is_multi: bool) -> Union[MultiValueMap, ValueMap]: """Returns the requested value maps.""" if is_multi: - return MultipleValueMap( + return MultiValueMap( [src.to_model() for src in self.sourceValues], self.targetValues, self.__get_dt(self.validFrom) if self.validFrom else None, @@ -76,7 +76,7 @@ class JsonRepresentationMap( def to_model( self, is_multi: bool = False - ) -> Sequence[Union[MultipleValueMap, ValueMap]]: + ) -> Sequence[Union[MultiValueMap, ValueMap]]: """Returns the requested value maps.""" return [rm.to_model(is_multi) for rm in self.representationMappings] @@ -84,12 +84,19 @@ def to_model( class JsonFixedValueMap(Struct, frozen=True): """SDMX-JSON payload for a fixed value map.""" - target: str values: Sequence[Any] + source: Optional[str] = None + target: Optional[str] = None - def to_model(self) -> ValueSetter: + def to_model(self) -> FixedValueMap: """Returns the requested fixed value map.""" - return ValueSetter(self.target, self.values[0]) + located_in = "source" if self.source else "target" + target = self.target if self.target else self.source + return FixedValueMap( + target, # type: ignore[arg-type] + self.values[0], + located_in, # type: ignore[arg-type] + ) class JsonComponentMap(Struct, frozen=True): @@ -101,22 +108,22 @@ class JsonComponentMap(Struct, frozen=True): def to_model( self, rms: Sequence[JsonRepresentationMap] - ) -> Union[ComponentMapper, MultipleComponentMapper, ImplicitMapper]: + ) -> Union[ComponentMap, MultiComponentMap, ImplicitComponentMap]: """Returns the requested map.""" if self.representationMap: rm = find_by_urn(rms, self.representationMap) if len(self.source) == 1 and len(self.target) == 1: - return ComponentMapper( + return ComponentMap( self.source[0], self.target[0], rm.to_model(), ) else: - return MultipleComponentMapper( + return MultiComponentMap( self.source, self.target, rm.to_model(True) ) else: - return ImplicitMapper(self.source[0], self.target[0]) + return ImplicitComponentMap(self.source[0], self.target[0]) class JsonMappedPair(Struct, frozen=True): @@ -131,21 +138,40 @@ class JsonDatePatternMap(Struct, frozen=True): sourcePattern: str mappedComponents: Sequence[JsonMappedPair] - targetFrequencyID: str + locale: str + id: Optional[str] = None + targetFrequencyID: Optional[str] = None + frequencyDimension: Optional[str] = None - def to_model(self) -> DateMapper: + def to_model(self) -> DatePatternMap: """Returns the requested date mapper.""" - return DateMapper( + freq = ( + self.targetFrequencyID + if self.targetFrequencyID + else self.frequencyDimension + ) + typ = "fixed" if self.targetFrequencyID else "variable" + return DatePatternMap( self.mappedComponents[0].source, self.mappedComponents[0].target, self.sourcePattern, - self.targetFrequencyID, + freq, # type: ignore[arg-type] + self.id, + self.locale, + typ, # type: ignore[arg-type] ) class JsonStructureMap(Struct, frozen=True): """SDMX-JSON payload for a structure map.""" + id: str + name: str + version: str + agencyID: str + source: str + target: str + description: Optional[str] = None datePatternMaps: Sequence[JsonDatePatternMap] = () componentMaps: Sequence[JsonComponentMap] = () fixedValueMaps: Sequence[JsonFixedValueMap] = () @@ -153,21 +179,20 @@ class JsonStructureMap(Struct, frozen=True): def to_model( self, rms: Sequence[JsonRepresentationMap], - ) -> MappingDefinition: + ) -> StructureMap: """Returns the requested mapping definition.""" - m1 = [dpm.to_model() for dpm in self.datePatternMaps] - m2 = [cm.to_model(rms) for cm in self.componentMaps] - m3 = [fvm.to_model() for fvm in self.fixedValueMaps] - m4 = [m for m in m2 if isinstance(m, ImplicitMapper)] - m5 = [m for m in m2 if isinstance(m, MultipleComponentMapper)] - m6 = [m for m in m2 if isinstance(m, ComponentMapper)] - - return MappingDefinition( - component_maps=m6, - date_maps=m1, - fixed_value_maps=m3, - implicit_maps=m4, - multiple_component_maps=m5, + m1 = tuple([dpm.to_model() for dpm in self.datePatternMaps]) + m2 = tuple([cm.to_model(rms) for cm in self.componentMaps]) + m3 = tuple([fvm.to_model() for fvm in self.fixedValueMaps]) + return StructureMap( + self.id, + self.name, + self.agencyID, + self.source, + self.target, + m1 + m2 + m3, + self.description, + self.version, ) @@ -177,7 +202,7 @@ class JsonStructureMaps(Struct, frozen=True): structureMaps: Sequence[JsonStructureMap] representationMaps: Sequence[JsonRepresentationMap] = () - def to_model(self) -> MappingDefinition: + def to_model(self) -> StructureMap: """Returns the requested mapping definition.""" return self.structureMaps[0].to_model(self.representationMaps) @@ -187,7 +212,7 @@ class JsonMappingMessage(Struct, frozen=True): data: JsonStructureMaps - def to_model(self) -> MappingDefinition: + def to_model(self) -> StructureMap: """Returns the requested mapping definition.""" return self.data.to_model() diff --git a/src/pysdmx/model/__init__.py b/src/pysdmx/model/__init__.py index 04a2027..9305955 100644 --- a/src/pysdmx/model/__init__.py +++ b/src/pysdmx/model/__init__.py @@ -19,14 +19,14 @@ Schema, ) from pysdmx.model.map import ( - ComponentMapper, - DateMapper, - ImplicitMapper, - MappingDefinition, - MultipleComponentMapper, - MultipleValueMap, + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + MultiValueMap, + StructureMap, ValueMap, - ValueSetter, ) from pysdmx.model.metadata import MetadataAttribute, MetadataReport from pysdmx.model.organisation import Contact, DataflowRef, Organisation @@ -76,26 +76,26 @@ def encoders(obj: Any) -> Any: "Codelist", "Component", "Components", - "ComponentMapper", + "ComponentMap", "Concept", "ConceptScheme", "Contact", "DataflowInfo", "DataflowRef", "DataType", - "DateMapper", + "DatePatternMap", "Facets", "HierarchicalCode", "Hierarchy", - "ImplicitMapper", - "MappingDefinition", + "ImplicitComponentMap", + "StructureMap", "MetadataAttribute", "MetadataReport", - "MultipleComponentMapper", - "MultipleValueMap", + "MultiComponentMap", + "MultiValueMap", "Organisation", "Role", "Schema", "ValueMap", - "ValueSetter", + "FixedValueMap", ] diff --git a/src/pysdmx/model/map.py b/src/pysdmx/model/map.py index 910163c..08af795 100644 --- a/src/pysdmx/model/map.py +++ b/src/pysdmx/model/map.py @@ -2,12 +2,12 @@ from datetime import datetime from re import Pattern -from typing import Any, Optional, Sequence, Union +from typing import Any, Iterator, Literal, Optional, Sequence, Union from msgspec import Struct -class DateMapper(Struct, frozen=True, omit_defaults=True): +class DatePatternMap(Struct, frozen=True, omit_defaults=True): """A mapping based on a date pattern. Examples: @@ -17,47 +17,63 @@ class DateMapper(Struct, frozen=True, omit_defaults=True): periods for monthly data (e.g. `2023-09`). This can be expressed with the following mapping: - >>> DateMapper("DATE", "TIME_PERIOD", "MMM yy", "M") + >>> DatePatternMap("DATE", "TIME_PERIOD", "MMM yy", "M") Attributes: - source: The ID of the source component to be mapped. + source: The ID of the source component. target: The ID of the target component. - pattern: The date pattern to use when parsing the source value. - frequency: The desired frequency for the output value. + pattern: Describes the source date using conventions for describing + years, months, days, etc + frequency: The frequency to convert the input date into or a + reference to a dimension or an atttribute with the frequency code. + See `pattern_type` below for additional information. + id: The Map ID, as defined in the Registry. + locale: The locale on which the input will be parsed according to the + pattern. + pattern_type: The type of date pattern, i.e. fixed or variable. When + the type is `fixed`, `frequency` is a fixed value from the + frequency codelist (e.g. `A` for annual frequency). When the type + is `variable`, `frequency` references a dimension or attribute in + the target structure (e.g. `FREQ`). In this case, the input date + can be converted to a different format, depending on the + frequency of the converted data. """ source: str target: str pattern: str frequency: str + id: Optional[str] = None + locale: str = "en" + pattern_type: Literal["fixed", "variable"] = "fixed" -class ValueSetter(Struct, frozen=True, omit_defaults=True): - """A mapping setting a fixed or default value in the target. +class FixedValueMap(Struct, frozen=True, omit_defaults=True): + """Set a component to a fixed value. Examples: For example, let's assume that all observations in the target must be treated as free for publication. This can be expressed with the following mapping: - >>> ValueSetter("CONF_STATUS", "F") + >>> FixedValueMap("CONF_STATUS", "F") Attributes: - target: - The ID of the component for which we want to set the fixed value. - value: The fixed value to be set in the referenced component. - is_fixed: - Whether the value must be set regardless of whether a value is - already set for the component. + target: The ID of the component to which the fixed value is assigned. + value: The fixed value of the referenced component. + located_in: Whether the component with a fixed value is in the source + structure or the target structure. It usually is in the target + structure (the default), but it can also be in the source, in case + of bi-directional mapping. """ target: str value: Any - is_fixed: bool = True + located_in: Literal["source", "target"] = "target" -class ImplicitMapper(Struct, frozen=True, omit_defaults=True): +class ImplicitComponentMap(Struct, frozen=True, omit_defaults=True): """A mapping where the value in the source is copied to the target. Examples: @@ -66,7 +82,7 @@ class ImplicitMapper(Struct, frozen=True, omit_defaults=True): a target component (`CONF_STATUS`). This can be expressed with the following mapping: - >>> ImplicitMapper("OBS_CONF", "CONF_STATUS") + >>> ImplicitComponentMap("OBS_CONF", "CONF_STATUS") Attributes: source: @@ -79,7 +95,7 @@ class ImplicitMapper(Struct, frozen=True, omit_defaults=True): target: str -class MultipleValueMap(Struct, frozen=True, omit_defaults=True): +class MultiValueMap(Struct, frozen=True, omit_defaults=True): """Provides the values for a mapping between one or more components. Examples: @@ -89,8 +105,8 @@ class MultipleValueMap(Struct, frozen=True, omit_defaults=True): the target should be `EUR` but, if the country is `CH`, then it should be `CHF`. This can be expressed with the following value maps: - >>> MultipleValueMap(["DE", "LC"], ["EUR"]) - >>> MultipleValueMap(["CH", "LC"], ["CHF"]) + >>> MultiValueMap(["DE", "LC"], ["EUR"]) + >>> MultiValueMap(["CH", "LC"], ["CHF"]) Also, the mapping can depending on the time period. @@ -102,9 +118,9 @@ class MultipleValueMap(Struct, frozen=True, omit_defaults=True): >>> from datetime import datetime >>> t1 = datetime(1998, 12, 31, 23, 59, 59) >>> t2 = datetime(1999, 1, 1) - >>> MultipleValueMap(["DE", "LC"], ["EUR"], valid_to: t1) - >>> MultipleValueMap(["DE", "LC"], ["EUR"], valid_from: t2) - >>> MultipleValueMap(["CH", "LC"], ["CHF"]) + >>> MultiValueMap(["DE", "LC"], ["EUR"], valid_to: t1) + >>> MultiValueMap(["DE", "LC"], ["EUR"], valid_from: t2) + >>> MultiValueMap(["CH", "LC"], ["CHF"]) Values in the source may represent regular expressions with capture groups. @@ -150,7 +166,7 @@ class ValueMap(Struct, frozen=True, omit_defaults=True): valid_to: Optional[datetime] = None -class MultipleComponentMapper(Struct, frozen=True, omit_defaults=True): +class MultiComponentMap(Struct, frozen=True, omit_defaults=True): """Maps one or more source components to one or more target components. Examples: @@ -160,11 +176,11 @@ class MultipleComponentMapper(Struct, frozen=True, omit_defaults=True): the target should be `EUR` but, if the country is `CH`, then it should be `CHF`. This can be expressed as follows: - >>> de = MultipleValueMap(["DE", "LC"], ["EUR"]) - >>> ch = MultipleValueMap(["CH", "LC"], ["CHF"]) + >>> de = MultiValueMap(["DE", "LC"], ["EUR"]) + >>> ch = MultiValueMap(["CH", "LC"], ["CHF"]) >>> src = ["COUNTRY", "CURRENCY"] >>> tgt = ["CURRENCY"] - >>> cm = MultipleComponentMapper(src, tgt, [de, ch]) + >>> cm = MultiComponentMap(src, tgt, [de, ch]) Attributes: source: The source component(s) @@ -175,10 +191,10 @@ class MultipleComponentMapper(Struct, frozen=True, omit_defaults=True): source: Sequence[str] target: Sequence[str] - values: Sequence[MultipleValueMap] + values: Sequence[MultiValueMap] -class ComponentMapper(Struct, frozen=True, omit_defaults=True): +class ComponentMap(Struct, frozen=True, omit_defaults=True): """Maps a source component to a target component. Examples: @@ -188,7 +204,7 @@ class ComponentMapper(Struct, frozen=True, omit_defaults=True): >>> ar = ValueMap("AR", "ARG") >>> uy = ValueMap("UY", "URY") - >>> cm = ComponentMapper("COUNTRY", "COUNTRY", [ar, uy]) + >>> cm = ComponentMap("COUNTRY", "COUNTRY", [ar, uy]) Attributes: source: The source component @@ -202,24 +218,152 @@ class ComponentMapper(Struct, frozen=True, omit_defaults=True): values: Sequence[ValueMap] -class MappingDefinition(Struct, frozen=True, omit_defaults=True): +class StructureMap(Struct, frozen=True, omit_defaults=True): """Maps a source structure to a target structure. The various mapping rules are classified by types. + A structure map is an iterable, i.e. it is possible to iterate over + the various mapping rules using a `for` loop. + + It is also possible to retrieve the mapping rule applying to a component + by using the component id (e.g. `map["FREQ"]`). + Attributes: - component_maps: The list of mappings between one source component - and one target component. - date_maps: The list of mappings based on date patterns - fixed_value_maps: The list of mappings with a fixed value - implicit_maps: The list of mappings where the value in the source - is copied to the target. - multiple_component_maps: The list of mappings between one or more - source components and one or more target components. + id: The identifier for the codelist (e.g. CL_FREQ). + name: The codelist name (e.g. "Frequency codelist"). + agency: The maintainer of the codelist (e.g. SDMX). + source: The source structure. + target: The target structure. + maps: The various mapping rules in the structure map. + description: Additional descriptive information about the codelist + (e.g. "This codelist provides a set of values indicating the + frequency of the data"). + version: The codelist version (e.g. 2.0.42) + """ - component_maps: Sequence[ComponentMapper] = () - date_maps: Sequence[DateMapper] = () - fixed_value_maps: Sequence[ValueSetter] = () - implicit_maps: Sequence[ImplicitMapper] = () - multiple_component_maps: Sequence[MultipleComponentMapper] = () + id: str + name: str + agency: str + source: str + target: str + maps: Sequence[ + Union[ + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + ] + ] + description: Optional[str] = None + version: str = "1.0" + + @property + def component_maps(self) -> Sequence[ComponentMap]: + """Maps between one source and one target component.""" + return list( + filter( + lambda i: isinstance( # type: ignore[arg-type] + i, + ComponentMap, + ), + self.maps, + ) + ) + + @property + def date_pattern_maps(self) -> Sequence[DatePatternMap]: + """Maps based on date patterns.""" + return list( + filter( + lambda i: isinstance( # type: ignore[arg-type] + i, + DatePatternMap, + ), + self.maps, + ) + ) + + @property + def fixed_value_maps(self) -> Sequence[FixedValueMap]: + """Maps with a fixed value.""" + return list( + filter( + lambda i: isinstance( # type: ignore[arg-type] + i, + FixedValueMap, + ), + self.maps, + ) + ) + + @property + def implicit_component_maps(self) -> Sequence[ImplicitComponentMap]: + """Maps where the source value is copied to the target.""" + return list( + filter( + lambda i: isinstance( # type: ignore[arg-type] + i, + ImplicitComponentMap, + ), + self.maps, + ) + ) + + @property + def multi_component_maps(self) -> Sequence[MultiComponentMap]: + """Maps between one or more source & one or more target components.""" + return list( + filter( + lambda i: isinstance( # type: ignore[arg-type] + i, + MultiComponentMap, + ), + self.maps, + ) + ) + + def __iter__( + self, + ) -> Iterator[ + Union[ + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + ] + ]: + """Return an iterator over the different mapping rules.""" + yield from self.maps + + def __len__(self) -> int: + """Return the number of mapping rules in the structure map.""" + return len(self.maps) + + def __getitem__( + self, id_: str + ) -> Optional[ + Sequence[ + Union[ + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + MultiComponentMap, + ] + ] + ]: + """Return the mapping rules for the supplied component.""" + out = [] + for m in self.maps: + if ( + hasattr(m, "source") and (m.source == id_ or id_ in m.source) + ) or (isinstance(m, FixedValueMap) and m.target == id_): + out.append(m) + if len(out) == 0: + return None + else: + return out diff --git a/tests/fmr/mapping_checks.py b/tests/fmr/mapping_checks.py index 2f361cb..23cf3c3 100644 --- a/tests/fmr/mapping_checks.py +++ b/tests/fmr/mapping_checks.py @@ -6,11 +6,11 @@ from pysdmx.fmr import AsyncRegistryClient, RegistryClient from pysdmx.model.map import ( - ComponentMapper, - DateMapper, - ImplicitMapper, - MappingDefinition, - ValueSetter, + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + StructureMap, ) @@ -25,15 +25,20 @@ def check_mapping(mock, fmr: RegistryClient, query, body): mapping = fmr.get_mapping("BIS", "SRC_2_MDD", "1.0") - assert isinstance(mapping, MappingDefinition) - count = ( - len(mapping.component_maps) - + len(mapping.date_maps) - + len(mapping.fixed_value_maps) - + len(mapping.implicit_maps) - + len(mapping.multiple_component_maps) - ) - assert count == 9 + assert isinstance(mapping, StructureMap) + assert mapping.id == "SRC_2_MDD" + assert mapping.name == "Map SRC data to MDD" + assert mapping.agency == "BIS" + assert mapping.version == "1.0" + assert "BIS:SRC(1.0)" in mapping.source + assert "BIS:MDD(1.0)" in mapping.target + assert mapping.description is None + assert len(mapping.maps) == 10 + assert len(mapping.component_maps) == 1 + assert len(mapping.date_pattern_maps) == 2 + assert len(mapping.fixed_value_maps) == 3 + assert len(mapping.implicit_component_maps) == 4 + assert len(mapping.multi_component_maps) == 0 def check_multi_mapping(mock, fmr: RegistryClient, query, body): @@ -47,17 +52,17 @@ def check_multi_mapping(mock, fmr: RegistryClient, query, body): mapping = fmr.get_mapping("BIS", "FXS_2_MDD", "1.0") - assert isinstance(mapping, MappingDefinition) + assert isinstance(mapping, StructureMap) count = ( len(mapping.component_maps) - + len(mapping.date_maps) + + len(mapping.date_pattern_maps) + len(mapping.fixed_value_maps) - + len(mapping.implicit_maps) - + len(mapping.multiple_component_maps) + + len(mapping.implicit_component_maps) + + len(mapping.multi_component_maps) ) assert count == 2 - assert len(mapping.multiple_component_maps) == 1 - assert len(mapping.implicit_maps) == 1 + assert len(mapping.multi_component_maps) == 1 + assert len(mapping.implicit_component_maps) == 1 async def check_mapping_rules(mock, fmr: AsyncRegistryClient, query, body): @@ -72,22 +77,22 @@ async def check_mapping_rules(mock, fmr: AsyncRegistryClient, query, body): mapping = await fmr.get_mapping("BIS", "SRC_2_MDD", "1.0") assert len(mapping.component_maps) == 1 - assert len(mapping.date_maps) == 1 + assert len(mapping.date_pattern_maps) == 2 assert len(mapping.fixed_value_maps) == 3 - assert len(mapping.implicit_maps) == 4 - assert len(mapping.multiple_component_maps) == 0 + assert len(mapping.implicit_component_maps) == 4 + assert len(mapping.multi_component_maps) == 0 for m in mapping.component_maps: __check_component(m) - for m in mapping.date_maps: + for m in mapping.date_pattern_maps: __check_date(m) for m in mapping.fixed_value_maps: __check_fixed(m) - for m in mapping.implicit_maps: + for m in mapping.implicit_component_maps: __check_implicit(m) -def __check_component(m: ComponentMapper): +def __check_component(m: ComponentMap): assert m.source == "CONTRACT" assert m.target == "CONTRACT" assert len(m.values) == 2 @@ -103,14 +108,25 @@ def __check_component(m: ComponentMapper): assert v.valid_to is None -def __check_date(m: DateMapper): - assert m.source == "ACTIVITY_DATE" - assert m.target == "TIME_PERIOD" - assert m.frequency == "M" - assert m.pattern == "MM/dd/yyyy" +def __check_date(m: DatePatternMap): + if m.id == "my_id": + assert m.source == "ACTIVITY_DATE" + assert m.target == "TIME_PERIOD" + assert m.frequency == "M" + assert m.pattern == "MM/dd/yyyy" + assert m.pattern_type == "fixed" + assert m.locale == "en" + else: + assert m.source == "VOLUME_MONTH" + assert m.target == "TIME_PERIOD" + assert m.frequency == "CONTRACT_CODE" + assert m.pattern == "ddMMyy" + assert m.pattern_type == "variable" + assert m.locale == "es" + assert m.id == "your_id" -def __check_implicit(m: ImplicitMapper): +def __check_implicit(m: ImplicitComponentMap): if m.source in ["OPTION_TYPE", "OI"]: assert m.target == m.source elif m.source == "VOL_MTD": @@ -121,12 +137,15 @@ def __check_implicit(m: ImplicitMapper): pytest.fail(f"Unexpected implicit value: {m}") -def __check_fixed(m: ValueSetter): +def __check_fixed(m: FixedValueMap): if m.target == "OBS_STATUS": assert m.value == "A" + assert m.located_in == "target" elif m.target == "FREQ": assert m.value == "M" + assert m.located_in == "target" elif m.target == "CONF_STATUS": assert m.value == "C" + assert m.located_in == "source" else: pytest.fail(f"Unexpected fixed value: {m}") diff --git a/tests/fmr/samples/map/sm.fusion.json b/tests/fmr/samples/map/sm.fusion.json index ffaf8da..bdd2012 100644 --- a/tests/fmr/samples/map/sm.fusion.json +++ b/tests/fmr/samples/map/sm.fusion.json @@ -53,15 +53,17 @@ "value": "Map SRC data to MDD" } ], - "agencyId": "BIS.XTD", + "agencyId": "BIS", "version": "1.0", "isFinal": false, - "source": "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS.XTD:SRC(1.0)", - "target": "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS.XTD:MDD(1.0)", + "source": "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS:SRC(1.0)", + "target": "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS:MDD(1.0)", + "fixedInput": { + "CONF_STATUS": "C" + }, "fixedOutput": { "OBS_STATUS": "A", - "FREQ": "M", - "CONF_STATUS": "C" + "FREQ": "M" }, "componentMaps": [ { @@ -108,11 +110,20 @@ ], "timePatternMaps": [ { + "id": "my_id", "source": "ACTIVITY_DATE", "target": "TIME_PERIOD", "freqId": "M", "pattern": "MM/dd/yyyy", "locale": "en" + }, + { + "id": "your_id", + "source": "VOLUME_MONTH", + "target": "TIME_PERIOD", + "freqDim": "CONTRACT_CODE", + "pattern": "ddMMyy", + "locale": "es" } ] } diff --git a/tests/fmr/samples/map/sm.json b/tests/fmr/samples/map/sm.json index 0f3b09c..6b20f98 100644 --- a/tests/fmr/samples/map/sm.json +++ b/tests/fmr/samples/map/sm.json @@ -87,6 +87,7 @@ "target": "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS:MDD(1.0)", "datePatternMaps": [ { + "id": "my_id", "sourcePattern": "MM/dd/yyyy", "locale": "en", "mappedComponents": [ @@ -96,6 +97,18 @@ } ], "targetFrequencyID": "M" + }, + { + "id": "your_id", + "sourcePattern": "ddMMyy", + "locale": "es", + "mappedComponents": [ + { + "source": "VOLUME_MONTH", + "target": "TIME_PERIOD" + } + ], + "frequencyDimension": "CONTRACT_CODE" } ], "componentMaps": [ @@ -155,7 +168,7 @@ ] }, { - "target": "CONF_STATUS", + "source": "CONF_STATUS", "values": [ "C" ] diff --git a/tests/model/test_map_date_mapper.py b/tests/model/test_map_date_mapper.py deleted file mode 100644 index 6d67690..0000000 --- a/tests/model/test_map_date_mapper.py +++ /dev/null @@ -1,52 +0,0 @@ -import pytest - -from pysdmx.model import DateMapper - - -@pytest.fixture() -def source(): - return "DATE" - - -@pytest.fixture() -def target(): - return "TIME_PERIOD" - - -@pytest.fixture() -def pattern(): - return "MMM yy" - - -@pytest.fixture() -def freq(): - return "M" - - -def test_full_instantiation(source, target, pattern, freq): - m = DateMapper(source, target, pattern, freq) - - assert m.source == source - assert m.target == target - assert m.pattern == pattern - assert m.frequency == freq - - -def test_immutable(source, target, pattern, freq): - m = DateMapper(source, target, pattern, freq) - with pytest.raises(AttributeError): - m.frequency = "A" - - -def test_equal(source, target, pattern, freq): - m1 = DateMapper(source, target, pattern, freq) - m2 = DateMapper(source, target, pattern, freq) - - assert m1 == m2 - - -def test_not_equal(source, target, pattern, freq): - m1 = DateMapper(source, target, pattern, freq) - m2 = DateMapper(source + "2", target, pattern, freq) - - assert m1 != m2 diff --git a/tests/model/test_map_date_pattern.py b/tests/model/test_map_date_pattern.py new file mode 100644 index 0000000..c250a51 --- /dev/null +++ b/tests/model/test_map_date_pattern.py @@ -0,0 +1,86 @@ +import pytest + +from pysdmx.model import DatePatternMap + + +@pytest.fixture() +def source(): + return "DATE" + + +@pytest.fixture() +def target(): + return "TIME_PERIOD" + + +@pytest.fixture() +def pattern(): + return "MMM yy" + + +@pytest.fixture() +def freq(): + return "M" + + +@pytest.fixture() +def pattern_type(): + return "variable" + + +@pytest.fixture() +def map_id(): + return "my_id" + + +@pytest.fixture() +def locale(): + return "es" + + +def test_default_instantiation(source, target, pattern, freq): + m = DatePatternMap(source, target, pattern, freq) + + assert m.source == source + assert m.target == target + assert m.pattern == pattern + assert m.frequency == freq + assert m.pattern_type == "fixed" + assert m.locale == "en" + assert m.id is None + + +def test_full_instantiation( + source, target, pattern, freq, map_id, locale, pattern_type +): + m = DatePatternMap( + source, target, pattern, freq, map_id, locale, pattern_type + ) + + assert m.source == source + assert m.target == target + assert m.pattern == pattern + assert m.frequency == freq + assert m.pattern_type == pattern_type + assert m.id == map_id + assert m.locale == locale + + +def test_immutable(source, target, pattern, freq): + m = DatePatternMap(source, target, pattern, freq) + with pytest.raises(AttributeError): + m.frequency = "A" + + +def test_equal(source, target, pattern, freq): + m1 = DatePatternMap(source, target, pattern, freq) + m2 = DatePatternMap(source, target, pattern, freq) + + assert m1 == m2 + + +def test_not_equal(source, target, pattern, freq): + m1 = DatePatternMap(source, target, pattern, freq) + m2 = DatePatternMap(source + "2", target, pattern, freq) + + assert m1 != m2 diff --git a/tests/model/test_map_fixed_value.py b/tests/model/test_map_fixed_value.py new file mode 100644 index 0000000..7965b81 --- /dev/null +++ b/tests/model/test_map_fixed_value.py @@ -0,0 +1,54 @@ +import pytest + +from pysdmx.model import FixedValueMap + + +@pytest.fixture() +def target(): + return "CONF_STATUS" + + +@pytest.fixture() +def value(): + return "C" + + +@pytest.fixture() +def located_in(): + return "source" + + +def test_default_instantiation(target, value): + m = FixedValueMap(target, value) + + assert m.target == target + assert m.value == value + assert m.located_in == "target" + + +def test_full_instantiation(target, value, located_in): + m = FixedValueMap(target, value, located_in) + + assert m.target == target + assert m.value == value + assert m.located_in == located_in + + +def test_immutable(target, value): + m = FixedValueMap(target, value) + with pytest.raises(AttributeError): + m.located_in = "source" + + +def test_equal(target, value, located_in): + m1 = FixedValueMap(target, value, located_in) + m2 = FixedValueMap(target, value, located_in) + + assert m1 == m2 + + +def test_not_equal(target, value, located_in): + m1 = FixedValueMap(target, value, located_in) + m2 = FixedValueMap(target, value + value, located_in) + + assert m1 != m2 diff --git a/tests/model/test_map_implicit_mapper.py b/tests/model/test_map_implicit_mapper.py index 4646f09..aa21c15 100644 --- a/tests/model/test_map_implicit_mapper.py +++ b/tests/model/test_map_implicit_mapper.py @@ -1,6 +1,6 @@ import pytest -from pysdmx.model import ImplicitMapper +from pysdmx.model import ImplicitComponentMap @pytest.fixture() @@ -14,27 +14,27 @@ def target(): def test_full_instantiation(source, target): - m = ImplicitMapper(source, target) + m = ImplicitComponentMap(source, target) assert m.source == source assert m.target == target def test_immutable(source, target): - m = ImplicitMapper(source, target) + m = ImplicitComponentMap(source, target) with pytest.raises(AttributeError): m.target = source def test_equal(source, target): - m1 = ImplicitMapper(source, target) - m2 = ImplicitMapper(source, target) + m1 = ImplicitComponentMap(source, target) + m2 = ImplicitComponentMap(source, target) assert m1 == m2 def test_not_equal(source, target): - m1 = ImplicitMapper(source, target) - m2 = ImplicitMapper(source, source) + m1 = ImplicitComponentMap(source, target) + m2 = ImplicitComponentMap(source, source) assert m1 != m2 diff --git a/tests/model/test_map_multi_component_mapper.py b/tests/model/test_map_multi_component_mapper.py index a664d9f..97e146a 100644 --- a/tests/model/test_map_multi_component_mapper.py +++ b/tests/model/test_map_multi_component_mapper.py @@ -1,6 +1,6 @@ import pytest -from pysdmx.model import MultipleComponentMapper, MultipleValueMap +from pysdmx.model import MultiComponentMap, MultiValueMap @pytest.fixture() @@ -15,14 +15,14 @@ def target(): @pytest.fixture() def values(): - vm1 = MultipleValueMap(["CH", "LC1"], ["CHF"]) - vm2 = MultipleValueMap(["CH", "CHF"], ["CHF"]) - vm3 = MultipleValueMap(["DE", "LC1"], ["EUR"]) + vm1 = MultiValueMap(["CH", "LC1"], ["CHF"]) + vm2 = MultiValueMap(["CH", "CHF"], ["CHF"]) + vm3 = MultiValueMap(["DE", "LC1"], ["EUR"]) return [vm1, vm2, vm3] def test_full_instantiation(source, target, values): - m = MultipleComponentMapper(source, target, values) + m = MultiComponentMap(source, target, values) assert m.source == source assert m.target == target @@ -30,20 +30,20 @@ def test_full_instantiation(source, target, values): def test_immutable(source, target, values): - m = MultipleComponentMapper(source, target, values) + m = MultiComponentMap(source, target, values) with pytest.raises(AttributeError): m.values = values def test_equal(source, target, values): - m1 = MultipleComponentMapper(source, target, values) - m2 = MultipleComponentMapper(source, target, values) + m1 = MultiComponentMap(source, target, values) + m2 = MultiComponentMap(source, target, values) assert m1 == m2 def test_not_equal(source, target, values): - m1 = MultipleComponentMapper(source, target, values) - m2 = MultipleComponentMapper(source, source, []) + m1 = MultiComponentMap(source, target, values) + m2 = MultiComponentMap(source, source, []) assert m1 != m2 diff --git a/tests/model/test_map_multi_value_map.py b/tests/model/test_map_multi_value_map.py index ab9afc5..93a6220 100644 --- a/tests/model/test_map_multi_value_map.py +++ b/tests/model/test_map_multi_value_map.py @@ -2,7 +2,7 @@ import pytest -from pysdmx.model import MultipleValueMap +from pysdmx.model import MultiValueMap @pytest.fixture() @@ -16,7 +16,7 @@ def target(): def test_default_instantiation(source, target): - m = MultipleValueMap(source, target) + m = MultiValueMap(source, target) assert m.source == source assert m.target == target @@ -27,7 +27,7 @@ def test_default_instantiation(source, target): def test_full_instantiation(source, target): vf = datetime.utcnow() - timedelta(days=1) vt = datetime.utcnow() - m = MultipleValueMap(source, target, vf, vt) + m = MultiValueMap(source, target, vf, vt) assert m.source == source assert m.target == target @@ -36,20 +36,20 @@ def test_full_instantiation(source, target): def test_immutable(source, target): - m = MultipleValueMap(source, target) + m = MultiValueMap(source, target) with pytest.raises(AttributeError): m.valid_from = datetime.utcnow() def test_equal(source, target): - m1 = MultipleValueMap(source, target) - m2 = MultipleValueMap(source, target) + m1 = MultiValueMap(source, target) + m2 = MultiValueMap(source, target) assert m1 == m2 def test_not_equal(source, target): - m1 = MultipleValueMap(source, target) - m2 = MultipleValueMap(source, target, datetime.utcnow()) + m1 = MultiValueMap(source, target) + m2 = MultiValueMap(source, target, datetime.utcnow()) assert m1 != m2 diff --git a/tests/model/test_map_single_component_mapper copy.py b/tests/model/test_map_single_component_mapper copy.py index 16135e8..3835a2c 100644 --- a/tests/model/test_map_single_component_mapper copy.py +++ b/tests/model/test_map_single_component_mapper copy.py @@ -1,6 +1,6 @@ import pytest -from pysdmx.model import ComponentMapper, ValueMap +from pysdmx.model import ComponentMap, ValueMap @pytest.fixture() @@ -21,7 +21,7 @@ def values(): def test_full_instantiation(source, target, values): - m = ComponentMapper(source, target, values) + m = ComponentMap(source, target, values) assert m.source == source assert m.target == target @@ -29,20 +29,20 @@ def test_full_instantiation(source, target, values): def test_immutable(source, target, values): - m = ComponentMapper(source, target, values) + m = ComponentMap(source, target, values) with pytest.raises(AttributeError): m.values = values def test_equal(source, target, values): - m1 = ComponentMapper(source, target, values) - m2 = ComponentMapper(source, target, values) + m1 = ComponentMap(source, target, values) + m2 = ComponentMap(source, target, values) assert m1 == m2 def test_not_equal(source, target, values): - m1 = ComponentMapper(source, target, values) - m2 = ComponentMapper(source, source, []) + m1 = ComponentMap(source, target, values) + m2 = ComponentMap(source, source, []) assert m1 != m2 diff --git a/tests/model/test_map_structure_map.py b/tests/model/test_map_structure_map.py index 6e4be93..5f9084f 100644 --- a/tests/model/test_map_structure_map.py +++ b/tests/model/test_map_structure_map.py @@ -1,27 +1,133 @@ +from typing import Iterable, Sized +import uuid + import pytest -from pysdmx.model import ImplicitMapper, MappingDefinition +from pysdmx.model import ( + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + StructureMap, + ValueMap, +) + + +@pytest.fixture() +def id(): + return "id" + + +@pytest.fixture() +def name(): + return "name" + + +@pytest.fixture() +def agency(): + return "5B0" + + +@pytest.fixture() +def source(): + return "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS:CIBL(1.0)" + + +@pytest.fixture() +def target(): + return "urn:sdmx:org.sdmx.infomodel.datastructure.Dataflow=BIS:CBS(1.0)" + + +@pytest.fixture() +def version(): + return "2.0" + + +@pytest.fixture() +def desc(): + return "my desc" @pytest.fixture() def mappings(): - m1 = ImplicitMapper("OBS_CONF", "CONF_STATUS") - m2 = ImplicitMapper("OBS_STATUS", "OBS_STATUS") - return [m1, m2] + m1 = ImplicitComponentMap("OBS_CONF", "CONF_STATUS") + m2 = ImplicitComponentMap("OBS_STATUS", "OBS_STATUS") + m3 = FixedValueMap("FREQ", "M") + m4 = DatePatternMap("ACTIVITY_DATE", "TIME_PERIOD", "%B %Y", "M") + m5 = ComponentMap("SRC1", "TGT1", [ValueMap("1", "A")]) + m6 = ComponentMap("SRC2", "TGT2", [ValueMap("2", "B")]) + m7 = ComponentMap("SRC3", "TGT3", [ValueMap("3", "C")]) + return [m1, m2, m3, m4, m5, m6, m7] + + +def test_default_initialization(id, name, agency, source, target, mappings): + sm = StructureMap(id, name, agency, source, target, mappings) + assert sm.id == id + assert sm.name == name + assert sm.agency == agency + assert sm.source == source + assert sm.target == target + assert sm.maps == mappings + assert sm.description is None + assert sm.version == "1.0" -def test_full_initialization(mappings): - sm = MappingDefinition(implicit_maps=mappings) - assert len(sm.implicit_maps) == 2 - assert sm.implicit_maps == mappings - assert not sm.component_maps - assert not sm.date_maps - assert not sm.fixed_value_maps - assert not sm.multiple_component_maps +def test_full_initialization( + id, name, agency, source, target, mappings, version, desc +): + sm = StructureMap( + id, name, agency, source, target, mappings, desc, version + ) + assert sm.id == id + assert sm.name == name + assert sm.agency == agency + assert sm.source == source + assert sm.target == target + assert sm.maps == mappings + assert sm.description == desc + assert sm.version == version + + +def test_immutable(id, name, agency, source, target, mappings): + sm = StructureMap(id, name, agency, source, target, mappings) -def test_immutable(mappings): - sm = MappingDefinition(mappings) with pytest.raises(AttributeError): - sm.fixed_value_maps = [] + sm.description = "blah" + + +def test_iterable(id, name, agency, source, target, mappings): + sm = StructureMap(id, name, agency, source, target, mappings) + + assert isinstance(sm, Iterable) + + for m in sm: + assert isinstance( + m, + ( + ComponentMap, + DatePatternMap, + FixedValueMap, + ImplicitComponentMap, + ), + ) + + +def test_sized(id, name, agency, source, target, mappings): + sm = StructureMap(id, name, agency, source, target, mappings) + + assert isinstance(sm, Sized) + assert len(sm) == len(mappings) + + +def test_get_map(id, name, agency, source, target, mappings): + sm = StructureMap(id, name, agency, source, target, mappings) + + id = mappings[3].source + resp1 = sm[id] + resp2 = sm[str(uuid.uuid4())] + + assert len(resp1) == 1 + assert resp1[0] == mappings[3] + assert resp2 is None diff --git a/tests/model/test_map_value_setter.py b/tests/model/test_map_value_setter.py deleted file mode 100644 index c89b858..0000000 --- a/tests/model/test_map_value_setter.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from pysdmx.model import ValueSetter - - -@pytest.fixture() -def target(): - return "CONF_STATUS" - - -@pytest.fixture() -def value(): - return "C" - - -def test_default_instantiation(target, value): - m = ValueSetter(target, value) - - assert m.target == target - assert m.value == value - assert m.is_fixed is True - - -def test_full_instantiation(target, value): - m = ValueSetter(target, value, False) - - assert m.target == target - assert m.value == value - assert m.is_fixed is False - - -def test_immutable(target, value): - m = ValueSetter(target, value) - with pytest.raises(AttributeError): - m.is_fixed = False - - -def test_equal(target, value): - m1 = ValueSetter(target, value) - m2 = ValueSetter(target, value) - - assert m1 == m2 - - -def test_not_equal(target, value): - m1 = ValueSetter(target, value) - m2 = ValueSetter(target, value, False) - - assert m1 != m2