tigrbl 0.3.3.dev1__py3-none-any.whl → 0.3.4.dev1__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.
tigrbl/app/tigrbl_app.py CHANGED
@@ -1,6 +1,7 @@
1
1
  # tigrbl/v3/app/tigrbl_app.py
2
2
  from __future__ import annotations
3
3
 
4
+ import asyncio
4
5
  import copy
5
6
  import inspect
6
7
  from types import SimpleNamespace
@@ -259,7 +260,13 @@ class TigrblApp(_App):
259
260
  if inspect.isawaitable(item):
260
261
  await item
261
262
 
262
- return _inner()
263
+ try:
264
+ loop = asyncio.get_running_loop()
265
+ except RuntimeError:
266
+ asyncio.run(_inner())
267
+ return None
268
+
269
+ return loop.create_task(_inner())
263
270
 
264
271
  async def rpc_call(
265
272
  self,
tigrbl/deps/__init__.py CHANGED
@@ -16,5 +16,5 @@ from .pydantic import * # noqa: F403, F401
16
16
  # Re-export all FastAPI dependencies
17
17
  from .fastapi import * # noqa: F403, F401
18
18
 
19
- # Note: starlette.py is reserved for future Starlette-specific imports
20
- # if we need to import directly from Starlette rather than through FastAPI
19
+ # Re-export Starlette dependencies (Request/Response live here)
20
+ from .starlette import * # noqa: F403, F401
tigrbl/deps/fastapi.py CHANGED
@@ -4,8 +4,6 @@ from fastapi import (
4
4
  FastAPI,
5
5
  Security,
6
6
  Depends,
7
- Request,
8
- Response,
9
7
  Path as FastAPIPath,
10
8
  Body,
11
9
  HTTPException,
@@ -36,8 +34,6 @@ __all__ = [
36
34
  "FastAPI",
37
35
  "Security",
38
36
  "Depends",
39
- "Request",
40
- "Response",
41
37
  "Path",
42
38
  "Body",
43
39
  "HTTPException",
@@ -8,23 +8,17 @@ from typing import (
8
8
  Mapping,
9
9
  MutableMapping,
10
10
  Optional,
11
+ Protocol,
11
12
  Sequence,
12
13
  Union,
13
- Protocol,
14
14
  runtime_checkable,
15
15
  )
16
16
 
17
- try:
18
- from fastapi import Request # type: ignore
19
- except Exception: # pragma: no cover
20
- Request = Any # type: ignore
17
+ from ...deps.starlette import Request as StarletteRequest
18
+ from sqlalchemy.ext.asyncio import AsyncSession
19
+ from sqlalchemy.orm import Session
21
20
 
22
- try:
23
- from sqlalchemy.orm import Session # type: ignore
24
- from sqlalchemy.ext.asyncio import AsyncSession # type: ignore
25
- except Exception: # pragma: no cover
26
- Session = Any # type: ignore
27
- AsyncSession = Any # type: ignore
21
+ Request = StarletteRequest if StarletteRequest is not None else Any # type: ignore
28
22
 
29
23
 
30
24
  @runtime_checkable
tigrbl/table/_base.py CHANGED
@@ -252,16 +252,27 @@ class Base(DeclarativeBase):
252
252
  # 0) Remove any previously registered class with the same module path.
253
253
  try:
254
254
  reg = Base.registry._class_registry
255
- key = f"{cls.__module__}.{cls.__name__}"
256
- existing = reg.get(key)
255
+ name = cls.__name__
256
+ existing = reg.get(name)
257
257
  if existing is not None:
258
258
  try:
259
259
  Base.registry._dispose_cls(existing)
260
260
  except Exception:
261
261
  pass
262
- reg.pop(key, None)
263
- if reg.get(cls.__name__) is existing:
264
- reg.pop(cls.__name__, None)
262
+ reg.pop(name, None)
263
+ module_reg = reg.get("_sa_module_registry")
264
+ if module_reg is not None:
265
+ marker = module_reg
266
+ for part in cls.__module__.split("."):
267
+ contents = getattr(marker, "contents", None)
268
+ if not isinstance(contents, dict) or part not in contents:
269
+ marker = None
270
+ break
271
+ marker = contents.get(part)
272
+ if marker is not None and isinstance(
273
+ getattr(marker, "contents", None), dict
274
+ ):
275
+ marker.contents.pop(name, None)
265
276
  except Exception:
266
277
  pass
267
278
 
@@ -27,7 +27,7 @@ from __future__ import annotations
27
27
  from typing import Any, Callable, Optional, Sequence
28
28
 
29
29
  # JSON-RPC transport
30
- from .jsonrpc import build_jsonrpc_router
30
+ from .jsonrpc import build_jsonrpc_router, build_openrpc_spec
31
31
 
32
32
  # REST transport (aggregator over per-model routers)
33
33
  from .rest import build_rest_router, mount_rest
@@ -67,6 +67,7 @@ def mount_jsonrpc(
67
67
  __all__ = [
68
68
  # JSON-RPC
69
69
  "build_jsonrpc_router",
70
+ "build_openrpc_spec",
70
71
  "mount_jsonrpc",
71
72
  # REST
72
73
  "build_rest_router",
@@ -6,14 +6,18 @@ Public helper:
6
6
  - build_jsonrpc_router(
7
7
  api, *, get_db=None, tags=("rpc",)
8
8
  ) -> Router
9
+ - build_openrpc_spec(api) -> dict
9
10
 
10
11
  Usage:
11
12
  from tigrbl.transport.jsonrpc import build_jsonrpc_router
12
13
  app.include_router(build_jsonrpc_router(api), prefix="/rpc")
14
+ # OpenRPC schema (JSON-RPC equivalent of OpenAPI)
15
+ build_openrpc_spec(api)
13
16
  """
14
17
 
15
18
  from __future__ import annotations
16
19
 
17
20
  from .dispatcher import build_jsonrpc_router
21
+ from .openrpc import build_openrpc_spec
18
22
 
19
- __all__ = ["build_jsonrpc_router"]
23
+ __all__ = ["build_jsonrpc_router", "build_openrpc_spec"]
@@ -78,6 +78,7 @@ except Exception: # pragma: no cover
78
78
  from ...runtime.errors import ERROR_MESSAGES, http_exc_to_rpc
79
79
  from ...config.constants import TIGRBL_AUTH_CONTEXT_ATTR
80
80
  from .models import RPCRequest, RPCResponse
81
+ from .openrpc import build_openrpc_spec
81
82
  from .helpers import (
82
83
  _authorize,
83
84
  _err,
@@ -337,6 +338,19 @@ def build_jsonrpc_router(
337
338
  # extra router deps already applied via Router(dependencies=...)
338
339
  )
339
340
 
341
+ def _openrpc_endpoint():
342
+ return build_openrpc_spec(api)
343
+
344
+ router.add_api_route(
345
+ path="/openrpc.json",
346
+ endpoint=_openrpc_endpoint,
347
+ methods=["GET"],
348
+ name="openrpc_json",
349
+ tags=list(tags) if tags else None,
350
+ summary="OpenRPC",
351
+ description="OpenRPC 1.2.6 schema for JSON-RPC methods.",
352
+ )
353
+
340
354
  # Compatibility: serve same endpoint without trailing slash
341
355
  router.add_api_route(
342
356
  path="/",
@@ -0,0 +1,116 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Iterable, List, Mapping, Sequence
4
+
5
+ from pydantic import BaseModel
6
+
7
+ from ...op import OpSpec
8
+
9
+ JsonObject = Dict[str, Any]
10
+
11
+
12
+ def _iter_models(api: Any) -> List[type]:
13
+ seen: set[type] = set()
14
+ models: List[type] = []
15
+
16
+ def _add_from(container: Any) -> None:
17
+ if isinstance(container, Mapping):
18
+ items: Iterable[Any] = container.values()
19
+ elif isinstance(container, Sequence):
20
+ items = container
21
+ else:
22
+ return
23
+ for model in items:
24
+ if isinstance(model, type) and model not in seen:
25
+ seen.add(model)
26
+ models.append(model)
27
+
28
+ _add_from(getattr(api, "models", None) or {})
29
+ for child in getattr(api, "apis", ()) or ():
30
+ _add_from(getattr(child, "models", None) or {})
31
+
32
+ return models
33
+
34
+
35
+ def _iter_ops(model: type) -> Sequence[OpSpec]:
36
+ ops = getattr(model, "ops", None)
37
+ if ops is not None:
38
+ all_ops = getattr(ops, "all", None)
39
+ if all_ops is not None:
40
+ return all_ops
41
+ opspecs = getattr(model, "opspecs", None)
42
+ if opspecs is not None:
43
+ all_ops = getattr(opspecs, "all", None)
44
+ if all_ops is not None:
45
+ return all_ops
46
+ return ()
47
+
48
+
49
+ def _schema_with_defs(schema: type[BaseModel]) -> tuple[JsonObject, JsonObject]:
50
+ raw = schema.model_json_schema(ref_template="#/components/schemas/{model}")
51
+ defs = raw.pop("$defs", {})
52
+ return raw, defs
53
+
54
+
55
+ def _describe_method(model: type, spec: OpSpec) -> str | None:
56
+ rpc_ns = getattr(model, "rpc", None)
57
+ rpc_call = getattr(rpc_ns, spec.alias, None)
58
+ if rpc_call is None:
59
+ return None
60
+ doc = getattr(rpc_call, "__doc__", None)
61
+ if doc:
62
+ return doc.strip()
63
+ return None
64
+
65
+
66
+ def build_openrpc_spec(api: Any) -> JsonObject:
67
+ info_title = getattr(api, "title", None) or getattr(api, "name", None) or "API"
68
+ info_version = getattr(api, "version", None) or "0.1.0"
69
+ spec: JsonObject = {
70
+ "openrpc": "1.2.6",
71
+ "info": {"title": f"{info_title} JSON-RPC API", "version": info_version},
72
+ "methods": [],
73
+ "components": {"schemas": {}},
74
+ }
75
+
76
+ components = spec["components"]["schemas"]
77
+ methods: List[JsonObject] = []
78
+
79
+ for model in _iter_models(api):
80
+ for op in _iter_ops(model):
81
+ if not getattr(op, "expose_rpc", True):
82
+ continue
83
+
84
+ method: JsonObject = {"name": f"{model.__name__}.{op.alias}"}
85
+ description = _describe_method(model, op)
86
+ if description:
87
+ method["description"] = description
88
+
89
+ alias_ns = getattr(getattr(model, "schemas", None), op.alias, None)
90
+ in_schema = getattr(alias_ns, "in_", None)
91
+ out_schema = getattr(alias_ns, "out", None)
92
+
93
+ if in_schema is not None:
94
+ in_json, defs = _schema_with_defs(in_schema)
95
+ for key, value in defs.items():
96
+ components.setdefault(key, value)
97
+ method["params"] = [
98
+ {"name": "params", "schema": in_json, "required": True}
99
+ ]
100
+ method["paramStructure"] = "by-name"
101
+ else:
102
+ method["params"] = []
103
+
104
+ if out_schema is not None:
105
+ out_json, defs = _schema_with_defs(out_schema)
106
+ for key, value in defs.items():
107
+ components.setdefault(key, value)
108
+ method["result"] = {"name": "result", "schema": out_json}
109
+
110
+ methods.append(method)
111
+
112
+ spec["methods"] = sorted(methods, key=lambda item: item["name"])
113
+ return spec
114
+
115
+
116
+ __all__ = ["build_openrpc_spec"]
tigrbl/types/__init__.py CHANGED
@@ -57,13 +57,12 @@ from ..deps.fastapi import (
57
57
  Router,
58
58
  Security,
59
59
  Depends,
60
- Request,
61
- Response,
62
60
  Path,
63
61
  Body,
64
62
  HTTPException,
65
63
  App,
66
64
  )
65
+ from ..deps.starlette import Request, Response
67
66
 
68
67
  # ── Local Package ─────────────────────────────────────────────────────────
69
68
  from .op import _Op, _SchemaVerb
tigrbl/types/authn_abc.py CHANGED
@@ -1,7 +1,8 @@
1
1
  # tigrbl/v3/types/authn_abc.py
2
2
  from __future__ import annotations
3
3
  from abc import ABC, abstractmethod
4
- from fastapi import Request
4
+
5
+ from ..deps.starlette import Request
5
6
 
6
7
 
7
8
  class AuthNProvider(ABC):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tigrbl
3
- Version: 0.3.3.dev1
3
+ Version: 0.3.4.dev1
4
4
  Summary: Automatic API generation tools by Swarmauri.
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -12,7 +12,7 @@ tigrbl/app/_model_registry.py,sha256=v6UoDd7hmv5J4DpNEV13BOgIKIcKou10w3zyZVPd-po
12
12
  tigrbl/app/app_spec.py,sha256=RzVHibgoIyQQtlQzRtrWDa4pSD7PpWsysgfztJ1tSRY,1474
13
13
  tigrbl/app/mro_collect.py,sha256=W-kOoKGUSW9dtsKaPuMkN5sLBw_0-H8MTqps1KPwPyI,3351
14
14
  tigrbl/app/shortcuts.py,sha256=PEMbmwaqW8a3jkcHM5AXsYA2mmuXGFpOTnqlJnUX9Lo,1775
15
- tigrbl/app/tigrbl_app.py,sha256=vN1mlMoUy7ENWKBbVGuY_OFjYjvlf3nv5ybELvfGqgI,14656
15
+ tigrbl/app/tigrbl_app.py,sha256=xM4tCdW43A-7C7-ne1okp3q2CsQynBWIgydxnZmlDlY,14836
16
16
  tigrbl/bindings/__init__.py,sha256=GcM5PTKNLS1Jx__OiAcjLI5yybL40RrA8OzMVPno4_U,2643
17
17
  tigrbl/bindings/api/__init__.py,sha256=ayw4ZBCvvUThQm9PXS6TyVkV1MEYY8YwGcyT4HwWZ7c,395
18
18
  tigrbl/bindings/api/common.py,sha256=ivTUwC35QKj0-5op_dksHx4vTsf-fWLYJjwHGuFMKcA,3599
@@ -77,8 +77,8 @@ tigrbl/core/crud/helpers/normalize.py,sha256=aDavCusuBDRltFX4baBlztDZ6dvgAG4Qs-D
77
77
  tigrbl/core/crud/ops.py,sha256=Qu0A4KNX0LuQdUXC5NcqzaN1aP49_ank4czEHArXcsQ,7900
78
78
  tigrbl/ddl/__init__.py,sha256=C9RQLef1l5XCE96CxCs0pN24fNATvOglK1zRYE7BdKY,11352
79
79
  tigrbl/decorators.py,sha256=U4iss3iN--4PPGJ90doAcYn4WuDx1dQMw-5Ar4SE7TU,445
80
- tigrbl/deps/__init__.py,sha256=yzhFPLJX8eXMIV41ar6IXh6ZXdjSlZ-ZYA_FKl-ZxFY,795
81
- tigrbl/deps/fastapi.py,sha256=qyRcu1c5Xy3RUgNEUxizxQbaZ8i4EdnBmQwJPDBdbCo,1181
80
+ tigrbl/deps/__init__.py,sha256=2SJWOvz7cEMFSXFkeiLS3cOqjzm00nd_uxWKnQCEEYw,758
81
+ tigrbl/deps/fastapi.py,sha256=SnrTwE7taghrkXEjQ8ZCGS8OYsv3E8s9A6u5s9nh5b8,1123
82
82
  tigrbl/deps/favicon.svg,sha256=C_S8UPLuLx4QIdP84OpzKw7IiwWjrHIzyKotoUKRLB4,195
83
83
  tigrbl/deps/jinja.py,sha256=dau8Y2v9UY3sptLhzP_8MpjKwlU-34_QraNgQXr4OCM,698
84
84
  tigrbl/deps/pydantic.py,sha256=cLReo5Gd3V7TDJ9L3sMOFD93FWMd5QnZZoCaOJ6ip14,497
@@ -193,7 +193,7 @@ tigrbl/runtime/executor/__init__.py,sha256=JuFfQo0xo9_C8XW014vdEtMvzNKaugXhLg8tW
193
193
  tigrbl/runtime/executor/guards.py,sha256=HiE9J_Y-AslRJZYBrjZmGFecq9Tx4nn6HMqL1jrezLY,4210
194
194
  tigrbl/runtime/executor/helpers.py,sha256=PU7Bj7VTdGaldNYuBRsrV4408G4UqxJIWwPUwtUgNaA,2239
195
195
  tigrbl/runtime/executor/invoke.py,sha256=IUSkjXXqRDX1cOmDcpXloKHgwI_HRYW2SsqOrKheB8U,4754
196
- tigrbl/runtime/executor/types.py,sha256=oxgyv5EI2hs2MixjXQAdhNu5nWpQ2F0kXC6pXNU_pSE,2386
196
+ tigrbl/runtime/executor/types.py,sha256=4zQnvW7h3-8f67c2DF7rXGgRanjuGuHqHNo3XMyuS5g,2247
197
197
  tigrbl/runtime/kernel.py,sha256=cL1bnw2glp33AjiFchMg8MZsJ3lb7c2JPhz26JejP1I,22895
198
198
  tigrbl/runtime/labels.py,sha256=aBXx65lMPlR60wXH4M_xeuXN4w3z07ep6NxICAiSy68,13134
199
199
  tigrbl/runtime/opview.py,sha256=UHbQbGg5GGi2AWFip8qBUD6fe6h8VNU232GRly1aT-I,2792
@@ -238,21 +238,22 @@ tigrbl/system/diagnostics/router.py,sha256=5RAv6LPoN4luGwIPnYGak66uT9FslYPc-d_GK
238
238
  tigrbl/system/diagnostics/utils.py,sha256=qxC8pUNK2lQKh5cGlF-PSFA-cfJFLlAHW0-iEosvPgg,1172
239
239
  tigrbl/system/uvicorn.py,sha256=ogvIqfv-1CxAPZ8BADucaNAy_ePsLA3IVIZxmhdfL3A,1526
240
240
  tigrbl/table/__init__.py,sha256=yyP9iZBUJh-D7TCy9WQIvMXKL3ztyX-EXdTK4RTE7iw,207
241
- tigrbl/table/_base.py,sha256=JKZ6Q_2bxDyedw43HkBtinfJFGzWuH3ISRXWu_cnb4c,17660
241
+ tigrbl/table/_base.py,sha256=k2ZEaMCygpoCMFEwOC2L02oe4cR8W3zwn2NInZcIcHI,18170
242
242
  tigrbl/table/_table.py,sha256=B7ft2SMnbp3aTWKO44M4EWTHmzFKyQlpdj-3QULRaGk,1740
243
243
  tigrbl/table/mro_collect.py,sha256=PbuSZnUvVbs3NCe2otie7pvcjxeF54hSiVp_VywclSA,2733
244
244
  tigrbl/table/shortcuts.py,sha256=-IZAZyMTsiCdKV0w7nq1C2YBsB6iE_uNGJb-PatlO8I,1716
245
245
  tigrbl/table/table_spec.py,sha256=dvilrGWX7fVc6ThTbAqJKxxl3r6_MKNFY0cs_wuyvC8,1001
246
- tigrbl/transport/__init__.py,sha256=Hq2yob_mvMOQdd8Ts04-rzL282rRpIXo2Prortk0fL4,1896
247
- tigrbl/transport/jsonrpc/__init__.py,sha256=YZKk9W1GSPkx98G0cshEcDKLWvmeJGjW90PeTJ0z_r4,438
248
- tigrbl/transport/jsonrpc/dispatcher.py,sha256=Eb-M_0n3npAWkw5NSIDDFklgj_J3p8-46yaXPtaiktI,12620
246
+ tigrbl/transport/__init__.py,sha256=QanSh_0kxBFBg32KNSrrzsQjfV8Lp1BljsLiSnE8cbc,1942
247
+ tigrbl/transport/jsonrpc/__init__.py,sha256=yIuAjf3zQsqezvS0TdzDlfIhtuzMi9_ykSIqjn-w8EM,618
248
+ tigrbl/transport/jsonrpc/dispatcher.py,sha256=KKvN2_Ct1AYiQdww2VO_sgaSkNmL7hLcDEFIpq_ou1s,13018
249
249
  tigrbl/transport/jsonrpc/helpers.py,sha256=oyqx36m8n7EofciPVvTEM9Pz1l51zJwsI224AXkE7q8,3010
250
250
  tigrbl/transport/jsonrpc/models.py,sha256=omtjb-NN8HyWgIZ5tHafEsbwC7f1XlttAFHFA41Xn2k,973
251
+ tigrbl/transport/jsonrpc/openrpc.py,sha256=FmczSrHkiS17Hdk6nVv_S8bi4oFFG8dPCdGSkJsNKqg,3706
251
252
  tigrbl/transport/rest/__init__.py,sha256=AU_twrP0A958FtXvLSf1i60Jn-UZSRUkAZ1Gd2TeYaw,764
252
253
  tigrbl/transport/rest/aggregator.py,sha256=V1zDvv1bwpNyt6rUPmEUEV5nORjb5sHU5LJ00m1ybYY,4454
253
- tigrbl/types/__init__.py,sha256=TSmKjMPDqn_AdZEfYKuGPRkBFsT2P7fqC-BSUXRwZlA,4120
254
+ tigrbl/types/__init__.py,sha256=4Io80FQh3L_gkfsv-TwPnK2wNtEAOYQJHGg-UEEfOAQ,4140
254
255
  tigrbl/types/allow_anon_provider.py,sha256=5mWvSfk_eCY_o6oMm1gSEqz6cKyJyoZ1-DcVYm0KmpA,565
255
- tigrbl/types/authn_abc.py,sha256=GtlXkMb59GEEXNEfeRX_ZfNzu-S4hcLsESzBAaPT2Fg,769
256
+ tigrbl/types/authn_abc.py,sha256=OrKSQQmeaCVh8vqnFM5oL--wdvhdKGocGpQKJjJoG8w,779
256
257
  tigrbl/types/nested_path_provider.py,sha256=1z-4Skz_X_hy-XGEAQnNv6vyrfFNsPIvlhBqf457Sjc,609
257
258
  tigrbl/types/op.py,sha256=-kdDABEyIBEAMMtatOXVPeMEaGFUiXtJSStZkNu3cnA,769
258
259
  tigrbl/types/op_config_provider.py,sha256=tYH_py9niHvLQF9c9OD13Wt8U4hoYxUatJO_Zp5Cs8Q,551
@@ -261,7 +262,7 @@ tigrbl/types/request_extras_provider.py,sha256=JOIpzx1PYA2AYYvkMiXrxlwpBLOPD2cQa
261
262
  tigrbl/types/response_extras_provider.py,sha256=sFB0R3vyUqmpT-o8983hH9FAlOq6-wwNVK6vfuCPHCg,653
262
263
  tigrbl/types/table_config_provider.py,sha256=EkfOhy9UDfy7EgiZddw9KIl5tRujRjXJlr4cSk1Rm5k,361
263
264
  tigrbl/types/uuid.py,sha256=pD-JrhS0L2GXeJ0Hv_oKzRuiXmxHDTVoMqExO48iqZE,1993
264
- tigrbl-0.3.3.dev1.dist-info/METADATA,sha256=CExkdEPpGN2jQMDVYfYSoPO8m1ev6hPqUoTi8IdA6do,17853
265
- tigrbl-0.3.3.dev1.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
266
- tigrbl-0.3.3.dev1.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
267
- tigrbl-0.3.3.dev1.dist-info/RECORD,,
265
+ tigrbl-0.3.4.dev1.dist-info/METADATA,sha256=i0xcgPVGNmiXd651uePY-tdF8hnMuxDkTOhfzwvt3NY,17853
266
+ tigrbl-0.3.4.dev1.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
267
+ tigrbl-0.3.4.dev1.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
268
+ tigrbl-0.3.4.dev1.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.3.0
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any