splent-cli 1.2.6__tar.gz → 1.2.7__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 (136) hide show
  1. {splent_cli-1.2.6/src/splent_cli.egg-info → splent_cli-1.2.7}/PKG-INFO +1 -1
  2. {splent_cli-1.2.6 → splent_cli-1.2.7}/pyproject.toml +1 -1
  3. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_orphans.py +1 -0
  4. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_status.py +18 -7
  5. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_deps.py +56 -23
  6. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_env.py +13 -5
  7. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_features.py +14 -6
  8. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_pyproject.py +11 -4
  9. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_console.py +8 -3
  10. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_migrate.py +27 -10
  11. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_reset.py +12 -3
  12. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_rollback.py +7 -2
  13. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_seed.py +12 -4
  14. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_status.py +13 -4
  15. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_upgrade.py +9 -2
  16. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/doctor.py +17 -13
  17. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/export_puml.py +187 -97
  18. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_add.py +12 -3
  19. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_attach.py +13 -5
  20. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_clone.py +19 -4
  21. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_contract.py +24 -18
  22. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_create.py +20 -6
  23. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_detach.py +2 -1
  24. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_diff.py +231 -142
  25. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_drift.py +4 -7
  26. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_edit.py +22 -5
  27. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hook_add.py +3 -1
  28. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hook_remove.py +2 -0
  29. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_hooks.py +5 -2
  30. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_list.py +5 -1
  31. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_order.py +40 -15
  32. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_release.py +24 -12
  33. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_remove.py +6 -2
  34. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_rename.py +4 -1
  35. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_status.py +25 -13
  36. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_sync_template.py +3 -9
  37. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_test.py +11 -4
  38. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_upgrade.py +37 -8
  39. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_versions.py +91 -29
  40. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature_compile.py +46 -13
  41. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/linter.py +10 -10
  42. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_create.py +1 -0
  43. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_derive.py +3 -5
  44. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_drift.py +2 -1
  45. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_status.py +28 -8
  46. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_sync.py +15 -5
  47. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_sync_template.py +2 -6
  48. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/tokens.py +1 -3
  49. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_check.py +10 -4
  50. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_fix.py +35 -10
  51. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_utils.py +3 -1
  52. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/version.py +3 -1
  53. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/compose.py +10 -2
  54. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/command_loader.py +3 -1
  55. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/db_utils.py +4 -1
  56. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/feature_installer.py +3 -1
  57. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/feature_utils.py +3 -1
  58. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/lifecycle.py +23 -21
  59. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/manifest.py +5 -1
  60. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/template_drift.py +43 -51
  61. {splent_cli-1.2.6 → splent_cli-1.2.7/src/splent_cli.egg-info}/PKG-INFO +1 -1
  62. {splent_cli-1.2.6 → splent_cli-1.2.7}/LICENSE +0 -0
  63. {splent_cli-1.2.6 → splent_cli-1.2.7}/README.md +0 -0
  64. {splent_cli-1.2.6 → splent_cli-1.2.7}/setup.cfg +0 -0
  65. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/__init__.py +0 -0
  66. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/__main__.py +0 -0
  67. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/cli.py +0 -0
  68. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/__init__.py +0 -0
  69. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/__init__.py +0 -0
  70. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_clear.py +0 -0
  71. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_outdated.py +0 -0
  72. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_prune.py +0 -0
  73. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_size.py +0 -0
  74. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_usage.py +0 -0
  75. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/cache/cache_versions.py +0 -0
  76. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/__init__.py +0 -0
  77. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_docker.py +0 -0
  78. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_github.py +0 -0
  79. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/check/check_pypi.py +0 -0
  80. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_cache.py +0 -0
  81. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_log.py +0 -0
  82. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/clear_uploads.py +0 -0
  83. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/command_create.py +0 -0
  84. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/coverage.py +0 -0
  85. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_dump.py +0 -0
  86. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/database/db_restore.py +0 -0
  87. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_list.py +0 -0
  88. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_set.py +0 -0
  89. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/env/env_show.py +0 -0
  90. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_delete.py +0 -0
  91. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_discard.py +0 -0
  92. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_env.py +0 -0
  93. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_fork.py +0 -0
  94. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_git.py +0 -0
  95. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_pull.py +0 -0
  96. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/feature/feature_search.py +0 -0
  97. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/locust.py +0 -0
  98. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/__init__.py +0 -0
  99. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_build.py +0 -0
  100. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_clean.py +0 -0
  101. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_deploy.py +0 -0
  102. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_down.py +0 -0
  103. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_env.py +0 -0
  104. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_list.py +0 -0
  105. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_logs.py +0 -0
  106. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_port.py +0 -0
  107. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_release.py +0 -0
  108. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_run.py +0 -0
  109. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_select.py +0 -0
  110. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_shell.py +0 -0
  111. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/product/product_up.py +0 -0
  112. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/release/__init__.py +0 -0
  113. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/release/release_core.py +0 -0
  114. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/route_list.py +0 -0
  115. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/selenium.py +0 -0
  116. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_configs.py +0 -0
  117. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_deps.py +0 -0
  118. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_features.py +0 -0
  119. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_fetch.py +0 -0
  120. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_info.py +0 -0
  121. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_missing.py +0 -0
  122. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_sync.py +0 -0
  123. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/commands/uvl/uvl_valid.py +0 -0
  124. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/__init__.py +0 -0
  125. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/context.py +0 -0
  126. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/services/release.py +0 -0
  127. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/__init__.py +0 -0
  128. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/cache_utils.py +0 -0
  129. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/decorators.py +0 -0
  130. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/dynamic_imports.py +0 -0
  131. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli/utils/path_utils.py +0 -0
  132. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/SOURCES.txt +0 -0
  133. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/dependency_links.txt +0 -0
  134. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/entry_points.txt +0 -0
  135. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/requires.txt +0 -0
  136. {splent_cli-1.2.6 → splent_cli-1.2.7}/src/splent_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_cli
