One useful idea
An adapter translates one provider’s schema into a common interface. The consumer asks get_job(job_id) without knowing whether the source uses job_id/state or id/status. Inject a client instead of constructing hidden network access inside the adapter. The caller keeps ownership of client lifetime and retry policy.
A class groups state and behavior. __init__ stores the injected client on self; get_job uses it. Provider is a Protocol describing that method for type checkers. Job is a TypedDict describing id/status/provider/source_status dictionary fields. Neither type hint validates runtime data; the adapter still needs explicit guards. These are small contracts, not the deeper domain models introduced in Module 8.
Provider A accepts exactly job_id/state and maps pending/processing/done/error to queued/running/succeeded/failed. Provider B accepts exactly id/status and maps queued/running/completed/failed to the same common states. Trim response IDs and require equality with the requested canonical ID. Trim/casefold source states for lookup but preserve the original source_status. Require strings, at most 40 source-status characters and known states. Return a new dictionary; refuse extra/missing keys, wrong types, mismatched IDs and unknown states as permanent schema errors.
Unknown is not failed: a fabricated failed state loses the distinction between a real provider outcome and data you cannot interpret. Declared HTTP transient errors propagate to the caller’s intentional retry policy. Both adapters run the same parametrized tests on invented schemas. This is not a real provider integration or evidence that any current commercial API uses these fields.
Refresh first: Injected mock HTTP clients, Caller-owned retries, Original-preserving normalization.
Trace a finished example
import httpx
from provider_tools.adapters import Provider, ProviderA, ProviderB
def summarize(provider: Provider, job_id: str) -> str:
job = provider.get_job(job_id)
return job["id"] + " " + job["status"]
for adapter_class, payload in [
(ProviderA, {"job_id": " new_4 ", "state": " done "}),
(ProviderB, {"id": "new_4", "status": "COMPLETED"}),
]:
def handle(request):
return httpx.Response(200, json=payload)
with httpx.Client(transport=httpx.MockTransport(handle), trust_env=False) as client:
adapter = adapter_class(client)
print(summarize(adapter, "new_4"))
print(repr(adapter.get_job("new_4")["source_status"]))The same summarize consumer calls each adapter. Each adapter fetches and validates its own invented schema, returning the same canonical status while preserving different source text. The second get_job in this example makes another mock read; it is not a cached job or paid request.
The finished implementation is in provider_tools/adapters.py and demo.py. Reading it is guided practice, not independent evidence.
Predict provenance
If A returns state=" done ", which source_status survives?
Compare your answer · self-reviewed
The original " done " string. Only the lookup uses stripped/casefolded text; the common status becomes succeeded.
Find invented failure
What should an unknown state such as waiting-for-review become?
Compare your answer · self-reviewed
A permanent schema refusal under this contract, not a fabricated failed or succeeded job. Extend an explicit mapping only when a reviewed contract supports it.
Recall runtime validation
Does annotating a consumer with Provider prove its returned dictionary is valid?
Compare your answer · self-reviewed
No. Protocol/TypedDict help type checkers describe shapes. Actual HTTP dictionaries still need explicit validation and behavioral tests.
Change it, then build your own
One controlled change
Return processing from A and RUNNING from B, then change one response ID and add an extra field. Predict normalized status/source text versus permanent schema refusals. Keep both clients mocked.
Your independent task
Implement ProviderA and ProviderB in practice.py, including constructors that store the injected client. Reviewed core.fetch_job is permitted to isolate the adapter task; disclose that support. Write your own complete normalization and source preservation, returning exact fresh Job dictionaries. Keep schema failures permanent and propagate declared transient HTTP errors. Do not edit the tests, add credentials or manufacture states.
What success looks like
The build4 group applies the same four-status, fresh-output, exact-schema, mismatch and transient-propagation checks to both adapters. Then rerun all implemented practice groups. The kit is groundwork for Portfolio II, not a finished dataset toolkit or a portfolio pass.
Hint 1 · a question
Write the common consumer contract first. Which schema differences belong inside each adapter, and who owns the client?
Hint 2 · a concept cue
Fetch with the declared provider, validate the complete key set/types/identity, map only known statuses and construct fresh output with the original source text.
Hint 3 · a localized example
self.client = client stores an injected dependency. A later get_job can call core.fetch_job(self.client, job_id, provider="a"); normalization and the second adapter still need your implementation.
Need the complete worked solution?
Open provider_tools/adapters.py and demo.py from the kit. Trace it, close it, then try fresh inputs in your own files. Treat the attempt as guided; seeing the solution does not award a practical pass.
Course help is guidance, not independent evidence. With JavaScript, opening help records guidance locally; otherwise note it in your README. Reset does not erase that history.
Repair a failed check
If consumers branch on every provider’s field names, move that translation into adapters. If unknown states default to failed, refuse them instead. If the original status disappears, preserve it before lookup. If one adapter passes only its own example, rerun the shared parametrized suite with changed IDs and all statuses.
NotImplementedError means a practice stub is still unfinished. Read the failing test name and the last error line. Change one behavior, rerun that build, then rerun all implemented builds.
Show it works on new inputs
Consume both of your adapters with a new job ID, then add an in-memory fake provider implementing get_job. Record changed statuses, exact source text and a refused unknown/mismatched response. Explain where runtime validation occurs and disclose reviewed fetch support.
Self-review: name the input, result, refused case and reason. Your local test output and explanation are separate from a quiz score; this page does not certify a pass.
Keep the idea
One stable interface reduces consumer complexity, while explicit validation keeps provider differences and missing knowledge honest.
Module 5 checkpoint
Five questions, followed by the separate practical task above. JavaScript loads the scored questions; the build, files and hints remain available without it.