splent-framework 1.2.5__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 (82) hide show
  1. {splent_framework-1.2.5/src/splent_framework.egg-info → splent_framework-1.2.7}/PKG-INFO +1 -1
  2. {splent_framework-1.2.5 → splent_framework-1.2.7}/pyproject.toml +1 -1
  3. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/blueprints/base_blueprint.py +5 -4
  4. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/config_manager.py +4 -1
  5. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/feature_loader.py +14 -4
  6. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/feature_manager.py +12 -3
  7. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/feature_order.py +2 -4
  8. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/migration_manager.py +5 -1
  9. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/namespace_manager.py +3 -1
  10. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/repositories/BaseRepository.py +2 -6
  11. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/resources/generic_resource.py +16 -6
  12. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/seeders/BaseSeeder.py +0 -1
  13. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/utils/path_utils.py +6 -2
  14. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/utils/pyproject_reader.py +1 -2
  15. {splent_framework-1.2.5 → splent_framework-1.2.7/src/splent_framework.egg-info}/PKG-INFO +1 -1
  16. {splent_framework-1.2.5 → splent_framework-1.2.7}/LICENSE +0 -0
  17. {splent_framework-1.2.5 → splent_framework-1.2.7}/README.md +0 -0
  18. {splent_framework-1.2.5 → splent_framework-1.2.7}/setup.cfg +0 -0
  19. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/_init__.py +0 -0
  20. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/blueprints/__init__.py +0 -0
  21. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/bootstraps/__init__.py +0 -0
  22. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/bootstraps/locustfile_bootstrap.py +0 -0
  23. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/configuration/__init__.py +0 -0
  24. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/configuration/configuration.py +0 -0
  25. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/configuration/default_config.py +0 -0
  26. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/context/__init__.py +0 -0
  27. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/context/context_manager.py +0 -0
  28. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/db.py +0 -0
  29. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/decorators/__init__.py +0 -0
  30. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/decorators/decorators.py +0 -0
  31. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/environment/__init__.py +0 -0
  32. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/environment/host.py +0 -0
  33. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/fixtures/__init__.py +0 -0
  34. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/fixtures/fixtures.py +0 -0
  35. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/helpers/__init__.py +0 -0
  36. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/helpers/test_helpers_auth.py +0 -0
  37. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/helpers/test_helpers_db.py +0 -0
  38. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/hooks/__init__.py +0 -0
  39. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/hooks/template_hooks.py +0 -0
  40. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/locust/__init__.py +0 -0
  41. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/locust/common.py +0 -0
  42. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/__init__.py +0 -0
  43. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/db_manager.py +0 -0
  44. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/error_handler_manager.py +0 -0
  45. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/jinja_manager.py +0 -0
  46. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/logging_manager.py +0 -0
  47. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/session_manager.py +0 -0
  48. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/managers/task_queue_manager.py +0 -0
  49. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/migrations/__init__.py +0 -0
  50. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/migrations/feature_env.py +0 -0
  51. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/namespaces/__init__.py +0 -0
  52. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/repositories/__init__.py +0 -0
  53. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/resources/__init__.py +0 -0
  54. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/seeders/__init__.py +0 -0
  55. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/selenium/__init__.py +0 -0
  56. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/selenium/common.py +0 -0
  57. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/serialisers/__init__.py +0 -0
  58. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/serialisers/serializer.py +0 -0
  59. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/services/BaseService.py +0 -0
  60. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/services/__init__.py +0 -0
  61. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/utils/__init__.py +0 -0
  62. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/utils/app_loader.py +0 -0
  63. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework/utils/feature_utils.py +0 -0
  64. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework.egg-info/SOURCES.txt +0 -0
  65. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework.egg-info/dependency_links.txt +0 -0
  66. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework.egg-info/requires.txt +0 -0
  67. {splent_framework-1.2.5 → splent_framework-1.2.7}/src/splent_framework.egg-info/top_level.txt +0 -0
  68. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_base_blueprint_security.py +0 -0
  69. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_base_repository.py +0 -0
  70. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_config_manager.py +0 -0
  71. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_config_manager_return.py +0 -0
  72. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_context_manager.py +0 -0
  73. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_default_config.py +0 -0
  74. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_error_handler_manager.py +0 -0
  75. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_feature_manager_parse.py +0 -0
  76. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_feature_order.py +0 -0
  77. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_feature_utils.py +0 -0
  78. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_generic_resource.py +0 -0
  79. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_migration_manager.py +0 -0
  80. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_namespace_manager.py +0 -0
  81. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_pyproject_reader.py +0 -0
  82. {splent_framework-1.2.5 → splent_framework-1.2.7}/tests/test_pyproject_reader_env.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_framework
3
- Version: 1.2.5
3
+ Version: 1.2.7
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.2.5"
7
+ version = "1.2.7"
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"
@@ -37,9 +37,7 @@ class BaseBlueprint(Blueprint):
37
37
  )
38
38
  pkg_dir = os.path.dirname(module.__file__)
39
39
  except (ImportError, AttributeError) as e:
40
- raise RuntimeError(
41
- f"Cannot resolve package path for {import_name}: {e}"
42
- )
40
+ raise RuntimeError(f"Cannot resolve package path for {import_name}: {e}")
43
41
 
44
42
  # Root directory of this feature package (src/splent_io/splent_feature_xxx)
45
43
  self.feature_code_path = pkg_dir
@@ -70,7 +68,10 @@ class BaseBlueprint(Blueprint):
70
68
  requested_path = os.path.realpath(os.path.join(base_dir, filename))
71
69
 
72
70
  # Prevent path traversal: resolved path must stay inside base_dir
73
- if not requested_path.startswith(base_dir + os.sep) and requested_path != base_dir:
71
+ if (
72
+ not requested_path.startswith(base_dir + os.sep)
73
+ and requested_path != base_dir
74
+ ):
74
75
  abort(403)
75
76
 
76
77
  if not os.path.isfile(requested_path):
@@ -25,7 +25,10 @@ class ConfigManager:
25
25
  except ModuleNotFoundError:
26
26
  from splent_framework.configuration import default_config as config_module
27
27
 
28
- logger.warning("No product config.py found for '%s', using SPLENT default config.", splent_app)
28
+ logger.warning(
29
+ "No product config.py found for '%s', using SPLENT default config.",
30
+ splent_app,
31
+ )
29
32
 
30
33
  config_class_name = f"{config_name.capitalize()}Config"
31
34
  config_class = getattr(config_module, config_class_name, None)
@@ -35,6 +35,7 @@ FEATURE_SUBMODULES = ("routes", "models", "hooks")
35
35
  # Domain exception
36
36
  # ---------------------------------------------------------------------------
37
37
 
38
+
38
39
  class FeatureError(RuntimeError):
39
40
  """Raised when any stage of feature loading fails."""
40
41
 
@@ -43,13 +44,14 @@ class FeatureError(RuntimeError):
43
44
  # Value object
44
45
  # ---------------------------------------------------------------------------
45
46
 
47
+
46
48
  @dataclass(frozen=True)
47
49
  class FeatureRef:
48
50
  """Immutable reference to a feature package."""
49
51
 
50
- org: str # original org slug (e.g. "splent-io")
51
- org_safe: str # Python-safe org namespace (e.g. "splent_io")
52
- name: str # feature package name (e.g. "splent_feature_auth")
52
+ org: str # original org slug (e.g. "splent-io")
53
+ org_safe: str # Python-safe org namespace (e.g. "splent_io")
54
+ name: str # feature package name (e.g. "splent_feature_auth")
53
55
  version: str | None # declared version (e.g. "v1.0.0"), or None if absent
54
56
 
55
57
  def import_name(self) -> str:
@@ -61,6 +63,7 @@ class FeatureRef:
61
63
  # SRP: parse raw entry strings
62
64
  # ---------------------------------------------------------------------------
63
65
 
66
+
64
67
  class FeatureEntryParser:
65
68
  """Parse raw feature entry strings into FeatureRef instances.
66
69
 
@@ -98,6 +101,7 @@ class FeatureEntryParser:
98
101
  # SRP: locate the feature directory on the filesystem
99
102
  # ---------------------------------------------------------------------------
100
103
 
104
+
101
105
  class FeatureLinkResolver:
102
106
  """Locate the symlink (or plain directory) for a feature on the filesystem.
103
107
 
@@ -143,6 +147,7 @@ class FeatureLinkResolver:
143
147
  # SRP: validate internal directory structure
144
148
  # ---------------------------------------------------------------------------
145
149
 
150
+
146
151
  class FeatureStructureValidator:
147
152
  """Validate that a resolved feature directory has the expected layout.
148
153
 
@@ -177,6 +182,7 @@ class FeatureStructureValidator:
177
182
  # SRP: importlib operations
178
183
  # ---------------------------------------------------------------------------
179
184
 
185
+
180
186
  class FeatureImporter:
181
187
  """Handle all importlib operations for a feature package."""
182
188
 
@@ -219,6 +225,7 @@ class FeatureImporter:
219
225
  # SRP: Flask integration hooks
220
226
  # ---------------------------------------------------------------------------
221
227
 
228
+
222
229
  class FeatureIntegrator:
223
230
  """Run Flask integration hooks for a loaded feature module.
