tina4-python 3.10.60__tar.gz → 3.10.66__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (144) hide show
  1. {tina4_python-3.10.60 → tina4_python-3.10.66}/PKG-INFO +1 -1
  2. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/__init__.py +1 -1
  3. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/dev_admin/metrics.py +70 -22
  4. {tina4_python-3.10.60 → tina4_python-3.10.66}/.gitignore +0 -0
  5. {tina4_python-3.10.60 → tina4_python-3.10.66}/README.md +0 -0
  6. {tina4_python-3.10.60 → tina4_python-3.10.66}/pyproject.toml +0 -0
  7. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/CLAUDE.md +0 -0
  8. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/HtmlElement.py +0 -0
  9. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/Testing.py +0 -0
  10. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/ai/__init__.py +0 -0
  11. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/api/__init__.py +0 -0
  12. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/auth/__init__.py +0 -0
  13. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/cache/__init__.py +0 -0
  14. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/cli/__init__.py +0 -0
  15. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/container/__init__.py +0 -0
  16. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/__init__.py +0 -0
  17. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/cache.py +0 -0
  18. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/constants.py +0 -0
  19. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/events.py +0 -0
  20. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/middleware.py +0 -0
  21. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/request.py +0 -0
  22. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/response.py +0 -0
  23. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/router.py +0 -0
  24. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/core/server.py +0 -0
  25. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/crud/__init__.py +0 -0
  26. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/__init__.py +0 -0
  27. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/adapter.py +0 -0
  28. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/connection.py +0 -0
  29. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/firebird.py +0 -0
  30. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/mongodb.py +0 -0
  31. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/mssql.py +0 -0
  32. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/mysql.py +0 -0
  33. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/odbc.py +0 -0
  34. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/postgres.py +0 -0
  35. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/database/sqlite.py +0 -0
  36. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/debug/__init__.py +0 -0
  37. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/debug/error_overlay.py +0 -0
  38. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/dev_admin/__init__.py +0 -0
  39. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/dev_reload.py +0 -0
  40. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/dotenv/__init__.py +0 -0
  41. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/frond/FROND.md +0 -0
  42. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/frond/__init__.py +0 -0
  43. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/frond/engine.py +0 -0
  44. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/auth/meta.json +0 -0
  45. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
  46. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/database/meta.json +0 -0
  47. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
  48. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/error-overlay/meta.json +0 -0
  49. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
  50. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/orm/meta.json +0 -0
  51. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
  52. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
  53. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/queue/meta.json +0 -0
  54. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
  55. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/rest-api/meta.json +0 -0
  56. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
  57. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/templates/meta.json +0 -0
  58. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
  59. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
  60. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/graphql/__init__.py +0 -0
  61. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/i18n/__init__.py +0 -0
  62. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/mcp/__init__.py +0 -0
  63. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/mcp/protocol.py +0 -0
  64. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/mcp/tools.py +0 -0
  65. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/messenger/__init__.py +0 -0
  66. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/migration/__init__.py +0 -0
  67. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/migration/runner.py +0 -0
  68. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/orm/__init__.py +0 -0
  69. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/orm/fields.py +0 -0
  70. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/orm/model.py +0 -0
  71. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/css/tina4.css +0 -0
  72. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/css/tina4.min.css +0 -0
  73. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/favicon.ico +0 -0
  74. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/images/logo.svg +0 -0
  75. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
  76. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/js/frond.min.js +0 -0
  77. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
  78. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/js/tina4.min.js +0 -0
  79. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/js/tina4js.min.js +0 -0
  80. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/swagger/index.html +0 -0
  81. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
  82. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/query_builder/__init__.py +0 -0
  83. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/queue/__init__.py +0 -0
  84. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/queue_backends/__init__.py +0 -0
  85. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/queue_backends/kafka_backend.py +0 -0
  86. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/queue_backends/mongo_backend.py +0 -0
  87. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
  88. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/__init__.py +0 -0
  89. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_alerts.scss +0 -0
  90. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_badges.scss +0 -0
  91. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_buttons.scss +0 -0
  92. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_cards.scss +0 -0
  93. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_forms.scss +0 -0
  94. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_grid.scss +0 -0
  95. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_modals.scss +0 -0
  96. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_nav.scss +0 -0
  97. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_reset.scss +0 -0
  98. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_tables.scss +0 -0
  99. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_typography.scss +0 -0
  100. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_utilities.scss +0 -0
  101. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/_variables.scss +0 -0
  102. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/base.scss +0 -0
  103. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/colors.scss +0 -0
  104. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/scss/tina4css/tina4.scss +0 -0
  105. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/seeder/__init__.py +0 -0
  106. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/service/__init__.py +0 -0
  107. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/session/__init__.py +0 -0
  108. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/session_handlers/__init__.py +0 -0
  109. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/session_handlers/mongodb_handler.py +0 -0
  110. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/session_handlers/redis_handler.py +0 -0
  111. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/session_handlers/valkey_handler.py +0 -0
  112. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/swagger/__init__.py +0 -0
  113. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/components/crud.twig +0 -0
  114. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
  115. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
  116. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/docker/python/Dockerfile +0 -0
  117. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/docker/uv/Dockerfile +0 -0
  118. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/302.twig +0 -0
  119. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/401.twig +0 -0
  120. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/403.twig +0 -0
  121. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/404.twig +0 -0
  122. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/500.twig +0 -0
  123. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/502.twig +0 -0
  124. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/503.twig +0 -0
  125. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/errors/base.twig +0 -0
  126. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/frontend/README.md +0 -0
  127. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/templates/readme.md +0 -0
  128. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/test_client/__init__.py +0 -0
  129. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
  130. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
  131. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
  132. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
  133. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
  134. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
  135. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
  136. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
  137. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
  138. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
  139. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
  140. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
  141. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/validator/__init__.py +0 -0
  142. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/websocket/__init__.py +0 -0
  143. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/websocket/backplane.py +0 -0
  144. {tina4_python-3.10.60 → tina4_python-3.10.66}/tina4_python/wsdl/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tina4-python
