py-serverless-kit 0.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.
Files changed (71) hide show
  1. py_serverless_kit/__init__.py +47 -0
  2. py_serverless_kit/__main__.py +3 -0
  3. py_serverless_kit/_version.py +2 -0
  4. py_serverless_kit/cli/__init__.py +0 -0
  5. py_serverless_kit/cli/main.py +491 -0
  6. py_serverless_kit/cli/templates.py +212 -0
  7. py_serverless_kit/config/__init__.py +12 -0
  8. py_serverless_kit/config/loader.py +316 -0
  9. py_serverless_kit/config/schema.py +457 -0
  10. py_serverless_kit/core/__init__.py +0 -0
  11. py_serverless_kit/core/deployer.py +180 -0
  12. py_serverless_kit/core/describe.py +112 -0
  13. py_serverless_kit/core/project.py +147 -0
  14. py_serverless_kit/dashboard/__init__.py +3 -0
  15. py_serverless_kit/dashboard/generator.py +65 -0
  16. py_serverless_kit/dashboard/template.html +248 -0
  17. py_serverless_kit/local/__init__.py +3 -0
  18. py_serverless_kit/local/runner.py +234 -0
  19. py_serverless_kit/local/server.py +200 -0
  20. py_serverless_kit/packaging/__init__.py +3 -0
  21. py_serverless_kit/packaging/builder.py +252 -0
  22. py_serverless_kit/providers/__init__.py +15 -0
  23. py_serverless_kit/providers/alibaba.py +268 -0
  24. py_serverless_kit/providers/aws.py +508 -0
  25. py_serverless_kit/providers/azure.py +417 -0
  26. py_serverless_kit/providers/base.py +310 -0
  27. py_serverless_kit/providers/cloudflare.py +264 -0
  28. py_serverless_kit/providers/digitalocean.py +211 -0
  29. py_serverless_kit/providers/gcp.py +480 -0
  30. py_serverless_kit/providers/huawei.py +292 -0
  31. py_serverless_kit/providers/ibm.py +132 -0
  32. py_serverless_kit/providers/oracle.py +304 -0
  33. py_serverless_kit/providers/registry.py +60 -0
  34. py_serverless_kit/providers/schedule.py +153 -0
  35. py_serverless_kit/providers/tencent.py +275 -0
  36. py_serverless_kit/providers/terraform_doc.py +125 -0
  37. py_serverless_kit/runtime/__init__.py +6 -0
  38. py_serverless_kit/runtime/adapters/__init__.py +18 -0
  39. py_serverless_kit/runtime/adapters/_httpfn.py +91 -0
  40. py_serverless_kit/runtime/adapters/alibaba.py +147 -0
  41. py_serverless_kit/runtime/adapters/aws.py +201 -0
  42. py_serverless_kit/runtime/adapters/azure.py +185 -0
  43. py_serverless_kit/runtime/adapters/base.py +86 -0
  44. py_serverless_kit/runtime/adapters/cloudflare.py +126 -0
  45. py_serverless_kit/runtime/adapters/digitalocean.py +74 -0
  46. py_serverless_kit/runtime/adapters/gcp.py +176 -0
  47. py_serverless_kit/runtime/adapters/huawei.py +173 -0
  48. py_serverless_kit/runtime/adapters/ibm.py +91 -0
  49. py_serverless_kit/runtime/adapters/oracle.py +161 -0
  50. py_serverless_kit/runtime/adapters/tencent.py +151 -0
  51. py_serverless_kit/runtime/batch.py +47 -0
  52. py_serverless_kit/runtime/context.py +45 -0
  53. py_serverless_kit/runtime/dispatch.py +277 -0
  54. py_serverless_kit/runtime/errors.py +83 -0
  55. py_serverless_kit/runtime/event.py +267 -0
  56. py_serverless_kit/runtime/invoke.py +119 -0
  57. py_serverless_kit/runtime/log.py +69 -0
  58. py_serverless_kit/runtime/manifest.py +121 -0
  59. py_serverless_kit/runtime/response.py +177 -0
  60. py_serverless_kit/runtime/routing.py +74 -0
  61. py_serverless_kit/runtime/workflow.py +312 -0
  62. py_serverless_kit/terraform/__init__.py +3 -0
  63. py_serverless_kit/terraform/runner.py +104 -0
  64. py_serverless_kit/workflows/__init__.py +35 -0
  65. py_serverless_kit/workflows/asl.py +170 -0
  66. py_serverless_kit/workflows/gcp.py +328 -0
  67. py_serverless_kit-0.1.0.dist-info/METADATA +300 -0
  68. py_serverless_kit-0.1.0.dist-info/RECORD +71 -0
  69. py_serverless_kit-0.1.0.dist-info/WHEEL +4 -0
  70. py_serverless_kit-0.1.0.dist-info/entry_points.txt +3 -0
  71. py_serverless_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,211 @@
