Files
jcloud-docsgen/src/jcloud_docsgen/core/python/namespaces.py
T
jakob.scheid 9941649841 feat(python-generator): check type at package namespace comparision
Added type check to the Python package namespace comparision function.
2026-09-27 22:07:12 +02:00

227 lines
6.4 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 typing import TypeAlias, Union
from ...exceptions import (
InvalidNamespaceError,
NamespaceExistsError,
NamespaceNotFoundError,
)
from ...utils import assert_that_is_instance
__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 not (isinstance(value, type(self)) and 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 isinstance(value, type(self)) and self.name == value.name