I took a publishing break but not a coding one.
Since my last blog post, I’ve been deep in the trenches, building tools, breaking assumptions, refactoring logic, and learning how to code like a professional from the ground up. This isn’t just a return to blogging, it’s a recommitment to doing things right, from the beginning.
Most importantly, I’m doing all of this to instill good habits now, while the stakes are low. Even if I’m building solo, I don’t want to cut corners. Habits are hard to form later. So I’m treating my small projects like real ones with tests, structure, documentation, and consistency.
Why I Stopped Blogging (But Never Stopped Building)
In the months since my last published post, I went all-in on hands-on learning. Instead of writing about my progress, I journaled on and off but that was inconsistent. So I wanted to adopt a system that lets me document my journey inside pull requests, track it through conventional commits, and shape it through real feedback loops (tests, CI, and CLI usage).
I don’t want to just practice Python. I need to learn how to work like a developer.
I tackled classic projects and layered in modern practices:
- Built a blog in Django with login, ownership checks, error handling, and dummy data generation.
- Implemented secure user flows using
@login_required
, CSRF protection, and custom redirects. - Faced real-world errors like failed migrations and
HttpResponse
misfires and learned to debug them methodically. - Created a PR template to document future work, lessons, and next steps.
- Chose to journal in PRs rather than flood my blog with every small update.
That break was the best thing I could’ve done because now I have something worth sharing and a consistent workflow to grow with.
The State of My Toolbox: Before
I started MyToolbox as a catch-all folder for practice scripts from Python Crash Course and Impractical Python Projects. Back then, it looked like this:
└── toolbox/
├── hello.py
├── hello_lists.py
└── pig_latin.py
No structure. No tests. Just functional blobs of Python. It was useful but not sustainable.
I wanted more than working code. I wanted reliable, testable, professional code. So I upgraded.
How I Modernized the Project (And My Thinking)
Modernizing this project wasn’t just about cleaning up code it was about rewiring how I think about building software. I wanted a workflow that scales, mirrors professional environments, and encourages maintainable habits from the start. Each change below reflects a deeper shift in mindset.
1. Moved to a src/
Layout
Using a src/ structure mirrors how real-world Python packages are built and helps avoid import issues during testing. It’s a small change that builds better habits for production-ready code.
mkdir -p src/mytoolbox/utils
mv toolbox/cli_utlis.py src/mytoolbox/utils/
2. Created a Real pyproject.toml
Instead of juggling multiple config files and ad hoc dependency lists, I centralized everything into pyproject.toml. This made installation easier, enabled editable installs (pip install -e ‘.[dev]’), and set the foundation for clean dependency management and tool integration moving forward.
[project]
name = "mytoolbox"
version = "0.0.1"
requires-python = ">=3.10"
3. Pre-commit with Ruff + Black
I integrated pre-commit with ruff and black so that every commit would auto-lint and auto-format the code. This removed all style debates and guaranteed consistent formatting across the codebase before anything even hits Git.
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
hooks:
- id: ruff
- repo: https://github.com/psf/black
hooks:
- id: black
4. Testing, Coverage, and TDD
I’m using pytest and coverage.py to test core functions under the utils folder, while excluding exercises and side projects. The goal is to build a small, well-tested library I can trust.
The big shift came from enforcing a local coverage threshold:
[tool.coverage.report]
show_missing = true
fail_under = 85
include = ["src/mytoolbox/utils/*.py"]
This means I can’t commit unless coverage stays above 85%. It’s a double-edged sword great for building testing habits, but strict enough to make me think twice before skipping tests. It’s not full TDD yet, but it’s helping me get there.
5. CI/CD with GitHub Actions
To back up my local checks, I set up GitHub Actions to run tests and measure coverage on every push and pull request. This isn’t just automation it’s a second line of defense.
name: CI
on: [push, pull_request]
jobs:
tests:
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- run: pip install -e '.[dev]'
- run: pytest --cov=src/mytoolbox/utils
6. Better Docs and Commit History
Writing clear, consistent commit messages used to drive me crazy. Every time I’d stage changes, I had to stop and think: “Wait, how should I format this? Is this a fix? A chore? Do I need a scope?” It broke my flow and made me dread committing altogether.
To solve that, I adopted Conventional Commits using Commitizen. Now, the format is enforced and guided I just follow the prompts. No second-guessing. It removes friction while making my history far more readable and automatable.
In parallel, I created a pull request template that turns every PR into an async journal entry: what I changed, what I learned, and what comes next. Together, these tools give me:
- A consistent, searchable Git history
- The foundation for changelogs or automated releases
- A habit of reflection baked into my workflow
Clear commits and structured PRs aren’t just about polish they’re part of how I learn in public. Each PR is like a a blog draft with context baked in.
Big Picture Lessons
Habit | Why It Matters |
---|---|
src/ layout | Real-world structure, better imports |
Tests + TDD | Catches bugs, enforces purpose |
Pre-commit hooks | Automates style and sanity |
CI/CD | Trust your pipeline, not your memory |
Refactoring | Get it working, then get it clean |
Journaling in PRs | Blog with intention, not pressure |
CLI Tools | Build usable, not just runnable, tools |
What Projects I Worked on During This Journey
🧮 Word Counter Utility
Purpose — Normalizes input text and counts word frequency
Features
- Sorted frequency output
- Fully tested, including edge cases
⏱️ 10K Run Timing Calculator
Purpose — Calculates average time over multiple runs
Features
- Sentinel-controlled input loop
- Clean, formatted output
🔢 Hex-to-Decimal Converter (No int()
)
Purpose — Practice manual base conversion logic
Features
- Uses
ord()
and string indexing - Reinforces base math fundamentals
🔺 Name Triangle Generator
Purpose — String pattern creation and formatting
Features
- Explores
enumerate()
, slicing, and formatting - Builds triangle-like patterns from user input
🎮 Hangman (Terminal Game)
Purpose — Terminal-based game logic and user feedback
Features
- Refactored logic for clarity
- Win/loss detection and life tracking
- Polished CLI experience
⚙️ CLI Utilities + TDD Practice
Purpose — Build reusable input utilities with a testing-first mindset
Features
- Centralized CLI input logic
- Practiced writing tests before implementation
- Used
pytest.raises(SystemExit)
to validate exit handling
What’s Next for CodingTides
This is the first of many pillar posts. From now on:
- Weekly long-form blogs on projects, patterns, and philosophy
- Micro updates logged as PRs and commit messages
- More CLI tools, TDD practice, and useful beginner utilities
Thanks for reading, and let’s see what the tide brings tomorrow!
Join Me on My Journey
If you’d like to follow along, I’ll be sharing more about my learning process, projects, and insights here and on my GitHub repository. Feel free to connect — I’d love to hear about your coding journey too!