# Copyright 2026 jCloud # 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. from __future__ import annotations from ...exceptions import InvalidNamespaceError, NamespaceNotFoundError, NamespaceExistsError from ...utils import assert_that_is_instance from typing import Union, TypeAlias, Dict, List __all__ = [ 'PythonNamespace', 'PythonPackageNamespace', 'PythonModuleNamespace' ] class PythonNamespace: ''' The base class for all Python namespaces such as modules or packages. :param name: The name of the namespace. :type name: str ''' def __init__(self, name: str) -> None: assert_that_is_instance(name, str) if not name.strip(): raise InvalidNamespaceError('invalid namespace', namespace_identifier = name) self.name = name def __repr__(self) -> str: return f'{type(self).__name__}({self.name!r})' PythonPackageNamespaceDictType: TypeAlias = Dict[str, Union[str, 'PythonPackageNamespaceDictType']] class PythonPackageNamespace(PythonNamespace): ''' A subclass of ``PythonNamespace`` for Python package namespaces. :param name: The name of the namespace. :type name: str :param sub_namespaces: The sub-namespaces. :type sub_namespaces: list[PythonNamespace] ''' def __init__(self, name: str, sub_namespaces: List[PythonNamespace]) -> None: assert_that_is_instance(sub_namespaces, list) super().__init__(name) self._sub_namespaces = sub_namespaces def namespace(self, sub_namespaces: List[str]) -> PythonNamespace: ''' Returns the namespace object with a specific identifier, such as ``['package', 'subpackage', 'module']``. :param sub_namespaces: The identifier of the namespace. :type sub_namespaces: list[str] :raises InvalidNamespaceError: If the namespace identifier is invalid. :raises NamespaceNotFoundError: If the namespace does not exist. :return: The namespace. :rtype: PythonNamespace ''' if not sub_namespaces: raise InvalidNamespaceError('invalid namespace') for sn in sub_namespaces: if not sn: raise InvalidNamespaceError('invalid namespace') sub_namespace = self.__getitem__(sub_namespaces[0]) # package namespace if isinstance(sub_namespace, PythonPackageNamespace): if len(sub_namespaces) == 1: return sub_namespace return sub_namespace.namespace(sub_namespaces[1:]) # module namespace else: return sub_namespace def _sub_namespaces_names(self, package: PythonPackageNamespace) -> PythonPackageNamespaceDictType: ''' Returns the names of all sub-namespaces of the specified package namespace and keeps the package structure. :param package: The package. :type package: PythonPackageNamespace :return: The names if all sub-namespaces. :rtype: PythonPackageNamespaceDictType ''' return { sn.name: self._sub_namespaces_names(sn) if isinstance(sn, PythonPackageNamespace) else sn.name for sn in package._sub_namespaces } def sub_namespace_names(self) -> PythonPackageNamespaceDictType: ''' Returns the names of all sub-namespaces and keeps the package structure. :return: The names if all sub-namespaces. :rtype: PythonPackageNamespaceDictType ''' return self._sub_namespaces_names(self) def __eq__(self, value: PythonPackageNamespace) -> bool: # For performance reasons, is not called self.sub_namespace_names # if the name attribute does not match, which can be quickly # verified if self.name != value.name: return False return value.sub_namespace_names() == self.sub_namespace_names() def __repr__(self) -> str: return type(self).__name__ + repr((self.name, self._sub_namespaces)) def __getitem__(self, name: str) -> PythonNamespace: ''' Returns a namespace with the specified name. :param name: The name of the namespace :type name: str :raises NamespaceNotFoundError: If the namespace does not exist. :return: The namespace :rtype: PythonNamespace ''' for sub in self._sub_namespaces: if sub.name == name: return sub raise NamespaceNotFoundError('no such namespace', namespace_identifier = name) def add(self, namespace: PythonNamespace) -> None: ''' Adds a namespace. :param namespace: The namespace to add. :type namespace: PythonNamespace :raises NamespaceExistsError: If a namespace with the name of the namespace that is attempted to add already exists. ''' try: self.__getitem__(namespace.name) raise NamespaceExistsError('namespace already exists', namespace_identifier = namespace.name) except NamespaceNotFoundError: self._sub_namespaces.append(namespace) @property def sub_namespaces(self) -> List[PythonNamespace]: return self._sub_namespaces class PythonModuleNamespace(PythonNamespace): ''' A subclass of ``PythonNamespace`` for Python module namespaces. :param name: The name of the namespace. :type name: str ''' def __init__(self, name: str) -> None: super().__init__(name) def __eq__(self, value: PythonModuleNamespace) -> bool: return self.name == value.name