splent-framework 1.6.0__tar.gz → 1.6.2__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 (94) hide show
  1. {splent_framework-1.6.0/src/splent_framework.egg-info → splent_framework-1.6.2}/PKG-INFO +1 -1
  2. {splent_framework-1.6.0 → splent_framework-1.6.2}/pyproject.toml +1 -1
  3. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/path_utils.py +18 -1
  4. {splent_framework-1.6.0 → splent_framework-1.6.2/src/splent_framework.egg-info}/PKG-INFO +1 -1
  5. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_base_blueprint_security.py +7 -0
  6. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_base_repository.py +5 -0
  7. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_config_manager.py +2 -0
  8. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_context_manager.py +2 -1
  9. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_default_config.py +1 -0
  10. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_error_handler_manager.py +14 -5
  11. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_manager_parse.py +2 -1
  12. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_order.py +7 -1
  13. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_feature_utils.py +4 -4
  14. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_generic_resource.py +13 -7
  15. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_migration_manager.py +36 -21
  16. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_namespace_manager.py +3 -2
  17. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_pyproject_reader.py +23 -11
  18. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_pyproject_reader_env.py +8 -4
  19. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_refinement.py +72 -44
  20. {splent_framework-1.6.0 → splent_framework-1.6.2}/LICENSE +0 -0
  21. {splent_framework-1.6.0 → splent_framework-1.6.2}/README.md +0 -0
  22. {splent_framework-1.6.0 → splent_framework-1.6.2}/setup.cfg +0 -0
  23. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/_init__.py +0 -0
  24. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/app_factory.py +0 -0
  25. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/blueprints/__init__.py +0 -0
  26. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/blueprints/base_blueprint.py +0 -0
  27. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/bootstraps/__init__.py +0 -0
  28. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/bootstraps/locustfile_bootstrap.py +0 -0
  29. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/__init__.py +0 -0
  30. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/configuration.py +0 -0
  31. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/configuration/default_config.py +0 -0
  32. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/context/__init__.py +0 -0
  33. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/context/context_manager.py +0 -0
  34. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/db.py +0 -0
  35. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/decorators/__init__.py +0 -0
  36. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/decorators/decorators.py +0 -0
  37. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/environment/__init__.py +0 -0
  38. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/environment/host.py +0 -0
  39. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/fixtures/__init__.py +0 -0
  40. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/fixtures/fixtures.py +0 -0
  41. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/helpers/__init__.py +0 -0
  42. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/helpers/test_helpers_auth.py +0 -0
  43. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/hooks/__init__.py +0 -0
  44. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/hooks/template_hooks.py +0 -0
  45. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/locust/__init__.py +0 -0
  46. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/locust/common.py +0 -0
  47. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/__init__.py +0 -0
  48. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/config_manager.py +0 -0
  49. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/db_manager.py +0 -0
  50. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/error_handler_manager.py +0 -0
  51. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_loader.py +0 -0
  52. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_manager.py +0 -0
  53. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/feature_order.py +0 -0
  54. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/jinja_manager.py +0 -0
  55. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/locale_manager.py +0 -0
  56. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/logging_manager.py +0 -0
  57. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/migration_manager.py +0 -0
  58. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/namespace_manager.py +0 -0
  59. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/session_manager.py +0 -0
  60. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/managers/task_queue_manager.py +0 -0
  61. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/migrations/__init__.py +0 -0
  62. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/migrations/feature_env.py +0 -0
  63. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/namespaces/__init__.py +0 -0
  64. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/__init__.py +0 -0
  65. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/model_extender.py +0 -0
  66. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/parser.py +0 -0
  67. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/registry.py +0 -0
  68. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/refinement/validator.py +0 -0
  69. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/repositories/BaseRepository.py +0 -0
  70. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/repositories/__init__.py +0 -0
  71. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/resources/__init__.py +0 -0
  72. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/resources/generic_resource.py +0 -0
  73. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/seeders/BaseSeeder.py +0 -0
  74. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/seeders/__init__.py +0 -0
  75. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/selenium/__init__.py +0 -0
  76. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/selenium/common.py +0 -0
  77. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/serialisers/__init__.py +0 -0
  78. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/serialisers/serializer.py +0 -0
  79. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/BaseService.py +0 -0
  80. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/__init__.py +0 -0
  81. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/services/service_locator.py +0 -0
  82. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/__init__.py +0 -0
  83. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/registry.py +0 -0
  84. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/signals/signal_utils.py +0 -0
  85. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/__init__.py +0 -0
  86. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/app_loader.py +0 -0
  87. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/feature_utils.py +0 -0
  88. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/form_helpers.py +0 -0
  89. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework/utils/pyproject_reader.py +0 -0
  90. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/SOURCES.txt +0 -0
  91. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/dependency_links.txt +0 -0
  92. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/requires.txt +0 -0
  93. {splent_framework-1.6.0 → splent_framework-1.6.2}/src/splent_framework.egg-info/top_level.txt +0 -0
  94. {splent_framework-1.6.0 → splent_framework-1.6.2}/tests/test_config_manager_return.py +1 -1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_framework
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_framework
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "splent_framework"
7
- version = "1.6.0"
7
+ version = "1.6.2"
8
8
  description = "SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -29,7 +29,24 @@ class PathUtils:
