Skip to content

fix(api): guard SitesClient job response parsing#20

Open
Arittra-Bag wants to merge 1 commit into
mainfrom
atomic-api-error
Open

fix(api): guard SitesClient job response parsing#20
Arittra-Bag wants to merge 1 commit into
mainfrom
atomic-api-error

Conversation

@Arittra-Bag
Copy link
Copy Markdown
Collaborator

Closes : #11

Copilot AI review requested due to automatic review settings May 8, 2026 15:37
@Arittra-Bag
Copy link
Copy Markdown
Collaborator Author

Verified the Changes on SDK locally:

create: malformed [] -> AtomicAPIError ok
delete: malformed [] -> AtomicAPIError ok
update_domain: malformed [] -> AtomicAPIError ok
manage_software: malformed [] -> AtomicAPIError ok
set_wordpress_version: malformed [] -> AtomicAPIError ok
update_options: malformed [] -> AtomicAPIError ok
update_persistent_data: malformed [] -> AtomicAPIError ok
reset_db_password: malformed [] -> AtomicAPIError ok
success payload -> Job with _client ok
missing job_id dict -> AtomicAPIError ok

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a centralized guard in SitesClient for parsing “job-style” responses so unexpected payload shapes raise an actionable AtomicAPIError rather than bubbling up a Pydantic ValidationError.

Changes:

  • Introduced SitesClient._parse_job() to validate response shape (dict + job_id) before parsing into Job.
  • Updated all job-returning Sites endpoints to use _parse_job() instead of duplicating Job.parse_obj(...) + _client wiring.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread atomic_sdk/api/sites.py
"Expected a job response (dict with 'job_id'), "
f"got {type(response_data).__name__}: {response_data!r}"
),
status_code=200,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving this as-is because issue #11 explicitly requested status_code=200 for malformed job payloads returned from a successful _post() call. Non-2xx responses are still handled earlier by ResourceClient._request; this helper only covers the “HTTP 200 but unexpected data shape” case.

@Arittra-Bag Arittra-Bag requested a review from mrrobot47 May 8, 2026 15:43
@Arittra-Bag
Copy link
Copy Markdown
Collaborator Author

Verified in bench console via EasyDash that the patched SitesClient._parse_job is loaded, all 8 job-returning site methods convert malformed data == [] responses into AtomicAPIError, and a valid job payload still returns a Job with _client attached.

Input:

from easydash.wp_cloud import atomic
from atomic_sdk.exceptions import AtomicAPIError
from atomic_sdk.models import Job

domain = "ab-prod-test.stagingview.net"
site = frappe.get_doc("WPC Site", domain)
client = atomic.get_client(site.organization)

print(client.sites.__class__)
print(client.sites.__class__._parse_job)

orig_post = client.sites._post

methods = [
    ("create", lambda client=client: client.sites.create(admin_user="admin", admin_email="admin@example.com", domain_name="example.com")),
    ("delete", lambda client=client, domain=domain: client.sites.delete(domain=domain)),
    ("update_domain", lambda client=client, domain=domain: client.sites.update_domain(new_domain="new.example.com", domain=domain, keep_old_domain=True)),
    ("manage_software", lambda client=client, domain=domain: client.sites.manage_software({"plugins/akismet/latest": "activate"}, domain=domain)),
    ("set_wordpress_version", lambda client=client, domain=domain: client.sites.set_wordpress_version("latest", domain=domain)),
    ("update_options", lambda client=client, domain=domain: client.sites.update_options({"set": {"blog_public": "0"}}, domain=domain)),
    ("update_persistent_data", lambda client=client, domain=domain: client.sites.update_persistent_data({"foo": {"value": "bar"}}, domain=domain)),
    ("reset_db_password", lambda client=client, domain=domain: client.sites.reset_db_password(domain=domain)),
]

def fake_post_empty(endpoint, data=None, json=None):
    print("fake _post:", endpoint)
    return []

client.sites._post = fake_post_empty

for name, call in methods:
    try:
        call()
    except AtomicAPIError as exc:
        print(name, "OK", exc)
    except Exception as exc:
        print(name, "WRONG", type(exc), exc)
    else:
        print(name, "WRONG no error")

def fake_post_success(endpoint, data=None, json=None, domain=domain):
    print("fake _post success:", endpoint)
    return {"job_id": 1234, "atomic_site_id": 5678, "domain_name": domain}

client.sites._post = fake_post_success
job = client.sites.update_domain(new_domain="new.example.com", domain=domain)
print(type(job), job.job_id, job.atomic_site_id, job.domain_name, job._client is client)

client.sites._post = orig_post

Output:

<class 'atomic_sdk.api.sites.SitesClient'>
<function SitesClient._parse_job at 0xffffaa535300>
fake _post: /create-site/easyengine
create OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /delete-site/domain/ab-prod-test.stagingview.net
delete OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /update-site-domain/domain/ab-prod-test.stagingview.net/new.example.com/keep
update_domain OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /site-manage-software/atomic/ab-prod-test.stagingview.net
manage_software OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /site-wordpress-version/ab-prod-test.stagingview.net/latest
set_wordpress_version OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /update-site-options/atomic/ab-prod-test.stagingview.net
update_options OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /site-persist-data/ab-prod-test.stagingview.net
update_persistent_data OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post: /reset-db-password/atomic/ab-prod-test.stagingview.net
reset_db_password OK [200] Expected a job response (dict with 'job_id'), got list: []
fake _post success: /update-site-domain/domain/ab-prod-test.stagingview.net/new.example.com
<class 'atomic_sdk.models.Job'> 1234 5678 ab-prod-test.stagingview.net True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(api): raise AtomicAPIError on non-dict job responses instead of Pydantic ValidationError

2 participants