stackshift 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.
- stackshift/__init__.py +5 -0
- stackshift/assets.py +89 -0
- stackshift/client.py +44 -0
- stackshift/projects.py +16 -0
- stackshift-0.1.0.dist-info/METADATA +6 -0
- stackshift-0.1.0.dist-info/RECORD +8 -0
- stackshift-0.1.0.dist-info/WHEEL +5 -0
- stackshift-0.1.0.dist-info/top_level.txt +1 -0
stackshift/__init__.py
ADDED
stackshift/assets.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, BinaryIO
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AssetsClient:
|
|
9
|
+
def __init__(self, client: Any) -> None:
|
|
10
|
+
self._client = client
|
|
11
|
+
|
|
12
|
+
def upload(
|
|
13
|
+
self,
|
|
14
|
+
file: str | Path | BinaryIO,
|
|
15
|
+
*,
|
|
16
|
+
bucket: str | None = None,
|
|
17
|
+
key: str | None = None,
|
|
18
|
+
folder: str | None = None,
|
|
19
|
+
visibility: str | None = None,
|
|
20
|
+
cache_control: str | None = None,
|
|
21
|
+
metadata: dict[str, Any] | None = None,
|
|
22
|
+
) -> dict[str, Any]:
|
|
23
|
+
return self._multipart("POST", "/assets/upload", file, {
|
|
24
|
+
"bucket": bucket,
|
|
25
|
+
"key": key,
|
|
26
|
+
"folder": folder,
|
|
27
|
+
"visibility": visibility,
|
|
28
|
+
"cacheControl": cache_control,
|
|
29
|
+
"metadata": json.dumps(metadata) if metadata else None,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
def list(self, **params: Any) -> dict[str, Any]:
|
|
33
|
+
clean = {key: value for key, value in params.items() if value not in (None, "")}
|
|
34
|
+
return self._client.request("GET", "/assets", params=clean)
|
|
35
|
+
|
|
36
|
+
def get(self, asset_id: str) -> dict[str, Any]:
|
|
37
|
+
return self._client.request("GET", f"/assets/{asset_id}")
|
|
38
|
+
|
|
39
|
+
def delete(self, asset_id: str) -> dict[str, Any]:
|
|
40
|
+
return self._client.request("DELETE", f"/assets/{asset_id}")
|
|
41
|
+
|
|
42
|
+
def replace(
|
|
43
|
+
self,
|
|
44
|
+
asset_id: str,
|
|
45
|
+
file: str | Path | BinaryIO,
|
|
46
|
+
*,
|
|
47
|
+
cache_control: str | None = None,
|
|
48
|
+
metadata: dict[str, Any] | None = None,
|
|
49
|
+
) -> dict[str, Any]:
|
|
50
|
+
return self._multipart("PUT", f"/assets/{asset_id}/replace", file, {
|
|
51
|
+
"cacheControl": cache_control,
|
|
52
|
+
"metadata": json.dumps(metadata) if metadata else None,
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
def signed_url(
|
|
56
|
+
self,
|
|
57
|
+
asset_id: str,
|
|
58
|
+
*,
|
|
59
|
+
expires_in: str = "10m",
|
|
60
|
+
max_downloads: int | None = None,
|
|
61
|
+
) -> dict[str, Any]:
|
|
62
|
+
return self._client.request("POST", f"/assets/{asset_id}/signed-url", json={
|
|
63
|
+
"expiresIn": expires_in,
|
|
64
|
+
"maxDownloads": max_downloads,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
def signed_upload_url(self, **payload: Any) -> dict[str, Any]:
|
|
68
|
+
return self.create_upload_session(**payload)
|
|
69
|
+
|
|
70
|
+
def create_upload_session(self, **payload: Any) -> dict[str, Any]:
|
|
71
|
+
clean = {key: value for key, value in payload.items() if value not in (None, "")}
|
|
72
|
+
return self._client.request("POST", "/assets/upload-sessions", json=clean)
|
|
73
|
+
|
|
74
|
+
def _multipart(self, method: str, suffix: str, file: str | Path | BinaryIO, fields: dict[str, Any]) -> dict[str, Any]:
|
|
75
|
+
should_close = False
|
|
76
|
+
if isinstance(file, (str, Path)):
|
|
77
|
+
handle = Path(file).open("rb")
|
|
78
|
+
filename = Path(file).name
|
|
79
|
+
should_close = True
|
|
80
|
+
else:
|
|
81
|
+
handle = file
|
|
82
|
+
filename = getattr(file, "name", "asset")
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
data = {key: value for key, value in fields.items() if value not in (None, "")}
|
|
86
|
+
return self._client.request(method, suffix, data=data, files={"file": (filename, handle)})
|
|
87
|
+
finally:
|
|
88
|
+
if should_close:
|
|
89
|
+
handle.close()
|
stackshift/client.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class StackShift:
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
*,
|
|
13
|
+
api_key: str | None = None,
|
|
14
|
+
base_url: str = "https://api.stackshift.cloud/api/v1",
|
|
15
|
+
session: requests.Session | None = None,
|
|
16
|
+
) -> None:
|
|
17
|
+
api_key = api_key or os.getenv("STACKSHIFT_API_KEY")
|
|
18
|
+
if not api_key:
|
|
19
|
+
raise ValueError("StackShift SDK requires an api_key")
|
|
20
|
+
|
|
21
|
+
self.api_key = api_key
|
|
22
|
+
self.base_url = base_url.rstrip("/")
|
|
23
|
+
self.session = session or requests.Session()
|
|
24
|
+
|
|
25
|
+
from .assets import AssetsClient
|
|
26
|
+
from .projects import ProjectsClient
|
|
27
|
+
|
|
28
|
+
self.projects = ProjectsClient(self)
|
|
29
|
+
self.assets = AssetsClient(self)
|
|
30
|
+
|
|
31
|
+
def request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
32
|
+
headers = kwargs.pop("headers", {})
|
|
33
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
34
|
+
headers["User-Agent"] = "StackShift-Python/0.1"
|
|
35
|
+
|
|
36
|
+
response = self.session.request(method, f"{self.base_url}{path}", headers=headers, **kwargs)
|
|
37
|
+
payload = response.json() if response.content else None
|
|
38
|
+
if response.status_code < 200 or response.status_code >= 300:
|
|
39
|
+
message = payload.get("message") if isinstance(payload, dict) else None
|
|
40
|
+
raise RuntimeError(message or f"StackShift API request failed with {response.status_code}")
|
|
41
|
+
|
|
42
|
+
if isinstance(payload, dict) and "success" in payload and "data" in payload:
|
|
43
|
+
return payload["data"]
|
|
44
|
+
return payload
|
stackshift/projects.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ProjectsClient:
|
|
7
|
+
def __init__(self, client: Any) -> None:
|
|
8
|
+
self._client = client
|
|
9
|
+
|
|
10
|
+
def list(self, *, page: int | None = None, per_page: int | None = None) -> list[dict[str, Any]]:
|
|
11
|
+
params = {
|
|
12
|
+
key: value
|
|
13
|
+
for key, value in {"page": page, "per_page": per_page}.items()
|
|
14
|
+
if value not in (None, "")
|
|
15
|
+
}
|
|
16
|
+
return self._client.request("GET", "/projects/", params=params)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
stackshift/__init__.py,sha256=Z0Q6SrKZyIx-YBt5pdIVXgnJGF4TeBYKtUA7vdxKlU4,161
|
|
2
|
+
stackshift/assets.py,sha256=qvu1qXd5wJqeWc4hpdGYcorXXMtZUNEwjlaT61lu7-A,3119
|
|
3
|
+
stackshift/client.py,sha256=YM9xgy0145f37l8x6A-Ecrg9zw5Z1E6DTRKEdiQ0P4g,1566
|
|
4
|
+
stackshift/projects.py,sha256=oYWaE7xNRR6OXuPEwJH-oFFeOmDA9ozq6jJY09ANIHA,498
|
|
5
|
+
stackshift-0.1.0.dist-info/METADATA,sha256=M9M2rhNT9c7bNRMCQkRTQ-Db8mYZ7T_Ccxva3KXCnq8,152
|
|
6
|
+
stackshift-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
7
|
+
stackshift-0.1.0.dist-info/top_level.txt,sha256=qe3uWsu_O9mNtq7bIck9Y8rjTIMrH0kzgAlGFDoRGqs,11
|
|
8
|
+
stackshift-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
stackshift
|