Skip to content

Commit 12a2884

Browse files
authored
Merge branch 'main' into sb/add-batch-methods
2 parents e123576 + 24ca570 commit 12a2884

25 files changed

+482
-53
lines changed

.github/workflows/publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,35 @@ jobs:
3030
PYPI_TEST_PASSWORD: ${{ secrets.PYPI_TEST_PASSWORD }}
3131
run: |
3232
make publish -e PYPI_USERNAME=$PYPI_USERNAME -e PYPI_PASSWORD=$PYPI_PASSWORD -e PYPI_TEST_PASSWORD=$PYPI_TEST_PASSWORD
33+
34+
deploy-docs:
35+
needs: build
36+
runs-on: ubuntu-latest
37+
permissions:
38+
contents: write
39+
steps:
40+
- name: 🛎️ Checkout
41+
uses: actions/checkout@v4
42+
with:
43+
fetch-depth: 0
44+
45+
- name: 🐍 Set up Python
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: '3.8'
49+
50+
- name: 📚 Install MkDocs and dependencies
51+
run: |
52+
python -m pip install --upgrade pip
53+
pip install mkdocs-material mkdocstrings mkdocstrings[python]
54+
pip install ".[dev]"
55+
56+
- name: 🏗️ Build documentation
57+
run: |
58+
mkdocs build
59+
60+
- name: 🚀 Deploy to GitHub Pages
61+
uses: peaceiris/actions-gh-pages@v3
62+
with:
63+
github_token: ${{ secrets.GITHUB_TOKEN }}
64+
publish_dir: ./site

.github/workflows/test.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ on:
88

99
jobs:
1010
build:
11-
runs-on: ubuntu-latest
1211
strategy:
1312
matrix:
14-
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
13+
os: ["ubuntu-latest", "windows-latest"]
14+
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
15+
runs-on: ${{ matrix.os }}
16+
env:
17+
PYTHONUTF8: 1
1518

1619
steps:
1720
- name: 🛎️ Checkout

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,3 +154,4 @@ tests/manual/data
154154
README.roboflow.txt
155155
*.zip
156156
.DS_Store
157+
.claude

