workpeg 0.3.2__tar.gz → 0.4.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 (35) hide show
  1. {workpeg-0.3.2/src/workpeg.egg-info → workpeg-0.4.0}/PKG-INFO +1 -1
  2. {workpeg-0.3.2 → workpeg-0.4.0}/pyproject.toml +1 -1
  3. workpeg-0.4.0/src/workpeg/context.py +134 -0
  4. {workpeg-0.3.2 → workpeg-0.4.0/src/workpeg.egg-info}/PKG-INFO +1 -1
  5. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg.egg-info/SOURCES.txt +2 -0
  6. workpeg-0.4.0/tests/test_context.py +201 -0
  7. {workpeg-0.3.2 → workpeg-0.4.0}/LICENSE +0 -0
  8. {workpeg-0.3.2 → workpeg-0.4.0}/MANIFEST.in +0 -0
  9. {workpeg-0.3.2 → workpeg-0.4.0}/README.md +0 -0
  10. {workpeg-0.3.2 → workpeg-0.4.0}/setup.cfg +0 -0
  11. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/__init__.py +0 -0
  12. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/build.py +0 -0
  13. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/cli.py +0 -0
  14. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/config.py +0 -0
  15. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/create_new.py +0 -0
  16. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/run.py +0 -0
  17. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/runtime.py +0 -0
  18. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/submit.py +0 -0
  19. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/__init__.py +0 -0
  20. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/Dockerfile +0 -0
  21. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/LICENSE +0 -0
  22. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/README.md +0 -0
  23. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/app/__init__.py +0 -0
  24. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/app/main.py +0 -0
  25. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg/templates/functions/requirements.txt +0 -0
  26. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg.egg-info/dependency_links.txt +0 -0
  27. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg.egg-info/entry_points.txt +0 -0
  28. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg.egg-info/requires.txt +0 -0
  29. {workpeg-0.3.2 → workpeg-0.4.0}/src/workpeg.egg-info/top_level.txt +0 -0
  30. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_build.py +0 -0
  31. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_cli.py +0 -0
  32. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_create_new.py +0 -0
  33. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_run.py +0 -0
  34. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_runtime.py +0 -0
  35. {workpeg-0.3.2 → workpeg-0.4.0}/tests/test_submit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workpeg
3
- Version: 0.3.2
3
+ Version: 0.4.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.3.2"
7
+ version = "0.4.0"
8
8
  description = "Workpeg function runtime and SDK"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,134 @@
1
+ import re
2
+
3
+
4
+ UUID_REGEX = re.compile(
5
+ r"^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-"
6
+ r"[89ab][a-f0-9]{3}-[a-f0-9]{12}$",
7
+ re.IGNORECASE,
8
+ )
9
+
10
+
11
+ class BaseContext:
12
+ @staticmethod
13
+ def verify(data):
14
+ raise NotImplementedError("Context must implement verify().")
15
+
16
+
17
+ class _TableContext(BaseContext):
18
+ REQUIRED_FIELDS = {
19
+ "uuid",
20
+ "name",
21
+ "owner_id",
22
+ "metadata",
23
+ "columns",
24
+ "namespace",
25
+ "peg",
26
+ }
27
+
28
+ @classmethod
29
+ def verify(cls, data):
30
+ if data is None:
31
+ return True
32
+
33
+ cls._verify_keys(data)
34
+ cls._verify_uuid_format(data["uuid"])
35
+ cls._verify_owner_id(data["owner_id"])
36
+ cls._verify_columns(data["columns"])
37
+ cls._verify_namespace(data["namespace"])
38
+ cls._verify_peg(data["peg"])
39
+
40
+ return True
41
+
42
+ @classmethod
43
+ def _verify_keys(cls, data):
44
+ if not isinstance(data, dict):
45
+ raise ValueError("Data must be a dictionary.")
46
+
47
+ missing = cls.REQUIRED_FIELDS - data.keys()
48
+ if missing:
49
+ raise ValueError(f"Missing required fields: {missing}")
50
+
51
+ @classmethod
52
+ def _verify_uuid_format(cls, uuid_str):
53
+ if not isinstance(uuid_str, str) or not UUID_REGEX.match(uuid_str):
54
+ raise ValueError("Invalid UUID format.")
55
+
56
+ @classmethod
57
+ def _verify_owner_id(cls, owner_id):
58
+ if owner_id is not None and (
59
+ not isinstance(owner_id, int) or owner_id <= 0
60
+ ):
61
+ raise ValueError("Owner ID must be a positive integer or None.")
62
+
63
+ @classmethod
64
+ def _verify_columns(cls, columns):
65
+ return True # TODO
66
+
67
+ @classmethod
68
+ def _verify_namespace(cls, namespace):
69
+ if namespace is not None and not isinstance(namespace, str):
70
+ raise ValueError("Namespace must be a non-empty string.")
71
+
72
+ @classmethod
73
+ def _verify_peg(cls, peg):
74
+ return True # TODO
75
+
76
+
77
+ class _StaffContext(BaseContext):
78
+ @staticmethod
79
+ def verify(data):
80
+ if data is None:
81
+ return True
82
+
83
+ if not isinstance(data, dict):
84
+ raise ValueError("Staff context must be a dictionary.")
85
+
86
+ if "email" not in data:
87
+ raise ValueError("Missing required field: 'email'")
88
+
89
+ return True
90
+
91
+
92
+ class _MetadataContext(BaseContext):
93
+ @staticmethod
94
+ def verify(data):
95
+ return True
96
+
97
+
98
+ class ContextBuilder:
99
+ registry = {}
100
+
101
+ def __init__(self):
102
+ self.context = {}
103
+
104
+ @classmethod
105
+ def register(cls, key, handler_class):
106
+ cls.registry[key] = handler_class
107
+
108
+ def add(self, key, serialized_data):
109
+ handler = self.registry.get(key)
110
+
111
+ if handler is None:
112
+ raise ValueError(f"{key} rejected: no handler registered")
113
+
114
+ verify_method = getattr(handler, "verify", None)
115
+
116
+ if not callable(verify_method):
117
+ raise ValueError(f"{key} rejected: 'verify' not implemented")
118
+
119
+ try:
120
+ if not verify_method(serialized_data):
121
+ raise ValueError(f"{key} rejected: verification failed")
122
+ except Exception as e:
123
+ raise ValueError(f"{key} rejected: verification error - {str(e)}")
124
+
125
+ self.context[key] = serialized_data
126
+ return self
127
+
128
+ def build(self):
129
+ return self.context
130
+
131
+
132
+ ContextBuilder.register("table", _TableContext)
133
+ ContextBuilder.register("staff", _StaffContext)
134
+ ContextBuilder.register("metadata", _MetadataContext)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: workpeg
3
- Version: 0.3.2
3
+ Version: 0.4.0
4
4
  Summary: Workpeg function runtime and SDK
