From d7e4ae43ac492155e3c1c99f94137eff58fe4d6a Mon Sep 17 00:00:00 2001 From: manylon Date: Thu, 30 Jul 2026 18:25:50 +0300 Subject: [PATCH] feat: add project type option to create and patch projects - add `--project-type` option to `create-project` and `patch-project` CLI commands - add `project_type` parameter to `Client.create_project` and `Client.patch_project` in the SDK - introduce a new `ProjectType` enum with regular, shared_datasets, and template values --- qfieldcloud_sdk/cli.py | 58 +++++++++++++++++++++++++++++++++++++--- qfieldcloud_sdk/sdk.py | 36 ++++++++++++++++++++----- qfieldcloud_sdk/utils.py | 3 ++- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/qfieldcloud_sdk/cli.py b/qfieldcloud_sdk/cli.py index 606daa7..145ee43 100755 --- a/qfieldcloud_sdk/cli.py +++ b/qfieldcloud_sdk/cli.py @@ -349,16 +349,46 @@ def list_files(ctx: Context, project_id): "owner", help="Owner of the project. If omitted, the current user is the owner.", ) -@click.option("--description", "description", help="Description of the project.") +@click.option( + "--description", + "description", + default="", + help="Description of the project.", +) @click.option( "--is-public/--is-private", "is_public", help="Mark the project as public." ) +@click.option( + "--project-type", + "project_type", + type=click.Choice( + [ + sdk.ProjectType.REGULAR.value, + sdk.ProjectType.TEMPLATE.value, + ] + ), + help="Type of the project. Defaults to `regular`.", +) @click.pass_context -def create_project(ctx: Context, name, owner, description, is_public): +def create_project( + ctx: Context, + name: str, + owner: Optional[str], + description: str, + is_public: bool, + project_type: Optional[str], +): """Creates a new empty QFieldCloud project.""" + if project_type is not None: + project_type = sdk.ProjectType(project_type) + project = ctx.obj["client"].create_project( - name, owner, description=description, is_public=is_public + name, + owner, + description=description, + is_public=is_public, + project_type=project_type, ) if ctx.obj["format_json"]: @@ -526,6 +556,17 @@ def download_files( is_flag=True, help="Whether the project shall be public", ) +@click.option( + "--project-type", + "project_type", + type=click.Choice( + [ + sdk.ProjectType.REGULAR.value, + sdk.ProjectType.TEMPLATE.value, + ] + ), + help="New project type", +) @click.pass_context def patch_project( ctx: Context, @@ -534,11 +575,20 @@ def patch_project( description: Optional[str] = None, owner: Optional[str] = None, is_public: Optional[bool] = None, + project_type: Optional[str] = None, ) -> None: """Patch the project with new data. Pass only the parameters that shall be changed.""" + if project_type is not None: + project_type = sdk.ProjectType(project_type) + project = ctx.obj["client"].patch_project( - project_id, name=name, owner=owner, description=description, is_public=is_public + project_id, + name=name, + owner=owner, + description=description, + is_public=is_public, + project_type=project_type, ) if ctx.obj["format_json"]: diff --git a/qfieldcloud_sdk/sdk.py b/qfieldcloud_sdk/sdk.py index fa5de55..64ff681 100644 --- a/qfieldcloud_sdk/sdk.py +++ b/qfieldcloud_sdk/sdk.py @@ -81,6 +81,18 @@ class JobTypes(str, Enum): CREATE_PROJECT = "create_project" +class ProjectType(str, Enum): + """Represents the type of a project. + + Only `REGULAR` and `TEMPLATE` can be set by clients when creating or updating a project. + `SHARED_DATASETS` is managed by the server and is exposed here only so it can be recognized on read. + """ + + REGULAR = "regular" + SHARED_DATASETS = "shared_datasets" + TEMPLATE = "template" + + class ProjectCollaboratorRole(str, Enum): """Defines roles for project collaborators. @@ -554,6 +566,7 @@ def create_project( owner: Optional[str] = None, description: str = "", is_public: bool = False, + project_type: Optional[ProjectType] = None, ) -> Dict[str, Any]: """Create a new project. @@ -562,6 +575,7 @@ def create_project( owner: The owner of the project. When None, the project will be owned by the currently logged-in user. Defaults to None. description: A description of the project. Defaults to an empty string. is_public: Whether the project should be public. Defaults to False. + project_type: The type of the project. Only `ProjectType.REGULAR` and `ProjectType.TEMPLATE` are accepted by the server. When omitted the server defaults to `ProjectType.REGULAR`. Returns: A dictionary containing the details of the created project. @@ -573,15 +587,20 @@ def create_project( ) ``` """ + data = { + "name": name, + "owner": owner, + "description": description, + "is_public": int(is_public), + } + + if project_type is not None: + data["project_type"] = project_type.value + resp = self._request( "POST", "projects", - data={ - "name": name, - "owner": owner, - "description": description, - "is_public": int(is_public), - }, + data=data, ) return resp.json() @@ -611,6 +630,7 @@ def patch_project( owner: Optional[str] = None, description: Optional[str] = None, is_public: Optional[bool] = None, + project_type: Optional[ProjectType] = None, ) -> Dict[str, Any]: """Update a project. @@ -620,6 +640,7 @@ def patch_project( owner (str | None, optional): if passed, the new owner. Defaults to None. description (str, optional): if passed, the new description. Defaults to None. is_public (bool, optional): if passed, the new public setting. Defaults to None. + project_type (ProjectType | None, optional): if passed, the new project type. Returns: Dict[str, Any]: the updated project @@ -640,6 +661,9 @@ def patch_project( if owner: project_data["owner"] = owner + if project_type is not None: + project_data["project_type"] = project_type.value + if is_public: project_data["is_public"] = is_public diff --git a/qfieldcloud_sdk/utils.py b/qfieldcloud_sdk/utils.py index 9214ec3..b4c17be 100644 --- a/qfieldcloud_sdk/utils.py +++ b/qfieldcloud_sdk/utils.py @@ -107,12 +107,13 @@ def format_project_table(projects: List) -> str: project["id"], project["owner"] + "/" + project["name"], project["is_public"], + project["project_type"], project["description"], ] ) return format_table( - headers=["ID", "OWNER/NAME", "IS PUBLIC", "DESCRIPTION"], + headers=["ID", "OWNER/NAME", "IS PUBLIC", "PROJECT TYPE", "DESCRIPTION"], data=data, )