pixlub 0.3.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.
pixlub/__init__.py ADDED
@@ -0,0 +1,71 @@
1
+ """Official Python SDK for the Pixlub API::
2
+
3
+ import pixlub
4
+
5
+ client = pixlub.Client() # reads PIXLUB_API_KEY
6
+ result = client.tools.run("removeBgApi", image="photo.jpg")
7
+ result.save(f"photo-nobg{result.extension}")
8
+ """
9
+
10
+ from ._config import API_KEY_ENV_VAR, BASE_URL_ENV_VAR, DEFAULT_BASE_URL
11
+ from ._core import DIRECT_UPLOAD_MAX_BYTES, MAX_INPUT_FILES
12
+ from ._files import SourceURL
13
+ from ._version import __version__
14
+ from .async_client import AsyncClient
15
+ from .client import Client
16
+ from .errors import (
17
+ APIConnectionError,
18
+ APIError,
19
+ APITimeoutError,
20
+ AuthenticationError,
21
+ ConfigurationError,
22
+ FileTooLargeError,
23
+ InsufficientCreditsError,
24
+ InvalidInputError,
25
+ InvalidRequestError,
26
+ JobTimeoutError,
27
+ NotFoundError,
28
+ PermissionDeniedError,
29
+ PixlubError,
30
+ RateLimitError,
31
+ ServerError,
32
+ ServiceUnavailableError,
33
+ UnexpectedResponseError,
34
+ UnknownToolError,
35
+ )
36
+ from .models import Tool, ToolInput, ToolOutput, ToolOutputSpec, ToolResult
37
+
38
+ __all__ = [
39
+ "API_KEY_ENV_VAR",
40
+ "BASE_URL_ENV_VAR",
41
+ "DEFAULT_BASE_URL",
42
+ "DIRECT_UPLOAD_MAX_BYTES",
43
+ "MAX_INPUT_FILES",
44
+ "APIConnectionError",
45
+ "APIError",
46
+ "APITimeoutError",
47
+ "AsyncClient",
48
+ "AuthenticationError",
49
+ "Client",
50
+ "ConfigurationError",
51
+ "FileTooLargeError",
52
+ "InsufficientCreditsError",
53
+ "InvalidInputError",
54
+ "InvalidRequestError",
55
+ "JobTimeoutError",
56
+ "NotFoundError",
57
+ "PermissionDeniedError",
58
+ "PixlubError",
59
+ "RateLimitError",
60
+ "ServerError",
61
+ "ServiceUnavailableError",
62
+ "SourceURL",
63
+ "Tool",
64
+ "ToolInput",
65
+ "ToolOutput",
66
+ "ToolOutputSpec",
67
+ "ToolResult",
68
+ "UnexpectedResponseError",
69
+ "UnknownToolError",
70
+ "__version__",
71
+ ]
pixlub/_config.py ADDED
@@ -0,0 +1,83 @@
1
+ """API key and base URL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import re
7
+ from typing import Optional
8
+ from urllib.parse import urljoin, urlsplit, urlunsplit
9
+
10
+ from .errors import ConfigurationError
11
+
12
+ API_KEY_ENV_VAR = "PIXLUB_API_KEY"
13
+ BASE_URL_ENV_VAR = "PIXLUB_BASE_URL"
14
+ DEFAULT_BASE_URL = "https://api.pixlub.com"
15
+
16
+ _API_KEY_RE = re.compile(r"pix\.[A-Za-z0-9_-]{1,64}\.[A-Za-z0-9_-]{1,256}")
17
+ _LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1"})
18
+
19
+
20
+ def resolve_api_key(api_key: Optional[str]) -> str:
21
+ raw = api_key if api_key is not None else os.environ.get(API_KEY_ENV_VAR)
22
+ key = raw.strip() if raw else ""
23
+ if not key:
24
+ raise ConfigurationError(
25
+ "No API key provided. Pass api_key=... or set the "
26
+ f"{API_KEY_ENV_VAR} environment variable."
27
+ )
28
+ if not _API_KEY_RE.fullmatch(key):
29
+ raise ConfigurationError(
30
+ "The API key is malformed. Copy the whole key (pix.<prefix>.<secret>) "
31
+ "from your dashboard."
32
+ )
33
+ return key
34
+
35
+
36
+ def resolve_base_url(base_url: Optional[str]) -> str:
37
+ raw = base_url if base_url is not None else os.environ.get(BASE_URL_ENV_VAR)
38
+ url = (raw or DEFAULT_BASE_URL).strip()
39
+ try:
40
+ parts = urlsplit(url)
41
+ parts.port # noqa: B018 - raises ValueError on an invalid port
42
+ except ValueError:
43
+ raise ConfigurationError(
44
+ "Invalid base URL. Expected a form like https://api.example.com"
45
+ ) from None
46
+
47
+ if parts.scheme not in ("http", "https") or not parts.hostname:
48
+ raise ConfigurationError(
49
+ "The base URL must start with http:// or https:// and name a host."
50
+ )
51
+ if parts.username is not None or parts.password is not None:
52
+ raise ConfigurationError("Do not put credentials in the base URL. Use api_key instead.")
53
+ if parts.query or parts.fragment:
54
+ raise ConfigurationError("The base URL must not contain a query string or a fragment.")
55
+ if parts.scheme == "http" and parts.hostname not in _LOOPBACK_HOSTS:
56
+ raise ConfigurationError(
57
+ "Plain http:// is only accepted for localhost. Use https:// so your API key "
58
+ "is encrypted in transit."
59
+ )
60
+
61
+ return urlunsplit((parts.scheme, parts.netloc, parts.path.rstrip("/"), "", ""))
62
+
63
+
64
+ def resolve_download_url(url: str, base_url: str) -> str:
65
+ if not isinstance(url, str) or not url.strip():
66
+ raise ValueError("empty link")
67
+ absolute = urljoin(base_url + "/", url.strip())
68
+ parts = urlsplit(absolute)
69
+ if parts.scheme not in ("http", "https") or not parts.hostname:
70
+ raise ValueError("link is not an http(s) URL")
71
+ if parts.username is not None or parts.password is not None:
72
+ raise ValueError("link carries credentials")
73
+ if parts.scheme == "http" and parts.hostname not in _LOOPBACK_HOSTS:
74
+ raise ValueError("link uses plain http outside localhost")
75
+ return absolute
76
+
77
+
78
+ def key_mask(api_key: str) -> str:
79
+ return f"pix.{api_key.split('.')[1]}.***"
80
+
81
+
82
+ def redact(text: str, api_key: str) -> str:
83
+ return text.replace(api_key, key_mask(api_key))