From ca46c11c9bb8ff4b00cd25c30a11062fa9d5a628 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Fri, 6 Nov 2026 12:48:55 +0800 Subject: [PATCH 1/3] Add first-class functions and decorators with tests Task: MIL-004#1 Task: MIL-004#2 Task: MIL-004#3 --- src/flask_day_54/domain/constants.py | 23 +++++++ src/flask_day_54/domain/decorators.py | 69 +++++++++++++++++++ .../domain/first_class_functions.py | 66 ++++++++++++++++++ tests/unit/test_decorators.py | 43 ++++++++++++ tests/unit/test_first_class_functions.py | 42 +++++++++++ 5 files changed, 243 insertions(+) create mode 100644 src/flask_day_54/domain/decorators.py create mode 100644 src/flask_day_54/domain/first_class_functions.py create mode 100644 tests/unit/test_decorators.py create mode 100644 tests/unit/test_first_class_functions.py diff --git a/src/flask_day_54/domain/constants.py b/src/flask_day_54/domain/constants.py index 2756463..196a916 100644 --- a/src/flask_day_54/domain/constants.py +++ b/src/flask_day_54/domain/constants.py @@ -38,3 +38,26 @@ CLI_COMMANDS: Final[tuple[tuple[str, str, str, bool, str], ...]] = ( NO_CONFIRMATION_WARNING, ), ) + +## @brief Message returned by the nested function of lecture 5. +NESTED_FUNCTION_MESSAGE: Final[str] = "Hello from the nested function" + +## @brief Demo line templates of lecture 5; `{value}` is the printed result. +ADD_DEMO_LINE: Final[str] = "calculate(add, 2, 3) = {value}" +SUBTRACT_DEMO_LINE: Final[str] = "calculate(subtract, 5, 3) = {value}" +OUTER_DEMO_LINE: Final[str] = "outer_function() = {value}" +RETURNED_DEMO_LINE: Final[str] = "returned nested function() = {value}" + +## @brief HTML tag names used by the lecture 6 decorators. +BOLD_TAG: Final[str] = "b" +EMPHASIS_TAG: Final[str] = "em" +UNDERLINE_TAG: Final[str] = "u" + +## @brief Template wrapping text in an HTML tag. +HTML_TAG_TEMPLATE: Final[str] = "<{tag}>{text}" + +## @brief Demo line templates of lecture 6; `{value}` is the printed result. +PLAIN_DEMO_LINE: Final[str] = "plain: {value}" +BY_HAND_DEMO_LINE: Final[str] = "by hand, make_bold(plain_hello)(): {value}" +AT_SYNTAX_DEMO_LINE: Final[str] = "@ syntax, say_hello(): {value}" +NAME_DEMO_LINE: Final[str] = "say_hello.__name__: {value}" diff --git a/src/flask_day_54/domain/decorators.py b/src/flask_day_54/domain/decorators.py new file mode 100644 index 0000000..faa59f8 --- /dev/null +++ b/src/flask_day_54/domain/decorators.py @@ -0,0 +1,69 @@ +## @file decorators.py +# @brief Lecture 6 demo of decorators and the `@` syntax. +"""A decorator wraps a function to add behaviour without editing it.""" + +from collections.abc import Callable +from functools import wraps + +from flask_day_54.domain.constants import ( + AT_SYNTAX_DEMO_LINE, + BOLD_TAG, + BY_HAND_DEMO_LINE, + EMPHASIS_TAG, + GREETING, + HTML_TAG_TEMPLATE, + NAME_DEMO_LINE, + PLAIN_DEMO_LINE, + UNDERLINE_TAG, +) + + +def _tag_decorator(tag: str) -> Callable[[Callable[[], str]], Callable[[], str]]: + """Build a decorator that wraps the returned text in the given HTML tag.""" + + def decorator(func: Callable[[], str]) -> Callable[[], str]: + @wraps(func) + def wrapper() -> str: + return HTML_TAG_TEMPLATE.format(tag=tag, text=func()) + + return wrapper + + return decorator + + +## @brief Wrap the text of a function in `` tags. +make_bold = _tag_decorator(BOLD_TAG) + +## @brief Wrap the text of a function in `` tags. +make_emphasis = _tag_decorator(EMPHASIS_TAG) + +## @brief Wrap the text of a function in `` tags. +make_underlined = _tag_decorator(UNDERLINE_TAG) + + +## @brief Greeting with the three decorators stacked; the nearest applies first. +@make_bold +@make_emphasis +@make_underlined +def say_hello() -> str: + """Return the greeting.""" + return GREETING + + +## @brief The same greeting without any decorator. +def plain_hello() -> str: + """Return the greeting.""" + return GREETING + + +## @brief Print the lecture 6 demo. +def main() -> None: + """Print the plain, hand-decorated and `@` decorated greeting.""" + print(PLAIN_DEMO_LINE.format(value=plain_hello())) + print(BY_HAND_DEMO_LINE.format(value=make_bold(plain_hello)())) + print(AT_SYNTAX_DEMO_LINE.format(value=say_hello())) + print(NAME_DEMO_LINE.format(value=say_hello.__name__)) + + +if __name__ == "__main__": + main() diff --git a/src/flask_day_54/domain/first_class_functions.py b/src/flask_day_54/domain/first_class_functions.py new file mode 100644 index 0000000..2bcd6f9 --- /dev/null +++ b/src/flask_day_54/domain/first_class_functions.py @@ -0,0 +1,66 @@ +## @file first_class_functions.py +# @brief Lecture 5 demo of functions as first-class objects. +"""Functions can be passed as arguments, nested and returned like any value.""" + +from collections.abc import Callable + +from flask_day_54.domain.constants import ( + ADD_DEMO_LINE, + NESTED_FUNCTION_MESSAGE, + OUTER_DEMO_LINE, + RETURNED_DEMO_LINE, + SUBTRACT_DEMO_LINE, +) + + +## @brief Add two numbers. +def add(n1: float, n2: float) -> float: + """Return the sum of the two numbers.""" + return n1 + n2 + + +## @brief Subtract the second number from the first. +def subtract(n1: float, n2: float) -> float: + """Return the first number minus the second.""" + return n1 - n2 + + +## @brief Apply a function passed as argument to two numbers. +# @param func The function to call with `n1` and `n2`. +def calculate(n1: float, n2: float, func: Callable[[float, float], float]) -> float: + """Return `func(n1, n2)`.""" + return func(n1, n2) + + +## @brief Call a function that is defined inside this one. +def outer_function() -> str: + """Return the result of the nested function, reachable only in here.""" + + def nested_function() -> str: + return NESTED_FUNCTION_MESSAGE + + return nested_function() + + +## @brief Hand the nested function to the caller. +def returns_nested_function() -> Callable[[], str]: + """Return the nested function so it can be stored and called later.""" + + def nested_function() -> str: + return NESTED_FUNCTION_MESSAGE + + return nested_function + + +## @brief Print the lecture 5 demo. +def main() -> None: + """Print the results of the first-class function examples.""" + print(ADD_DEMO_LINE.format(value=calculate(2, 3, add))) + print(SUBTRACT_DEMO_LINE.format(value=calculate(5, 3, subtract))) + print(OUTER_DEMO_LINE.format(value=outer_function())) + stored = returns_nested_function() + print(RETURNED_DEMO_LINE.format(value=stored())) + + +if __name__ == "__main__": + main() diff --git a/tests/unit/test_decorators.py b/tests/unit/test_decorators.py new file mode 100644 index 0000000..79e3323 --- /dev/null +++ b/tests/unit/test_decorators.py @@ -0,0 +1,43 @@ +from collections.abc import Callable + +from flask_day_54.domain.decorators import ( + make_bold, + make_emphasis, + make_underlined, + plain_hello, + say_hello, +) + + +def test_make_bold_wraps_text_in_b_tags() -> None: + assert make_bold(plain_hello)() == "Hello, World!" + + +def test_make_emphasis_wraps_text_in_em_tags() -> None: + assert make_emphasis(plain_hello)() == "Hello, World!" + + +def test_make_underlined_wraps_text_in_u_tags() -> None: + assert make_underlined(plain_hello)() == "Hello, World!" + + +def test_say_hello_applies_stacked_decorators_nearest_first() -> None: + assert say_hello() == "Hello, World!" + + +def test_plain_hello_is_not_decorated() -> None: + assert plain_hello() == "Hello, World!" + + +def test_decorator_keeps_name_and_docstring_of_wrapped_function() -> None: + assert say_hello.__name__ == "say_hello" + assert say_hello.__doc__ == "Return the greeting." + + +def test_decorator_wraps_function_returning_another_string() -> None: + def goodbye() -> str: + return "Bye" + + decorated: Callable[[], str] = make_bold(goodbye) + + assert decorated() == "Bye" diff --git a/tests/unit/test_first_class_functions.py b/tests/unit/test_first_class_functions.py new file mode 100644 index 0000000..52ee485 --- /dev/null +++ b/tests/unit/test_first_class_functions.py @@ -0,0 +1,42 @@ +from flask_day_54.domain import first_class_functions +from flask_day_54.domain.first_class_functions import ( + add, + calculate, + outer_function, + returns_nested_function, + subtract, +) + + +def test_add_returns_sum_of_two_numbers() -> None: + assert add(2, 3) == 5 + + +def test_subtract_returns_difference_of_two_numbers() -> None: + assert subtract(5, 3) == 2 + + +def test_calculate_applies_add_when_add_is_passed() -> None: + assert calculate(2, 3, add) == 5 + + +def test_calculate_applies_subtract_when_subtract_is_passed() -> None: + assert calculate(5, 3, subtract) == 2 + + +def test_calculate_applies_lambda_when_lambda_is_passed() -> None: + assert calculate(4, 3, lambda n1, n2: n1 * n2) == 12 + + +def test_outer_function_returns_nested_function_result() -> None: + assert outer_function() == "Hello from the nested function" + + +def test_returned_nested_function_is_callable_independently() -> None: + stored = returns_nested_function() + + assert stored() == "Hello from the nested function" + + +def test_nested_function_is_not_a_module_attribute() -> None: + assert not hasattr(first_class_functions, "nested_function") -- 2.54.0 From 6dac2be41a1857a8459520ece1f61c8683f79912 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Fri, 6 Nov 2026 12:48:59 +0800 Subject: [PATCH 2/3] Add screenshots for lectures 5 and 6, README section and code review RC-018 --- README.md | 8 +++ docs/artifact-registry.md | 2 +- docs/images/lecture-05.png | Bin 0 -> 11628 bytes docs/images/lecture-06.png | Bin 0 -> 11967 bytes docs/images/sources/lecture-05.html | 10 +++ docs/images/sources/lecture-06.html | 10 +++ ...c-018-code-mil-004-functions-decorators.md | 61 ++++++++++++++++++ 7 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 docs/images/lecture-05.png create mode 100644 docs/images/lecture-06.png create mode 100644 docs/images/sources/lecture-05.html create mode 100644 docs/images/sources/lecture-06.html create mode 100644 docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md diff --git a/README.md b/README.md index 7e5962e..8217cb9 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,14 @@ Each lecture has its own screenshot in `docs/images/` (made by `docs/images/capt ![Lecture 4: __name__ and __main__](docs/images/lecture-04.png) +**Lecture 5: functions are first-class objects.** A function can be passed as an argument (`calculate(add, 2, 3)`), defined inside another function and returned from it. See `flask_day_54.domain.first_class_functions`; run it with `python -m flask_day_54.domain.first_class_functions`. + +![Lecture 5: functions as first-class objects](docs/images/lecture-05.png) + +**Lecture 6: decorators and the `@` syntax.** `@make_bold` above a function is shorthand for `say_hello = make_bold(say_hello)`. See `flask_day_54.domain.decorators`; run it with `python -m flask_day_54.domain.decorators`. + +![Lecture 6: decorators](docs/images/lecture-06.png) + ## 📋 Requirements - Python 3.13 or newer, because the project sets `requires-python = ">=3.13"` in `pyproject.toml`. diff --git a/docs/artifact-registry.md b/docs/artifact-registry.md index 4235e9a..818301f 100644 --- a/docs/artifact-registry.md +++ b/docs/artifact-registry.md @@ -18,7 +18,7 @@ document of a type. `Primary File` may contain a glob (e.g. | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0002 | | DICT | Domain Dictionary | docs/dictionary.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | -| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 018 | +| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 019 | ## Languages diff --git a/docs/images/lecture-05.png b/docs/images/lecture-05.png new file mode 100644 index 0000000000000000000000000000000000000000..5bff5ca276a615aee4d28e179cec0631f247e087 GIT binary patch literal 11628 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/images/lecture-06.png b/docs/images/lecture-06.png new file mode 100644 index 0000000000000000000000000000000000000000..c23fdfebb1766f88012b869a6593ef7fceb35d44 GIT binary patch literal 11967 zcmeHtXH-+^yKmG{XQYkeC

