Skip to main content
Learning Center
Python Programming

Application Design and Contracts

Protocols and Injected Dependencies

Separate policy from infrastructure with structural interfaces that remain simple to fake in tests.

Lesson 2 of 6 in the recommended order · About 25 min (estimate)

On this page

Outcome

Define a Protocol and inject an implementation without coupling domain logic to a concrete client.

Why it matters

At this level, code is judged by how safely it changes under load, failure, and team ownership. The technique in this lesson makes an important boundary visible enough to test and review.

Concept

A Protocol describes the behavior a collaborator must provide. Business logic can accept that behavior through a constructor or function argument, while production and tests supply different implementations. This is structural guidance for a static type checker; Python does not automatically validate the full protocol when the argument is passed at runtime.

Read the code

from typing import Protocol

class Clock(Protocol):
    def today(self) -> str: ...

class FixedClock:
    def today(self) -> str:
        return "2026-09-05"

def report_name(clock: Clock) -> str:
    return f"report-{clock.today()}.csv"

print(report_name(FixedClock()))

Read from the public behavior inward: identify the input boundary, the decision, and the observable result before studying syntax.

One required behavior, one supplied clock

1. Static shape: Clock

Required method
today() -> str
Function boundary
report_name(clock: Clock)

A static type checker can compare the supplied object with this shape. FixedClock needs no Clock base class.

2. Runtime collaborator: FixedClock

Caller supplies FixedClock()

report_name calls clock.today()

Python invokes the method on the actual supplied object. The Clock annotation alone does not validate the full protocol at call time.

3. Observable boundary

report_name returns filename text

print displays that text

The worked code does not read system time or write a file. Predict the exact printed string before opening the answer.

The seam is the today() behavior. FixedClock supplies it structurally; the type annotation guides static checking while runtime uses the actual method. This figure leaves the exact printed date for the prediction.

Predict the output

Predict the exact output before running the example.

Check your prediction

It prints report-2026-09-05.csv. report_name depends on behavior, not on system time or a particular class.

Modify the code

Change one valid input into the nearest invalid or overloaded case. Write down which layer should reject it and what the caller should observe.

Review the change

Keep the failure at the narrowest boundary that owns the rule. Preserve a stable return value or exception contract so callers do not need to inspect implementation details.

Debug the bug

Creating the real client inside report_name hides the dependency and makes failure cases hard to test. Passing it in makes ownership and lifetime explicit.

Try it yourself

Complete the focused implementation and run its deterministic checks.

Loading this exercise…

Practical challenge (optional)

Define a Store protocol with save(name, text), then implement a memory store and test a function without touching disk.

Sign in to track your progress on this exercise.

AI collaboration

Checkpoint

  1. Why does FixedClock satisfy Clock without inheriting from it?
  2. Does the annotation clock: Clock perform full runtime validation when the function is called?
  3. What testability problem appears if report_name constructs the real clock or client itself?
Answers
  1. Protocols use structural typing: a static type checker sees that FixedClock provides the required today method.
  2. No. Ordinary annotations guide tools; they do not automatically check the complete protocol at runtime.
  3. The dependency and its lifetime become hidden, so a deterministic fake and failure cases are difficult to supply.

Sign in to track your progress on this exercise.

Summary and next step

You made the boundary explicit, predicted its behavior, tested a deterministic implementation, and examined its failure mode. Continue to the next lesson to combine this technique with a wider application or production constraint.

learning.goultergroup.com

The interactive parts of this page have not loaded. Reading and links still work; reload the page to try again.