spaceforge 1.0.0__py3-none-any.whl → 1.1.0__py3-none-any.whl

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.
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '1.0.0'
32
- __version_tuple__ = version_tuple = (1, 0, 0)
31
+ __version__ = version = '1.1.0'
32
+ __version_tuple__ = version_tuple = (1, 1, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
spaceforge/cls.py CHANGED
@@ -121,6 +121,7 @@ class Context:
121
121
  labels (Optional[List[str]]): Labels associated with the context.
122
122
  env (list): List of variables associated with the context.
123
123
  hooks (dict): Hooks associated with the context.
124
+ priority (optional[int]): The priority of the context, contexts with 0 priority run before contexts with 1 priority.
124
125
  """
125
126
 
126
127
  name_prefix: str
@@ -129,6 +130,7 @@ class Context:
129
130
  mounted_files: Optional[List[MountedFile]] = optional_field
130
131
  hooks: Optional[Dict[HookType, List[str]]] = optional_field
131
132
  labels: Optional[List[str]] = optional_field
133
+ priority: int = 0
132
134
 
133
135
 
134
136
  @pydantic_dataclass
spaceforge/generator.py CHANGED
@@ -30,6 +30,41 @@ from .plugin import SpaceforgePlugin
30
30
  static_binary_directory = "/mnt/workspace/plugins/plugin_binaries"
31
31
 
32
32
 
33
+ def _update_context_names_for_priority(contexts: List[Context]) -> List[Context]:
34
+ if len(contexts) <= 1:
35
+ return contexts
36
+
37
+ # Get unique priority values and sort them
38
+ priorities = sorted(set(ctx.priority for ctx in contexts))
39
+
40
+ # Create mapping from priority to letter prefix
41
+ priority_to_letter = {}
42
+
43
+ for i, priority in enumerate(priorities):
44
+ if priority == 0:
45
+ # Priority 0 gets 'Z' (lowest priority = furthest from A)
46
+ letter = "Z"
47
+ else:
48
+ # Higher priority numbers get letters closer to 'A'
49
+ # Reverse the mapping: higher priority index = closer to A
50
+ letter_index = max(0, 25 - i) # Start from Z and work backwards
51
+ letter = chr(ord("A") + letter_index)
52
+
53
+ priority_to_letter[priority] = letter * 5 # Repeat 5 times
54
+
55
+ # Update context names
56
+ for ctx in contexts:
57
+ # Remove existing prefix if it starts with repeated letters
58
+ name = ctx.name_prefix
59
+ if len(name) >= 5 and name[:5].isupper() and len(set(name[:5])) == 1:
60
+ name = name[5:]
61
+
62
+ prefix = priority_to_letter[ctx.priority]
63
+ ctx.name_prefix = prefix + "-" + name
64
+
65
+ return contexts
66
+
67
+
33
68
  class PluginGenerator:
34
69
  """Generates plugin.yaml from a Python plugin class."""
35
70
 
@@ -257,29 +292,39 @@ class PluginGenerator:
257
292
  if self.plugin_class is None:
258
293
  raise ValueError("Plugin class not loaded. Call load_plugin() first.")
259
294
 
260
- contexts = getattr(
261
- self.plugin_class,
262
- "__contexts__",
263
- [
264
- Context(
265
- name_prefix=self.plugin_class.__plugin_name__.lower(),
266
- description=f"Main context for {self.plugin_class.__plugin_name__}",
267
- )
268
- ],
269
- )
295
+ contexts = getattr(self.plugin_class, "__contexts__", [])
270
296
 
271
- if contexts[0].hooks is None:
272
- contexts[0].hooks = {}
273
- if contexts[0].mounted_files is None:
274
- contexts[0].mounted_files = []
275
- if contexts[0].env is None:
276
- contexts[0].env = []
297
+ main_context: Optional[Context] = None
298
+ main_context_found = False
299
+ for context in contexts:
300
+ if context.priority == 0:
301
+ main_context = context
302
+ main_context_found = True
303
+ break
304
+
305
+ if main_context is None:
306
+ main_context = Context(
307
+ name_prefix=self.plugin_class.__plugin_name__.lower(),
308
+ description=f"Main context for {self.plugin_class.__plugin_name__}",
309
+ )
310
+
311
+ if main_context.hooks is None:
312
+ main_context.hooks = {}
313
+ if main_context.mounted_files is None:
314
+ main_context.mounted_files = []
315
+ if main_context.env is None:
316
+ main_context.env = []
277
317
 
278
318
  # Add the hooks and mounted files to the first context
279
- merge(contexts[0].hooks, hooks, strategy=Strategy.TYPESAFE_ADDITIVE)
280
- contexts[0].mounted_files += mounted_files
319
+ merge(main_context.hooks, hooks, strategy=Strategy.TYPESAFE_ADDITIVE)
320
+ main_context.mounted_files += mounted_files
321
+
322
+ # Ensure the main context is first
323
+ if not main_context_found:
324
+ contexts.insert(0, main_context)
281
325
 
282
326
  self._map_variables_to_parameters(contexts)
327
+ contexts = _update_context_names_for_priority(contexts)
283
328
 
284
329
  return contexts
285
330
 
@@ -380,6 +425,7 @@ class PluginGenerator:
380
425
  field.name: getattr(data, field.name)
381
426
  for field in fields(data)
382
427
  if getattr(data, field.name) is not None
428
+ and field.name != "priority"
383
429
  }
384
430
  return self.represent_dict(filtered_dict)
385
431
 
spaceforge/runner.py CHANGED
@@ -73,11 +73,11 @@ class PluginRunner:
73
73
  return
74
74
 
75
75
  try:
76
- print(f"[SPACEPY] Running hook: {hook_name}")
76
+ print(f"[SpaceForge] Running hook: {hook_name}")
77
77
  method()
78
- print(f"[SPACEPY] Hook completed: {hook_name}")
78
+ print(f"[SpaceForge] Hook completed: {hook_name}")
79
79
  except Exception as e:
80
- print(f"[SPACEPY] Error running hook '{hook_name}': {e}")
80
+ print(f"[SpaceForge] Error running hook '{hook_name}': {e}")
81
81
  raise
82
82
 
83
83
 
spaceforge/schema.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$defs": {
3
3
  "Context": {
4
- "description": "A class to represent a context for a plugin.\n\nAttributes:\n name_prefix (str): The name of the context, will be appended with a unique ID.\n description (str): A description of the context.\n labels (Optional[List[str]]): Labels associated with the context.\n env (list): List of variables associated with the context.\n hooks (dict): Hooks associated with the context.",
4
+ "description": "A class to represent a context for a plugin.\n\nAttributes:\n name_prefix (str): The name of the context, will be appended with a unique ID.\n description (str): A description of the context.\n labels (Optional[List[str]]): Labels associated with the context.\n env (list): List of variables associated with the context.\n hooks (dict): Hooks associated with the context.\n priority (optional[int]): The priority of the context, contexts with 0 priority run before contexts with 1 priority.",
5
5
  "properties": {
6
6
  "name_prefix": {
7
7
  "title": "Name Prefix",
@@ -84,6 +84,11 @@
84
84
  }
85
85
  ],
86
86
  "title": "Labels"
87
+ },
88
+ "priority": {
89
+ "default": 0,
90
+ "title": "Priority",
91
+ "type": "integer"
87
92
  }
88
93
  },
89
94
  "required": [
spaceforge/test_runner.py CHANGED
@@ -170,8 +170,8 @@ class NotAPlugin:
170
170
  assert "after_plan" in getattr(runner.plugin_instance, "executed_hooks")
171
171
 
172
172
  # Verify print statements
173
- mock_print.assert_any_call("[SPACEPY] Running hook: after_plan")
174
- mock_print.assert_any_call("[SPACEPY] Hook completed: after_plan")
173
+ mock_print.assert_any_call("[SpaceForge] Running hook: after_plan")
174
+ mock_print.assert_any_call("[SpaceForge] Hook completed: after_plan")
175
175
 
176
176
  def test_run_hook_not_found(self) -> None:
177
177
  """Test running a hook that doesn't exist."""
@@ -210,7 +210,7 @@ class NotAPlugin:
210
210
 
211
211
  # Should print error message
212
212
  mock_print.assert_any_call(
213
- "[SPACEPY] Error running hook 'error_hook': Test error from hook"
213
+ "[SpaceForge] Error running hook 'error_hook': Test error from hook"
214
214
  )
215
215
 
216
216
  def test_run_hook_multiple_hooks(self) -> None:
@@ -61,8 +61,8 @@ class TestRunnerPlugin(SpaceforgePlugin):
61
61
  assert hasattr(runner.plugin_instance, "executed_hooks")
62
62
  assert "after_plan" in getattr(runner.plugin_instance, "executed_hooks")
63
63
 
64
- mock_print.assert_any_call("[SPACEPY] Running hook: after_plan")
65
- mock_print.assert_any_call("[SPACEPY] Hook completed: after_plan")
64
+ mock_print.assert_any_call("[SpaceForge] Running hook: after_plan")
65
+ mock_print.assert_any_call("[SpaceForge] Hook completed: after_plan")
66
66
 
67
67
  def test_should_print_error_when_hook_method_not_found(
68
68
  self, test_plugin_file: str
@@ -125,7 +125,7 @@ class ErrorPlugin(SpaceforgePlugin):
125
125
  runner.run_hook("error_hook")
126
126
 
127
127
  mock_print.assert_any_call(
128
- "[SPACEPY] Error running hook 'error_hook': Test error from hook"
128
+ "[SpaceForge] Error running hook 'error_hook': Test error from hook"
129
129
  )
130
130
 
131
131
  def test_should_execute_multiple_hooks_maintaining_state(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaceforge
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: A Python framework for building Spacelift plugins
5
5
  Home-page: https://github.com/spacelift-io/plugins
6
6
  Author: Spacelift
@@ -2,13 +2,13 @@ spaceforge/README.md,sha256=8o1Nuyasb4OxX3E7ZycyducOrR4J19bZcHrLvFeoFNg,7730
2
2
  spaceforge/__init__.py,sha256=TU-vvm15dK1ucixNW0V42eTT72x3_hmKSyxP4MC1Occ,589
3
3
  spaceforge/__main__.py,sha256=c3nAw4WBnHXIcfMlRV6Ja7r87pEhSeK-SAqiSYIasIY,643
4
4
  spaceforge/_version.py,sha256=RP_LfUd4ODnrfwn9nam8wB6bR3lM4VwmoRxK08Tkiiw,2155
5
- spaceforge/_version_scm.py,sha256=vLA4ITz09S-S435nq6yTF6l3qiSz6w4euS1rOxXgd1M,704
6
- spaceforge/cls.py,sha256=8jZ0noS-kIBQWHp7pWHOX6yzs5N30gIEm6-slWgBaKI,6182
5
+ spaceforge/_version_scm.py,sha256=ePNVzJOkxR8FY5bezqKQ_fgBRbzH1G7QTaRDHvGQRAY,704
6
+ spaceforge/cls.py,sha256=oYW5t5_xs9ZM6Ne_b4trxCPxLHQrqbqgUeibeM8O4PU,6329
7
7
  spaceforge/conftest.py,sha256=U-xCavCsgRAQXqflIIOMeq9pcGbeqRviUNkEXgZol8g,2141
8
- spaceforge/generator.py,sha256=7tt0zpqrD2pZsoVxQa0BfzBH1ZtGr6o3r_LmpBjrOJk,16739
8
+ spaceforge/generator.py,sha256=hCxtbOKmrGd7HCCz7HMaiK566kIre5MNxcJEx2TVURM,18430
9
9
  spaceforge/plugin.py,sha256=nNMus9cfVsazqvZG-buTWhK9CkeROdC5vQdOvc8EUQA,16129
10
- spaceforge/runner.py,sha256=aBQQLG8MFVrqCzM0X8HgERYbhLKbmlOHUPKjV7-xvpA,3163
11
- spaceforge/schema.json,sha256=F0STokM3YYT0xzcNdtnELZJJgpyi49mwk9Oj8ueD39s,10631
10
+ spaceforge/runner.py,sha256=EUZ98gmOiJ766zOSk7YcTTrLCtHfst1xf3iE2Xu7Tao,3172
11
+ spaceforge/schema.json,sha256=89IROLVlCj8txGMiLt4Bbuo_2muSxKoZCyaXQ2vuA9c,10869
12
12
  spaceforge/test_cls.py,sha256=nXAgbnFnGdFxrtA7vNXiePjNUASuoYW-lEuQGx9WMGs,468
13
13
  spaceforge/test_generator.py,sha256=Nst3YVu_iZbFopH6ajjxCfqYrZvybteGbwMfZzjBFnI,32615
14
14
  spaceforge/test_generator_binaries.py,sha256=X_7pPLGE45eQt-Kv9_ku__LsyLgOvViHc_BvVpSCMp0,7263
@@ -19,15 +19,15 @@ spaceforge/test_plugin.py,sha256=rZ4Uv_0lIR0qb1GFHkiosGO3WHTWhO7epz8INDxV8Q0,130
19
19
  spaceforge/test_plugin_file_operations.py,sha256=B0qvIo5EcfKMiHLhBv-hAnpSonn83ojcmJHXasydojA,3782
20
20
  spaceforge/test_plugin_hooks.py,sha256=rNCZZyd_SDMkm1x3yl5mjQ5tBMGm3YNd1U6h_niWRQs,2962
21
21
  spaceforge/test_plugin_inheritance.py,sha256=WHfvU5s-2GtfcI9-1bHXH7bacr77ikq68V3Z3BBQKvQ,3617
22
- spaceforge/test_runner.py,sha256=HOGJ-RAA1k82zLWPgtYVWseh7KMXJKGs2S2xDf2TgJo,17730
22
+ spaceforge/test_runner.py,sha256=fDnUf6gEuf1CNMxz6zs3xXvERQsQU3z8qy9KdUc0Wo4,17739
23
23
  spaceforge/test_runner_cli.py,sha256=Sf5X0O9Wc9EhGB5L8SzvlmO7QmgQZQoClSdNYefa-lQ,2299
24
24
  spaceforge/test_runner_core.py,sha256=eNR9YOwJwv7LsMtNQ4WXXMPIW6RE_A7hUp4bCpzz1Rk,3941
25
- spaceforge/test_runner_execution.py,sha256=BRlOApCmlpjE9YYvqHc8creSRu2vyPubzOCVXv-on0w,5335
25
+ spaceforge/test_runner_execution.py,sha256=GJhoECdhIY2M3MWcmTrIYfkJd2P5n86zixO3FY38_CQ,5344
26
26
  spaceforge/templates/binary_install.sh.j2,sha256=znz2G9mm3dOg0iqyxZoj4_ATBEi7HviMETMGRk5D7uM,536
27
27
  spaceforge/templates/ensure_spaceforge_and_run.sh.j2,sha256=g5BldIEve0IkZ-mCzTXfB_rFvyWqUJqymRRaaMrpp0s,550
28
- spaceforge-1.0.0.dist-info/licenses/LICENSE,sha256=wyljRrfnWY2ggQKkSCg3Nw2hxwPMmupopaKs9Kpgys8,1065
29
- spaceforge-1.0.0.dist-info/METADATA,sha256=74iP3CHYPghk_rzIsj9RT_2CglDl22W2APj2o8RjbE0,16803
30
- spaceforge-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- spaceforge-1.0.0.dist-info/entry_points.txt,sha256=qawuuKBSNTGg-njnQnhxxFldFvXYAPej6bF_f3iyQ48,56
32
- spaceforge-1.0.0.dist-info/top_level.txt,sha256=eVw-Lw4Th0oHM8Gx1Y8YetyNgbNbMBU00yWs-kwGeSs,11
33
- spaceforge-1.0.0.dist-info/RECORD,,
28
+ spaceforge-1.1.0.dist-info/licenses/LICENSE,sha256=wyljRrfnWY2ggQKkSCg3Nw2hxwPMmupopaKs9Kpgys8,1065
29
+ spaceforge-1.1.0.dist-info/METADATA,sha256=pn-bRqlzGeEYFB-mqbrdgfYWOJUvy9pU3lGsbUuR-Ro,16803
30
+ spaceforge-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
+ spaceforge-1.1.0.dist-info/entry_points.txt,sha256=qawuuKBSNTGg-njnQnhxxFldFvXYAPej6bF_f3iyQ48,56
32
+ spaceforge-1.1.0.dist-info/top_level.txt,sha256=eVw-Lw4Th0oHM8Gx1Y8YetyNgbNbMBU00yWs-kwGeSs,11
33
+ spaceforge-1.1.0.dist-info/RECORD,,