3
- Version: 1.2.6
3
+ Version: 1.2.7
4
4
  Summary: SPLENT-CLI is a CLI to be able to work on your development more easily.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_cli
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_cli"
7
- version = "1.2.6"
7
+ version = "1.2.7"
8
8
  description = "SPLENT-CLI is a CLI to be able to work on your development more easily."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -40,6 +40,7 @@ def _get_cache_entries(cache_root: Path) -> list:
40
40
  def _get_all_product_refs(workspace: Path) -> set:
41
41
  """Returns set of 'name' and 'name@version' (no namespace) from all products' pyproject.toml."""
42
42
  import tomllib
43
+
43
44
  refs = set()
44
45
  for product_dir in sorted(workspace.iterdir()):
45
46
  if not product_dir.is_dir() or product_dir.name.startswith("."):
@@ -37,10 +37,12 @@ def _get_workspace_root_features(workspace: Path) -> dict:
37
37
  if not src.is_dir():
38
38
  continue
39
39
  for ns_dir in src.iterdir():
40
- if (ns_dir.is_dir()
41
- and not ns_dir.name.startswith(("_", "."))
42
- and "." not in ns_dir.name
43
- and (ns_dir / entry.name).is_dir()):
40
+ if (
41
+ ns_dir.is_dir()
42
+ and not ns_dir.name.startswith(("_", "."))
43
+ and "." not in ns_dir.name
44
+ and (ns_dir / entry.name).is_dir()
45
+ ):
44
46
  grouped[f"{ns_dir.name}/{entry.name}"].append("workspace")
45
47
  break
46
48
  return grouped
@@ -75,11 +77,20 @@ def cache_status():
75
77
  for i, v in enumerate(sorted_versions):
76
78
  connector = "└──" if i == len(sorted_versions) - 1 else "├──"
77
79
  if v == "workspace":
78
- click.echo(f" {connector} " + click.style("editable (workspace root)", fg="magenta"))
80
+ click.echo(
81
+ f" {connector} "
82
+ + click.style("editable (workspace root)", fg="magenta")
83
+ )
79
84
  elif v is None:
80
- click.echo(f" {connector} " + click.style("editable (cache)", fg="blue"))
85
+ click.echo(
86
+ f" {connector} " + click.style("editable (cache)", fg="blue")
87
+ )
81
88
  else:
82
- click.echo(f" {connector} " + click.style(f"@{v}", fg="green") + click.style(" (pinned)", fg="bright_black"))
89
+ click.echo(
90
+ f" {connector} "
91
+ + click.style(f"@{v}", fg="green")
92
+ + click.style(" (pinned)", fg="bright_black")
93
+ )
83
94
  click.echo()
84
95
 
85
96
 
@@ -16,7 +16,6 @@ import re
16
16
  import click
17
17
 
18
18
  from splent_cli.services import context
19
- from splent_cli.utils.feature_utils import read_features_from_data
20
19
  from splent_framework.utils.pyproject_reader import PyprojectReader
21
20
 
22
21
 
@@ -24,6 +23,7 @@ from splent_framework.utils.pyproject_reader import PyprojectReader
24
23
  # UVL parser — extract dependency graph
25
24
  # ---------------------------------------------------------------------------
26
25
 
26
+
27
27
  def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]:
28
28
  """Parse UVL file and return (package_map, allowed_deps).
29
29
 
@@ -82,7 +82,10 @@ def _parse_uvl_deps(uvl_path: str) -> tuple[dict[str, str], dict[str, set[str]]]
82
82
  # Source code scanner — find actual imports of other features
83
83
  # ---------------------------------------------------------------------------
84
84
 
85
- def _scan_feature_imports(feature_path: str, feature_name: str, all_packages: set[str]) -> set[str]:
85
+
86
+ def _scan_feature_imports(
87
+ feature_path: str, feature_name: str, all_packages: set[str]
88
+ ) -> set[str]:
86
89
  """Scan all .py files in a feature and return set of other feature packages imported."""
87
90
  imported: set[str] = set()
88
91
 
@@ -109,7 +112,9 @@ def _scan_feature_imports(feature_path: str, feature_name: str, all_packages: se
109
112
  continue
110
113
 
111
114
  # Find imports: from splent_io.splent_feature_X... or import splent_io.splent_feature_X
112
- for match in re.findall(r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", content):
115
+ for match in re.findall(
116
+ r"(?:from|import)\s+splent_io\.(splent_feature_\w+)", content
117
+ ):
113
118
  if match != feature_name and match in all_packages:
114
119
  imported.add(match)
115
120
 
@@ -120,7 +125,10 @@ def _scan_feature_imports(feature_path: str, feature_name: str, all_packages: se
120
125
  # Feature path resolver
121
126
  # ---------------------------------------------------------------------------
122
127
 
123
- def _resolve_feature_paths(workspace: str, product: str, features: list[str]) -> dict[str, str]:
128
+
129
+ def _resolve_feature_paths(
130
+ workspace: str, product: str, features: list[str]
131
+ ) -> dict[str, str]:
124
132
  """Return {package_name: feature_path} for each declared feature."""
125
133
  result = {}
126
134
  features_dir = os.path.join(workspace, product, "features")
@@ -144,6 +152,7 @@ def _resolve_feature_paths(workspace: str, product: str, features: list[str]) ->
144
152
  # Command
145
153
  # ---------------------------------------------------------------------------
146
154
 
155
+
147
156
  @click.command(
148
157
  "check:deps",
149
158
  short_help="Validate that feature imports respect UVL dependency constraints.",
@@ -202,17 +211,21 @@ def check_deps():
202
211
  fpath = feature_paths.get(pkg_name)
203
212
 
204
213
  if not fpath:
205
- click.echo(click.style(f" {short}", bold=True) +
206
- click.style(" (not in cache, skipped)", fg="bright_black"))
214
+ click.echo(
215
+ click.style(f" {short}", bold=True)
216
+ + click.style(" (not in cache, skipped)", fg="bright_black")
217
+ )
207
218
  continue
208
219
 
209
220
  actual_imports = _scan_feature_imports(fpath, pkg_name, all_packages)
210
221
  allowed = allowed_deps.get(pkg_name, set())
211
222
 
212
223
  if not actual_imports:
213
- click.echo(click.style(" [✔] ", fg="green") +
214
- click.style(f"{short}", bold=True) +
215
- " — no cross-feature imports")
224
+ click.echo(
225
+ click.style(" [✔] ", fg="green")
226
+ + click.style(f"{short}", bold=True)
227
+ + " — no cross-feature imports"
228
+ )
216
229
  ok += 1
217
230
  continue
218
231
 
@@ -221,24 +234,38 @@ def check_deps():
221
234
  imp_short = pkg_to_short.get(imp, imp)
222
235
 
223
236
  if imp in allowed:
224
- click.echo(click.style(" [✔] ", fg="green") +
225
- click.style(f"{short}", bold=True) +
226
- f" imports {imp_short}" +
227
- click.style(f" (allowed: {short} => {imp_short})", fg="bright_black"))
237
+ click.echo(
238
+ click.style(" [✔] ", fg="green")
239
+ + click.style(f"{short}", bold=True)
240
+ + f" imports {imp_short}"
241
+ + click.style(
242
+ f" (allowed: {short} => {imp_short})", fg="bright_black"
243
+ )
244
+ )
228
245
  ok += 1
229
246
  else:
230
247
  # Check if the reverse is declared (inverted dependency)
231
248
  reverse_allowed = allowed_deps.get(imp, set())
232
249
  if pkg_name in reverse_allowed:
233
- click.echo(click.style(" [✖] ", fg="red") +
234
- click.style(f"{short}", bold=True) +
235
- f" imports {imp_short}" +
236
- click.style(f" INVERTED UVL says {imp_short} => {short}, not the reverse", fg="red"))
250
+ click.echo(
251
+ click.style(" [✖] ", fg="red")
252
+ + click.style(f"{short}", bold=True)
253
+ + f" imports {imp_short}"
254
+ + click.style(
255
+ f" INVERTED — UVL says {imp_short} => {short}, not the reverse",
256
+ fg="red",
257
+ )
258
+ )
237
259
  else:
238
- click.echo(click.style(" [✖] ", fg="red") +
239
- click.style(f"{short}", bold=True) +
240
- f" imports {imp_short}" +
241
- click.style(f" UNDECLARED no UVL constraint between {short} and {imp_short}", fg="red"))
260
+ click.echo(
261
+ click.style(" [✖] ", fg="red")
262
+ + click.style(f"{short}", bold=True)
263
+ + f" imports {imp_short}"
264
+ + click.style(
265
+ f" UNDECLARED — no UVL constraint between {short} and {imp_short}",
266
+ fg="red",
267
+ )
268
+ )
242
269
  violations += 1
243
270
  has_violation = True
244
271
 
@@ -247,10 +274,16 @@ def check_deps():
247
274
 
248
275
  click.echo()
249
276
  if violations:
250
- click.secho(f" {violations} violation(s) found. Fix the code or update the UVL.", fg="red")
277
+ click.secho(
278
+ f" {violations} violation(s) found. Fix the code or update the UVL.",
279
+ fg="red",
280
+ )
251
281
  raise SystemExit(1)
252
282
  else:
253
- click.secho(f" ✅ All cross-feature imports are consistent with UVL ({ok} checks passed).", fg="green")
283
+ click.secho(
284
+ f" ✅ All cross-feature imports are consistent with UVL ({ok} checks passed).",
285
+ fg="green",
286
+ )
254
287
  click.echo()
255
288
 
256
289
 
@@ -1,6 +1,7 @@
1
1
  """
2
2
  check:env — Validate workspace environment variables and tool versions.
3
3
  """
4
+
4
5
  import os
5
6
  import sys
6
7
 
@@ -15,21 +16,26 @@ def _pkg_version(name: str) -> str | None:
15
16
  return None
16
17
 
17
18
 
18
- @click.command("check:env", short_help="Validate workspace environment and tool versions.")
19
+ @click.command(
20
+ "check:env", short_help="Validate workspace environment and tool versions."
21
+ )
19
22
  def check_env():
20
23
  """Check Python version, SPLENT env vars, CLI/framework compatibility."""
21
24
  ok = fail = warn = 0
22
25
 
23
26
  def _ok(msg):
24
- nonlocal ok; ok += 1
27
+ nonlocal ok
28
+ ok += 1
25
29
  click.echo(click.style(" [✔] ", fg="green") + msg)
26
30
 
27
31
  def _fail(msg):
28
- nonlocal fail; fail += 1
32
+ nonlocal fail
33
+ fail += 1
29
34
  click.echo(click.style(" [✖] ", fg="red") + msg)
30
35
 
31
36
  def _warn(msg):
32
- nonlocal warn; warn += 1
37
+ nonlocal warn
38
+ warn += 1
33
39
  click.echo(click.style(" [⚠] ", fg="yellow") + msg)
34
40
 
35
41
  click.echo()
@@ -78,7 +84,9 @@ def check_env():
78
84
  else:
79
85
  _fail(f"CLI {cli_v} / Framework {fw_v} — major version mismatch")
80
86
  else:
81
- _fail(f"CLI={'?' if not cli_v else cli_v} / Framework={'?' if not fw_v else fw_v}")
87
+ _fail(
88
+ f"CLI={'?' if not cli_v else cli_v} / Framework={'?' if not fw_v else fw_v}"
89
+ )
82
90
 
83
91
  # Credentials
84
92
  if os.getenv("GITHUB_TOKEN"):
@@ -1,6 +1,7 @@
1
1
  """
2
2
  check:features — Validate feature cache, symlinks, pip install, and git state.
3
3
  """
4
+
4
5
  import os
5
6
  import subprocess
6
7
  import importlib.metadata
@@ -20,7 +21,9 @@ def _pkg_installed(name: str) -> bool:
20
21
  return False
21
22
 
22
23
 
23
- @click.command("check:features", short_help="Validate feature cache, symlinks, and install state.")
24
+ @click.command(
25
+ "check:features", short_help="Validate feature cache, symlinks, and install state."
26
+ )
24
27
  def check_features():
25
28
  """Check every declared feature: cache entry, symlink, pip install, git state."""
26
29
  workspace = str(context.workspace())
@@ -31,15 +34,18 @@ def check_features():
31
34
  ok = fail = warn = 0
32
35
 
33
36
  def _ok(msg):
34
- nonlocal ok; ok += 1
37
+ nonlocal ok
38
+ ok += 1
35
39
  click.echo(click.style(" [✔] ", fg="green") + msg)
36
40
 
37
41
  def _fail(msg):
38
- nonlocal fail; fail += 1
42
+ nonlocal fail
43
+ fail += 1
39
44
  click.echo(click.style(" [✖] ", fg="red") + msg)
40
45
 
41
46
  def _warn(msg):
42
- nonlocal warn; warn += 1
47
+ nonlocal warn
48
+ warn += 1
43
49
  click.echo(click.style(" [⚠] ", fg="yellow") + msg)
44
50
 
45
51
  click.echo()
@@ -102,7 +108,7 @@ def check_features():
102
108
  if os.path.exists(link_path):
103
109
  target = os.readlink(link_path)
104
110
  if os.path.isabs(target):
105
- _warn(f"Symlink uses absolute path (should be relative)")
111
+ _warn("Symlink uses absolute path (should be relative)")
106
112
  else:
107
113
  _ok("Symlink OK (relative)")
108
114
  else:
@@ -123,7 +129,9 @@ def check_features():
123
129
  try:
124
130
  r = subprocess.run(
125
131
  ["git", "-C", feature_dir, "status", "--porcelain"],
126
- capture_output=True, text=True, timeout=5,
132
+ capture_output=True,
133
+ text=True,
134
+ timeout=5,
127
135
  )
128
136
  if r.returncode != 0:
129
137
  _warn("Not a git repo")
@@ -1,6 +1,7 @@
1
1
  """
2
2
  check:pyproject — Validate the active product's pyproject.toml.
3
3
  """
4
+
4
5
  import os
5
6
  import re
6
7
 
@@ -13,6 +14,7 @@ from splent_cli.utils.feature_utils import read_features_from_data
13
14
 
14
15
  def _find_missing_pkgs(deps: list) -> list:
15
16
  import importlib.metadata
17
+
16
18
  missing = []
17
19
  for dep in deps:
18
20
  pkg = re.split(r"[=<>!~\[]", dep)[0].strip()
@@ -24,7 +26,9 @@ def _find_missing_pkgs(deps: list) -> list:
24
26
  return missing
25
27
 
26
28
 
27
- @click.command("check:pyproject", short_help="Validate pyproject.toml and dependencies.")
29
+ @click.command(
30
+ "check:pyproject", short_help="Validate pyproject.toml and dependencies."
31
+ )
28
32
  def check_pyproject():
29
33
  """Parse pyproject.toml, check dependencies, and validate feature declarations."""
30
34
  workspace = str(context.workspace())
@@ -34,15 +38,18 @@ def check_pyproject():
34
38
  ok = fail = warn = 0
35
39
 
36
40
  def _ok(msg):
37
- nonlocal ok; ok += 1
41
+ nonlocal ok
42
+ ok += 1
38
43
  click.echo(click.style(" [✔] ", fg="green") + msg)
39
44
 
40
45
  def _fail(msg):
41
- nonlocal fail; fail += 1
46
+ nonlocal fail
47
+ fail += 1
42
48
  click.echo(click.style(" [✖] ", fg="red") + msg)
43
49
 
44
50
  def _warn(msg):
45
- nonlocal warn; warn += 1
51
+ nonlocal warn
52
+ warn += 1
46
53
  click.echo(click.style(" [⚠] ", fg="yellow") + msg)
47
54
 
48
55
  click.echo()
@@ -16,18 +16,23 @@ def db_console():
16
16
  mariadb_database = os.getenv("MARIADB_DATABASE")
17
17
 
18
18
  missing = [
19
- name for name, val in {
19
+ name
20
+ for name, val in {
20
21
  "MARIADB_HOSTNAME": mariadb_hostname,
21
22
  "MARIADB_USER": mariadb_user,
22
23
  "MARIADB_PASSWORD": mariadb_password,
23
24
  "MARIADB_DATABASE": mariadb_database,
24
- }.items() if not val
25
+ }.items()
26
+ if not val
25
27
  ]
26
28
  if missing:
27
29
  click.secho("❌ Missing required environment variables:", fg="red")
28
30
  for var in missing:
29
31
  click.secho(f" - {var}", fg="red")
30
- click.secho("\n Make sure the product .env is loaded: splent product:env --merge --dev", fg="yellow")
32
+ click.secho(
33
+ "\n Make sure the product .env is loaded: splent product:env --merge --dev",
34
+ fg="yellow",
35
+ )
31
36
  raise SystemExit(1)
32
37
 
33
38
  try:
@@ -5,7 +5,11 @@ from flask import current_app
5
5
  from flask_migrate import migrate as alembic_migrate, upgrade as alembic_upgrade
6
6
 
7
7
  from splent_cli.utils.decorators import requires_db
8
- from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry, require_editable
8
+ from splent_cli.utils.lifecycle import (
9
+ advance_state,
10
+ resolve_feature_key_from_entry,
11
+ require_editable,
12
+ )
9
13
  from splent_framework.managers.migration_manager import MigrationManager
10
14
  from splent_framework.utils.feature_utils import get_features_from_pyproject
11
15
  from splent_framework.utils.path_utils import PathUtils
@@ -25,16 +29,17 @@ def _is_empty_migration(path: str) -> bool:
25
29
  content = f.read()
26
30
  # Strip upgrade() and downgrade() bodies — if both are just pass, it's empty
27
31
  import re
32
+
28
33
  upgrades = re.findall(r"def upgrade\(\).*?(?=\ndef |\Z)", content, re.DOTALL)
29
34
  downgrades = re.findall(r"def downgrade\(\).*?(?=\ndef |\Z)", content, re.DOTALL)
30
35
  for body in upgrades + downgrades:
31
36
  # Remove comments, docstrings, and whitespace — if only 'pass' remains, it's empty
32
37
  lines = [
33
- l.strip()
34
- for l in body.splitlines()
35
- if l.strip()
36
- and not l.strip().startswith("#")
37
- and not l.strip().startswith("def ")
38
+ line.strip()
39
+ for line in body.splitlines()
40
+ if line.strip()
41
+ and not line.strip().startswith("#")
42
+ and not line.strip().startswith("def ")
38
43
  ]
39
44
  if any(line != "pass" for line in lines):
40
45
  return False
@@ -97,6 +102,7 @@ def db_migrate(feature):
97
102
 
98
103
  # Suppress Alembic's verbose output during generation
99
104
  import logging
105
+
100
106
  alembic_logger = logging.getLogger("alembic")
101
107
  prev_level = alembic_logger.level
102
108
  alembic_logger.setLevel(logging.WARNING)
@@ -114,14 +120,20 @@ def db_migrate(feature):
114
120
  if after > before:
115
121
  versions_dir = os.path.join(mdir, "versions")
116
122
  newest = max(
117
- (os.path.join(versions_dir, f) for f in os.listdir(versions_dir) if f.endswith(".py")),
123
+ (
124
+ os.path.join(versions_dir, f)
125
+ for f in os.listdir(versions_dir)
126
+ if f.endswith(".py")
127
+ ),
118
128
  key=os.path.getmtime,
119
129
  )
120
130
  if _is_empty_migration(newest):
121
131
  os.remove(newest)
122
132
  click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
123
133
  else:
124
- click.echo(click.style(f" 📝 {feat}: new migration generated", fg="cyan"))
134
+ click.echo(
135
+ click.style(f" 📝 {feat}: new migration generated", fg="cyan")
136
+ )
125
137
  else:
126
138
  click.echo(click.style(f" ✔ {feat}: up to date", fg="green"))
127
139
 
@@ -139,8 +151,13 @@ def db_migrate(feature):
139
151
  if info:
140
152
  key, ns, name, version = info
141
153
  advance_state(
142
- product_path, product_name, key,
143
- to="migrated", namespace=ns, name=name, version=version,
154
+ product_path,
155
+ product_name,
156
+ key,
157
+ to="migrated",
158
+ namespace=ns,
159
+ name=name,
160
+ version=version,
144
161
  )
145
162
  except Exception as e:
146
163
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
@@ -92,7 +92,11 @@ def db_reset(yes):
92
92
  if not dirs:
93
93
  click.echo(click.style("⚠️ No feature migrations found.", fg="yellow"))
94
94
  else:
95
- click.echo(click.style(f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"))
95
+ click.echo(
96
+ click.style(
97
+ f"⬆️ Applying migrations for {len(dirs)} features...", fg="cyan"
98
+ )
99
+ )
96
100
  for feat, mdir in dirs.items():
97
101
  try:
98
102
  alembic_upgrade(directory=mdir)
@@ -109,8 +113,13 @@ def db_reset(yes):
109
113
  if info:
110
114
  key, ns, name, version = info
111
115
  advance_state(
112
- product_path, product_name, key,
113
- to="migrated", namespace=ns, name=name, version=version,
116
+ product_path,
117
+ product_name,
118
+ key,
119
+ to="migrated",
120
+ namespace=ns,
121
+ name=name,
122
+ version=version,
114
123
  )
115
124
  except Exception as e:
116
125
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))
@@ -49,8 +49,13 @@ def db_rollback(feature, steps):
49
49
  if name == feature:
50
50
  target = "installed" if revision is None else "migrated"
51
51
  advance_state(
52
- product_path, product_name, key,
53
- to=target, namespace=ns, name=name, version=version,
52
+ product_path,
53
+ product_name,
54
+ key,
55
+ to=target,
56
+ namespace=ns,
57
+ name=name,
58
+ version=version,
54
59
  )
55
60
  break
56
61
  except Exception as e:
@@ -84,7 +84,6 @@ def get_installed_seeders(specific_module=None):
84
84
 
85
85
  def _truncate_data():
86
86
  """Delete all row data from feature tables, preserving schema and migrations."""
87
- from flask import current_app
88
87
  from splent_framework.db import db
89
88
  from sqlalchemy import text, MetaData
90
89
  from splent_framework.managers.migration_manager import SPLENT_MIGRATIONS_TABLE
@@ -110,13 +109,20 @@ def _truncate_data():
110
109
  @click.command(
111
110
  "db:seed", short_help="Populate the database using feature-level seeders."
112
111
  )
113
- @click.option("--reset", is_flag=True, help="Clear all data before seeding (keeps schema and migrations).")
112
+ @click.option(
113
+ "--reset",
114
+ is_flag=True,
115
+ help="Clear all data before seeding (keeps schema and migrations).",
116
+ )
114
117
  @click.option("-y", "--yes", is_flag=True, help="Skip confirmation prompts.")
115
118
  @click.argument("module", required=False)
116
119
  def db_seed(reset, yes, module):
117
120
  if reset:
118
121
  if yes or click.confirm(
119
- click.style("⚠️ This will delete all data (tables and migrations are preserved). Continue?", fg="red"),
122
+ click.style(
123
+ "⚠️ This will delete all data (tables and migrations are preserved). Continue?",
124
+ fg="red",
125
+ ),
120
126
  abort=True,
121
127
  ):
122
128
  click.echo(click.style("🔄 Clearing data...", fg="yellow"))
@@ -158,7 +164,9 @@ def db_seed(reset, yes, module):
158
164
  )
159
165
  else:
160
166
  click.echo(
161
- click.style(f"❌ Error in {seeder.__class__.__name__}: {e}", fg="red")
167
+ click.style(
168
+ f"❌ Error in {seeder.__class__.__name__}: {e}", fg="red"
169
+ )
162
170
  )
163
171
  success = False
164
172
  break
@@ -24,7 +24,9 @@ def _get_filesystem_head(mdir: str) -> str | None:
24
24
  with open(path, "r", encoding="utf-8") as fh:
25
25
  content = fh.read()
26
26
  rev_m = re.search(r"^revision\s*=\s*['\"](\w+)['\"]", content, re.MULTILINE)
27
- down_m = re.search(r"^down_revision\s*=\s*['\"](\w+)['\"]", content, re.MULTILINE)
27
+ down_m = re.search(
28
+ r"^down_revision\s*=\s*['\"](\w+)['\"]", content, re.MULTILINE
29
+ )
28
30
  if rev_m:
29
31
  revisions[rev_m.group(1)] = down_m.group(1) if down_m else None
30
32
  except Exception:
@@ -72,7 +74,9 @@ def db_status():
72
74
  col_feat = max(col_feat, len("Feature"))
73
75
  col_rev = 14
74
76
 
75
- click.echo(f" {'Feature':<{col_feat}} {'Applied':<{col_rev}} {'Latest':<{col_rev}} Status")
77
+ click.echo(
78
+ f" {'Feature':<{col_feat}} {'Applied':<{col_rev}} {'Latest':<{col_rev}} Status"
79
+ )
76
80
  click.echo(f" {'-' * col_feat} {'-' * col_rev} {'-' * col_rev} {'-' * 12}")
77
81
 
78
82
  issues = 0
@@ -98,11 +102,16 @@ def db_status():
98
102
  else:
99
103
  status = click.style("— none", fg="bright_black")
100
104
 
101
- click.echo(f" {feat:<{col_feat}} {db_display:<{col_rev}} {fs_display:<{col_rev}} {status}")
105
+ click.echo(
106
+ f" {feat:<{col_feat}} {db_display:<{col_rev}} {fs_display:<{col_rev}} {status}"
107
+ )
102
108
 
103
109
  click.echo()
104
110
  if issues:
105
- click.secho(f" {issues} feature(s) out of sync. Run 'splent db:upgrade' to apply pending migrations.", fg="yellow")
111
+ click.secho(
112
+ f" {issues} feature(s) out of sync. Run 'splent db:upgrade' to apply pending migrations.",
113
+ fg="yellow",
114
+ )
106
115
  click.echo()
107
116
 
108
117
 
@@ -12,6 +12,7 @@ from splent_framework.utils.path_utils import PathUtils
12
12
  def _resolve_product():
13
13
  """Return (product_path, product_name) from env."""
14
14
  import os
15
+
15
16
  product = os.getenv("SPLENT_APP", "")
16
17
  product_path = PathUtils.get_app_base_dir()
17
18
  return product_path, product
@@ -54,6 +55,7 @@ def db_upgrade(feature):
54
55
 
55
56
  # Suppress Alembic's verbose INFO output
56
57
  import logging
58
+
57
59
  logging.getLogger("alembic").setLevel(logging.WARNING)
58
60
 
59
61
  for feat, mdir in dirs.items():
@@ -70,8 +72,13 @@ def db_upgrade(feature):
70
72
  if info:
71
73
  key, ns, name, version = info
72
74
  advance_state(
73
- product_path, product_name, key,
74
- to="migrated", namespace=ns, name=name, version=version,
75
+ product_path,
76
+ product_name,
77
+ key,
78
+ to="migrated",
79
+ namespace=ns,
80
+ name=name,
81
+ version=version,
75
82
  )
76
83
  except Exception as e:
77
84
  click.echo(click.style(f" ❌ {feat}: {e}", fg="red"))