3
- Version: 3.10.60
3
+ Version: 3.10.66
4
4
  Summary: Tina4 for Python — 54 built-in features, zero dependencies
5
5
  Author-email: Andre van Zuydam <andrevanzuydam@gmail.com>
6
6
  License: MIT
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
8
8
 
9
9
  One import, everything works.
10
10
  """
11
- __version__ = "3.10.60"
11
+ __version__ = "3.10.66"
12
12
 
13
13
  # ── Route decorators ──
14
14
  from tina4_python.core.router import ( # noqa: E402, F401
@@ -15,6 +15,11 @@ import hashlib
15
15
  from pathlib import Path
16
16
 
17
17
 
18
+ # ── Scan root tracking ────────────────────────────────────────
19
+ # Stores the resolved root so file_detail() can locate framework files.
20
+ _last_scan_root: str = ""
21
+
22
+
18
23
  # ── Quick Metrics ──────────────────────────────────────────────
19
24
 
20
25
 
@@ -24,12 +29,15 @@ def _resolve_root(root: str = "src") -> str:
24
29
  If src/ has Python files, scan the user's project code.
25
30
  Otherwise, scan the framework itself — so the bubble chart is never empty.
26
31
  """
32
+ global _last_scan_root
27
33
  src = Path(root)
28
34
  if src.exists() and list(src.rglob("*.py")):
35
+ _last_scan_root = str(Path(root).resolve())
29
36
  return root
30
37
  # Fallback: scan the framework package
31
38
  import tina4_python
32
39
  framework_dir = str(Path(tina4_python.__file__).parent)
40
+ _last_scan_root = framework_dir
33
41
  return framework_dir
34
42
 
35
43
 
@@ -105,7 +113,7 @@ def quick_metrics(root: str = "src") -> dict:
105
113
  total_functions += functions
106
114
 
