11_packaging_demo.py

Download
python 558 lines 13.3 KB
  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""")