generated from jCloud/repository-template
190 lines
6.1 KiB
Python
190 lines
6.1 KiB
Python
# 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
|