Create your first Python project with uv
This Python uv tutorial walks you through building a text analysis tool that counts words, measures sentence length, and reports word frequency. No prior Python experience required; uv handles the Python install, project scaffoldingGenerate the initial file and folder structure for a new project. , and dependency managementInstalling, updating, and tracking the external packages your project needs. Includes resolving compatible versions, separating dev from production deps, and keeping installs reproducible. for you.
Want a broad tour of uv’s features before building something? See Getting started with uv. This tutorial instead builds one real thing end to end.
Prerequisites
Before we begin, make sure you have uv installed on your system. You can install it following the directions from the uv documentation.
Git is optional. uv does not install Git for you, but if Git is already on your PATH, uv init initializes a Git repository in the new project. The tutorial works either way.
Tip
You do not have to have Python installed on your computer to run this tutorial.
Creating a New Project
Let’s create a project called “text_analyzer” that will analyze text statistics like word frequency, sentence length, and readability scores:
$ uv init text_analyzer
Initialized project `text-analyzer` at `/path/to/text_analyzer`
$ cd text_analyzer
uv prints a single confirmation line. If you see error: project name '...' is not valid, the directory you tried to create already exists; pick a fresh name or remove the existing directory first.
Notice the new files uv created in the project: pyproject.toml, src/text_analyzer/__init__.py, README.md, a .python-version file pinning the interpreterThe program that reads and executes Python code. When you run "python3 hello.py", python3 is the interpreter.
, and a .gitignore. If Git is installed on your system, the directory is also a Git repository ready for its first commit. Without Git, uv skips that step but still creates the rest of the project.
Notice also that uv normalized the name: the directory is text_analyzer with an underscore, but the project is text-analyzer with a hyphen. Python module names use underscores; package and command names use hyphens.
Look at the generated pyproject.toml, which stores the project configuration:
[project]
name = "text-analyzer"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = []
[project.scripts]
text-analyzer = "text_analyzer:main"
[build-system]
requires = ["uv_build>=0.12.0,<0.13.0"]
build-backend = "uv_build"The [project.scripts] line is what makes text-analyzer runnable as a command: it points at the main function inside the text_analyzer module. The [build-system] table tells Python how to build the project into an installable package.
--no-package keeps the project layout flat (a main.py at the top level instead of a src/ package). This tutorial uses the default packaged layout.
Running the Starter Project
Before adding anything, run what uv just made. uv init generated a main function that prints a greeting, wired to the text-analyzer command:
$ uv run text-analyzer
Using CPython 3.14.6
Creating virtual environment at: .venv
Building text-analyzer @ file:///path/to/text_analyzer
Built text-analyzer @ file:///path/to/text_analyzer
Installed 1 package in 1ms
Hello from text-analyzer!
That is your first win. uv run installed Python if it was missing, created the virtual environmentAn isolated folder where Python installs packages for one project, keeping them separate from other projects and your system Python.
Learn more →
, built your project, installed it, and ran the code, all from one command. Use the hyphenated name here: uv run text_analyzer fails with error: Failed to spawn: text_analyzer. Now let’s turn it into a real text analyzer.
Adding Dependencies
If you see error: No pyproject.toml found in current directory or any parent directory, you ran the next commands outside the project. cd into text_analyzer first.
Our text analyzer will need some packages for data processing and analysis. Add pandas first:
$ uv add pandas
Resolved 6 packages in 4ms
Building text-analyzer @ file:///path/to/text_analyzer
Built text-analyzer @ file:///path/to/text_analyzer
Prepared 1 package in 2ms
Uninstalled 1 package in 0.42ms
Installed 5 packages in 18ms
+ numpy==2.5.1
+ pandas==3.0.5
+ python-dateutil==2.9.0.post0
+ six==1.17.0
~ text-analyzer==0.1.0 (from file:///path/to/text_analyzer)
The exact package versions and timings will differ on your machine, and the first time uv fetches a package you also see Downloading lines. The ~ text-analyzer line is your own project: because it declares a build system, every uv add rebuilds and reinstalls it alongside the new dependencyAn external package your project needs, listed in pyproject.toml so tools can install it automatically.
.
Notice the new uv.lock file in the project; the .venv/ directory appeared when you ran the starter project. The virtual environment holds the project’s Python interpreter and installed packages; the lockfileA file that records the exact version of every installed package, so everyone working on the project gets identical installs.
pins exact versions so anyone else can reproduce the environment with one command.
Add nltk for natural language processing:
$ uv add nltk
Resolved 13 packages in 4ms
Building text-analyzer @ file:///path/to/text_analyzer
Built text-analyzer @ file:///path/to/text_analyzer
Prepared 1 package in 2ms
Uninstalled 1 package in 0.41ms
Installed 7 packages in 6ms
+ click==8.4.2
+ defusedxml==0.7.1
+ joblib==1.5.3
+ nltk==3.10.0
+ regex==2026.7.19
~ text-analyzer==0.1.0 (from file:///path/to/text_analyzer)
+ tqdm==4.70.0
Each uv add updates pyproject.toml, refreshes uv.lock, and installs the package into .venv/. The pyproject.tomlThe standard configuration file for Python projects. Declares the project name, version, dependencies, build system, and tool settings in one place.
Learn more →
now includes these dependencies:
[project]
name = "text-analyzer"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"nltk>=3.10.0",
"pandas>=3.0.5",
]Creating the Project Structure
Let’s create a directory for our sample data:
mkdir dataLet’s create a sample text file to analyze. Create data/sample.txt with this content:
The quick brown fox jumps over the lazy dog. This pangram contains every letter of the English alphabet at least once. Pangrams are useful for testing fonts, keyboards, and printers. The five boxing wizards jump quickly! How vexingly quick daft zebras jump.Now let’s replace the contents of src/text_analyzer/__init__.py with our analysis code. That file is the module the text-analyzer command runs:
import pandas as pd
import nltk
from collections import Counter
from pathlib import Path
nltk.download('punkt_tab')
class TextAnalyzer:
"""A class for analyzing text statistics."""
def read_text(self, file_path):
"""Read text from a file."""
return Path(file_path).read_text()
def analyze_text(self, text):
"""Analyze text and return statistics."""
# Tokenize text into sentences and words
sentences = nltk.sent_tokenize(text)
words = nltk.word_tokenize(text.lower())
# Calculate basic statistics
word_count = len(words)
sentence_count = len(sentences)
avg_sentence_length = word_count / sentence_count
# Calculate word frequencies
word_freq = Counter(words)
most_common = word_freq.most_common(5)
# Create statistics dictionary
stats = {
"Total Words": word_count,
"Total Sentences": sentence_count,
"Average Sentence Length": round(avg_sentence_length, 2),
"Unique Words": len(word_freq),
}
# Create word frequency DataFrame
freq_df = pd.DataFrame(most_common, columns=['Word', 'Frequency'])
return stats, freq_df
def main():
# Initialize analyzer
analyzer = TextAnalyzer()
# Read and analyze sample text, relative to where you run the command
file_path = Path("data") / "sample.txt"
text = analyzer.read_text(file_path)
# Get analysis results
stats, word_freq = analyzer.analyze_text(text)
# Print results
print("\nText Statistics:")
for metric, value in stats.items():
print(f"{metric}: {value}")
print("\nMost Common Words:")
print(word_freq.to_string(index=False))The TextAnalyzer class reads text from a file, tokenizes it into sentences and words using NLTK, then computes statistics like word count and average sentence length. It also uses Counter to find the most common words and returns the results as both a dictionary and a pandas DataFrame.
There is no if __name__ == "__main__": block at the bottom. The [project.scripts] entry in pyproject.toml already points the text-analyzer command at main, so uv calls it for you.
Running the Project
Run the project from the text_analyzer directory so it finds data/sample.txt:
uv run text-analyzeruv run resolves any pending changes in pyproject.toml, makes sure .venv/ is up to date, and then runs the command against the project’s interpreter. If you bypass uv run and invoke your system python directly, you’ll likely see ModuleNotFoundError: No module named 'pandas' because your system Python isn’t using the project’s venv.
The first run also downloads the punkt_tab tokenizer NLTK needs. Expect output like this:
[nltk_data] Downloading package punkt_tab to /path/to/home/nltk_data...
[nltk_data] Unzipping tokenizers/punkt_tab.zip.
Text Statistics:
Total Words: 49
Total Sentences: 5
Average Sentence Length: 9.8
Unique Words: 40
Most Common Words:
Word Frequency
the 4
. 4
quick 2
, 2
jump 2The [nltk_data] lines disappear on subsequent runs because the tokenizer is cached in an nltk_data/ directory under your user home (~/nltk_data/ on macOS and Linux, %USERPROFILE%\nltk_data\ on Windows).
Adding Development Dependencies
Let’s add some development tools for testing and code quality. Add pytest first:
$ uv add --dev pytest
Resolved 18 packages in 123ms
Building text-analyzer @ file:///path/to/text_analyzer
Built text-analyzer @ file:///path/to/text_analyzer
Prepared 1 package in 2ms
Uninstalled 1 package in 0.49ms
Installed 6 packages in 13ms
+ iniconfig==2.3.0
+ packaging==26.2
+ pluggy==1.6.0
+ pygments==2.20.0
+ pytest==9.1.1
~ text-analyzer==0.1.0 (from file:///path/to/text_analyzer)
Then add Ruff:
$ uv add --dev ruff
Resolved 19 packages in 215ms
Building text-analyzer @ file:///path/to/text_analyzer
Built text-analyzer @ file:///path/to/text_analyzer
Prepared 1 package in 2ms
Uninstalled 1 package in 0.44ms
Installed 2 packages in 1ms
+ ruff==0.16.0
~ text-analyzer==0.1.0 (from file:///path/to/text_analyzer)
Notice that --dev lands these in a separate [dependency-groups] table instead of the main dependencies list. They get installed in .venv/ like any other package, but uv sync --no-dev will skip them, which matters when you build a slim Docker image or deploy to production.
Both tools now sit in the dev dependency group in pyproject.toml:
[dependency-groups]
dev = [
"pytest>=9.1.1",
"ruff>=0.16.0",
]Using Development Tools
Use the dev tools through uv run so they pick up the project’s venv automatically. If you call ruff directly without uv run, your shell either reports command not found: ruff or runs a different Ruff installed elsewhere on your machine.
Format the code with Ruff:
$ uv run ruff format .
1 file reformatted, 1 file left unchanged
Then run the linter with automatic fixes:
$ uv run ruff check --fix .
Found 1 error (1 fixed, 0 remaining).
The remaining error count drops to zero and Ruff exits cleanly. Open src/text_analyzer/__init__.py and notice that the import block has been reordered (standard-library imports first, third-party imports next, each group sorted alphabetically). That’s Ruff’s I001 rule auto-fixing the import order.
Next Steps
You built a project from scratch: scaffolded it, added dependencies, wrote code, and ran linting and formatting. Where to go next:
- Set up a complete Python project adds type checking, testing, and pre-commit hooks to a project like this one.
- Getting started with uv tours uv’s other features, including running one-off scripts and managing Python versions.
- Set up Ruff for formatting and checking your code goes deeper on the linter you just used.
- Setting up testing with pytest and uv covers writing and running your first tests.
- Modern Python Tooling Checklist is the reference companion: the defaults to apply on your next project.