Skip to content

Commit 01d713f

Browse files
authored
Merge pull request #356 from roboflow/lean/deploy-versionless-model
Versionless model deploy/upload
2 parents b5ed55b + 5bd9b37 commit 01d713f

File tree

3 files changed

+95
-8
lines changed

3 files changed

+95
-8
lines changed

roboflow/__init__.py

+1-1
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.52"
18+
__version__ = "1.1.53"
1919

2020

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

roboflow/core/workspace.py

+71-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import concurrent.futures
24
import glob
35
import json
@@ -10,11 +12,12 @@
1012

1113
from roboflow.adapters import rfapi
1214
from roboflow.adapters.rfapi import AnnotationSaveError, ImageUploadError, RoboflowError
13-
from roboflow.config import API_URL, CLIP_FEATURIZE_URL, DEMO_KEYS
15+
from roboflow.config import API_URL, APP_URL, CLIP_FEATURIZE_URL, DEMO_KEYS
1416
from roboflow.core.project import Project
1517
from roboflow.util import folderparser
1618
from roboflow.util.active_learning_utils import check_box_size, clip_encode, count_comparisons
1719
from roboflow.util.image_utils import load_labelmap
20+
from roboflow.util.model_processor import process
1821
from roboflow.util.two_stage_utils import ocr_infer
1922

2023

@@ -566,6 +569,73 @@ def active_learning(
566569
prediction_results if type(raw_data_location) is not np.ndarray else prediction_results[-1]["predictions"]
567570
)
568571

572+
def deploy_model(
573+
self,
574+
model_type: str,
575+
model_path: str,
576+
project_ids: list[str],
577+
model_name: str,
578+
filename: str = "weights/best.pt",
579+
):
580+
"""Uploads provided weights file to Roboflow.
581+
Args:
582+
model_type (str): The type of the model to be deployed.
583+
model_path (str): File path to the model weights to be uploaded.
584+
project_ids (list[str]): List of project IDs to deploy the model to.
585+
filename (str, optional): The name of the weights file. Defaults to "weights/best.pt".
586+
"""
587+
588+
if not project_ids:
589+
raise ValueError("At least one project ID must be provided")
590+
591+
# Validate if provided project URLs belong to user's projects
592+
user_projects = set(project.split("/")[-1] for project in self.projects())
593+
for project_id in project_ids:
594+
if project_id not in user_projects:
595+
raise ValueError(f"Project {project_id} is not accessible in this workspace")
596+
597+
zip_file_name = process(model_type, model_path, filename)
598+
599+
if zip_file_name is None:
600+
raise RuntimeError("Failed to process model")
601+
602+
self._upload_zip(model_type, model_path, project_ids, model_name, zip_file_name)
603+
604+
def _upload_zip(
605+
self,
606+
model_type: str,
607+
model_path: str,
608+
project_ids: list[str],
609+
model_name: str,
610+
model_file_name: str,
611+
):
612+
# This endpoint returns a signed URL to upload the model
613+
res = requests.post(
614+
f"{API_URL}/{self.url}/models/prepareUpload?api_key={self.__api_key}&modelType={model_type}&modelName={model_name}&projectIds={','.join(project_ids)}&nocache=true"
615+
)
616+
try:
617+
res.raise_for_status()
618+
except Exception as e:
619+
print(f"An error occured when getting the model deployment URL: {e}")
620+
return
621+
622+
# Upload the model to the signed URL
623+
res = requests.put(
624+
res.json()["url"],
625+
data=open(os.path.join(model_path, model_file_name), "rb"),
626+
)
627+
try:
628+
res.raise_for_status()
629+
630+
for project_id in project_ids:
631+
print(
632+
f"View the status of your deployment for project {project_id} at:"
633+
f" {APP_URL}/{self.url}/{project_id}/models"
634+
)
635+
636+
except Exception as e:
637+
print(f"An error occured when uploading the model: {e}")
638+
569639
def __str__(self):
570640
projects = self.projects()
571641
json_value = {"name": self.name, "url": self.url, "projects": projects}

roboflow/roboflowpy.py

+23-6
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,20 @@ def upload_model(args):
7979
rf = roboflow.Roboflow(args.api_key)
8080
workspace = rf.workspace(args.workspace)
8181

82-
# Deploy to specific version
83-
project = workspace.project(args.project)
84-
version = project.version(args.version_number)
85-
version.deploy(str(args.model_type), str(args.model_path), str(args.filename))
82+
if args.version_number is not None:
83+
# Deploy to specific version
84+
project = workspace.project(args.project)
85+
version = project.version(args.version_number)
86+
version.deploy(str(args.model_type), str(args.model_path), str(args.filename))
87+
else:
88+
# Deploy to multiple projects
89+
workspace.deploy_model(
90+
model_type=str(args.model_type),
91+
model_path=str(args.model_path),
92+
project_ids=args.project,
93+
model_name=str(args.model_name),
94+
filename=str(args.filename),
95+
)
8696

8797

8898
def list_projects(args):
@@ -479,13 +489,15 @@ def _add_upload_model_parser(subparsers):
479489
upload_model_parser.add_argument(
480490
"-p",
481491
dest="project",
482-
help="project_id to upload the model into",
492+
action="append", # Allow multiple projects
493+
help="project_id to upload the model into (can be specified multiple times)",
483494
)
484495
upload_model_parser.add_argument(
485496
"-v",
486497
dest="version_number",
487498
type=int,
488-
help="version number to upload the model to",
499+
help="version number to upload the model to (optional)",
500+
default=None,
489501
)
490502
upload_model_parser.add_argument(
491503
"-t",
@@ -503,6 +515,11 @@ def _add_upload_model_parser(subparsers):
503515
default="weights/best.pt",
504516
help="name of the model file",
505517
)
518+
upload_model_parser.add_argument(
519+
"-n",
520+
dest="model_name",
521+
help="name of the model",
522+
)
506523
upload_model_parser.set_defaults(func=upload_model)
507524

508525

0 commit comments

Comments
 (0)