Skip to content

Commit 4048bfc

Browse files
committed
fix(tests): fix logging registration in stitched builds
- Move apathetic_logging import after runtime_swap() in conftest.py - Use stitched version of apathetic_logging when available in sys.modules - Change all tests to use 'import apathetic_logging as mod_logging' format - Add debug traces to show which logging module is being used - Update poetry.lock to pin apathetic-logging to 0.3.2
1 parent b7dc2a9 commit 4048bfc

File tree

8 files changed

+792
-73
lines changed

8 files changed

+792
-73
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
📘 **[Roadmap](./ROADMAP.md)** · 📝 **[Release Notes](https://github.com/apathetic-tools/python-utils/releases)**
88

99
**Grab bag of helpers for Apathetic projects.**
10-
*When stdlib isn't enough.*
10+
*When stdlib just isn't enough.*
1111

1212
*Apathetic Python Utils* provides a lightweight, dependency-free collection of utility functions designed for CLI tools. It includes helpers for file loading, path manipulation, system detection, text processing, type checking, pattern matching, and more.
1313

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ permalink: /
77
# Apathetic Python Utils ⚙️
88

99
**Grab bag of helpers for Apathetic projects.**
10-
*When stdlib isn't enough.*
10+
*When stdlib just isn't enough.*
1111

1212
*Apathetic Python Utils* provides a lightweight, dependency-free collection of utility functions designed for CLI tools. It includes helpers for file loading, path manipulation, system detection, text processing, type checking, pattern matching, and more.
1313

poetry.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ readme = "README.md"
1111
license-files = ["LICENSE"]
1212
requires-python = ">=3.10"
1313
dependencies = [
14-
"apathetic-logging>=0.2.2,<2.0.0"
14+
"apathetic-logging (>=0.3.2,<2.0.0)"
1515
]
1616
keywords = ["library", "utilities", "utils"]
1717
classifiers = [

src/apathetic_utils/modules.py

Lines changed: 125 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -145,16 +145,77 @@ def detect_packages_from_files( # noqa: C901, PLR0912, PLR0915
145145
detected: set[str] = set()
146146
parent_dirs: list[Path] = []
147147
seen_parents: set[Path] = set()
148+
# Track which packages were detected via source_bases
149+
# (for namespace package detection)
150+
detected_via_source_bases: set[str] = set()
148151

149152
# Detect packages from files
150153
for file_path in file_paths:
154+
file_path_resolved = file_path.resolve()
151155
pkg_root = ApatheticUtils_Internal_Modules._find_package_root_for_file(
152156
file_path, source_bases=source_bases
153157
)
154158
if pkg_root:
159+
# Check if this package was detected via source_bases
160+
# (by checking if file is under any source_base and no __init__.py)
161+
detected_via_sb = False
162+
if source_bases:
163+
# Check if file is under a source_base
164+
for base_str in source_bases:
165+
base_path = Path(base_str).resolve()
166+
try:
167+
rel_path = file_path_resolved.relative_to(base_path)
168+
# If file is in a subdirectory of base, check __init__.py
169+
if (
170+
len(rel_path.parts) > 1
171+
and not (pkg_root / "__init__.py").exists()
172+
):
173+
# No __init__.py, so detected via source_bases
174+
detected_via_sb = True
175+
break
176+
except ValueError:
177+
continue
178+
155179
# Extract package name from directory name
156180
pkg_name = pkg_root.name
157181
detected.add(pkg_name)
182+
if detected_via_sb:
183+
detected_via_source_bases.add(pkg_name)
184+
185+
# For source_bases, also detect nested packages (all directory levels)
186+
if detected_via_sb and source_bases:
187+
# Find which base this file is under
188+
for base_str in source_bases:
189+
base_path = Path(base_str).resolve()
190+
try:
191+
rel_path = file_path_resolved.relative_to(base_path)
192+
# Detect all directory levels between base and file
193+
# (excluding the file itself and the base)
194+
# More than base + first level + file
195+
MIN_NESTED_PARTS = 3
196+
if len(rel_path.parts) >= MIN_NESTED_PARTS:
197+
# Walk from base to file, detecting intermediate dirs
198+
current = base_path
199+
for part in rel_path.parts[:-1]: # Exclude filename
200+
current = current / part
201+
if current.is_dir() and current != pkg_root:
202+
nested_pkg_name = current.name
203+
if (
204+
nested_pkg_name
205+
and nested_pkg_name != package_name
206+
):
207+
detected.add(nested_pkg_name)
208+
detected_via_source_bases.add(
209+
nested_pkg_name
210+
)
211+
logger.trace(
212+
"[PKG_DETECT] Detected nested package "
213+
"%s from %s",
214+
nested_pkg_name,
215+
file_path,
216+
)
217+
except ValueError:
218+
continue
158219

159220
# Extract parent directory (module base)
160221
parent_dir = pkg_root.parent.resolve()
@@ -165,14 +226,16 @@ def detect_packages_from_files( # noqa: C901, PLR0912, PLR0915
165226
parent_dirs.append(parent_dir)
166227

167228
logger.trace(
168-
"[PKG_DETECT] Detected package %s from %s (root: %s, parent: %s)",
229+
"[PKG_DETECT] Detected package %s from %s "
230+
"(root: %s, parent: %s, via_sb=%s)",
169231
pkg_name,
170232
file_path,
171233
pkg_root,
172234
parent_dir,
235+
detected_via_sb,
173236
)
174237

175-
# Also detect directories in source_bases as packages if they contain
238+
# Also detect directories as namespace packages if they contain
176239
# subdirectories that are packages (namespace packages)
177240
# This must happen BEFORE adding package_name to detected, so we can check
178241
# if base_name == package_name correctly
@@ -195,61 +258,72 @@ def detect_packages_from_files( # noqa: C901, PLR0912, PLR0915
195258
# No common root, use first file's parent
196259
common_root = file_paths[0].parent
197260
break
261+
262+
# Build set of source_bases paths for quick lookup
263+
source_bases_paths: set[Path] = set()
198264
if source_bases:
199265
for base_str in source_bases:
200266
base_path = Path(base_str).resolve()
201-
if not base_path.exists() or not base_path.is_dir():
202-
continue
203-
# Check if this base contains any detected packages as direct children
204-
base_name = base_path.name
205-
# Skip if base is filesystem root, empty name, already detected,
206-
# is package_name, or is the common root of all files
207-
if (
208-
not base_name
209-
or base_name in detected
210-
or base_name == package_name
211-
or base_path == base_path.parent # filesystem root
212-
or (common_root and base_path == common_root.resolve())
213-
):
214-
logger.trace(
215-
"[PKG_DETECT] Skipping base %s: name=%s, in_detected=%s, "
216-
"is_package_name=%s, is_common_root=%s",
217-
base_path,
218-
base_name,
219-
base_name in detected,
220-
base_name == package_name,
221-
common_root and base_path == common_root.resolve(),
222-
)
223-
continue
224-
# Check if any detected package has this base as its parent
225-
for file_path in file_paths:
226-
pkg_root = (
227-
ApatheticUtils_Internal_Modules._find_package_root_for_file(
228-
file_path, source_bases=source_bases
229-
)
230-
)
231-
if pkg_root:
232-
pkg_parent = pkg_root.parent.resolve()
267+
if base_path.exists() and base_path.is_dir():
268+
source_bases_paths.add(base_path)
269+
270+
# Check all detected packages' parent directories to see if they should
271+
# be detected as namespace packages
272+
# Only detect namespace packages when using source_bases
273+
if source_bases:
274+
checked_parents: set[Path] = set()
275+
for file_path in file_paths:
276+
file_path_resolved = file_path.resolve()
277+
pkg_root = ApatheticUtils_Internal_Modules._find_package_root_for_file(
278+
file_path, source_bases=source_bases
279+
)
280+
if pkg_root:
281+
pkg_name = pkg_root.name
282+
# Only check parents if this package was detected via source_bases
283+
if pkg_name not in detected_via_source_bases:
284+
continue
285+
286+
pkg_parent = pkg_root.parent.resolve()
287+
# Skip if we've already checked this parent
288+
if pkg_parent in checked_parents:
289+
continue
290+
checked_parents.add(pkg_parent)
291+
292+
# Skip if parent is filesystem root
293+
if pkg_parent == pkg_parent.parent:
294+
continue
295+
296+
parent_name = pkg_parent.name
297+
# Skip if empty name, already detected, is package_name,
298+
# is the common root, or is in source_bases
299+
if (
300+
not parent_name
301+
or parent_name in detected
302+
or parent_name == package_name
303+
or (common_root and pkg_parent == common_root.resolve())
304+
or pkg_parent in source_bases_paths
305+
):
233306
logger.trace(
234-
"[PKG_DETECT] Checking base: %s (base_path=%s), "
235-
"pkg_root=%s, pkg_parent=%s, match=%s",
236-
base_name,
237-
base_path,
238-
pkg_root,
307+
"[PKG_DETECT] Skipping parent %s: name=%s, in_detected=%s, "
308+
"is_package_name=%s, is_common_root=%s, in_source_bases=%s",
239309
pkg_parent,
240-
pkg_parent == base_path,
310+
parent_name,
311+
parent_name in detected,
312+
parent_name == package_name,
313+
common_root and pkg_parent == common_root.resolve(),
314+
pkg_parent in source_bases_paths,
241315
)
242-
if pkg_parent == base_path:
243-
# This base contains a detected package,
244-
# so it's also a package
245-
detected.add(base_name)
246-
logger.trace(
247-
"[PKG_DETECT] Detected base directory as package: %s "
248-
"(contains package: %s)",
249-
base_name,
250-
pkg_root.name,
251-
)
252-
break
316+
continue
317+
318+
# This parent contains a detected package, so it's also a package
319+
detected.add(parent_name)
320+
detected_via_source_bases.add(parent_name)
321+
logger.trace(
322+
"[PKG_DETECT] Detected parent directory as namespace package: "
323+
"%s (contains package: %s)",
324+
parent_name,
325+
pkg_root.name,
326+
)
253327

254328
# Always include configured package (for fallback and multi-package scenarios)
255329
detected.add(package_name)

tests/00_tooling/test_pytest__runtime_mode_swap.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
import sys
2323
from pathlib import Path
2424

25+
import apathetic_logging as mod_logging
2526
import pytest
26-
from apathetic_logging import makeSafeTrace
2727

2828
import apathetic_utils.runtime as amod_utils_runtime
2929
from tests.utils import PROGRAM_PACKAGE, PROGRAM_SCRIPT, PROJ_ROOT
@@ -37,7 +37,11 @@
3737
# Helpers
3838
# ---------------------------------------------------------------------------
3939

40-
safeTrace = makeSafeTrace("🪞")
40+
safeTrace = mod_logging.makeSafeTrace("🪞")
41+
42+
# Debug: show which apathetic_logging module we're using in the test
43+
mod_logging_source = getattr(mod_logging, "__file__", "unknown")
44+
safeTrace(f"🔍 test: Using apathetic_logging from: {mod_logging_source}")
4145

4246
SRC_ROOT = PROJ_ROOT / "src"
4347
DIST_ROOT = PROJ_ROOT / "dist"

0 commit comments

Comments
 (0)