Skip to content

Commit 4c31ead

Browse files
Remove restriction on the number of subtasks (#19)
* Update dependencies * Update dataclasses to latest protobuf messages * Merge future tasks to submissions * Switch to merged task submissions message * Add additional type checks * Update Changelog * Re-record runner tests grpc calls * Filter out python 3.10 google.api_core future warnings
1 parent 8c69faf commit 4c31ead

30 files changed

Lines changed: 1997 additions & 1148 deletions

.pre-commit-config.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ repos:
44
hooks:
55
- id: check-yaml
66
- id: end-of-file-fixer
7+
- repo: https://github.com/tsvikas/sync-with-uv
8+
rev: v0.4.0
9+
hooks:
10+
- id: sync-with-uv
711
- repo: https://github.com/charliermarsh/ruff-pre-commit
8-
# keep the version here in sync with the version in uv.lock
9-
rev: "v0.13.0"
12+
rev: v0.14.3
1013
hooks:
1114
- id: ruff-check
1215
args: [--fix, --exit-non-zero-on-fix]
1316
- id: ruff-format
1417
- repo: https://github.com/RobertCraigie/pyright-python
15-
# keep the version here in sync with the version in uv.lock
1618
rev: v1.1.400
1719
hooks:
1820
- id: pyright

CHANGELOG.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.45.0] - 2025-11-17
11+
12+
### Added
13+
14+
- `tilebox-workflows`: Added `execution_stats` to the `Job` object to provide programmatic access to a job's execution
15+
statistics.
16+
- `tilebox-workflows`: Added query filters to the `JobClient.query` method to filter jobs by multiple automation ids,
17+
job state, and job name.
18+
- `tilebox-workflows`: Added additional JobState values to indicate a job's current state and progress more accurately.
19+
- `tilebox-workflows`: Removed the restriction of `64` subtasks per task.
20+
21+
### Changed
22+
23+
- `tilebox-workflows`: Deprecated the `job.canceled` property, which will be removed in a future version. The
24+
equivalent expression behavior for the old `job.canceled` is `(job.state in (JobState.CANCELED, JobState.FAILED)`.
25+
- `tilebox-workflows`: Deprecated the `job.started_at` property, which will be removed in a future version. The
26+
equivalent information is available in `job.execution_stats.first_task_started_at` instead.
27+
- `tilebox-workflows`: The `JobState.QUEUED` enum value has been renamed in favor of the more accurate
28+
`JobState.SUBMITTED`. The `QUEUED` value is still available as an alias for `SUBMITTED` for backwards compatibility,
29+
but will be removed in a future version.
30+
- `tilebox-workflows`: Switched to an updated internal `TaskSubmission` message format that allows for more efficient
31+
submission of a very large number of tasks.
32+
1033
## [0.44.0] - 2025-09-18
1134

