diff --git a/.github/workflows/Pipeline.yml b/.github/workflows/Pipeline.yml index 1bce18c..91dc81e 100644 --- a/.github/workflows/Pipeline.yml +++ b/.github/workflows/Pipeline.yml @@ -9,7 +9,7 @@ on: jobs: Pipeline: - uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@r2 + uses: pyTooling/Actions/.github/workflows/CompletePipeline.yml@dev with: package_namespace: pyEDAA package_name: IPXACT diff --git a/.gitignore b/.gitignore index 0dcbdd7..0b71bfc 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ coverage.xml /report/unit # setuptools -/build/**/*.* +/build/**/* /dist/**/*.* /*.egg-info diff --git a/.gitmodules b/.gitmodules index e842284..184621f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "lib/schema"] - path = lib/schema + path = pyEDAA/IPXACT/Schema url = ../IPXACT-Schema.git diff --git a/lib/schema b/lib/schema deleted file mode 160000 index 60d5e03..0000000 --- a/lib/schema +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 60d5e03b0abd1f62b2c024082e576d0b0a439a15 diff --git a/pyEDAA/IPXACT/Design.py b/pyEDAA/IPXACT/Design.py index 77d7215..2de342b 100644 --- a/pyEDAA/IPXACT/Design.py +++ b/pyEDAA/IPXACT/Design.py @@ -49,9 +49,12 @@ def __init__(self, vlnv: Vlnv, description: str): self._adHocConnections = [] def AddItem(self, item) -> None: - if isinstance(item, ComponentInstance): self._componentInstances.append(item) - elif isinstance(item, Interconnection): self._interconnections.append(item) - elif isinstance(item, AdHocConnection): self._adHocConnections.append(item) + if isinstance(item, ComponentInstance): + self._componentInstances.append(item) + elif isinstance(item, Interconnection): + self._interconnections.append(item) + elif isinstance(item, AdHocConnection): + self._adHocConnections.append(item) else: raise ValueError() diff --git a/pyEDAA/IPXACT/DesignConfiguration.py b/pyEDAA/IPXACT/DesignConfiguration.py index b42dd56..978e03f 100644 --- a/pyEDAA/IPXACT/DesignConfiguration.py +++ b/pyEDAA/IPXACT/DesignConfiguration.py @@ -49,9 +49,12 @@ def __init__(self, vlnv: Vlnv, description: str): self._viewConfiguration = None def SetItem(self, item): - if isinstance(item, GeneratorChainConfiguration): self._generatorChainConfiguration = item - elif isinstance(item, InterconnectionConfiguration): self._interconnectionConfiguration = item - elif isinstance(item, ViewConfiguration): self._viewConfiguration = item + if isinstance(item, GeneratorChainConfiguration): + self._generatorChainConfiguration = item + elif isinstance(item, InterconnectionConfiguration): + self._interconnectionConfiguration = item + elif isinstance(item, ViewConfiguration): + self._viewConfiguration = item else: raise ValueError() diff --git a/pyEDAA/IPXACT/Schema b/pyEDAA/IPXACT/Schema new file mode 160000 index 0000000..a52abf1 --- /dev/null +++ b/pyEDAA/IPXACT/Schema @@ -0,0 +1 @@ +Subproject commit a52abf1a4c9e92d40744a634d5e894c7b24d6e8d diff --git a/pyEDAA/IPXACT/__init__.py b/pyEDAA/IPXACT/__init__.py index 86200cc..6d73714 100644 --- a/pyEDAA/IPXACT/__init__.py +++ b/pyEDAA/IPXACT/__init__.py @@ -31,9 +31,16 @@ # """A DOM based IP-XACT implementation for Python.""" from pathlib import Path +from sys import version_info from textwrap import dedent +from typing import Union, Dict + +from pyTooling.Decorators import export, readonly +from pyTooling.Common import getResourceFile, getFullyQualifiedName +from pyTooling.Versioning import SemanticVersion, CalendarVersion + +from . import Schema -from pyTooling.Decorators import export __author__ = "Patrick Lehmann" __email__ = "Paebbels@gmail.com" @@ -43,45 +50,129 @@ @export -class IpxactSchemaStruct: +class IpxactSchema: """Schema descriptor made of version, namespace prefix, URI, URL and local path.""" - Version: str #: Schema version - NamespacePrefix: str #: XML namespace prefix - SchemaUri: str #: Schema URI - SchemaUrl: str #: Schema URL - LocalPath: Path #: Local path - - def __init__(self, version: str, namespacePrefix: str, schemaUri: str, schemaUrl: str, localPath: Path): - """Constructor""" - self.Version = version - self.NamespacePrefix = namespacePrefix - self.SchemaUri = schemaUri - self.SchemaUrl = schemaUrl - self.LocalPath = localPath - - -_SCHEMA_PATH = Path("lib/schema") #version, xmlns, URI URL, Local Path -_IPXACT_10 = IpxactSchemaStruct("1.0", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.0", "", _SCHEMA_PATH / "1.0") -_IPXACT_11 = IpxactSchemaStruct("1.1", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.1", "", _SCHEMA_PATH / "1.1") -_IPXACT_14 = IpxactSchemaStruct("1.4", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4", "", _SCHEMA_PATH / "1.4") -_IPXACT_15 = IpxactSchemaStruct("1.5", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.5", "", _SCHEMA_PATH / "1.5") -_IPXACT_2009 = IpxactSchemaStruct("2009", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009", "", _SCHEMA_PATH / "2009") -_IPXACT_2014 = IpxactSchemaStruct("2014", "ipxact", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd", _SCHEMA_PATH / "2014") - -__VERSION_TABLE__ = { + _version: Union[SemanticVersion, CalendarVersion] #: Schema version + _namespacePrefix: str #: XML namespace prefix + _schemaUri: str #: Schema URI + _schemaUrl: str #: Schema URL + _localPath: Path #: Local path + + def __init__( + self, + version: Union[str, SemanticVersion, CalendarVersion], + xmlNamespacePrefix: str, + schemaUri: str, + schemaUrl: str, + localPath: Path + ) -> None: + """ + Initializes an IP-XACT Schema description. + + :param version: Version of the IP-XACT Schema. + :param xmlNamespacePrefix: XML namespace prefix (````) + :param schemaUri: IP-XACT schema URI + :param schemaUrl: URL the IP-XACT schema definition file (XSD). + :param localPath: Path to the local XSD file. + """ + + if version is None: + raise ValueError(f"Parameter 'version' is None.") + elif isinstance(version, str): + if version.startswith("20"): + self._version = CalendarVersion.Parse(version) + else: + self._version = SemanticVersion.Parse(version) + elif isinstance(version, (SemanticVersion, CalendarVersion)): + self._version = version + else: + ex = TypeError(f"Parameter 'version' is neither a 'SemanticVersion', a 'CalendarVersion' nor a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.") + raise ex + + if xmlNamespacePrefix is None: + raise ValueError(f"Parameter 'namespacePrefix' is None.") + elif not isinstance(xmlNamespacePrefix, str): + ex = TypeError(f"Parameter 'namespacePrefix' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.") + raise ex + + if schemaUri is None: + raise ValueError(f"Parameter 'schemaUri' is None.") + elif not isinstance(schemaUri, str): + ex = TypeError(f"Parameter 'schemaUri' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(schemaUri)}'.") + raise ex + + if schemaUrl is None: + raise ValueError(f"Parameter 'schemaUrl' is None.") + elif not isinstance(schemaUrl, str): + ex = TypeError(f"Parameter 'schemaUrl' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(schemaUrl)}'.") + raise ex + + if localPath is None: + raise ValueError(f"Parameter 'localPath' is None.") + elif not isinstance(localPath, Path): + ex = TypeError(f"Parameter 'localPath' is not a Path.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(localPath)}'.") + raise ex + + self._namespacePrefix = xmlNamespacePrefix + self._schemaUri = schemaUri + self._schemaUrl = schemaUrl + self._localPath = localPath + + @readonly + def Version(self) -> Union[SemanticVersion, CalendarVersion]: + return self._version + + @readonly + def NamespacePrefix(self) -> str: + return self._namespacePrefix + + @readonly + def SchemaUri(self) -> str: + return self._schemaUri + + @readonly + def SchemaUrl(self) -> str: + return self._schemaUrl + + @readonly + def LocalPath(self) -> Path: + return self._localPath + + +# version, xmlns, URI URL, Local Path +_IPXACT_10 = IpxactSchema("1.0", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.0", "", getResourceFile(Schema, "ipxact-1.0/index.xsd")) +_IPXACT_11 = IpxactSchema("1.1", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.1", "", getResourceFile(Schema, "ipxact-1.1/index.xsd")) +_IPXACT_12 = IpxactSchema("1.2", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.2", "", getResourceFile(Schema, "ipxact-1.2/index.xsd")) +_IPXACT_14 = IpxactSchema("1.4", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.4", "", getResourceFile(Schema, "ipxact-1.4/index.xsd")) +_IPXACT_15 = IpxactSchema("1.5", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1.5", "", getResourceFile(Schema, "ipxact-1.5/index.xsd")) +_IPXACT_2009 = IpxactSchema("2009", "spirit", "http://www.spiritconsortium.org/XMLSchema/SPIRIT/1685-2009", "", getResourceFile(Schema, "ieee-1685-2009/index.xsd")) +_IPXACT_2014 = IpxactSchema("2014", "ipxact", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014", "http://www.accellera.org/XMLSchema/IPXACT/1685-2014/index.xsd", getResourceFile(Schema, "ieee-1685-2014/index.xsd")) +_IPXACT_2022 = IpxactSchema("2022", "ipxact", "http://www.accellera.org/XMLSchema/IPXACT/1685-2022", "http://www.accellera.org/XMLSchema/IPXACT/1685-2022/index.xsd", getResourceFile(Schema, "ieee-1685-2022/index.xsd")) + +__VERSION_TABLE__: Dict[str, IpxactSchema] = { '1.0': _IPXACT_10, '1.1': _IPXACT_11, '1.4': _IPXACT_14, '1.5': _IPXACT_15, '2009': _IPXACT_2009, - '2014': _IPXACT_2014 -} #: Dictionary of all IP-XACT versions mapping to :class:`IpxactSchemaStruct` instances. - -__URI_MAP__ = {value.SchemaUri: value for key, value in __VERSION_TABLE__.items()} #: Mapping from schema URIs to :class:`IpxactSchemaStruct` instances. + '2014': _IPXACT_2014, + '2022': _IPXACT_2022 +} #: Dictionary of all IP-XACT versions mapping to :class:`IpxactSchema` instances. +__URI_MAP__: Dict[str, IpxactSchema] = {value.SchemaUri: value for key, value in __VERSION_TABLE__.items()} #: Mapping from schema URIs to :class:`IpxactSchema` instances. -__DEFAULT_VERSION__ = "2014" #: IP-XACT default version +__DEFAULT_VERSION__ = "2022" #: IP-XACT default version __DEFAULT_SCHEMA__ = __VERSION_TABLE__[__DEFAULT_VERSION__] #: IP-XACT default Schema @@ -89,17 +180,76 @@ def __init__(self, version: str, namespacePrefix: str, schemaUri: str, schemaUrl class Vlnv: """VLNV data structure (Vendor, Library, Name, Version) as a unique identifier in IP-XACT.""" - Vendor: str #: Vendor name in a VLNV unique identifier - Library: str #: Library name in a VLNV unique identifier - Name: str #: Component name in a VLNV unique identifier - Version: str #: Version in a VLNV unique identifier + _vendor: str #: Vendor name in a VLNV unique identifier + _library: str #: Library name in a VLNV unique identifier + _name: str #: Component name in a VLNV unique identifier + _version: SemanticVersion #: Version in a VLNV unique identifier + + def __init__(self, vendor: str, library: str, name: str, version: Union[str, SemanticVersion]) -> None: + """ + Initializes the VLNV data structure. + + :param vendor: Vendor name in a VLNV unique identifier + :param library: Library name in a VLNV unique identifier + :param name: Component name in a VLNV unique identifier + :param version: Version in a VLNV unique identifier + """ + + if vendor is None: + raise ValueError(f"Parameter 'vendor' is None.") + elif not isinstance(vendor, str): + ex = TypeError(f"Parameter 'vendor' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(vendor)}'.") + raise ex + + if library is None: + raise ValueError(f"Parameter 'library' is None.") + elif not isinstance(library, str): + ex = TypeError(f"Parameter 'library' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(library)}'.") + raise ex - def __init__(self, vendor, library, name, version): - """Constructor""" - self.Vendor = vendor - self.Library = library - self.Name = name - self.Version = version + if name is None: + raise ValueError(f"Parameter 'name' is None.") + elif not isinstance(name, str): + ex = TypeError(f"Parameter 'name' is not a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") + raise ex + + if version is None: + raise ValueError(f"Parameter 'version' is None.") + elif isinstance(version, str): + self._version = SemanticVersion.Parse(version) + elif isinstance(version, SemanticVersion): + self._version = version + else: + ex = TypeError(f"Parameter 'version' is neither a 'SemanticVersion' nor a string.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(version)}'.") + raise ex + + self._vendor = vendor + self._library = library + self._name = name + + @readonly + def Vendor(self) -> str: + return self._vendor + + @readonly + def Library(self) -> str: + return self._library + + @readonly + def Name(self) -> str: + return self._name + + @readonly + def Version(self) -> SemanticVersion: + return self._version def ToXml(self, indent=1, isVersionedIdentifier=False): """Converts the object's data into XML format.""" @@ -114,7 +264,7 @@ def ToXml(self, indent=1, isVersionedIdentifier=False): else: buffer = """{indent}<{xmlns}:vlnv vendor="{vendor}" library="{library}" name="{name}" version="{version}"/>""" - return buffer.format(indent= "\t" *indent, xmlns=__DEFAULT_SCHEMA__.NamespacePrefix, vendor=self.Vendor, library=self.Library, name=self.Name, version=self.Version) + return buffer.format(indent= "\t" *indent, xmlns=__DEFAULT_SCHEMA__.NamespacePrefix, vendor=self._vendor, library=self._library, name=self._name, version=self._version) @export @@ -123,8 +273,12 @@ class RootElement: _vlnv: Vlnv #: VLNV unique identifier. - def __init__(self, vlnv): - """Base-constructor to set a VLNV field for all derives classes.""" + def __init__(self, vlnv: Vlnv) -> None: + """ + Initializes the RootElement with an VLNV field for all derives classes. + + :param vlnv: VLNV unique identifier. + """ self._vlnv = vlnv @classmethod diff --git a/pyEDAA/IPXACT/py.typed b/pyEDAA/IPXACT/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py index ff137b5..640892f 100644 --- a/setup.py +++ b/setup.py @@ -52,12 +52,16 @@ ], dataFiles={ packageName: [ - str(file) - for file in chain( - Path.cwd().glob("ipxact*/**/*"), - Path.cwd().glob("ieee*/**/*") + str(file.relative_to(Path.cwd() / "pyEDAA/IPXACT")) for file in chain( + Path.cwd().glob("pyEDAA/IPXACT/Schema/ipxact-*/*.xsd"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/ipxact-*/README.md"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/ieee-1685-*/*.xsd"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/ieee-1685-*/README.md"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/ieee-1685-*/LICENSE"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/ieee-1685-*/NOTICE"), + Path.cwd().glob("pyEDAA/IPXACT/Schema/*.md"), + Path.cwd().glob("pyEDAA/IPXACT/py.typed") ) - ] + ], } )) - diff --git a/tests/unit/Schemas.py b/tests/unit/Schemas.py new file mode 100644 index 0000000..472a3db --- /dev/null +++ b/tests/unit/Schemas.py @@ -0,0 +1,63 @@ +# ==================================================================================================================== # +# _____ ____ _ _ ___ ______ __ _ ____ _____ # +# _ __ _ _| ____| _ \ / \ / \ |_ _| _ \ \/ / / \ / ___|_ _| # +# | '_ \| | | | _| | | | |/ _ \ / _ \ | || |_) \ / / _ \| | | | # +# | |_) | |_| | |___| |_| / ___ \ / ___ \ _ | || __// \ / ___ \ |___ | | # +# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___|_| /_/\_\/_/ \_\____| |_| # +# |_| |___/ # +# ==================================================================================================================== # +# Authors: # +# Patrick Lehmann # +# # +# License: # +# ==================================================================================================================== # +# Copyright 2017-2024 Patrick Lehmann - Bötzingen, Germany # +# Copyright 2016-2016 Patrick Lehmann - Dresden, Germany # +# # +# Licensed under the Apache License, Version 2.0 (the "License"); # +# you may not use this file except in compliance with the License. # +# You may obtain a copy of the License at # +# # +# http://www.apache.org/licenses/LICENSE-2.0 # +# # +# Unless required by applicable law or agreed to in writing, software # +# distributed under the License is distributed on an "AS IS" BASIS, # +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # +# See the License for the specific language governing permissions and # +# limitations under the License. # +# # +# SPDX-License-Identifier: Apache-2.0 # +# ==================================================================================================================== # +# +"""Testcases for IP-XACT XSD schema files.""" +from unittest import TestCase + +from lxml.etree import XMLParser, parse, XMLSchema + +from pyEDAA.IPXACT import __VERSION_TABLE__, __DEFAULT_VERSION__, __DEFAULT_SCHEMA__ + +if __name__ == "__main__": # pragma: no cover + print("ERROR: you called a testcase declaration file as an executable module.") + print("Use: 'python -m unitest '") + exit(1) + + +class CheckPaths(TestCase): + def test_DefaultVersion(self): + self.assertEqual(2022, int(__DEFAULT_VERSION__)) + + def test_DefaultSchema(self): + self.assertEqual(2022, __DEFAULT_SCHEMA__.Version) + + def test_VersionTable(self): + print() + + for version, schema in __VERSION_TABLE__.items(): + with self.subTest(version): + self.assertTrue(schema.LocalPath.exists()) + + print(f"Loading schema for '{schema.SchemaUri}' from '{schema.LocalPath}' ...") + schemaParser = XMLParser(ns_clean=True) + schemaRoot = parse(schema.LocalPath, schemaParser) + + junitSchema = XMLSchema(schemaRoot)