apx-example 0.1.0__tar.gz → 0.2.0__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 (24) hide show
  1. {apx_example-0.1.0 → apx_example-0.2.0}/PKG-INFO +5 -4
  2. {apx_example-0.1.0 → apx_example-0.2.0}/README.md +3 -2
  3. apx_example-0.2.0/apx_example/overlays/cloud/example/DEPLOY.md +7 -0
  4. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/plugin.py +6 -3
  5. apx_example-0.2.0/apx_example/target.py +56 -0
  6. apx_example-0.2.0/apx_example/tools.py +34 -0
  7. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/PKG-INFO +5 -4
  8. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/SOURCES.txt +1 -0
  9. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/entry_points.txt +3 -0
  10. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/requires.txt +1 -1
  11. {apx_example-0.1.0 → apx_example-0.2.0}/pyproject.toml +5 -2
  12. {apx_example-0.1.0 → apx_example-0.2.0}/tests/test_plugin.py +21 -0
  13. apx_example-0.1.0/apx_example/overlays/cloud/example/DEPLOY.md +0 -3
  14. apx_example-0.1.0/apx_example/tools.py +0 -21
  15. {apx_example-0.1.0 → apx_example-0.2.0}/LICENSE +0 -0
  16. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/__init__.py +0 -0
  17. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/cli.py +0 -0
  18. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/overlays/cloud/example/deploy/run.sh +0 -0
  19. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/overlays/index.json +0 -0
  20. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/release.py +0 -0
  21. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example/rules.py +0 -0
  22. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/dependency_links.txt +0 -0
  23. {apx_example-0.1.0 → apx_example-0.2.0}/apx_example.egg-info/top_level.txt +0 -0
  24. {apx_example-0.1.0 → apx_example-0.2.0}/setup.cfg +0 -0
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apx-example
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Template for an Action Platform plugin: tools, a CLI command, an overlay, a release strategy, a replaced slot, lifecycle hooks.
5
5
  Author-email: Action Platform <cloud@actionplatform.io>