1235
### Added
@@ -266,7 +289,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
266289
- Released under the [MIT](https://opensource.org/license/mit) license.
267290
- Released packages: `tilebox-datasets`, `tilebox-workflows`, `tilebox-storage`, `tilebox-grpc`
268291

269-
[Unreleased]: https://github.com/tilebox/tilebox-python/compare/v0.44.0...HEAD
292+
[Unreleased]: https://github.com/tilebox/tilebox-python/compare/v0.45.0...HEAD
293+
[0.45.0]: https://github.com/tilebox/tilebox-python/compare/v0.45.0...v0.45.0
270294
[0.44.0]: https://github.com/tilebox/tilebox-python/compare/v0.43.0...v0.44.0
271295
[0.43.0]: https://github.com/tilebox/tilebox-python/compare/v0.42.0...v0.43.0
272296
[0.42.0]: https://github.com/tilebox/tilebox-python/compare/v0.41.0...v0.42.0

tests.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,15 @@ for package in $packages; do
1313
if [ -d _tilebox ]; then
1414
module=_tilebox
1515
fi
16-
uv run --all-packages pytest -Wall -Werror --cov=$module --cov-branch -v --junitxml=test-report.xml . || exit 1
16+
17+
PYTHON_VERSION=$(uv run python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null || true)
18+
19+
if [ "$PYTHON_VERSION" = "3.10" ]; then
20+
# ignore: FutureWarning: You are using a Python version (3.10.11) which Google will stop supporting in new releases of google.api_core once it reaches its end of life (2026-10-04).
21+
uv run --all-packages pytest -Wall -Werror -W "ignore::FutureWarning" --cov=$module --cov-branch -v --junitxml=test-report.xml . || exit 1
22+
else
23+
uv run --all-packages pytest -Wall -Werror --cov=$module --cov-branch -v --junitxml=test-report.xml . || exit 1
24+
fi
1725

1826
cd .. || exit 1 # cd back to the root of the monorepo
1927
done

tilebox-datasets/tests/test_timeseries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1+
from dataclasses import dataclass
12
from datetime import datetime, timedelta
23
from unittest.mock import MagicMock, patch
34
from uuid import uuid4
45

56
import pytest
67
import xarray as xr
7-
from attr import dataclass
88
from hypothesis import assume, given, settings
99
from hypothesis.stateful import Bundle, RuleBasedStateMachine, consumes, invariant, rule
1010
from hypothesis.strategies import lists

tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.py

Lines changed: 8 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tilebox-datasets/tilebox/datasets/datasets/v1/core_pb2.pyi

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class CollectionInfos(_message.Message):
112112
def __init__(self, data: _Optional[_Iterable[_Union[CollectionInfo, _Mapping]]] = ...) -> None: ...
113113

114114
class Dataset(_message.Message):
115-
__slots__ = ("id", "group_id", "type", "code_name", "name", "summary", "icon", "description", "permissions", "visibility", "slug", "type_editable")
115+
__slots__ = ("id", "group_id", "type", "code_name", "name", "summary", "icon", "description", "permissions", "visibility", "slug", "type_editable", "collections")
116116
ID_FIELD_NUMBER: _ClassVar[int]
117117
GROUP_ID_FIELD_NUMBER: _ClassVar[int]
118118
TYPE_FIELD_NUMBER: _ClassVar[int]
@@ -125,6 +125,7 @@ class Dataset(_message.Message):
125125
VISIBILITY_FIELD_NUMBER: _ClassVar[int]
126126
SLUG_FIELD_NUMBER: _ClassVar[int]
127127
TYPE_EDITABLE_FIELD_NUMBER: _ClassVar[int]
128+
COLLECTIONS_FIELD_NUMBER: _ClassVar[int]
128129
id: _id_pb2.ID
129130
group_id: _id_pb2.ID
130131
type: _dataset_type_pb2.AnnotatedType
@@ -137,7 +138,8 @@ class Dataset(_message.Message):
137138
visibility: Visibility
138139
slug: str
139140
type_editable: bool
140-
def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., group_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[_dataset_type_pb2.AnnotatedType, _Mapping]] = ..., code_name: _Optional[str] = ..., name: _Optional[str] = ..., summary: _Optional[str] = ..., icon: _Optional[str] = ..., description: _Optional[str] = ..., permissions: _Optional[_Iterable[_Union[DatasetPermission, str]]] = ..., visibility: _Optional[_Union[Visibility, str]] = ..., slug: _Optional[str] = ..., type_editable: bool = ...) -> None: ...
141+
collections: _containers.RepeatedCompositeFieldContainer[CollectionInfo]
142+
def __init__(self, id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., group_id: _Optional[_Union[_id_pb2.ID, _Mapping]] = ..., type: _Optional[_Union[_dataset_type_pb2.AnnotatedType, _Mapping]] = ..., code_name: _Optional[str] = ..., name: _Optional[str] = ..., summary: _Optional[str] = ..., icon: _Optional[str] = ..., description: _Optional[str] = ..., permissions: _Optional[_Iterable[_Union[DatasetPermission, str]]] = ..., visibility: _Optional[_Union[Visibility, str]] = ..., slug: _Optional[str] = ..., type_editable: bool = ..., collections: _Optional[_Iterable[_Union[CollectionInfo, _Mapping]]] = ...) -> None: ...
141143

142144
class DatasetGroup(_message.Message):
143145
__slots__ = ("id", "parent_id", "code_name", "name", "icon")

tilebox-workflows/tests/jobs/test_client.py

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77

