workpeg 0.4.2__tar.gz → 0.5.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 (38) hide show
  1. {workpeg-0.4.2/src/workpeg.egg-info → workpeg-0.5.0}/PKG-INFO +1 -1
  2. {workpeg-0.4.2 → workpeg-0.5.0}/pyproject.toml +1 -1
  3. workpeg-0.5.0/src/workpeg/cli.py +373 -0
  4. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/run.py +3 -1
  5. workpeg-0.5.0/src/workpeg/utils.py +10 -0
  6. {workpeg-0.4.2 → workpeg-0.5.0/src/workpeg.egg-info}/PKG-INFO +1 -1
  7. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg.egg-info/SOURCES.txt +3 -1
  8. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_cli.py +35 -15
  9. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_create_new.py +4 -5
  10. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_run.py +67 -14
  11. workpeg-0.5.0/tests/test_utils.py +46 -0
  12. workpeg-0.4.2/src/workpeg/cli.py +0 -118
  13. {workpeg-0.4.2 → workpeg-0.5.0}/LICENSE +0 -0
  14. {workpeg-0.4.2 → workpeg-0.5.0}/MANIFEST.in +0 -0
  15. {workpeg-0.4.2 → workpeg-0.5.0}/README.md +0 -0
  16. {workpeg-0.4.2 → workpeg-0.5.0}/setup.cfg +0 -0
  17. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/__init__.py +0 -0
  18. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/build.py +0 -0
  19. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/config.py +0 -0
  20. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/context.py +0 -0
  21. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/create_new.py +0 -0
  22. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/runtime.py +0 -0
  23. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/submit.py +0 -0
  24. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/__init__.py +0 -0
  25. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/Dockerfile +0 -0
  26. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/LICENSE +0 -0
  27. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/README.md +0 -0
  28. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/app/__init__.py +0 -0
  29. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/app/main.py +0 -0
  30. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg/templates/functions/requirements.txt +0 -0
  31. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg.egg-info/dependency_links.txt +0 -0
  32. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg.egg-info/entry_points.txt +0 -0
  33. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg.egg-info/requires.txt +0 -0
  34. {workpeg-0.4.2 → workpeg-0.5.0}/src/workpeg.egg-info/top_level.txt +0 -0
  35. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_build.py +0 -0
  36. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_context.py +0 -0
  37. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_runtime.py +0 -0
  38. {workpeg-0.4.2 → workpeg-0.5.0}/tests/test_submit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workpeg
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Workpeg function runtime and SDK
5
5
  Author-email: Workpeg <support@workpeg.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "workpeg"
7
- version = "0.4.2"
7
+ version = "0.5.0"
8
8
  description = "Workpeg function runtime and SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,373 @@
