Skip to main content

SmartNotes Package Capstone

If you're new to programming

This is a timed refactoring challenge. Refactoring means reorganizing code without changing what it does. You move existing SmartNotes code into a proper package structure. No new features, just better organization. The key constraint: all existing tests must still pass after the reorganization. The <details> blocks have hints if you get stuck.

If you've coded before

25 minutes. Refactor SmartNotes from flat files into a package. Design the module architecture, write the __init__.py public API, create the __main__.py entry point, and verify everything with pyright and pytest.

Emma looks at the SmartNotes project directory. Files everywhere: Note class, search functions, FileManager, tests, scripts. "You have the code. You have the tests. Now organize it into something a colleague could navigate without asking you where things are."

She sets the timer. "Twenty-five minutes. The tests are your safety net. If they pass after the refactoring, you did not break anything."


The Problem

Reorganize your SmartNotes code into this package structure:

project/
├── smartnotes/
│ ├── __init__.py # Public API
│ ├── __main__.py # Entry point
│ ├── models.py # Note dataclass
│ ├── search.py # search_notes, filter_by_tag, sort_by_word_count
│ └── storage.py # FileManager class
├── tests/
│ ├── test_models.py # Tests for Note
│ ├── test_search.py # Tests for search functions
│ └── test_storage.py # Tests for FileManager
└── pyproject.toml # (optional) package metadata

Your deliverables:

DeliverableVerification
smartnotes/ package with 5 filesls smartnotes/ shows all files
__init__.py exports Note, search_notes, FileManagerpython -c "from smartnotes import Note, search_notes, FileManager"
__main__.py runs a demopython -m smartnotes prints output
All tests passuv run pytest tests/ -v all GREEN
No circular importsuv run pyright smartnotes/ zero errors

Start the timer.


Step 1: Design the Architecture (5 minutes)

Before moving any files, draw the dependency graph:

__init__.py  →  models.py  (Note)
→ search.py (search_notes, filter_by_tag)
→ storage.py (FileManager)

search.py → models.py (needs Note type)
storage.py → models.py (needs Note type)

__main__.py → __init__.py (uses public API)

Verify: all arrows point down toward models.py. No cycles.

Decide what is public (exported in __init__.py) and what is internal:

Public (exported)Internal (not exported)
Notenote_from_csv_row
search_notesfilter_by_tag
FileManagersort_by_word_count
exclude_drafts
Hint: why are filter functions internal?

filter_by_tag and sort_by_word_count are utility functions used by search_notes and FileManager. External users call search_notes (which uses filters internally). Tests import the internal functions directly from smartnotes.search for unit testing.


Step 2: Create the Package (5 minutes)

Create the directory structure and move your code:

mkdir -p smartnotes tests

Create each file. The bodies should contain the code you have already written in Chapters 57, 58, and 62. If you are starting fresh, write stubs (placeholder functions with ... as the body, like you practiced in the TDG cycle).

smartnotes/models.py: The Note dataclass only.

smartnotes/search.py: Search and filter functions, importing Note from smartnotes.models.

smartnotes/storage.py: FileManager class, importing Note from smartnotes.models.

smartnotes/__init__.py: Re-exports the public API.

"""SmartNotes: a note-taking application with search and file persistence."""

from smartnotes.models import Note
from smartnotes.search import search_notes
from smartnotes.storage import FileManager

__all__ = ["Note", "search_notes", "FileManager"]

smartnotes/__main__.py: A demo that exercises all features.

Run uv run pyright smartnotes/ after creating each file. Fix type errors as they appear.


Step 3: Migrate Tests (5 minutes)

Update your test imports to point to the new package:

Before:

from smartnotes_search import Note, search_notes
from file_manager import FileManager

After:

from smartnotes import Note, search_notes, FileManager
# OR for internal functions:
from smartnotes.search import filter_by_tag

Move test files into the tests/ directory. Run:

uv run pytest tests/ -v

Every test should pass. If any fail, the import paths need fixing, not the logic.

Hint: common test migration issues
  • ModuleNotFoundError: The import path changed. Update from old_module import ... to from smartnotes.module import ....
  • Tests that create files: Tests using tmp_path should still work. Tests using hardcoded paths may need updating.
  • Missing fixtures: If test helper functions (like sample_notes()) were defined in the old test files, copy them into the new test files.

Step 4: Verify (3 minutes)

Run the full verification stack:

uv run ruff check smartnotes/
uv run pyright smartnotes/
uv run pytest tests/ -v
uv run python -m smartnotes

All four commands should succeed:

  • ruff: no lint errors
  • pyright: no type errors
  • pytest: all tests pass
  • python -m smartnotes: demo runs and prints output

Step 5: Review (2 minutes)

Check your package:

  1. No circular imports: Does uv run python -c "import smartnotes" work without errors?
  2. Clean public API: Does from smartnotes import Note, search_notes, FileManager import exactly the three names you intended?
  3. Internal access works: Can tests still import from smartnotes.search import filter_by_tag?
  4. No duplicated code: Is the Note class defined in exactly one place (models.py)?

Try With AI

Opening Claude Code

If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.

Prompt 1: Review My Package Structure

Here is my SmartNotes package:

smartnotes/
├── __init__.py
├── __main__.py
├── models.py
├── search.py
└── storage.py

And here are the imports in each file:

[paste the import section of each file]

Review the structure. Are the dependencies clean?
Any suggestions for improvement?

What you're learning: Package review catches structural issues that individual file reviews miss. The AI evaluates the overall architecture, not just the code in each file.

Prompt 2: Add pyproject.toml

I want to make my SmartNotes package installable with pip.
What do I need in a pyproject.toml file? Show me a minimal
configuration that lets someone run:
pip install .
python -m smartnotes

What you're learning: pyproject.toml is the standard Python package metadata file. This is a preview of how real Python packages are distributed.

Prompt 3: Rate My Refactoring

I refactored SmartNotes from flat files into a package.
Here is what I did:

1. Created smartnotes/ directory with __init__.py
2. Moved Note to models.py
3. Moved search functions to search.py
4. Moved FileManager to storage.py
5. Created __main__.py entry point
6. Updated all test imports
7. All tests pass, pyright clean

Rate my refactoring. What would a senior developer
change?

What you're learning: Self-evaluation after refactoring builds judgment. The AI acts as a code reviewer who evaluates your structural decisions, not just whether the code works.


James runs the full verification stack. All green. He runs python -m smartnotes. The demo prints three notes and a search result. He runs the tests. Twelve tests, all passing.

"I did not write a single new function," he says. "I moved code. Changed imports. Updated tests. The logic is identical."

"That is refactoring," Emma says. "Restructuring code without changing behavior. The tests prove the behavior did not change. If any test broke during the move, you would know exactly where the import went wrong."

She looks at the package structure. "Your SmartNotes has grown from a single function to a package with five modules, three public exports, and a runnable entry point. The TDG cycle works for functions, classes, and now packages. The building blocks scale."

James thinks about the code inside the package. "The search function uses a loop to filter notes. The FileManager uses loops to process files. Everything is loop and if-statement. Is there a more expressive way to write these transformations?"

"Comprehensions," Emma says. "List comprehensions, dict comprehensions, generators. Python's most powerful syntax for transforming data. Chapter 64 teaches them."

"Like batch processing at the warehouse," James says. "Instead of handling one item at a time, you describe what you want and the system processes the whole batch."

"Exactly. And when the batch is too large for memory, generators process it one piece at a time without loading everything at once."