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 ADDED
@@ -0,0 +1,5 @@
1
+ from .client import StackShift
2
+ from .assets import AssetsClient
3
+ from .projects import ProjectsClient
4
+
5
+ __all__ = ["StackShift", "AssetsClient", "ProjectsClient"]
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,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: stackshift
3
+ Version: 0.1.0
4
+ Summary: Official Python SDK for StackShift.
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: requests>=2.31
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ stackshift