1
+ import argparse
2
+ import sys
3
+
4
+ from workpeg import build, create_new, run, runtime, submit
5
+
6
+ try:
7
+ from importlib.metadata import version
8
+ __version__ = version("workpeg")
9
+ except Exception:
10
+ __version__ = "unknown"
11
+
12
+
13
+ def build_parser() -> argparse.ArgumentParser:
14
+ p = argparse.ArgumentParser(
15
+ prog="workpeg",
16
+ description=(
17
+ "Workpeg CLI\n\n"
18
+ "Build, run, and deploy Workpeg workloads."
19
+ ),
20
+ formatter_class=argparse.RawTextHelpFormatter,
21
+ epilog=(
22
+ "Examples:\n"
23
+ " workpeg function new hello\n"
24
+ " workpeg function build\n"
25
+ " workpeg function run --with docker\n"
26
+ "\n"
27
+ "Use 'workpeg <namespace> --help' for more information.\n"
28
+ ),
29
+ )
30
+
31
+ p.add_argument(
32
+ "-v",
33
+ "--version",
34
+ action="version",
35
+ version=f"workpeg {__version__}",
36
+ )
37
+
38
+ root_sub = p.add_subparsers(
39
+ dest="namespace",
40
+ metavar="{function}",
41
+ )
42
+
43
+ #
44
+ # FUNCTION NAMESPACE
45
+ #
46
+ function_p = root_sub.add_parser(
47
+ "function",
48
+ help="Manage Workpeg Functions",
49
+ description=(
50
+ "Manage Workpeg Functions.\n\n"
51
+ "Functions are portable execution units that run\n"
52
+ "inside isolated runtimes such as Docker and\n"
53
+ "future Firecracker microVM runtimes."
54
+ ),
55
+ formatter_class=argparse.RawTextHelpFormatter,
56
+ epilog=(
57
+ "Examples:\n"
58
+ " workpeg function new hello\n"
59
+ " workpeg function build\n"
60
+ " workpeg function run --with docker\n"
61
+ " workpeg function runtime --server\n"
62
+ " workpeg function submit hello:1.0.0\n"
63
+ ),
64
+ )
65
+
66
+ function_p.set_defaults(_namespace_parser=function_p)
67
+
68
+ function_sub = function_p.add_subparsers(
69
+ dest="command",
70
+ metavar="{new,build,run,runtime,submit}",
71
+ )
72
+
73
+ #
74
+ # FUNCTION NEW
75
+ #
76
+ new_p = function_sub.add_parser(
77
+ "new",
78
+ help="Create a new function project",
79
+ description=(
80
+ "Create a new Workpeg Function project scaffold.\n\n"
81
+ "Generated structure includes:\n"
82
+ " - app/main.py\n"
83
+ " - workpeg.json\n"
84
+ " - Dockerfile\n"
85
+ " - tests/\n"
86
+ ),
87
+ formatter_class=argparse.RawTextHelpFormatter,
88
+ epilog=(
89
+ "Examples:\n"
90
+ " workpeg function new hello\n"
91
+ " workpeg function new hello --force\n"
92
+ ),
93
+ )
94
+
95
+ new_p.add_argument(
96
+ "path",
97
+ help="Directory path for the new function project",
98
+ )
99
+
100
+ new_p.add_argument(
101
+ "--force",
102
+ action="store_true",
103
+ help="Overwrite existing files if the directory already exists",
104
+ )
105
+
106
+ new_p.set_defaults(_handler="function.new")
107
+
108
+ #
109
+ # FUNCTION RUNTIME
110
+ #
111
+ runtime_p = function_sub.add_parser(
112
+ "runtime",
113
+ help="Run the raw function runtime",
114
+ description=(
115
+ "Run the low-level Workpeg runtime.\n\n"
116
+ "Default mode:\n"
117
+ " Reads a single invocation from stdin.\n\n"
118
+ "Server mode:\n"
119
+ " Starts a persistent HTTP runtime."
120
+ ),
121
+ formatter_class=argparse.RawTextHelpFormatter,
122
+ epilog=(
123
+ "Examples:\n"
124
+ " echo '{\"context\": {}, \"payload\": {}}' | \\\n"
125
+ " workpeg function runtime\n\n"
126
+ " workpeg function runtime --server\n"
127
+ ),
128
+ )
129
+
130
+ runtime_p.add_argument(
131
+ "--server",
132
+ action="store_true",
133
+ help="Run as a persistent HTTP server",
134
+ )
135
+
136
+ runtime_p.set_defaults(_handler="function.runtime")
137
+
138
+ #
139
+ # FUNCTION BUILD
140
+ #
141
+ build_p = function_sub.add_parser(
142
+ "build",
143
+ help="Build the function Docker image",
144
+ description=(
145
+ "Build a Docker image for the current function project."
146
+ ),
147
+ formatter_class=argparse.RawTextHelpFormatter,
148
+ epilog=(
149
+ "Examples:\n"
150
+ " workpeg function build\n"
151
+ " workpeg function build --tag my-image\n"
152
+ ),
153
+ )
154
+
155
+ build_p.add_argument(
156
+ "--path",
157
+ default=".",
158
+ help="Path to the function project (default: current directory)",
159
+ )
160
+
161
+ build_p.add_argument(
162
+ "--tag",
163
+ default=None,
164
+ help="Custom Docker image tag",
165
+ )
166
+
167
+ build_p.set_defaults(_handler="function.build")
168
+
169
+ #
170
+ # FUNCTION RUN
171
+ #
172
+ run_p = function_sub.add_parser(
173
+ "run",
174
+ help="Run the function using a configured runtime",
175
+ description=(
176
+ "Run a Workpeg Function locally.\n\n"
177
+ "Docker runtime:\n"
178
+ " Starts a warm containerized runtime.\n\n"
179
+ "Cracker runtime:\n"
180
+ " Reserved for future Firecracker microVM support."
181
+ ),
182
+ formatter_class=argparse.RawTextHelpFormatter,
183
+ epilog=(
184
+ "Examples:\n"
185
+ " workpeg function run --with docker\n"
186
+ " workpeg function run --network workpeg_net\n"
187
+ " workpeg function run --name fn-hello\n"
188
+ " workpeg function run --no-expose\n"
189
+ ),
190
+ )
191
+
192
+ run_p.add_argument(
193
+ "--with",
194
+ dest="with_runtime",
195
+ choices=["docker", "cracker"],
196
+ help="Runtime backend to use",
197
+ )
198
+
199
+ run_p.add_argument(
200
+ "--path",
201
+ default=".",
202
+ help="Path to the function project",
203
+ )
204
+
205
+ run_p.add_argument(
206
+ "--no-build",
207
+ action="store_true",
208
+ help="Skip Docker image build step",
209
+ )
210
+
211
+ run_p.add_argument(
212
+ "--network",
213
+ default=None,
214
+ help="Docker network to attach the container to",
215
+ )
216
+
217
+ run_p.add_argument(
218
+ "--name",
219
+ default=None,
220
+ help="Container name",
221
+ )
222
+
223
+ run_p.add_argument(
224
+ "--no-expose",
225
+ action="store_true",
226
+ help="Do not expose the runtime port to the host",
227
+ )
228
+
229
+ run_p.set_defaults(_handler="function.run")
230
+
231
+ #
232
+ # FUNCTION SUBMIT
233
+ #
234
+ submit_p = function_sub.add_parser(
235
+ "submit",
236
+ help="Submit a function bundle",
237
+ description=(
238
+ "Package and upload a Workpeg Function to the registry."
239
+ ),
240
+ formatter_class=argparse.RawTextHelpFormatter,
241
+ epilog=(
242
+ "Examples:\n"
243
+ " workpeg function submit hello:1.0.0\n"
244
+ " workpeg function submit hello:1.0.0 --timeout 30\n"
245
+ ),
246
+ )
247
+
248
+ submit_p.add_argument(
249
+ "ref",
250
+ help="Function reference in format <name>:<version>",
251
+ )
252
+
253
+ submit_p.add_argument(
254
+ "--api",
255
+ default=None,
256
+ help="Override Workpeg registry API base URL",
257
+ )
258
+
259
+ submit_p.add_argument(
260
+ "--path",
261
+ default=None,
262
+ help="Path to the function project",
263
+ )
264
+
265
+ submit_p.add_argument(
266
+ "--timeout",
267
+ type=int,
268
+ default=None,
269
+ help="HTTP request timeout in seconds",
270
+ )
271
+
272
+ submit_p.set_defaults(_handler="function.submit")
273
+
274
+ return p
275
+
276
+
277
+ def main(argv=None) -> None:
278
+ argv = argv if argv is not None else sys.argv[1:]
279
+ parser = build_parser()
280
+
281
+ if not argv:
282
+ parser.print_help()
283
+ return
284
+
285
+ args = parser.parse_args(argv)
286
+
287
+ if not hasattr(args, "_handler"):
288
+ parser.print_help()
289
+ return
290
+
291
+ #
292
+ # function new
293
+ #
294
+ if args._handler == "function.new":
295
+ try:
296
+ out = create_new.create_new_project(
297
+ args.path,
298
+ force=args.force,
299
+ )
300
+ print(str(out))
301
+ except create_new.TemplateError as e:
302
+ print(f"ERROR: {e}", file=sys.stderr)
303
+ raise SystemExit(2)
304
+ return
305
+
306
+ #
307
+ # function runtime
308
+ #
309
+ if args._handler == "function.runtime":
310
+ runtime_argv = []
311
+
312
+ if args.server:
313
+ runtime_argv.append("--server")
314
+
315
+ runtime.main(runtime_argv)
316
+ return
317
+
318
+ #
319
+ # function submit
320
+ #
321
+ if args._handler == "function.submit":
322
+ submit_argv = [args.ref]
323
+
324
+ if args.api:
325
+ submit_argv += ["--api", args.api]
326
+
327
+ if args.path:
328
+ submit_argv += ["--path", args.path]
329
+
330
+ if args.timeout is not None:
331
+ submit_argv += ["--timeout", str(args.timeout)]
332
+
333
+ submit.main(submit_argv)
334
+ return
335
+
336
+ #
337
+ # function build
338
+ #
339
+ if args._handler == "function.build":
340
+ image = build.build_image(
341
+ path=args.path,
342
+ tag=args.tag,
343
+ )
344
+
345
+ print(image)
346
+ return
347
+
348
+ #
349
+ # function run
350
+ #
351
+ if args._handler == "function.run":
352
+ run_argv = []
353
+
354
+ if args.with_runtime:
355
+ run_argv += ["--with", args.with_runtime]
356
+
357
+ if args.path:
358
+ run_argv += ["--path", args.path]
359
+
360
+ if args.no_build:
361
+ run_argv.append("--no-build")
362
+
363
+ if args.network:
364
+ run_argv += ["--network", args.network]
365
+
366
+ if args.name:
367
+ run_argv += ["--name", args.name]
368
+
369
+ if args.no_expose:
370
+ run_argv.append("--no-expose")
371
+
372
+ run.main(run_argv)
373
+ return
@@ -27,7 +27,7 @@ def run_with_docker(
27
27
  cfg.get("build", {}).get("image") or project_path.name
28
28
  )
