Typorithm · easy

API URLs and encoded query values

pythonapi-clientsautomationindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

API URLs and encoded query values

0%

Concept and contract

Build one fixed-origin URL while preserving the boundary between query syntax and caller-supplied values.

An API cursor is an opaque value, not a fragment of query syntax. A space, ampersand or equals sign inside it must remain part of that value after transport.

This lesson builds a URL at one fixed fictional HTTPS origin and uses urlencode for its query. It does not request that address or discover real company feeds. The country and cursor contracts are intentionally small and explicit.

Mechanism and worked example

The function normalizes the country into a declared market set and builds a mapping of query names to values.

The function normalizes the country into a declared market set and builds a mapping of query names to values. A missing cursor is omitted; a supplied empty cursor is rejected instead of being treated as missing.

urlencode performs the query escaping, including a plus for a space and percent escapes for reserved value characters. Query ordering here follows insertion order for reproducible fixtures, while servers should interpret query names semantically.

Complete worked example

python

from urllib.parse import urlencode

def catalogue_url(country, cursor=None):
    if not isinstance(country, str) or country.strip().upper() not in {"US", "IL", "UA", "CA"}:
        raise ValueError("country required")
    query = {"country": country.strip().upper()}
    if cursor is not None:
        if not isinstance(cursor, str) or not 1 <= len(cursor) <= 80:
            raise ValueError("bounded cursor required")
        query["cursor"] = cursor
    return "https://jobs.example.test/v1/jobs?" + urlencode(query)
Reasoning
Build one fixed-origin URL while preserving the boundary between query syntax and caller-supplied values. Compare the stated values and failure boundary before changing the implementation.

Counterexamples and common mistakes

The counterexample concatenates a raw cursor onto the URL.

The counterexample concatenates a raw cursor onto the URL. A cursor containing ampersand then becomes a second query parameter rather than the original cursor value.

Encoding the entire completed URL is another mistake because it also escapes separators that belong to URL structure. urljoin has its own absolute-URL behavior, so do not treat an arbitrary supplied path as a harmless fixed-origin suffix without validating that requirement.

Counterexample: a runnable policy defect

python

from urllib.parse import urlencode

def catalogue_url(country, cursor=None):
    if not isinstance(country, str) or country.strip().upper() not in {"US", "IL", "UA", "CA"}:
        raise ValueError("country required")
    query = {"country": country.strip().upper()}
    if cursor is not None:
        if not isinstance(cursor, str) or not 1 <= len(cursor) <= 80:
            raise ValueError("bounded cursor required")
        query["cursor"] = cursor
    return "https://jobs.example.test/v1/jobs?" + "&".join(f"{key}={value}" for key, value in query.items())
Reasoning
This runnable counterexample violates the declared contract; the companion lab requires it to fail.

Independent practice: API client task

Add a limit query parameter, defaulting to two and accepting only exact integers from one through five.

Add a limit query parameter, defaulting to two and accepting only exact integers from one through five. Append it after any supplied cursor and preserve the existing cursor encoding.

Begin with the default URL and then a cursor containing a space and ampersand. This task adds one validated query field; it does not change the base origin or introduce credentials into the URL.

Step 1: Add a bounded integer limit to the query. Step 2: Keep opaque cursor characters encoded as values.

Practice starter: solve before reading the solution

python

from urllib.parse import urlencode

def catalogue_url(country, cursor=None):
    # Remove this placeholder, then adapt the retained baseline:
    # Add a bounded integer limit to the query.
    raise NotImplementedError
    if not isinstance(country, str) or country.strip().upper() not in {"US", "IL", "UA", "CA"}:
        raise ValueError("country required")
    query = {"country": country.strip().upper()}
    if cursor is not None:
        if not isinstance(cursor, str) or not 1 <= len(cursor) <= 80:
            raise ValueError("bounded cursor required")
        query["cursor"] = cursor
    return "https://jobs.example.test/v1/jobs?" + urlencode(query)
Reasoning
Add a bounded integer limit to the query. Keep opaque cursor characters encoded as values.
  • Hint 1: Reject bool with type(limit) is int before checking the range.
  • Hint 2: Add limit to the mapping before calling urlencode once.

Solution, tests and trade-offs

The lab compares exact URLs and includes a cursor whose reserved characters expose raw concatenation.

The lab compares exact URLs and includes a cursor whose reserved characters expose raw concatenation. Practice cases reject a boolean limit even though bool subclasses int in Python.

The fixed example.test hostname is a documentation fixture, not an endpoint the checker contacts. The HTTP lessons use a separate temporary loopback server so requests remain under the lab’s control and cannot burden a production API.

Practice solution and acceptance checks

python

from urllib.parse import urlencode

def catalogue_url(country, cursor=None, limit=2):
    if not isinstance(country, str) or country.strip().upper() not in {"US", "IL", "UA", "CA"}:
        raise ValueError("country required")
    query = {"country": country.strip().upper()}
    if cursor is not None:
        if not isinstance(cursor, str) or not 1 <= len(cursor) <= 80:
            raise ValueError("bounded cursor required")
        query["cursor"] = cursor
    if type(limit) is not int or not 1 <= limit <= 5:
        raise ValueError("limit must be 1..5")
    query["limit"] = limit
    return "https://jobs.example.test/v1/jobs?" + urlencode(query)
Reasoning
Reject bool with type(limit) is int before checking the range. Add limit to the mapping before calling urlencode once.

Interview reasoning and next step

Explain which pieces of a URL are trusted structure and which are values.

Explain which pieces of a URL are trusted structure and which are values. A production client may also need origin validation, redirect restrictions, signed parameters and a careful cache key.

Those are not supplied automatically by query encoding. Retain the normalized country and optional cursor as explicit inputs; they will later help define pagination state and distinguish cache entries for different requested populations.

Optional stretch — after the core task works: Support repeated filters with a deliberately specified doseq policy.

Computer practice: https://applydjinn.com/typorithm/python-automation-lab-20261105.zip. Run verify.py using your selected Python interpreter, then adapt the retained exercise baseline and check it with --practice followed by the lesson id. The standard-library lab uses owned temporary 127.0.0.1 loopback servers, fictional pages and disposable files; it never scans production feeds or opens the working ApplyDjinn database.

Build one fixed-origin URL while preserving the boundary between query syntax and caller-supplied values.