224
231
 
@@ -280,7 +287,9 @@ class FeatureIntegrator:
280
287
  if self._strict:
281
288
  raise FeatureError(f"No blueprints found in {import_name}")
282
289
 
283
- def _collect_candidate_modules(self, module: types.ModuleType, import_name: str) -> list[types.ModuleType]:
290
+ def _collect_candidate_modules(
291
+ self, module: types.ModuleType, import_name: str
292
+ ) -> list[types.ModuleType]:
284
293
  """Return the feature root module plus any already-imported submodules."""
285
294
  return [module] + [
286
295
  sys.modules[f"{import_name}.{sub}"]
@@ -321,6 +330,7 @@ class FeatureIntegrator:
321
330
  # Orchestrator: load a single feature (DIP — collaborators injected)
322
331
  # ---------------------------------------------------------------------------
323
332
 
333
+
324
334
  class FeatureLoader:
325
335
  """Orchestrate the full loading pipeline for a single FeatureRef.
326
336
 
@@ -71,11 +71,20 @@ class FeatureManager:
71
71
 
72
72
  # Advance lifecycle state to "active"
73
73
  try:
74
- from splent_cli.utils.lifecycle import advance_state, resolve_feature_key_from_entry
74
+ from splent_cli.utils.lifecycle import (
75
+ advance_state,
76
+ resolve_feature_key_from_entry,
77
+ )
78
+
75
79
  key, ns, name, version = resolve_feature_key_from_entry(entry)
76
80
  advance_state(
77
- product_dir, splent_app, key,
78
- to="active", namespace=ns, name=name, version=version,
81
+ product_dir,
82
+ splent_app,
83
+ key,
84
+ to="active",
85
+ namespace=ns,
86
+ name=name,
87
+ version=version,
79
88
  )
80
89
  except Exception:
81
90
  pass # CLI may not be installed (e.g. production without dev deps)
@@ -152,7 +152,7 @@ class FeatureLoadOrderResolver:
152
152
  def _topological_sort(
153
153
  self,
154
154
  features_raw: list[str],
155
- package_map: dict[str, str], # {short_name → package_name}
155
+ package_map: dict[str, str], # {short_name → package_name}
156
156
  constraints: list[tuple[str, str]], # [(requirer_short, required_short)]
157
157
  ) -> list[str]:
158
158
  """Stable Kahn topological sort over *features_raw*.
@@ -199,9 +199,7 @@ class FeatureLoadOrderResolver:
199
199
 
200
200
  # Min-heap keyed by original pyproject index → stable ordering.
201
201
  heap: list[tuple[int, str]] = [
202
- (pkg_to_index[pkg], pkg)
203
- for pkg, deg in in_degree.items()
204
- if deg == 0
202
+ (pkg_to_index[pkg], pkg) for pkg, deg in in_degree.items() if deg == 0
205
203
  ]
206
204
  heapq.heapify(heap)
207
205
 
@@ -108,6 +108,7 @@ class MigrationManager:
108
108
 
109
109
  # 1. Filesystem lookup via features/ directory
110
110
  from splent_framework.utils.path_utils import PathUtils
111
+
111
112
  features_base = os.path.join(PathUtils.get_app_base_dir(), "features")
112
113
  if os.path.isdir(features_base):
113
114
  for org_dir in os.listdir(features_base):
@@ -203,7 +204,10 @@ class MigrationManager:
203
204
  return row[0] if row else None
204
205
  except sa_exc.SQLAlchemyError:
205
206
  # Table may not exist yet (feature not yet migrated) — this is expected
206
- logger.debug("No revision found for feature '%s' (table may not exist yet)", feature_name)
207
+ logger.debug(
208
+ "No revision found for feature '%s' (table may not exist yet)",
209
+ feature_name,
210
+ )
207
211
  return None
208
212
 
209
213
  @staticmethod
@@ -88,4 +88,6 @@ class NamespaceManager:
88
88
  importlib.import_module(org)
89
89
  logger.debug("Namespace '%s' registered.", org)
90
90
  except (ImportError, ModuleNotFoundError) as e:
91
- logger.error("Failed to import namespace '%s': %s", org, e, exc_info=True)
91
+ logger.error(
92
+ "Failed to import namespace '%s': %s", org, e, exc_info=True
93
+ )
@@ -25,9 +25,7 @@ class BaseRepository(Generic[T]):
25
25
  return self.session.get(self.model, id)
26
26
 
27
27
  def get_by_column(self, column_name: str, value: Any) -> list[T]:
28
- stmt = select(self.model).where(
29
- getattr(self.model, column_name) == value
30
- )
28
+ stmt = select(self.model).where(getattr(self.model, column_name) == value)
31
29
  return list(self.session.scalars(stmt).all())
32
30
 
33
31
  def get_or_404(self, id: int) -> T | NoReturn:
@@ -60,6 +58,4 @@ class BaseRepository(Generic[T]):
60
58
  return True
61
59
 
62
60
  def count(self) -> int:
63
- return self.session.scalar(
64
- select(func.count()).select_from(self.model)
65
- ) or 0
61
+ return self.session.scalar(select(func.count()).select_from(self.model)) or 0
@@ -29,9 +29,7 @@ class GenericResource(Resource):
29
29
  def _serialize(self, item) -> dict:
30
30
  if self._serializer:
31
31
  return self._serializer.serialize(item)
32
- fields = self._allowed_fields or [
33
- c.name for c in self.model.__table__.columns
34
- ]
32
+ fields = self._allowed_fields or [c.name for c in self.model.__table__.columns]
35
33
  return {f: getattr(item, f, None) for f in fields}
36
34
 
37
35
  def get(self, id: int | None = None) -> tuple:
@@ -52,12 +50,19 @@ class GenericResource(Resource):
52
50
  if self._allowed_fields:
53
51
  data = {k: v for k, v in data.items() if k in self._allowed_fields}
54
52
  elif self._serializer and self._serializer.serialization_fields:
55
- data = {k: v for k, v in data.items() if k in self._serializer.serialization_fields}
53
+ data = {
54
+ k: v
55
+ for k, v in data.items()
56
+ if k in self._serializer.serialization_fields
57
+ }
56
58
 
57
59
  item = self.model(**data)
58
60
  db.session.add(item)
59
61
  db.session.commit()
60
- return {"message": f"{self.model_name} created successfully", "id": item.id}, 201
62
+ return {
63
+ "message": f"{self.model_name} created successfully",
64
+ "id": item.id,
65
+ }, 201
61
66
 
62
67
  def put(self, id: int) -> tuple:
63
68
  item = db.session.get(self.model, id)
@@ -69,7 +74,11 @@ class GenericResource(Resource):
69
74
  return {"message": "No input data provided"}, 400
70
75
 
71
76
  allowed = self._allowed_fields
72
- if allowed is None and self._serializer and self._serializer.serialization_fields:
77
+ if (
78
+ allowed is None
79
+ and self._serializer
80
+ and self._serializer.serialization_fields
81
+ ):
73
82
  allowed = list(self._serializer.serialization_fields)
74
83
 
75
84
  for key, value in data.items():
@@ -89,6 +98,7 @@ class GenericResource(Resource):
89
98
 
90
99
  def create_resource(model: type, serialization_fields: list[str] | None = None) -> type:
91
100
  """Factory that returns a GenericResource subclass for a given model."""
101
+
92
102
  class ConcreteResource(GenericResource):
93
103
  def __init__(self):
94
104
  super().__init__(model, serialization_fields)
@@ -10,7 +10,6 @@ class SeederError(RuntimeError):
10
10
 
11
11
 
12
12
  class BaseSeeder(ABC):
13
-
14
13
  def __init__(self):
15
14
  self.db = db
16
15
 
@@ -58,7 +58,9 @@ class PathUtils:
58
58
  return os.path.join(working_dir, splent_app, uploads_folder_name())
59
59
 
60
60
  if not splent_app:
61
- raise FileNotFoundError("SPLENT_APP not set — cannot resolve uploads directory.")
61
+ raise FileNotFoundError(
62
+ "SPLENT_APP not set — cannot resolve uploads directory."
63
+ )
62
64
 
63
65
  try:
64
66
  package = importlib.util.find_spec(splent_app)
@@ -68,4 +70,6 @@ class PathUtils:
68
70
  if package and package.origin:
69
71
  return os.path.join(os.path.dirname(package.origin), uploads_folder_name())
70
72
 
71
- raise FileNotFoundError(f"Could not resolve uploads directory for '{splent_app}'.")
73
+ raise FileNotFoundError(
74
+ f"Could not resolve uploads directory for '{splent_app}'."
75
+ )
@@ -95,8 +95,7 @@ class PyprojectReader:
95
95
  raw = self._data.get("tool", {}).get("splent", {}).get(key)
96
96
  if raw is None and key == "features":
97
97
  raw = (
98
- self._data
99
- .get("project", {})
98
+ self._data.get("project", {})
100
99
  .get("optional-dependencies", {})
101
100
  .get("features", [])
102
101
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: splent_framework
3
- Version: 1.2.5
3
+ Version: 1.2.7
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