Fix(python-generator): ignore invalid namespaces #61

Merged
jakob.scheid merged 5 commits from fix/ignore-invalid-python-namespaces into main 2026-10-04 17:19:13 +02:00
19 changed files with 78 additions and 109 deletions
Showing only changes of commit 0f8f9d3cfa - Show all commits

No files matched your search

+10 -3
View File
@@ -37,7 +37,11 @@ from .definitions import (
PythonFunctionDefinition, PythonFunctionDefinition,
) )
from .docstrings import PythonDocstring from .docstrings import PythonDocstring
from .namespaces import PythonModuleNamespace, PythonPackageNamespace from .namespaces import (
PythonModuleNamespace,
PythonPackageNamespace,
validate_namespace_name,
)
__all__ = [ __all__ = [
'PythonDefinitionDocumentationIncludeSections', 'PythonDefinitionDocumentationIncludeSections',
@@ -926,9 +930,12 @@ class PythonModuleDocumentationGenerator:
), ),
**kwargs, **kwargs,
) -> None: ) -> None:
if module_path.name.count('.') > 1: if not (
module_path.suffix == '.py'
and validate_namespace_name(module_path.stem)
):
raise InvalidNamespaceError( raise InvalidNamespaceError(
'namespace identifier cannot contain more than one dot', 'invalid module file name',
namespace_identifier=module_path.name, namespace_identifier=module_path.name,
) )
+26 -3
View File
@@ -14,6 +14,7 @@
from __future__ import annotations from __future__ import annotations
import keyword
from typing import TypeAlias, Union from typing import TypeAlias, Union
from ...exceptions import ( from ...exceptions import (
@@ -24,27 +25,47 @@ from ...exceptions import (
from ...utils import assert_that_is_instance from ...utils import assert_that_is_instance
__all__ = [ __all__ = [
'validate_namespace_name',
'PythonNamespace', 'PythonNamespace',
'PythonPackageNamespace', 'PythonPackageNamespace',
'PythonModuleNamespace', 'PythonModuleNamespace',
] ]
def validate_namespace_name(name: str) -> bool:
"""
Checks whether a namespace name is valid. Namespace names are valid
if they contain no dots and are valid Python identifiers.
:param name: The namespace name to validate.
:type name: str
:returns: Whether the namespace name is valid.
:rtype: bool
"""
if not isinstance(name, str):
return False
return name.isidentifier() and not keyword.iskeyword(name)
class PythonNamespace: class PythonNamespace:
""" """
The base class for all Python namespaces such as modules or packages. The base class for all Python namespaces such as modules or packages.
:param name: The name of the namespace. :param name: The name of the namespace.
:type name: str :type name: str
:raises InvalidNamespaceError: If the namespace name is invalid.
""" """
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
assert_that_is_instance(name, str) assert_that_is_instance(name, str)
if not name.strip(): if not validate_namespace_name(name):
raise InvalidNamespaceError( raise InvalidNamespaceError(
'invalid namespace', 'invalid namespace', namespace_identifier=name
namespace_identifier=name,
) )
self.name = name self.name = name
@@ -70,6 +91,8 @@ class PythonPackageNamespace(PythonNamespace):
:type name: str :type name: str
:param sub_namespaces: The sub-namespaces. :param sub_namespaces: The sub-namespaces.
:type sub_namespaces: list[PythonNamespace] :type sub_namespaces: list[PythonNamespace]
:raises InvalidNamespaceError: If the namespace name is invalid.
""" """
def __init__( def __init__(
@@ -25,176 +25,134 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError
[ [
( (
pathlib.Path( pathlib.Path(
( ('tests/unit/core/python/_core/invalid_module_names/a.b.c'),
'tests/unit/core/python/_core/fil'
'es_with_more_than_one_dot/a.b.c'
),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.c', 'invalid module file name: a.b.c',
),
(
pathlib.Path(
('tests/unit/core/python/_core/invalid_module_names/a.b.c.d'),
),
None,
InvalidNamespaceError,
'invalid module file name: a.b.c.d',
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/file' 'tests/unit/core/python/_core/i'
's_with_more_than_one_dot/a.b.c.d' 'nvalid_module_names/a.b.c.d.e'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.c.d', 'invalid module file name: a.b.c.d.e',
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files' 'tests/unit/core/python/_core/in'
'_with_more_than_one_dot/a.b.c.d.e' 'valid_module_names/a.b.c.d.e.f'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f',
'namespace identifier cannot cont'
'ain more than one dot: a.b.c.d.e'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_' 'tests/unit/core/python/_core/inv'
'with_more_than_one_dot/a.b.c.d.e.f' 'alid_module_names/a.b.c.d.e.f.g'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f.g',
'namespace identifier cannot conta'
'in more than one dot: a.b.c.d.e.f'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_w' 'tests/unit/core/python/_core/inva'
'ith_more_than_one_dot/a.b.c.d.e.f.g' 'lid_module_names/a.b.c.d.e.f.g.h'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f.g.h',
'namespace identifier cannot contai'
'n more than one dot: a.b.c.d.e.f.g'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_wi' 'tests/unit/core/python/_core/inval'
'th_more_than_one_dot/a.b.c.d.e.f.g.h' 'id_module_names/a.b.c.d.e.f.g.h.py'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f.g.h.py',
'namespace identifier cannot contain'
' more than one dot: a.b.c.d.e.f.g.h'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_wit' 'tests/unit/core/python/_core/inva'
'h_more_than_one_dot/a.b.c.d.e.f.g.h.py' 'lid_module_names/a.b.c.d.e.f.g.py'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f.g.py',
'namespace identifier cannot contain m'
'ore than one dot: a.b.c.d.e.f.g.h.py'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_wi' 'tests/unit/core/python/_core/in'
'th_more_than_one_dot/a.b.c.d.e.f.g.py' 'lid_module_names/a.b.c.d.e.f.py'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.f.py',
'namespace identifier cannot contain '
'more than one dot: a.b.c.d.e.f.g.py'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_w' 'tests/unit/core/python/_core/in'
'ith_more_than_one_dot/a.b.c.d.e.f.py' 'valid_module_names/a.b.c.d.e.py'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.e.py',
'namespace identifier cannot contain'
' more than one dot: a.b.c.d.e.f.py'
),
), ),
( (
pathlib.Path( pathlib.Path(
( (
'tests/unit/core/python/_core/files_' 'tests/unit/core/python/_core/i'
'with_more_than_one_dot/a.b.c.d.e.py' 'nvalid_module_names/a.b.c.d.py'
), ),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.d.py',
'namespace identifier cannot contai'
'n more than one dot: a.b.c.d.e.py'
),
), ),
( (
pathlib.Path( pathlib.Path(
( ('tests/unit/core/python/_core/invalid_module_names/a.b.c.py'),
'tests/unit/core/python/_core/files'
'_with_more_than_one_dot/a.b.c.d.py'
),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.c.py',
'namespace identifier cannot conta'
'in more than one dot: a.b.c.d.py'
),
), ),
( (
pathlib.Path( pathlib.Path(
( ('tests/unit/core/python/_core/invalid_module_names/a.b.py'),
'tests/unit/core/python/_core/file'
's_with_more_than_one_dot/a.b.c.py'
),
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
( 'invalid module file name: a.b.py',
'namespace identifier cannot cont'
'ain more than one dot: a.b.c.py'
),
),
(
pathlib.Path(
(
'tests/unit/core/python/_core/fil'
'es_with_more_than_one_dot/a.b.py'
),
),
None,
InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.py',
), ),
( (
pathlib.Path( pathlib.Path(
@@ -205,7 +163,7 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError
), ),
None, None,
InvalidNamespaceError, InvalidNamespaceError,
'namespace identifier cannot contain more than one dot: a.b.py', 'invalid module file name: a.b.py',
), ),
], ],
) )
@@ -28,7 +28,6 @@ class StrSubclass(str): ...
'namespace,expected_name', 'namespace,expected_name',
[ [
(PythonModuleNamespace('a'), 'a'), (PythonModuleNamespace('a'), 'a'),
(PythonModuleNamespace('1'), '1'),
(PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')), (PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')),
], ],
) )
@@ -41,11 +40,7 @@ def test_PythonModuleNamespace_name_attribute(namespace, expected_name):
[ [
(PythonModuleNamespace('a'), PythonModuleNamespace('a'), True), (PythonModuleNamespace('a'), PythonModuleNamespace('a'), True),
(PythonModuleNamespace('b'), PythonModuleNamespace('b'), True), (PythonModuleNamespace('b'), PythonModuleNamespace('b'), True),
(PythonModuleNamespace('1'), PythonModuleNamespace('1'), True),
(PythonModuleNamespace('42'), PythonModuleNamespace('42'), True),
(PythonModuleNamespace('a'), PythonModuleNamespace('b'), False), (PythonModuleNamespace('a'), PythonModuleNamespace('b'), False),
(PythonModuleNamespace('0'), PythonModuleNamespace('-0'), False),
(PythonModuleNamespace('1'), PythonModuleNamespace('a'), False),
(PythonModuleNamespace('a'), 'a', False), (PythonModuleNamespace('a'), 'a', False),
(PythonModuleNamespace('a'), 'b', False), (PythonModuleNamespace('a'), 'b', False),
(PythonModuleNamespace('a'), PythonPackageNamespace('a', []), False), (PythonModuleNamespace('a'), PythonPackageNamespace('a', []), False),
@@ -61,7 +56,6 @@ def test_PythonModuleNamespace___eq__(namespace1, namespace2, expected):
'namespace,expected', 'namespace,expected',
[ [
(PythonModuleNamespace('a'), "PythonModuleNamespace('a')"), (PythonModuleNamespace('a'), "PythonModuleNamespace('a')"),
(PythonModuleNamespace('1'), "PythonModuleNamespace('1')"),
( (
PythonModuleNamespace(StrSubclass('a')), PythonModuleNamespace(StrSubclass('a')),
"PythonModuleNamespace('a')", "PythonModuleNamespace('a')",
@@ -24,7 +24,6 @@ class StrSubclass(str): ...
'namespace,expected_name', 'namespace,expected_name',
[ [
(PythonNamespace('a'), 'a'), (PythonNamespace('a'), 'a'),
(PythonNamespace('1'), '1'),
(PythonNamespace(StrSubclass('a')), StrSubclass('a')), (PythonNamespace(StrSubclass('a')), StrSubclass('a')),
], ],
) )
@@ -36,7 +35,6 @@ def test_PythonNamespace_name_attribute(namespace, expected_name):
'namespace,expected', 'namespace,expected',
[ [
(PythonNamespace('a'), "PythonNamespace('a')"), (PythonNamespace('a'), "PythonNamespace('a')"),
(PythonNamespace('1'), "PythonNamespace('1')"),
(PythonNamespace(StrSubclass('a')), "PythonNamespace('a')"), (PythonNamespace(StrSubclass('a')), "PythonNamespace('a')"),
], ],
) )
@@ -38,13 +38,6 @@ class StrSubclass(str): ...
), ),
'a', 'a',
), ),
(
PythonPackageNamespace(
'1',
[],
),
'1',
),
( (
PythonPackageNamespace( PythonPackageNamespace(
StrSubclass('a'), StrSubclass('a'),
@@ -291,10 +284,6 @@ def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected):
PythonPackageNamespace('a', []), PythonPackageNamespace('a', []),
"PythonPackageNamespace('a', [])", "PythonPackageNamespace('a', [])",
), ),
(
PythonPackageNamespace('1', []),
"PythonPackageNamespace('1', [])",
),
( (
PythonPackageNamespace(StrSubclass('a'), []), PythonPackageNamespace(StrSubclass('a'), []),
"PythonPackageNamespace('a', [])", "PythonPackageNamespace('a', [])",