webfunction 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.
- webfunction/__init__.py +44 -0
- webfunction/_request.py +106 -0
- webfunction/_version.py +1 -0
- webfunction/client.py +296 -0
- webfunction/errors.py +54 -0
- webfunction/models.py +325 -0
- webfunction/page.py +134 -0
- webfunction/pipeline.py +163 -0
- webfunction/py.typed +0 -0
- webfunction/wftype.py +337 -0
- webfunction-0.1.0.dist-info/METADATA +100 -0
- webfunction-0.1.0.dist-info/RECORD +14 -0
- webfunction-0.1.0.dist-info/WHEEL +4 -0
- webfunction-0.1.0.dist-info/licenses/LICENSE +21 -0
webfunction/wftype.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""The Web Function type grammar, ported from the Ruby reference gem's ``type.rb``.
|
|
2
|
+
|
|
3
|
+
Every type node supports ``.format(fmt)``, ``.valid(value)``, ``.objects``, and
|
|
4
|
+
``.without_refinements()``, mirroring the Ruby/Go/Java/C# clients' Type classes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import ipaddress
|
|
10
|
+
import re
|
|
11
|
+
from typing import Any, List, Optional, Union as _TypingUnion
|
|
12
|
+
from urllib.parse import urlparse
|
|
13
|
+
|
|
14
|
+
ALLOWED_REFINEMENTS = {
|
|
15
|
+
"number": {"u32", "u64", "i32", "i64", "f32", "f64", "timestamp"},
|
|
16
|
+
"string": {
|
|
17
|
+
"date", "time", "datetime", "uuid", "base64", "email", "phone",
|
|
18
|
+
"url", "uri", "ipv4", "ipv6", "hostname",
|
|
19
|
+
},
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
_DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
23
|
+
_TIME_RE = re.compile(r"^\d{2}:\d{2}:\d{2}(\.\d+)?$")
|
|
24
|
+
_DATETIME_RE = re.compile(r"^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$")
|
|
25
|
+
_UUID_RE = re.compile(r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$")
|
|
26
|
+
_BASE64_RE = re.compile(r"^[A-Za-z0-9+/]*={0,2}$")
|
|
27
|
+
_EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
|
28
|
+
_PHONE_RE = re.compile(r"^\+[1-9]\d{1,14}$")
|
|
29
|
+
_HOSTNAME_RE = re.compile(
|
|
30
|
+
r"^(?=.{1,253}$)([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)"
|
|
31
|
+
r"(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_number(v: Any) -> bool:
|
|
36
|
+
return isinstance(v, (int, float)) and not isinstance(v, bool)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _is_int(v: Any) -> bool:
|
|
40
|
+
return isinstance(v, int) and not isinstance(v, bool)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _v_u32(v): return _is_int(v) and 0 <= v <= 0xFFFFFFFF
|
|
44
|
+
def _v_u64(v): return _is_int(v) and 0 <= v <= 0xFFFFFFFFFFFFFFFF
|
|
45
|
+
def _v_i32(v): return _is_int(v) and -0x80000000 <= v <= 0x7FFFFFFF
|
|
46
|
+
def _v_i64(v): return _is_int(v) and -0x8000000000000000 <= v <= 0x7FFFFFFFFFFFFFFF
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _v_f32(v):
|
|
50
|
+
if not _is_number(v):
|
|
51
|
+
return False
|
|
52
|
+
f = float(v)
|
|
53
|
+
return f == f and f not in (float("inf"), float("-inf")) and abs(f) <= 3.4028235e38
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _v_f64(v):
|
|
57
|
+
if not _is_number(v):
|
|
58
|
+
return False
|
|
59
|
+
f = float(v)
|
|
60
|
+
return f == f and f not in (float("inf"), float("-inf"))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _v_timestamp(v): return _is_int(v) and v >= 0
|
|
64
|
+
def _v_date(v): return isinstance(v, str) and bool(_DATE_RE.match(v))
|
|
65
|
+
def _v_time(v): return isinstance(v, str) and bool(_TIME_RE.match(v))
|
|
66
|
+
def _v_datetime(v): return isinstance(v, str) and bool(_DATETIME_RE.match(v))
|
|
67
|
+
def _v_uuid(v): return isinstance(v, str) and bool(_UUID_RE.match(v))
|
|
68
|
+
def _v_base64(v): return isinstance(v, str) and len(v) % 4 == 0 and bool(_BASE64_RE.match(v))
|
|
69
|
+
def _v_email(v): return isinstance(v, str) and bool(_EMAIL_RE.match(v))
|
|
70
|
+
def _v_phone(v): return isinstance(v, str) and bool(_PHONE_RE.match(v))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _v_url(v):
|
|
74
|
+
if not isinstance(v, str):
|
|
75
|
+
return False
|
|
76
|
+
try:
|
|
77
|
+
parsed = urlparse(v)
|
|
78
|
+
return parsed.scheme in ("http", "https") and bool(parsed.netloc)
|
|
79
|
+
except ValueError:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _v_uri(v):
|
|
84
|
+
if not isinstance(v, str):
|
|
85
|
+
return False
|
|
86
|
+
try:
|
|
87
|
+
return bool(urlparse(v).scheme)
|
|
88
|
+
except ValueError:
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _v_ipv4(v):
|
|
93
|
+
if not isinstance(v, str):
|
|
94
|
+
return False
|
|
95
|
+
try:
|
|
96
|
+
ipaddress.IPv4Address(v)
|
|
97
|
+
return True
|
|
98
|
+
except ValueError:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _v_ipv6(v):
|
|
103
|
+
if not isinstance(v, str):
|
|
104
|
+
return False
|
|
105
|
+
try:
|
|
106
|
+
ipaddress.IPv6Address(v)
|
|
107
|
+
return True
|
|
108
|
+
except ValueError:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _v_hostname(v): return isinstance(v, str) and bool(_HOSTNAME_RE.match(v))
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
REFINEMENT_VALIDATORS = {
|
|
116
|
+
"u32": _v_u32, "u64": _v_u64, "i32": _v_i32, "i64": _v_i64,
|
|
117
|
+
"f32": _v_f32, "f64": _v_f64, "timestamp": _v_timestamp,
|
|
118
|
+
"date": _v_date, "time": _v_time, "datetime": _v_datetime,
|
|
119
|
+
"uuid": _v_uuid, "base64": _v_base64, "email": _v_email, "phone": _v_phone,
|
|
120
|
+
"url": _v_url, "uri": _v_uri, "ipv4": _v_ipv4, "ipv6": _v_ipv6, "hostname": _v_hostname,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Type:
|
|
125
|
+
"""Base class for all type nodes. Not instantiated directly."""
|
|
126
|
+
|
|
127
|
+
base_type: Optional[str] = None
|
|
128
|
+
refinement: Optional[str] = None
|
|
129
|
+
|
|
130
|
+
def format(self, fmt: str = "default") -> str:
|
|
131
|
+
raise NotImplementedError
|
|
132
|
+
|
|
133
|
+
def valid(self, value: Any) -> bool:
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def objects(self) -> List[str]:
|
|
138
|
+
return []
|
|
139
|
+
|
|
140
|
+
def without_refinements(self) -> "Type":
|
|
141
|
+
return self
|
|
142
|
+
|
|
143
|
+
def __str__(self) -> str:
|
|
144
|
+
return self.format()
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class BaseTypeNode(Type):
|
|
148
|
+
def __init__(self, base_type: str, refinement: Optional[str] = None):
|
|
149
|
+
self.base_type = base_type
|
|
150
|
+
self.refinement = refinement
|
|
151
|
+
|
|
152
|
+
def format(self, fmt: str = "default") -> str:
|
|
153
|
+
if fmt == "compact":
|
|
154
|
+
return self.refinement or self.base_type
|
|
155
|
+
if fmt == "base":
|
|
156
|
+
return self.base_type
|
|
157
|
+
return f"{self.base_type}.{self.refinement}" if self.refinement else self.base_type
|
|
158
|
+
|
|
159
|
+
def __eq__(self, other):
|
|
160
|
+
return isinstance(other, BaseTypeNode) and other.base_type == self.base_type and other.refinement == self.refinement
|
|
161
|
+
|
|
162
|
+
def __hash__(self):
|
|
163
|
+
return hash((BaseTypeNode, self.base_type, self.refinement))
|
|
164
|
+
|
|
165
|
+
def __repr__(self):
|
|
166
|
+
return f"<{self.base_type} {self.refinement}>" if self.refinement else f"<{self.base_type}>"
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def objects(self) -> List[str]:
|
|
170
|
+
return [self.refinement] if self.base_type == "object" and self.refinement else []
|
|
171
|
+
|
|
172
|
+
def without_refinements(self) -> "Type":
|
|
173
|
+
return self if self.refinement is None else BaseTypeNode(self.base_type)
|
|
174
|
+
|
|
175
|
+
def valid(self, value: Any) -> bool:
|
|
176
|
+
if self.base_type == "string":
|
|
177
|
+
return isinstance(value, str) and self._refinement_valid(value)
|
|
178
|
+
if self.base_type == "number":
|
|
179
|
+
return _is_number(value) and self._refinement_valid(value)
|
|
180
|
+
if self.base_type == "object":
|
|
181
|
+
return isinstance(value, dict)
|
|
182
|
+
if self.base_type == "boolean":
|
|
183
|
+
return isinstance(value, bool)
|
|
184
|
+
if self.base_type == "null":
|
|
185
|
+
return value is None
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
def _refinement_valid(self, value: Any) -> bool:
|
|
189
|
+
if self.refinement is None:
|
|
190
|
+
return True
|
|
191
|
+
return REFINEMENT_VALIDATORS[self.refinement](value)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ArrayType(Type):
|
|
195
|
+
def __init__(self, of: Type):
|
|
196
|
+
self.base_type = "array"
|
|
197
|
+
self.of = of
|
|
198
|
+
|
|
199
|
+
def format(self, fmt: str = "default") -> str:
|
|
200
|
+
if fmt == "base":
|
|
201
|
+
return "array"
|
|
202
|
+
return f"array<{self.of.format(fmt)}>"
|
|
203
|
+
|
|
204
|
+
def __eq__(self, other):
|
|
205
|
+
return isinstance(other, ArrayType) and other.of == self.of
|
|
206
|
+
|
|
207
|
+
def __hash__(self):
|
|
208
|
+
return hash((ArrayType, self.of))
|
|
209
|
+
|
|
210
|
+
def __repr__(self):
|
|
211
|
+
return f"<ArrayOf {self.of!r}>"
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def objects(self) -> List[str]:
|
|
215
|
+
return self.of.objects
|
|
216
|
+
|
|
217
|
+
def without_refinements(self) -> "Type":
|
|
218
|
+
return ArrayType(self.of.without_refinements())
|
|
219
|
+
|
|
220
|
+
def valid(self, value: Any) -> bool:
|
|
221
|
+
return isinstance(value, list) and all(self.of.valid(v) for v in value)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class UnionType(Type):
|
|
225
|
+
def __init__(self, members: List[Type]):
|
|
226
|
+
self.members = members
|
|
227
|
+
|
|
228
|
+
def format(self, fmt: str = "default") -> str:
|
|
229
|
+
return " | ".join(m.format(fmt) for m in self.members)
|
|
230
|
+
|
|
231
|
+
def __eq__(self, other):
|
|
232
|
+
return isinstance(other, UnionType) and other.members == self.members
|
|
233
|
+
|
|
234
|
+
def __hash__(self):
|
|
235
|
+
return hash((UnionType, tuple(self.members)))
|
|
236
|
+
|
|
237
|
+
def __repr__(self):
|
|
238
|
+
return f"<Union {' | '.join(repr(m) for m in self.members)}>"
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def objects(self) -> List[str]:
|
|
242
|
+
seen, result = set(), []
|
|
243
|
+
for m in self.members:
|
|
244
|
+
for o in m.objects:
|
|
245
|
+
if o not in seen:
|
|
246
|
+
seen.add(o)
|
|
247
|
+
result.append(o)
|
|
248
|
+
return result
|
|
249
|
+
|
|
250
|
+
def without_refinements(self) -> "Type":
|
|
251
|
+
return union_of([m.without_refinements() for m in self.members])
|
|
252
|
+
|
|
253
|
+
def valid(self, value: Any) -> bool:
|
|
254
|
+
return any(m.valid(value) for m in self.members)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class AnyType(Type):
|
|
258
|
+
base_type = "any"
|
|
259
|
+
|
|
260
|
+
def format(self, fmt: str = "default") -> str:
|
|
261
|
+
return "any"
|
|
262
|
+
|
|
263
|
+
def __eq__(self, other):
|
|
264
|
+
return isinstance(other, AnyType)
|
|
265
|
+
|
|
266
|
+
def __hash__(self):
|
|
267
|
+
return hash(AnyType)
|
|
268
|
+
|
|
269
|
+
def __repr__(self):
|
|
270
|
+
return "<any>"
|
|
271
|
+
|
|
272
|
+
def valid(self, value: Any) -> bool:
|
|
273
|
+
return True
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def any_type() -> Type: return AnyType()
|
|
277
|
+
def string_type(refinement: Optional[str] = None) -> Type: return BaseTypeNode("string", refinement)
|
|
278
|
+
def number_type(refinement: Optional[str] = None) -> Type: return BaseTypeNode("number", refinement)
|
|
279
|
+
def object_type(refinement: Optional[str] = None) -> Type: return BaseTypeNode("object", refinement)
|
|
280
|
+
def array_type(of: Optional[Type] = None) -> Type: return ArrayType(of if of is not None else any_type())
|
|
281
|
+
def boolean_type() -> Type: return BaseTypeNode("boolean")
|
|
282
|
+
def null_type() -> Type: return BaseTypeNode("null")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def union_of(types: List[Type]) -> Type:
|
|
286
|
+
uniq: List[Type] = []
|
|
287
|
+
for t in types:
|
|
288
|
+
if t not in uniq:
|
|
289
|
+
uniq.append(t)
|
|
290
|
+
if len(uniq) > 1:
|
|
291
|
+
return UnionType(uniq)
|
|
292
|
+
return uniq[0]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _base(type_str: str) -> Optional[Type]:
|
|
296
|
+
base_type, _, refinement = type_str.partition(".")
|
|
297
|
+
refinement = refinement or None
|
|
298
|
+
|
|
299
|
+
if base_type == "string":
|
|
300
|
+
if refinement and refinement not in ALLOWED_REFINEMENTS["string"]:
|
|
301
|
+
return string_type()
|
|
302
|
+
return string_type(refinement)
|
|
303
|
+
if base_type == "number":
|
|
304
|
+
if refinement and refinement not in ALLOWED_REFINEMENTS["number"]:
|
|
305
|
+
return number_type()
|
|
306
|
+
return number_type(refinement)
|
|
307
|
+
if base_type == "object":
|
|
308
|
+
return object_type(refinement)
|
|
309
|
+
if base_type == "array":
|
|
310
|
+
return array_type()
|
|
311
|
+
if base_type == "boolean":
|
|
312
|
+
return boolean_type()
|
|
313
|
+
if base_type == "null":
|
|
314
|
+
return null_type()
|
|
315
|
+
if base_type == "any":
|
|
316
|
+
return any_type()
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _detect(raw: Any) -> Optional[Type]:
|
|
321
|
+
if isinstance(raw, str):
|
|
322
|
+
return _base(raw)
|
|
323
|
+
if isinstance(raw, list):
|
|
324
|
+
types = [t for t in (_detect(r) for r in raw) if t is not None]
|
|
325
|
+
if not types:
|
|
326
|
+
return array_type(any_type())
|
|
327
|
+
return array_type(union_of(types))
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def parse(raw: _TypingUnion[str, List[Any], None]) -> Type:
|
|
332
|
+
"""Parses a wfn ``type`` value (a string, or an array of strings/arrays) into a Type node."""
|
|
333
|
+
items = raw if isinstance(raw, list) else ([raw] if raw is not None else [])
|
|
334
|
+
types = [t for t in (_detect(r) for r in items) if t is not None]
|
|
335
|
+
if not types:
|
|
336
|
+
return any_type()
|
|
337
|
+
return union_of(types)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webfunction
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Reference client library for the Web Function (wfn) protocol
|
|
5
|
+
Project-URL: Homepage, https://webfunction.org
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: httpx>=0.24
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# webfunction-python
|
|
13
|
+
|
|
14
|
+
A reference client library for the [Web Function (wfn)](https://webfunction.org) protocol.
|
|
15
|
+
|
|
16
|
+
Part of the same reference-client suite as the Ruby gem
|
|
17
|
+
(`https://github.com/webfunction-protocol/webfunction-ruby`, the spec author's own implementation),
|
|
18
|
+
`webfunction-go`, `webfunction-java`, and `webfunction-csharp`.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install webfunction
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The only runtime dependency is [httpx](https://www.python-httpx.org/), which is what
|
|
27
|
+
lets this library offer both a synchronous `Client` and an async `AsyncClient` from a
|
|
28
|
+
single implementation.
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Sync
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from webfunction import Client
|
|
36
|
+
|
|
37
|
+
client = Client.from_package_endpoint("https://api.example.com/package")
|
|
38
|
+
result = client.list_items(email="a@example.com") # dynamic dispatch, no generated code
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Async
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import asyncio
|
|
45
|
+
from webfunction import AsyncClient
|
|
46
|
+
|
|
47
|
+
async def main():
|
|
48
|
+
client = await AsyncClient.from_package_endpoint("https://api.example.com/package")
|
|
49
|
+
result = await client.list_items(email="a@example.com")
|
|
50
|
+
|
|
51
|
+
asyncio.run(main())
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Pagination
|
|
55
|
+
|
|
56
|
+
Endpoints that declare the `paginated` flag return a `Page` (or `AsyncPage`):
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
page = client.list_people()
|
|
60
|
+
for person in page:
|
|
61
|
+
...
|
|
62
|
+
if page.has_next:
|
|
63
|
+
page = page.next_page()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Pipelining
|
|
67
|
+
|
|
68
|
+
If the package declares a `pipeline_url`, pass `pipelined=True` to batch several
|
|
69
|
+
endpoint invocations into a single request. Calls return a `Promise` instead of
|
|
70
|
+
executing immediately; index into a promise (`promise["id"]`) to build a reference to
|
|
71
|
+
one of its fields before it's resolved.
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
client = Client.from_package_endpoint(url, pipelined=True)
|
|
75
|
+
item = client.list_items(email="a@example.com")
|
|
76
|
+
detail = client.get_widget(id=item["id"]) # references item's future "id" field
|
|
77
|
+
results = client.pipeline.execute()
|
|
78
|
+
print(detail.value)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Errors
|
|
82
|
+
|
|
83
|
+
All errors are subclasses of `WebFunctionError`, carrying `.code`, `.message`, and
|
|
84
|
+
`.details`: `BadRequestError`, `UnexpectedStatusCodeError`, `JsonParseError`,
|
|
85
|
+
`UnresolvedPromiseError`.
|
|
86
|
+
|
|
87
|
+
## A note on unknown fields
|
|
88
|
+
|
|
89
|
+
This library never validates incoming JSON against a strict schema -- model classes
|
|
90
|
+
only read the keys they know about via plain `dict.get()`, so an API that adds an
|
|
91
|
+
undocumented field to its responses (this has happened for real against
|
|
92
|
+
`api.reservepay.com`) won't break parsing the way it did for the Java client, which
|
|
93
|
+
needed an explicit fix to disable strict unknown-field rejection.
|
|
94
|
+
|
|
95
|
+
## Development
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
pip install -e .
|
|
99
|
+
python -m unittest discover tests
|
|
100
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
webfunction/__init__.py,sha256=5I_9MKIvpTRaM3Kfgmm8CHK6CbRRdkfS6SD2lB7s2OY,1002
|
|
2
|
+
webfunction/_request.py,sha256=Or4EJrbt9PHPrELz3Vu6X-Op8CDElw6raVqw_U1GeVc,4530
|
|
3
|
+
webfunction/_version.py,sha256=CpXi3jGlx23RvRyU7iytOMZrnspdWw4yofS8lpP1AJU,18
|
|
4
|
+
webfunction/client.py,sha256=9KB-7mlS9KcGYQcH0gNsX6wBTj5ZbHtzMhR-iOd50PY,13164
|
|
5
|
+
webfunction/errors.py,sha256=5Ny5lfTO2in0hkRJv_lQBpBYSw30xG1LU-A2dNIBVxg,1860
|
|
6
|
+
webfunction/models.py,sha256=Rkdnzy0D17d_x89hbLPsIlfc0z4cw-c9xttqDf20FWE,10803
|
|
7
|
+
webfunction/page.py,sha256=_djvZAH2YDLv8r7xcrVOKOCJcWi2L8XSO_82j10SijI,5007
|
|
8
|
+
webfunction/pipeline.py,sha256=qS29Y9nDhVqFY_FeTPnNTvGxmUWRVJaEHej1iUmcnXs,5441
|
|
9
|
+
webfunction/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
webfunction/wftype.py,sha256=RtuKHb4fUQnPtaP2O5z3LycLXGVgkMbsWBI6D36Umj0,10571
|
|
11
|
+
webfunction-0.1.0.dist-info/METADATA,sha256=OG7SlTwhDs-ec4GYHeprrc5MSU4tLpIrr5cV37_MVU0,2843
|
|
12
|
+
webfunction-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
13
|
+
webfunction-0.1.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
14
|
+
webfunction-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|