6
6
  License-Expression: Apache-2.0
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: action-platform>=0.16
10
+ Requires-Dist: action-platform>=0.26
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest>=8; extra == "dev"
13
13
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -21,16 +21,17 @@ Template for an [Action Platform](https://github.com/actionplatform/action-platf
21
21
  | File | Extension point |
22
22
  |---|---|
23
23
  | `plugin.py` | the `Plugin`: slug, `needs`, `register`, `after_release`, `after_deploy` |
24
- | `tools.py` | an MCP tool, published as `example_hello` with input and output schemas |
24
+ | `tools.py` | MCP tools `example_hello` and `example_remember`, with input and output schemas; `remember` writes the plugin's options store |
25
25
  | `cli.py` | `action-platform example hello` |
26
26
  | `rules.py` | a replaced core slot (`gitflow_rules`) — stricter branch kinds while the plugin is enabled |
27
27
  | `release.py` | named providers: `[release] strategy = "calver"`, `[release] changelog = "plain"` |
28
+ | `target.py` | the `DeployTarget` for the `example` cloud: `preflight`, `readiness` (checks before a deploy), `deploy` (streams its output to the job log), `verify`, `diagnose`, `delete` |
28
29
  | `overlays/` | a cloud overlay `example` — plain files, copied as they are by `action-platform cloud set example`; add a `cookiecutter.json` only when the files need rendering |
29
30
 
30
31
  ## Use this template
31
32
 
32
33
  1. Create a repository from it, named `apx-<slug>`.
33
- 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `cli.py`, `overlays/index.json`.
34
+ 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `target.py` (`name`), `cli.py`, `overlays/index.json`.
34
35
  3. Delete what you do not need; a plugin with only `plugin.py` and one tool is fine.
35
36
  4. `pip install -e ".[dev]"`, `pytest`, `action-platform plugin list` (shows `example` once installed).
36
37
  5. Publish to PyPI; open a pull request to [plugins-index](https://github.com/actionplatform/plugins-index) with `<slug>.json`.
@@ -5,16 +5,17 @@ Template for an [Action Platform](https://github.com/actionplatform/action-platf
5
5
  | File | Extension point |
6
6
  |---|---|
7
7
  | `plugin.py` | the `Plugin`: slug, `needs`, `register`, `after_release`, `after_deploy` |
8
- | `tools.py` | an MCP tool, published as `example_hello` with input and output schemas |
8
+ | `tools.py` | MCP tools `example_hello` and `example_remember`, with input and output schemas; `remember` writes the plugin's options store |
9
9
  | `cli.py` | `action-platform example hello` |
10
10
  | `rules.py` | a replaced core slot (`gitflow_rules`) — stricter branch kinds while the plugin is enabled |
11
11
  | `release.py` | named providers: `[release] strategy = "calver"`, `[release] changelog = "plain"` |
12
+ | `target.py` | the `DeployTarget` for the `example` cloud: `preflight`, `readiness` (checks before a deploy), `deploy` (streams its output to the job log), `verify`, `diagnose`, `delete` |
12
13
  | `overlays/` | a cloud overlay `example` — plain files, copied as they are by `action-platform cloud set example`; add a `cookiecutter.json` only when the files need rendering |
13
14
 
14
15
  ## Use this template
15
16
 
16
17
  1. Create a repository from it, named `apx-<slug>`.
17
- 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `cli.py`, `overlays/index.json`.
18
+ 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `target.py` (`name`), `cli.py`, `overlays/index.json`.
18
19
  3. Delete what you do not need; a plugin with only `plugin.py` and one tool is fine.
19
20
  4. `pip install -e ".[dev]"`, `pytest`, `action-platform plugin list` (shows `example` once installed).
20
21
  5. Publish to PyPI; open a pull request to [plugins-index](https://github.com/actionplatform/plugins-index) with `<slug>.json`.
@@ -0,0 +1,7 @@
1
+ # Deploy — example
2
+
3
+ This project deploys to the `example` target. The files under `deploy/` came from the `example` plugin's overlay, copied as they are — no templating.
4
+
5
+ The contract a real cloud overlay follows: every web project **serves HTTP on `$PORT`**, and the overlay's build step is `ap-build package` (from [images-base](https://github.com/actionplatform/images-base)), which assembles the app, its dependencies and `run.sh` for any language. One `template.yaml` (or Dockerfile, or workflow) per cloud is enough — no files per language. See [apx-aws-lambda](https://github.com/actionplatform/apx-aws-lambda) for the real thing: `template.yaml` with the runtime chosen by language, a `Makefile` with one line, `requirements/` with the least-privilege policy.
6
+
7
+ The platform calls the target's `readiness(ctx)` after every release, for every scope of the app, and refuses a deploy the checks block. The scope's name arrives as `ctx.stage`.
@@ -15,8 +15,8 @@ from apx_example.tools import register_tools
15
15
 
16
16
  class ExamplePlugin(Plugin):
17
17
  slug = "example"
18
- description = "Template plugin: a tool, a command, an overlay, a release strategy, stricter git-flow"
19
- min_core = "0.16"
18
+ description = "Template plugin: a tool, a command, a deploy target with readiness, an overlay, a release strategy, stricter git-flow"
19
+ min_core = "0.26"
20
20
  needs = ["nothing outside this machine"]
21
21
 
22
22
  @property
@@ -24,8 +24,10 @@ class ExamplePlugin(Plugin):
24
24
  return Path(__file__).parent / "overlays"
25
25
 
26
26
  def register(self, surface: Surface) -> None:
27
+ self.options = surface.options
28
+
27
29
  if surface.mcp is not None:
28
- register_tools(surface.mcp)
30
+ register_tools(surface.mcp, surface.options)
29
31
 
30
32
  if surface.cli is not None:
31
33
  surface.cli.add_typer(cli.app, name="example")
@@ -33,6 +35,7 @@ class ExamplePlugin(Plugin):
33
35
  surface.core.replace("gitflow_rules", StrictRules)
34
36
 
35
37
  def after_release(self, ctx: Context) -> None:
38
+ self.options.set("last_release", ctx.next_version)
36
39
  logger.info(f"example: released {ctx.next_version} from {ctx.branch}")
37
40
 
38
41
  def after_deploy(self, results: list[DeployResult]) -> None:
@@ -0,0 +1,56 @@
1
+ """The deploy target: what the platform calls to ship a release to the `example` cloud. Replace the bodies with your cloud's CLI; keep the shape."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from action_platform.abc import DeployTarget
6
+ from action_platform.core.context import Check, Context, DeployResult, Diagnosis
7
+ from action_platform.core.process import stream
8
+ from action_platform.logging import logger
9
+
10
+
11
+ class ExampleTarget(DeployTarget):
12
+ name = "example"
13
+
14
+ def preflight(self, ctx: Context) -> None:
15
+ if not (ctx.repo_root / "deploy" / "run.sh").exists():
16
+ raise FileNotFoundError(
17
+ "deploy/run.sh not found: apply the example overlay first"
18
+ )
19
+
20
+ def readiness(self, ctx: Context) -> list[Check]:
21
+ present = (ctx.repo_root / "deploy" / "run.sh").exists()
22
+
23
+ return [
24
+ Check(
25
+ "example.overlay",
26
+ present,
27
+ "deploy/run.sh is there" if present else "deploy/run.sh not found",
28
+ fix=None if present else "action-platform cloud set example",
29
+ )
30
+ ]
31
+
32
+ def deploy(self, ctx: Context) -> DeployResult:
33
+ result = stream(
34
+ ["sh", "deploy/run.sh"],
35
+ cwd=ctx.repo_root,
36
+ env={**ctx.env, "SCOPE": ctx.stage},
37
+ )
38
+ logger.info("example: deployed %s to %s", ctx.next_version, ctx.stage)
39
+
40
+ return DeployResult(
41
+ ok=result.ok,
42
+ target=self.name,
43
+ version=ctx.next_version,
44
+ error=None if result.ok else result.output,
45
+ )
46
+
47
+ def verify(self, version: str, stage: str | None = None) -> bool:
48
+ return True
49
+
50
+ def diagnose(self, ctx: Context) -> Diagnosis:
51
+ return Diagnosis(
52
+ ok=True, target=self.name, status="example", details={"scope": ctx.stage}
53
+ )
54
+
55
+ def delete(self, ctx: Context) -> None:
56
+ logger.info("example: nothing to tear down for %s", ctx.stage)
@@ -0,0 +1,34 @@
1
+ """MCP tools. They come out as `example_<name>`; annotate inputs and return a model so the client gets schemas. `options` is the plugin's own store — a file on a machine, a table on the hosted platform."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated, Any
6
+
7
+ from action_platform.mcp.annotations import READ_ONLY, WRITES_LOCAL
8
+ from action_platform.plugins import Options
9
+ from pydantic import BaseModel, Field
10
+
11
+
12
+ class Greeting(BaseModel):
13
+ greeting: str
14
+
15
+
16
+ class Remembered(BaseModel):
17
+ key: str
18
+ value: Any
19
+
20
+
21
+ def register_tools(mcp: Any, options: Options) -> None:
22
+ @mcp.tool(annotations=READ_ONLY)
23
+ def hello(
24
+ name: Annotated[str, Field(description="Who to greet")] = "world",
25
+ ) -> Greeting:
26
+ """Say hello — the smallest tool a plugin can add. The word comes from the plugin's options when set."""
27
+ return Greeting(greeting=f"{options.get('greeting', 'hello')} {name}")
28
+
29
+ @mcp.tool(annotations=WRITES_LOCAL)
30
+ def remember(key: str, value: str) -> Remembered:
31
+ """Store a value in the plugin's options — the platform keeps it for the next call."""
32
+ options.set(key, value)
33
+
34
+ return Remembered(key=key, value=value)
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apx-example
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Template for an Action Platform plugin: tools, a CLI command, an overlay, a release strategy, a replaced slot, lifecycle hooks.
5
5
  Author-email: Action Platform <cloud@actionplatform.io>
6
6
  License-Expression: Apache-2.0
7
7
  Requires-Python: >=3.11
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: action-platform>=0.16
10
+ Requires-Dist: action-platform>=0.26
11
11
  Provides-Extra: dev
12
12
  Requires-Dist: pytest>=8; extra == "dev"
13
13
  Requires-Dist: ruff>=0.6; extra == "dev"
@@ -21,16 +21,17 @@ Template for an [Action Platform](https://github.com/actionplatform/action-platf
21
21
  | File | Extension point |
22
22
  |---|---|
23
23
  | `plugin.py` | the `Plugin`: slug, `needs`, `register`, `after_release`, `after_deploy` |
24
- | `tools.py` | an MCP tool, published as `example_hello` with input and output schemas |
24
+ | `tools.py` | MCP tools `example_hello` and `example_remember`, with input and output schemas; `remember` writes the plugin's options store |
25
25
  | `cli.py` | `action-platform example hello` |
26
26
  | `rules.py` | a replaced core slot (`gitflow_rules`) — stricter branch kinds while the plugin is enabled |
27
27
  | `release.py` | named providers: `[release] strategy = "calver"`, `[release] changelog = "plain"` |
28
+ | `target.py` | the `DeployTarget` for the `example` cloud: `preflight`, `readiness` (checks before a deploy), `deploy` (streams its output to the job log), `verify`, `diagnose`, `delete` |
28
29
  | `overlays/` | a cloud overlay `example` — plain files, copied as they are by `action-platform cloud set example`; add a `cookiecutter.json` only when the files need rendering |
29
30
 
30
31
  ## Use this template
31
32
 
32
33
  1. Create a repository from it, named `apx-<slug>`.
33
- 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `cli.py`, `overlays/index.json`.
34
+ 2. Rename `apx_example` → `apx_<slug>`, `example` → `<slug>` in `pyproject.toml` (package name, entry points), `plugin.py` (`slug`), `target.py` (`name`), `cli.py`, `overlays/index.json`.
34
35
  3. Delete what you do not need; a plugin with only `plugin.py` and one tool is fine.
35
36
  4. `pip install -e ".[dev]"`, `pytest`, `action-platform plugin list` (shows `example` once installed).
36
37
  5. Publish to PyPI; open a pull request to [plugins-index](https://github.com/actionplatform/plugins-index) with `<slug>.json`.
@@ -6,6 +6,7 @@ apx_example/cli.py
6
6
  apx_example/plugin.py
7
7
  apx_example/release.py
8
8
  apx_example/rules.py
9
+ apx_example/target.py
9
10
  apx_example/tools.py
10
11
  apx_example.egg-info/PKG-INFO
11
12
  apx_example.egg-info/SOURCES.txt
@@ -1,6 +1,9 @@
1
1
  [action_platform.changelog]
2
2
  plain = apx_example.release:Plain
3
3
 
4
+ [action_platform.deploy_target]
5
+ example = apx_example.target:ExampleTarget
6
+
4
7
  [action_platform.plugins]
5
8
  example = apx_example:ExamplePlugin
6
9
 
@@ -1,4 +1,4 @@
1
- action-platform>=0.16
1
+ action-platform>=0.26
2
2
 
3
3
  [dev]
4
4
  pytest>=8
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "apx-example"
3
- version = "0.1.0"
3
+ version = "0.2.0"
4
4
  description = "Template for an Action Platform plugin: tools, a CLI command, an overlay, a release strategy, a replaced slot, lifecycle hooks."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
7
7
  requires-python = ">=3.11"
8
8
  authors = [{ name = "Action Platform", email = "cloud@actionplatform.io" }]
9
- dependencies = ["action-platform>=0.16"]
9
+ dependencies = ["action-platform>=0.26"]
10
10
 
11
11
  [project.optional-dependencies]
12
12
  dev = ["pytest>=8", "ruff>=0.6", "action-platform[mcp]"]
@@ -14,6 +14,9 @@ dev = ["pytest>=8", "ruff>=0.6", "action-platform[mcp]"]
14
14
  [project.entry-points."action_platform.plugins"]
15
15
  example = "apx_example:ExamplePlugin"
16
16
 
17
+ [project.entry-points."action_platform.deploy_target"]
18
+ example = "apx_example.target:ExampleTarget"
19
+
17
20
  [project.entry-points."action_platform.release_strategy"]
18
21
  calver = "apx_example.release:Calver"
19
22
 
@@ -57,3 +57,24 @@ class ExamplePluginTest(unittest.TestCase):
57
57
  def test_release_providers(self):
58
58
  self.assertTrue(Calver().next("1.0.0", "patch", False, []).count(".") == 2)
59
59
  self.assertIn("- feat: x", Plain().render("1.0.0", ["feat: x"]))
60
+
61
+
62
+ class TargetTest(unittest.TestCase):
63
+ def test_readiness_names_the_overlay(self):
64
+ from pathlib import Path
65
+ from tempfile import TemporaryDirectory
66
+
67
+ from action_platform.core.context import Context
68
+
69
+ from apx_example.target import ExampleTarget
70
+
71
+ with TemporaryDirectory() as tmp:
72
+ root = Path(tmp)
73
+ missing = ExampleTarget().readiness(Context(repo_root=root, stage="dev"))
74
+ (root / "deploy").mkdir()
75
+ (root / "deploy" / "run.sh").write_text("#!/bin/sh\necho ok\n")
76
+ present = ExampleTarget().readiness(Context(repo_root=root, stage="dev"))
77
+
78
+ self.assertFalse(missing[0].ok)
79
+ self.assertEqual(missing[0].fix, "action-platform cloud set example")
80
+ self.assertTrue(present[0].ok)
@@ -1,3 +0,0 @@
1
- # Deploy — example
2
-
3
- This project deploys to the example target. The files under `deploy/` came from the `example` plugin's overlay, copied as they are — no templating. Replace them with what your target needs (a `template.yaml`, a `Dockerfile`, a workflow) and ship the matching `DeployTarget` under the `action_platform.deploy_target` entry-point group.
@@ -1,21 +0,0 @@
1
- """MCP tools. They come out as `example_<name>`; annotate inputs and return a model or a typed dict so the client gets schemas."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import Annotated, Any
6
-
7
- from action_platform.mcp.annotations import READ_ONLY
8
- from pydantic import BaseModel, Field
9
-
10
-
11
- class Greeting(BaseModel):
12
- greeting: str
13
-
14
-
15
- def register_tools(mcp: Any) -> None:
16
- @mcp.tool(annotations=READ_ONLY)
17
- def hello(
18
- name: Annotated[str, Field(description="Who to greet")] = "world",
19
- ) -> Greeting:
20
- """Say hello — the smallest tool a plugin can add."""
21
- return Greeting(greeting=f"hello {name}")
File without changes
File without changes