blueticks 1.0.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.
- blueticks/__init__.py +25 -0
- blueticks/_base_resource.py +13 -0
- blueticks/_client.py +101 -0
- blueticks/_errors.py +120 -0
- blueticks/_transport.py +141 -0
- blueticks/_version.py +1 -0
- blueticks/py.typed +0 -0
- blueticks/resources/__init__.py +4 -0
- blueticks/resources/account.py +14 -0
- blueticks/resources/ping.py +14 -0
- blueticks/types/__init__.py +4 -0
- blueticks/types/account.py +17 -0
- blueticks/types/ping.py +13 -0
- blueticks-1.0.0.dist-info/METADATA +75 -0
- blueticks-1.0.0.dist-info/RECORD +18 -0
- blueticks-1.0.0.dist-info/WHEEL +5 -0
- blueticks-1.0.0.dist-info/licenses/LICENSE +21 -0
- blueticks-1.0.0.dist-info/top_level.txt +1 -0
blueticks/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from blueticks._client import Blueticks
|
|
2
|
+
from blueticks._errors import (
|
|
3
|
+
APIConnectionError,
|
|
4
|
+
APIError,
|
|
5
|
+
AuthenticationError,
|
|
6
|
+
BadRequestError,
|
|
7
|
+
BluetickError,
|
|
8
|
+
NotFoundError,
|
|
9
|
+
PermissionDeniedError,
|
|
10
|
+
RateLimitError,
|
|
11
|
+
)
|
|
12
|
+
from blueticks._version import __version__
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"APIConnectionError",
|
|
16
|
+
"APIError",
|
|
17
|
+
"AuthenticationError",
|
|
18
|
+
"BadRequestError",
|
|
19
|
+
"Blueticks",
|
|
20
|
+
"BluetickError",
|
|
21
|
+
"NotFoundError",
|
|
22
|
+
"PermissionDeniedError",
|
|
23
|
+
"RateLimitError",
|
|
24
|
+
"__version__",
|
|
25
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from blueticks._client import Blueticks
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseResource:
|
|
10
|
+
"""Shared plumbing for all resource classes. Holds a reference to the parent client."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, client: Blueticks) -> None:
|
|
13
|
+
self._client = client
|
blueticks/_client.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from blueticks.types.ping import Ping
|
|
10
|
+
|
|
11
|
+
from blueticks._errors import BluetickError
|
|
12
|
+
from blueticks._transport import Transport
|
|
13
|
+
from blueticks._version import __version__
|
|
14
|
+
|
|
15
|
+
_DEFAULT_BASE_URL = "https://api.blueticks.co"
|
|
16
|
+
_DEFAULT_TIMEOUT = 30.0
|
|
17
|
+
_DEFAULT_MAX_RETRIES = 2
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Blueticks:
|
|
21
|
+
"""Synchronous client for the Blueticks API."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
api_key: str | None = None,
|
|
26
|
+
*,
|
|
27
|
+
base_url: str | None = None,
|
|
28
|
+
timeout: float = _DEFAULT_TIMEOUT,
|
|
29
|
+
max_retries: int = _DEFAULT_MAX_RETRIES,
|
|
30
|
+
user_agent: str | None = None,
|
|
31
|
+
_http_transport: httpx.BaseTransport | None = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
resolved_key = api_key if api_key is not None else os.environ.get("BLUETICKS_API_KEY")
|
|
34
|
+
if not resolved_key:
|
|
35
|
+
raise BluetickError(
|
|
36
|
+
status_code=None,
|
|
37
|
+
code="authentication_required",
|
|
38
|
+
message=(
|
|
39
|
+
"No api_key provided. Pass Blueticks(api_key=...) "
|
|
40
|
+
"or set BLUETICKS_API_KEY in your environment."
|
|
41
|
+
),
|
|
42
|
+
request_id=None,
|
|
43
|
+
)
|
|
44
|
+
self._api_key = resolved_key
|
|
45
|
+
|
|
46
|
+
resolved_base = (
|
|
47
|
+
base_url
|
|
48
|
+
if base_url is not None
|
|
49
|
+
else os.environ.get("BLUETICKS_BASE_URL") or _DEFAULT_BASE_URL
|
|
50
|
+
)
|
|
51
|
+
self._base_url = resolved_base
|
|
52
|
+
|
|
53
|
+
self._transport = Transport(
|
|
54
|
+
api_key=self._api_key,
|
|
55
|
+
base_url=self._base_url,
|
|
56
|
+
timeout=timeout,
|
|
57
|
+
max_retries=max_retries,
|
|
58
|
+
user_agent_suffix=user_agent,
|
|
59
|
+
version=__version__,
|
|
60
|
+
http_transport=_http_transport,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Lazy-imported to avoid import cycles and to keep core import cheap.
|
|
64
|
+
from blueticks.resources.account import AccountResource
|
|
65
|
+
|
|
66
|
+
self.account = AccountResource(self)
|
|
67
|
+
|
|
68
|
+
# -- Public API ----------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
def ping(self) -> Ping:
|
|
71
|
+
from blueticks.resources.ping import PingResource
|
|
72
|
+
|
|
73
|
+
return PingResource(self).retrieve()
|
|
74
|
+
|
|
75
|
+
def close(self) -> None:
|
|
76
|
+
self._transport.close()
|
|
77
|
+
|
|
78
|
+
def __enter__(self) -> Blueticks:
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
def __exit__(self, *_: Any) -> None:
|
|
82
|
+
self.close()
|
|
83
|
+
|
|
84
|
+
# -- Internal ------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
def _request(
|
|
87
|
+
self,
|
|
88
|
+
method: str,
|
|
89
|
+
path: str,
|
|
90
|
+
*,
|
|
91
|
+
params: dict[str, Any] | None = None,
|
|
92
|
+
body: dict[str, Any] | None = None,
|
|
93
|
+
idempotency_key: str | None = None,
|
|
94
|
+
) -> Any:
|
|
95
|
+
return self._transport.request(
|
|
96
|
+
method,
|
|
97
|
+
path,
|
|
98
|
+
params=params,
|
|
99
|
+
body=body,
|
|
100
|
+
idempotency_key=idempotency_key,
|
|
101
|
+
)
|
blueticks/_errors.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BluetickError(Exception):
|
|
9
|
+
"""Base class for all Blueticks client errors."""
|
|
10
|
+
|
|
11
|
+
status_code: int | None
|
|
12
|
+
code: str | None
|
|
13
|
+
message: str
|
|
14
|
+
request_id: str | None
|
|
15
|
+
response: httpx.Response | None
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
*,
|
|
20
|
+
status_code: int | None = None,
|
|
21
|
+
code: str | None = None,
|
|
22
|
+
message: str = "",
|
|
23
|
+
request_id: str | None = None,
|
|
24
|
+
response: httpx.Response | None = None,
|
|
25
|
+
) -> None:
|
|
26
|
+
self.status_code = status_code
|
|
27
|
+
self.code = code
|
|
28
|
+
self.message = message
|
|
29
|
+
self.request_id = request_id
|
|
30
|
+
self.response = response
|
|
31
|
+
super().__init__(str(self))
|
|
32
|
+
|
|
33
|
+
def __str__(self) -> str:
|
|
34
|
+
return f"{self.status_code} {self.code}: {self.message} (request_id={self.request_id})"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AuthenticationError(BluetickError):
|
|
38
|
+
"""401 — missing or invalid API key."""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PermissionDeniedError(BluetickError):
|
|
42
|
+
"""403 — the key lacks the required scope or workspace access."""
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NotFoundError(BluetickError):
|
|
46
|
+
"""404 — the resource does not exist."""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BadRequestError(BluetickError):
|
|
50
|
+
"""400 / 422 — the request was malformed or failed validation."""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class RateLimitError(BluetickError):
|
|
54
|
+
"""429 — the client is being throttled."""
|
|
55
|
+
|
|
56
|
+
retry_after: float | None
|
|
57
|
+
|
|
58
|
+
def __init__(self, *, retry_after: float | None = None, **kwargs: Any) -> None:
|
|
59
|
+
super().__init__(**kwargs)
|
|
60
|
+
self.retry_after = retry_after
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class APIError(BluetickError):
|
|
64
|
+
"""5xx or otherwise-unparseable error response from the API."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class APIConnectionError(BluetickError):
|
|
68
|
+
"""Network failure: DNS, TLS, timeout, connection reset."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
_STATUS_TO_CLASS: dict[int, type[BluetickError]] = {
|
|
72
|
+
400: BadRequestError,
|
|
73
|
+
401: AuthenticationError,
|
|
74
|
+
403: PermissionDeniedError,
|
|
75
|
+
404: NotFoundError,
|
|
76
|
+
422: BadRequestError,
|
|
77
|
+
429: RateLimitError,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _class_for_status(status: int) -> type[BluetickError]:
|
|
82
|
+
return _STATUS_TO_CLASS.get(status, APIError)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def from_envelope(
|
|
86
|
+
*,
|
|
87
|
+
status_code: int,
|
|
88
|
+
body: Any,
|
|
89
|
+
response: httpx.Response | None,
|
|
90
|
+
retry_after: float | None = None,
|
|
91
|
+
) -> BluetickError:
|
|
92
|
+
"""Map a non-2xx response body to a typed Blueticks exception."""
|
|
93
|
+
cls = _class_for_status(status_code)
|
|
94
|
+
|
|
95
|
+
error = body.get("error") if isinstance(body, dict) else None
|
|
96
|
+
if isinstance(error, dict) and isinstance(error.get("code"), str):
|
|
97
|
+
kwargs: dict[str, Any] = {
|
|
98
|
+
"status_code": status_code,
|
|
99
|
+
"code": error.get("code"),
|
|
100
|
+
"message": error.get("message", ""),
|
|
101
|
+
"request_id": error.get("request_id"),
|
|
102
|
+
"response": response,
|
|
103
|
+
}
|
|
104
|
+
if cls is RateLimitError:
|
|
105
|
+
kwargs["retry_after"] = retry_after
|
|
106
|
+
return cls(**kwargs)
|
|
107
|
+
|
|
108
|
+
# Fallback: unknown shape. Render the body as a truncated string.
|
|
109
|
+
raw = body if isinstance(body, str) else repr(body)
|
|
110
|
+
truncated = raw[:200] + ("..." if len(raw) > 200 else "")
|
|
111
|
+
kwargs = {
|
|
112
|
+
"status_code": status_code,
|
|
113
|
+
"code": None,
|
|
114
|
+
"message": truncated,
|
|
115
|
+
"request_id": None,
|
|
116
|
+
"response": response,
|
|
117
|
+
}
|
|
118
|
+
if cls is RateLimitError:
|
|
119
|
+
kwargs["retry_after"] = retry_after
|
|
120
|
+
return cls(**kwargs)
|
blueticks/_transport.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import random
|
|
5
|
+
import time
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from blueticks._errors import (
|
|
11
|
+
APIConnectionError,
|
|
12
|
+
from_envelope,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_RETRIABLE_STATUS = {429, 502, 503, 504}
|
|
16
|
+
_IDEMPOTENT_METHODS = {"GET", "HEAD", "OPTIONS", "DELETE", "PATCH", "PUT"}
|
|
17
|
+
# Note: PATCH is not strictly idempotent per RFC 5789 but most REST APIs treat
|
|
18
|
+
# partial updates as idempotent in practice. Matches Stripe/OpenAI convention.
|
|
19
|
+
_BACKOFF_BASE_SECONDS = 0.5
|
|
20
|
+
_BACKOFF_CAP_SECONDS = 8.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Transport:
|
|
24
|
+
"""HTTP transport for the Blueticks API. Wraps httpx.Client with retries."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
*,
|
|
29
|
+
api_key: str,
|
|
30
|
+
base_url: str,
|
|
31
|
+
timeout: float,
|
|
32
|
+
max_retries: int,
|
|
33
|
+
user_agent_suffix: str | None,
|
|
34
|
+
version: str,
|
|
35
|
+
http_transport: httpx.BaseTransport | None = None,
|
|
36
|
+
) -> None:
|
|
37
|
+
self._api_key = api_key
|
|
38
|
+
self._base_url = base_url.rstrip("/")
|
|
39
|
+
self._max_retries = max_retries
|
|
40
|
+
self._version = version
|
|
41
|
+
self._user_agent = f"blueticks-python/{version}"
|
|
42
|
+
if user_agent_suffix:
|
|
43
|
+
self._user_agent = f"{self._user_agent} {user_agent_suffix}"
|
|
44
|
+
self._client = httpx.Client(
|
|
45
|
+
base_url=self._base_url,
|
|
46
|
+
timeout=timeout,
|
|
47
|
+
transport=http_transport,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def close(self) -> None:
|
|
51
|
+
self._client.close()
|
|
52
|
+
|
|
53
|
+
def request(
|
|
54
|
+
self,
|
|
55
|
+
method: str,
|
|
56
|
+
path: str,
|
|
57
|
+
*,
|
|
58
|
+
params: dict[str, Any] | None,
|
|
59
|
+
body: dict[str, Any] | None,
|
|
60
|
+
idempotency_key: str | None = None,
|
|
61
|
+
) -> Any:
|
|
62
|
+
method = method.upper()
|
|
63
|
+
headers: dict[str, str] = {
|
|
64
|
+
"Authorization": f"Bearer {self._api_key}",
|
|
65
|
+
"User-Agent": self._user_agent,
|
|
66
|
+
"Accept": "application/json",
|
|
67
|
+
}
|
|
68
|
+
if body is not None:
|
|
69
|
+
headers["Content-Type"] = "application/json"
|
|
70
|
+
if idempotency_key:
|
|
71
|
+
headers["Idempotency-Key"] = idempotency_key
|
|
72
|
+
|
|
73
|
+
is_idempotent = method in _IDEMPOTENT_METHODS or idempotency_key is not None
|
|
74
|
+
|
|
75
|
+
attempt = 0
|
|
76
|
+
while True:
|
|
77
|
+
try:
|
|
78
|
+
response = self._client.request(
|
|
79
|
+
method,
|
|
80
|
+
path,
|
|
81
|
+
params=params,
|
|
82
|
+
content=json.dumps(body).encode() if body is not None else None,
|
|
83
|
+
headers=headers,
|
|
84
|
+
)
|
|
85
|
+
except httpx.TransportError as exc:
|
|
86
|
+
if attempt < self._max_retries:
|
|
87
|
+
self._sleep_backoff(attempt, retry_after=None)
|
|
88
|
+
attempt += 1
|
|
89
|
+
continue
|
|
90
|
+
raise APIConnectionError(
|
|
91
|
+
status_code=None,
|
|
92
|
+
code=None,
|
|
93
|
+
message=f"connection error: {exc}",
|
|
94
|
+
request_id=None,
|
|
95
|
+
response=None,
|
|
96
|
+
) from exc
|
|
97
|
+
|
|
98
|
+
if 200 <= response.status_code < 300:
|
|
99
|
+
if not response.content:
|
|
100
|
+
return None
|
|
101
|
+
return response.json()
|
|
102
|
+
|
|
103
|
+
parsed_body: Any
|
|
104
|
+
try:
|
|
105
|
+
parsed_body = response.json()
|
|
106
|
+
except (json.JSONDecodeError, ValueError):
|
|
107
|
+
parsed_body = response.text
|
|
108
|
+
|
|
109
|
+
retry_after = self._parse_retry_after(response) if response.status_code == 429 else None
|
|
110
|
+
|
|
111
|
+
retriable_status = response.status_code in _RETRIABLE_STATUS
|
|
112
|
+
if retriable_status and is_idempotent and attempt < self._max_retries:
|
|
113
|
+
self._sleep_backoff(attempt, retry_after=retry_after)
|
|
114
|
+
attempt += 1
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
raise from_envelope(
|
|
118
|
+
status_code=response.status_code,
|
|
119
|
+
body=parsed_body,
|
|
120
|
+
response=response,
|
|
121
|
+
retry_after=retry_after,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@staticmethod
|
|
125
|
+
def _parse_retry_after(response: httpx.Response) -> float | None:
|
|
126
|
+
raw = response.headers.get("retry-after")
|
|
127
|
+
if raw is None:
|
|
128
|
+
return None
|
|
129
|
+
try:
|
|
130
|
+
return float(raw)
|
|
131
|
+
except ValueError:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _sleep_backoff(attempt: int, *, retry_after: float | None) -> None:
|
|
136
|
+
if retry_after is not None:
|
|
137
|
+
time.sleep(retry_after)
|
|
138
|
+
return
|
|
139
|
+
backoff = min(_BACKOFF_BASE_SECONDS * (2**attempt), _BACKOFF_CAP_SECONDS)
|
|
140
|
+
jitter = random.uniform(0, _BACKOFF_BASE_SECONDS)
|
|
141
|
+
time.sleep(backoff + jitter)
|
blueticks/_version.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.0.0"
|
blueticks/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from blueticks._base_resource import BaseResource
|
|
4
|
+
from blueticks.types.account import Account
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AccountResource(BaseResource):
|
|
8
|
+
def retrieve(self) -> Account:
|
|
9
|
+
"""Retrieve the authenticated account.
|
|
10
|
+
|
|
11
|
+
Returns the account associated with the API key used for this request.
|
|
12
|
+
"""
|
|
13
|
+
data = self._client._request("GET", "/v1/account")
|
|
14
|
+
return Account.model_validate(data)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from blueticks._base_resource import BaseResource
|
|
4
|
+
from blueticks.types.ping import Ping
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class PingResource(BaseResource):
|
|
8
|
+
def retrieve(self) -> Ping:
|
|
9
|
+
"""Health check.
|
|
10
|
+
|
|
11
|
+
Returns basic info about the authenticated API key.
|
|
12
|
+
"""
|
|
13
|
+
data = self._client._request("GET", "/v1/ping")
|
|
14
|
+
return Ping.model_validate(data)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Account(BaseModel):
|
|
10
|
+
"""Response from GET /v1/account."""
|
|
11
|
+
|
|
12
|
+
model_config = ConfigDict(extra="ignore")
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: str
|
|
16
|
+
timezone: Optional[str] = None # noqa: UP045
|
|
17
|
+
created_at: datetime
|
blueticks/types/ping.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blueticks
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official Python client for the Blueticks API.
|
|
5
|
+
Author-email: Blueticks <support@blueticks.co>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 serenix-com
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://blueticks.co
|
|
29
|
+
Project-URL: Documentation, https://docs.blueticks.co
|
|
30
|
+
Project-URL: Source, https://github.com/serenix-com/blueticks-python
|
|
31
|
+
Project-URL: Changelog, https://github.com/serenix-com/blueticks-python/blob/main/CHANGELOG.md
|
|
32
|
+
Keywords: blueticks,whatsapp,api,sdk
|
|
33
|
+
Classifier: Development Status :: 4 - Beta
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Requires-Python: >=3.9
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
46
|
+
Requires-Dist: pydantic<3.0,>=2.6
|
|
47
|
+
Provides-Extra: dev
|
|
48
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
49
|
+
Requires-Dist: pytest-httpx>=0.30; extra == "dev"
|
|
50
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
51
|
+
Requires-Dist: mypy>=1.10; extra == "dev"
|
|
52
|
+
Dynamic: license-file
|
|
53
|
+
|
|
54
|
+
# blueticks — Python client for the Blueticks API
|
|
55
|
+
|
|
56
|
+
Official Python SDK for [Blueticks](https://blueticks.co).
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install blueticks
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from blueticks import Blueticks
|
|
68
|
+
|
|
69
|
+
client = Blueticks(api_key="bt_live_...")
|
|
70
|
+
ping = client.ping()
|
|
71
|
+
account = client.account.retrieve()
|
|
72
|
+
print(account.name)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
See https://docs.blueticks.co for full documentation.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
blueticks/__init__.py,sha256=hmmpwdP3y-bD-NHJxeeKWbP7tvBuCE87y5Ye3BHCKs8,522
|
|
2
|
+
blueticks/_base_resource.py,sha256=itU_RAahBEmY91XxGIQoBS0uNIZe5rq797GUyLInNRU,328
|
|
3
|
+
blueticks/_client.py,sha256=-0NeYumrSuQWJkLK6TUO5GxQSi62DB8weo22j5m0A3g,2883
|
|
4
|
+
blueticks/_errors.py,sha256=zSnzxyJNEbWtCgxio9VNosydYwkJ0o7okRkSXRYAD9E,3320
|
|
5
|
+
blueticks/_transport.py,sha256=cbM8pSAZNv2jBfwe8pC0FBFvOVJHHfVxQzr22mkMk6o,4524
|
|
6
|
+
blueticks/_version.py,sha256=J-j-u0itpEFT6irdmWmixQqYMadNl1X91TxUmoiLHMI,22
|
|
7
|
+
blueticks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
blueticks/resources/__init__.py,sha256=1AtiCuEvYCjHHOup_m9yC8Dbk_RAl0Y6cOUkx1YMEQw,153
|
|
9
|
+
blueticks/resources/account.py,sha256=IXlD5KVbwYX4_wDhtkPRUPmP44oGxw2AmHF7H1gniDA,446
|
|
10
|
+
blueticks/resources/ping.py,sha256=Ger2Kn37nmomnSDVgcpxGoXDqlMaNRl6TBPUGythz28,387
|
|
11
|
+
blueticks/types/__init__.py,sha256=CrPKlrqRuiva695qZR2uU7z_56AL3UG1IxbWdfKFLx8,113
|
|
12
|
+
blueticks/types/account.py,sha256=dMgKf6PhAxA1v8MYbvQgacm93Q615-2tYJy3l_MCGvY,356
|
|
13
|
+
blueticks/types/ping.py,sha256=yUiZBRsa0IvYe4fhURwVM-dS-2pDZNUZsk5IE1Kx1pY,252
|
|
14
|
+
blueticks-1.0.0.dist-info/licenses/LICENSE,sha256=N9gRIkVgfrCtyAj5rMQJcKxcBSbhqmycHvtHbpNWOso,1068
|
|
15
|
+
blueticks-1.0.0.dist-info/METADATA,sha256=d0KHvdg4D-0ATI3Vl945HEiT-4S1Ih0lBqiSFH-eJ4E,2928
|
|
16
|
+
blueticks-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
17
|
+
blueticks-1.0.0.dist-info/top_level.txt,sha256=Os-3VKE73EIGlrIrw37TpewHTTryFOZRrV2s71v5aPc,10
|
|
18
|
+
blueticks-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 serenix-com
|
|
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.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blueticks
|