Skip to content

Commit 3049fe3

Browse files
committed
Initial commit: Create Python project starter template
1 parent 3a091de commit 3049fe3

File tree

9 files changed

+332
-2
lines changed

9 files changed

+332
-2
lines changed

.github/workflows/lint.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: Lint
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
lint:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v3
10+
- name: Set up Python
11+
uses: actions/setup-python@v4
12+
with:
13+
python-version: '3.8'
14+
- name: Install dependencies
15+
run: |
16+
python -m pip install --upgrade pip
17+
pip install ".[dev]"
18+
- name: Run ruff
19+
run: ruff check .
20+
- name: Run mypy
21+
run: mypy src tests

.github/workflows/test.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Test
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
test:
7+
runs-on: ubuntu-latest
8+
strategy:
9+
matrix:
10+
python-version: ['3.8', '3.9', '3.10', '3.11']
11+
steps:
12+
- uses: actions/checkout@v3
13+
- name: Set up Python ${{ matrix.python-version }}
14+
uses: actions/setup-python@v4
15+
with:
16+
python-version: ${{ matrix.python-version }}
17+
- name: Install dependencies
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install ".[dev]"
21+
- name: Run tests
22+
run: pytest

.gitignore

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual Environment
24+
venv/
25+
env/
26+
ENV/
27+
28+
# IDE
29+
.idea/
30+
.vscode/
31+
*.swp
32+
*.swo
33+
34+
# Project specific
35+
*.png
36+
*.csv
37+
!data/sample.csv

.pre-commit-config.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
repos:
2+
- repo: https://github.com/charliermarsh/ruff-pre-commit
3+
rev: v0.1.6
4+
hooks:
5+
- id: ruff
6+
args: [--fix]
7+
- id: ruff-format
8+
- repo: https://github.com/pre-commit/mirrors-mypy
9+
rev: v1.7.1
10+
hooks:
11+
- id: mypy
12+
additional_dependencies: [types-all]