29
29
 
30
- cmd = ["docker", "run", "--rm"]
30
+ cmd = ["docker", "run", "-d"]
31
31
 
32
32
  if name:
33
33
  cmd += ["--name", name]
@@ -38,6 +38,8 @@ def run_with_docker(
38
38
  if network:
39
39
  cmd += ["--network", network]
40
40
 
41
+ cmd += ["--restart", "unless-stopped"]
42
+
41
43
  cmd.append(image)
42
44
 
43
45
  subprocess.run(cmd, check=True)
@@ -0,0 +1,10 @@
1
+ def get_hook_urn(hook_name: str) -> str:
2
+ # move to workpeg SDK
3
+ """Convert a hook name to a valid Python method name."""
4
+ # remove "custom." prefix if it exists
5
+ urn = hook_name[len("custom."):] if str(
6
+ hook_name).startswith("custom.") else str(hook_name)
7
+
8
+ urn = urn.replace(":", "_").lower()
9
+
10
+ return f"http://{urn}:8000"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workpeg
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Workpeg function runtime and SDK
5
5
  Author-email: Workpeg <support@workpeg.com>
6
6
  License: MIT
@@ -11,6 +11,7 @@ src/workpeg/create_new.py
11
11
  src/workpeg/run.py
12
12
  src/workpeg/runtime.py
13
13
  src/workpeg/submit.py
14
+ src/workpeg/utils.py
14
15
  src/workpeg.egg-info/PKG-INFO
15
16
  src/workpeg.egg-info/SOURCES.txt
16
17
  src/workpeg.egg-info/dependency_links.txt
@@ -30,4 +31,5 @@ tests/test_context.py
30
31
  tests/test_create_new.py
31
32
  tests/test_run.py
32
33
  tests/test_runtime.py
33
- tests/test_submit.py
34
+ tests/test_submit.py
35
+ tests/test_utils.py
@@ -3,11 +3,20 @@ import pytest
3
3
  from workpeg import cli
4
4
 
5
5
 
6
- def test_cli_no_args_prints_help(monkeypatch, capsys):
6
+ def test_cli_no_args_prints_help(capsys):
7
7
  cli.main([])
8
8
 
9
9
  captured = capsys.readouterr()
10
10
  assert "usage: workpeg" in captured.out
11
+ assert "function" in captured.out
12
+
13
+
14
+ def test_cli_function_no_args_prints_help(capsys):
15
+ cli.main(["function"])
16
+
17
+ captured = capsys.readouterr()
18
+ assert "usage: workpeg" in captured.out
19
+ assert "function" in captured.out
11
20
 
12
21
 
13
22
  def test_cli_version_long(capsys):
@@ -28,34 +37,34 @@ def test_cli_version_short(capsys):
28
37
  assert captured.out.strip().startswith("workpeg ")
29
38
 
30
39
 
31
- def test_cli_new_function_success(monkeypatch, capsys):
40
+ def test_cli_function_new_success(monkeypatch, capsys):
32
41
  monkeypatch.setattr(
33
42
  cli.create_new,
34
43
  "create_new_project",
35
44
  lambda path, force=False: "/tmp/output",
36
45
  )
37
46
 
38
- cli.main(["new-function", "foo"])
47
+ cli.main(["function", "new", "foo"])
39
48
 
40
49
  out = capsys.readouterr().out.strip()
41
50
  assert out == "/tmp/output"
42
51
 
43
52
 
44
- def test_cli_new_function_template_error(monkeypatch, capsys):
53
+ def test_cli_function_new_template_error(monkeypatch, capsys):
45
54
  def fake_create(path, force=False):
46
55
  raise cli.create_new.TemplateError("boom")
47
56
 
48
57
  monkeypatch.setattr(cli.create_new, "create_new_project", fake_create)
49
58
 
50
59
  with pytest.raises(SystemExit) as exc:
51
- cli.main(["new-function", "foo"])
60
+ cli.main(["function", "new", "foo"])
52
61
 
53
62
  assert exc.value.code == 2
54
63
  err = capsys.readouterr().err
55
64
  assert "ERROR: boom" in err
56
65
 
57
66
 
58
- def test_cli_runtime_default(monkeypatch):
67
+ def test_cli_function_runtime_default(monkeypatch):
59
68
  called = {}
60
69
 
61
70
  def fake_runtime_main(argv):
@@ -63,11 +72,11 @@ def test_cli_runtime_default(monkeypatch):
63
72
 
64
73
  monkeypatch.setattr(cli.runtime, "main", fake_runtime_main)
65
74
 
66
- cli.main(["runtime"])
75
+ cli.main(["function", "runtime"])
67
76
  assert called["argv"] == []
68
77
 
69
78
 
70
- def test_cli_runtime_server(monkeypatch):
79
+ def test_cli_function_runtime_server(monkeypatch):
71
80
  called = {}
72
81
 
73
82
  def fake_runtime_main(argv):
@@ -75,11 +84,11 @@ def test_cli_runtime_server(monkeypatch):
75
84
 
76
85
  monkeypatch.setattr(cli.runtime, "main", fake_runtime_main)
77
86
 
78
- cli.main(["runtime", "--server"])
87
+ cli.main(["function", "runtime", "--server"])
79
88
  assert called["argv"] == ["--server"]
80
89
 
81
90
 
82
- def test_cli_submit(monkeypatch):
91
+ def test_cli_function_submit(monkeypatch):
83
92
  called = {}
84
93
 
85
94
  def fake_submit_main(argv):
@@ -88,6 +97,7 @@ def test_cli_submit(monkeypatch):
88
97
  monkeypatch.setattr(cli.submit, "main", fake_submit_main)
89
98
 
90
99
  cli.main([
100
+ "function",
91
101
  "submit",
92
102
  "fn_echo:1.0.0",
93
103
  "--api", "https://repo.workpeg.com",
@@ -103,20 +113,20 @@ def test_cli_submit(monkeypatch):
103
113
  ]
104
114
 
105
115
 
106
- def test_cli_build(monkeypatch, capsys):
116
+ def test_cli_function_build(monkeypatch, capsys):
107
117
  monkeypatch.setattr(
108
118
  cli.build,
109
119
  "build_image",
110
120
  lambda path=".", tag=None: "built-image",
111
121
  )
112
122
 
113
- cli.main(["build"])
123
+ cli.main(["function", "build"])
114
124
 
115
125
  out = capsys.readouterr().out.strip()
116
126
  assert out == "built-image"
117
127
 
118
128
 
119
- def test_cli_build_with_args(monkeypatch, capsys):
129
+ def test_cli_function_build_with_args(monkeypatch, capsys):
120
130
  called = {}
121
131
 
122
132
  def fake_build_image(path=".", tag=None):
@@ -126,14 +136,19 @@ def test_cli_build_with_args(monkeypatch, capsys):
126
136
 
127
137
  monkeypatch.setattr(cli.build, "build_image", fake_build_image)
128
138
 
129
- cli.main(["build", "--path", "/tmp/demo", "--tag", "custom-image"])
139
+ cli.main([
140
+ "function",
141
+ "build",
142
+ "--path", "/tmp/demo",
143
+ "--tag", "custom-image",
144
+ ])
130
145
 
131
146
  assert called == {"path": "/tmp/demo", "tag": "custom-image"}
132
147
  out = capsys.readouterr().out.strip()
133
148
  assert out == "custom-image"
134
149
 
135
150
 
136
- def test_cli_run(monkeypatch):
151
+ def test_cli_function_run(monkeypatch):
137
152
  called = {}
138
153
 
139
154
  def fake_run_main(argv):
@@ -142,11 +157,14 @@ def test_cli_run(monkeypatch):
142
157
  monkeypatch.setattr(cli.run, "main", fake_run_main)
143
158
 
144
159
  cli.main([
160
+ "function",
145
161
  "run",
146
162
  "--with", "docker",
147
163
  "--path", "/tmp/demo",
148
164
  "--no-build",
149
165
  "--network", "workpeg_net",
166
+ "--name", "foo",
167
+ "--no-expose",
150
168
  ])
151
169
 
152
170
  assert called["argv"] == [
@@ -154,4 +172,6 @@ def test_cli_run(monkeypatch):
154
172
  "--path", "/tmp/demo",
155
173
  "--no-build",
156
174
  "--network", "workpeg_net",
175
+ "--name", "foo",
176
+ "--no-expose",
157
177
  ]
@@ -6,7 +6,7 @@ def test_create_new_copies_template(tmp_path):
6
6
  project_dir = tmp_path / "my-func"
7
7
 
8
8
  result = subprocess.run(
9
- ["workpeg", "new-function", str(project_dir)],
9
+ ["workpeg", "function", "new", str(project_dir)],
10
10
  text=True,
11
11
  capture_output=True,
12
12
  cwd=tmp_path,
@@ -19,8 +19,7 @@ def test_create_new_copies_template(tmp_path):
19
19
  assert (project_dir / "app" / "main.py").exists()
20
20
 
21
21
  template_pkg = "workpeg.templates.functions.app"
22
- template_main = pkg_resources.files(
23
- template_pkg).joinpath("main.py").read_text()
22
+ template_main = pkg_resources.files(template_pkg).joinpath("main.py").read_text()
24
23
 
25
24
  created_main = (project_dir / "app" / "main.py").read_text()
26
25
  assert created_main == template_main
@@ -33,7 +32,7 @@ def test_create_new_refuses_overwrite_without_force(tmp_path):
33
32
  (project_dir / "app" / "main.py").write_text("existing")
34
33
 
35
34
  result = subprocess.run(
36
- ["workpeg", "new-function", str(project_dir)],
35
+ ["workpeg", "function", "new", str(project_dir)],
37
36
  text=True,
38
37
  capture_output=True,
39
38
  cwd=tmp_path,
@@ -50,7 +49,7 @@ def test_create_new_overwrites_with_force(tmp_path):
50
49
  (project_dir / "app" / "main.py").write_text("existing")
51
50
 
52
51
  result = subprocess.run(
53
- ["workpeg", "new-function", str(project_dir), "--force"],
52
+ ["workpeg", "function", "new", str(project_dir), "--force"],
54
53
  text=True,
55
54
  capture_output=True,
56
55
  cwd=tmp_path,
@@ -38,14 +38,19 @@ def test_run_with_docker_builds_first(monkeypatch, tmp_path):
38
38
 
39
39
  monkeypatch.setattr(run.subprocess, "run", fake_run)
40
40
 
41
- run.run_with_docker(path=str(project), build_first=True, network="workpeg_net")
41
+ run.run_with_docker(
42
+ path=str(project),
43
+ build_first=True,
44
+ network="workpeg_net",
45
+ )
42
46
 
43
47
  assert len(calls) == 1
44
48
  cmd, check = calls[0]
45
49
  assert cmd == [
46
- "docker", "run", "--rm",
50
+ "docker", "run", "-d",
47
51
  "-p", "9000:9000",
48
52
  "--network", "workpeg_net",
53
+ "--restart", "unless-stopped",
49
54
  "built-image",
50
55
  ]
51
56
  assert check is True
@@ -55,7 +60,8 @@ def test_run_with_docker_no_build(monkeypatch, tmp_path):
55
60
  project = tmp_path / "demo"
56
61
  project.mkdir()
57
62
  (project / "workpeg.json").write_text(
58
- '{"build": {"image": "configured-image"}, "runtime": {"docker": {"port": 8000}}}'
63
+ '{"build": {"image": "configured-image"}, '
64
+ '"runtime": {"docker": {"port": 8000}}}'
59
65
  )
60
66
 
61
67
  calls = []
@@ -65,13 +71,18 @@ def test_run_with_docker_no_build(monkeypatch, tmp_path):
65
71
 
66
72
  monkeypatch.setattr(run.subprocess, "run", fake_run)
67
73
 
68
- run.run_with_docker(path=str(project), build_first=False, network="bridge")
74
+ run.run_with_docker(
75
+ path=str(project),
76
+ build_first=False,
77
+ network="bridge",
78
+ )
69
79
 
70
80
  cmd, check = calls[0]
71
81
  assert cmd == [
72
- "docker", "run", "--rm",
82
+ "docker", "run", "-d",
73
83
  "-p", "8000:8000",
74
84
  "--network", "bridge",
85
+ "--restart", "unless-stopped",
75
86
  "configured-image",
76
87
  ]
77
88
  assert check is True
@@ -80,10 +91,18 @@ def test_run_with_docker_no_build(monkeypatch, tmp_path):
80
91
  def test_run_main_with_docker(monkeypatch):
81
92
  called = {}
82
93
 
83
- def fake_run_with_docker(path=".", build_first=True, network=None):
94
+ def fake_run_with_docker(
95
+ path=".",
96
+ build_first=True,
97
+ network=None,
98
+ expose=True,
99
+ name=None,
100
+ ):
84
101
  called["path"] = path
85
102
  called["build_first"] = build_first
86
103
  called["network"] = network
104
+ called["expose"] = expose
105
+ called["name"] = name
87
106
 
88
107
  monkeypatch.setattr(run, "run_with_docker", fake_run_with_docker)
89
108
 
@@ -93,6 +112,8 @@ def test_run_main_with_docker(monkeypatch):
93
112
  "path": "/tmp/demo",
94
113
  "build_first": False,
95
114
  "network": None,
115
+ "expose": True,
116
+ "name": None,
96
117
  }
97
118
 
98
119
 
@@ -118,10 +139,18 @@ def test_run_main_uses_config_default(monkeypatch, tmp_path):
118
139
 
119
140
  called = {}
120
141
 
121
- def fake_run_with_docker(path=".", build_first=True, network=None):
142
+ def fake_run_with_docker(
143
+ path=".",
144
+ build_first=True,
145
+ network=None,
146
+ expose=True,
147
+ name=None,
148
+ ):
122
149
  called["path"] = path
123
150
  called["build_first"] = build_first
124
151
  called["network"] = network
152
+ called["expose"] = expose
153
+ called["name"] = name
125
154
 
126
155
  monkeypatch.setattr(run, "run_with_docker", fake_run_with_docker)
127
156
 
@@ -131,16 +160,26 @@ def test_run_main_uses_config_default(monkeypatch, tmp_path):
131
160
  "path": str(project),
132
161
  "build_first": True,
133
162
  "network": None,
163
+ "expose": True,
164
+ "name": None,
134
165
  }
135
166
 
136
167
 
137
168
  def test_run_main_with_docker_and_network(monkeypatch):
138
169
  called = {}
139
170
 
140
- def fake_run_with_docker(path=".", build_first=True, network=None):
171
+ def fake_run_with_docker(
172
+ path=".",
173
+ build_first=True,
174
+ network=None,
175
+ expose=True,
176
+ name=None,
177
+ ):
141
178
  called["path"] = path
142
179
  called["build_first"] = build_first
143
180
  called["network"] = network
181
+ called["expose"] = expose
182
+ called["name"] = name
144
183
 
145
184
  monkeypatch.setattr(run, "run_with_docker", fake_run_with_docker)
146
185
 
@@ -154,6 +193,8 @@ def test_run_main_with_docker_and_network(monkeypatch):
154
193
  "path": "/tmp/demo",
155
194
  "build_first": True,
156
195
  "network": "workpeg_net",
196
+ "expose": True,
197
+ "name": None,
157
198
  }
158
199
 
159
200
 
@@ -171,12 +212,17 @@ def test_run_with_docker_no_expose(monkeypatch, tmp_path):
171
212
 
172
213
  monkeypatch.setattr(run.subprocess, "run", fake_run)
173
214
 
174
- run.run_with_docker(path=str(project), build_first=False, expose=False)
215
+ run.run_with_docker(
216
+ path=str(project),
217
+ build_first=False,
218
+ expose=False,
219
+ )
175
220
 
176
221
  assert len(calls) == 1
177
222
  cmd, check = calls[0]
178
223
  assert cmd == [
179
- "docker", "run", "--rm",
224
+ "docker", "run", "-d",
225
+ "--restart", "unless-stopped",
180
226
  "demo",
181
227
  ]
182
228
  assert check is True
@@ -196,12 +242,17 @@ def test_run_with_docker_expose(monkeypatch, tmp_path):
196
242
 
197
243
  monkeypatch.setattr(run.subprocess, "run", fake_run)
198
244
 
199
- run.run_with_docker(path=str(project), build_first=False, expose=True)
245
+ run.run_with_docker(
246
+ path=str(project),
247
+ build_first=False,
248
+ expose=True,
249
+ )
200
250
 
201
251
  cmd, check = calls[0]
202
252
  assert cmd == [
203
- "docker", "run", "--rm",
253
+ "docker", "run", "-d",
204
254
  "-p", "9000:9000",
255
+ "--restart", "unless-stopped",
205
256
  "demo",
206
257
  ]
207
258
  assert check is True
@@ -232,8 +283,9 @@ def test_run_with_docker_with_name(monkeypatch, tmp_path):
232
283
  cmd, check = calls[0]
233
284
 
234
285
  assert cmd == [
235
- "docker", "run", "--rm",
286
+ "docker", "run", "-d",
236
287
  "--name", "foo",
288
+ "--restart", "unless-stopped",
237
289
  "demo",
238
290
  ]
239
291
  assert check is True
@@ -264,9 +316,10 @@ def test_run_with_docker_name_and_network(monkeypatch, tmp_path):
264
316
  cmd, check = calls[0]
265
317
 
266
318
  assert cmd == [
267
- "docker", "run", "--rm",
319
+ "docker", "run", "-d",
268
320
  "--name", "foo",
269
321
  "--network", "workpeg_net",
322
+ "--restart", "unless-stopped",
270
323
  "demo",
271
324
  ]
272
325
  assert check is True
@@ -0,0 +1,46 @@
1
+ from unittest import TestCase
2
+
3
+ from workpeg.utils import get_hook_urn
4
+
5
+
6
+ class TestGetHookUrn(TestCase):
7
+
8
+ def test_returns_hook_urn(self):
9
+ result = get_hook_urn("mail_sanitizer:v1")
10
+
11
+ self.assertEqual(
12
+ result,
13
+ "http://mail_sanitizer_v1:8000",
14
+ )
15
+
16
+ def test_removes_custom_prefix(self):
17
+ result = get_hook_urn("custom.mail_sanitizer:v1")
18
+
19
+ self.assertEqual(
20
+ result,
21
+ "http://mail_sanitizer_v1:8000",
22
+ )
23
+
24
+ def test_lowercases_hook_name(self):
25
+ result = get_hook_urn("Mail_Sanitizer:V1")
26
+
27
+ self.assertEqual(
28
+ result,
29
+ "http://mail_sanitizer_v1:8000",
30
+ )
31
+
32
+ def test_replaces_colon_with_underscore(self):
33
+ result = get_hook_urn("calendar:1.1")
34
+
35
+ self.assertEqual(
36
+ result,
37
+ "http://calendar_1.1:8000",
38
+ )
39
+
40
+ def test_handles_hook_without_version(self):
41
+ result = get_hook_urn("mailer")
42
+
43
+ self.assertEqual(
44
+ result,
45
+ "http://mailer:8000",
46
+ )
@@ -1,118 +0,0 @@
1
- import argparse
2
- import sys
3
-
4
- from workpeg import create_new, runtime, submit, build, run
5
-
6
- try:
7
- from importlib.metadata import version
8
- __version__ = version("workpeg")
9
- except Exception:
10
- __version__ = "unknown"
11
-
12
-
13
- def build_parser() -> argparse.ArgumentParser:
14
- p = argparse.ArgumentParser(
15
- prog="workpeg",
16
- description="Workpeg CLI",
17
- )
18
-
19
- # version flag
20
- p.add_argument(
21
- "-v", "--version",
22
- action="version",
23
- version=f"workpeg {__version__}",
24
- )
25
-
26
- # IMPORTANT: no required=True
27
- sub = p.add_subparsers(dest="command")
28
-
29
- # submit
30
- submit_p = sub.add_parser("submit", help="Submit a function bundle")
31
- submit_p.add_argument("ref")
32
- submit_p.add_argument("--api", default=None)
33
- submit_p.add_argument("--path", default=None)
34
- submit_p.add_argument("--timeout", type=int, default=None)
35
- submit_p.set_defaults(_handler="submit")
36
-
37
- # new-function
38
- new_p = sub.add_parser("new-function", help="Create a new function project from template")
39
- new_p.add_argument("path")
40
- new_p.add_argument("--force", action="store_true")
41
- new_p.set_defaults(_handler="new-function")
42
-
43
- # runtime
44
- runtime_p = sub.add_parser("runtime", help="Run the function runtime")
45
- runtime_p.add_argument("--server", action="store_true")
46
- runtime_p.set_defaults(_handler="runtime")
47
-
48
- # build
49
- build_p = sub.add_parser("build", help="Build the function docker image")
50
- build_p.add_argument("--path", default=".")
51
- build_p.add_argument("--tag", default=None)
52
- build_p.set_defaults(_handler="build")
53
-
54
- # run
55
- run_p = sub.add_parser("run", help="Run the function using a configured runtime")
56
- run_p.add_argument("--with", dest="with_runtime", choices=["docker", "cracker"])
57
- run_p.add_argument("--path", default=".")
58
- run_p.add_argument("--no-build", action="store_true")
59
- run_p.add_argument("--network", default=None)
60
- run_p.set_defaults(_handler="run")
61
-
62
- return p
63
-
64
-
65
- def main(argv=None) -> None:
66
- argv = argv if argv is not None else sys.argv[1:]
67
- parser = build_parser()
68
-
69
- if not argv:
70
- parser.print_help()
71
- return
72
-
73
- args = parser.parse_args(argv)
74
-
75
- if args._handler == "new-function":
76
- try:
77
- out = create_new.create_new_project(args.path, force=args.force)
78
- print(str(out))
79
- except create_new.TemplateError as e:
80
- print(f"ERROR: {e}", file=sys.stderr)
81
- raise SystemExit(2)
82
- return
83
-
84
- if args._handler == "runtime":
85
- runtime_argv = []
86
- if args.server:
87
- runtime_argv.append("--server")
88
- runtime.main(runtime_argv)
89
- return
90
-
91
- if args._handler == "submit":
92
- submit_argv = [args.ref]
93
- if args.api:
94
- submit_argv += ["--api", args.api]
95
- if args.path:
96
- submit_argv += ["--path", args.path]
97
- if args.timeout is not None:
98
- submit_argv += ["--timeout", str(args.timeout)]
99
- submit.main(submit_argv)
100
- return
101
-
102
- if args._handler == "build":
103
- image = build.build_image(path=args.path, tag=args.tag)
104
- print(image)
105
- return
106
-
107
- if args._handler == "run":
108
- run_argv = []
109
- if args.with_runtime:
110
- run_argv += ["--with", args.with_runtime]
111
- if args.path:
112
- run_argv += ["--path", args.path]
113
- if args.no_build:
114
- run_argv.append("--no-build")
115
- if args.network:
116
- run_argv += ["--network", args.network]
117
- run.main(run_argv)
118
- return
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes