splime 0.2.1__tar.gz → 0.2.3__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.
- {splime-0.2.1/src/splime.egg-info → splime-0.2.3}/PKG-INFO +2 -1
- {splime-0.2.1 → splime-0.2.3}/pyproject.toml +2 -1
- splime-0.2.3/src/spl/_http.py +23 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/function.py +5 -2
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/environment.py +35 -1
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/remote_client.py +14 -9
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/object.py +13 -1
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/server.py +7 -2
- {splime-0.2.1 → splime-0.2.3}/src/spl/server_client.py +5 -3
- {splime-0.2.1 → splime-0.2.3/src/splime.egg-info}/PKG-INFO +2 -1
- {splime-0.2.1 → splime-0.2.3}/src/splime.egg-info/SOURCES.txt +1 -0
- {splime-0.2.1 → splime-0.2.3}/src/splime.egg-info/requires.txt +1 -0
- {splime-0.2.1 → splime-0.2.3}/LICENSE +0 -0
- {splime-0.2.1 → splime-0.2.3}/NOTICE +0 -0
- {splime-0.2.1 → splime-0.2.3}/README.md +0 -0
- {splime-0.2.1 → splime-0.2.3}/setup.cfg +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/_client.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/_deprecate.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/client.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/_common.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/common.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/adapter.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/artifact.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/control.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/distribution.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/local_function.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/misc.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/module.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/node.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/node_function.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/node_remote.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/pipeline.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/entities/scalar.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/ir/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/ir/common.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/ir/parse.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/ir/unparse.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/core/ir/utils.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/__main__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/canonical.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/cli.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/client.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/docker_environment.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/docker_pool.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/doctor.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/environment_base.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/heartbeat_service.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/metadata.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/env.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/library.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/run.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/server_connection.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/repositories/sync_event.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/_helpers.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/artifacts.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/diagnostics.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/envs.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/libraries.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/objects.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/remote.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/runs.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/routes/server_connections.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/run_progress.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/runtime_backend.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/runtime_config.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/runtime_dependencies.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/secret_store.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/server_connection.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/services/__init__.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/services/sync.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/signature.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/storage_base.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/store.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon/worker.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/daemon_client.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/pipeline_widget.py +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/spl/py.typed +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/splime.egg-info/dependency_links.txt +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/splime.egg-info/entry_points.txt +0 -0
- {splime-0.2.1 → splime-0.2.3}/src/splime.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splime
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Reuse Python functions across projects without rewriting or redeploying them.
|
|
5
5
|
Author-email: Yastrebov Kirill <yastrebovks@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,6 +21,7 @@ Requires-Python: >=3.13
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: NOTICE
|
|
24
|
+
Requires-Dist: certifi==2026.6.17
|
|
24
25
|
Requires-Dist: pyyaml==6.0.3
|
|
25
26
|
Requires-Dist: keyring==25.7.0
|
|
26
27
|
Requires-Dist: quart==0.20.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "splime"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
|
|
9
9
|
description = "Reuse Python functions across projects without rewriting or redeploying them."
|
|
10
10
|
readme = "README.md"
|
|
@@ -18,6 +18,7 @@ authors = [
|
|
|
18
18
|
requires-python = ">= 3.13"
|
|
19
19
|
|
|
20
20
|
dependencies = [
|
|
21
|
+
"certifi==2026.6.17",
|
|
21
22
|
"pyyaml==6.0.3",
|
|
22
23
|
"keyring==25.7.0",
|
|
23
24
|
"quart==0.20.0",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""HTTP helpers shared by central-server clients."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import ssl
|
|
6
|
+
from functools import lru_cache
|
|
7
|
+
from typing import Any
|
|
8
|
+
from urllib.request import Request, urlopen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@lru_cache(maxsize=1)
|
|
12
|
+
def verified_https_context() -> ssl.SSLContext:
|
|
13
|
+
"""Return a certificate-verifying HTTPS context backed by certifi."""
|
|
14
|
+
|
|
15
|
+
import certifi
|
|
16
|
+
|
|
17
|
+
return ssl.create_default_context(cafile=certifi.where())
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def urlopen_verified(request: Request) -> Any:
|
|
21
|
+
"""Open a URL request with the bundled certifi CA bundle."""
|
|
22
|
+
|
|
23
|
+
return urlopen(request, context=verified_https_context())
|
|
@@ -3,6 +3,7 @@ import builtins
|
|
|
3
3
|
import dis
|
|
4
4
|
import inspect
|
|
5
5
|
import sys
|
|
6
|
+
import textwrap
|
|
6
7
|
import typing
|
|
7
8
|
from dataclasses import dataclass
|
|
8
9
|
from itertools import chain
|
|
@@ -134,7 +135,9 @@ def serialize_function_output(func: FunctionType, tree: ast.FunctionDef):
|
|
|
134
135
|
|
|
135
136
|
def serialize_function(func: FunctionType, tree: ast.FunctionDef | None = None):
|
|
136
137
|
if tree is None:
|
|
137
|
-
|
|
138
|
+
# dedent: getsource keeps the original indentation, so a function
|
|
139
|
+
# defined inside an ``if``/``with``/function body would not parse.
|
|
140
|
+
[tree] = ast.parse(textwrap.dedent(inspect.getsource(func))).body
|
|
138
141
|
|
|
139
142
|
args = tree.args
|
|
140
143
|
|
|
@@ -163,7 +166,7 @@ def _ir_parse__function(
|
|
|
163
166
|
# We imported this function using SPL, using it's metadata.
|
|
164
167
|
return _attach(chain([DSPLImport(*getattr(x, LOCATION_DUNDER_NAME))]))
|
|
165
168
|
|
|
166
|
-
[tree] = ast.parse(inspect.getsource(x)).body
|
|
169
|
+
[tree] = ast.parse(textwrap.dedent(inspect.getsource(x))).body
|
|
167
170
|
return _branch(
|
|
168
171
|
x,
|
|
169
172
|
lambda: serialize_function(x, tree),
|
|
@@ -11,6 +11,7 @@ from __future__ import annotations
|
|
|
11
11
|
import os
|
|
12
12
|
import shutil
|
|
13
13
|
import subprocess
|
|
14
|
+
import sys
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Any
|
|
16
17
|
|
|
@@ -41,7 +42,7 @@ class EnvironmentManager(BaseEnvironmentManager):
|
|
|
41
42
|
def build_spec(self, object_record: dict[str, Any]) -> dict[str, Any]:
|
|
42
43
|
"""Build a deterministic venv specification from an object version."""
|
|
43
44
|
|
|
44
|
-
base_python =
|
|
45
|
+
base_python = self._base_python_for_object(object_record)
|
|
45
46
|
distributions = self._normalize_distributions(object_record["distributions"])
|
|
46
47
|
runtime_packages = self._runtime_packages(distributions)
|
|
47
48
|
python_version = self._python_version(base_python)
|
|
@@ -74,6 +75,39 @@ class EnvironmentManager(BaseEnvironmentManager):
|
|
|
74
75
|
"force_rebuild": False,
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
def _base_python_for_object(self, object_record: dict[str, Any]) -> str:
|
|
79
|
+
stored_python = Path(str(object_record["env_python"])).expanduser().absolute()
|
|
80
|
+
if stored_python.exists():
|
|
81
|
+
return str(stored_python)
|
|
82
|
+
|
|
83
|
+
env_name = object_record.get("env")
|
|
84
|
+
if env_name:
|
|
85
|
+
try:
|
|
86
|
+
current_env = self.store.get_env(str(env_name))
|
|
87
|
+
except KeyError:
|
|
88
|
+
current_env = None
|
|
89
|
+
if current_env is not None:
|
|
90
|
+
env_python = Path(str(current_env.get("python"))).expanduser().absolute()
|
|
91
|
+
if env_python.exists():
|
|
92
|
+
return str(env_python)
|
|
93
|
+
|
|
94
|
+
if object_record.get("origin") == "server":
|
|
95
|
+
try:
|
|
96
|
+
default_env = self.store.get_env("default")
|
|
97
|
+
except KeyError:
|
|
98
|
+
default_env = None
|
|
99
|
+
if default_env is not None:
|
|
100
|
+
default_python = Path(
|
|
101
|
+
str(default_env.get("python"))
|
|
102
|
+
).expanduser().absolute()
|
|
103
|
+
if default_python.exists():
|
|
104
|
+
return str(default_python)
|
|
105
|
+
executable = Path(sys.executable).expanduser().absolute()
|
|
106
|
+
if executable.exists():
|
|
107
|
+
return str(executable)
|
|
108
|
+
|
|
109
|
+
return str(stored_python)
|
|
110
|
+
|
|
77
111
|
def _build_environment(self, spec: dict[str, Any]) -> None:
|
|
78
112
|
env_dir = Path(spec["venv_path"]).parent
|
|
79
113
|
venv_path = Path(spec["venv_path"])
|
|
@@ -9,7 +9,9 @@ from pathlib import Path
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
from urllib.error import HTTPError, URLError
|
|
11
11
|
from urllib.parse import quote, urlencode, urlparse
|
|
12
|
-
from urllib.request import Request
|
|
12
|
+
from urllib.request import Request
|
|
13
|
+
|
|
14
|
+
from spl._http import urlopen_verified, verified_https_context
|
|
13
15
|
|
|
14
16
|
DEFAULT_SERVER_URL = "https://splime.io/api"
|
|
15
17
|
DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 60.0
|
|
@@ -66,7 +68,7 @@ class ServerClient:
|
|
|
66
68
|
method=method,
|
|
67
69
|
)
|
|
68
70
|
try:
|
|
69
|
-
with
|
|
71
|
+
with urlopen_verified(request) as response:
|
|
70
72
|
raw = response.read().decode("utf-8")
|
|
71
73
|
except HTTPError as exc:
|
|
72
74
|
raw = exc.read().decode("utf-8")
|
|
@@ -95,7 +97,7 @@ class ServerClient:
|
|
|
95
97
|
def _bytes_request(self, path: str) -> bytes:
|
|
96
98
|
request = Request(f"{self.base_url}{path}", headers=self._headers())
|
|
97
99
|
try:
|
|
98
|
-
with
|
|
100
|
+
with urlopen_verified(request) as response:
|
|
99
101
|
return response.read()
|
|
100
102
|
except HTTPError as exc:
|
|
101
103
|
raw = exc.read().decode("utf-8")
|
|
@@ -128,15 +130,18 @@ class ServerClient:
|
|
|
128
130
|
url = urlparse(self.base_url)
|
|
129
131
|
if url.scheme not in {"http", "https"}:
|
|
130
132
|
raise ServerClientError(400, f"unsupported server URL scheme: {url.scheme}")
|
|
131
|
-
connection_class = (
|
|
132
|
-
http.client.HTTPSConnection
|
|
133
|
-
if url.scheme == "https"
|
|
134
|
-
else http.client.HTTPConnection
|
|
135
|
-
)
|
|
136
133
|
host = url.hostname
|
|
137
134
|
if not host:
|
|
138
135
|
raise ServerClientError(400, f"invalid server URL: {self.base_url}")
|
|
139
|
-
|
|
136
|
+
if url.scheme == "https":
|
|
137
|
+
connection = http.client.HTTPSConnection(
|
|
138
|
+
host,
|
|
139
|
+
url.port,
|
|
140
|
+
timeout=300,
|
|
141
|
+
context=verified_https_context(),
|
|
142
|
+
)
|
|
143
|
+
else:
|
|
144
|
+
connection = http.client.HTTPConnection(host, url.port, timeout=300)
|
|
140
145
|
request_path = f"{url.path.rstrip('/')}{path}"
|
|
141
146
|
request_headers = {
|
|
142
147
|
**self._headers(),
|
|
@@ -129,8 +129,20 @@ class ObjectRepository(RepositoryBase):
|
|
|
129
129
|
(remote_version_id,),
|
|
130
130
|
).fetchone()
|
|
131
131
|
if existing_remote is not None:
|
|
132
|
+
version_id = existing_remote["id"]
|
|
133
|
+
env_python = str(Path(env_record["python"]).expanduser().absolute())
|
|
134
|
+
if origin == "server" and Path(env_python).exists():
|
|
135
|
+
self._conn.execute(
|
|
136
|
+
"""
|
|
137
|
+
UPDATE object_versions
|
|
138
|
+
SET env = ?,
|
|
139
|
+
env_python = ?
|
|
140
|
+
WHERE id = ?
|
|
141
|
+
""",
|
|
142
|
+
(env, env_python, version_id),
|
|
143
|
+
)
|
|
132
144
|
return self.get_object_version(
|
|
133
|
-
|
|
145
|
+
version_id,
|
|
134
146
|
include_yaml=False,
|
|
135
147
|
)
|
|
136
148
|
|
|
@@ -1268,6 +1268,7 @@ class DaemonRuntime:
|
|
|
1268
1268
|
include_yaml=False,
|
|
1269
1269
|
**server_scope,
|
|
1270
1270
|
)
|
|
1271
|
+
self._ensure_server_object_envs([remote_current])
|
|
1271
1272
|
remote_object_id = remote_current["id"]
|
|
1272
1273
|
remote_version_id = remote_current["version_id"]
|
|
1273
1274
|
existing_current = self.store.get_object_by_remote_version(
|
|
@@ -1375,6 +1376,8 @@ class DaemonRuntime:
|
|
|
1375
1376
|
def _register_auto_server_env(self, name: str) -> dict[str, Any]:
|
|
1376
1377
|
try:
|
|
1377
1378
|
base_python = self.store.get_env("default")["python"]
|
|
1379
|
+
if not Path(str(base_python)).expanduser().exists():
|
|
1380
|
+
raise KeyError("default environment python is missing")
|
|
1378
1381
|
except KeyError:
|
|
1379
1382
|
base_python = sys.executable
|
|
1380
1383
|
return self.store.register_env(name, base_python)
|
|
@@ -1420,10 +1423,11 @@ class DaemonRuntime:
|
|
|
1420
1423
|
|
|
1421
1424
|
def _has_local_env(self, name: str) -> bool:
|
|
1422
1425
|
try:
|
|
1423
|
-
self.store.get_env(name)
|
|
1426
|
+
env = self.store.get_env(name)
|
|
1424
1427
|
except KeyError:
|
|
1425
1428
|
return False
|
|
1426
|
-
|
|
1429
|
+
python = env.get("python")
|
|
1430
|
+
return bool(python) and Path(str(python)).expanduser().exists()
|
|
1427
1431
|
|
|
1428
1432
|
def sync_once(
|
|
1429
1433
|
self,
|
|
@@ -1583,6 +1587,7 @@ class DaemonRuntime:
|
|
|
1583
1587
|
status="fetching_object",
|
|
1584
1588
|
message="registering object bundle in local daemon",
|
|
1585
1589
|
)
|
|
1590
|
+
self._ensure_server_object_envs([version])
|
|
1586
1591
|
object_record = self.register_object(
|
|
1587
1592
|
local_name,
|
|
1588
1593
|
version["entrypoint"],
|
|
@@ -14,7 +14,9 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any, Literal
|
|
15
15
|
from urllib.error import HTTPError, URLError
|
|
16
16
|
from urllib.parse import quote, urlencode
|
|
17
|
-
from urllib.request import Request
|
|
17
|
+
from urllib.request import Request
|
|
18
|
+
|
|
19
|
+
from spl._http import urlopen_verified
|
|
18
20
|
|
|
19
21
|
DEFAULT_SERVER_URL = "https://splime.io/api"
|
|
20
22
|
TERMINAL_REMOTE_RUN_STATUSES = {"succeeded", "failed", "cancelled", "stale"}
|
|
@@ -208,7 +210,7 @@ class SPLServerClient:
|
|
|
208
210
|
method=method,
|
|
209
211
|
)
|
|
210
212
|
try:
|
|
211
|
-
with
|
|
213
|
+
with urlopen_verified(request) as response:
|
|
212
214
|
raw = response.read().decode("utf-8")
|
|
213
215
|
except HTTPError as exc:
|
|
214
216
|
raw = exc.read().decode("utf-8")
|
|
@@ -232,7 +234,7 @@ class SPLServerClient:
|
|
|
232
234
|
def _bytes_request(self, path: str) -> bytes:
|
|
233
235
|
request = Request(f"{self.base_url}{path}", headers=self._headers())
|
|
234
236
|
try:
|
|
235
|
-
with
|
|
237
|
+
with urlopen_verified(request) as response:
|
|
236
238
|
return response.read()
|
|
237
239
|
except HTTPError as exc:
|
|
238
240
|
raw = exc.read().decode("utf-8")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: splime
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Reuse Python functions across projects without rewriting or redeploying them.
|
|
5
5
|
Author-email: Yastrebov Kirill <yastrebovks@gmail.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -21,6 +21,7 @@ Requires-Python: >=3.13
|
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
23
|
License-File: NOTICE
|
|
24
|
+
Requires-Dist: certifi==2026.6.17
|
|
24
25
|
Requires-Dist: pyyaml==6.0.3
|
|
25
26
|
Requires-Dist: keyring==25.7.0
|
|
26
27
|
Requires-Dist: quart==0.20.0
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|