29
29
  def get_app_env_file():
30
30
  working_dir = PathUtils.get_working_dir()
31
31
  splent_app = os.getenv("SPLENT_APP", "splent_app")
32
- return os.path.join(working_dir, splent_app, "docker", ".env")
32
+ docker_dir = os.path.join(working_dir, splent_app, "docker")
33
+
34
+ # In production the container is configured through the deploy env_file
35
+ # (``.env.deploy``), which is the single source of truth for prod values
36
+ # such as ``MARIADB_HOSTNAME``. The development ``.env`` is baked into the
37
+ # production image at build time (it points at the *dev* database host),
38
+ # so loading it with ``override=True`` would clobber the correct deploy
39
+ # values that the container already received from its ``env_file``.
40
+ #
41
+ # When running in production, return the ``.env.deploy`` path. It is not
42
+ # baked into the image (it is generated after the build), so callers that
43
+ # guard with ``os.path.exists(...)`` simply skip the load and keep the
44
+ # authoritative process environment untouched. If it ever is present, it
45
+ # carries the correct production values, so loading it is still right.
46
+ if os.getenv("SPLENT_ENV", "dev").lower() == "prod":
47
+ return os.path.join(docker_dir, ".env.deploy")
48
+
49
+ return os.path.join(docker_dir, ".env")
33
50
 
34
51
  @staticmethod
35
52
  def get_modules_dir():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_framework
3
- Version: 1.6.0
3
+ Version: 1.6.2
4
4
  Summary: SPLENT-FRAMEWORK is a set of libraries for agile product development within SPLENT.
5
5
  Author-email: DiversoLab <diversolab@us.es>
6
6
  Project-URL: Homepage, https://github.com/diverso-lab/splent_framework
@@ -8,6 +8,7 @@ assets directory, and verify:
8
8
  - Filenames with path traversal sequences → 403
9
9
  - Non-existent files → 404
10
10
  """
11
+
11
12
  import os
12
13
  import pytest
13
14
  from flask import Flask
@@ -20,6 +21,7 @@ from splent_framework.blueprints.base_blueprint import BaseBlueprint
20
21
  # Helpers
21
22
  # ---------------------------------------------------------------------------
22
23
 
24
+
23
25
  def make_asset_tree(base: str):
24
26
  """Create a small asset tree under base/assets/."""
25
27
  for subfolder in ("js", "css", "dist"):
@@ -43,6 +45,7 @@ def make_asset_tree(base: str):
43
45
  # Fixtures
44
46
  # ---------------------------------------------------------------------------
45
47
 
48
+
46
49
  @pytest.fixture
47
50
  def feature_dir(tmp_path):
48
51
  """Temp directory acting as a feature package root."""
@@ -77,6 +80,7 @@ def client(app):
77
80
  # Serving legitimate files
78
81
  # ---------------------------------------------------------------------------
79
82
 
83
+
80
84
  class TestServeLegitimateFiles:
81
85
  def test_serves_js_with_correct_mimetype(self, client):
82
86
  resp = client.get("/test_feature/js/app.js")
@@ -101,6 +105,7 @@ class TestServeLegitimateFiles:
101
105
  # Security: path traversal blocked
102
106
  # ---------------------------------------------------------------------------
103
107
 
108
+
104
109
  class TestPathTraversalBlocked:
105
110
  def test_traversal_in_filename_blocked(self, client):
106
111
  # Attempt to escape assets/js/ via ../
@@ -120,6 +125,7 @@ class TestPathTraversalBlocked:
120
125
  # Security: subfolder whitelist enforced
121
126
  # ---------------------------------------------------------------------------
122
127
 
128
+
123
129
  class TestSubfolderWhitelist:
124
130
  def test_invalid_subfolder_returns_404(self, client):
125
131
  resp = client.get("/test_feature/templates/base.html")
@@ -139,6 +145,7 @@ class TestSubfolderWhitelist:
139
145
  # Non-existent files
140
146
  # ---------------------------------------------------------------------------
141
147
 
148
+
142
149
  class TestNonExistentFiles:
143
150
  def test_missing_file_returns_404(self, client):
144
151
  resp = client.get("/test_feature/js/nonexistent.js")
@@ -4,6 +4,7 @@ Tests for splent_framework.repositories.BaseRepository
4
4
  Uses SQLite in-memory via a minimal Flask app.
5
5
  Covers all CRUD methods and edge cases.
6
6
  """
