bossalabs 0.1.0__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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: bossalabs
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.10
5
+ Description-Content-Type: text/markdown
6
+ Requires-Dist: boto3
7
+ Requires-Dist: requests
8
+ Requires-Dist: fire
9
+ Requires-Dist: tomli; python_version < "3.11"
10
+ Requires-Dist: requests-toolbelt>=1.0.0
11
+ Requires-Dist: mypy
12
+ Requires-Dist: ruff
13
+ Requires-Dist: pytest
14
+ Requires-Dist: types-requests
15
+ Requires-Dist: tqdm>=4.67.3
16
+ Requires-Dist: python-dotenv>=1.2.2
17
+
18
+ [![python](https://img.shields.io/badge/python-3.13-blue?logo=python)](https://www.python.org/)
19
+ [![CI](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml)
20
+
21
+ # BossaLabs Python Client
22
+
23
+ Small Python client for requesting upload authorization and uploading
24
+ video files using a presigned S3 POST.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ uv venv .venv
30
+ source .venv/bin/activate
31
+ uv pip install -e .
32
+ ```
33
+
34
+ Or with plain pip:
35
+
36
+ ```bash
37
+ pip install -e .
38
+ ```
39
+
40
+ Or from PyPI:
41
+
42
+ ```bash
43
+ pip install bossalabs
44
+ ```
45
+
46
+ ## Python usage
47
+
48
+ ```python
49
+ from bossalabs import Client
50
+
51
+ client = Client(
52
+ api_key="YOUR_API_KEY",
53
+ model="MODEL_NAME"
54
+ )
55
+
56
+ result = client.upload_video("/path/to/video.mp4")
57
+ print(result)
58
+ ```
59
+
60
+ ## CLI usage
61
+
62
+ After installation, you can run:
63
+
64
+ ```bash
65
+ bossalabs process-video "/path/to/video.mp4" "MODEL_NAME" --api_key "YOUR_API_KEY"
66
+ ```
67
+
68
+ ## Documentation
69
+
70
+ Full documentation is built with [MkDocs](https://www.mkdocs.org/) and
71
+ [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
72
+
73
+ ### Install documentation dependencies
74
+
75
+ ```bash
76
+ uv sync --group docs
77
+ ```
78
+
79
+ ### Serve docs locally
80
+
81
+ Preview the site with live reload:
82
+
83
+ ```bash
84
+ uv run mkdocs serve
85
+ ```
86
+
87
+ Open [http://127.0.0.1:8000](http://127.0.0.1:8000) in your browser.
88
+
89
+ ### Build static HTML
90
+
91
+ Generate the static site into the `site/` directory:
92
+
93
+ ```bash
94
+ uv run mkdocs build
95
+ ```
96
+
97
+ The HTML output is written to `site/` (gitignored). Deploy that folder to any
98
+ static hosting provider.
99
+
100
+ ### What is documented
101
+
102
+ The docs cover the public user-facing API:
103
+
104
+ - `bossalabs.Client` — authorize and upload video files
105
+ - `bossalabs.UploadClientError` — authorization failure type
106
+ - `bossalabs.API_BASE_URL` — default API endpoint constant
107
+ - `bossalabs` CLI — `process-video` command
108
+
109
+ Internal modules (`bossalabs.models`, scripts, and private helpers) are
110
+ excluded from the API reference.
@@ -0,0 +1,93 @@
1
+ [![python](https://img.shields.io/badge/python-3.13-blue?logo=python)](https://www.python.org/)
2
+ [![CI](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml)
3
+
4
+ # BossaLabs Python Client
5
+
6
+ Small Python client for requesting upload authorization and uploading
7
+ video files using a presigned S3 POST.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ uv venv .venv
13
+ source .venv/bin/activate
14
+ uv pip install -e .
15
+ ```
16
+
17
+ Or with plain pip:
18
+
19
+ ```bash
20
+ pip install -e .
21
+ ```
22
+
23
+ Or from PyPI:
24
+
25
+ ```bash
26
+ pip install bossalabs
27
+ ```
28
+
29
+ ## Python usage
30
+
31
+ ```python
32
+ from bossalabs import Client
33
+
34
+ client = Client(
35
+ api_key="YOUR_API_KEY",
36
+ model="MODEL_NAME"
37
+ )
38
+
39
+ result = client.upload_video("/path/to/video.mp4")
40
+ print(result)
41
+ ```
42
+
43
+ ## CLI usage
44
+
45
+ After installation, you can run:
46
+
47
+ ```bash
48
+ bossalabs process-video "/path/to/video.mp4" "MODEL_NAME" --api_key "YOUR_API_KEY"
49
+ ```
50
+
51
+ ## Documentation
52
+
53
+ Full documentation is built with [MkDocs](https://www.mkdocs.org/) and
54
+ [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
55
+
56
+ ### Install documentation dependencies
57
+
58
+ ```bash
59
+ uv sync --group docs
60
+ ```
61
+
62
+ ### Serve docs locally
63
+
64
+ Preview the site with live reload:
65
+
66
+ ```bash
67
+ uv run mkdocs serve
68
+ ```
69
+
70
+ Open [http://127.0.0.1:8000](http://127.0.0.1:8000) in your browser.
71
+
72
+ ### Build static HTML
73
+
74
+ Generate the static site into the `site/` directory:
75
+
76
+ ```bash
77
+ uv run mkdocs build
78
+ ```
79
+
80
+ The HTML output is written to `site/` (gitignored). Deploy that folder to any
81
+ static hosting provider.
82
+
83
+ ### What is documented
84
+
85
+ The docs cover the public user-facing API:
86
+
87
+ - `bossalabs.Client` — authorize and upload video files
88
+ - `bossalabs.UploadClientError` — authorization failure type
89
+ - `bossalabs.API_BASE_URL` — default API endpoint constant
90
+ - `bossalabs` CLI — `process-video` command
91
+
92
+ Internal modules (`bossalabs.models`, scripts, and private helpers) are
93
+ excluded from the API reference.
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "bossalabs"
3
+ version = "0.1.0"
4
+ readme = "README.md"
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "boto3",
8
+ "requests",
9
+ "fire",
10
+ "tomli; python_version < '3.11'",
11
+ "requests-toolbelt>=1.0.0",
12
+ "mypy",
13
+ "ruff",
14
+ "pytest",
15
+ "types-requests",
16
+ "tqdm>=4.67.3",
17
+ "python-dotenv>=1.2.2",
18
+ ]
19
+
20
+ [build-system]
21
+ requires = ["setuptools>=68", "wheel"]
22
+ build-backend = "setuptools.build_meta"
23
+
24
+ [project.scripts]
25
+ bossalabs = "bossalabs.cli.main:main"
26
+
27
+ [dependency-groups]
28
+ docs = [
29
+ "mkdocs>=1.6",
30
+ "mkdocs-material>=9.5",
31
+ "mkdocstrings[python]>=0.27",
32
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,29 @@
1
+ """Public API for the BossaLabs Python client.
2
+
3
+ Import the client module to authorize and upload videos::
4
+
5
+ from bossalabs import client
6
+
7
+ c = client.Client(api_key="...", model="...")
8
+ result = c.upload_video("video.mp4")
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from importlib.metadata import version
14
+
15
+ from bossalabs.client import Client
16
+ from bossalabs.constants import API_BASE_URL
17
+ from bossalabs.errors import UploadClientError
18
+
19
+ from . import client
20
+
21
+ __version__ = version("bossalabs")
22
+
23
+ __all__ = [
24
+ "client",
25
+ "Client",
26
+ "UploadClientError",
27
+ "API_BASE_URL",
28
+ "__version__",
29
+ ]
@@ -0,0 +1 @@
1
+ """CLI package for bossalabs commands."""
@@ -0,0 +1,95 @@
1
+ """Command-line interface for uploading videos to BossaLabs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+
8
+ from bossalabs.client import Client
9
+
10
+
11
+ def _print_banner() -> None:
12
+ """Print a compact CLI banner."""
13
+ print("=" * 54)
14
+ print("🎬 BossaLabs Video Processor")
15
+ print("=" * 54)
16
+
17
+
18
+ def _print_success(result: dict[str, object]) -> None:
19
+ """Print success message with formatted payload."""
20
+ print("✅ Video processed successfully.")
21
+ print(f"Model: {result.get('model')}")
22
+ print(
23
+ f"🚨 IMPORTANT: Your Job ID is: [{result.get('job_id')}] "
24
+ "You need this ID to request the results."
25
+ )
26
+ print(json.dumps(result, indent=2))
27
+
28
+
29
+ def _print_error(message: str) -> None:
30
+ """Print a formatted failure message."""
31
+ print(f"❌ Video processing failed: {message}")
32
+
33
+
34
+ def _process_video(args: argparse.Namespace) -> int:
35
+ """Upload a video and print the result.
36
+
37
+ Returns:
38
+ ``0`` on success, ``1`` when the upload fails.
39
+ """
40
+ video_path = args.video_path
41
+ api_key = args.api_key
42
+ model = args.model
43
+
44
+ if not video_path:
45
+ raise ValueError("video_path is required")
46
+ if not api_key:
47
+ raise ValueError("api_key is required")
48
+ if not model:
49
+ raise ValueError("model is required")
50
+
51
+ _print_banner()
52
+ client = Client(api_key=api_key, model=model)
53
+ result = client.upload_video(video_path)
54
+
55
+ if result.get("success", False) is False:
56
+ error_message = str(result.get("error", "Some error occurred."))
57
+ _print_error(error_message)
58
+ return 1
59
+
60
+ _print_success(result)
61
+ return 0
62
+
63
+
64
+ def build_parser() -> argparse.ArgumentParser:
65
+ """Build the ``bossalabs`` argument parser and subcommands."""
66
+ parser = argparse.ArgumentParser(prog="bossalabs")
67
+ subparsers = parser.add_subparsers(dest="command", required=True)
68
+
69
+ process_video = subparsers.add_parser("process-video")
70
+ process_video.add_argument("video_path")
71
+ process_video.add_argument("model")
72
+ process_video.add_argument("--api_key", required=True)
73
+
74
+ return parser
75
+
76
+
77
+ def main() -> int:
78
+ """Run the ``bossalabs`` CLI.
79
+
80
+ Returns:
81
+ ``0`` on success, ``1`` on upload failure, ``2`` for an unknown
82
+ command.
83
+ """
84
+ parser = build_parser()
85
+ args = parser.parse_args()
86
+
87
+ if args.command == "process-video":
88
+ return _process_video(args)
89
+
90
+ parser.error(f"Unknown command: {args.command}")
91
+ return 2
92
+
93
+
94
+ if __name__ == "__main__":
95
+ raise SystemExit(main())
@@ -0,0 +1,254 @@
1
+ """BossaLabs API client for authorizing and uploading video files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, metadata, version
6
+ from pathlib import Path
7
+ import socket
8
+ from typing import Any
9
+
10
+ import requests # type: ignore[import-untyped]
11
+ from requests_toolbelt.multipart.encoder import MultipartEncoder # type: ignore[import-untyped]
12
+ from requests_toolbelt.multipart.encoder import MultipartEncoderMonitor # type: ignore[import-untyped]
13
+ from tqdm import tqdm
14
+ from bossalabs.errors import UploadClientError
15
+ from bossalabs.models import UploadAuthorization
16
+ import tomllib as toml
17
+ from bossalabs.constants import ALLOWED_VIDEO_EXTENSIONS, API_BASE_URL
18
+
19
+
20
+ def _load_package_info() -> tuple[str, str]:
21
+ """Load package name/version from metadata or pyproject fallback."""
22
+ distribution_name = "bossalabs"
23
+ try:
24
+ package_version = version(distribution_name)
25
+ package_metadata = metadata(distribution_name)
26
+ package_name = package_metadata.get("Name", distribution_name)
27
+ return package_name, package_version
28
+ except PackageNotFoundError:
29
+ pyproject_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
30
+ pyproject_data = toml.loads(pyproject_path.read_text(encoding="utf-8"))
31
+ project_data = pyproject_data["project"]
32
+ return str(project_data["name"]), str(project_data["version"])
33
+
34
+
35
+ PACKAGE_NAME, PACKAGE_VERSION = _load_package_info()
36
+
37
+
38
+ class Client:
39
+ """Upload videos to BossaLabs via presigned S3 POST.
40
+
41
+ Authorizes uploads through the BossaLabs API, then transfers the file with
42
+ ``requests``. Failures from :meth:`upload_file` are returned as result
43
+ dictionaries rather than raised exceptions.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ api_key: str,
49
+ model: str,
50
+ connect_timeout: int = 30,
51
+ upload_timeout: int = 14400,
52
+ ) -> None:
53
+ """Create a client for the given API key and model.
54
+
55
+ Args:
56
+ api_key: Sent in the ``x-api-key`` header on every request.
57
+ model: Model name passed to the upload authorization endpoint.
58
+ connect_timeout: Seconds to wait for a TCP connection.
59
+ upload_timeout: Seconds to wait for a response body after
60
+ connecting (authorization and file upload).
61
+
62
+ Raises:
63
+ ValueError: If ``api_key`` or ``model`` is empty.
64
+ """
65
+ if not api_key:
66
+ raise ValueError("API key is required")
67
+ if not model:
68
+ raise ValueError("Model is required")
69
+
70
+ self.api_base_url = API_BASE_URL
71
+ self.api_key = api_key
72
+ self.model = model
73
+ self.connect_timeout = connect_timeout
74
+ self.upload_timeout = upload_timeout
75
+ self.package_version = PACKAGE_VERSION
76
+ self.package_name = PACKAGE_NAME
77
+ self.hostname = socket.gethostname()
78
+ self.session = requests.Session()
79
+ self.session.headers.update(
80
+ {
81
+ "x-api-key": self.api_key,
82
+ "Accept": "application/json",
83
+ "Content-Type": "application/json",
84
+ "User-Agent": f"{self.package_name}/{self.package_version} ({self.hostname})",
85
+ }
86
+ )
87
+
88
+ def _authorize_upload(self) -> UploadAuthorization | UploadClientError:
89
+ url = f"{self.api_base_url}/upload-videos"
90
+ response = self.session.post(
91
+ url,
92
+ json={"model": self.model},
93
+ timeout=(self.connect_timeout, self.upload_timeout),
94
+ )
95
+
96
+ if response.status_code != 200:
97
+ return UploadClientError(
98
+ message="Upload authorization failed.",
99
+ status_code=response.status_code,
100
+ body=response.text,
101
+ )
102
+
103
+ payload = response.json()
104
+
105
+ return UploadAuthorization(
106
+ job_id=payload["job_id"],
107
+ model=payload["model"],
108
+ bucket=payload["bucket"],
109
+ key=payload["key"],
110
+ expires_in_seconds=int(payload["expires_in_seconds"]),
111
+ max_upload_size_bytes=int(payload["max_upload_size_bytes"]),
112
+ max_upload_size_gb=float(payload["max_upload_size_gb"]),
113
+ upload_url=payload["upload"]["url"],
114
+ upload_fields=dict(payload["upload"]["fields"]),
115
+ )
116
+
117
+ def upload_video(
118
+ self,
119
+ video_path: str | Path,
120
+ content_type: str = "video/mp4",
121
+ ) -> dict[str, Any]:
122
+ """Upload a local video file to S3 using boto3.
123
+
124
+ Args:
125
+ video_path: Local path to the video file that will be uploaded.
126
+ content_type: MIME type used for uploaded content.
127
+
128
+ Returns:
129
+ A result dictionary. On success, ``success`` is ``True`` and the
130
+ dict includes ``job_id``, ``model``, ``file_size_bytes``, and
131
+ ``status_code``. On failure, ``success`` is ``False`` and
132
+ ``error`` contains a human-readable message; other fields may be
133
+ ``None``.
134
+ """
135
+ path = Path(video_path)
136
+
137
+ if path.suffix.lower() not in ALLOWED_VIDEO_EXTENSIONS:
138
+ return {
139
+ "job_id": None,
140
+ "model": None,
141
+ "file_size_bytes": None,
142
+ "status_code": None,
143
+ "success": False,
144
+ "error": f"File extension not allowed: {path.suffix}",
145
+ }
146
+
147
+ if not path.exists():
148
+ return {
149
+ "job_id": None,
150
+ "model": None,
151
+ "file_size_bytes": None,
152
+ "status_code": None,
153
+ "success": False,
154
+ "error": f"File not found: {path}",
155
+ }
156
+
157
+ if not path.is_file():
158
+ return {
159
+ "job_id": None,
160
+ "model": None,
161
+ "file_size_bytes": None,
162
+ "status_code": None,
163
+ "success": False,
164
+ "error": f"Path is not a file: {path}",
165
+ }
166
+
167
+ auth = self._authorize_upload()
168
+ if isinstance(auth, UploadClientError):
169
+ return {
170
+ "job_id": None,
171
+ "model": None,
172
+ "file_size_bytes": None,
173
+ "status_code": None,
174
+ "success": False,
175
+ "error": auth.message,
176
+ }
177
+
178
+ file_size = path.stat().st_size
179
+ if file_size > auth.max_upload_size_bytes:
180
+ return {
181
+ "job_id": None,
182
+ "model": None,
183
+ "file_size_bytes": file_size,
184
+ "status_code": None,
185
+ "success": False,
186
+ "error": f"File too large. size={file_size} bytes, max={auth.max_upload_size_bytes} bytes",
187
+ }
188
+
189
+ final_content_type = content_type or "application/octet-stream"
190
+
191
+ fields = dict(auth.upload_fields)
192
+
193
+ if "Content-Type" not in fields:
194
+ fields["Content-Type"] = final_content_type
195
+
196
+ try:
197
+ with path.open("rb") as f:
198
+ multipart_fields = {
199
+ **fields,
200
+ "file": (path.name, f, final_content_type),
201
+ }
202
+
203
+ encoder = MultipartEncoder(fields=multipart_fields)
204
+
205
+ with tqdm(
206
+ total=encoder.len,
207
+ unit="B",
208
+ unit_scale=True,
209
+ unit_divisor=1024,
210
+ desc=f"Uploading {path.name}",
211
+ ) as progress_bar:
212
+ last_bytes_read = 0
213
+
214
+ def callback(monitor: MultipartEncoderMonitor) -> None:
215
+ nonlocal last_bytes_read
216
+ progress_bar.update(monitor.bytes_read - last_bytes_read)
217
+ last_bytes_read = monitor.bytes_read
218
+
219
+ monitor = MultipartEncoderMonitor(encoder, callback)
220
+
221
+ response = requests.post(
222
+ auth.upload_url,
223
+ data=monitor,
224
+ headers={"Content-Type": monitor.content_type},
225
+ timeout=(self.connect_timeout, self.upload_timeout),
226
+ )
227
+
228
+ if response.status_code not in (200, 201, 204):
229
+ return {
230
+ "job_id": None,
231
+ "model": None,
232
+ "file_size_bytes": file_size,
233
+ "status_code": response.status_code,
234
+ "success": False,
235
+ "error": response.text,
236
+ }
237
+
238
+ except requests.RequestException as error:
239
+ return {
240
+ "job_id": None,
241
+ "model": None,
242
+ "file_size_bytes": file_size,
243
+ "status_code": None,
244
+ "success": False,
245
+ "error": str(error),
246
+ }
247
+
248
+ return {
249
+ "job_id": auth.job_id,
250
+ "model": auth.model,
251
+ "file_size_bytes": file_size,
252
+ "status_code": response.status_code,
253
+ "success": True,
254
+ }
@@ -0,0 +1,18 @@
1
+ """Base URL for the BossaLabs API."""
2
+
3
+ import os
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ ENV = os.getenv("ENV", "DEV")
9
+
10
+ if ENV == "DEV":
11
+ API_BASE_URL = "https://3q6tjso3ce.execute-api.us-east-1.amazonaws.com/DEV"
12
+ elif ENV == "PROD":
13
+ API_BASE_URL = "https://3q6tjso3ce.execute-api.us-east-1.amazonaws.com/"
14
+ else:
15
+ raise ValueError(f"Invalid environment: {ENV}")
16
+
17
+
18
+ ALLOWED_VIDEO_EXTENSIONS = [".mp4", ".mov", ".avi", ".mkv", ".webm"]
@@ -0,0 +1,24 @@
1
+ """Error types used by the BossaLabs client."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class UploadClientError(Exception):
7
+ """Error returned when upload authorization fails.
8
+
9
+ :class:`~bossalabs.client.Client` returns this from internal
10
+ authorization calls. :meth:`~bossalabs.client.Client.upload_file`
11
+ converts it into a failure result dictionary instead of raising.
12
+ """
13
+
14
+ def __init__(
15
+ self,
16
+ message: str,
17
+ *,
18
+ status_code: int | None = None,
19
+ body: str | None = None,
20
+ ) -> None:
21
+ self.message = message
22
+ self.status_code = status_code
23
+ self.body = body
24
+ super().__init__(message)
@@ -0,0 +1,33 @@
1
+ """Data models for BossaLabs API responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass
9
+ class AwsCredentials:
10
+ """Temporary AWS credentials for direct S3 access.
11
+
12
+ Not used by the current upload flow, which relies on presigned POST URLs.
13
+ """
14
+
15
+ access_key_id: str
16
+ secret_access_key: str
17
+ session_token: str | None = None
18
+ region: str | None = None
19
+
20
+
21
+ @dataclass
22
+ class UploadAuthorization:
23
+ """Presigned upload details returned by the BossaLabs API."""
24
+
25
+ job_id: str
26
+ model: str
27
+ bucket: str
28
+ key: str
29
+ expires_in_seconds: int
30
+ max_upload_size_bytes: int
31
+ max_upload_size_gb: float
32
+ upload_url: str
33
+ upload_fields: dict[str, str]
File without changes
@@ -0,0 +1,32 @@
1
+ """Fire-based script entry point for uploading a single video file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from bossalabs.client import Client
6
+
7
+
8
+ def main(
9
+ api_key: str,
10
+ upload_video_path: str,
11
+ model: str,
12
+ content_type: str = "video/mp4",
13
+ ) -> dict[str, object]:
14
+ """Upload a video file via :class:`~bossalabs.client.Client`.
15
+
16
+ Args:
17
+ api_key: API key used for authorization requests.
18
+ upload_video_path: Local path of the file to upload.
19
+ model: Model name to use for processing the video.
20
+ content_type: MIME type sent with the uploaded file.
21
+
22
+ Returns:
23
+ Upload result payload returned by `Client.upload_video`.
24
+ """
25
+ client = Client(api_key=api_key, model=model)
26
+ return client.upload_video(upload_video_path, content_type=content_type)
27
+
28
+
29
+ if __name__ == "__main__":
30
+ import fire
31
+
32
+ fire.Fire(main)
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: bossalabs
3
+ Version: 0.1.0
4
+ Requires-Python: >=3.10
5
+ Description-Content-Type: text/markdown
6
+ Requires-Dist: boto3
7
+ Requires-Dist: requests
8
+ Requires-Dist: fire
9
+ Requires-Dist: tomli; python_version < "3.11"
10
+ Requires-Dist: requests-toolbelt>=1.0.0
11
+ Requires-Dist: mypy
12
+ Requires-Dist: ruff
13
+ Requires-Dist: pytest
14
+ Requires-Dist: types-requests
15
+ Requires-Dist: tqdm>=4.67.3
16
+ Requires-Dist: python-dotenv>=1.2.2
17
+
18
+ [![python](https://img.shields.io/badge/python-3.13-blue?logo=python)](https://www.python.org/)
19
+ [![CI](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/computervisionsports/bossa-client/actions/workflows/ci.yml)
20
+
21
+ # BossaLabs Python Client
22
+
23
+ Small Python client for requesting upload authorization and uploading
24
+ video files using a presigned S3 POST.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ uv venv .venv
30
+ source .venv/bin/activate
31
+ uv pip install -e .
32
+ ```
33
+
34
+ Or with plain pip:
35
+
36
+ ```bash
37
+ pip install -e .
38
+ ```
39
+
40
+ Or from PyPI:
41
+
42
+ ```bash
43
+ pip install bossalabs
44
+ ```
45
+
46
+ ## Python usage
47
+
48
+ ```python
49
+ from bossalabs import Client
50
+
51
+ client = Client(
52
+ api_key="YOUR_API_KEY",
53
+ model="MODEL_NAME"
54
+ )
55
+
56
+ result = client.upload_video("/path/to/video.mp4")
57
+ print(result)
58
+ ```
59
+
60
+ ## CLI usage
61
+
62
+ After installation, you can run:
63
+
64
+ ```bash
65
+ bossalabs process-video "/path/to/video.mp4" "MODEL_NAME" --api_key "YOUR_API_KEY"
66
+ ```
67
+
68
+ ## Documentation
69
+
70
+ Full documentation is built with [MkDocs](https://www.mkdocs.org/) and
71
+ [Material for MkDocs](https://squidfunk.github.io/mkdocs-material/).
72
+
73
+ ### Install documentation dependencies
74
+
75
+ ```bash
76
+ uv sync --group docs
77
+ ```
78
+
79
+ ### Serve docs locally
80
+
81
+ Preview the site with live reload:
82
+
83
+ ```bash
84
+ uv run mkdocs serve
85
+ ```
86
+
87
+ Open [http://127.0.0.1:8000](http://127.0.0.1:8000) in your browser.
88
+
89
+ ### Build static HTML
90
+
91
+ Generate the static site into the `site/` directory:
92
+
93
+ ```bash
94
+ uv run mkdocs build
95
+ ```
96
+
97
+ The HTML output is written to `site/` (gitignored). Deploy that folder to any
98
+ static hosting provider.
99
+
100
+ ### What is documented
101
+
102
+ The docs cover the public user-facing API:
103
+
104
+ - `bossalabs.Client` — authorize and upload video files
105
+ - `bossalabs.UploadClientError` — authorization failure type
106
+ - `bossalabs.API_BASE_URL` — default API endpoint constant
107
+ - `bossalabs` CLI — `process-video` command
108
+
109
+ Internal modules (`bossalabs.models`, scripts, and private helpers) are
110
+ excluded from the API reference.
@@ -0,0 +1,18 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/bossalabs/__init__.py
4
+ src/bossalabs/client.py
5
+ src/bossalabs/constants.py
6
+ src/bossalabs/errors.py
7
+ src/bossalabs/models.py
8
+ src/bossalabs.egg-info/PKG-INFO
9
+ src/bossalabs.egg-info/SOURCES.txt
10
+ src/bossalabs.egg-info/dependency_links.txt
11
+ src/bossalabs.egg-info/entry_points.txt
12
+ src/bossalabs.egg-info/requires.txt
13
+ src/bossalabs.egg-info/top_level.txt
14
+ src/bossalabs/cli/__init__.py
15
+ src/bossalabs/cli/main.py
16
+ src/bossalabs/scripts/__init__.py
17
+ src/bossalabs/scripts/upload_video.py
18
+ tests/test_client.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bossalabs = bossalabs.cli.main:main
@@ -0,0 +1,13 @@
1
+ boto3
2
+ requests
3
+ fire
4
+ requests-toolbelt>=1.0.0
5
+ mypy
6
+ ruff
7
+ pytest
8
+ types-requests
9
+ tqdm>=4.67.3
10
+ python-dotenv>=1.2.2
11
+
12
+ [:python_version < "3.11"]
13
+ tomli
@@ -0,0 +1 @@
1
+ bossalabs
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from bossalabs import client
4
+
5
+
6
+ class DummySession:
7
+ """Minimal requests.Session test double."""
8
+
9
+ def __init__(self) -> None:
10
+ self.headers: dict[str, str] = {}
11
+
12
+
13
+ def test_create_client_object_with_mocked_session(monkeypatch) -> None:
14
+ """Creates a client object without real network dependencies."""
15
+
16
+ monkeypatch.setattr("bossalabs.client.requests.Session", DummySession)
17
+
18
+ c = client.Client(api_key="test-api-key", model="my-dummy-model")
19
+
20
+ assert c.api_key == "test-api-key"
21
+ assert c.model == "my-dummy-model"
22
+ assert c.session.headers["x-api-key"] == "test-api-key"
23
+ assert c.session.headers["Accept"] == "application/json"