CLAUDE.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Development Commands
6+
7+
### Running Tests
8+
```bash
9+
python -m unittest
10+
```
11+
12+
### Linting and Code Quality
13+
```bash
14+
# Format code with ruff
15+
make style
16+
17+
# Check code quality (includes ruff and mypy)
18+
make check_code_quality
19+
20+
# Individual commands
21+
ruff format roboflow
22+
ruff check roboflow --fix
23+
mypy roboflow
24+
```
25+
26+
### Building Documentation
27+
```bash
28+
# Install documentation dependencies
29+
python -m pip install mkdocs mkdocs-material mkdocstrings mkdocstrings[python]
30+
31+
# Serve documentation locally
32+
mkdocs serve
33+
```
34+
35+
### Installing Development Environment
36+
```bash
37+
# Create virtual environment
38+
python3 -m venv env
39+
source env/bin/activate
40+
41+
# Install in editable mode with dev dependencies
42+
pip install -e ".[dev]"
43+
44+
# Install pre-commit hooks
45+
pip install pre-commit
46+
pre-commit install
47+
```
48+
49+
## Architecture Overview
50+
51+
The Roboflow Python SDK follows a hierarchical object model that mirrors the Roboflow platform structure:
52+
53+
### Core Components
54+
55+
1. **Roboflow** (`roboflow/__init__.py`) - Entry point and authentication
56+
- Handles API key management and workspace initialization
57+
- Provides `login()` for CLI authentication
58+
- Creates workspace connections
59+
60+
2. **Workspace** (`roboflow/core/workspace.py`) - Manages Roboflow workspaces
61+
- Lists and accesses projects
62+
- Handles dataset uploads and model deployments
63+
- Manages workspace-level operations
64+
65+
3. **Project** (`roboflow/core/project.py`) - Represents a computer vision project
66+
- Manages project metadata and versions
67+
- Handles image/annotation uploads
68+
- Supports different project types (object-detection, classification, etc.)
69+
70+
4. **Version** (`roboflow/core/version.py`) - Dataset version management
71+
- Downloads datasets in various formats
72+
- Deploys models
73+
- Provides access to trained models for inference
74+
75+
5. **Model Classes** (`roboflow/models/`) - Type-specific inference models
76+
- `ObjectDetectionModel` - Bounding box predictions
77+
- `ClassificationModel` - Image classification
78+
- `InstanceSegmentationModel` - Pixel-level segmentation
79+
- `SemanticSegmentationModel` - Class-based segmentation
80+
- `KeypointDetectionModel` - Keypoint predictions
81+
82+
### API Adapters
83+
84+
- **rfapi** (`roboflow/adapters/rfapi.py`) - Low-level API communication
85+
- **deploymentapi** (`roboflow/adapters/deploymentapi.py`) - Model deployment operations
86+
87+
### CLI Interface
88+
89+
The `roboflow` command line tool (`roboflow/roboflowpy.py`) provides:
90+
- Authentication: `roboflow login`
91+
- Dataset operations: `roboflow download`, `roboflow upload`, `roboflow import`
92+
- Inference: `roboflow infer`
93+
- Project/workspace management: `roboflow project`, `roboflow workspace`
94+
95+
### Key Design Patterns
96+
97+
1. **Hierarchical Access**: Always access objects through their parent (Workspace → Project → Version → Model)
98+
2. **API Key Flow**: API key is passed down through the object hierarchy
99+
3. **Format Flexibility**: Supports multiple dataset formats (YOLO, COCO, Pascal VOC, etc.)
100+
4. **Batch Operations**: Upload and download operations support concurrent processing
101+
102+
## Project Configuration
103+
104+
- **Python Version**: 3.8+
105+
- **Main Dependencies**: See `requirements.txt`
106+
- **Entry Point**: `roboflow=roboflow.roboflowpy:main`
107+
- **Code Style**: Enforced by ruff with Google docstring convention
108+
- **Type Checking**: mypy configured for Python 3.8
109+
110+
## Important Notes
111+
112+
- API keys are stored in `~/.config/roboflow/config.json` (Unix) or `~/roboflow/config.json` (Windows)
113+
- The SDK supports both hosted inference (Roboflow platform) and local inference (via Roboflow Inference)
114+
- Pre-commit hooks automatically run formatting and linting checks
115+
- Test files intentionally excluded from linting: `tests/manual/debugme.py`