107
115
  file_details.append({
108
- "path": str(f.relative_to(".")),
116
+ "path": str(f.relative_to(root_path)),
109
117
  "loc": loc,
110
118
  "blank": blank,
111
119
  "comment": comment,
@@ -211,7 +219,10 @@ def full_analysis(root: str = "src") -> dict:
211
219
  except (SyntaxError, OSError):
212
220
  continue
213
221
 
214
- rel_path = str(f.relative_to("."))
222
+ try:
223
+ rel_path = str(f.relative_to(root_path))
224
+ except ValueError:
225
+ rel_path = str(f.relative_to(root_path.parent)) if root_path.parent != f else f.name
215
226
  lines = source.splitlines()
216
227
  loc = sum(1 for l in lines if l.strip() and not l.strip().startswith("#"))
217
228
 
@@ -327,6 +338,12 @@ def full_analysis(root: str = "src") -> dict:
327
338
  def file_detail(file_path: str) -> dict:
328
339
  """Detailed metrics for a single file."""
329
340
  p = Path(file_path)
341
+ if not p.exists() and _last_scan_root:
342
+ # Try resolving relative to the last scan root (framework mode)
343
+ candidate = Path(_last_scan_root) / file_path
344
+ if candidate.exists():
345
+ p = candidate
346
+ file_path = str(p)
330
347
  if not p.exists():
331
348
  return {"error": f"File not found: {file_path}"}
332
349
 
@@ -495,34 +512,65 @@ def _count_halstead(node: ast.AST, stats: dict):
495
512
  def _has_matching_test(rel_path: str) -> bool:
496
513
  """Check if a source file has a matching test file.
497
514
 
498
- Looks for common test file patterns and also scans any test file
499
- that imports the module by name.
515
+ Three-stage detection:
516
+ 1. Filename matching test_module.py, module_test.py, module_spec.py
517
+ 2. Path matching — any test file referencing the full import path
518
+ 3. Content matching — any test file mentioning the module or class name
500
519
  """
520
+ import re
521
+
501
522
  p = Path(rel_path)
502
- module = p.stem # e.g. "auth" from "tina4_python/auth/__init__.py"
523
+ module = p.stem # e.g. "sqlite" from "tina4_python/database/sqlite.py"
503
524
  if module == "__init__":
504
525
  module = p.parent.name # use parent dir name
505
- # Check common test file patterns
506
- test_patterns = [
507
- Path("tests") / f"test_{module}.py",
508
- Path("tests") / f"test_{module}s.py",
509
- Path("test") / f"test_{module}.py",
510
- Path("spec") / f"test_{module}.py",
511
- Path("tests") / f"{module}_test.py",
526
+
527
+ # Build the full dotted import path for deeper matching
528
+ # e.g. "tina4_python/database/sqlite.py" "tina4_python.database.sqlite"
529
+ parts = list(p.with_suffix("").parts)
530
+ dotted_path = ".".join(parts) # "tina4_python.database.sqlite"
531
+
532
+ # Also track the parent package name for broader matching
533
+ # e.g. "database" from "tina4_python/database/sqlite.py"
534
+ parent_module = p.parent.name if len(p.parts) > 1 else ""
535
+
536
+ # Stage 1: Filename patterns
537
+ test_dirs = [Path("tests"), Path("test"), Path("spec")]
538
+ for td in test_dirs:
539
+ patterns = [
540
+ td / f"test_{module}.py",
541
+ td / f"test_{module}s.py",
542
+ td / f"{module}_test.py",
543
+ td / f"{module}_spec.py",
544
+ ]
545
+ # Also check parent-named tests (test_database.py covers database/sqlite.py)
546
+ if parent_module and parent_module != module:
547
+ patterns.extend([
548
+ td / f"test_{parent_module}.py",
549
+ td / f"test_{parent_module}s.py",
550
+ td / f"{parent_module}_test.py",
551
+ ])
552
+ if any(tp.exists() for tp in patterns):
553
+ return True
554
+
555
+ # Stage 2+3: Content scan — check if ANY test file references this module
556
+ search_terms = [
557
+ re.compile(rf'\b{re.escape(module)}\b', re.IGNORECASE),
512
558
  ]
513
- if any(tp.exists() for tp in test_patterns):
514
- return True
515
- # Grep-based: check if any test file imports this module
516
- import re
517
- import_patterns = [re.compile(rf'\bimport\s+{re.escape(module)}\b'),
518
- re.compile(rf'\bfrom\s+{re.escape(module)}\b')]
519
- for test_dir in (Path("tests"), Path("test"), Path("spec")):
520
- if not test_dir.is_dir():
559
+ # Add dotted path patterns for import matching
560
+ if dotted_path:
561
+ search_terms.append(re.compile(rf'{re.escape(dotted_path)}'))
562
+ # Add class name guesses (CamelCase from snake_case module name)
563
+ class_name = "".join(w.capitalize() for w in module.split("_"))
564
+ if class_name != module:
565
+ search_terms.append(re.compile(rf'\b{re.escape(class_name)}\b'))
566
+
567
+ for td in test_dirs:
568
+ if not td.is_dir():
521
569
  continue
522
- for test_file in test_dir.glob("*.py"):
570
+ for test_file in td.rglob("*.py"):
523
571
  try:
524
572
  content = test_file.read_text(encoding="utf-8", errors="ignore")
525
- if any(pat.search(content) for pat in import_patterns):
573
+ if any(pat.search(content) for pat in search_terms):
526
574
  return True
527
575
  except OSError:
528
576
  pass
File without changes