A wrong Python version or a missing dependency can interrupt development before the actual work begins. Then there are the smaller interruptions: finding the right command, untangling file copies, and setting up the same checks for another project.

While preparing a technical knowledge-sharing session, I revisited the practices that have helped make my own workflows more reliable. Three stood out: reproducible environments, version control, and automation. Their value comes from using them consistently.

Start with the terminal

I find built-in help a useful starting point. It describes the tool installed on your machine, which is often more useful than a command copied from an old tutorial.

uv --help
git --version
git help commit

On systems with manual pages, man <command> is useful too. You do not need to memorise everything. Understanding a few commands makes troubleshooting easier, even if most of your work happens in VS Code.

Make the environment reproducible

A project that runs on one laptop can still fail on the next. uv brings Python versions, virtual environments, dependencies, and lockfiles into one workflow. That removes several setup decisions each time you switch projects.

Install once

For Windows, install Git for Windows, then install uv from PowerShell:

winget install --id=astral-sh.uv -e

Open a new terminal and verify both tools:

git --version
uv --version

If a command is not recognised, restart the terminal or editor to pick up the updated PATH. Other installation methods are in the uv documentation.

Join an existing project

The aim here is to recreate the project's established environment. Clone the repository and enter its folder, replacing the placeholders:

git clone <REPOSITORY_URL>
cd <PROJECT_FOLDER>

For a uv project with pyproject.toml, uv.lock, and a .python-version file:

uv python install
uv sync --locked

If there is no .python-version, check the README and requires-python in pyproject.toml. For a project that supports Python 3.12:

uv python install 3.12
uv sync --locked --python 3.12

--locked checks that the lockfile matches the project metadata and fails if it is missing or outdated. When intentionally changing dependencies, use uv add or update the metadata and run uv sync, then review the lockfile changes. For a repository using another dependency manager, follow its setup instructions.

Start something new

For a new project, these commands create the scaffold, add dependencies, and run the starter script:

uv init my-project --python 3.12
cd my-project
uv add pandas numpy requests
uv run main.py

Choose a supported Python version and add only the packages you need. Later, use uv add to record new dependencies and uv run python main.py to run your code in the project environment without activating it manually.

Commit pyproject.toml, uv.lock, and .python-version alongside your code. Keep .venv/ local; another developer can recreate it with uv sync --locked.

The lesson: Define the environment in project files. The next developer should not have to reconstruct setup steps from someone else's memory.

If setup fails, start with the specific error: check the Python requirement, dependency constraints, and lockfile. If Windows blocks an executable, verify its source and follow your organisation's approved installation process.

Give your code a history

Once the environment is reproducible, the next question is how to change the code safely. Git provides history, branches, and a way to review changes. GitHub is one platform for hosting those repositories and collaborating around them.

git switch -c feature/my-change
# Make changes, then inspect them
git status
git diff
git add main.py
git commit -m "Handle missing account balances"
git push -u origin feature/my-change

Adapt the branch, file, and message to your change. I find focused commits especially useful: a change with one clear purpose is easier to understand, review, and recover. A pull request then gives colleagues a place to discuss it before merging.

VS Code's Source Control panel and extensions such as GitLens make this convenient. Understanding the working tree, staging area, and commits still matters: it helps you know what the interface is about to do.

Merge conflicts are overlapping changes that need a decision. Inspect both versions, resolve the intended behaviour, and run the relevant checks.

The lesson: Version control makes changes understandable, reviewable, and recoverable. A useful commit records an intention as well as a snapshot.

Automate what keeps repeating

Project structure, formatting, and routine checks are useful conventions to establish once. Repeating those decisions in every project adds little value.

Give projects a familiar structure

Cookiecutter Data Science provides a starting structure for code, notebooks, data, and outputs. Its v2 workflow can be launched with:

uvx --from cookiecutter-data-science ccds

Review the generated configuration and keep the parts your project needs. A template should make the repository easier to navigate.

Make checks repeatable

Ruff handles Python linting and formatting. Add it as a development dependency so the lockfile captures its version:

uv add --dev ruff
uv run ruff check .
uv run ruff format .

Keep the configuration in the repository and run the same checks in continuous integration. That gives local development and pull requests a shared standard.

Keep local files local

A .gitignore keeps untracked environments, caches, and local secrets out of ordinary staging operations:

.venv/
__pycache__/
*.pyc
.env
data/
.DS_Store

Adapt it to the repository: small datasets or test fixtures may belong in Git. Ignore rules do not remove already tracked files, and ignoring .env does not replace secret management.

The lesson: Automate repeated checks and conventions so more attention can go to decisions that require judgment.

Keep the workflow as simple as the project allows

For a small analytics application, these habits answer useful questions: which Python version does it need, which packages were installed, what changed, and were the checks run? Data and AI projects may also need to track datasets, configuration, and model versions; a Git commit alone cannot identify every input to a result.

My preference is to introduce a tool when it removes a real source of friction. Recreate the environment, work on a branch, run the checks, and review the change. Extend that workflow when the project needs more.

Good workflows are invisible. Bad workflows become meetings.

Further learning