1
+ """DigitalOcean Functions.
2
+
3
+ The DigitalOcean Terraform provider has no resource for Functions, so pskit
4
+ renders a standard ``doctl serverless`` project (``do_project/project.yml`` +
5
+ ``packages/<service>-<stage>/<fn>/``) and deploys it from a ``terraform_data``
6
+ resource with ``local-exec`` provisioners. Terraform therefore tracks *when*
7
+ to redeploy (artifact hash) while ``doctl`` does the upload.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import hashlib
13
+ import shutil
14
+ from pathlib import Path
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ import yaml
18
+
19
+ from ..config.schema import ServiceConfig
20
+ from ..runtime.adapters.base import envelope
21
+ from . import schedule as sched
22
+ from .base import HTTP, SCHEDULE, Issue, Provider, RenderContext
23
+ from .terraform_doc import TerraformDoc
24
+
25
+ PROJECT_DIR = "do_project"
26
+
27
+
28
+ class DigitalOceanProvider(Provider):
29
+ name = "digitalocean"
30
+ display_name = "DigitalOcean Functions"
31
+ tier = "beta"
32
+ terraform_source = "terraform.io/builtin/terraform" # terraform_data + doctl, no cloud provider plugin
33
+ default_region = ""
34
+ capabilities = frozenset({HTTP, SCHEDULE})
35
+ max_name_length = 63
36
+ runtimes = {"python3.9": "python:3.9", "python3.11": "python:3.11"}
37
+ vendor_dependencies = True
38
+ max_timeout = 900
39
+ notes = (
40
+ "Deployed with `doctl serverless deploy` (doctl must be installed and authenticated: "
41
+ "`doctl auth init`). Requires options.namespace (fn-...); set options.api_host to get URLs. "
42
+ "Supports http (web: raw, public) and schedule triggers only. Runtimes: python3.9 / python3.11."
43
+ )
44
+
45
+ # ------------------------------------------------------------------ terraform header
46
+ def init_doc(self, ctx: RenderContext) -> TerraformDoc:
47
+ doc = TerraformDoc()
48
+ doc.required_version(ctx.config.provider.terraform.required_version)
49
+ state = ctx.config.provider.state
50
+ if state.backend:
51
+ doc.backend(state.backend, state.config)
52
+ return doc
53
+
54
+ def provider_block(self, ctx: RenderContext) -> Optional[Dict[str, Any]]:
55
+ return None
56
+
57
+ def region(self, config: ServiceConfig) -> str:
58
+ return config.provider.region or str(config.provider.options.get("region", ""))
59
+
60
+ # ------------------------------------------------------------------ bundle
61
+ def shim_files(self, config: ServiceConfig) -> Dict[str, str]:
62
+ return {
63
+ "__main__.py": (
64
+ "# Generated by py-serverless-kit. DigitalOcean entrypoint: main(event, context)\n"
65
+ "from py_serverless_kit.runtime.adapters.digitalocean import main # noqa: F401\n"
66
+ )
67
+ }
68
+
69
+ def extra_validation(self, config: ServiceConfig) -> List[Issue]:
70
+ issues: List[Issue] = []
71
+ if not config.provider.options.get("namespace"):
72
+ issues.append(
73
+ Issue(
74
+ "error",
75
+ "options.namespace is required (see `doctl serverless namespaces list`)",
76
+ "provider.options.namespace",
77
+ )
78
+ )
79
+ if shutil.which("doctl") is None:
80
+ issues.append(
81
+ Issue(
82
+ "warning",
83
+ "`doctl` is not on PATH; deploy will fail until it is installed and "
84
+ "authenticated (`doctl auth init`)",
85
+ "provider",
86
+ )
87
+ )
88
+ for name, fn in config.functions.items():
89
+ mem = config.memory_of(fn)
90
+ if not 128 <= mem <= 1024:
91
+ issues.append(
92
+ Issue(
93
+ "error",
94
+ f"memory {mem} MB is outside DigitalOcean's 128-1024 MB range",
95
+ f"functions.{name}.memory",
96
+ )
97
+ )
98
+ for ev in fn.events_of("http"):
99
+ if ev.auth != "none":
100
+ issues.append(
101
+ Issue(
102
+ "error",
103
+ f"auth `{ev.auth}` is not supported; DigitalOcean web functions "
104
+ "are public - validate tokens in the handler",
105
+ f"functions.{name}",
106
+ )
107
+ )
108
+ return issues
109
+
110
+ # ------------------------------------------------------------------ render
111
+ def package_name(self, ctx: RenderContext) -> str:
112
+ return ctx.prefix
113
+
114
+ def project_spec(self, ctx: RenderContext) -> Dict[str, Any]:
115
+ cfg = ctx.config
116
+ functions = []
117
+ for name, fn in ctx.function_items():
118
+ spec: Dict[str, Any] = {
119
+ "name": name,
120
+ "runtime": self.runtime_id(cfg.runtime_of(fn)),
121
+ "main": "main",
122
+ "web": "raw" if fn.events_of("http") else False,
123
+ "limits": {"timeout": cfg.timeout_of(fn) * 1000, "memory": cfg.memory_of(fn)},
124
+ "environment": ctx.environment(name, fn),
125
+ }
126
+ triggers = []
127
+ for i, ev in enumerate(fn.events_of("schedule")):
128
+ if not ev.enabled:
129
+ continue
130
+ triggers.append(
131
+ {
132
+ "name": f"{name}-{ev.name or i}",
133
+ "sourceType": "scheduler",
134
+ "sourceDetails": {
135
+ "cron": sched.to_cron5(ev),
136
+ "withBody": envelope("schedule", ev.input, source="digitalocean.scheduler"),
137
+ },
138
+ }
139
+ )
140
+ if triggers:
141
+ spec["triggers"] = triggers
142
+ spec.update(fn.options)
143
+ functions.append(spec)
144
+ return {"packages": [{"name": self.package_name(ctx), "functions": functions}]}
145
+
146
+ def write_project(self, ctx: RenderContext) -> str:
147
+ """Materialise the doctl project next to main.tf.json; return a content hash."""
148
+ root = ctx.work_dir / PROJECT_DIR
149
+ if root.exists():
150
+ shutil.rmtree(root)
151
+ spec = self.project_spec(ctx)
152
+ build = ctx.artifact.build_dir
153
+ pkg = self.package_name(ctx)
154
+ for fn in spec["packages"][0]["functions"]:
155
+ dest = root / "packages" / pkg / fn["name"]
156
+ if build is not None and Path(build).is_dir():
157
+ shutil.copytree(build, dest)
158
+ else: # pragma: no cover - build_dir is always set by the packager
159
+ dest.mkdir(parents=True)
160
+ text = yaml.safe_dump(spec, sort_keys=False)
161
+ (root / "project.yml").write_text(text, "utf-8")
162
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()
163
+
164
+ def render_resources(self, doc: TerraformDoc, ctx: RenderContext) -> None:
165
+ cfg = ctx.config
166
+ namespace = str(ctx.opt("namespace", ""))
167
+ pkg = self.package_name(ctx)
168
+ project_hash = self.write_project(ctx)
169
+ doc.resource(
170
+ "terraform_data",
171
+ "deploy",
172
+ {
173
+ "triggers_replace": [ctx.artifact.sha256, project_hash],
174
+ "input": {"namespace": namespace, "package": pkg},
175
+ "provisioner": [
176
+ {
177
+ "local-exec": {
178
+ "working_dir": "${path.module}",
179
+ "command": f"doctl serverless connect {namespace} && doctl serverless deploy {PROJECT_DIR}",
180
+ }
181
+ },
182
+ {
183
+ "local-exec": {
184
+ "when": "destroy",
185
+ "command": "doctl serverless connect ${self.input.namespace} && "
186
+ "doctl serverless undeploy --packages ${self.input.package}",
187
+ }
188
+ },
189
+ ],
190
+ },
191
+ )
192
+ api_host = str(ctx.opt("api_host", "")).rstrip("/")
193
+ fn_out: Dict[str, Dict[str, Any]] = {}
194
+ endpoint: Optional[str] = None
195
+ for name, fn in ctx.function_items():
196
+ url = f"{api_host}/api/v1/web/{namespace}/{pkg}/{name}" if api_host and fn.events_of("http") else None
197
+ out: Dict[str, Any] = {
198
+ "id": f"{pkg}/{name}",
199
+ "url": url,
200
+ "console": "https://cloud.digitalocean.com/functions",
201
+ }
202
+ if url:
203
+ out["base_url"] = url
204
+ endpoint = endpoint or f"{api_host}/api/v1/web/{namespace}/{pkg}"
205
+ fn_out[name] = out
206
+ wf_out = {n: {"id": f"{pkg}/{n}-orchestrator"} for n in cfg.workflows}
207
+ doc.output(
208
+ "pskit",
209
+ {"functions": fn_out, "workflows": wf_out, "endpoint": endpoint},
210
+ description="py-serverless-kit deployment summary",
211
+ )