From 0f8f9d3cfa2b6f0ea747195306d5f8d9fb93a65d Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 4 Oct 2026 16:39:31 +0200 Subject: [PATCH 1/5] feat(python-namespaces)!: add validation for namespace names Added more strict validation for namespaces names to PythonNamespace than before and updated error messages. All tests depending on this were updated to the new behavior. --- src/jcloud_docsgen/core/python/_core.py | 13 +- src/jcloud_docsgen/core/python/namespaces.py | 29 +++- .../a.b.c | 0 .../a.b.c.d | 0 .../a.b.c.d.e | 0 .../a.b.c.d.e.f | 0 .../a.b.c.d.e.f.g | 0 .../a.b.c.d.e.f.g.h | 0 .../a.b.c.d.e.f.g.h.py | 0 .../a.b.c.d.e.f.g.py | 0 .../a.b.c.d.e.f.py | 0 .../a.b.c.d.e.py | 0 .../a.b.c.d.py | 0 .../a.b.c.py | 0 .../a.b.py | 0 ...test_PythonModuleDocumentationGenerator.py | 126 ++++++------------ .../namespaces/test_PythonModuleNamespace.py | 6 - .../python/namespaces/test_PythonNamespace.py | 2 - .../namespaces/test_PythonPackageNamespace.py | 11 -- 19 files changed, 78 insertions(+), 109 deletions(-) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f.g (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f.g.h (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f.g.h.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f.g.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.f.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.e.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.d.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.c.py (100%) rename tests/unit/core/python/_core/{files_with_more_than_one_dot => invalid_module_names}/a.b.py (100%) diff --git a/src/jcloud_docsgen/core/python/_core.py b/src/jcloud_docsgen/core/python/_core.py index 78b3f14..5e2cd6b 100644 --- a/src/jcloud_docsgen/core/python/_core.py +++ b/src/jcloud_docsgen/core/python/_core.py @@ -37,7 +37,11 @@ from .definitions import ( PythonFunctionDefinition, ) from .docstrings import PythonDocstring -from .namespaces import PythonModuleNamespace, PythonPackageNamespace +from .namespaces import ( + PythonModuleNamespace, + PythonPackageNamespace, + validate_namespace_name, +) __all__ = [ 'PythonDefinitionDocumentationIncludeSections', @@ -926,9 +930,12 @@ class PythonModuleDocumentationGenerator: ), **kwargs, ) -> None: - if module_path.name.count('.') > 1: + if not ( + module_path.suffix == '.py' + and validate_namespace_name(module_path.stem) + ): raise InvalidNamespaceError( - 'namespace identifier cannot contain more than one dot', + 'invalid module file name', namespace_identifier=module_path.name, ) diff --git a/src/jcloud_docsgen/core/python/namespaces.py b/src/jcloud_docsgen/core/python/namespaces.py index 706a816..4391cff 100644 --- a/src/jcloud_docsgen/core/python/namespaces.py +++ b/src/jcloud_docsgen/core/python/namespaces.py @@ -14,6 +14,7 @@ from __future__ import annotations +import keyword from typing import TypeAlias, Union from ...exceptions import ( @@ -24,27 +25,47 @@ from ...exceptions import ( from ...utils import assert_that_is_instance __all__ = [ + 'validate_namespace_name', 'PythonNamespace', 'PythonPackageNamespace', '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: """ The base class for all Python namespaces such as modules or packages. :param name: The name of the namespace. :type name: str + + :raises InvalidNamespaceError: If the namespace name is invalid. """ def __init__(self, name: str) -> None: assert_that_is_instance(name, str) - if not name.strip(): + if not validate_namespace_name(name): raise InvalidNamespaceError( - 'invalid namespace', - namespace_identifier=name, + 'invalid namespace', namespace_identifier=name ) self.name = name @@ -70,6 +91,8 @@ class PythonPackageNamespace(PythonNamespace): :type name: str :param sub_namespaces: The sub-namespaces. :type sub_namespaces: list[PythonNamespace] + + :raises InvalidNamespaceError: If the namespace name is invalid. """ def __init__( diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c b/tests/unit/core/python/_core/invalid_module_names/a.b.c similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c rename to tests/unit/core/python/_core/invalid_module_names/a.b.c diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.h similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.h diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.h.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.h.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.h.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.g.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.g.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.f.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.f.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.e.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.e.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.d.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.d.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.d.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.py b/tests/unit/core/python/_core/invalid_module_names/a.b.c.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.c.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.c.py diff --git a/tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.py b/tests/unit/core/python/_core/invalid_module_names/a.b.py similarity index 100% rename from tests/unit/core/python/_core/files_with_more_than_one_dot/a.b.py rename to tests/unit/core/python/_core/invalid_module_names/a.b.py diff --git a/tests/unit/core/python/_core/test_PythonModuleDocumentationGenerator.py b/tests/unit/core/python/_core/test_PythonModuleDocumentationGenerator.py index 33c63b7..03921c9 100644 --- a/tests/unit/core/python/_core/test_PythonModuleDocumentationGenerator.py +++ b/tests/unit/core/python/_core/test_PythonModuleDocumentationGenerator.py @@ -25,176 +25,134 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError [ ( pathlib.Path( - ( - 'tests/unit/core/python/_core/fil' - 'es_with_more_than_one_dot/a.b.c' - ), + ('tests/unit/core/python/_core/invalid_module_names/a.b.c'), ), None, 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( ( - 'tests/unit/core/python/_core/file' - 's_with_more_than_one_dot/a.b.c.d' + 'tests/unit/core/python/_core/i' + 'nvalid_module_names/a.b.c.d.e' ), ), None, InvalidNamespaceError, - 'namespace identifier cannot contain more than one dot: a.b.c.d', + 'invalid module file name: a.b.c.d.e', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files' - '_with_more_than_one_dot/a.b.c.d.e' + 'tests/unit/core/python/_core/in' + 'valid_module_names/a.b.c.d.e.f' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot cont' - 'ain more than one dot: a.b.c.d.e' - ), + 'invalid module file name: a.b.c.d.e.f', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_' - 'with_more_than_one_dot/a.b.c.d.e.f' + 'tests/unit/core/python/_core/inv' + 'alid_module_names/a.b.c.d.e.f.g' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot conta' - 'in more than one dot: a.b.c.d.e.f' - ), + 'invalid module file name: a.b.c.d.e.f.g', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_w' - 'ith_more_than_one_dot/a.b.c.d.e.f.g' + 'tests/unit/core/python/_core/inva' + 'lid_module_names/a.b.c.d.e.f.g.h' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contai' - 'n more than one dot: a.b.c.d.e.f.g' - ), + 'invalid module file name: a.b.c.d.e.f.g.h', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_wi' - 'th_more_than_one_dot/a.b.c.d.e.f.g.h' + 'tests/unit/core/python/_core/inval' + 'id_module_names/a.b.c.d.e.f.g.h.py' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contain' - ' more than one dot: a.b.c.d.e.f.g.h' - ), + 'invalid module file name: a.b.c.d.e.f.g.h.py', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_wit' - 'h_more_than_one_dot/a.b.c.d.e.f.g.h.py' + 'tests/unit/core/python/_core/inva' + 'lid_module_names/a.b.c.d.e.f.g.py' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contain m' - 'ore than one dot: a.b.c.d.e.f.g.h.py' - ), + 'invalid module file name: a.b.c.d.e.f.g.py', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_wi' - 'th_more_than_one_dot/a.b.c.d.e.f.g.py' + 'tests/unit/core/python/_core/in' + 'lid_module_names/a.b.c.d.e.f.py' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contain ' - 'more than one dot: a.b.c.d.e.f.g.py' - ), + 'invalid module file name: a.b.c.d.e.f.py', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_w' - 'ith_more_than_one_dot/a.b.c.d.e.f.py' + 'tests/unit/core/python/_core/in' + 'valid_module_names/a.b.c.d.e.py' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contain' - ' more than one dot: a.b.c.d.e.f.py' - ), + 'invalid module file name: a.b.c.d.e.py', ), ( pathlib.Path( ( - 'tests/unit/core/python/_core/files_' - 'with_more_than_one_dot/a.b.c.d.e.py' + 'tests/unit/core/python/_core/i' + 'nvalid_module_names/a.b.c.d.py' ), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot contai' - 'n more than one dot: a.b.c.d.e.py' - ), + 'invalid module file name: a.b.c.d.py', ), ( pathlib.Path( - ( - 'tests/unit/core/python/_core/files' - '_with_more_than_one_dot/a.b.c.d.py' - ), + ('tests/unit/core/python/_core/invalid_module_names/a.b.c.py'), ), None, InvalidNamespaceError, - ( - 'namespace identifier cannot conta' - 'in more than one dot: a.b.c.d.py' - ), + 'invalid module file name: a.b.c.py', ), ( pathlib.Path( - ( - 'tests/unit/core/python/_core/file' - 's_with_more_than_one_dot/a.b.c.py' - ), + ('tests/unit/core/python/_core/invalid_module_names/a.b.py'), ), None, InvalidNamespaceError, - ( - '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', + 'invalid module file name: a.b.py', ), ( pathlib.Path( @@ -205,7 +163,7 @@ from src.jcloud_docsgen.exceptions import InvalidNamespaceError ), None, InvalidNamespaceError, - 'namespace identifier cannot contain more than one dot: a.b.py', + 'invalid module file name: a.b.py', ), ], ) diff --git a/tests/unit/core/python/namespaces/test_PythonModuleNamespace.py b/tests/unit/core/python/namespaces/test_PythonModuleNamespace.py index 53d7e0e..3f86f7f 100644 --- a/tests/unit/core/python/namespaces/test_PythonModuleNamespace.py +++ b/tests/unit/core/python/namespaces/test_PythonModuleNamespace.py @@ -28,7 +28,6 @@ class StrSubclass(str): ... 'namespace,expected_name', [ (PythonModuleNamespace('a'), 'a'), - (PythonModuleNamespace('1'), '1'), (PythonModuleNamespace(StrSubclass('a')), StrSubclass('a')), ], ) @@ -41,11 +40,7 @@ def test_PythonModuleNamespace_name_attribute(namespace, expected_name): [ (PythonModuleNamespace('a'), PythonModuleNamespace('a'), True), (PythonModuleNamespace('b'), PythonModuleNamespace('b'), True), - (PythonModuleNamespace('1'), PythonModuleNamespace('1'), True), - (PythonModuleNamespace('42'), PythonModuleNamespace('42'), True), (PythonModuleNamespace('a'), PythonModuleNamespace('b'), False), - (PythonModuleNamespace('0'), PythonModuleNamespace('-0'), False), - (PythonModuleNamespace('1'), PythonModuleNamespace('a'), False), (PythonModuleNamespace('a'), 'a', False), (PythonModuleNamespace('a'), 'b', False), (PythonModuleNamespace('a'), PythonPackageNamespace('a', []), False), @@ -61,7 +56,6 @@ def test_PythonModuleNamespace___eq__(namespace1, namespace2, expected): 'namespace,expected', [ (PythonModuleNamespace('a'), "PythonModuleNamespace('a')"), - (PythonModuleNamespace('1'), "PythonModuleNamespace('1')"), ( PythonModuleNamespace(StrSubclass('a')), "PythonModuleNamespace('a')", diff --git a/tests/unit/core/python/namespaces/test_PythonNamespace.py b/tests/unit/core/python/namespaces/test_PythonNamespace.py index e4ee640..ba56d89 100644 --- a/tests/unit/core/python/namespaces/test_PythonNamespace.py +++ b/tests/unit/core/python/namespaces/test_PythonNamespace.py @@ -24,7 +24,6 @@ class StrSubclass(str): ... 'namespace,expected_name', [ (PythonNamespace('a'), 'a'), - (PythonNamespace('1'), '1'), (PythonNamespace(StrSubclass('a')), StrSubclass('a')), ], ) @@ -36,7 +35,6 @@ def test_PythonNamespace_name_attribute(namespace, expected_name): 'namespace,expected', [ (PythonNamespace('a'), "PythonNamespace('a')"), - (PythonNamespace('1'), "PythonNamespace('1')"), (PythonNamespace(StrSubclass('a')), "PythonNamespace('a')"), ], ) diff --git a/tests/unit/core/python/namespaces/test_PythonPackageNamespace.py b/tests/unit/core/python/namespaces/test_PythonPackageNamespace.py index 1087cf0..30ca226 100644 --- a/tests/unit/core/python/namespaces/test_PythonPackageNamespace.py +++ b/tests/unit/core/python/namespaces/test_PythonPackageNamespace.py @@ -38,13 +38,6 @@ class StrSubclass(str): ... ), 'a', ), - ( - PythonPackageNamespace( - '1', - [], - ), - '1', - ), ( PythonPackageNamespace( StrSubclass('a'), @@ -291,10 +284,6 @@ def test_PythonPackageNamespace___eq__(namespace1, namespace2, expected): PythonPackageNamespace('a', []), "PythonPackageNamespace('a', [])", ), - ( - PythonPackageNamespace('1', []), - "PythonPackageNamespace('1', [])", - ), ( PythonPackageNamespace(StrSubclass('a'), []), "PythonPackageNamespace('a', [])", -- 2.47.3 From efa2dbf724a2283993b1145fa0e675293213d251 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 4 Oct 2026 16:47:33 +0200 Subject: [PATCH 2/5] test(python-namespaces): add test for namespace name validation --- .../test_validate_namespace_name.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 tests/unit/core/python/namespaces/test_validate_namespace_name.py diff --git a/tests/unit/core/python/namespaces/test_validate_namespace_name.py b/tests/unit/core/python/namespaces/test_validate_namespace_name.py new file mode 100644 index 0000000..a254eb4 --- /dev/null +++ b/tests/unit/core/python/namespaces/test_validate_namespace_name.py @@ -0,0 +1,52 @@ +# 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. + +import pytest + +from src.jcloud_docsgen.core.python.namespaces import validate_namespace_name + + +@pytest.mark.parametrize( + 'name,expected', + [ + ('a', True), + ('namespace', True), + ('match', True), + ('case', True), + ('type', True), + ('_', True), + ('__', True), + ('___', True), + ('_a', True), + ('_a_', True), + ('_a.', False), + ('a.b', False), + ('a-b', False), + ('a.', False), + ('a-', False), + ('.a', False), + ('-a', False), + ('.a.', False), + ('-a-', False), + ('.', False), + ('a-.b', False), + ('"', False), + ('""', False), + ('def', False), + ('class', False), + ('import', False), + ], +) +def test_validate_namespace_name(name, expected): + assert validate_namespace_name(name) == expected -- 2.47.3 From 03fa81f0df5624189f72af21f31b6002950963a7 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 4 Oct 2026 16:54:48 +0200 Subject: [PATCH 3/5] fix(python-generator): ignore invalid namespaces Python modules or packages are now ignored if their name is not a valid Python namespace name. --- src/jcloud_docsgen/core/python/_core.py | 18 ++++++++++++------ .../case02/expected/pkg/a/b.md | 9 +++++++++ .../case02/project/pkg/a-b/c-d.py | 1 + .../case02/project/pkg/a-b/c.d.py | 1 + .../case02/project/pkg/a-b/c.py | 1 + .../case02/project/pkg/a.b/c-d.py | 1 + .../case02/project/pkg/a.b/c.d.py | 1 + .../case02/project/pkg/a.b/c.py | 1 + .../case02/project/pkg/a/b-c.py | 1 + .../case02/project/pkg/a/b.c.py | 1 + .../case02/project/pkg/a/b.py | 1 + .../case02/project/pkg/b-c/.gitkeep | 0 .../case02/project/pkg/b.c/.gitkeep | 0 .../case02/project/pkg/c-d.py | 1 + .../case02/project/pkg/c.d.py | 1 + 15 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/expected/pkg/a/b.md create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c-d.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.d.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c-d.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.d.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b-c.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.c.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b-c/.gitkeep create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b.c/.gitkeep create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c-d.py create mode 100644 tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c.d.py diff --git a/src/jcloud_docsgen/core/python/_core.py b/src/jcloud_docsgen/core/python/_core.py index 5e2cd6b..2df4681 100644 --- a/src/jcloud_docsgen/core/python/_core.py +++ b/src/jcloud_docsgen/core/python/_core.py @@ -1114,12 +1114,18 @@ class PythonDocumentationGenerator: ) for entry in directory.iterdir(): - if entry.is_dir() and ( - entry.parent / (f'{entry.stem}.py') - ).resolve() not in [e.resolve() for e in entry.parent.iterdir()]: - namespace.add(self._namespace(entry)) - elif entry.suffix == '.py': - namespace.add(PythonModuleNamespace(entry.stem)) + try: + if entry.is_dir() and ( + entry.parent / (f'{entry.stem}.py') + ).resolve() not in [ + e.resolve() for e in entry.parent.iterdir() + ]: + namespace.add(self._namespace(entry)) + elif entry.suffix == '.py': + namespace.add(PythonModuleNamespace(entry.stem)) + except InvalidNamespaceError: + # the name of the directory or module is invalid; ignore. + pass return namespace diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/expected/pkg/a/b.md b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/expected/pkg/a/b.md new file mode 100644 index 0000000..3238719 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/expected/pkg/a/b.md @@ -0,0 +1,9 @@ +# Module `pkg.a.b` + +## Function `fn` + +### Function signature + +```python +fn() +``` \ No newline at end of file diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c-d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c-d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c-d.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.d.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a-b/c.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c-d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c-d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c-d.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.d.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a.b/c.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b-c.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b-c.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b-c.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.c.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.c.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.c.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/a/b.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b-c/.gitkeep b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b-c/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b.c/.gitkeep b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/b.c/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c-d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c-d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c-d.py @@ -0,0 +1 @@ +def fn(): ... diff --git a/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c.d.py b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c.d.py new file mode 100644 index 0000000..9b7a678 --- /dev/null +++ b/tests/unit/core/python/_core/test_PythonDocumentationGenerator/test_generate_documentation/case02/project/pkg/c.d.py @@ -0,0 +1 @@ +def fn(): ... -- 2.47.3 From 82e10c5493bbbb4f586d26904d76122126ff43a8 Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 4 Oct 2026 17:04:04 +0200 Subject: [PATCH 4/5] docs(changelog): add Python invalid namespaces ignoring entry --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 01926dc..60de1a9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - Python namespace name conflict when package and module have the same name - Internal namespace name error which occured when generating Python documentation with `.` as the project directory +- Ignore Python namespaces with invalid names during documentation generation ## [0.2.0] - 2026-09-28 -- 2.47.3 From a25c66d77b90386f92cadfcf07f4957815ba964e Mon Sep 17 00:00:00 2001 From: Jakob Scheid Date: Sun, 4 Oct 2026 17:06:44 +0200 Subject: [PATCH 5/5] docs(changelog): add entry for stricter Python namespace name validation --- docs/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 60de1a9..7f0d925 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -8,6 +8,10 @@ - Internal namespace name error which occured when generating Python documentation with `.` as the project directory - Ignore Python namespaces with invalid names during documentation generation +### Changed + +- Stricter Python namespace name validation + ## [0.2.0] - 2026-09-28 ### Added -- 2.47.3