README.md

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,93 @@
1-
# starter-repo
2-
An example starter repo for Python projects
1+
# Python Project Starter Repository
2+
3+
This repository serves as a template demonstrating Python best practices for data analysis projects. It includes examples of:
4+
- CSV data processing
5+
- Data visualization with matplotlib
6+
- Command-line argument parsing
7+
- Type annotations
8+
- Testing
9+
- Code quality tools
10+
- Continuous Integration
11+
12+
## Features
13+
14+
### 1. Data Processing and Visualization
15+
The main script ([src/starter_repo/plot_data.py](src/starter_repo/plot_data.py)) demonstrates:
16+
- Reading CSV files using pandas
17+
- Creating plots with matplotlib
18+
- Modern Python type annotations
19+
- Command-line argument parsing with argparse
20+
21+
Example usage:
22+
```bash
23+
# Install the package
24+
pip install .
25+
26+
# Create a plot from the sample data
27+
python -m starter_repo.plot_data data/sample.csv year population --title "Population Growth" -o population.png
28+
```
29+
30+
### 2. Testing
31+
The project uses pytest for testing. Test files are located in the [tests/](tests/) directory.
32+
33+
To run tests:
34+
```bash
35+
pip install ".[dev]" # Install development dependencies
36+
pytest
37+
```
38+
39+
### 3. Code Quality Tools
40+
This project uses several tools to maintain code quality:
41+
42+
#### Pre-commit Hooks
43+
We use [pre-commit](.pre-commit-config.yaml) with:
44+
- [Ruff](https://github.com/charliermarsh/ruff) for linting and formatting
45+
- [mypy](https://mypy.readthedocs.io/) for static type checking
46+
47+
To set up pre-commit:
48+
```bash
49+
pip install pre-commit
50+
pre-commit install
51+
```
52+
53+
### 4. Continuous Integration
54+
GitHub Actions workflows are set up for:
55+
- [Linting](.github/workflows/lint.yml): Runs Ruff and mypy
56+
- [Testing](.github/workflows/test.yml): Runs pytest on multiple Python versions
57+
58+
### 5. Project Structure
59+
```
60+
.
61+
|- src/
62+
| |- starter_repo/ # Source code
63+
| |- plot_data.py # Main script
64+
|- tests/ # Test files
65+
| |- test_plot_data.py
66+
|- data/ # Sample data
67+
| |- sample.csv
68+
|- .github/workflows/ # CI configuration
69+
|- pyproject.toml # Project metadata and dependencies
70+
|- .pre-commit-config.yaml # Pre-commit hook configuration
71+
|- README.md
72+
```
73+
74+
## Installation
75+
76+
```bash
77+
# For users
78+
pip install .
79+
80+
# For developers
81+
pip install -e ".[dev]"
82+
```
83+
84+
## Contributing
85+
1. Fork the repository
86+
2. Install development dependencies: `pip install -e ".[dev]"`
87+
3. Install pre-commit hooks: `pre-commit install`
88+
4. Make your changes
89+
5. Run tests: `pytest`
90+
6. Submit a pull request
91+
92+
## Author
93+
Graham Neubig ([email protected])

data/sample.csv

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
year,population
2+
2000,6115
3+
2001,6214
4+
2002,6312
5+
2003,6411
6+
2004,6510
7+
2005,6609
8+
2006,6709
9+
2007,6809
10+
2008,6909
11+
2009,7009
12+
2010,7109

pyproject.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[build-system]
2+
requires = ["hatchling"]
3+
build-backend = "hatchling.build"
4+
5+
[project]
6+
name = "starter-repo"
7+
version = "0.1.0"
8+
authors = [
9+
{ name="Graham Neubig", email="[email protected]" },
10+
]
11+
description = "A starter repository demonstrating Python best practices"
12+
readme = "README.md"
13+
requires-python = ">=3.8"
14+
dependencies = [
15+
"pandas",
16+
"matplotlib",
17+
]
18+
19+
[project.optional-dependencies]
20+
dev = [
21+
"pytest",
22+
"ruff",
23+
"mypy",
24+
"pre-commit",
25+
]
26+
27+
[tool.ruff]
28+
select = ["E", "F", "I"]
29+
line-length = 100
30+
31+
[tool.mypy]
32+
python_version = "3.8"
33+
warn_return_any = true
34+
warn_unused_configs = true
35+
disallow_untyped_defs = true
36+
check_untyped_defs = true
37+
38+
[tool.pytest.ini_options]
39+
testpaths = ["tests"]
40+
python_files = ["test_*.py"]

src/starter_repo/plot_data.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env python3
2+
3+
from pathlib import Path
4+
import argparse
5+
import pandas as pd
6+
import matplotlib.pyplot as plt
7+
from typing import list, tuple
8+
9+
def read_csv_data(file_path: Path, x_col: str, y_col: str) -> tuple[list[float], list[float]]:
10+
"""Read data from a CSV file and return specified columns.
11+
12+
Args:
13+
file_path: Path to the CSV file
14+
x_col: Name of the column to use for x-axis
15+
y_col: Name of the column to use for y-axis
16+
17+
Returns:
18+
Tuple of x and y data as lists
19+
"""
20+
df = pd.read_csv(file_path)
21+
return df[x_col].tolist(), df[y_col].tolist()
22+
23+
def create_plot(x_data: list[float], y_data: list[float],
24+
x_label: str, y_label: str, title: str) -> plt.Figure:
25+
"""Create a plot from the provided data.
26+
27+
Args:
28+
x_data: Data for x-axis
29+
y_data: Data for y-axis
30+
x_label: Label for x-axis
31+
y_label: Label for y-axis
32+
title: Plot title
33+
34+
Returns:
35+
matplotlib Figure object
36+
"""
37+
fig, ax = plt.subplots()
38+
ax.plot(x_data, y_data)
39+
ax.set_xlabel(x_label)
40+
ax.set_ylabel(y_label)
41+
ax.set_title(title)
42+
return fig
43+
44+
def main() -> None:
45+
parser = argparse.ArgumentParser(description="Create plots from CSV data")
46+
parser.add_argument("file_path", type=Path, help="Path to the CSV file")
47+
parser.add_argument("x_column", type=str, help="Column name for x-axis")
48+
parser.add_argument("y_column", type=str, help="Column name for y-axis")
49+
parser.add_argument("--output", "-o", type=Path, default=Path("plot.png"),
50+
help="Output file path (default: plot.png)")
51+
parser.add_argument("--title", "-t", type=str, default="Data Plot",
52+
help="Plot title (default: Data Plot)")
53+
54+
args = parser.parse_args()
55+
56+
x_data, y_data = read_csv_data(args.file_path, args.x_column, args.y_column)
57+
fig = create_plot(x_data, y_data, args.x_column, args.y_column, args.title)
58+
fig.savefig(args.output)
59+
plt.close(fig)
60+
61+
if __name__ == "__main__":
62+
main()

tests/test_plot_data.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from pathlib import Path
2+
import pandas as pd
3+
import pytest
4+
from starter_repo.plot_data import read_csv_data, create_plot
5+
6+
@pytest.fixture
7+
def sample_csv(tmp_path: Path) -> Path:
8+
"""Create a sample CSV file for testing."""
9+
df = pd.DataFrame({
10+
'x': [1, 2, 3, 4, 5],
11+
'y': [2, 4, 6, 8, 10]
12+
})
13+
file_path = tmp_path / "test.csv"
14+
df.to_csv(file_path, index=False)
15+
return file_path
16+
17+
def test_read_csv_data(sample_csv: Path) -> None:
18+
"""Test reading data from CSV file."""
19+
x_data, y_data = read_csv_data(sample_csv, 'x', 'y')
20+
assert x_data == [1, 2, 3, 4, 5]
21+
assert y_data == [2, 4, 6, 8, 10]
22+
23+
def test_create_plot() -> None:
24+
"""Test plot creation."""
25+
x_data = [1, 2, 3]
26+
y_data = [2, 4, 6]
27+
fig = create_plot(x_data, y_data, "X", "Y", "Test Plot")
28+
assert fig is not None
29+
# Basic check that the figure contains the expected elements
30+
ax = fig.axes[0]
31+
assert ax.get_xlabel() == "X"
32+
assert ax.get_ylabel() == "Y"
33+
assert ax.get_title() == "Test Plot"

0 commit comments

Comments
 (0)