Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/generated-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,6 @@ Auto-generated from the server OpenAPI spec. Each operation is available in both
| DELETE | `/api/vcs/branches/{name}` | `autoDeleteApiVcsBranchesName` | `auto_delete_api_vcs_branches_name` |
| POST | `/api/vcs/commit` | `vcscommit` | `vcs_commit` |
| GET | `/api/vcs/diff` | `vcsdiff` | `vcs_diff` |
| POST | `/api/vcs/init` | `vcsinit` | `vcs_init` |
| GET | `/api/vcs/log` | `vcslog` | `vcs_log` |
| GET | `/api/vcs/log/dataset/{name}` | `autoGetApiVcsLogDatasetName` | `auto_get_api_vcs_log_dataset_name` |
| GET | `/api/vcs/prs` | `autoGetApiVcsPrs` | `auto_get_api_vcs_prs` |
Expand All @@ -369,6 +368,8 @@ Auto-generated from the server OpenAPI spec. Each operation is available in both
| GET | `/api/vcs/prs/{id}/conflicts` | `autoGetApiVcsPrsIdConflicts` | `auto_get_api_vcs_prs_id_conflicts` |
| POST | `/api/vcs/prs/{id}/merge` | `autoPostApiVcsPrsIdMerge` | `auto_post_api_vcs_prs_id_merge` |
| POST | `/api/vcs/prs/{id}/reviews` | `autoPostApiVcsPrsIdReviews` | `auto_post_api_vcs_prs_id_reviews` |
| GET | `/api/vcs/repos` | `vcslistrepos` | `vcs_list_repos` |
| POST | `/api/vcs/repos` | `vcscreaterepo` | `vcs_create_repo` |
| GET | `/api/vcs/tags` | `autoGetApiVcsTags` | `auto_get_api_vcs_tags` |
| POST | `/api/vcs/tags` | `autoPostApiVcsTags` | `auto_post_api_vcs_tags` |
| DELETE | `/api/vcs/tags/{name}` | `autoDeleteApiVcsTagsName` | `auto_delete_api_vcs_tags_name` |
Expand Down
2 changes: 1 addition & 1 deletion docs/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ from roteiro import analysis, attachments, collections, layers, raster, vcs
| `collections` | `list_collections`, `get_collection`, `get_items`, `get_item`, `create_item`, `update_item`, `delete_item` |
| `attachments` | `upload_attachment`, `list_attachments`, `download_attachment`, `delete_attachment` |
| `layers` | `upload_layer`, `list_layers`, `get_layer`, `update_layer`, `publish_layer`, `archive_layer`, `upload_layer_data`, `delete_layer`, `preview_layer` |
| `vcs` | `init_repo`, `commit`, `log`, `diff`, `checkout` |
| `vcs` | `create_repo`, `list_repos`, `get_repo`, `delete_repo`, `commit`, `log`, `log_for_dataset`, `diff`, `checkout` |
| `raster` | `get_raster_info`, `get_raster_stats`, `get_raster_histogram`, `get_raster_dimensions`, `get_raster_band_values`, `band_math`, `ndvi`, `hillshade`, `zonal_stats`, `export_raster`, `contour`, `viewshed`, `elevation_profile`, `kde`, `process`, `mosaic`, `get_mosaic_info` |

