1"""
2Python Packaging and Project Structure
3
4Demonstrates:
5- pyproject.toml structure
6- setup.py vs pyproject.toml
7- Entry points
8- Virtual environments
9- Package structure
10- Versioning
11- Dependencies management
12"""
13
14from typing import List
15
16
17def section(title: str) -> None:
18 """Print a section header."""
19 print("\n" + "=" * 60)
20 print(f" {title}")
21 print("=" * 60)
22
23
24# =============================================================================
25# Modern Python Package Structure
26# =============================================================================
27
28section("Modern Python Package Structure")
29
30print("""
31Recommended package structure:
32
33my_package/
34├── pyproject.toml # Project metadata (PEP 518, 621)
35├── README.md # Project description
36├── LICENSE # License file
37├── .gitignore # Git ignore patterns
38├── src/ # Source code directory
39│ └── my_package/ # Actual package
40│ ├── __init__.py # Package initialization
41│ ├── core.py # Core functionality
42│ ├── utils.py # Utility functions
43│ └── py.typed # Type hints marker
44├── tests/ # Test directory
45│ ├── __init__.py
46│ ├── test_core.py
47│ └── test_utils.py
48├── docs/ # Documentation
49│ └── index.md
50└── examples/ # Usage examples
51 └── example.py
52
53Why src/ layout?
54- Prevents accidental imports from source
55- Ensures package is installed before testing
56- Better isolation during development
57""")
58
59
60# =============================================================================
61# pyproject.toml Example
62# =============================================================================
63
64section("pyproject.toml Example")
65
66pyproject_toml = '''
67[build-system]
68requires = ["setuptools>=61.0", "wheel"]
69build-backend = "setuptools.build_meta"
70
71[project]
72name = "my-package"
73version = "0.1.0"
74description = "A short description of my package"
75readme = "README.md"
76requires-python = ">=3.8"
77license = {text = "MIT"}
78authors = [
79 {name = "Your Name", email = "your.email@example.com"}
80]
81maintainers = [
82 {name = "Your Name", email = "your.email@example.com"}
83]
84keywords = ["example", "package", "tutorial"]
85classifiers = [
86 "Development Status :: 3 - Alpha",
87 "Intended Audience :: Developers",
88 "License :: OSI Approved :: MIT License",
89 "Programming Language :: Python :: 3",
90 "Programming Language :: Python :: 3.8",
91 "Programming Language :: Python :: 3.9",
92 "Programming Language :: Python :: 3.10",
93 "Programming Language :: Python :: 3.11",
94]
95
96dependencies = [
97 "requests>=2.28.0",
98 "numpy>=1.22.0",
99 "pandas>=1.5.0",
100]
101
102[project.optional-dependencies]
103dev = [
104 "pytest>=7.0.0",
105 "pytest-cov>=4.0.0",
106 "black>=22.0.0",
107 "mypy>=0.990",
108 "ruff>=0.0.250",
109]
110docs = [
111 "sphinx>=5.0.0",
112 "sphinx-rtd-theme>=1.0.0",
113]
114
115[project.urls]
116Homepage = "https://github.com/username/my-package"
117Documentation = "https://my-package.readthedocs.io"
118Repository = "https://github.com/username/my-package"
119"Bug Tracker" = "https://github.com/username/my-package/issues"
120
121[project.scripts]
122my-command = "my_package.cli:main"
123
124[tool.setuptools]
125package-dir = {"" = "src"}
126
127[tool.setuptools.packages.find]
128where = ["src"]
129
130[tool.pytest.ini_options]
131testpaths = ["tests"]
132python_files = ["test_*.py"]
133
134[tool.mypy]
135python_version = "3.8"
136warn_return_any = true
137warn_unused_configs = true
138
139[tool.black]
140line-length = 88
141target-version = ["py38", "py39", "py310", "py311"]
142
143[tool.ruff]
144line-length = 88
145target-version = "py38"
146'''
147
148print("Modern pyproject.toml (PEP 621):")
149print(pyproject_toml)
150
151
152# =============================================================================
153# Entry Points
154# =============================================================================
155
156section("Entry Points")
157
158print("""
159Entry points create command-line scripts from Python functions.
160
161In pyproject.toml:
162 [project.scripts]
163 my-command = "my_package.cli:main"
164
165This creates a 'my-command' executable that calls main() in my_package/cli.py
166
167Example cli.py:
168""")
169
170cli_example = '''
171# src/my_package/cli.py
172
173def main():
174 """Main entry point for CLI."""
175 import sys
176 print(f"Hello from my-package!")
177 print(f"Arguments: {sys.argv[1:]}")
178 return 0
179
180if __name__ == "__main__":
181 sys.exit(main())
182'''
183
184print(cli_example)
185
186print("""
187After installation:
188 $ my-command arg1 arg2
189 Hello from my-package!
190 Arguments: ['arg1', 'arg2']
191""")
192
193
194# =============================================================================
195# Virtual Environments
196# =============================================================================
197
198section("Virtual Environments")
199
200print("""
201Virtual environments isolate project dependencies.
202
203Creating virtual environment:
204 # Using venv (built-in)
205 $ python -m venv venv
206 $ python -m venv .venv # Hidden directory
207
208 # Using virtualenv (third-party, more features)
209 $ pip install virtualenv
210 $ virtualenv venv
211
212Activating:
213 # Linux/Mac
214 $ source venv/bin/activate
215 $ source .venv/bin/activate
216
217 # Windows
218 $ venv\\Scripts\\activate
219
220 # With activated venv, prompt shows:
221 (venv) $ python --version
222
223Deactivating:
224 (venv) $ deactivate
225
226Modern alternatives:
227 # Poetry - dependency management + virtual environments
228 $ pip install poetry
229 $ poetry init
230 $ poetry add requests
231 $ poetry install
232 $ poetry run python script.py
233
234 # pipenv - combines pip and virtualenv
235 $ pip install pipenv
236 $ pipenv install requests
237 $ pipenv shell
238
239 # uv - faster pip/venv (Rust-based)
240 $ pip install uv
241 $ uv venv
242 $ uv pip install requests
243""")
244
245
246# =============================================================================
247# Installing Package
248# =============================================================================
249
250section("Installing Package")
251
252print("""
253Development installation (editable mode):
254 # Changes to source code immediately available
255 $ pip install -e .
256 $ pip install -e ".[dev]" # With dev dependencies
257 $ pip install -e ".[dev,docs]" # Multiple extras
258
259Regular installation:
260 $ pip install .
261 $ pip install .[dev]
262
263From PyPI (after publishing):
264 $ pip install my-package
265 $ pip install my-package==0.1.0
266 $ pip install my-package>=0.1.0
267
268From git:
269 $ pip install git+https://github.com/username/my-package.git
270 $ pip install git+https://github.com/username/my-package.git@v0.1.0
271
272From local wheel:
273 $ pip install dist/my_package-0.1.0-py3-none-any.whl
274""")
275
276
277# =============================================================================
278# Building and Publishing
279# =============================================================================
280
281section("Building and Publishing")
282
283print("""
284Build package:
285 # Install build tools
286 $ pip install build twine
287
288 # Build distributions
289 $ python -m build
290 # Creates:
291 # dist/my_package-0.1.0-py3-none-any.whl
292 # dist/my_package-0.1.0.tar.gz
293
294Check package:
295 $ twine check dist/*
296
297Upload to TestPyPI (testing):
298 $ twine upload --repository testpypi dist/*
299
300Upload to PyPI (production):
301 $ twine upload dist/*
302
303Test installation from TestPyPI:
304 $ pip install --index-url https://test.pypi.org/simple/ my-package
305""")
306
307
308# =============================================================================
309# Requirements Files
310# =============================================================================
311
312section("Requirements Files")
313
314print("""
315requirements.txt (production dependencies):
316 requests>=2.28.0
317 numpy>=1.22.0,<2.0.0
318 pandas==1.5.3
319
320requirements-dev.txt (development dependencies):
321 -r requirements.txt # Include production deps
322 pytest>=7.0.0
323 black>=22.0.0
324 mypy>=0.990
325
326requirements-lock.txt (exact versions, for reproducibility):
327 requests==2.28.2
328 numpy==1.24.3
329 pandas==1.5.3
330 # ... with all transitive dependencies
331
332Installing:
333 $ pip install -r requirements.txt
334 $ pip install -r requirements-dev.txt
335
336Freezing current environment:
337 $ pip freeze > requirements-lock.txt
338
339Generating requirements from pyproject.toml:
340 $ pip-compile pyproject.toml # Using pip-tools
341""")
342
343
344# =============================================================================
345# Package Versioning
346# =============================================================================
347
348section("Package Versioning")
349
350print("""
351Semantic Versioning (SemVer): MAJOR.MINOR.PATCH
352
353 MAJOR: Incompatible API changes (1.0.0 -> 2.0.0)
354 MINOR: Add functionality (backward-compatible) (1.0.0 -> 1.1.0)
355 PATCH: Bug fixes (backward-compatible) (1.0.0 -> 1.0.1)
356
357Examples:
358 0.1.0 - Initial development
359 1.0.0 - First stable release
360 1.1.0 - New feature added
361 1.1.1 - Bug fix
362 2.0.0 - Breaking change
363
364Pre-release versions:
365 1.0.0a1 - Alpha 1
366 1.0.0b1 - Beta 1
367 1.0.0rc1 - Release candidate 1
368
369Version specifiers in dependencies:
370 requests>=2.28.0 # At least 2.28.0
371 requests>=2.28.0,<3.0.0 # At least 2.28.0, but less than 3.0.0
372 requests==2.28.2 # Exactly 2.28.2
373 requests~=2.28.0 # Compatible (>=2.28.0, <2.29.0)
374 requests!=2.28.1 # Not 2.28.1
375
376Single-sourcing version:
377 # In src/my_package/__init__.py
378 __version__ = "0.1.0"
379
380 # In pyproject.toml
381 [project]
382 dynamic = ["version"]
383
384 [tool.setuptools.dynamic]
385 version = {attr = "my_package.__version__"}
386""")
387
388
389# =============================================================================
390# Package __init__.py
391# =============================================================================
392
393section("Package __init__.py")
394
395init_example = '''
396# src/my_package/__init__.py
397
398"""My Package - A short description."""
399
400from my_package.core import main_function, AnotherClass
401from my_package.utils import helper_function
402
403__version__ = "0.1.0"
404__author__ = "Your Name"
405__email__ = "your.email@example.com"
406
407# Define public API
408__all__ = [
409 "main_function",
410 "AnotherClass",
411 "helper_function",
412]
413
414# Package-level initialization (if needed)
415def _initialize():
416 """Initialize package."""
417 pass
418
419_initialize()
420'''
421
422print("Example __init__.py:")
423print(init_example)
424
425
426# =============================================================================
427# Manifest and Data Files
428# =============================================================================
429
430section("Including Data Files")
431
432print("""
433MANIFEST.in (for sdist, source distribution):
434 include README.md
435 include LICENSE
436 include pyproject.toml
437 recursive-include src/my_package *.py
438 recursive-include src/my_package *.typed
439 recursive-include tests *.py
440 include src/my_package/data/*.json
441 exclude *.pyc
442 exclude __pycache__
443 prune .git
444
445pyproject.toml configuration:
446 [tool.setuptools]
447 include-package-data = true
448
449 [tool.setuptools.package-data]
450 my_package = ["data/*.json", "py.typed"]
451
452Accessing data files in code:
453 # Using importlib.resources (Python 3.9+)
454 from importlib.resources import files
455 data_path = files("my_package").joinpath("data/config.json")
456
457 # Using importlib.resources (Python 3.7-3.8)
458 from importlib.resources import read_text
459 content = read_text("my_package.data", "config.json")
460
461 # Using pkg_resources (legacy)
462 from pkg_resources import resource_filename
463 path = resource_filename("my_package", "data/config.json")
464""")
465
466
467# =============================================================================
468# Development Workflow
469# =============================================================================
470
471section("Development Workflow")
472
473print("""
4741. Create project structure:
475 $ mkdir my-package
476 $ cd my-package
477 $ mkdir -p src/my_package tests docs
478
4792. Initialize git:
480 $ git init
481 $ git add .
482 $ git commit -m "Initial commit"
483
4843. Create virtual environment:
485 $ python -m venv .venv
486 $ source .venv/bin/activate
487
4884. Install in editable mode:
489 $ pip install -e ".[dev]"
490
4915. Run tests:
492 $ pytest
493
4946. Format code:
495 $ black src/ tests/
496
4977. Type checking:
498 $ mypy src/
499
5008. Build distribution:
501 $ python -m build
502
5039. Publish to PyPI:
504 $ twine upload dist/*
505
506Pre-commit hooks (optional):
507 $ pip install pre-commit
508 $ cat > .pre-commit-config.yaml << EOF
509 repos:
510 - repo: https://github.com/psf/black
511 rev: 23.0.0
512 hooks:
513 - id: black
514 - repo: https://github.com/pycqa/isort
515 rev: 5.12.0
516 hooks:
517 - id: isort
518 EOF
519 $ pre-commit install
520""")
521
522
523# =============================================================================
524# Summary
525# =============================================================================
526
527section("Summary")
528
529print("""
530Python packaging essentials:
5311. pyproject.toml - Modern project metadata (PEP 621)
5322. src/ layout - Better isolation and testing
5333. Entry points - Create CLI commands
5344. Virtual environments - Isolate dependencies
5355. Editable installs - Development workflow
5366. build + twine - Build and publish packages
5377. requirements.txt - Pin dependencies
5388. Semantic versioning - Version management
5399. __init__.py - Define package API
54010. MANIFEST.in - Include non-Python files
541
542Modern tools:
543- Poetry - All-in-one dependency + package management
544- pipenv - Combines pip + virtualenv
545- uv - Faster pip/venv alternative (Rust-based)
546- build - Standard build tool
547- twine - Secure PyPI uploads
548
549Best practices:
550- Use pyproject.toml over setup.py
551- Use src/ layout for packages
552- Pin exact versions in production
553- Use semantic versioning
554- Include type hints (py.typed)
555- Write comprehensive README
556- Test before publishing
557""")