5
5
  Author-email: Workpeg <support@workpeg.com>
6
6
  License: MIT
@@ -6,6 +6,7 @@ src/workpeg/__init__.py
6
6
  src/workpeg/build.py
7
7
  src/workpeg/cli.py
8
8
  src/workpeg/config.py
9
+ src/workpeg/context.py
9
10
  src/workpeg/create_new.py
10
11
  src/workpeg/run.py
11
12
  src/workpeg/runtime.py
@@ -25,6 +26,7 @@ src/workpeg/templates/functions/app/__init__.py
25
26
  src/workpeg/templates/functions/app/main.py
26
27
  tests/test_build.py
27
28
  tests/test_cli.py
29
+ tests/test_context.py
28
30
  tests/test_create_new.py
29
31
  tests/test_run.py
30
32
  tests/test_runtime.py
@@ -0,0 +1,201 @@
1
+ import pytest
2
+ from unittest.mock import patch
3
+ from uuid import uuid4
4
+
5
+ from workpeg.context import (
6
+ ContextBuilder,
7
+ _TableContext, _StaffContext, _MetadataContext
8
+ )
9
+
10
+
11
+ @pytest.fixture
12
+ def valid_table_data():
13
+ return {
14
+ "uuid": str(uuid4()),
15
+ "name": "Employees",
16
+ "owner_id": 1,
17
+ "metadata": {},
18
+ "columns": [],
19
+ "namespace": "workpeg.hr",
20
+ "peg": 10,
21
+ }
22
+
23
+
24
+ class TestTableContext:
25
+
26
+ def test_verify_table_returns_true_for_valid_data(self, valid_table_data):
27
+ assert _TableContext.verify(valid_table_data) is True
28
+
29
+ def test_verify_table_returns_true_when_owner_id_is_none(
30
+ self,
31
+ valid_table_data,
32
+ ):
33
+ valid_table_data["owner_id"] = None
34
+
35
+ assert _TableContext.verify(valid_table_data) is True
36
+
37
+ def test_verify_table_raises_when_required_key_is_missing(
38
+ self,
39
+ valid_table_data,
40
+ ):
41
+ del valid_table_data["uuid"]
42
+
43
+ with pytest.raises(ValueError, match="Missing required fields"):
44
+ _TableContext.verify(valid_table_data)
45
+
46
+ def test_verify_table_raises_for_invalid_uuid(self, valid_table_data):
47
+ valid_table_data["uuid"] = "bad-uuid"
48
+
49
+ with pytest.raises(ValueError, match="Invalid UUID format."):
50
+ _TableContext.verify(valid_table_data)
51
+
52
+ def test_verify_table_raises_for_invalid_owner_id(self, valid_table_data):
53
+ valid_table_data["owner_id"] = 0
54
+
55
+ with pytest.raises(
56
+ ValueError,
57
+ match="Owner ID must be a positive integer or None.",
58
+ ):
59
+ _TableContext.verify(valid_table_data)
60
+
61
+ def test_verify_table_raises_for_invalid_namespace(self, valid_table_data):
62
+ valid_table_data["namespace"] = {}
63
+
64
+ with pytest.raises(
65
+ ValueError,
66
+ match="Namespace must be a non-empty string.",
67
+ ):
68
+ _TableContext.verify(valid_table_data)
69
+
70
+ def test_verify_keys_accepts_extra_fields(self, valid_table_data):
71
+ valid_table_data["extra"] = "allowed"
72
+
73
+ assert _TableContext._verify_keys(valid_table_data) is None
74
+
75
+ def test_verify_uuid_format_accepts_valid_uuid(self):
76
+ assert _TableContext._verify_uuid_format(str(uuid4())) is None
77
+
78
+ def test_verify_uuid_format_raises_for_invalid_uuid(self):
79
+ with pytest.raises(ValueError, match="Invalid UUID format."):
80
+ _TableContext._verify_uuid_format("123")
81
+
82
+
83
+ class TestContextBuilder:
84
+
85
+ def test_adds_valid_table_context(self, valid_table_data):
86
+ builder = ContextBuilder()
87
+ builder.register("table", _TableContext)
88
+
89
+ context = builder.add("table", valid_table_data).build()
90
+
91
+ assert context["table"] == valid_table_data
92
+
93
+ def test_add_returns_self_for_chaining(self, valid_table_data):
94
+ builder = ContextBuilder()
95
+ builder.register("table", _TableContext)
96
+
97
+ result = builder.add("table", valid_table_data)
98
+
99
+ assert result is builder
100
+
101
+ def test_rejects_unregistered_context_key(self, valid_table_data):
102
+ builder = ContextBuilder()
103
+
104
+ with pytest.raises(
105
+ ValueError,
106
+ match="unknown rejected: no handler registered",
107
+ ):
108
+ builder.add("unknown", valid_table_data)
109
+
110
+ def test_rejects_handler_without_verify_method(self, valid_table_data):
111
+ class BadTableContext:
112
+ pass
113
+
114
+ builder = ContextBuilder()
115
+ builder.register("something", BadTableContext)
116
+
117
+ with pytest.raises(
118
+ ValueError,
119
+ match="something rejected: 'verify' not implemented",
120
+ ):
121
+ builder.add("something", valid_table_data)
122
+
123
+ def test_rejects_invalid_table_context_with_exact_reason(
124
+ self,
125
+ valid_table_data,
126
+ ):
127
+ builder = ContextBuilder()
128
+ builder.register("table", _TableContext)
129
+
130
+ valid_table_data["uuid"] = "bad-uuid"
131
+
132
+ with pytest.raises(
133
+ ValueError,
134
+ match="table rejected: verification error - Invalid UUID format.",
135
+ ):
136
+ builder.add("table", valid_table_data)
137
+
138
+ def test_uses_verify_table_method(self, valid_table_data):
139
+ builder = ContextBuilder()
140
+ builder.register("table", _TableContext)
141
+
142
+ with patch.object(_TableContext, "verify", return_value=True) as mock_verify:
143
+ builder.add("table", valid_table_data)
144
+
145
+ mock_verify.assert_called_once_with(valid_table_data)
146
+
147
+
148
+ class TestStaffContext:
149
+
150
+ def test_verify_staff_allows_none(self):
151
+ assert _StaffContext.verify(None) is True
152
+
153
+ def test_verify_staff_accepts_valid_data(self):
154
+ data = {"email": "test@example.com"}
155
+
156
+ assert _StaffContext.verify(data) is True
157
+
158
+ def test_verify_staff_rejects_non_dict(self):
159
+ with pytest.raises(
160
+ ValueError,
161
+ match="Staff context must be a dictionary.",
162
+ ):
163
+ _StaffContext.verify("not-a-dict")
164
+
165
+ def test_verify_staff_rejects_missing_email(self):
166
+ with pytest.raises(
167
+ ValueError,
168
+ match="Missing required field: 'email'",
169
+ ):
170
+ _StaffContext.verify({})
171
+
172
+ def test_verify_staff_allows_extra_fields(self):
173
+ data = {
174
+ "email": "test@example.com",
175
+ "name": "John",
176
+ "role": "admin",
177
+ }
178
+
179
+ assert _StaffContext.verify(data) is True
180
+
181
+
182
+ class TestMetadataContext:
183
+
184
+ def test_verify_extra_accepts_none(self):
185
+ assert _MetadataContext.verify(None) is True
186
+
187
+ def test_verify_metadata_accepts_dict(self):
188
+ assert _MetadataContext.verify({"foo": "bar"}) is True
189
+
190
+ def test_verify_metadata_accepts_any_type(self):
191
+ test_cases = [
192
+ "string",
193
+ 123,
194
+ 12.5,
195
+ [],
196
+ (),
197
+ object(),
198
+ ]
199
+
200
+ for case in test_cases:
201
+ assert _MetadataContext.verify(case) is True
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
File without changes
File without changes