### Example: analysis helpers
Expand Down
2 changes: 1 addition & 1 deletion docs/typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ import {
| `collections` | `listCollections`, `getCollection`, `getItems`, `getItem`, `createItem`, `updateItem`, `deleteItem` |
| `attachments` | `uploadAttachment`, `listAttachments`, `downloadAttachment`, `deleteAttachment` |
| `layers` | `uploadLayer`, `listLayers`, `getLayer`, `updateLayer`, `publishLayer`, `archiveLayer`, `uploadLayerData`, `deleteLayer`, `previewLayer` |
| `vcs` | `initRepo`, `commit`, `log`, `diff`, `checkout` |
| `vcs` | `createRepo`, `listRepos`, `getRepo`, `deleteRepo`, `commit`, `log`, `logForDataset`, `diff`, `checkout` |
| `raster` | `getRasterInfo`, `getRasterStats`, `getRasterHistogram`, `getRasterDimensions`, `getRasterBandValues`, `bandMath`, `ndvi`, `hillshade`, `zonalStats`, `exportRaster`, `contour`, `viewshed`, `elevationProfile`, `kde`, `process`, `mosaic`, `getMosaicInfo` |

### Example: analysis helpers
Expand Down
2 changes: 1 addition & 1 deletion python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ print(health.status, len(collections), len(features.features), len(areas), len(h
| `collections` | `list_collections`, `get_collection`, `get_items`, `get_item`, `create_item`, `update_item`, `delete_item` |
| `attachments` | `upload_attachment`, `list_attachments`, `download_attachment`, `delete_attachment` |
| `layers` | `upload_layer`, `list_layers`, `get_layer`, `update_layer`, `publish_layer`, `archive_layer`, `upload_layer_data`, `delete_layer`, `preview_layer` |
| `vcs` | `init_repo`, `commit`, `log`, `diff`, `checkout` |
| `vcs` | `create_repo`, `list_repos`, `get_repo`, `delete_repo`, `commit`, `log`, `log_for_dataset`, `diff`, `checkout` |
| `raster` | `get_raster_info`, `get_raster_stats`, `get_raster_histogram`, `get_raster_dimensions`, `get_raster_band_values`, `band_math`, `ndvi`, `hillshade`, `zonal_stats`, `export_raster`, `contour`, `viewshed`, `elevation_profile`, `kde`, `process`, `mosaic`, `get_mosaic_info` |

## Full API Coverage
Expand Down
10 changes: 5 additions & 5 deletions python/examples/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,16 @@

from roteiro import vcs

# Initialise a new spatial repository
# repo = vcs.init_repo(client, "/tmp/my-spatial-repo")
# print(f"VCS repo initialised at {repo.path}")
# Create a managed repository
# repo = vcs.create_repo(client, "roads-history", dataset_name="roads")
# print(f"Managed repo {repo.id}: {repo.name}")

# Create a commit
# commit_obj = vcs.commit(client, "/tmp/my-spatial-repo", "/data/buildings.geojson", "Initial import")
# commit_obj = vcs.commit(client, repo.id, "/data/buildings.geojson", "Initial import")
# print(f"Commit {commit_obj.id}: {commit_obj.message}")

# View commit history
# commits = vcs.log(client, "/tmp/my-spatial-repo")
# commits = vcs.log(client, repo.id)
# for c in commits:
# print(f" [{c.id[:8]}] {c.message}")

Expand Down
39 changes: 26 additions & 13 deletions python/roteiro/generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -4616,19 +4616,6 @@ def vcs_diff(self, query: Optional[Dict[str, Any]] = None, body: Any = None, hea
extra_headers['Content-Type'] = 'application/json'
return self._client._request('GET', path, body=payload, extra_headers=extra_headers)

def vcs_init(self, query: Optional[Dict[str, Any]] = None, body: Any = None, headers: Optional[Dict[str, str]] = None) -> Any:
"""Initialize spatial version control for a dataset"""
path = "/api/vcs/init"
if query:
q = urlencode({k: v for k, v in query.items() if v is not None})
if q:
path = f"{path}?{q}"
extra_headers = dict(headers or {})
payload = body
if payload is not None and 'Content-Type' not in extra_headers:
extra_headers['Content-Type'] = 'application/json'
return self._client._request('POST', path, body=payload, extra_headers=extra_headers)

def vcs_log(self, query: Optional[Dict[str, Any]] = None, body: Any = None, headers: Optional[Dict[str, str]] = None) -> Any:
"""View version history"""
path = "/api/vcs/log"
Expand Down Expand Up @@ -4759,6 +4746,32 @@ def auto_post_api_vcs_prs_id_reviews(self, id: str, query: Optional[Dict[str, An
extra_headers['Content-Type'] = 'application/json'
return self._client._request('POST', path, body=payload, extra_headers=extra_headers)

def vcs_list_repos(self, query: Optional[Dict[str, Any]] = None, body: Any = None, headers: Optional[Dict[str, str]] = None) -> Any:
"""List managed VCS repositories"""
path = "/api/vcs/repos"
if query:
q = urlencode({k: v for k, v in query.items() if v is not None})
if q:
path = f"{path}?{q}"
extra_headers = dict(headers or {})
payload = body
if payload is not None and 'Content-Type' not in extra_headers:
extra_headers['Content-Type'] = 'application/json'
return self._client._request('GET', path, body=payload, extra_headers=extra_headers)

def vcs_create_repo(self, query: Optional[Dict[str, Any]] = None, body: Any = None, headers: Optional[Dict[str, str]] = None) -> Any:
"""Create a managed VCS repository"""
path = "/api/vcs/repos"
if query:
q = urlencode({k: v for k, v in query.items() if v is not None})
if q:
path = f"{path}?{q}"
extra_headers = dict(headers or {})
payload = body
if payload is not None and 'Content-Type' not in extra_headers:
extra_headers['Content-Type'] = 'application/json'
return self._client._request('POST', path, body=payload, extra_headers=extra_headers)

def auto_get_api_vcs_tags(self, query: Optional[Dict[str, Any]] = None, body: Any = None, headers: Optional[Dict[str, str]] = None) -> Any:
"""[auto] GET /api/vcs/tags"""
path = "/api/vcs/tags"
Expand Down
20 changes: 18 additions & 2 deletions python/roteiro/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,14 +528,28 @@ def from_dict(cls, data: Dict[str, Any]) -> "DiffSummary":

@dataclass
class Repo:
"""A spatial VCS repository."""
"""A managed spatial VCS repository."""

path: str
id: str = ""
name: str = ""
tenant_id: Optional[int] = None
project_id: Optional[int] = None
dataset_name: Optional[str] = None
created_by: Optional[int] = None
created_at: str = ""
path: str = ""
status: str = ""

@classmethod
def from_dict(cls, data: Dict[str, Any]) -> "Repo":
return cls(
id=data.get("id", ""),
name=data.get("name", ""),
tenant_id=data.get("tenant_id"),
project_id=data.get("project_id"),
dataset_name=data.get("dataset_name"),
created_by=data.get("created_by"),
created_at=data.get("created_at", ""),
path=data.get("path", ""),
status=data.get("status", ""),
)
Expand All @@ -549,6 +563,7 @@ class Commit:
message: str = ""
timestamp: str = ""
parent: Optional[str] = None
blob_id: Optional[str] = None
blob_hash: Optional[str] = None
feature_count: Optional[int] = None

Expand All @@ -559,6 +574,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "Commit":
message=data.get("message", ""),
timestamp=data.get("timestamp", ""),
parent=data.get("parent"),
blob_id=data.get("blob_id"),
blob_hash=data.get("blob_hash"),
feature_count=data.get("feature_count"),
)
Expand Down
90 changes: 66 additions & 24 deletions python/roteiro/vcs.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,74 @@
"""Spatial version control system (VCS) operations.

Provides functions for initialising repositories, creating commits, viewing
commit history, computing diffs, and checking out historical snapshots.
Provides helpers for managed repository creation, commit history, and diffs.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, List, Optional

from .client import _with_query
from .client import _encode_path_value, _with_query
from .models import Commit, DiffResult, Repo

if TYPE_CHECKING:
from .client import RoteiroClient


def init_repo(client: RoteiroClient, path: str) -> Repo:
"""Initialise a new spatial VCS repository.

Creates a ``.roteiro-vcs/`` directory at the given path to track
content-addressable geospatial snapshots.
def create_repo(
client: RoteiroClient,
name: str,
project_id: Optional[int] = None,
dataset_name: Optional[str] = None,
) -> Repo:
"""Create a managed VCS repository.

Args:
client: An initialised RoteiroClient instance.
path: Filesystem path where the repository should be initialised.
name: Repository display name.
project_id: Optional project association. Defaults to the client's
configured project scope when available.
dataset_name: Optional dataset linkage for the repository.

Returns:
A Repo object with the initialised path and status.
The created repository record.
"""
data = client._post("/api/vcs/init", {"path": path})
body = {"name": name}
effective_project_id = client._effective_project_id(project_id)
if effective_project_id is not None:
body["project_id"] = effective_project_id
if dataset_name:
body["dataset_name"] = dataset_name
data = client._post("/api/vcs/repos", body)
return Repo.from_dict(data)


def list_repos(
client: RoteiroClient,
project_id: Optional[int] = None,
) -> List[Repo]:
"""List managed repositories visible to the current tenant."""
effective_project_id = client._effective_project_id(project_id)
path = "/api/vcs/repos"
if effective_project_id is not None:
path = _with_query(path, {"project_id": effective_project_id})
data = client._get(path)
return [Repo.from_dict(repo) for repo in data]


def get_repo(client: RoteiroClient, repo_id: str) -> Repo:
"""Fetch a managed repository by ID."""
data = client._get(f"/api/vcs/repos/{_encode_path_value(repo_id)}")
return Repo.from_dict(data)


def delete_repo(client: RoteiroClient, repo_id: str) -> None:
"""Delete a managed repository by ID."""
client._delete(f"/api/vcs/repos/{_encode_path_value(repo_id)}")


def commit(
client: RoteiroClient,
repo_path: str,
repo_id: str,
input_path: str,
message: str,
) -> Commit:
Expand All @@ -45,7 +79,7 @@ def commit(

Args:
client: An initialised RoteiroClient instance.
repo_path: Filesystem path to the VCS repository.
repo_id: Managed repository ID.
input_path: Path to the geospatial dataset to commit.
message: Human-readable commit message.

Expand All @@ -54,30 +88,38 @@ def commit(
"""
data = client._post(
"/api/vcs/commit",
{"path": repo_path, "input": input_path, "message": message},
{"repo_id": repo_id, "input": input_path, "message": message},
)
return Commit.from_dict(data)


def log(client: RoteiroClient, repo_path: str) -> List[Commit]:
def log(client: RoteiroClient, repo_id: str) -> List[Commit]:
"""Get the commit history for a repository.

Returns commits ordered from most recent to oldest.

Args:
client: An initialised RoteiroClient instance.
repo_path: Filesystem path to the VCS repository.
repo_id: Managed repository ID.

Returns:
A list of Commit objects.
"""
data = client._get(_with_query("/api/vcs/log", {"path": repo_path}))
data = client._get(_with_query("/api/vcs/log", {"repo_id": repo_id}))
return [Commit.from_dict(c) for c in data]


def log_for_dataset(client: RoteiroClient, dataset_name: str) -> List[Commit]:
"""Get commit history for the managed repository linked to a dataset."""
data = client._get(
f"/api/vcs/log/dataset/{_encode_path_value(dataset_name)}"
)
return [Commit.from_dict(c) for c in data]


def diff(
client: RoteiroClient,
repo_path: str,
repo_id: str,
commit_a: str,
commit_b: str,
) -> DiffResult:
Expand All @@ -88,7 +130,7 @@ def diff(

Args:
client: An initialised RoteiroClient instance.
repo_path: Filesystem path to the VCS repository.
repo_id: Managed repository ID.
commit_a: Commit ID or ref for the base (from) commit.
commit_b: Commit ID or ref for the target (to) commit.

Expand All @@ -98,15 +140,15 @@ def diff(
data = client._get(
_with_query(
"/api/vcs/diff",
{"path": repo_path, "from": commit_a, "to": commit_b},
{"repo_id": repo_id, "from": commit_a, "to": commit_b},
)
)
return DiffResult.from_dict(data)


def checkout(
client: RoteiroClient,
repo_path: str,
repo_id: str,
commit_id: str,
) -> None:
"""Check out a specific commit (restore a historical snapshot).
Expand All @@ -117,7 +159,7 @@ def checkout(

Args:
client: An initialised RoteiroClient instance.
repo_path: Filesystem path to the VCS repository.
repo_id: Managed repository ID.
commit_id: The commit ID to check out.

Note:
Expand All @@ -126,7 +168,7 @@ def checkout(
commit blob directly. This method retrieves the diff from the
initial commit to the target as a lightweight proxy.
"""
commits = log(client, repo_path)
commits = log(client, repo_id)
if not commits:
from .client import RoteiroAPIError

Expand All @@ -139,4 +181,4 @@ def checkout(
return

# Retrieve the diff to validate the commit exists.
diff(client, repo_path, oldest.id, commit_id)
diff(client, repo_id, oldest.id, commit_id)
Loading
Loading