7
+
7
8
  import pytest
8
9
  from flask import Flask
9
10
  from sqlalchemy import String
@@ -17,6 +18,7 @@ from splent_framework.repositories.BaseRepository import BaseRepository
17
18
  # Minimal model for testing
18
19
  # ---------------------------------------------------------------------------
19
20
 
21
+
20
22
  class Base(DeclarativeBase):
21
23
  pass
22
24
 
@@ -32,6 +34,7 @@ class Widget(_db.Model):
32
34
  # Fixtures
33
35
  # ---------------------------------------------------------------------------
34
36
 
37
+
35
38
  @pytest.fixture(scope="function")
36
39
  def app():
37
40
  application = Flask(__name__)
@@ -60,6 +63,7 @@ def one_widget(repo):
60
63
  # Tests
61
64
  # ---------------------------------------------------------------------------
62
65
 
66
+
63
67
  class TestCreate:
64
68
  def test_creates_and_returns_instance(self, repo):
65
69
  w = repo.create(name="Bolt", color="silver")
@@ -108,6 +112,7 @@ class TestGetOr404:
108
112
 
109
113
  def test_raises_404_when_not_found(self, repo, app):
110
114
  from werkzeug.exceptions import NotFound
115
+
111
116
  with app.test_request_context():
112
117
  with pytest.raises(NotFound):
113
118
  repo.get_or_404(9999)
@@ -11,6 +11,7 @@ from splent_framework.managers.config_manager import ConfigManager
11
11
  # Helpers
12
12
  # ---------------------------------------------------------------------------
13
13
 
14
+
14
15
  def _make_app():
15
16
  return Flask(__name__)
16
17
 
@@ -19,6 +20,7 @@ def _make_app():
19
20
  # load_config — uses splent_framework default config (no product module)
20
21
  # ---------------------------------------------------------------------------
21
22
 
23
+
22
24
  class TestLoadConfigDefaults:
23
25
  def test_development_sets_debug_true(self, monkeypatch):
24
26
  monkeypatch.setenv("SPLENT_APP", "nonexistent_product_xyz")
@@ -2,7 +2,6 @@
2
2
  Tests for context_manager.build_jinja_context.
3
3
  """
4
4
 
5
- import pytest
6
5
  from flask import Flask
7
6
  from splent_framework.context.context_manager import build_jinja_context
8
7
 
@@ -11,6 +10,7 @@ from splent_framework.context.context_manager import build_jinja_context
11
10
  # Helpers
12
11
  # ---------------------------------------------------------------------------
13
12
 
13
+
14
14
  def _app():
15
15
  app = Flask(__name__)
16
16
  app.context_processors = []
@@ -21,6 +21,7 @@ def _app():
21
21
  # Basic behaviour
22
22
  # ---------------------------------------------------------------------------
23
23
 
24
+
24
25
  class TestBuildJinjaContext:
25
26
  def test_returns_base_context_unchanged_when_no_processors(self):
26
27
  app = _app()
@@ -8,6 +8,7 @@ Covers:
8
8
  - ProductionConfig: DEBUG=False, picks MARIADB_DATABASE
9
9
  - Config base: SECRET_KEY, static class attributes
10
10
  """
11
+
11
12
  import pytest
12
13
  from splent_framework.configuration.default_config import (
13
14
  _build_db_uri,
@@ -3,8 +3,7 @@ Tests for ErrorHandlerManager.
3
3
  """
4
4
 
5
5
  import types
6
- import pytest
7
- from unittest.mock import MagicMock, patch
6
+ from unittest.mock import patch
8
7
  from flask import Flask
9
8
  from splent_framework.managers.error_handler_manager import ErrorHandlerManager
10
9
 
@@ -13,6 +12,7 @@ from splent_framework.managers.error_handler_manager import ErrorHandlerManager
13
12
  # Helpers
14
13
  # ---------------------------------------------------------------------------
15
14
 
15
+
16
16
  def _make_app():
17
17
  app = Flask(__name__)
18
18
  app.config["TESTING"] = True
@@ -23,6 +23,7 @@ def _make_app():
23
23
  # _import_custom_handlers
24
24
  # ---------------------------------------------------------------------------
25
25
 
26
+
26
27
  class TestImportCustomHandlers:
27
28
  def test_returns_none_when_module_not_found(self, monkeypatch):
28
29
  monkeypatch.setenv("SPLENT_APP", "nonexistent_app_xyz")
@@ -46,6 +47,7 @@ class TestImportCustomHandlers:
46
47
  # _get_handler
47
48
  # ---------------------------------------------------------------------------
48
49
 
50
+
49
51
  class TestGetHandler:
50
52
  def _manager_with_handlers(self, handlers_dict):
51
53
  mod = types.SimpleNamespace(**handlers_dict)
@@ -55,19 +57,25 @@ class TestGetHandler:
55
57
  return manager
56
58
 
57
59
  def test_returns_custom_handler_when_present(self):
58
- custom = lambda app, e: ("custom", 404)
60
+ def custom(app, e):
61
+ return ("custom", 404)
62
+
59
63
  manager = self._manager_with_handlers({"handle_404": custom})
60
64
  result = manager._get_handler("handle_404", lambda a, e: ("default", 404))
61
65
  assert result is custom
62
66
 
63
67
  def test_returns_fallback_when_custom_missing(self):
64
- fallback = lambda app, e: ("default", 404)
68
+ def fallback(app, e):
69
+ return ("default", 404)
70
+
65
71
  manager = self._manager_with_handlers({})
66
72
  result = manager._get_handler("handle_404", fallback)
67
73
  assert result is fallback
68
74
 
69
75
  def test_returns_fallback_when_no_custom_handlers_module(self):
70
- fallback = lambda app, e: ("default", 500)
76
+ def fallback(app, e):
77
+ return ("default", 500)
78
+
71
79
  manager = ErrorHandlerManager.__new__(ErrorHandlerManager)
72
80
  manager.app = _make_app()
73
81
  manager.custom_handlers = None
@@ -79,6 +87,7 @@ class TestGetHandler:
79
87
  # register_error_handlers + default handlers
80
88
  # ---------------------------------------------------------------------------
81
89
 
90
+
82
91
  class TestRegisterErrorHandlers:
83
92
  def test_registers_500_handler(self, monkeypatch):
84
93
  monkeypatch.setenv("SPLENT_APP", "nonexistent_xyz")
@@ -4,8 +4,9 @@ Tests for FeatureEntryParser and FeatureRef.
4
4
  Covers all supported entry formats:
5
5
  org/name@version | name@version | org/name | name
6
6
  """
7
+
7
8
  import pytest
8
- from splent_framework.managers.feature_loader import FeatureEntryParser, FeatureRef, FeatureError
9
+ from splent_framework.managers.feature_loader import FeatureEntryParser, FeatureError
9
10
 
10
11
 
11
12
  @pytest.fixture
@@ -70,6 +70,7 @@ def pkg(name: str) -> str:
70
70
  # UVL parsing
71
71
  # ---------------------------------------------------------------------------
72
72
 
73
+
73
74
  class TestParsePackageMap:
74
75
  def test_extracts_short_to_package_mapping(self):
75
76
  mapping = FeatureLoadOrderResolver._parse_package_map(SAMPLE_UVL)
@@ -102,6 +103,7 @@ class TestParseConstraints:
102
103
  # Core ordering — single constraint
103
104
  # ---------------------------------------------------------------------------
104
105
 
106
+
105
107
  class TestSingleDependency:
106
108
  def test_profile_loads_after_auth_when_profile_declared_first(self, tmp_path):
107
109
  uvl = write_uvl(tmp_path, SAMPLE_UVL)
@@ -132,6 +134,7 @@ class TestSingleDependency:
132
134
  # Full product set (mirrors sample_splent_app)
133
135
  # ---------------------------------------------------------------------------
134
136
 
137
+
135
138
  class TestFullProductOrder:
136
139
  FULL = [
137
140
  pkg("profile"),
@@ -174,6 +177,7 @@ class TestFullProductOrder:
174
177
  # Fallback behaviour
175
178
  # ---------------------------------------------------------------------------
176
179
 
180
+
177
181
  class TestFallback:
178
182
  def test_returns_original_order_when_uvl_path_is_none(self):
179
183
  features = [pkg("profile"), pkg("auth")]
@@ -188,7 +192,7 @@ class TestFallback:
188
192
  def test_returns_original_order_when_no_constraints_apply(self, tmp_path):
189
193
  """Features not mentioned in UVL constraints → pyproject order."""
190
194
  uvl = write_uvl(tmp_path, SAMPLE_UVL)
191
- features = [pkg("public"), pkg("redis")] # neither in UVL constraints
195
+ features = [pkg("public"), pkg("redis")] # neither in UVL constraints
192
196
  result = RESOLVER.resolve(features, uvl)
193
197
  assert result == features
194
198
 
@@ -201,6 +205,7 @@ class TestFallback:
201
205
  # Cycle detection
202
206
  # ---------------------------------------------------------------------------
203
207
 
208
+
204
209
  class TestCycleDetection:
205
210
  def test_raises_on_direct_cycle(self, tmp_path):
206
211
  uvl = write_uvl(tmp_path, CYCLIC_UVL)
@@ -221,6 +226,7 @@ class TestCycleDetection:
221
226
  # Stability (original order preserved for ties)
222
227
  # ---------------------------------------------------------------------------
223
228
 
229
+
224
230
  class TestStableOrdering:
225
231
  def test_three_independents_keep_original_order(self, tmp_path):
226
232
  """No constraints between auth, public, redis → pyproject order preserved."""
@@ -7,6 +7,7 @@ Covers:
7
7
  - Returns [] when [project.optional-dependencies] key is missing
8
8
  - Raises RuntimeError on malformed TOML (not silent swallow)
9
9
  """
10
+
10
11
  import pytest
11
12
  from splent_framework.utils.feature_utils import get_features_from_pyproject
12
13
 
@@ -27,7 +28,7 @@ def app_dir(tmp_path):
27
28
  class TestGetFeaturesFromPyproject:
28
29
  def test_returns_features_list(self, app_dir):
29
30
  (app_dir / "pyproject.toml").write_text(
30
- '[project.optional-dependencies]\n'
31
+ "[project.optional-dependencies]\n"
31
32
  'features = ["splent_feature_auth@v1.0.0", "splent_feature_public@v1.0.0"]\n'
32
33
  )
33
34
  result = get_features_from_pyproject()
@@ -35,8 +36,7 @@ class TestGetFeaturesFromPyproject:
35
36
 
36
37
  def test_returns_empty_list_when_features_key_missing(self, app_dir):
37
38
  (app_dir / "pyproject.toml").write_text(
38
- '[project.optional-dependencies]\n'
39
- 'core = ["flask"]\n'
39
+ '[project.optional-dependencies]\ncore = ["flask"]\n'
40
40
  )
41
41
  result = get_features_from_pyproject()
42
42
  assert result == []
@@ -60,7 +60,7 @@ class TestGetFeaturesFromPyproject:
60
60
 
61
61
  def test_returns_empty_list_for_empty_features(self, app_dir):
62
62
  (app_dir / "pyproject.toml").write_text(
63
- '[project.optional-dependencies]\nfeatures = []\n'
63
+ "[project.optional-dependencies]\nfeatures = []\n"
64
64
  )
65
65
  result = get_features_from_pyproject()
66
66
  assert result == []
@@ -2,21 +2,27 @@
2
2
  Tests for GenericResource — verifies both Serializer objects and plain
3
3
  field lists work correctly as the serializer parameter.
4
4
  """
5
- import json
5
+
6
6
  import pytest
7
7
  from flask import Flask
8
8
 
9
9
  flask_restful = pytest.importorskip("flask_restful")
10
10
  Api = flask_restful.Api
11
11
 
12
- from splent_framework.db import db as _db
13
- from splent_framework.resources.generic_resource import GenericResource, create_resource
12
+ # Imported after importorskip on purpose: generic_resource pulls in
13
+ # flask_restful, so the module must be skipped first when it is absent.
14
+ from splent_framework.db import db as _db # noqa: E402
15
+ from splent_framework.resources.generic_resource import ( # noqa: E402
16
+ GenericResource,
17
+ create_resource,
18
+ )
14
19
 
15
20
 
16
21
  # ---------------------------------------------------------------------------
17
22
  # Model fixture
18
23
  # ---------------------------------------------------------------------------
19
24
 
25
+
20
26
  class Item(_db.Model):
21
27
  __tablename__ = "item"
22
28
  id = _db.Column(_db.Integer, primary_key=True)
@@ -46,8 +52,8 @@ def client(app):
46
52
  # Tests: create_resource with field list
47
53
  # ---------------------------------------------------------------------------
48
54
 
49
- class TestCreateResourceWithFieldList:
50
55
 
56
+ class TestCreateResourceWithFieldList:
51
57
  def test_post_filters_by_allowed_fields(self, app, client):
52
58
  api = Api(app)
53
59
  ResourceClass = create_resource(Item, ["name"])
@@ -97,7 +103,9 @@ class TestCreateResourceWithFieldList:
97
103
  _db.session.commit()
98
104
  item_id = item.id
99
105
 
100
- resp = client.put(f"/items/{item_id}", json={"name": "new", "secret": "changed"})
106
+ resp = client.put(
107
+ f"/items/{item_id}", json={"name": "new", "secret": "changed"}
108
+ )
101
109
  assert resp.status_code == 200
102
110
 
103
111
  with app.app_context():
@@ -121,7 +129,6 @@ class TestCreateResourceWithFieldList:
121
129
 
122
130
 
123
131
  class TestCreateResourceWithNone:
124
-
125
132
  def test_post_with_no_fields_accepts_all(self, app, client):
126
133
  api = Api(app)
127
134
  ResourceClass = create_resource(Item)
@@ -148,7 +155,6 @@ class TestCreateResourceWithNone:
148
155
 
149
156
 
150
157
  class TestCreateResourceWithSerializer:
151
-
152
158
  def test_works_with_serializer_object(self, app, client):
153
159
  from splent_framework.serialisers.serializer import Serializer
154
160
 
@@ -2,15 +2,16 @@
2
2
  Tests for MigrationManager — filesystem-based migration directory resolution
3
3
  and UVL-ordered feature discovery.
4
4
  """
5
+
5
6
  import os
6
7
  import pytest
7
- from unittest.mock import patch
8
8
 
9
9
 
10
10
  # ---------------------------------------------------------------------------
11
11
  # Fixtures
12
12
  # ---------------------------------------------------------------------------
13
13
 
14
+
14
15
  @pytest.fixture
15
16
  def feature_workspace(tmp_path, monkeypatch):
16
17
  """
@@ -30,11 +31,11 @@ def feature_workspace(tmp_path, monkeypatch):
30
31
  pyproject = product / "pyproject.toml"
31
32
  pyproject.write_text(
32
33
  '[project]\nname = "test_app"\nversion = "1.0.0"\n'
33
- '[project.optional-dependencies]\n'
34
- 'features = [\n'
34
+ "[project.optional-dependencies]\n"
35
+ "features = [\n"
35
36
  ' "splent-io/splent_feature_auth@v1.0.0",\n'
36
37
  ' "splent-io/splent_feature_profile@v1.0.0",\n'
37
- ']\n'
38
+ "]\n"
38
39
  )
39
40
 
40
41
  # Create cached features with migrations
@@ -47,9 +48,7 @@ def feature_workspace(tmp_path, monkeypatch):
47
48
 
48
49
  # Create relative symlinks (like product:sync does)
49
50
  for feat in ("splent_feature_auth", "splent_feature_profile"):
50
- target = os.path.relpath(
51
- cache / f"{feat}@v1.0.0", features_dir
52
- )
51
+ target = os.path.relpath(cache / f"{feat}@v1.0.0", features_dir)
53
52
  (features_dir / f"{feat}@v1.0.0").symlink_to(target)
54
53
 
55
54
  return tmp_path
@@ -71,17 +70,21 @@ def feature_workspace_no_migrations(tmp_path, monkeypatch):
71
70
  pyproject = product / "pyproject.toml"
72
71
  pyproject.write_text(
73
72
  '[project]\nname = "test_app"\nversion = "1.0.0"\n'
74
- '[project.optional-dependencies]\n'
73
+ "[project.optional-dependencies]\n"
75
74
  'features = ["splent-io/splent_feature_nomigrations@v1.0.0"]\n'
76
75
  )
77
76
 
78
- feat_dir = cache / "splent_feature_nomigrations@v1.0.0" / "src" / "splent_io" / "splent_feature_nomigrations"
77
+ feat_dir = (
78
+ cache
79
+ / "splent_feature_nomigrations@v1.0.0"
80
+ / "src"
81
+ / "splent_io"
82
+ / "splent_feature_nomigrations"
83
+ )
79
84
  feat_dir.mkdir(parents=True)
80
85
  # No migrations/ directory
81
86
 
82
- target = os.path.relpath(
83
- cache / "splent_feature_nomigrations@v1.0.0", features_dir
84
- )
87
+ target = os.path.relpath(cache / "splent_feature_nomigrations@v1.0.0", features_dir)
85
88
  (features_dir / "splent_feature_nomigrations@v1.0.0").symlink_to(target)
86
89
 
87
90
  return tmp_path
@@ -91,8 +94,8 @@ def feature_workspace_no_migrations(tmp_path, monkeypatch):
91
94
  # Tests: get_feature_migration_dir
92
95
  # ---------------------------------------------------------------------------
93
96
 
94
- class TestGetFeatureMigrationDir:
95
97
 
98
+ class TestGetFeatureMigrationDir:
96
99
  def test_finds_migration_dir_via_filesystem(self, feature_workspace):
97
100
  from splent_framework.managers.migration_manager import MigrationManager
98
101
 
@@ -104,26 +107,34 @@ class TestGetFeatureMigrationDir:
104
107
  def test_returns_none_when_no_migrations(self, feature_workspace_no_migrations):
105
108
  from splent_framework.managers.migration_manager import MigrationManager
106
109
 
107
- result = MigrationManager.get_feature_migration_dir("splent_feature_nomigrations")
110
+ result = MigrationManager.get_feature_migration_dir(
111
+ "splent_feature_nomigrations"
112
+ )
108
113
  assert result is None
109
114
 
110
115
  def test_returns_none_for_unknown_feature(self, feature_workspace):
111
116
  from splent_framework.managers.migration_manager import MigrationManager
112
117
 
113
- result = MigrationManager.get_feature_migration_dir("splent_feature_nonexistent")
118
+ result = MigrationManager.get_feature_migration_dir(
119
+ "splent_feature_nonexistent"
120
+ )
114
121
  assert result is None
115
122
 
116
123
  def test_strips_version_from_name(self, feature_workspace):
117
124
  from splent_framework.managers.migration_manager import MigrationManager
118
125
 
119
- result = MigrationManager.get_feature_migration_dir("splent_feature_auth@v1.0.0")
126
+ result = MigrationManager.get_feature_migration_dir(
127
+ "splent_feature_auth@v1.0.0"
128
+ )
120
129
  assert result is not None
121
130
  assert "migrations" in result
122
131
 
123
132
  def test_strips_org_from_name(self, feature_workspace):
124
133
  from splent_framework.managers.migration_manager import MigrationManager
125
134
 
126
- result = MigrationManager.get_feature_migration_dir("splent-io/splent_feature_auth")
135
+ result = MigrationManager.get_feature_migration_dir(
136
+ "splent-io/splent_feature_auth"
137
+ )
127
138
  assert result is not None
128
139
 
129
140
 
@@ -131,8 +142,8 @@ class TestGetFeatureMigrationDir:
131
142
  # Tests: get_all_feature_migration_dirs
132
143
  # ---------------------------------------------------------------------------
133
144
 
134
- class TestGetAllFeatureMigrationDirs:
135
145
 
146
+ class TestGetAllFeatureMigrationDirs:
136
147
  def test_finds_all_features_with_migrations(self, feature_workspace):
137
148
  from splent_framework.managers.migration_manager import MigrationManager
138
149
 
@@ -149,10 +160,11 @@ class TestGetAllFeatureMigrationDirs:
149
160
  product.mkdir()
150
161
  (product / "pyproject.toml").write_text(
151
162
  '[project]\nname = "test_app"\nversion = "1.0.0"\n'
152
- '[project.optional-dependencies]\nfeatures = []\n'
163
+ "[project.optional-dependencies]\nfeatures = []\n"
153
164
  )
154
165
 
155
166
  from splent_framework.managers.migration_manager import MigrationManager
167
+
156
168
  dirs = MigrationManager.get_all_feature_migration_dirs()
157
169
  assert dirs == {}
158
170
 
@@ -160,6 +172,7 @@ class TestGetAllFeatureMigrationDirs:
160
172
  monkeypatch.delenv("SPLENT_APP", raising=False)
161
173
 
162
174
  from splent_framework.managers.migration_manager import MigrationManager
175
+
163
176
  dirs = MigrationManager.get_all_feature_migration_dirs()
164
177
  assert dirs == {}
165
178
 
@@ -188,14 +201,16 @@ class TestGetAllFeatureMigrationDirs:
188
201
  # Tests: relative symlinks work correctly
189
202
  # ---------------------------------------------------------------------------
190
203
 
191
- class TestSymlinkResolution:
192
204
 
205
+ class TestSymlinkResolution:
193
206
  def test_symlinks_are_relative(self, feature_workspace):
194
207
  features_dir = feature_workspace / "test_app" / "features" / "splent_io"
195
208
  for link in features_dir.iterdir():
196
209
  assert link.is_symlink()
197
210
  target = os.readlink(str(link))
198
- assert not os.path.isabs(target), f"Symlink {link} has absolute target: {target}"
211
+ assert not os.path.isabs(target), (
212
+ f"Symlink {link} has absolute target: {target}"
213
+ )
199
214
 
200
215
  def test_relative_symlinks_resolve_correctly(self, feature_workspace):
201
216
  features_dir = feature_workspace / "test_app" / "features" / "splent_io"
@@ -5,9 +5,7 @@ NamespaceManager.init_app is integration-only (touches sys.path and importlib),
5
5
  so we test the four extracted static methods in isolation using tmp_path.
6
6
  """
7
7
 
8
- import os
9
8
  import sys
10
- import pytest
11
9
  from splent_framework.managers.namespace_manager import NamespaceManager
12
10
 
13
11
 
@@ -15,6 +13,7 @@ from splent_framework.managers.namespace_manager import NamespaceManager
15
13
  # _detect_orgs
16
14
  # ---------------------------------------------------------------------------
17
15
 
16
+
18
17
  class TestDetectOrgs:
19
18
  def test_returns_all_subdirectories(self, tmp_path):
20
19
  (tmp_path / "org_a").mkdir()
@@ -36,6 +35,7 @@ class TestDetectOrgs:
36
35
  # _ensure_init_files
37
36
  # ---------------------------------------------------------------------------
38
37
 
38
+
39
39
  class TestEnsureInitFiles:
40
40
  def test_creates_init_py_when_missing(self, tmp_path):
41
41
  (tmp_path / "myorg").mkdir()
@@ -65,6 +65,7 @@ class TestEnsureInitFiles:
65
65
  # _add_to_syspath
66
66
  # ---------------------------------------------------------------------------
67
67
 
68
+
68
69
  class TestAddToSyspath:
69
70
  def test_adds_src_dirs_to_syspath(self, tmp_path):
70
71
  src = tmp_path / "org_a" / "feature_x" / "src"