Dockerfile.dev

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
FROM python:3.8
2-
RUN apt-get update && apt-get install -y make libgl1-mesa-glx && rm -rf /var/lib/apt/lists/*
1+
FROM python:3.10
2+
RUN apt-get update && apt-get install -y make curl libgl1-mesa-glx && rm -rf /var/lib/apt/lists/*
3+
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
4+
ENV PATH="/root/.local/bin:${PATH}"
5+
36
WORKDIR /roboflow-python
47
COPY .devcontainer/bashrc_ext /root/bashrc_ext
58
RUN echo "source /root/bashrc_ext" >> ~/.bashrc
6-
COPY ./setup.py ./pyproject.toml ./README.md ./requirements.txt ./
9+
10+
COPY ./requirements.txt ./
11+
RUN uv pip install --system -r requirements.txt
12+
13+
COPY ./setup.py ./pyproject.toml ./README.md ./
714
COPY roboflow/__init__.py ./roboflow/__init__.py
8-
RUN pip install -e ".[dev]"
15+
RUN uv pip install --system -e ".[dev]"
16+
917
COPY . .

roboflow/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from roboflow.models import CLIPModel, GazeModel # noqa: F401
1616
from roboflow.util.general import write_line
1717

18-
__version__ = "1.1.58"
18+
__version__ = "1.1.65"
1919

2020

2121
def check_key(api_key, model, notebook, num_retries=0):

roboflow/adapters/deploymentapi.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ class DeploymentApiError(Exception):
99
pass
1010

1111

12-
def add_deployment(api_key, machine_type, duration, delete_on_expiration, deployment_name, inference_version):
12+
def add_deployment(
13+
api_key, creator_email, machine_type, duration, delete_on_expiration, deployment_name, inference_version
14+
):
1315
url = f"{DEDICATED_DEPLOYMENT_URL}/add"
1416
params = {
1517
"api_key": api_key,
18+
"creator_email": creator_email,
1619
# "security_level": security_level,
1720
"duration": duration,
1821
"delete_on_expiration": delete_on_expiration,
@@ -69,6 +72,22 @@ def get_deployment_usage(api_key, deployment_name, from_timestamp, to_timestamp)
6972
return response.status_code, response.json()
7073

7174

75+
def pause_deployment(api_key, deployment_name):
76+
url = f"{DEDICATED_DEPLOYMENT_URL}/pause"
77+
response = requests.post(url, json={"api_key": api_key, "deployment_name": deployment_name})
78+
if response.status_code != 200:
79+
return response.status_code, response.text
80+
return response.status_code, response.json()
81+
82+
83+
def resume_deployment(api_key, deployment_name):
84+
url = f"{DEDICATED_DEPLOYMENT_URL}/resume"
85+
response = requests.post(url, json={"api_key": api_key, "deployment_name": deployment_name})
86+
if response.status_code != 200:
87+
return response.status_code, response.text
88+
return response.status_code, response.json()
89+
90+
7291
def delete_deployment(api_key, deployment_name):
7392
url = f"{DEDICATED_DEPLOYMENT_URL}/delete"
7493
response = requests.post(url, json={"api_key": api_key, "deployment_name": deployment_name})

roboflow/core/workspace.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,10 +301,11 @@ def upload_dataset(
301301
""" # noqa: E501 // docs
302302
if dataset_format != "NOT_USED":
303303
print("Warning: parameter 'dataset_format' is deprecated and will be removed in a future release")
304-
parsed_dataset = folderparser.parsefolder(dataset_path)
305304
project, created = self._get_or_create_project(
306305
project_id=project_name, license=project_license, type=project_type
307306
)
307+
is_classification = project.type == "classification"
308+
parsed_dataset = folderparser.parsefolder(dataset_path, is_classification=is_classification)
308309
if created:
309310
print(f"Created project {project.id}")
310311
else:
@@ -361,15 +362,19 @@ def _save_annotation(image_id, imagedesc):
361362

362363
annotationdesc = imagedesc.get("annotationfile")
363364
if isinstance(annotationdesc, dict):
364-
if annotationdesc.get("rawText"):
365+
if annotationdesc.get("type") == "classification_folder":
366+
annotation_path = annotationdesc.get("classification_label")
367+
elif annotationdesc.get("rawText"):
365368
annotation_path = annotationdesc
366-
else:
369+
elif annotationdesc.get("file"):
367370
annotation_path = f"{location}{annotationdesc['file']}"
368-
labelmap = annotationdesc.get("labelmap")
371+
labelmap = annotationdesc.get("labelmap")
369372

370373
if isinstance(labelmap, str):
371374
labelmap = load_labelmap(labelmap)
372-
else:
375+
376+
# If annotation_path is still None at this point, then no annotation will be saved.
377+
if annotation_path is None:
373378
return None, None
374379

375380
annotation, upload_time, _retry_attempts = project.save_annotation(
@@ -619,7 +624,13 @@ def _upload_zip(
619624
try:
620625
res.raise_for_status()
621626
except Exception as e:
622-
print(f"An error occured when getting the model deployment URL: {e}")
627+
error_message = str(e)
628+
status_code = str(res.status_code)
629+
630+
print("\n\033[91m❌ ERROR\033[0m: Failed to get model deployment URL")
631+
print("\033[93mDetails\033[0m:", error_message)
632+
print("\033[93mStatus\033[0m:", status_code)
633+
print(f"\033[93mResponse\033[0m:\n{res.text}\n")
623634
return
624635

625636
# Upload the model to the signed URL

roboflow/deployment.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ def add_deployment_parser(subparsers):
6161
deployment_usage_deployment_parser = deployment_subparsers.add_parser(
6262
"usage_deployment", help="get usage of a specific dedicated deployments"
6363
)
64+
deployment_pause_parser = deployment_subparsers.add_parser("pause", help="pause a dedicated deployment")
65+
deployment_resume_parser = deployment_subparsers.add_parser("resume", help="resume a dedicated deployment")
6466
deployment_delete_parser = deployment_subparsers.add_parser("delete", help="delete a dedicated deployment")
6567
deployment_log_parser = deployment_subparsers.add_parser("log", help="show log info for a dedicated deployment")
6668

@@ -77,7 +79,13 @@ def add_deployment_parser(subparsers):
7779
# "-s", "--security_level", help="security level (protected)", default="protected"
7880
# )
7981
deployment_add_parser.add_argument(
80-
"-m", "--machine_type", help="machine type, run `roboflow deployment machine_type` to see available options"
82+
"-m",
83+
"--machine_type",
84+
help="machine type, run `roboflow deployment machine_type` to see available options",
85+
required=True,
86+
)
87+
deployment_add_parser.add_argument(
88+
"-e", "--creator_email", help="your email address (must be added to the workspace)", required=True
8189
)
8290
deployment_add_parser.add_argument(
8391
"-t",
@@ -87,7 +95,7 @@ def add_deployment_parser(subparsers):
8795
default=3,
8896
)
8997
deployment_add_parser.add_argument(
90-
"-e", "--no_delete_on_expiration", help="keep when expired (default: False)", action="store_true"
98+
"-nodel", "--no_delete_on_expiration", help="keep when expired (default: False)", action="store_true"
9199
)
92100
deployment_add_parser.add_argument(
93101
"-v",
@@ -128,6 +136,14 @@ def add_deployment_parser(subparsers):
128136
"-t", "--to_timestamp", help="end time stamp in ISO8601 format (YYYY-MM-DD HH:MM:SS)", default=None
129137
)
130138

139+
deployment_pause_parser.set_defaults(func=pause_deployment)
140+
deployment_pause_parser.add_argument("-a", "--api_key", help="api key")
141+
deployment_pause_parser.add_argument("deployment_name", help="deployment name")
142+
143+
deployment_resume_parser.set_defaults(func=resume_deployment)
144+
deployment_resume_parser.add_argument("-a", "--api_key", help="api key")
145+
deployment_resume_parser.add_argument("deployment_name", help="deployment name")
146+
131147
deployment_delete_parser.set_defaults(func=delete_deployment)
132148
deployment_delete_parser.add_argument("-a", "--api_key", help="api key")
133149
deployment_delete_parser.add_argument("deployment_name", help="deployment name")
@@ -163,6 +179,7 @@ def add_deployment(args):
163179
exit(1)
164180
status_code, msg = deploymentapi.add_deployment(
165181
api_key,
182+
args.creator_email,
166183
# args.security_level,
167184
args.machine_type,
168185
args.duration,
@@ -241,6 +258,30 @@ def get_deployment_usage(args):
241258
print(json.dumps(msg, indent=2))
242259

243260

261+
def pause_deployment(args):
262+
api_key = args.api_key or load_roboflow_api_key(None)
263+
if api_key is None:
264+
print("Please provide an api key")
265+
exit(1)
266+
status_code, msg = deploymentapi.pause_deployment(api_key, args.deployment_name)
267+
if status_code != 200:
268+
print(f"{status_code}: {msg}")
269+
exit(status_code)
270+
print(json.dumps(msg, indent=2))
271+
272+
273+
def resume_deployment(args):
274+
api_key = args.api_key or load_roboflow_api_key(None)
275+
if api_key is None:
276+
print("Please provide an api key")
277+
exit(1)
278+
status_code, msg = deploymentapi.resume_deployment(api_key, args.deployment_name)
279+
if status_code != 200:
280+
print(f"{status_code}: {msg}")
281+
exit(status_code)
282+
print(json.dumps(msg, indent=2))
283+
284+
244285
def delete_deployment(args):
245286
api_key = args.api_key or load_roboflow_api_key(None)
246287
if api_key is None:

0 commit comments

Comments
 (0)