88
from _tilebox.grpc.error import NotFoundError
99
from tilebox.datasets.query.time_interval import datetime_to_timestamp
10-
from tilebox.workflows.data import Job, uuid_message_to_uuid, uuid_to_uuid_message
10+
from tilebox.workflows.data import Job, JobState, uuid_message_to_uuid, uuid_to_uuid_message
1111
from tilebox.workflows.jobs.client import JobClient
1212
from tilebox.workflows.jobs.service import JobService
1313
from tilebox.workflows.task import ExecutionContext, Task
1414
from tilebox.workflows.workflows.v1.core_pb2 import Job as JobMessage
15-
from tilebox.workflows.workflows.v1.core_pb2 import JobState
15+
from tilebox.workflows.workflows.v1.core_pb2 import JobState as JobStateEnum
1616
from tilebox.workflows.workflows.v1.diagram_pb2 import Diagram
1717
from tilebox.workflows.workflows.v1.job_pb2 import (
1818
CancelJobRequest,
@@ -45,8 +45,7 @@ def SubmitJob(self, req: SubmitJobRequest) -> JobMessage: # noqa: N802
4545
id=uuid_to_uuid_message(job_id),
4646
name=req.job_name,
4747
trace_parent=req.trace_parent,
48-
canceled=False,
49-
state=JobState.JOB_STATE_QUEUED,
48+
state=JobStateEnum.JOB_STATE_SUBMITTED,
5049
submitted_at=datetime_to_timestamp(datetime.now(tz=timezone.utc)),
5150
)
5251
self.jobs[job_id] = job
@@ -65,8 +64,7 @@ def RetryJob(self, req: RetryJobRequest) -> RetryJobResponse: # noqa: N802
6564
raise NotFoundError(f"No such job: {job_id}")
6665

6766
copy = JobMessage.FromString(self.jobs[job_id].SerializeToString())
68-
copy.canceled = False
69-
copy.state = JobState.JOB_STATE_QUEUED
67+
copy.state = JobStateEnum.JOB_STATE_SUBMITTED
7068
self.jobs[job_id] = copy
7169
return RetryJobResponse(num_tasks_rescheduled=1)
7270

@@ -76,8 +74,7 @@ def CancelJob(self, req: CancelJobRequest) -> CancelJobResponse: # noqa: N802
7674
raise NotFoundError(f"No such job: {job_id}")
7775

7876
copy = JobMessage.FromString(self.jobs[job_id].SerializeToString())
79-
copy.canceled = True
80-
copy.state = JobState.JOB_STATE_STARTED
77+
copy.state = JobStateEnum.JOB_STATE_CANCELED
8178
self.jobs[job_id] = copy
8279
return CancelJobResponse()
8380

@@ -87,7 +84,7 @@ def VisualizeJob(self, req: VisualizeJobRequest) -> Diagram: # noqa: N802
8784
raise NotFoundError(f"No such job: {job_id}")
8885

8986
job = self.jobs[job_id]
90-
if job.canceled:
87+
if job.state == JobStateEnum.JOB_STATE_CANCELED:
9188
return Diagram(svg=b"<svg><text>Job canceled</text></svg>")
9289

9390
return Diagram(svg=b"<svg><text>Job queued</text></svg>")
@@ -129,26 +126,26 @@ def submit_job(self, job: Job) -> Job:
129126
def get_queued_job(self, job: Job) -> None:
130127
got = self.job_client.find(job.id)
131128
assert got.name == job.name
132-
assert not got.canceled
129+
assert got.state == JobState.SUBMITTED
133130

134131
@rule(job=cancelled_jobs)
135132
def get_canceled_job(self, job: Job) -> None:
136133
got = self.job_client.find(job.id)
137134
assert got.name == job.name
138-
assert got.canceled
135+
assert got.state == JobState.CANCELED
139136

140137
@rule(target=cancelled_jobs, job=consumes(queued_jobs)) # consumes -> remove from bundle afterwards
141138
def cancel_job(self, job: Job) -> Job:
142139
self.job_client.cancel(job.id)
143140
job = self.job_client.find(job.id)
144-
assert job.canceled
141+
assert job.state == JobState.CANCELED
145142
return job
146143

147144
@rule(target=queued_jobs, job=consumes(cancelled_jobs)) # consumes -> remove from bundle afterwards
148145
def retry_job(self, job: Job) -> Job:
149146
self.job_client.retry(job.id)
150147
job = self.job_client.find(job.id)
151-
assert not job.canceled
148+
assert job.state == JobState.SUBMITTED
152149
return job
153150

154151
@rule()

0 commit comments

Comments
 (0)