From e33d048c32fcb548ef2bee5a878f7463898807a0 Mon Sep 17 00:00:00 2001 From: Robin Genz Date: Thu, 1 Oct 2026 13:41:50 +0200 Subject: [PATCH 1/2] fix!: return parsed job logs and remove `git_repository_url` - `jobs.logs()` returns a list of `JobLog` instead of the raw JSON response text - remove `git_repository_url` from `apps.update()`, which the API ignores; use `apps.repository.set()` instead BREAKING CHANGE: `jobs.logs()` returns `List[JobLog]` instead of `str`, and `apps.update()` no longer accepts `git_repository_url`. --- README.md | 3 ++- examples/trigger_native_build.py | 3 ++- src/capawesome_cloud/__init__.py | 2 ++ src/capawesome_cloud/models.py | 8 ++++++++ src/capawesome_cloud/resources/apps.py | 2 -- src/capawesome_cloud/resources/jobs.py | 9 ++++----- tests/test_jobs.py | 20 ++++++++++++++++++++ 7 files changed, 38 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5213fde..08cd7a4 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,8 @@ build = client.apps.builds.create(app_id, platform="ios", git_ref="main") job = client.jobs.wait(build.job_id) print(job.status) -logs = client.jobs.logs(job.id) +for log in client.jobs.logs(job.id): + print(log.payload) # Explain a failure if job.status == "failed": diff --git a/examples/trigger_native_build.py b/examples/trigger_native_build.py index f663004..f4f131a 100644 --- a/examples/trigger_native_build.py +++ b/examples/trigger_native_build.py @@ -29,7 +29,8 @@ def main(app_id: str) -> None: print(f"Build finished with status: {job.status}") if job.status != "succeeded": - print(client.jobs.logs(build.job_id)) + for log in client.jobs.logs(build.job_id): + print(log.payload) return for artifact in client.apps.builds.artifacts.list(app_id, build.id): diff --git a/src/capawesome_cloud/__init__.py b/src/capawesome_cloud/__init__.py index 826c799..6838c1c 100644 --- a/src/capawesome_cloud/__init__.py +++ b/src/capawesome_cloud/__init__.py @@ -33,6 +33,7 @@ GitRepository, Job, JobFailureSummary, + JobLog, LicenseKey, Organization, OrganizationInvitation, @@ -75,6 +76,7 @@ "GitRepository", "Job", "JobFailureSummary", + "JobLog", "LicenseKey", "Organization", "OrganizationInvitation", diff --git a/src/capawesome_cloud/models.py b/src/capawesome_cloud/models.py index bb3d97c..5975834 100644 --- a/src/capawesome_cloud/models.py +++ b/src/capawesome_cloud/models.py @@ -41,6 +41,7 @@ "AppGitRepository", "Job", "JobFailureSummary", + "JobLog", "User", "Organization", "OrganizationMember", @@ -227,6 +228,13 @@ class Job(CapawesomeModel): created_at: Optional[datetime] = None +class JobLog(CapawesomeModel): + job_id: Optional[str] = None + number: Optional[int] = None + payload: Optional[str] = None + timestamp: Optional[datetime] = None + + class JobFailureSummary(CapawesomeModel): summary: Optional[str] = None diff --git a/src/capawesome_cloud/resources/apps.py b/src/capawesome_cloud/resources/apps.py index dd836c6..51aaec9 100644 --- a/src/capawesome_cloud/resources/apps.py +++ b/src/capawesome_cloud/resources/apps.py @@ -117,7 +117,6 @@ def update( app_channel_id: Union[str, None, NotGiven] = NOT_GIVEN, app_environment_id: Union[str, None, NotGiven] = NOT_GIVEN, app_channel_discovery_enabled: Union[bool, NotGiven] = NOT_GIVEN, - git_repository_url: Union[str, None, NotGiven] = NOT_GIVEN, next_app_build_number: Union[int, NotGiven] = NOT_GIVEN, ) -> App: """Update an app.""" @@ -128,7 +127,6 @@ def update( "appChannelId": app_channel_id, "appEnvironmentId": app_environment_id, "appChannelDiscoveryEnabled": app_channel_discovery_enabled, - "gitRepositoryUrl": git_repository_url, "nextAppBuildNumber": next_app_build_number, } ) diff --git a/src/capawesome_cloud/resources/jobs.py b/src/capawesome_cloud/resources/jobs.py index c9a3549..e6151ce 100644 --- a/src/capawesome_cloud/resources/jobs.py +++ b/src/capawesome_cloud/resources/jobs.py @@ -6,7 +6,7 @@ from typing import List, Optional, Sequence, Union from ..exceptions import CapawesomeCloudError -from ..models import Job, JobFailureSummary +from ..models import Job, JobFailureSummary, JobLog from ..pagination import DEFAULT_PAGE_SIZE, Paginator from ._base import BaseResource, build_params @@ -65,10 +65,9 @@ def get(self, job_id: str, *, relations: Optional[str] = None) -> Job: params = build_params({"relations": relations}) return self._request_model("GET", f"{self._path}/{job_id}", Job, params=params) - def logs(self, job_id: str) -> str: - """Return the job's logs as text.""" - response = self._http.request("GET", f"{self._path}/{job_id}/logs") - return response.text + def logs(self, job_id: str) -> List[JobLog]: + """List the log lines of a job.""" + return self._list_page(f"{self._path}/{job_id}/logs", JobLog) def get_failure_summary(self, job_id: str) -> JobFailureSummary: """Return a summary explaining why a failed job failed. diff --git a/tests/test_jobs.py b/tests/test_jobs.py index 4d0c9fd..1ef1340 100644 --- a/tests/test_jobs.py +++ b/tests/test_jobs.py @@ -53,3 +53,23 @@ def test_get_failure_summary(client: CapawesomeCloud) -> None: return_value=httpx.Response(200, json={"summary": "The signing certificate expired."}) ) assert client.jobs.get_failure_summary("job1").summary == "The signing certificate expired." + + +@respx.mock +def test_logs_are_parsed(client: CapawesomeCloud) -> None: + respx.get(f"{BASE_URL}/v1/jobs/job1/logs").mock( + return_value=httpx.Response( + 200, + json=[ + { + "jobId": "job1", + "number": 1, + "payload": "Cloning repository", + "timestamp": "2026-10-01T08:00:00Z", + } + ], + ) + ) + logs = client.jobs.logs("job1") + assert logs[0].number == 1 + assert logs[0].payload == "Cloning repository" From 87d4995173a96f13195ee6ffe553a4536d9a0ddf Mon Sep 17 00:00:00 2001 From: Robin Genz Date: Thu, 1 Oct 2026 13:44:02 +0200 Subject: [PATCH 2/2] docs: add `BREAKING.md` --- BREAKING.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 BREAKING.md diff --git a/BREAKING.md b/BREAKING.md new file mode 100644 index 0000000..b2ba390 --- /dev/null +++ b/BREAKING.md @@ -0,0 +1,28 @@ +# Breaking Changes + +This is a comprehensive list of the breaking changes introduced in the major version releases. Until version 1.0.0, breaking changes are introduced in minor version releases. + +## Versions + +- [Version 0.2.x](#version-02x) + +## Version 0.2.x + +### Job Logs + +`jobs.logs()` now returns a list of `JobLog` models (with `number`, `payload` and `timestamp`) instead of the raw response text. + +```diff +- print(client.jobs.logs(job_id)) ++ for log in client.jobs.logs(job_id): ++ print(log.payload) +``` + +### Removed `git_repository_url` Parameter + +The `git_repository_url` parameter has been removed from `apps.update()` as the API no longer supports it and ignored the value. Use `apps.repository.set()` to link an app to a Git repository instead. + +```diff +- client.apps.update(app_id, git_repository_url="https://github.com/owner/name") ++ client.apps.repository.set(app_id, git_connection_id=git_connection_id, path="owner/name") +```