7DoP7VCjX}_2kI%+IeIIjQz02cM< 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 literal 0 HcmV?d00001 diff --git a/docs/images/sources/lecture-05.html b/docs/images/sources/lecture-05.html new file mode 100644 index 0000000..0c60d8c --- /dev/null +++ b/docs/images/sources/lecture-05.html @@ -0,0 +1,10 @@ +

Terminal - lecture 5: functions as first-class objects
$ python -m flask_day_54.domain.first_class_functions
+calculate(add, 2, 3) = 5
+calculate(subtract, 5, 3) = 2
+outer_function() = Hello from the nested function
+returned nested function() = Hello from the nested function
\ No newline at end of file diff --git a/docs/images/sources/lecture-06.html b/docs/images/sources/lecture-06.html new file mode 100644 index 0000000..dc1d74d --- /dev/null +++ b/docs/images/sources/lecture-06.html @@ -0,0 +1,10 @@ +
Terminal - lecture 6: decorators and the @ syntax
$ python -m flask_day_54.domain.decorators
+plain: Hello, World!
+by hand, make_bold(plain_hello)(): <b>Hello, World!</b>
+@ syntax, say_hello(): <b><em><u>Hello, World!</u></em></b>
+say_hello.__name__: say_hello
\ No newline at end of file diff --git a/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md b/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md new file mode 100644 index 0000000..6e2ab90 --- /dev/null +++ b/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md @@ -0,0 +1,61 @@ +# Review of MIL-004 + +## Metadata +| Key | Value | +| --- | --- | +| ID | RC-018 | +| CrossReference | [MIL-004], [QC-PY-001] | + +## Version History +| Date | Status | Author | Reviewer | Change | Commit | +| --- | --- | --- | --- | --- | --- | +| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending | + +--- + +## Artifact Under Review + +- Instance reviewed: [MIL-004] +- Checklist used: [QC-PY-001] +- Scope: full review of the source and tests delivered by MIL-004 (src/, tests/) +- Language and domain: n/a +- Language reviewer: none (the reviewer reads English and knows the IT domain) +- Mode: autonomous, not independent (AI as stand-in for S01) + +## Checklist Results + +| # | Criterion | Status | Evidence/Notes | +| --- | --- | --- | --- | +| 1 | Packages, modules, functions, variables, classes and constants follow PEP 8 casing (`snake_case`, `PascalCase`, `UPPER_SNAKE`) | Pass | Modules, functions and constants use snake_case / UPPER_SNAKE; classes none or PascalCase. | +| 2 | Names state purpose in the domain's language; no unexplained abbreviations, no single-letter names outside tiny scopes | Pass | Names follow the lecture and the domain dictionary. | +| 3 | Code is produced by the project's formatter and passes its linter with no unexplained suppressions | Pass | `ruff format` leaves files unchanged and `ruff check` (E, F, I, B, UP) passes; configured in pyproject.toml. | +| 4 | Every function and method signature is type-annotated, including `-> None` | Pass | Every function signature is annotated, including `-> None`. | +| 5 | No bare `except:`, no swallowed exceptions; specific exceptions are raised and the cause is kept (`raise ... from`) | Pass | No bare except and no swallowed exception. | +| 6 | No mutable default arguments and no shadowed builtins | Pass | No mutable defaults; no builtin shadowed. | +| 7 | Files, locks and connections are managed with context managers | N-A | No file, lock or connection is opened. | +| 8 | Public modules, classes and functions have docstrings that say what, not how | Pass | Module and public names carry docstrings plus Doxygen comments. | +| 9 | Logging uses `logging`, not `print`; no secrets or personal data in log output | N-A | Console output of the demo entry points (print under the __main__ guard) is the program's result, not diagnostic logging; no secrets handled. | +| 10 | Classes and operations trace to the Design Class Diagram they implement; deviations are recorded | N-A | No DCD exists in this project; the layered layout is recorded in ADR-0001. | +| 11 | Tests exist for new behaviour, are named for the behaviour, and do not depend on order or the network | Pass | pytest tests named for behaviour; no order or network dependence. | +| 12 | Type checker runs in strict mode without errors; `Any` is justified in a comment | N-A | No type checker is configured (optional criterion). | +| 13 | Dependencies are declared and pinned in the project's dependency file, none unused | Pass | Dependencies are declared in pyproject.toml; none unused (Flask is the optional `web` extra). | +| 14 | Each piece of knowledge (a business rule, constant, format, validation or query) is defined in one place; code is merged only where it expresses the same knowledge, not where it merely looks alike | Pass | Each constant is defined once in the constants.py of its layer. | +| 15 | Imports point inward: business-rule modules import no framework, database, UI or delivery-mechanism code, and there are no import cycles | Pass | `domain` imports no framework or outer layer; imports point inward; no cycles. | +| 16 | SOLID holds: a class or module has one reason to change; new behaviour is added by extension, not by editing a type test in several places; subclasses honour the contract of their base; protocols are narrow; high-level code depends on protocols or abstract types and receives its implementations | Pass | Small modules with one reason to change; no type tests or subclass hierarchies. | +| 17 | Source is laid out in the layers and folders of the clean architecture folder structure (`domain`, `application`, `infrastructure`, `presentation` packages under `src/`, and `unit`, `integration` and `e2e` under `tests/`), in `snake_case`, and imports between the layers point inward | Pass | Layers domain, application, infrastructure, presentation under src/flask_day_54; tests in unit and e2e (ADR-0001); no layer merged. | +| 18 | File and folder names are English, contain no space and no character outside letters, digits, `-`, `_` and `.`, and follow the casing of the language for files | Pass | English snake_case file names without spaces. | + +## Overall Verdict + +Go — All applicable mandatory criteria pass. + +## Action Items + +| Action | Owner | Due | +| --- | --- | --- | +| None | S01 | - | + +--- + +[MIL-004]: ../../milestones/mil-004-functions-decorators.md +[QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md -- 2.54.0 From 690a05d07393370ab9ad3d806f6b4e031ec88b94 Mon Sep 17 00:00:00 2001 From: Jens Tirsvad Nielsen Date: Fri, 6 Nov 2026 12:49:01 +0800 Subject: [PATCH 3/3] Resolve pending commit links --- docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md b/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md index 6e2ab90..31e15bd 100644 --- a/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md +++ b/docs/sqa/reviews/rc-018-code-mil-004-functions-decorators.md @@ -9,7 +9,7 @@ ## Version History | Date | Status | Author | Reviewer | Change | Commit | | --- | --- | --- | --- | --- | --- | -| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | pending | +| 2026-11-06 | Proposed | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [6dac2be] | --- @@ -59,3 +59,4 @@ Go — All applicable mandatory criteria pass. [MIL-004]: ../../milestones/mil-004-functions-decorators.md [QC-PY-001]: ../../../framework/qc/code/qc-programming-python.md +[6dac2be]: https://git.tirsystem.com/Tirsvad-Udemy-100-days-of-code/054-flask/commit/6dac2be41a1857a8459520ece1f61c8683f79912 -- 2.54.0