[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: Google\nAlignAfterOpenBracket: BlockIndent\nAllowAllParametersOfDeclarationOnNextLine: false\nBinPackParameters: false\nColumnLimit: 150\n"
  },
  {
    "path": ".claude-plugin/marketplace.json",
    "content": "{\n  \"name\": \"pysheeet\",\n  \"owner\": {\n    \"name\": \"crazyguitar\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"pysheeet\",\n      \"source\": {\n        \"source\": \"github\",\n        \"repo\": \"crazyguitar/pysheeet\"\n      },\n      \"description\": \"Comprehensive Python programming reference covering syntax, concurrency, networking, databases, ML/LLM development, and HPC\",\n      \"version\": \"1.0.0\",\n      \"author\": {\n        \"name\": \"crazyguitar\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": ".claude-plugin/plugin.json",
    "content": "{\n  \"name\": \"pysheeet\",\n  \"description\": \"Comprehensive Python programming reference covering syntax, concurrency, networking, databases, ML/LLM development, and HPC\",\n  \"version\": \"1.0.0\",\n  \"author\": {\n    \"name\": \"crazyguitar\"\n  },\n  \"homepage\": \"https://www.pythonsheets.com\",\n  \"repository\": \"https://github.com/crazyguitar/pysheeet\",\n  \"license\": \"MIT\"\n}\n"
  },
  {
    "path": ".coveragerc",
    "content": "[report]\nomit =\n    */python?.?/*\n    */site-packages/*\n    app_test.py\n\nexclude_lines =\n    if __name__ == .__main__.:\n    if .DYNO. in os.environ:\n"
  },
  {
    "path": ".gitattributes",
    "content": "*.png filter=lfs diff=lfs merge=lfs -text\ndocs/_static/appendix/*.png !filter !diff !merge\ndocs/_static/appendix/nixl/*.png !filter !diff !merge\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: crazyguitar\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"21:00\"\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: sphinx\n    versions:\n    - 3.5.3\n"
  },
  {
    "path": ".github/workflows/pythonpackage.yml",
    "content": "name: Build\n\non: [push, pull_request]\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    strategy:\n      max-parallel: 4\n      matrix:\n        python: [\"3.11\", \"3.12\", \"3.13\"]\n\n    steps:\n    - uses: actions/checkout@v4\n    - name: Set up Python ${{ matrix.python }}\n      uses: actions/setup-python@v5\n      with:\n        python-version: ${{ matrix.python }}\n    - name: Install clang-format and cmake\n      run: sudo apt-get install -y clang-format cmake\n    - name: Install\n      run: make deps\n    - name: Test\n      run: make clean && make test\n"
  },
  {
    "path": ".gitignore",
    "content": "_build/\n\n# Created by https://www.gitignore.io/api/vim,python\n\n### Python ###\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\n### Vim ###\n# swap\n[._]*.s[a-v][a-z]\n[._]*.sw[a-p]\n[._]s[a-v][a-z]\n[._]sw[a-p]\n# session\nSession.vim\n# temporary\n.netrwhist\n*~\n# auto-generated tag files\ntags\n\n# End of https://www.gitignore.io/api/vim,python\n"
  },
  {
    "path": "CITATION.cff",
    "content": "cff-version: 1.2.0\nmessage: \"If you use this software, please cite it as below.\"\ntitle: \"Python Cheatsheet\"\nauthors:\n- family-names: \"Tsai\"\n  given-names: \"Chang-Ning\"\n  orcid: \"https://orcid.org/0009-0000-5297-5940\"\n  github: \"crazyguitar\"\nabstract: \"A comprehensive Python cheat sheet covering Python 2 and 3 syntax, tips, and code snippets.\"\nversion: \"master\"\nrepository-code: \"https://github.com/crazyguitar/pysheeet\"\nlicense: \"MIT\"\ndate-released: \"2016-02-29\"\nurl: \"https://www.pythonsheets.com\"\nkeywords:\n  - python\n  - cheatsheet\n  - python-2\n  - python-3\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2015-2026 Chang Ning Tsai\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "REQUIREMENT = requirements.txt\n\nVER  = $(word 2, $(shell python --version 2>&1))\nSRC  = app.py app_test.py\nPY36 = $(shell expr $(VER) \\>= 3.6)\nCEXT_DIR = src/cext\nCEXT_BUILD = $(CEXT_DIR)/build\nCAPI_DIR = src/cext/capi\nCPP_FROM_PY_DIR = src/cpp_from_python\nCPP_FROM_PY_BUILD = $(CPP_FROM_PY_DIR)/build\nPY_ARCH = $(shell python -c \"import platform; print(platform.machine())\")\n\n.PHONY: build deps test format cext\nbuild: html\n\n%:\n\tcd docs && make $@\n\nclean:\n\tcd docs && make clean\n\trm -rf $(CEXT_BUILD)\n\trm -rf $(CPP_FROM_PY_BUILD)\n\trm -rf $(CAPI_DIR)/build $(CAPI_DIR)/*.so $(CAPI_DIR)/*.egg-info\n\ncext:\n\t@echo \"Building C/C++ extensions for $(PY_ARCH)...\"\n\tmkdir -p $(CEXT_BUILD) && \\\n\tcd $(CEXT_BUILD) && \\\n\tcmake -DCMAKE_OSX_ARCHITECTURES=$(PY_ARCH) .. && \\\n\tmake\n\tcd $(CAPI_DIR) && python setup.py build_ext --inplace\n\ncpp_from_python:\n\t@echo \"Building C++ from Python examples...\"\n\tmkdir -p $(CPP_FROM_PY_BUILD) && \\\n\tcd $(CPP_FROM_PY_BUILD) && \\\n\tcmake .. && \\\n\tmake\n\ntest: clean build cext cpp_from_python\n\tpycodestyle $(SRC)\n\tpydocstyle $(SRC)\n\tbandit app.py\n\tcoverage run app_test.py && coverage report --fail-under=100 -m $(SRC)\n\tpython -m pytest src/basic/*.py src/new_py3/*.py -v\n\tpython -m pytest $(CEXT_DIR)/test_cext.py -v\n\tpython -m pytest $(CAPI_DIR)/test_capi.py -v\n\tcd $(CPP_FROM_PY_BUILD) && make test\nifeq ($(PY36), 1)\n\tblack --quiet --diff --check --line-length 79 $(SRC)\nendif\n\ndeps:\n\tpip install -r requirements.txt\n\tpip install pybind11\nifeq ($(PY36), 1)\n\tpip install black==22.3.0\nendif\n\nformat:\n\tblack --line-length 79 $(SRC) src/\n\tfind src/cext -type f \\( -name \"*.cpp\" -o -name \"*.c\" -o -name \"*.h\" \\) | xargs -I{} clang-format -style=file -i {}\n"
  },
  {
    "path": "Procfile",
    "content": "web: make clean && make && gunicorn app:app --log-file -\n"
  },
  {
    "path": "README.rst",
    "content": "\n.. raw:: html\n\n    <h1 align=\"center\">\n    <br>\n      <a href=\"https://www.pythonsheets.com\"><img src=\"docs/_static/logo.png\" alt=\"pysheeet\" width=200\"></a>\n    </h1>\n    <p align=\"center\">\n      <a href=\"https://github.com/crazyguitar/pysheeet/actions\">\n        <img src=\"https://github.com/crazyguitar/pysheeet/actions/workflows/pythonpackage.yml/badge.svg\" alt=\"Build Status\">\n      </a>\n      <a href=\"https://coveralls.io/github/crazyguitar/pysheeet?branch=master\">\n        <img src=\"https://coveralls.io/repos/github/crazyguitar/pysheeet/badge.svg?branch=master\" alt=\"Coverage\">\n      </a>\n      <a href=\"https://raw.githubusercontent.com/crazyguitar/pysheeet/master/LICENSE\">\n        <img src=\"https://img.shields.io/badge/License-MIT-blue.svg\" alt=\"License MIT\">\n      </a>\n      <a href=\"https://doi.org/10.5281/zenodo.15529042\">\n        <img src=\"https://zenodo.org/badge/52760178.svg\" alt=\"DOI\">\n      </a>\n    </p>\n\nIntroduction\n=============\n\nThis project was started to bring together useful Python code snippets that make\ncoding faster, easier, and more enjoyable. You can explore all the cheat sheets at\n`Pysheeet <https://www.pythonsheets.com/>`_. Contributions are always welcome—feel\nfree to fork the repo and submit a pull request to help it grow!\n\nPlugin\n======\n\n**pysheeet** is available as a Claude Code plugin. Once installed, Claude\nautomatically uses the cheat sheets to answer Python questions — just ask\nnaturally and the skill triggers based on context.\n\nInstallation\n------------\n\n**As a Claude Code plugin (recommended):**\n\n.. code-block:: bash\n\n    # Step 1: Add the marketplace\n    claude plugin marketplace add crazyguitar/pysheeet\n\n    # Step 2: Install the plugin\n    claude plugin install pysheeet@pysheeet\n\n**Local testing (single session only):**\n\n.. code-block:: bash\n\n    claude --plugin-dir /path/to/pysheeet\n\n**Manual installation (requires cloning the repo):**\n\n.. code-block:: bash\n\n    git clone https://github.com/crazyguitar/pysheeet.git\n    mkdir -p ~/.claude/skills\n    cp -r pysheeet/skills/py ~/.claude/skills/py\n\nWhat's New In Python 3\n======================\n\nThis part only provides a quick glance at some important features in Python 3.\nIf you're interested in all of the most important features, please read the\nofficial document, `What’s New in Python <https://docs.python.org/3/whatsnew/index.html>`_.\n\n- `New in Python3 <docs/notes/python-new-py3.rst>`_\n\n\nCheat Sheet\n===========\n\nCore Python fundamentals including data types, functions, classes, and commonly\nused patterns for everyday programming tasks.\n\n- `From Scratch <docs/notes/basic/python-basic.rst>`_\n- `Future <docs/notes/basic/python-future.rst>`_\n- `Typing <docs/notes/basic/python-typing.rst>`_\n- `Class <docs/notes/basic/python-object.rst>`_\n- `Function <docs/notes/basic/python-func.rst>`_\n- `Unicode <docs/notes/basic/python-unicode.rst>`_\n- `List <docs/notes/basic/python-list.rst>`_\n- `Set <docs/notes/basic/python-set.rst>`_\n- `Dictionary <docs/notes/basic/python-dict.rst>`_\n- `Heap <docs/notes/basic/python-heap.rst>`_\n- `Generator <docs/notes/basic/python-generator.rst>`_\n- `Regular expression <docs/notes/basic/python-rexp.rst>`_\n\n\nSystem\n======\n\nDate/time handling, file I/O, and operating system interfaces.\n\n- `Datetime <docs/notes/os/python-date.rst>`_ - Timestamps, formatting, parsing, timezones, timedelta\n- `Files and I/O <docs/notes/os/python-io.rst>`_ - Reading, writing, pathlib, shutil, tempfile\n- `Operating System <docs/notes/os/python-os.rst>`_ - Processes, environment, system calls\n\n\nConcurrency\n===========\n\nThreading, multiprocessing, and concurrent.futures for parallel execution.\nCovers synchronization primitives, process pools, and bypassing the GIL.\n\n- `Threading <docs/notes/concurrency/python-threading.rst>`_ - Threads, locks, semaphores, events, conditions\n- `Multiprocessing <docs/notes/concurrency/python-multiprocessing.rst>`_ - Processes, pools, shared memory, IPC\n- `concurrent.futures <docs/notes/concurrency/python-futures.rst>`_ - Executors, futures, callbacks\n\n\nAsyncio\n=======\n\nAsynchronous programming with Python's ``asyncio`` module. Covers coroutines,\nevent loops, tasks, networking, and advanced patterns.\n\n- `A Hitchhiker's Guide to Asynchronous Programming <docs/notes/asyncio/python-asyncio-guide.rst>`_ - Design philosophy and evolution\n- `Asyncio Basics <docs/notes/asyncio/python-asyncio-basic.rst>`_ - Coroutines, tasks, gather, timeouts\n- `Asyncio Networking <docs/notes/asyncio/python-asyncio-server.rst>`_ - TCP/UDP servers, HTTP, SSL/TLS\n- `Asyncio Advanced <docs/notes/asyncio/python-asyncio-advanced.rst>`_ - Synchronization, queues, subprocesses\n\n\nC/C++ Extensions\n================\n\nNative extensions for performance-critical code. Covers modern pybind11 (used by\nPyTorch, TensorFlow), ctypes, cffi, Cython, and the traditional Python C API.\nAlso includes a guide for Python developers learning modern C++ syntax.\n\n- `ctypes <docs/notes/extension/python-ctypes.rst>`_ - Load shared libraries without compilation\n- `Python C API <docs/notes/extension/python-capi.rst>`_ - Traditional C extension reference\n- `Modern C/C++ Extensions <docs/notes/extension/python-cext-modern.rst>`_ - pybind11, Cython\n- `Learn C++ from Python <docs/notes/extension/cpp-from-python.rst>`_ - Modern C++ for Python developers\n\n\nSecurity\n========\n\nModern cryptographic practices and common security vulnerabilities. Covers\nencryption, TLS/SSL, and why legacy patterns are dangerous.\n\n- `Modern Cryptography <docs/notes/security/python-crypto.rst>`_ - AES-GCM, RSA-OAEP, Ed25519, Argon2\n- `TLS/SSL and Certificates <docs/notes/security/python-tls.rst>`_ - HTTPS servers, certificate generation\n- `Common Vulnerabilities <docs/notes/security/python-vulnerability.rst>`_ - Padding oracle, injection, timing attacks\n\n\nNetwork\n=======\n\nLow-level network programming with Python sockets. Covers TCP/UDP communication,\nserver implementations, asynchronous I/O, SSL/TLS encryption, and packet analysis.\n\n- `Socket Basics <docs/notes/network/python-socket.rst>`_\n- `Socket Servers <docs/notes/network/python-socket-server.rst>`_\n- `Async Socket I/O <docs/notes/network/python-socket-async.rst>`_\n- `SSL/TLS Sockets <docs/notes/network/python-socket-ssl.rst>`_\n- `Packet Sniffing <docs/notes/network/python-socket-sniffer.rst>`_\n- `SSH and Tunnels <docs/notes/network/python-ssh.rst>`_\n\n\nDatabase\n========\n\nDatabase access with SQLAlchemy, Python's most popular ORM. Covers connection\nmanagement, raw SQL, object-relational mapping, and common query patterns.\n\n- `SQLAlchemy Basics <docs/notes/database/python-sqlalchemy.rst>`_\n- `SQLAlchemy ORM <docs/notes/database/python-sqlalchemy-orm.rst>`_\n- `SQLAlchemy Query Recipes <docs/notes/database/python-sqlalchemy-query.rst>`_\n\n\nLLM\n===\n\nLarge Language Models (LLM) training, inference, and optimization. Covers PyTorch\nfor model development, distributed training across GPUs, and vLLM/SGLang for\nhigh-performance LLM inference and serving.\n\n- `PyTorch <docs/notes/llm/pytorch.rst>`_ - Tensors, autograd, neural networks, training loops\n- `Megatron <docs/notes/llm/megatron.rst>`_ - NVIDIA Megatron training/fine-tuning framework with enroot/pyxis\n- `LLM Serving <docs/notes/llm/llm-serving.rst>`_ - vLLM and SGLang for production inference with TP/PP/DP/EP\n- `LLM Benchmark <docs/notes/llm/llm-bench.rst>`_ - Benchmark suite for measuring serving performance\n\n\nHPC\n===\n\nHigh-Performance Computing tools for cluster management and job scheduling.\nCovers Slurm workload manager for distributed computing and GPU clusters.\n\n- `Slurm <docs/notes/hpc/slurm.rst>`_\n\n\nBlog\n====\n\nSupplementary topics covering Python internals, debugging techniques, and\nlanguage features that don't fit elsewhere.\n\n- `Is Disaggregated Prefill/Decode a Silver Bullet for LLM Serving? <docs/notes/appendix/disaggregated-prefill-decode.rst>`_\n- `Monitoring EFA with NCCL GIN and Nsys <docs/notes/appendix/megatron-efa-monitoring.rst>`_\n- `GPU-Initiated Networking for NCCL on AWS <docs/notes/appendix/nccl-gin.rst>`_\n- `PEP 572 and the walrus operator <docs/notes/appendix/python-walrus.rst>`_\n- `Python Interpreter in GNU Debugger <docs/notes/appendix/python-gdb.rst>`_\n\nPDF Version\n============\n\n`pdf`_\n\n.. _pdf: https://media.readthedocs.org/pdf/pysheeet/latest/pysheeet.pdf\n\nHow to run the server\n=======================\n\n.. code-block:: bash\n\n    $ virtualenv venv\n    $ . venv/bin/activate\n    $ pip install -r requirements.txt\n    $ make\n    $ python app.py\n\n    # URL: localhost:5000\n"
  },
  {
    "path": "app.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"This is a simple cheatsheet webapp.\"\"\"\n\nimport os\nfrom flask import Flask, abort, send_from_directory, render_template\nfrom flask_sslify import SSLify\nfrom flask_seasurf import SeaSurf\nfrom flask_talisman import Talisman\nfrom werkzeug.exceptions import NotFound\nfrom werkzeug.utils import safe_join\n\nDIR = os.path.dirname(os.path.realpath(__file__))\nROOT = os.path.join(DIR, \"docs\", \"_build\", \"html\")\n\n\ndef find_key(token):\n    \"\"\"Find the key from the environment variable.\"\"\"\n    if token == os.environ.get(\"ACME_TOKEN\"):\n        return os.environ.get(\"ACME_KEY\")\n    for k, v in os.environ.items():\n        if v == token and k.startswith(\"ACME_TOKEN_\"):\n            n = k.replace(\"ACME_TOKEN_\", \"\")\n            return os.environ.get(\"ACME_KEY_{}\".format(n))\n\n\ncsp = {\n    \"default-src\": \"'none'\",\n    \"style-src\": [\"'self'\", \"'unsafe-inline'\"],\n    \"script-src\": [\n        \"'self'\",\n        \"*.cloudflare.com\",\n        \"*.cloudflareinsights.com\",\n        \"*.googletagmanager.com\",\n        \"*.google-analytics.com\",\n        \"*.carbonads.com\",\n        \"*.carbonads.net\",\n        \"cdn.carbonads.com\",\n        \"srv.carbonads.net\",\n        \"'unsafe-inline'\",\n        \"'unsafe-eval'\",\n    ],\n    \"connect-src\": [\n        \"'self'\",\n        \"*.google-analytics.com\",\n        \"*.analytics.google.com\",\n        \"analytics.google.com\",\n        \"*.googletagmanager.com\",\n        \"*.carbonads.com\",\n        \"*.carbonads.net\",\n        \"*.doubleclick.net\",\n    ],\n    \"font-src\": \"'self'\",\n    \"form-action\": \"'self'\",\n    \"base-uri\": \"'self'\",\n    \"img-src\": \"*\",\n    \"frame-src\": [\"ghbtns.com\", \"*.carbonads.com\", \"*.carbonads.net\"],\n    \"frame-ancestors\": \"'none'\",\n    \"object-src\": \"'none'\",\n}\n\nfeature_policy = {\"geolocation\": \"'none'\"}\n\napp = Flask(__name__, template_folder=ROOT)\napp.config[\"SECRET_KEY\"] = os.urandom(16)\napp.config[\"SESSION_COOKIE_NAME\"] = \"__Secure-session\"\napp.config[\"SESSION_COOKIE_SAMESITE\"] = \"Strict\"\napp.config[\"CSRF_COOKIE_NAME\"] = \"__Secure-csrf-token\"\napp.config[\"CSRF_COOKIE_HTTPONLY\"] = True\napp.config[\"CSRF_COOKIE_SECURE\"] = True\ncsrf = SeaSurf(app)\ntalisman = Talisman(\n    app,\n    force_https=False,\n    content_security_policy=csp,\n    feature_policy=feature_policy,\n)\n\nif \"DYNO\" in os.environ:\n    sslify = SSLify(app, permanent=True, skips=[\".well-known\"])\n\n\n@app.errorhandler(404)\ndef page_not_found(e):\n    \"\"\"Redirect to 404.html.\"\"\"\n    return render_template(\"404.html\"), 404\n\n\n@app.route(\"/<path:path>\")\ndef static_proxy(path):\n    \"\"\"Find static files safely.\"\"\"\n    try:\n        return send_from_directory(ROOT, path)\n    except NotFound:\n        # Handle file not found or directory errors\n        return render_template(\"404.html\"), 404\n\n\n@app.route(\"/\")\ndef index_redirection():\n    \"\"\"Redirecting index file.\"\"\"\n    return send_from_directory(ROOT, \"index.html\")\n\n\n@csrf.exempt\n@app.route(\"/.well-known/acme-challenge/<token>\")\ndef acme(token):\n    \"\"\"Find the acme-key from environment variable.\"\"\"\n    key = find_key(token)\n    if key is None:\n        abort(404)\n    return key\n\n\nif __name__ == \"__main__\":\n    # Only run the app in debug mode during development\n    app.run(debug=os.environ.get(\"FLASK_ENV\") == \"development\")\n"
  },
  {
    "path": "app_test.py",
    "content": "\"\"\"Test app.py.\"\"\"\n\nimport multiprocessing\nimport platform\nimport unittest\nimport requests\nimport os\n\nfrom pathlib import Path\nfrom werkzeug.exceptions import NotFound\nfrom flask_testing import LiveServerTestCase\n\nfrom app import acme, find_key, static_proxy, index_redirection, page_not_found\n\nfrom app import ROOT\nfrom app import app\n\n\nif platform.system() == \"Darwin\":\n    multiprocessing.set_start_method(\"fork\")\n\n\nclass PysheeetTest(LiveServerTestCase):\n    \"\"\"Test app.\"\"\"\n\n    def create_app(self):\n        \"\"\"Create a app for test.\"\"\"\n        # remove env ACME_TOKEN*\n        for k, v in os.environ.items():\n            if not k.startswith(\"ACME_TOKEN\"):\n                continue\n            del os.environ[k]\n\n        self.token = \"token\"\n        self.key = \"key\"\n        os.environ[\"ACME_TOKEN\"] = self.token\n        os.environ[\"ACME_KEY\"] = self.key\n        os.environ[\"FLASK_ENV\"] = \"development\"\n        os.environ[\"FLASK_DEBUG\"] = \"1\"\n        app.config[\"TESTING\"] = True\n        app.config[\"LIVESERVER_PORT\"] = 0\n        return app\n\n    def check_security_headers(self, resp):\n        \"\"\"Check security headers.\"\"\"\n        headers = resp.headers\n        self.assertTrue(\"Content-Security-Policy\" in headers)\n        self.assertTrue(\"X-Content-Type-Options\" in headers)\n        self.assertTrue(\"Content-Security-Policy\" in headers)\n        self.assertTrue(\"Feature-Policy\" in headers)\n        self.assertEqual(headers[\"Feature-Policy\"], \"geolocation 'none'\")\n        self.assertEqual(headers[\"X-Frame-Options\"], \"SAMEORIGIN\")\n\n    def check_csrf_cookies(self, resp):\n        \"\"\"Check cookies for csrf.\"\"\"\n        cookies = resp.cookies\n        self.assertTrue(cookies.get(\"__Secure-session\"))\n        self.assertTrue(cookies.get(\"__Secure-csrf-token\"))\n\n    def test_index_redirection_req(self):\n        \"\"\"Test that send a request for the index page.\"\"\"\n        url = self.get_server_url()\n        resp = requests.get(url)\n        self.check_security_headers(resp)\n        self.check_csrf_cookies(resp)\n        self.assertEqual(resp.status_code, 200)\n\n    def test_static_proxy_req(self):\n        \"\"\"Test that send a request for notes.\"\"\"\n        url = self.get_server_url()\n        notes = Path(ROOT) / \"notes\"\n        for html in notes.rglob(\"*.html\"):\n            page = html.relative_to(ROOT)\n            u = f\"{url}/{page}\"\n            resp = requests.get(u)\n            self.check_security_headers(resp)\n            self.check_csrf_cookies(resp)\n            self.assertEqual(resp.status_code, 200)\n\n    def test_acme_req(self):\n        \"\"\"Test that send a request for a acme key.\"\"\"\n        url = self.get_server_url()\n        u = url + \"/.well-known/acme-challenge/token\"\n        resp = requests.get(u)\n        self.check_security_headers(resp)\n        self.assertEqual(resp.status_code, 200)\n\n        u = url + \"/.well-known/acme-challenge/foo\"\n        resp = requests.get(u)\n        self.check_security_headers(resp)\n        self.assertEqual(resp.status_code, 404)\n\n    def test_find_key(self):\n        \"\"\"Test that find a acme key from the environment.\"\"\"\n        token = self.token\n        key = self.key\n        self.assertEqual(find_key(token), key)\n\n        del os.environ[\"ACME_TOKEN\"]\n        del os.environ[\"ACME_KEY\"]\n\n        os.environ[\"ACME_TOKEN_ENV\"] = token\n        os.environ[\"ACME_KEY_ENV\"] = key\n        self.assertEqual(find_key(token), key)\n\n        del os.environ[\"ACME_TOKEN_ENV\"]\n        del os.environ[\"ACME_KEY_ENV\"]\n\n    def test_acme(self):\n        \"\"\"Test that send a request for a acme key.\"\"\"\n        token = self.token\n        key = self.key\n        self.assertEqual(acme(token), key)\n\n        token = token + \"_env\"\n        key = key + \"_env\"\n        os.environ[\"ACME_TOKEN_ENV\"] = token\n        os.environ[\"ACME_KEY_ENV\"] = key\n        self.assertEqual(find_key(token), key)\n\n        del os.environ[\"ACME_TOKEN_ENV\"]\n        del os.environ[\"ACME_KEY_ENV\"]\n\n        self.assertRaises(NotFound, acme, token)\n\n    def test_index_redirection(self):\n        \"\"\"Test index page redirection.\"\"\"\n        resp = index_redirection()\n        self.assertEqual(resp.status_code, 200)\n        resp.close()\n\n    def test_static_proxy(self):\n        \"\"\"Test that request static pages.\"\"\"\n        notes = Path(ROOT) / \"notes\"\n        for html in notes.rglob(\"*.html\"):\n            u = html.relative_to(ROOT)\n            resp = static_proxy(u)\n            self.assertEqual(resp.status_code, 200)\n            resp.close()\n\n        u = \"notes/../conf.py\"\n        _, code = static_proxy(u)\n        self.assertEqual(code, 404)\n\n    def test_page_not_found(self):\n        \"\"\"Test page not found.\"\"\"\n        html, status_code = page_not_found(None)\n        self.assertEqual(status_code, 404)\n\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "docs/404.rst",
    "content": ":orphan:\n\n404 Page Not Found\n==================\n\nWhat you were looking for is just not there.\n\n`Click here to go back to homepage. </>`_\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILDDIR)/*\n\n.PHONY: html\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\n.PHONY: dirhtml\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\n.PHONY: singlehtml\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\n.PHONY: pickle\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\n.PHONY: json\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\n.PHONY: htmlhelp\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\n.PHONY: qthelp\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/pysheeet.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/pysheeet.qhc\"\n\n.PHONY: applehelp\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\n.PHONY: devhelp\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/pysheeet\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pysheeet\"\n\t@echo \"# devhelp\"\n\n.PHONY: epub\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\n.PHONY: latex\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\n.PHONY: latexpdf\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: latexpdfja\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: text\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\n.PHONY: man\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\n.PHONY: texinfo\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\n.PHONY: info\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\n.PHONY: gettext\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\n.PHONY: changes\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\n.PHONY: linkcheck\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\n.PHONY: doctest\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\n.PHONY: coverage\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\n.PHONY: xml\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\n.PHONY: pseudoxml\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/_extra/robots.txt",
    "content": "User-agent: *\nAllow: /\nSitemap: https://www.pythonsheets.com/sitemap.xml\n"
  },
  {
    "path": "docs/_static/.gitignore",
    "content": "# Ignore everything in this directory\n*\n# Except this file\n!.gitignore\n!guido.png\n!logo.svg\n!style.css\n!carbonad.css\n!favicon.ico\n"
  },
  {
    "path": "docs/_static/carbonad.css",
    "content": "#carbonads {\n  display: block;\n  overflow: hidden;\n  padding: 1em;\n  padding-bottom: 0.3em;\n  line-height: 1.5;\n  margin-top: 10px;\n  margin-bottom: 10px;\n}\n\n#carbonads a {\n  text-decoration: none !important;\n  border-bottom: none;\n}\n\n#carbonads a:hover {\n  color: inherit;\n}\n\n#carbonads span {\n  display: block;\n  overflow: hidden;\n}\n\n.carbon-img {\n  display: block;\n  margin: 0 auto 8px;\n}\n\n.carbon-text {\n  display: block;\n  text-align: left;\n  margin-bottom: .1em;\n  color: #666;\n}\n\n.carbon-poweredby {\n  display: block;\n  text-align: left;\n  font-size: .9em;\n  color: #888 !important;\n}\n\n@media only screen and (min-width: 320px) and (max-width: 875px) {\n  #carbonads {\n    float: none;\n    max-width: 330px;\n    border: 0;\n    display: block;\n    overflow: hidden;\n    margin-top: 20px;\n    margin-bottom: 20px;\n    border-radius: 4px;\n    text-align: center;\n    box-shadow: 0 0 0 1px hsla(0, 0%, 0%, .1);\n    font-size: var(--font-size);\n    background-color: #eee;\n    line-height: 1.5;\n  }\n  #carbonads span {\n    position: relative;\n  }\n  #carbonads > span {\n    max-width: none;\n  }\n  .carbon-img {\n    float: left;\n    margin: 0;\n  }\n\n  .carbon-img img {\n    max-width: 130px !important;\n  }\n  .carbon-text {\n    float: left;\n    margin-bottom: 0;\n    padding: 8px 20px;\n    text-align: left;\n    color: #333 !important;\n    max-width: calc(100% - 130px - 3em);\n  }\n  .carbon-poweredby {\n    left: 130px;\n    bottom: 0;\n    display: block;\n    color: #555 !important;\n    text-align: right;\n    width: 100%;\n  }\n}\n"
  },
  {
    "path": "docs/_static/style.css",
    "content": "nav#table-of-contents {\n\tdisplay: none;\n}\n\ndiv.highlight > pre {\n    font-size: 14px;\n    border-radius: 3px;\n    background: #f6f8fa !important;\n    border: 1px solid #000000 !important;\n}\n\n:root {\n    --cu-boulder-gold: #CFB87C;\n}\n\n.bd-container {\n    max-width: 99%;\n}\n\n.bd-container .bd-container__inner {\n    max-width: 99%;\n}\n\n.bd-main .bd-content .bd-article-container {\n    max-width: 100em;\n}\n\n.code-block-caption {\n    color: black;\n}\n\n.bd-sidebar-primary li.has-children>details>summary .toctree-toggle {\n    justify-content: left;\n}\n\nhtml[data-theme=light] {\n    --pst-font-size-base: none;\n    --pst-color-secondary: #176de8;\n    --pst-color-primary: #176de8;\n}\n\n.graph#doc-flowchart .node text {\n    font-weight: bold;\n}\n\n.bd-content .sd-tab-set .sd-tab-content {\n    padding: 1.5rem;\n}\n\na {\n    text-decoration: none;\n}\n\na:hover {\n    text-decoration: underline;\n}\n\nbutton.theme-switch-button {\n    display: none !important;\n}\n\nblockquote {\n  background-color: transparent;\n  border: none;\n}\n"
  },
  {
    "path": "docs/_templates/carbonad.html",
    "content": "<script async type=\"text/javascript\" src=\"https://cdn.carbonads.com/carbon.js?serve={{ carbonad_serve }}&placement={{ carbonad_placement }}\" id=\"_carbonads_js\"></script>\n"
  },
  {
    "path": "docs/_templates/cheatsheets.html",
    "content": "<h3>Cheat Sheets</h3>\n<ul>\n  <li><a href=\"https://www.cppcheatsheet.com\">C/C++ cheat sheet</a></li>\n</ul>\n"
  },
  {
    "path": "docs/_templates/layout.html",
    "content": "{% extends \"!layout.html\" %}\n{%- block extrahead %}\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<meta name=\"description\" content=\"{% if pagename == 'index' %}Comprehensive Python cheat sheet with practical code snippets for developers. Learn Python basics, advanced topics, databases, networking, and more.{% else %}{{ title }} - Python code examples and snippets from the comprehensive Python cheat sheet.{% endif %}\">\n<meta name=\"keywords\" content=\"Python, Python cheat sheet, Python tutorial, Python examples, Python code snippets, programming, development\">\n<meta name=\"author\" content=\"crazyguitar\">\n<meta name=\"robots\" content=\"index, follow\">\n{%- if pagename == 'index' %}\n<link rel=\"canonical\" href=\"https://www.pythonsheets.com/\"/>\n<meta property=\"og:url\" content=\"https://www.pythonsheets.com/\">\n<meta property=\"og:description\" content=\"Comprehensive Python cheat sheet with practical code snippets for developers. Learn Python basics, advanced topics, databases, networking, and more.\">\n{%- elif pagename == '404' -%}\n{%- else %}\n<link rel=\"canonical\" href=\"https://www.pythonsheets.com/{{ pagename }}{{ file_suffix }}\"/>\n<meta property=\"og:url\" content=\"https://www.pythonsheets.com/{{ pagename }}{{ file_suffix }}\">\n<meta property=\"og:description\" content=\"{{ title }} - Python code examples and snippets from the comprehensive Python cheat sheet.\">\n{%- endif %}\n<meta property=\"og:type\" content=\"article\">\n<meta property=\"og:title\" content=\"{{ title }}{{ titlesuffix }}\">\n<meta property=\"og:site_name\" content=\"Python Cheat Sheet\">\n<meta name=\"twitter:card\" content=\"summary\">\n<meta name=\"twitter:title\" content=\"{{ title }}{{ titlesuffix }}\">\n<meta name=\"twitter:description\" content=\"{% if pagename == 'index' %}Comprehensive Python cheat sheet with practical code snippets for developers{% else %}{{ title }} - Python code examples and snippets{% endif %}\">\n{%- if tracking_id %}\n<!-- Global site tag (gtag.js) - Google Analytics -->\n<script async src=\"https://www.googletagmanager.com/gtag/js?id={{ tracking_id }}\"></script>\n<script>\n  window.dataLayer = window.dataLayer || [];\n  function gtag(){dataLayer.push(arguments);}\n  gtag('js', new Date());\n\n  gtag('config', '{{ tracking_id }}');\n</script>\n{% endif -%}\n{%- if pagename == '404' -%}\n<script>\n  gtag('event', '404', {\n    'event_category': 'error',\n    'event_label': document.referrer,\n    'non_interaction': true\n  });\n</script>\n{%- endif -%}\n{% endblock %}\n"
  },
  {
    "path": "docs/_templates/link.html",
    "content": "<h3>Useful Links</h3>\n<ul>\n  <li><a href=\"https://www.pythonsheets.com\">pysheeet website</a></li>\n  <li><a href=\"https://github.com/crazyguitar/pysheeet\">pysheeet @ GitHub</a></li>\n  <li><a href=\"https://github.com/crazyguitar/pysheeet/issues\">Issue Tracker</a></li>\n  <li><a href=\"https://media.readthedocs.org/pdf/pysheeet/latest/pysheeet.pdf\">pysheeet as a PDF</a></li>\n</ul>\n"
  },
  {
    "path": "docs/_templates/sidebarintro.html",
    "content": "<p>This project tries to provide many snippets of Python code that make life easier.</p>\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# python-cheatsheet documentation build configuration file, created by\n# sphinx-quickstart on Sun Feb 28 09:26:04 2016.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nfrom datetime import datetime\nimport os\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n# sys.path.insert(0, os.path.abspath('.'))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.todo',\n    'sphinx.ext.coverage',\n    'sphinx.ext.viewcode',\n    'myst_parser',\n    'sphinx_copybutton',\n    'sphinx.ext.graphviz',\n    'sphinx_design',\n    'sphinx.ext.extlinks'\n]\n\nmyst_enable_extensions = [\n    \"colon_fence\",\n    \"attrs_inline\",\n    \"attrs_block\",\n    \"tasklist\",\n    \"substitution\",\n]\n\nmyst_enable_checkboxes = True\nmyst_heading_anchors = 6\ncopybutton_prompt_text = r'^\\$ '\ncopybutton_prompt_is_regexp = True\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nyear = datetime.now().year\nproject = u'pysheeet'\ncopyright = u'2016-{}, crazyguitar'.format(year)\nauthor = u'crazyguitar'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = u'0.1.0'\n# The full version, including alpha/beta/rc tags.\nrelease = u'0.1.0'\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = 'en'\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = []\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = True\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'sphinx_book_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\nhtml_theme_options = {\n  \"repository_url\": \"https://github.com/crazyguitar/pysheeet\",\n  \"use_repository_button\": True,\n}\n\n# Custom sidebar templates\n\n# Add any paths that contain custom themes here, relative to this directory.\n#html_theme_path = []\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\nhtml_title = \"Python Cheat Sheet\"\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\nhtml_logo = \"_static/logo.svg\"\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\nhtml_favicon = '_static/favicon.ico'\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\nhtml_css_files = ['style.css']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\nhtml_extra_path = ['_extra']\nhtml_context = {\n    \"tracking_id\": os.environ.get(\"TRACKING_ID\"),\n}\n\nhas_carbonad = os.environ.get(\"CARBONAD_SERVE\") and os.environ.get(\"CARBONAD_PLACEMENT\")\nif has_carbonad:\n    html_context[\"carbonad_serve\"] = os.environ.get(\"CARBONAD_SERVE\")\n    html_context[\"carbonad_placement\"] = os.environ.get(\"CARBONAD_PLACEMENT\")\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'python-cheatsheetdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n\n# Latex figure (float) alignment\n#'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (master_doc, 'python-cheatsheet.tex', u'python-cheatsheet Documentation',\n     u'crazyguitar', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'python-cheatsheet', u'python-cheatsheet Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (master_doc, 'python-cheatsheet', u'python-cheatsheet Documentation',\n     author, 'python-cheatsheet', 'One line description of project.',\n     'Miscellaneous'),\n]\n\nhtml_sidebars = {\n    \"**\": [\n        \"navbar-logo.html\",\n        \"search-button-field.html\",\n        \"sbt-sidebar-nav.html\",\n        \"carbonad.html\",\n    ]\n}\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n\ndef add_html_link(app, pagename, templatename, context, doctree):\n    \"\"\"Append html page.\"\"\"\n    if pagename in ['404', 'search', 'genindex']:\n        return\n    app.sitemaps.append({\n        'pagename': pagename + \".html\",\n        'priority': '1.0' if pagename == 'index' else '0.8',\n        'changefreq': 'weekly' if pagename == 'index' else 'monthly'\n    })\n\n\ndef create_sitemap(app, exception):\n    \"\"\"Generate a sitemap.xml\"\"\"\n    from xml.etree.ElementTree import ElementTree, Element, SubElement\n    from datetime import datetime\n\n    r = Element(\"urlset\")\n    r.set(\"xmlns\", \"http://www.sitemaps.org/schemas/sitemap/0.9\")\n    r.set(\"xmlns:xsi\", \"http://www.w3.org/2001/XMLSchema-instance\")\n    r.set(\"xsi:schemaLocation\", \"http://www.sitemaps.org/schemas/sitemap/0.9\" +\n        \" http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\")\n\n    for link_info in app.sitemaps:\n        url = SubElement(r, \"url\")\n        now = datetime.now()\n        SubElement(url, \"loc\").text = app.pysheeet + link_info['pagename']\n        SubElement(url, \"lastmod\").text = now.date().isoformat()\n        SubElement(url, \"changefreq\").text = link_info['changefreq']\n        SubElement(url, \"priority\").text = link_info['priority']\n\n    f = app.outdir + \"/sitemap.xml\"\n    t = ElementTree(r)\n    t.write(f, xml_declaration=True, encoding='utf-8', method=\"xml\")\n\n\ndef setup(app):\n    \"\"\"Customize setup.\"\"\"\n    site = os.environ.get(\"PYSHEEET\")\n    if not site:\n        return\n\n    if site[-1] != '/':\n        site += '/'\n\n    # create a sitemap\n    app.pysheeet = site\n    app.sitemaps = []\n    app.connect('html-page-context', add_html_link)\n    app.connect('build-finished', create_sitemap)\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. python-cheatsheet documentation master file, created by\n   sphinx-quickstart on Sun Feb 28 09:26:04 2016.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\n.. meta::\n    :description lang=en: Comprehensive Python cheat sheet with practical code snippets, examples, and tutorials for Python developers. Learn Python basics, advanced topics, databases, networking, and more.\n    :keywords: Python, Python Cheat Sheet, Python Tutorial, Python Examples, Python Code Snippets, Programming, Development, Python Reference, Python Guide\n\n\nPython Cheat Sheet - Complete Guide with Code Examples\n======================================================\n\nWelcome to **pysheeet** - your ultimate Python cheat sheet! This comprehensive resource contains practical Python\ncode snippets, examples, and tutorials to make coding easier and more efficient for Python developers of all levels.\nFrom basic Python syntax to advanced topics like databases, networking, and multitasking, this cheat sheet serves as your\ncomplete Python reference guide. Ideal for beginners learning Python fundamentals and experienced developers seeking\nquick code examples. Whether you're learning Python for web development, data science, automation, or general programming,\nyou'll find practical examples that save you time and improve your coding efficiency.\n\nContributions are always welcome—feel free to share ideas for new snippets, improvements, or clearer explanations!\nIf you'd like to contribute, `fork pysheeet on GitHub`_.\nIf there is any question or suggestion, please create an issue on `GitHub Issues`_.\n\n.. _fork pysheeet on GitHub: https://github.com/crazyguitar/pysheeet\n.. _GitHub Issues: https://github.com/crazyguitar/pysheeet/issues\n\nPlugin\n------\n\n**pysheeet** is available as a `Claude Code <https://claude.com/claude-code>`_ plugin. Once installed,\nClaude automatically uses the cheat sheets to answer Python questions.\n\n.. code-block:: bash\n\n    # Step 1: Add the marketplace\n    claude plugin marketplace add crazyguitar/pysheeet\n\n    # Step 2: Install the plugin\n    claude plugin install pysheeet@pysheeet\n\nFor local testing and manual installation, see the main `README <https://github.com/crazyguitar/pysheeet/blob/master/README.rst>`_.\n\nWhat's New In Python 3\n----------------------\n\nThe official document, `What's New In Python`_, displays all of the most\nimportant changes. However, if you're too busy to read the whole changes,\nthis part provides a brief glance of new features in Python 3.\n\n.. _What's New In Python: https://docs.python.org/3/whatsnew/index.html\n\n.. toctree::\n   :maxdepth: 1\n\n   notes/python-new-py3\n\n\nPython Cheat Sheet\n------------------\n\nThis section focuses on commonly used Python code snippets. The cheat sheet\ncovers not only core Python features but also essential data structures,\nalgorithms, and frequently used modules to help programmers efficiently tackle\neveryday tasks.\n\n.. toctree::\n   :maxdepth: 1\n\n   notes/basic/index\n   notes/os/index\n   notes/concurrency/index\n   notes/asyncio/index\n   notes/network/index\n   notes/database/index\n   notes/security/index\n   notes/extension/index\n   notes/llm/index\n   notes/hpc/index\n   notes/appendix/index\n"
  },
  {
    "path": "docs/notes/appendix/disaggregated-prefill-decode.rst",
    "content": ".. meta::\n    :description lang=en: Evaluating disaggregated prefill/decode for LLM serving with vLLM, NIXL, and EFA on AWS\n    :keywords: LLM, vLLM, NIXL, disaggregated prefill decode, KV cache, EFA, inference serving\n\nIs Disaggregated Prefill/Decode a Silver Bullet for LLM Serving?\n================================================================\n\n:Date: 2026-03-10\n\nAbstract\n--------\n\nDisaggregated prefill/decode has gained traction as a promising architecture for\nLLM serving, separating the compute-intensive prefill phase from the\nmemory-bound decode phase onto dedicated node groups. Proponents argue that this\nseparation enables independent scaling and eliminates interference between the\ntwo phases. But is it truly a silver bullet? This article puts the claim to the\ntest by evaluating disaggregated prefill/decode using vLLM with NIXL over the\nAWS Elastic Fabric Adapter (EFA) on a 4-node cluster. We compare data\nparallelism and simple load-balanced routing as baselines against disaggregated\nconfigurations. Our results show that while disaggregation dramatically reduces\ninter-token latency (ITL), it comes at a significant cost to throughput and\ntime-to-first-token (TTFT), revealing that the architecture is far from a\nuniversal solution.\n\nIntroduction\n------------\n\nIn standard LLM serving, each node handles both prefill and decode for incoming\nrequests. The prefill phase is compute-bound and processes the entire input\nprompt in parallel, while the decode phase is memory-bandwidth-bound and\ngenerates tokens autoregressively. When both phases share the same GPU pool,\nlong prefill requests can block decode iterations, increasing inter-token\nlatency for concurrent requests.\n\nDisaggregated prefill/decode addresses this interference by assigning prefill\nand decode to separate node groups. After a prefill node completes prompt\nprocessing, the KV cache is transferred to a decode node via a high-bandwidth\ninterconnect. NIXL [1]_ (NVIDIA Inference Xfer Library) provides the KV cache\ntransfer mechanism, and on AWS, this transfer occurs over EFA using the\n``LIBFABRIC`` backend.\n\nThe appeal is intuitive: by isolating decode nodes from prefill interference,\ntoken generation should proceed at a steady, low-latency pace. However, this\nseparation introduces new costs — KV cache transfer overhead, prefill node\nsaturation at long input lengths, and reduced effective cluster capacity for\neach phase. The question is whether these trade-offs are worthwhile compared to\nsimpler alternatives like data parallelism or stateless load-balanced routing.\n\nThis experiment uses vLLM [2]_ with the\n``NixlConnector`` to orchestrate disaggregated serving, and ``vllm-router`` [3]_ as\na reverse proxy to load-balance requests across node groups. The experiment\ncode is available under `src/nixl <https://github.com/crazyguitar/pysheeet/tree/master/src/nixl>`_ in the companion repository.\n\nContainer Image\n---------------\n\nThe experiment uses a custom Docker image that bundles all required components.\nThe ``Dockerfile`` builds on ``nvidia/cuda:12.8.1-devel-ubuntu24.04`` and\ninstalls the following stack:\n\n- **GDRCopy** v2.5.1 for GPU-direct memory registration\n- **EFA installer** v1.47.0 for AWS Elastic Fabric Adapter support\n- **UCX** v1.20.0 built with verbs, rdmacm, and EFA transport\n- **NIXL** v0.10.1 with ``LIBFABRIC`` backend for KV cache transfer\n- **nixlbench** for standalone NIXL bandwidth/latency microbenchmarks\n- **PyTorch** 2.9.1, **flash-attn** 2.8.1, and **DeepGEMM** v2.1.1.post3\n- **vLLM** 0.15.1 with ``NixlConnector`` support\n- **vllm-router** for load-balancing across disaggregated node groups\n\nThe image is built and saved as a portable tarball via the ``Makefile``:\n\n.. code-block:: bash\n\n    make docker && make save\n\nThis produces ``nixl-latest.tar.gz``, which is distributed to all Slurm nodes\nat launch time via ``pigz`` decompression and ``docker load``.\n\nServing Script\n--------------\n\nThe ``vllm.sbatch`` script orchestrates multi-node vLLM serving on Slurm. It\naccepts two key flags that control the serving topology:\n\n- ``--route R``: splits the allocated nodes into ``R`` identical groups, each\n  running an independent vLLM instance. A ``vllm-router`` process on the head\n  node round-robins requests across groups.\n- ``--prefill P``: within each group, assigns ``P`` nodes as prefill-only\n  (``kv_producer``) and the remaining nodes as decode-only (``kv_consumer``).\n  KV cache transfer between prefill and decode nodes uses ``NixlConnector``\n  with the ``LIBFABRIC`` backend over EFA.\n\nWhen ``--prefill 0`` (default), all nodes in a group run standard data-parallel\nserving. The script computes ``DP = nodes_per_group * (8 / TP)`` and launches\nvLLM with ``--data-parallel-size`` accordingly.\n\nFor disaggregated mode, each prefill and decode node runs as an independent\nvLLM process with explicit KV transfer configuration:\n\n.. code-block:: bash\n\n    # Prefill node\n    vllm serve ... \\\n        --kv-transfer-config.kv_connector NixlConnector \\\n        --kv-transfer-config.kv_role kv_producer \\\n        --kv-transfer-config.kv_connector_extra_config.backends+ LIBFABRIC\n\n    # Decode node\n    vllm serve ... \\\n        --kv-transfer-config.kv_connector NixlConnector \\\n        --kv-transfer-config.kv_role kv_consumer \\\n        --kv-transfer-config.kv_connector_extra_config.backends+ LIBFABRIC\n\nThe router uses ``round_robin`` policy for pure-DP groups and\n``consistent_hash`` with ``--vllm-pd-disaggregation`` for PD groups, directing\ninitial requests to prefill endpoints and subsequent decode traffic to decode\nendpoints:\n\n.. code-block:: bash\n\n    # Router for pure-DP groups (round-robin across group endpoints)\n    vllm-router \\\n        --policy round_robin \\\n        --worker-urls http://<GROUP0_IP>:8000 http://<GROUP1_IP>:8001 \\\n        --host 0.0.0.0 --port 8010\n\n    # Router for PD disaggregation (consistent hash with prefill/decode split)\n    vllm-router \\\n        --policy consistent_hash \\\n        --vllm-pd-disaggregation \\\n        --prefill http://<PREFILL0_IP>:8000 \\\n        --decode http://<DECODE0_IP>:8001 --decode http://<DECODE1_IP>:8002 \\\n        --host 0.0.0.0 --port 8010\n\nEach container is launched with ``--privileged``, ``--net=host``, and explicit\n``/dev/infiniband/uverbs*`` and ``/dev/gdrdrv`` device mounts to enable\nGPU-direct RDMA over EFA.\n\nBenchmark Script\n----------------\n\nThe ``bench.sh`` script wraps ``vllm bench serve`` and handles Docker image\nloading transparently. If the ``vllm`` CLI is not available on the host, the\nscript re-executes itself inside the container. It points the benchmark client\nat the router endpoint (or the direct vLLM endpoint for single-group\nconfigurations):\n\n.. code-block:: bash\n\n    bash bench.sh -H <ROUTER_IP> -p <ROUTER_PORT> -- \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --dataset-name random \\\n        --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 1024\n\nExperimental Setup\n------------------\n\nAll experiments run on 4 nodes with 8 GPUs each (TP=8) using\nDeepSeek-V2-Lite as the model. The benchmark uses random input/output data\nwith 1024 prompts via ``vllm bench serve``.\n\nThe configurations are:\n\n- **Baseline (data parallelism)**: 4 nodes, TP=8, DP=4. All nodes serve both\n  prefill and decode. This is the standard data-parallel serving setup.\n- **Route 2**: 2 groups of 2 nodes each, TP=8, DP=2 per group. A router\n  round-robins requests across groups. Each group independently handles both\n  prefill and decode.\n- **Route 4**: 4 groups of 1 node each, TP=8, no data parallelism. A router\n  distributes requests across all 4 independent nodes.\n- **PD 1P3D**: Disaggregated prefill/decode with 1 prefill node and 3 decode\n  nodes. KV cache is transferred from the prefill node to decode nodes via NIXL.\n- **PD 2P2D**: Disaggregated prefill/decode with 2 prefill nodes and 2 decode\n  nodes.\n\n.. code-block:: bash\n\n    # Exp 1: Baseline — 4 nodes, TP=8, pure DP\n    salloc -N 4 bash vllm.sbatch \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --gpu-memory-utilization 0.9\n\n    # Exp 2: 2 groups × 2 nodes, DP=2 per group, router round-robins\n    salloc -N 4 bash vllm.sbatch --route 2 \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --gpu-memory-utilization 0.9\n\n    # Exp 3: 4 groups × 1 node, no DP, router round-robins\n    salloc -N 4 bash vllm.sbatch --route 4 \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --gpu-memory-utilization 0.9\n\n    # Exp 4: 1 prefill + 3 decode\n    salloc -N 4 bash vllm.sbatch --prefill 1 \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --gpu-memory-utilization 0.9\n\n    # Exp 5: 2 prefill + 2 decode\n    salloc -N 4 bash vllm.sbatch --prefill 2 \\\n        --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        --gpu-memory-utilization 0.9\n\nResults\n-------\n\nWe evaluate each configuration along four metrics: output token throughput,\nrequest throughput, time to first token (TTFT), and inter-token latency (ITL).\nEach plot contains two panels — the left panel sweeps input length with a fixed\noutput length of 256 tokens (prefill-dominated regime), while the right panel\nsweeps output length with a fixed input length of 512 tokens (decode-dominated\nregime). This allows us to observe how each configuration behaves when the\nworkload shifts from prefill-heavy to decode-heavy.\n\nMicrobenchmark: KV Cache Transfer Bandwidth\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBefore examining end-to-end serving results, we use ``nixlbench`` to measure\nthe raw NIXL transfer bandwidth over EFA between two nodes. This establishes\nan upper bound on KV cache transfer speed and helps contextualize the TTFT\noverhead observed in disaggregated configurations.\n\nThe benchmark runs in Multi-GPU (MG) mode with all 8 GPUs per node performing\nVRAM-to-VRAM transfers over the ``LIBFABRIC`` backend:\n\n.. code-block:: bash\n\n    salloc -N 2 bash nixl.sbatch --backend LIBFABRIC \\\n        --initiator_seg_type VRAM --target_seg_type VRAM \\\n        --mode MG --num_initiator_dev 8 --num_target_dev 8\n\n    Block Size (B)      Batch Size     B/W (GB/Sec)   Avg Lat. (us)  P99 Tx (us)\n    ---------------------------------------------------------------------------------\n    4096                1              0.670064       6.1            47.0\n    8192                1              1.315392       6.2            45.0\n    16384               1              2.511416       6.5            47.0\n    32768               1              4.820423       6.8            50.0\n    65536               1              8.733224       7.5            56.0\n    131072              1              12.341950      10.6           52.0\n    262144              1              23.272188      11.3           59.0\n    524288              1              43.365764      12.1           62.0\n    1048576             1              74.816773      14.0           77.0\n    2097152             1              121.086563     17.3           105.0\n    4194304             1              180.631395     23.2           146.0\n    8388608             1              239.037623     35.1           247.0\n    16777216            1              289.500030     58.0           432.0\n    33554432            1              327.436372     102.5          796.0\n    67108864            1              349.608429     192.0          1724.0\n\n**Mapping to DeepSeek-V2-Lite KV cache transfer.** DeepSeek-V2-Lite uses\nMulti-head Latent Attention (MLA), which compresses the KV cache into a latent\nvector per token per layer. The per-token-per-layer KV cache size is\n``(kv_lora_rank + qk_rope_head_dim) × dtype_size = (512 + 64) × 2 = 1,152 bytes``.\nFor 512 input tokens across 27 layers, the total KV cache is approximately **15.2 MB**.\nWith TP=8, each GPU transfers about **1.9 MB**, which falls in the ~121 GB/s\nbandwidth range per the table above. Without tensor parallelism, the full 15.2 MB\ntransfer achieves approximately ~289 GB/s.\n\nOutput Token Throughput\n~~~~~~~~~~~~~~~~~~~~~~~\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/nixl/throughput.png\n    :alt: Output token throughput comparison\n\nThe left panel varies input length with a fixed output length of 256 tokens\n(prefill-dominated), while the right panel varies output length with a fixed\ninput length of 512 tokens (decode-dominated).\n\nFor prefill-dominated workloads, Route 4 achieves the highest throughput since\neach node operates independently without the overhead of data parallelism\ncoordination. The disaggregated configurations (PD 1P3D and PD 2P2D) show\ncompetitive throughput at shorter input lengths but degrade at longer inputs\nwhere the prefill nodes become the bottleneck.\n\nFor decode-dominated workloads, Route 4 again leads, followed by PD 1P3D.\nPD 2P2D shows the lowest throughput in this regime, as its two decode nodes\ncannot match the decode capacity of other configurations.\n\nRequest Throughput\n~~~~~~~~~~~~~~~~~~\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/nixl/req_throughput.png\n    :alt: Request throughput comparison\n\nRequest throughput follows a similar pattern. Route 4 consistently achieves the\nhighest request throughput across all configurations. The disaggregated PD 1P3D\nconfiguration maintains reasonable request throughput for short inputs but drops\nsignificantly at longer input lengths (4096 tokens), where the single prefill\nnode becomes saturated.\n\nTime to First Token (TTFT)\n~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/nixl/ttft.png\n    :alt: TTFT comparison\n\nTTFT is critical for user-perceived latency. The baseline DP and Route 2\nconfigurations show moderate TTFT that scales with input length. Route 4\nachieves the lowest TTFT across all input lengths due to the absence of\ncross-node coordination.\n\nThe disaggregated configurations exhibit higher TTFT, particularly at longer\ninput lengths. PD 1P3D shows TTFT exceeding 37 seconds at 4096 input tokens,\nas all prefill work funnels through a single node. PD 2P2D improves on this\nbut still lags behind the non-disaggregated configurations. The additional\nlatency from KV cache transfer over NIXL contributes to the elevated TTFT.\n\nFor decode-dominated workloads (right panel), the differences are smaller. At\nshort output lengths (256–512 tokens), PD 1P3D shows 1–2 seconds higher TTFT\nthan the baseline, as the KV cache transfer overhead is proportionally more\nsignificant. At longer output lengths (1024+ tokens), the disaggregated\nconfigurations converge with or improve upon the baseline, as the baseline\nsuffers from increased prefill/decode contention under heavier concurrent\ndecode load.\n\nInter-Token Latency (ITL)\n~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/nixl/itl.png\n    :alt: ITL comparison\n\nITL measures the latency between consecutive generated tokens during the decode\nphase. This is where disaggregated serving shows its primary advantage.\n\nIn the prefill-dominated regime (left panel), PD 1P3D achieves the lowest ITL\nacross all input lengths, with mean ITL as low as 10 ms at 4096 input tokens.\nBy isolating decode nodes from prefill interference, the decode phase runs\nuninterrupted. PD 2P2D also shows reduced ITL compared to the baseline, though\nthe benefit is less pronounced due to having fewer decode nodes. The baseline DP\nand Route configurations show higher ITL, particularly at longer input lengths\nwhere prefill and decode contend for the same GPU resources.\n\nIn the decode-dominated regime (right panel), Route 4 achieves the lowest ITL\n(~25–29 ms) since each node serves independently without cross-node\ncoordination. Among the disaggregated configurations, PD 1P3D outperforms\nPD 2P2D due to its greater decode capacity (3 decode nodes vs. 2), maintaining\nITL around 26–35 ms. PD 2P2D, with only 2 decode nodes, shows ITL comparable\nto the baseline (~45–50 ms). As output length increases, ITL gradually rises\nacross all configurations, reflecting the growing decode load.\n\nDiscussion\n----------\n\nSo, is disaggregated prefill/decode a silver bullet? The answer is clearly no —\nat least not under the conditions tested here. All benchmarks use randomly\ngenerated prompts, meaning every request produces a unique KV cache with zero\nprefix cache hit rate. This represents a worst-case scenario for disaggregated\nserving, where every prefill must be computed from scratch and the full KV cache\nmust be transferred over the network. In production workloads with shared system\nprompts or repeated prefixes, prefix caching on prefill nodes could\nsubstantially reduce redundant computation and transfer volume, potentially\nshifting the balance in favor of disaggregation. Even so, the results reveal a\nset of sharp trade-offs that make disaggregation a specialized tool rather than\na universal improvement:\n\n- **ITL wins, but throughput depends on scaling**: Disaggregated configurations\n  deliver dramatically lower inter-token latency — PD 1P3D achieves as low as\n  10 ms ITL at long input lengths, up to 14× better than the baseline in\n  prefill-dominated regimes and 1.4–2.4× better in decode-dominated regimes.\n  The throughput and TTFT degradation observed here is partly an artifact of a\n  fixed 4-node cluster: dedicating nodes to one role starves the other. In\n  practice, prefill and decode pools can be scaled independently — adding more\n  prefill nodes to eliminate the prefill bottleneck, or more decode nodes to\n  increase token throughput. The challenge is finding the right ratio between\n  prefill and decode capacity for a given workload, as over-provisioning either\n  side increases cost without proportional benefit.\n\n- **Prefill bottleneck is a hard constraint**: With a fixed cluster size,\n  dedicating nodes to prefill reduces decode capacity and vice versa. PD 1P3D\n  suffers severe prefill saturation at long input lengths (TTFT > 37s at 4096\n  tokens), while PD 2P2D has fewer decode nodes, limiting decode throughput.\n  Frameworks such as `NVIDIA Dynamo <https://github.com/ai-dynamo/dynamo>`_\n  aim to address this by dynamically scaling prefill and decode pools based on\n  real-time demand, though this adds operational complexity.\n\n- **Simple routing beats disaggregation on throughput**: Route 4 (pure routing,\n  no DP, no disaggregation) consistently achieves the highest throughput across\n  all configurations by eliminating cross-node synchronization entirely. It also\n  achieves the lowest TTFT in prefill-dominated workloads, though PD 1P3D edges\n  it out on TTFT in decode-dominated regimes where the fixed 512-token input is\n  short enough to avoid prefill saturation. This is a surprisingly strong\n  baseline — for workloads where ITL is not the primary concern, stateless\n  load-balanced independent nodes outperform both data parallelism and\n  disaggregated configurations.\n\n- **KV cache transfer is not free**: The NIXL transfer over EFA adds measurable\n  latency to TTFT in disaggregated configurations. This overhead is amortized\n  for longer decode sequences but is noticeable for short output lengths,\n  making disaggregation less attractive for short-response workloads.\n\nIn summary, disaggregated prefill/decode aims to optimize both TTFT and ITL by\nisolating the two phases, but achieving these goals is not guaranteed. KV cache\ntransfer over the network introduces additional overhead that can negate the\nTTFT benefit, particularly at long input lengths where the transfer volume is\nlarge. While ITL improvements are consistently observed due to the elimination\nof prefill interference on decode nodes, the overall serving performance depends\nheavily on the prefill-to-decode ratio, workload characteristics, and network\nbandwidth. Teams considering this architecture should carefully profile their\ninput/output length distributions, latency SLAs, and throughput requirements\nbefore committing to the added complexity.\n\nReferences\n----------\n\n.. [1] NVIDIA, \"NIXL: NVIDIA Inference Xfer Library,\" GitHub, 2025.\n   https://github.com/ai-dynamo/nixl\n\n.. [2] vLLM Project, \"vLLM: Easy, fast, and cheap LLM serving,\" GitHub, 2024.\n   https://github.com/vllm-project/vllm\n\n.. [3] vLLM Project, \"vllm-router: Production-ready router for vLLM,\" GitHub, 2025.\n   https://github.com/vllm-project/vllm-router\n"
  },
  {
    "path": "docs/notes/appendix/index.rst",
    "content": ".. meta::\n    :description lang=en: Python appendix covering advanced topics including the walrus operator (PEP 572) and Python debugging with GDB\n    :keywords: Python, Python3, walrus operator, PEP 572, GDB, debugging, advanced Python\n\nBlog\n----\n\nThis section explores advanced programming topics to help users build a deeper\nunderstanding of complex concepts and practical techniques. Programmers working\nin other languages, such as C/C++, often use Python as a versatile debugging\ntool. With debuggers like GDB, they may write Python scripts to parse memory\nregions, improve output readability, or automate troubleshooting tasks.\n\nMore advanced topics and examples can be found in the following link.\n\n.. toctree::\n    :maxdepth: 1\n\n    disaggregated-prefill-decode\n    megatron-efa-monitoring\n    nccl-gin\n    python-walrus\n    python-gdb\n"
  },
  {
    "path": "docs/notes/appendix/megatron-efa-monitoring.rst",
    "content": ".. meta::\n    :description lang=en: Monitoring EFA network performance with NCCL GIN and Nsys during distributed LLM training on AWS\n    :keywords: EFA, NCCL, GIN, Nsys, Megatron-LM, distributed training, network monitoring, AWS\n\nMonitoring EFA with NCCL GIN and Nsys\n======================================\n\n:Date: 2026-02-28\n\nAbstract\n--------\n\nDistributed training at scale requires deep visibility into network behavior to\nidentify bottlenecks and optimize communication patterns. When training large\nlanguage models with Megatron-LM on AWS infrastructure using the Elastic Fabric\nAdapter (EFA), understanding network performance becomes critical for achieving\noptimal throughput. This article demonstrates how to enable NCCL GPU-Initiated\nNetworking (GIN) in Megatron-LM using Megatron Bridge and leverage Nsys with\nEFA metrics to monitor network behavior during distributed training workloads.\nThe techniques presented here are based on best practices from AWS re:Invent\n2024 [1]_.\n\nIntroduction\n------------\n\n`Megatron-LM <https://github.com/NVIDIA/Megatron-LM>`_ is a widely adopted\nframework for training large transformer models using model parallelism,\npipeline parallelism, and data parallelism. When deployed on AWS instances with\nEFA, the network fabric provides high-bandwidth, low-latency communication\nessential for scaling to hundreds or thousands of GPUs. However, achieving peak\nperformance requires careful tuning and monitoring of the communication layer.\n\nNCCL GPU-Initiated Networking allows GPUs to initiate network operations\ndirectly without CPU involvement, reducing latency and enabling kernel fusion.\nNsys (NVIDIA Nsight Systems) provides comprehensive profiling of GPU kernels,\nCUDA API calls, and network operations. When combined with EFA metrics\ncollection (``--enable efa_metrics``), Nsys captures detailed network adapter\nstatistics including bandwidth utilization, packet counts, and error rates,\ncorrelated with GPU execution timelines. This enables practitioners to diagnose\nperformance issues and validate that the network is operating at expected\ncapacity.\n\n`Megatron Bridge <https://github.com/NVIDIA/Megatron-LM/tree/main/megatron/bridge>`_\nsimplifies the configuration and deployment of Megatron-LM training jobs by\nproviding a high-level recipe-based interface. This eliminates the need to\nmanually construct complex command-line arguments and makes it easier to enable\nadvanced features like NCCL GIN and DeepEP for MoE models. Therefore, the\ntutorial in this article will use Megatron Bridge.\n\nPrerequisites\n-------------\n\nThis guide assumes the following environment:\n\n- AWS HyperPod or EC2 instances with EFA support (e.g., P5, P5e, P5en)\n- NCCL >= v2.29.3-1 with Device API support\n- aws-ofi-nccl plugin with GIN support\n- Megatron-LM with Megatron-Bridge\n\nWe have demonstrated how to use vLLM with NCCL GIN and DeepEP in a previous\narticle. If you are interested in building NCCL and aws-ofi-nccl from source,\nrefer to the `NCCL GIN article\n<https://www.pythonsheets.com/notes/appendix/nccl-gin.html>`_\nin this repository.\n\nBuilding the Megatron Container\n--------------------------------\n\nThe Megatron training environment is packaged as a Docker container and\nconverted to an Enroot squash file for deployment on Slurm clusters. The\ncontainer includes NCCL with Device API support, aws-ofi-nccl with GIN support,\nand Megatron-LM with Megatron Bridge.\n\nTo build the container and create the Enroot image:\n\n.. code-block:: bash\n\n    cd src/megatron\n    make build\n\nThis will create a ``megatron-lm+latest.sqsh`` file that can be used with the\nSlurm launcher scripts. For details on the container build process, refer to\nthe `Dockerfile\n<https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/Dockerfile>`_\nand `enroot.sh\n<https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/enroot.sh>`_\nscripts in the repository.\n\nEnabling NCCL GIN in Megatron Bridge\n-------------------------------------\n\nMegatron Bridge recipes provide a declarative way to configure training jobs.\nTo enable NCCL GIN for MoE models using DeepEP, the following environment\nvariables are set automatically by the ``srun.sh`` launcher script:\n\n.. code-block:: bash\n\n    export DEEP_EP_BACKEND=nccl\n    export NCCL_GIN_TYPE=2  # proxy-based GIN\n    export LD_LIBRARY_PATH=/opt/amazon/ofi-nccl/lib:$LD_LIBRARY_PATH\n\n``NCCL_GIN_TYPE=2`` selects the proxy-based implementation, where a CPU thread\nmediates GPU-initiated transfers. This mode is currently supported on EFA,\nwhile GPU Direct Async Kernel-Initiated (DAKI) networking (``NCCL_GIN_TYPE=3``)\nis not yet available on AWS at the time of writing (February 2026).\n\nThe ``srun.sh`` script also configures additional EFA-specific settings for\noptimal performance:\n\n.. code-block:: bash\n\n    export FI_PROVIDER=efa\n    export FI_EFA_USE_DEVICE_RDMA=1\n    export FI_EFA_FORK_SAFE=1\n    export NCCL_NET_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-net-ofi.so\n    export NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\n    export NCCL_BUFFSIZE=8388608\n    export NCCL_P2P_NET_CHUNKSIZE=524288\n\nLaunching Megatron Training with DeepEP and NCCL GIN\n-----------------------------------------------------\n\nThe following example demonstrates how to launch a DeepSeek-V2-Lite pretraining\njob with DeepEP enabled for MoE token dispatching. The recipe configures the\nmodel to use expert parallelism across 64 ranks with NCCL GIN for low-latency\nall-to-all communication.\n\n.. code-block:: bash\n\n    cd src/megatron\n\n    # Allocate 2 nodes on Slurm\n    salloc -N 2\n\n    # Launch DeepSeek-V2-Lite with DeepEP and NCCL GIN\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        moe_token_dispatcher_type=deepep \\\n        model.tensor_model_parallel_size=1 \\\n        model.expert_model_parallel_size=64 \\\n        model.sequence_parallel=false\n\nThe ``moe_token_dispatcher_type=deepep`` argument enables DeepEP as the MoE\ndispatcher backend. Under the hood, the recipe configures the following\nsettings:\n\n.. code-block:: python\n\n    cfg.model.moe_token_dispatcher_type = \"flex\"\n    cfg.model.moe_flex_dispatcher_backend = \"deepep\"\n    cfg.model.moe_enable_deepep = True\n    cfg.model.moe_shared_expert_overlap = False\n\nWhen the training job starts, verify that NCCL initializes with GIN enabled by\nchecking the logs for Device API initialization messages:\n\n.. code-block:: text\n\n    [NCCL] Device API initialized\n    [NCCL] GIN proxy mode enabled (type=2)\n    [NCCL Backend] LOW LATENCY MODE: Rank 0 connecting to all ranks\n    [NCCL Backend] Initialized global rank 0/64\n\nMonitoring EFA with Nsys and EFA Metrics\n-----------------------------------------\n\nNsys (NVIDIA Nsight Systems) provides comprehensive profiling of GPU kernels,\nCUDA API calls, and network operations. The ``--enable efa_metrics`` flag\ninstructs Nsys to collect EFA adapter statistics in real-time from the EFA\ndevice counters (e.g., rdmap113s0, rdmap114s0) at 10Hz sampling rate, including:\n\n- **TX/RX Bandwidth**: Transmit and receive throughput\n- **TX/RX Packets**: Packet counts sent and received\n- **Error Counters**: Link errors and dropped packets\n\nAdditionally, aws-ofi-nccl uses NVTX annotations to mark NCCL operations in the\ntimeline, allowing correlation between NCCL collective calls and EFA network\nactivity. These metrics are embedded in the Nsys timeline and correlated with\nGPU kernel execution and NCCL operations, making it easy to identify\ncommunication bottlenecks and validate network saturation.\n\nTo profile a Megatron training run with Nsys and capture EFA metrics:\n\n.. code-block:: bash\n\n    cd src/megatron\n    salloc -N 8\n\n    ./srun.sh --nsys recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        moe_token_dispatcher_type=deepep \\\n        model.tensor_model_parallel_size=1 \\\n        model.expert_model_parallel_size=64 \\\n        model.sequence_parallel=false \\\n        profiling.use_nsys_profiler=true \\\n        profiling.profile_step_start=10 \\\n        profiling.profile_step_end=15 \\\n        profiling.profile_ranks=[0]\n\nThe ``--nsys`` flag enables Nsys profiling with the following configuration:\n\n.. code-block:: bash\n\n    nsys profile \\\n        -t cuda,nvtx \\\n        -s none \\\n        --cpuctxsw=none \\\n        --capture-range=cudaProfilerApi \\\n        --capture-range-end=stop \\\n        --enable efa_metrics \\\n        -o nsys-megatron/profile-<hostname>-rank<RANK>.nsys-rep \\\n        --force-overwrite=true\n\nThe ``--enable efa_metrics`` flag is the key parameter that enables EFA adapter\nmonitoring. Nsys will automatically detect all EFA devices (typically\n``rdmap182s0``, ``rdmap183s0``, etc.) and collect statistics at regular intervals\nthroughout the profiling session.\n\nAfter profiling completes, the ``.nsys-rep`` files can be downloaded and opened\nin Nsight Systems GUI for analysis. The EFA metrics appear as additional rows\nin the timeline view, showing bandwidth and packet rate correlated with GPU\nkernel execution and NCCL collective operations.\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/deepep-nsys.png\n\nProfiling with Viztracer\n-------------------------\n\nFor Python-level profiling of the training loop, Megatron Bridge supports\nViztracer, a low-overhead tracing tool that captures function calls and timing\ninformation. This is useful for identifying CPU bottlenecks in data loading,\npreprocessing, or scheduler logic that may indirectly impact network\nperformance.\n\n.. code-block:: bash\n\n    salloc -N 2\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        train.train_iters=100 \\\n        profiling.use_viztracer=true \\\n        profiling.profile_step_start=10 \\\n        profiling.profile_step_end=15 \\\n        profiling.profile_ranks=[0]\n\nThe resulting ``.json`` trace files can be visualized in the Viztracer web UI\nor Chrome's ``chrome://tracing`` interface. By enabling ``log_torch``, Viztracer\ncan capture additional PyTorch-level details such as NCCL stream and CUDA stream\noperations, providing visibility into the execution flow of collective\ncommunications and GPU kernels. However, to observe detailed EFA adapter\nstatistics (bandwidth, packet counts, error counters), Nsys with\n``--enable efa_metrics`` remains the required tool.\n\nConclusion\n----------\n\nNsys profiling with ``--enable efa_metrics`` now provides the capability to\nmonitor both EFA adapter behavior and NCCL operations simultaneously during\ndistributed training. This visibility is essential for diagnosing whether long\nNCCL operation times are caused by actual EFA transmission delays or other\nissues such as CPU bottlenecks, memory contention, or suboptimal NCCL\nconfiguration. By examining the correlated timeline of GPU kernels, NCCL\ncollectives, and EFA bandwidth utilization, practitioners can pinpoint the root\ncause of performance bottlenecks and validate that the network fabric is\noperating at expected capacity.\n\nIn this article, we demonstrated this monitoring approach using Megatron-LM\nwith NCCL GIN and DeepEP as an example. The recipe-based approach of Megatron\nBridge simplifies the deployment of complex training configurations, making it\neasier to adopt advanced features like DeepEP and NCCL GIN for large-scale MoE\nmodel training while maintaining full observability into network performance.\n\nFor complete examples and scripts, refer to the `megatron directory\n<https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/>`_ in this\nrepository.\n\nReferences\n----------\n\n.. [1] `AWS re:Invent 2024 - CMP335: Drilling down into performance for distributed training <https://d1.awsstatic.com/onedam/marketing-channels/website/aws/en_US/events/approved/reinvent-2025/reinvent/2024/slides/cmp/CMP335_Drilling-down-into-performance-for-distributed-training.pdf>`_\n"
  },
  {
    "path": "docs/notes/appendix/nccl-gin.rst",
    "content": "\n.. meta::\n    :description lang=en: Enabling GPU-Initiated Networking for NCCL with DeepEP on AWS using EFA\n    :keywords: NCCL, GIN, GPU-Initiated Networking, DeepEP, EFA, AWS, MoE, HyperPod\n\nGPU-Initiated Networking for NCCL on AWS\n========================================\n\n:Date: 2026-02-22\n\nAbstract\n--------\n\nGPU-Initiated Networking (GIN) has attracted significant attention as a key\nenabler for kernel fusion in large language model (LLM) training and inference.\nMixture-of-Experts (MoE) architectures, such as DeepSeek-V3 and Qwen3-30B,\nrequire efficient token dispatching and combining across MoE layers.\nConventionally, inter-GPU communication is initiated by the CPU through\ncollective libraries such as NCCL or Gloo, necessitating explicit GPU\nsynchronization barriers and additional ``cudaLaunchKernel`` calls that\nintroduce non-trivial overhead. GPU-Initiated Networking eliminates this\nCPU-mediated round-trip by allowing data exchange to occur directly within CUDA\nkernels, thereby enabling kernel fusion and efficient CUDA Graph capture for\naccelerating end-to-end LLM layer computation. This article demonstrates how to\nenable NCCL GIN with DeepEP on AWS HyperPod Slurm using the AWS Elastic Fabric\nAdapter (EFA).\n\nIntroduction\n------------\n\nPrior to 2026, adopting DeepEP as a Mixture-of-Experts dispatch and combine\nbackend on AWS presented a significant challenge. The DeepEP kernel was\noriginally built on top of InfiniBand with a customized NVSHMEM implementation,\na transport layer unavailable on AWS infrastructure. This incompatibility\neffectively prevented users from leveraging DeepEP on instances equipped with\nthe Elastic Fabric Adapter (EFA). Recent collaborative efforts by NVIDIA and\nAmazon Annapurna Labs have addressed this gap by introducing GPU-Initiated\nNetworking support in NCCL and the EFA provider, enabling DeepEP to operate\nover EFA without relying on InfiniBand (see `DeepEP PR #521\n<https://github.com/deepseek-ai/DeepEP/pull/521>`_ and `aws-ofi-nccl PR #1069\n<https://github.com/aws/aws-ofi-nccl/pull/1069>`_). The following experiment\nbuilds upon these contributions to illustrate how to deploy DeepEP with NCCL\nGIN on AWS using EFA.\n\nBuild DeepEP\n------------\n\nBefore deploying DeepEP on AWS HyperPod Slurm, several components must be built\nfrom source. First, NCCL >= v2.29.3-1 is required, as this is the minimum\nversion that exposes the Device API needed for GPU-Initiated Networking. The\nbuild targets ``sm_90`` (NVIDIA H100) and ``sm_100`` (NVIDIA B200) compute\ncapabilities to ensure compatibility with current-generation GPU instances.\n\n.. code-block:: bash\n\n    NCCL_VERSION=v2.29.3-1\n    git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git /opt/nccl \\\n        && cd /opt/nccl \\\n        && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n        NVCC_GENCODE=\"-gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\nOptionally, the NCCL Device API examples can be built to verify that\nGPU-initiated communication functions correctly in the target environment. In\naddition, the latest release of nccl-tests (v2.17.9) ships with a GIN-enabled\nmicrobenchmark for the ``alltoall`` collective, which is useful for validating\ninter-GPU bandwidth and latency before running full-scale MoE workloads (see\n`nccl-tests alltoall.cu <https://github.com/NVIDIA/nccl-tests/blob/v2.17.9/src/alltoall.cu#L214-L291>`_).\n\n.. code-block:: bash\n\n    ## Build NCCL Device API examples\n    cd /opt/nccl/examples/06_device_api \\\n        && make -j $(nproc) NCCL_HOME=/opt/nccl/build CUDA_HOME=/usr/local/cuda MPI=1 MPI_HOME=/opt/amazon/openmpi\n\n    NCCL_TESTS_VERSION=v2.17.9\n    git clone -b ${NCCL_TESTS_VERSION} https://github.com/NVIDIA/nccl-tests.git /opt/nccl-tests \\\n        && cd /opt/nccl-tests \\\n        && make -j $(nproc) \\\n        MPI=1 \\\n        MPI_HOME=/opt/amazon/openmpi/ \\\n        CUDA_HOME=/usr/local/cuda \\\n        NCCL_HOME=/opt/nccl/build \\\n        NVCC_GENCODE=\"-gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\nTo test DeepEP on HyperPod Slurm, both DeepEP and aws-ofi-nccl must be pinned\nto specific commits that include the NCCL GIN transport path. The DeepEP fork\nby Aamir Shafi introduces an NCCL-based communication backend as an alternative\nto the original NVSHMEM/InfiniBand path, while the aws-ofi-nccl plugin provides\nthe libfabric-to-NCCL translation layer required for EFA. Note that the NCCL\nGIN implementation has since been merged into the aws-ofi-nccl main branch; the\ncommit hash is pinned here for reproducibility.\n\n.. code-block:: bash\n\n    ## Install DeepEP with NCCL GIN backend (PR #521)\n    unset NVSHMEM_DIR NVSHMEM_HOME \\\n        && export ENABLE_NCCL=1 \\\n        && export NCCL_DIR=/opt/nccl/build \\\n        && export LD_LIBRARY_PATH=/opt/nccl/build/lib:$LD_LIBRARY_PATH \\\n        && export LD_PRELOAD=/opt/nccl/build/lib/libnccl.so.2 \\\n        && git clone -b nccl https://github.com/aamirshafi/DeepEP.git /opt/DeepEP \\\n        && cd /opt/DeepEP \\\n        && git checkout 6d29f34 \\\n        && python3 setup.py build_ext --inplace \\\n        && pip install --break-system-packages --no-build-isolation .\n\n    AWS_OFI_NCCL_VERSION=5f4202f11db1585d878196db4430aeda0e834a0c\n    git clone https://github.com/aws/aws-ofi-nccl.git /tmp/aws-ofi-nccl \\\n        && cd /tmp/aws-ofi-nccl \\\n        && git checkout ${AWS_OFI_NCCL_VERSION} \\\n        && ./autogen.sh \\\n        && ./configure --prefix=/opt/amazon/ofi-nccl \\\n            --with-libfabric=/opt/amazon/efa \\\n            --with-cuda=/usr/local/cuda \\\n        && make -j$(nproc) \\\n        && make install \\\n        && rm -rf /tmp/aws-ofi-nccl\n\nFor a complete build with all necessary dependencies, refer to the\n`Dockerfile <https://github.com/crazyguitar/pysheeet/blob/master/src/gin/Dockerfile>`_\nprovided in this repository.\n\nTest NCCL GIN\n-------------\n\nWith the Docker image (or Enroot squash file) prepared in the previous section,\nNCCL GIN functionality can be validated on a Slurm cluster. The following\nexamples demonstrate how to launch the NCCL Device API samples and nccl-tests\nbenchmarks. The corresponding Slurm wrapper scripts are available under the\n`gin <https://github.com/crazyguitar/pysheeet/blob/master/src/gin/>`_ directory\nin this repository.\n\n.. code-block:: bash\n\n    make docker && make save  # build a docker image and import an Enroot squash file\n\n    # 01_allreduce_lsa (single node only)\n    salloc -N 1 ./run.enroot /opt/nccl/examples/06_device_api/01_allreduce_lsa/allreduce_lsa\n\n    # 01_allreduce_lsa (multi-node) — requires MNNVL (e.g. P6e-GB200), does NOT work over RDMA/EFA\n    salloc -N 2 ./run.enroot /opt/nccl/examples/06_device_api/01_allreduce_lsa/allreduce_lsa\n\n    # 02_alltoall_gin (multi-node)\n    salloc -N 2 ./run.enroot /opt/nccl/examples/06_device_api/02_alltoall_gin/alltoall_gin\n\n    # 03_alltoall_hybrid (multi-node)\n    salloc -N 2 ./run.enroot /opt/nccl/examples/06_device_api/03_alltoall_hybrid/alltoall_hybrid\n\nThe nccl-tests ``alltoall`` benchmark exposes two critical flags for selecting\nthe GIN transport mode and memory registration strategy:\n\nThe ``-D`` flag selects the device-side implementation for the ``alltoall``\ncollective:\n\n.. code-block:: text\n\n    -D 0 — Host API (default)\n    -D 1 — NVL simple (LSA/NVLink only)\n    -D 2 — NVL optimized (LSA/NVLink only)\n    -D 3 — GIN only (network)\n    -D 4 — Hybrid (LSA intra-node + GIN inter-node)\n\nThe ``-R`` flag controls memory registration. Symmetric memory allocation\n(``NCCL_MEM_SHARED``) is required for any device-side implementation\n(``-D > 0``), as it maps GPU memory across all ranks to enable direct\nremote read and write over the network:\n\n.. code-block:: text\n\n    -R 0 — no registration (default)\n    -R 1 — register memory with ncclMemAlloc\n    -R 2 — register memory with symmetric memory allocation (NCCL_MEM_SHARED)\n\nThe following examples launch the nccl-tests ``alltoall_perf`` benchmark in\nGIN-only mode (``-D 3``) and hybrid mode (``-D 4``), sweeping message sizes\nfrom 32 MB to 2048 MB. The ``--blocking 0`` flag enables non-blocking\ncollectives, which is representative of how MoE layers overlap communication\nwith computation in production workloads:\n\n.. code-block:: bash\n\n    # alltoall_perf with GIN (-D 3)\n    salloc -N 2 ./run.enroot /opt/nccl-tests/build/alltoall_perf \\\n      -D 3 -R 2 -b 32M -e 2048M -f 2 -n 1000 -w 10 --blocking 0\n\n    # alltoall_perf with Hybrid LSA+GIN (-D 4)\n    salloc -N 2 ./run.enroot /opt/nccl-tests/build/alltoall_perf \\\n      -D 4 -R 2 -b 32M -e 2048M -f 2 -n 1000 -w 10 --blocking 0\n\nServing MoE Models with vLLM and DeepEP over NCCL GIN\n-----------------------------------------------------\n\nWith NCCL GIN and EFA validated on AWS HyperPod Slurm, this section\ndemonstrates an end-to-end inference deployment using vLLM with DeepEP as the\nMoE all-to-all communication backend. DeepEP's low-latency dispatch and combine\nkernels, now operating over NCCL GIN rather than NVSHMEM, enable efficient\nexpert-parallel inference for large MoE models such as DeepSeek-V3.\n\nThe Slurm launch script ``run.sbatch`` is the same one used to launch a vLLM\nserver in the `vllm example directory\n<https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/>`_. However,\nto direct the DeepEP backend to use NCCL GIN, the following environment\nvariables must be set at launch time:\n\n.. code-block:: bash\n\n    DEEP_EP_BACKEND=nccl\n    NCCL_GIN_TYPE=2  # proxy-based GIN\n\n``NCCL_GIN_TYPE=2`` selects the proxy-based GIN path, in which a CPU-side proxy\nthread mediates network transfers on behalf of the GPU. ``NCCL_GIN_TYPE=3``\nwould enable GPU Direct Async Kernel-Initiated (DAKI) networking, which\nbypasses the proxy entirely; however, DAKI is not yet supported on AWS with EFA\nat the time of writing.\n\nFor additional details on serving configurations and benchmarking, refer to\n`llm-serving.rst\n<https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-serving.rst>`_\nor the `vLLM README\n<https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/README.rst>`_.\n\nThe following example launches a multi-node vLLM inference server for\nDeepSeek-V3-0324 with expert parallelism enabled and the DeepEP low-latency\nall-to-all backend:\n\n.. code-block:: bash\n\n   IMAGE=\"${PWD}/src/gin/nccl+latest.tar.gz\"\n   MODEL=\"/fsx/models/deepseek-ai/DeepSeek-V3-0324\"\n\n   salloc -N 4 bash run.sbatch \"${MODEL}\" \\\n     --image \"${IMAGE}\" \\\n     --all2all-backend deepep_low_latency \\\n     --tensor-parallel-size 8 \\\n     --enable-expert-parallel \\\n     --gpu-memory-utilization 0.8 \\\n     --enforce-eager\n\nUpon successful launch, the vLLM server logs confirm that DeepEP is active as\nthe all-to-all backend and that NCCL GIN is being used for inter-GPU\ncommunication. The key indicators are the ``DeepEPLLAll2AllManager`` manager\nselection and the ``[NCCL Backend]`` initialization messages showing\ncommunicator setup, symmetric memory allocation, and window registration across\nall ranks:\n\n.. code-block:: bash\n\n    ...\n    INFO 02-22 19:06:49 [serve.py:100] Defaulting api_server_count to data_parallel_size (4).\n    INFO 02-22 19:06:49 [utils.py:325]\n    INFO 02-22 19:06:49 [utils.py:325]        █     █     █▄   ▄█\n    INFO 02-22 19:06:49 [utils.py:325]  ▄▄ ▄█ █     █     █ ▀▄▀ █  version 0.15.1\n    INFO 02-22 19:06:49 [utils.py:325]   █▄█▀ █     █     █     █  model   /fsx/models/deepseek-ai/DeepSeek-V3-0324\n    INFO 02-22 19:06:49 [utils.py:325]    ▀▀  ▀▀▀▀▀ ▀▀▀▀▀ ▀     ▀\n    INFO 02-22 19:06:49 [utils.py:325]\n    ...\n    INFO 02-22 19:07:51 [cuda_communicator.py:124] Using DeepEPLLAll2AllManager all2all manager.\n    ...\n    [NCCL Backend] LOW LATENCY MODE: Rank 0 connecting to all 32 ranks\n    [NCCL Backend] NCCL version: 2.29.3 (loaded library)\n    [NCCL Backend] Initializing 2 communicator(s) (qps_per_rank=8) for rank 0/32\n    [NCCL Backend] Rank 0 successfully initialized 2 communicator(s)\n    [NCCL Backend] Rank 0 created 2 device communication(s) with 32 barrier sessions each\n    [NCCL Backend] Initialized global rank 0/32 (comm rank 0/32)\n    [NCCL Backend - Memory Alloc] Rank 0: Allocated ptr=0xf882000000, size=3816818816\n    [NCCL Backend - Memory Register] Rank 0: Copying 2 NCCL windows to GPU\n    [NCCL Backend - Memory Register] Rank 0: Successfully copied windows to GPU\n    [NCCL Backend - Memory Register] Rank 0: Registered windows for ptr=0xf882000000, size=3816818816\n\nOnce the server is ready, inference requests can be issued via the\nOpenAI-compatible completions API:\n\n.. code-block:: bash\n\n    curl -sf -X POST http://<VLLM_HOST>:8000/v1/completions \\\n      -H 'Content-Type: application/json' \\\n      -d '{\n        \"model\": \"/fsx/models/deepseek-ai/DeepSeek-V3-0324\",\n        \"prompt\": \"Hello\",\n        \"max_tokens\": 10\n      }'\n\n      # output\n      {\"id\":\"cmpl-b6e9530a07561f11\",\"object\":\"text_completion\" ... }\n\nConclusion\n----------\n\nThis article has demonstrated how to deploy vLLM with DeepEP and NCCL GIN on\nAWS HyperPod Slurm using the Elastic Fabric Adapter. As this integration is\nstill under active development, certain limitations remain at the time of\nwriting. For instance, although DeepEP's low-latency mode supports CUDA Graph\ncapture, enabling it by removing ``--enforce-eager`` currently results in a\nstartup failure in vLLM. Additionally, performance over EFA may not yet match\nthat of InfiniBand-based deployments, as further optimizations are ongoing.\n\nThis article is intended as an early reference for evaluating DeepEP with NCCL\nGIN on AWS. For production workloads, it is advisable to wait for official\nstable releases from NVIDIA and Amazon Annapurna Labs.\n"
  },
  {
    "path": "docs/notes/appendix/python-gdb.rst",
    "content": ".. meta::\n    :description lang=en: Python interpreter in GNU Debugger (GDB)\n    :keywords: Python, Python3, GDB\n\n==================================\nPython Interpreter in GNU Debugger\n==================================\n\n:Date: 2025-08-30\n\nAbstract\n--------\n\nThe GNU Debugger (GDB) is the most powerful debugging tool for developers to\ntroubleshoot errors in their code. However, it is hard for beginners to learn,\nand that is why many programmers prefer to insert ``print`` to examine runtime\nstatus. Fortunately, `GDB Text User Interface (TUI)`_ provides a way for\ndevelopers to review their source code and debug simultaneously. More\nexcitingly, In GDB 7, **Python Interpreter** was built into GDB. This feature\noffers more straightforward ways to customize GDB printers and commands through\nthe Python library. By discussing examples, this article tries to explore\nadvanced debugging techniques via Python to develop tool kits for GDB.\n\nIntroduction\n------------\n\nTroubleshooting software bugs is a big challenge for developers. While GDB\nprovides many “debug commands” to inspect programs’ runtime status, its\nnon-intuitive usages impede programmers to use it to solve problems. Indeed,\nmastering GDB is a long-term process. However, a quick start is not complicated;\nyou must unlearn what you have learned like Yoda. To better understand how to\nuse Python in GDB, this article will focus on discussing Python interpreter in\nGDB.\n\nDefine Commands\n---------------\n\nGDB supports customizing commands by using ``define``. It is useful to run a\nbatch of commands to troubleshoot at the same time. For example, a developer\ncan display the current frame information by defining a ``sf`` command.\n\n.. code-block:: bash\n\n    # define in .gdbinit\n    define sf\n      where        # find out where the program is\n      info args    # show arguments\n      info locals  # show local variables\n    end\n\nHowever, writing a user-defined command may be inconvenient due to limited APIs.\nFortunately, by interacting with Python interpreter in GDB, developers can\nutilize Python libraries to establish their debugging tool kits readily. The\nfollowing sections show how to use Python to simplify debugging processes.\n\nDump Memory\n-----------\n\nInspecting a process’s memory information is an effective way to troubleshoot\nmemory issues. Developers can acquire memory contents by ``info proc mappings``\nand ``dump memory``. To simplify these steps, defining a customized command is\nuseful. However, the implementation is not straightforward by using pure GDB\nsyntax. Even though GDB supports conditions, processing output is not intuitive.\nTo solve this problem, using Python API in GDB would be helpful because Python\ncontains many useful operations for handling strings.\n\n.. code-block:: python\n\n    # mem.py\n    import gdb\n    import time\n    import re\n\n    class DumpMemory(gdb.Command):\n        \"\"\"Dump memory info into a file.\"\"\"\n\n        def __init__(self):\n            super().__init__(\"dm\", gdb.COMMAND_USER)\n\n        def get_addr(self, p, tty):\n            \"\"\"Get memory addresses.\"\"\"\n            cmd = \"info proc mappings\"\n            out = gdb.execute(cmd, tty, True)\n            addrs = []\n            for l in out.split(\"\\n\"):\n                if re.match(f\".*{p}*\", l):\n                    s, e, *_ = l.split()\n                    addrs.append((s, e))\n            return addrs\n\n        def dump(self, addrs):\n            \"\"\"Dump memory result.\"\"\"\n            if not addrs:\n                return\n\n            for s, e in addrs:\n                f = int(time.time() * 1000)\n                gdb.execute(f\"dump memory {f}.bin {s} {e}\")\n\n        def invoke(self, args, tty):\n            try:\n                # cat /proc/self/maps\n                addrs = self.get_addr(args, tty)\n                # dump memory\n                self.dump(addrs)\n            except Exception as e:\n                print(\"Usage: dm [pattern]\")\n\n    DumpMemory()\n\n\nRunning the ``dm`` command will invoke ``DumpMemory.invoke``. By sourcing\nor implementing Python scripts in *.gdbinit*, developers can utilize\nuser-defined commands to trace bugs when a program is running. For example, the\nfollowing steps show how to invoke ``DumpMemory`` in GDB.\n\n.. code-block:: bash\n\n    (gdb) start\n    ...\n    (gdb) source mem.py  # source commands\n    (gdb) dm stack       # dump stack to ${timestamp}.bin\n    (gdb) shell ls       # ls current dir\n    1577283091687.bin  a.cpp  a.out  mem.py\n\nDump JSON\n---------\n\nParsing JSON is helpful when a developer is inspecting a JSON string in a\nrunning program. GDB can parse a ``std::string`` via ``gdb.parse_and_eval``\nand return it as a ``gdb.Value``. By processing ``gdb.Value``, developers can\npass a JSON string into Python ``json`` API and print it in a pretty format.\n\n.. code-block:: python\n\n    # dj.py\n    import gdb\n    import re\n    import json\n\n    class DumpJson(gdb.Command):\n        \"\"\"Dump std::string as a styled JSON.\"\"\"\n\n        def __init__(self):\n            super().__init__(\"dj\", gdb.COMMAND_USER)\n\n        def get_json(self, args):\n            \"\"\"Parse std::string to JSON string.\"\"\"\n            ret = gdb.parse_and_eval(args)\n            typ = str(ret.type)\n            if re.match(\"^std::.*::string\", typ):\n                return json.loads(str(ret))\n            return None\n\n        def invoke(self, args, tty):\n            try:\n                # string to json string\n                s = self.get_json(args)\n                # json string to object\n                o = json.loads(s)\n                print(json.dumps(o, indent=2))\n            except Exception as e:\n                print(f\"Parse json error! {args}\")\n\n    DumpJson()\n\nThe command ``dj`` displays a more readable JSON format in GDB. This command\nhelps improve visual recognization when a JSON string large. Also, by using\nthis command, it can detect or monitor whether a ``std::string`` is JSON or\nnot.\n\n.. code-block:: bash\n\n    (gdb) start\n    (gdb) list\n    1       #include <string>\n    2\n    3       int main(int argc, char *argv[])\n    4       {\n    5           std::string json = R\"({\"foo\": \"FOO\",\"bar\": \"BAR\"})\";\n    6           return 0;\n    7       }\n    ...\n    (gdb) ptype json\n    type = std::string\n    (gdb) p json\n    $1 = \"{\\\"foo\\\": \\\"FOO\\\",\\\"bar\\\": \\\"BAR\\\"}\"\n    (gdb) source dj.py\n    (gdb) dj json\n    {\n      \"foo\": \"FOO\",\n      \"bar\": \"BAR\"\n    }\n\nHighlight Syntax\n----------------\n\nSyntax highlighting is useful for developers to trace source code or to\ntroubleshoot issues. By using `Pygments`_, applying color to the source is easy\nwithout defining ANSI escape code manually. The following example shows how to\napply color to the ``list`` command output.\n\n.. code-block:: python\n\n    import gdb\n\n    from pygments import highlight\n    from pygments.lexers import CLexer\n    from pygments.formatters import TerminalFormatter\n\n    class PrettyList(gdb.Command):\n        \"\"\"Print source code with color.\"\"\"\n\n        def __init__(self):\n            super().__init__(\"pl\", gdb.COMMAND_USER)\n            self.lex = CLexer()\n            self.fmt = TerminalFormatter()\n\n        def invoke(self, args, tty):\n            try:\n                out = gdb.execute(f\"l {args}\", tty, True)\n                print(highlight(out, self.lex, self.fmt))\n            except Exception as e:\n                print(e)\n\n    PrettyList()\n\nTracepoints\n-----------\n\nAlthough a developer can insert ``printf``, ``std::cout``, or ``syslog`` to\ninspect functions, printing messages is not an effective way to debug when a\nproject is enormous. Developers may waste their time in building source code\nand may acquire little information. Even worse, the output may become too much\nto detect problems. In fact, inspecting functions or variables do not require\nto embed *print functions* in code. By writing a Python script with GDB API,\ndevelopers can customize watchpoints to trace issues dynamically at runtime.\nFor example, by implementing a ``gdb.Breakpoint`` and a ``gdb.Command``, it is\nuseful for developers to acquire essential information, such as parameters,\ncall stacks, or memory usage.\n\n.. code-block:: python\n\n    # tp.py\n    import gdb\n\n    tp = {}\n\n    class Tracepoint(gdb.Breakpoint):\n        def __init__(self, *args):\n            super().__init__(*args)\n            self.silent = True\n            self.count = 0\n\n        def stop(self):\n            self.count += 1\n            frame = gdb.newest_frame()\n            block = frame.block()\n            sym_and_line = frame.find_sal()\n            framename = frame.name()\n            filename = sym_and_line.symtab.filename\n            line = sym_and_line.line\n            # show tracepoint info\n            print(f\"{framename} @ {filename}:{line}\")\n            # show args and vars\n            for s in block:\n                if not s.is_argument and not s.is_variable:\n                    continue\n                typ = s.type\n                val = s.value(frame)\n                size = typ.sizeof\n                name = s.name\n                print(f\"\\t{name}({typ}: {val}) [{size}]\")\n            # do not stop at tracepoint\n            return False\n\n    class SetTracepoint(gdb.Command):\n        def __init__(self):\n            super().__init__(\"tp\", gdb.COMMAND_USER)\n\n        def invoke(self, args, tty):\n            try:\n                global tp\n                tp[args] = Tracepoint(args)\n            except Exception as e:\n                print(e)\n\n    def finish(event):\n        for t, p in tp.items():\n            c = p.count\n            print(f\"Tracepoint '{t}' Count: {c}\")\n\n    gdb.events.exited.connect(finish)\n    SetTracepoint()\n\nInstead of inserting ``std::cout`` at the beginning of functions, using a\ntracepoint at a function's entry point provides useful information to inspect\narguments, variables, and stacks. For instance, by setting a tracepoint at\n``fib``, it is helpful to examine memory usage, stack, and the number of calls.\n\n.. code-block:: cpp\n\n    int fib(int n)\n    {\n        if (n < 2) {\n            return 1;\n        }\n        return fib(n-1) + fib(n-2);\n    }\n\n    int main(int argc, char *argv[])\n    {\n        fib(3);\n        return 0;\n    }\n\nThe following output shows the result of an inspection of the function ``fib``.\nIn this case, tracepoints display all information a developer needs, including\narguments' value, recursive flow, and variables' size. By using tracepoints,\ndevelopers can acquire more useful information comparing with ``std::cout``.\n\n.. code-block:: bash\n\n    (gdb) source tp.py\n    (gdb) tp main\n    Breakpoint 1 at 0x647: file a.cpp, line 12.\n    (gdb) tp fib\n    Breakpoint 2 at 0x606: file a.cpp, line 3.\n    (gdb) r\n    Starting program: /root/a.out\n    main @ a.cpp:12\n            argc(int: 1) [4]\n            argv(char **: 0x7fffffffe788) [8]\n    fib @ a.cpp:3\n            n(int: 3) [4]\n    fib @ a.cpp:3\n            n(int: 2) [4]\n    fib @ a.cpp:3\n            n(int: 1) [4]\n    fib @ a.cpp:3\n            n(int: 0) [4]\n    fib @ a.cpp:3\n            n(int: 1) [4]\n    [Inferior 1 (process 5409) exited normally]\n    Tracepoint 'main' Count: 1\n    Tracepoint 'fib' Count: 5\n\nProfiling\n---------\n\nWithout inserting timestamps, profiling is still feasible through tracepoints.\nBy using a ``gdb.FinishBreakpoint`` after a ``gdb.Breakpoint``, GDB sets a\ntemporary breakpoint at the return address of a frame for developers to get\nthe current timestamp and to calculate the time difference. Note that profiling\nvia GDB is not precise. Other tools, such as `Linux perf`_ or `Valgrind`_,\nprovide more useful and accurate information to trace performance issues.\n\n.. code-block:: python\n\n    import gdb\n    import time\n\n    class EndPoint(gdb.FinishBreakpoint):\n        def __init__(self, breakpoint, *a, **kw):\n            super().__init__(*a, **kw)\n            self.silent = True\n            self.breakpoint = breakpoint\n\n        def stop(self):\n            # normal finish\n            end = time.time()\n            start, out = self.breakpoint.stack.pop()\n            diff = end - start\n            print(out.strip())\n            print(f\"\\tCost: {diff}\")\n            return False\n\n    class StartPoint(gdb.Breakpoint):\n        def __init__(self, *a, **kw):\n            super().__init__(*a, **kw)\n            self.silent = True\n            self.stack = []\n\n        def stop(self):\n            start = time.time()\n            # start, end, diff\n            frame = gdb.newest_frame()\n            sym_and_line = frame.find_sal()\n            func = frame.function().name\n            filename = sym_and_line.symtab.filename\n            line = sym_and_line.line\n            block = frame.block()\n\n            args = []\n            for s in block:\n                if not s.is_argument:\n                    continue\n                name = s.name\n                typ = s.type\n                val = s.value(frame)\n                args.append(f\"{name}: {val} [{typ}]\")\n\n            # format\n            out = \"\"\n            out += f\"{func} @ {filename}:{line}\\n\"\n            for a in args:\n                out += f\"\\t{a}\\n\"\n\n            # append current status to a breakpoint stack\n            self.stack.append((start, out))\n            EndPoint(self, internal=True)\n            return False\n\n    class Profile(gdb.Command):\n        def __init__(self):\n            super().__init__(\"prof\", gdb.COMMAND_USER)\n\n        def invoke(self, args, tty):\n            try:\n                StartPoint(args)\n            except Exception as e:\n                print(e)\n\n    Profile()\n\n\nThe following output shows the profiling result by setting a tracepoint at the\nfunction ``fib``. It is convenient to inspect the function's performance and\nstack at the same time.\n\n.. code-block:: bash\n\n    (gdb) source prof.py\n    (gdb) prof fib\n    Breakpoint 1 at 0x606: file a.cpp, line 3.\n    (gdb) r\n    Starting program: /root/a.out\n    fib(int) @ a.cpp:3\n            n: 1 [int]\n            Cost: 0.0007786750793457031\n    fib(int) @ a.cpp:3\n            n: 0 [int]\n            Cost: 0.002572298049926758\n    fib(int) @ a.cpp:3\n            n: 2 [int]\n            Cost: 0.008517265319824219\n    fib(int) @ a.cpp:3\n            n: 1 [int]\n            Cost: 0.0014069080352783203\n    fib(int) @ a.cpp:3\n            n: 3 [int]\n            Cost: 0.01870584487915039\n\nPretty Print\n------------\n\nAlthough ``set print pretty on`` in GDB offers a better format to inspect\nvariables, developers may require to parse variables' value for readability.\nTake the system call ``stat`` as an example. While it provides useful information\nto examine file attributes, the output values, such as the permission, may not\nbe readable for debugging. By implementing a user-defined pretty print,\ndevelopers can parse ``struct stat`` and output information in a readable format.\n\n.. code-block:: python\n\n    import gdb\n    import pwd\n    import grp\n    import stat\n    import time\n\n    from datetime import datetime\n\n\n    class StatPrint:\n        def __init__(self, val):\n            self.val = val\n\n        def get_filetype(self, st_mode):\n            if stat.S_ISDIR(st_mode):\n                return \"directory\"\n            if stat.S_ISCHR(st_mode):\n                return \"character device\"\n            if stat.S_ISBLK(st_mode):\n                return \"block device\"\n            if stat.S_ISREG:\n                return \"regular file\"\n            if stat.S_ISFIFO(st_mode):\n                return \"FIFO\"\n            if stat.S_ISLNK(st_mode):\n                return \"symbolic link\"\n            if stat.S_ISSOCK(st_mode):\n                return \"socket\"\n            return \"unknown\"\n\n        def get_access(self, st_mode):\n            out = \"-\"\n            info = (\"r\", \"w\", \"x\")\n            perm = [\n                (stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR),\n                (stat.S_IRGRP, stat.S_IRWXG, stat.S_IXGRP),\n                (stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH),\n            ]\n            for pm in perm:\n                for c, p in zip(pm, info):\n                    out += p if st_mode & c else \"-\"\n            return out\n\n        def get_time(self, st_time):\n            tv_sec = int(st_time[\"tv_sec\"])\n            return datetime.fromtimestamp(tv_sec).isoformat()\n\n        def to_string(self):\n            st = self.val\n            st_ino = int(st[\"st_ino\"])\n            st_mode = int(st[\"st_mode\"])\n            st_uid = int(st[\"st_uid\"])\n            st_gid = int(st[\"st_gid\"])\n            st_size = int(st[\"st_size\"])\n            st_blksize = int(st[\"st_blksize\"])\n            st_blocks = int(st[\"st_blocks\"])\n            st_atim = st[\"st_atim\"]\n            st_mtim = st[\"st_mtim\"]\n            st_ctim = st[\"st_ctim\"]\n\n            out = \"{\\n\"\n            out += f\"Size: {st_size}\\n\"\n            out += f\"Blocks: {st_blocks}\\n\"\n            out += f\"IO Block: {st_blksize}\\n\"\n            out += f\"Inode: {st_ino}\\n\"\n            out += f\"Access: {self.get_access(st_mode)}\\n\"\n            out += f\"File Type: {self.get_filetype(st_mode)}\\n\"\n            out += f\"Uid: ({st_uid}/{pwd.getpwuid(st_uid).pw_name})\\n\"\n            out += f\"Gid: ({st_gid}/{grp.getgrgid(st_gid).gr_name})\\n\"\n            out += f\"Access: {self.get_time(st_atim)}\\n\"\n            out += f\"Modify: {self.get_time(st_mtim)}\\n\"\n            out += f\"Change: {self.get_time(st_ctim)}\\n\"\n            out += \"}\"\n            return out\n\n    p = gdb.printing.RegexpCollectionPrettyPrinter(\"sp\")\n    p.add_printer(\"stat\", \"^stat$\", StatPrint)\n\n    o = gdb.current_objfile()\n    gdb.printing.register_pretty_printer(o, p)\n\nBy sourcing the previous Python script, the ``PrettyPrinter`` can recognize\n``struct stat`` and output a readable format for developers to inspect file\nattributes. Without inserting functions to parse and print ``struct stat``, it\nis a more convenient way to acquire a better output from Python API.\n\n.. code-block:: bash\n\n    (gdb) list 15\n    10          struct stat st;\n    11\n    12          if ((rc = stat(\"./a.cpp\", &st)) < 0) {\n    13              perror(\"stat failed.\");\n    14              goto end;\n    15          }\n    16\n    17          rc = 0;\n    18       end:\n    19          return rc;\n    (gdb) source st.py\n    (gdb) b 17\n    Breakpoint 1 at 0x762: file a.cpp, line 17.\n    (gdb) r\n    Starting program: /root/a.out\n\n    Breakpoint 1, main (argc=1, argv=0x7fffffffe788) at a.cpp:17\n    17          rc = 0;\n    (gdb) p st\n    $1 = {\n    Size: 298\n    Blocks: 8\n    IO Block: 4096\n    Inode: 1322071\n    Access: -rw-rw-r--\n    File Type: regular file\n    Uid: (0/root)\n    Gid: (0/root)\n    Access: 2019-12-28T15:53:17\n    Modify: 2019-12-28T15:53:01\n    Change: 2019-12-28T15:53:01\n    }\n\nNote that developers can disable a user-defined pretty-print via the command\n``disable``. For example, the previous Python script registers a pretty printer\nunder the global pretty-printers. By calling ``disable pretty-print``, the\nprinter ``sp`` will be disabled.\n\n.. code-block:: bash\n\n    (gdb) disable pretty-print global sp\n    1 printer disabled\n    1 of 2 printers enabled\n    (gdb) i pretty-print\n    global pretty-printers:\n      builtin\n        mpx_bound128\n      sp [disabled]\n        stat\n\nAdditionally, developers can exclude a printer in the current GDB debugging\nsession if it is no longer required. The following snippet shows how to delete\nthe ``sp`` printer through ``gdb.pretty_printers.remove``.\n\n.. code-block:: bash\n\n    (gdb) python\n    >import gdb\n    >for p in gdb.pretty_printers:\n    >    if p.name == \"sp\":\n    >        gdb.pretty_printers.remove(p)\n    >end\n    (gdb) i pretty-print\n    global pretty-printers:\n      builtin\n        mpx_bound128\n\nConclusion\n----------\n\nIntegrating Python interpreter into GDB offers many flexible ways to\ntroubleshoot issues. While many integrated development environments (IDEs) may\nembed GDB to debug visually, GDB allows developers to implement their commands\nand parse variables’ output at runtime. By using debugging scripts, developers\ncan monitor and record necessary information without modifying their code.\nHonestly, inserting or enabling debugging code blocks may change a program’s\nbehaviors, and developers should get rid of this bad habit. Also, when a problem\nis reproduced, GDB can attach that process and examine its status without stopping\nit. Obviously, debugging via GDB is inevitable if a challenging issue emerges.\nThanks to integrating Python into GDB, developing a script to troubleshoot becomes\nmore accessible that leads to developers establishing their debugging methods\ndiversely.\n\n\nReference\n---------\n\n1. `Extending GDB using Python`_\n2. `gcc/gcc/gdbhooks.py`_\n3. `gdbinit/Gdbinit`_\n4. `cyrus-and/gdb-dashboard`_\n5. `hugsy/gef`_\n6. `sharkdp/stack-inspector`_\n7. `gdb Debugging Full Example (Tutorial)`_\n\n.. _Pygments: https://pygments.org/\n.. _Extending GDB using Python: https://sourceware.org/gdb/onlinedocs/gdb/Python.html\n.. _gcc/gcc/gdbhooks.py: https://github.com/gcc-mirror/gcc/blob/master/gcc/gdbhooks.py\n.. _hugsy/gef: https://github.com/hugsy/gef\n.. _cyrus-and/gdb-dashboard: https://github.com/cyrus-and/gdb-dashboard\n.. _gdbinit/Gdbinit: https://github.com/gdbinit/Gdbinit\n.. _sharkdp/stack-inspector: https://github.com/sharkdp/stack-inspector\n.. _GDB Text User Interface (TUI): https://sourceware.org/gdb/onlinedocs/gdb/TUI.html\n.. _Linux perf: https://github.com/torvalds/linux/tree/master/tools/perf\n.. _Valgrind: https://valgrind.org/\n.. _gdb Debugging Full Example (Tutorial): http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html\n"
  },
  {
    "path": "docs/notes/appendix/python-walrus.rst",
    "content": ".. meta::\n    :description lang=en: Design philosophy of pep 572, the walrus operator\n    :keywords: Python3, PEP 572, walrus operator\n\n\nPEP 572 and The Walrus Operator\n===============================\n\n:Date: 2025-08-30\n\nAbstract\n--------\n\n`PEP 572`_ is one of the most contentious proposals in Python3 history because\nassigning a value within an expression seems unnecessary. Also, it is ambiguous\nfor developers to distinguish the difference between **the walrus operator**\n(``:=``) and the equal operator (``=``). Even though sophisticated developers\ncan use \"``:=``\" smoothly, they may concern the readability of their code. To\nbetter understand the usage of \"``:=``,\" this article discusses its design\nphilosophy and what kind of problems it tries to solve.\n\n\nIntroduction\n------------\n\nFor C/C++ developer, assigning a function return to a variable is common due\nto error code style handling. Managing function errors includes two steps;\none is to check the return value; another is to check ``errno``. For example,\n\n.. code-block:: cpp\n\n    #include <stdio.h>\n    #include <unistd.h>\n    #include <string.h>\n    #include <errno.h>\n\n    int main(int argc, char *argv[]) {\n        int rc = -1;\n\n        // assign access return to rc and check its value\n        if ((rc = access(\"hello_walrus\", R_OK)) == -1) {\n            fprintf(stderr, \"%s\", strerror(errno));\n            goto end;\n        }\n        rc = 0;\n    end:\n        return rc;\n    }\n\nIn this case, ``access`` will assign its return value to the variable ``rc``\nfirst. Then, the program will compare the ``rc`` value with ``-1`` to check\nwhether the execution of ``access`` is successful or not. However, Python did\nnot allow assigning values to variables within an expression before 3.8. To fix\nthis problem, therefore, PEP 572 introduced the walrus operator for developers.\nThe following Python snippet is equal to the previous C example.\n\n.. code-block:: python\n\n    >>> import os\n    >>> from ctypes import *\n    >>> libc = CDLL(\"libc.dylib\", use_errno=True)\n    >>> access = libc.access\n    >>> path = create_string_buffer(b\"hello_walrus\")\n    >>> if (rc := access(path, os.R_OK)) == -1:\n    ...     errno = get_errno()\n    ...     print(os.strerror(errno), file=sys.stderr)\n    ...\n    No such file or directory\n\n\nWhy ``:=`` ?\n------------\n\nDevelopers may confuse the difference between \"``:=``\" and  \"``=``.\" In fact, they\nserve the same purpose, assigning somethings to variables. Why Python introduced\n\"``:=``\" instead of using \"``=``\"? What is the benefit of using \"``:=``\"? One\nreason is to reinforce the visual recognition due to a common mistake made by\nC/C++ developers. For instance,\n\n.. code-block:: cpp\n\n    int rc = access(\"hello_walrus\", R_OK);\n\n    // rc is unintentionally assigned to -1\n    if (rc = -1) {\n        fprintf(stderr, \"%s\", strerror(errno));\n        goto end;\n    }\n\nRather than comparison, the variable, ``rc``, is mistakenly assigned to -1. To\nprevent this error, some people advocate using `Yoda conditions`_ within an\nexpression.\n\n.. code-block:: cpp\n\n    int rc = access(\"hello_walrus\", R_OK);\n\n    // -1 = rc will raise a compile error\n    if (-1 == rc) {\n        fprintf(stderr, \"%s\", strerror(errno));\n        goto end;\n    }\n\nHowever, Yoda style is not readable enough like Yoda speaks non-standardized\nEnglish. Also, unlike C/C++ can detect assigning error during the compile-time\nvia compiler options (e.g., -Wparentheses), it is difficult for Python interpreter\nto distinguish such mistakes throughout the runtime. Thus, the final result\nof PEP 572 was to use a new syntax as a solution to implement *assignment\nexpressions*.\n\nThe walrus operator was not the first solution for PEP 572. The original proposal\nused ``EXPR as NAME`` to assign values to variables. Unfortunately, there are\nsome rejected reasons in this solution and other solutions as well. After\nintense debates, the final decision was ``:=``.\n\nScopes\n------\n\nUnlike other expressions, which a variable is bound to a scope, an assignment\nexpression belongs to the current scope. The purpose of this design is to\nallow a compact way to write code.\n\n.. code-block:: python3\n\n    >>> if not (env := os.environ.get(\"HOME\")):\n    ...     raise KeyError(\"env HOME does not find!\")\n    ...\n    >>> print(env)\n    /root\n\nIn PEP 572, another benefit is to conveniently capture a \"witness\" for an\n``any()`` or an ``all()`` expression. Although capturing function inputs can\nassist an interactive debugger, the advantage is not so obvious, and examples\nlack readability. Therefore, this benefit does not discuss here. Note that\nother languages (e.g., C/C++ or Go) may bind an assignment to a scope. Take\nGolang as an example.\n\n.. code-block:: go\n\n    package main\n\n    import (\n        \"fmt\"\n        \"os\"\n    )\n\n    func main() {\n        if env := os.Getenv(\"HOME\"); env == \"\" {\n            panic(fmt.Sprintf(\"Home does not find\"))\n        }\n        fmt.Print(env) // <--- compile error: undefined: env\n    }\n\nPitfalls\n--------\n\nAlthough an assigning expression allows writing compact code, there are many\npitfalls when a developer uses it in a list comprehension. A common ``SyntaxError``\nis to rebind iteration variables.\n\n.. code-block:: python3\n\n    >>> [i := i+1 for i in range(5)]  # invalid\n\nHowever, updating an iteration variable will reduce readability and introduce\nbugs. Even if Python 3.8 did not implement the walrus operator, a programmer\nshould avoid reusing iteration variables within a scope.\n\nAnother pitfall is Python prohibits using assignment expressions within a\ncomprehension under a class scope.\n\n.. code-block:: python3\n\n    >>> class Example:\n    ...     [(j := i) for i in range(5)] # invalid\n    ...\n\nThis limitation was from `bpo-3692`_. The interpreter's behavior is\nunpredictable when a class declaration contains a list comprehension. To avoid\nthis corner case, assigning expression is invalid under a class.\n\n.. code-block:: python3\n\n    >>> class Foo:\n    ...     a = [1, 2, 3]\n    ...     b = [4, 5, 6]\n    ...     c = [i for i in zip(a, b)]  # b is defined\n    ...\n    >>> class Bar:\n    ...     a = [1,2,3]\n    ...     b = [4,5,6]\n    ...     c = [x * y for x in a for y in b] # b is undefined\n    ...\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n      File \"<stdin>\", line 4, in Bar\n      File \"<stdin>\", line 4, in <listcomp>\n    NameError: name 'b' is not defined\n\nConclusion\n----------\n\nThe reason why the walrus operator (``:=``) is so controversial is that code\nreadability may decrease. In fact, in the discussion `mail thread <https://mail.python.org/pipermail/python-ideas/2018-March/049409.html>`_,\nthe author of PEP 572, Christoph Groth, had considered using \"``=``\" to implement\ninline assignment like C/C++. Without judging \"``:=``\" is ugly, many developers\nargue that distinguishing the functionality between \"``:=``\" and \"``=``\" is\ndifficult because they serve the same purpose, but behaviors are not consistent.\nAlso, writing compact code is not persuasive enough because smaller is not\nalways better. However, in some cases, the walrus operator can enhance\nreadability (if you understand how to use ``:=``). For example,\n\n.. code-block:: python3\n\n    buf = b\"\"\n    while True:\n        data = read(1024)\n        if not data:\n            break\n        buf += data\n\nBy using ``:=``, the previous example can be simplified.\n\n.. code-block:: python3\n\n    buf = b\"\"\n    while (data := read(1024)):\n        buf += data\n\n`Python document`_ and GitHub `issue-8122`_ provides many great examples about\nimproving code readability by \"``:=``\". However, using the walrus operator\nshould be careful. Some cases, such as ``foo(x := 3, cat='vector')``, may\nintroduce new bugs if developers are not aware of scopes. Although PEP 572\nmay be risky for developers to write buggy code, an in-depth understanding of\ndesign philosophy and useful examples will help us use it to write readable\ncode at the right time.\n\nReferences\n----------\n\n1. `PEP 572 - Assignment Expressions`_\n2. `What’s New In Python 3.8`_\n3. `PEP 572 and decision-making in Python`_\n4. `The PEP 572 endgame`_\n5. `Use assignment expression in stdlib (combined PR)`_\n6. `Improper scope in list comprehension, when used in class declaration`_\n\n.. _PEP 572: https://www.python.org/dev/peps/pep-0572/\n.. _PEP 572 - Assignment Expressions: https://www.python.org/dev/peps/pep-0572/\n.. _What’s New In Python 3.8: https://docs.python.org/3/whatsnew/3.8.html\n.. _PEP 572 and decision-making in Python: https://lwn.net/Articles/757713/\n.. _The PEP 572 endgame: https://lwn.net/Articles/759558/\n.. _Use assignment expression in stdlib (combined PR): https://github.com/python/cpython/pull/8122/files\n.. _improper scope in list comprehension, when used in class declaration: https://bugs.python.org/issue3692\n.. _Yoda conditions: https://en.wikipedia.org/wiki/Yoda_conditions\n.. _bpo-3692: https://bugs.python.org/issue3692\n.. _Python document: https://docs.python.org/3/whatsnew/3.8.html#assignment-expressions\n.. _issue-8122: https://github.com/python/cpython/pull/8122/files\n"
  },
  {
    "path": "docs/notes/asyncio/index.rst",
    "content": ".. meta::\n    :description lang=en: Python asyncio tutorial covering coroutines, event loops, tasks, async/await syntax, networking, and asynchronous programming patterns\n    :keywords: Python, Python3, asyncio, async, await, coroutine, event loop, asynchronous, concurrent, networking, TCP, UDP\n\nAsyncio\n=======\n\nPython's ``asyncio`` module provides infrastructure for writing single-threaded\nconcurrent code using coroutines, multiplexing I/O access over sockets and other\nresources, running network clients and servers, and other related primitives.\nUnlike threading, asyncio uses cooperative multitasking, where tasks voluntarily\nyield control to allow other tasks to run. This makes it ideal for I/O-bound\napplications like web servers, database clients, and network services where\nwaiting for external resources is the primary bottleneck.\n\nThis section covers asyncio from basic concepts to advanced patterns, including\nthe event loop, coroutines, tasks, synchronization primitives, and real-world\nexamples like TCP/UDP servers, HTTP clients, and connection pools.\n\n.. toctree::\n   :maxdepth: 1\n\n   python-asyncio-guide\n   python-asyncio-basic\n   python-asyncio-server\n   python-asyncio-advanced\n"
  },
  {
    "path": "docs/notes/asyncio/python-asyncio-advanced.rst",
    "content": ".. meta::\n    :description lang=en: Python asyncio advanced - synchronization, queues, subprocesses, debugging, patterns\n    :keywords: Python, Python3, Asyncio, Synchronization, Queue, Semaphore, Lock, Subprocess, Debugging\n\n=================\nAsyncio Advanced\n=================\n\n:Source: `src/basic/asyncio_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/asyncio_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nBeyond basic coroutines and networking, asyncio provides synchronization\nprimitives, queues, subprocess management, and debugging tools. This section\ncovers advanced patterns for building robust async applications, including\nproducer-consumer patterns, rate limiting, graceful shutdown, and integration\nwith synchronous code.\n\nLocks\n-----\n\n``asyncio.Lock`` prevents multiple coroutines from accessing a shared resource\nsimultaneously. Unlike threading locks, async locks must be used with ``await``\nand only work within the same event loop.\n\n.. code-block:: python\n\n    import asyncio\n\n    class SharedCounter:\n        def __init__(self):\n            self.value = 0\n            self._lock = asyncio.Lock()\n\n        async def increment(self):\n            async with self._lock:\n                current = self.value\n                await asyncio.sleep(0.01)  # Simulate work\n                self.value = current + 1\n\n    async def worker(counter, name, count):\n        for _ in range(count):\n            await counter.increment()\n        print(f\"{name} done\")\n\n    async def main():\n        counter = SharedCounter()\n        await asyncio.gather(\n            worker(counter, \"A\", 100),\n            worker(counter, \"B\", 100),\n            worker(counter, \"C\", 100),\n        )\n        print(f\"Final value: {counter.value}\")  # Should be 300\n\n    asyncio.run(main())\n\nSemaphores for Rate Limiting\n----------------------------\n\n``asyncio.Semaphore`` limits the number of concurrent operations. This is\nessential for rate limiting API calls, limiting database connections, or\ncontrolling resource usage.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def fetch(url, semaphore):\n        async with semaphore:\n            print(f\"Fetching {url}\")\n            await asyncio.sleep(1)  # Simulate network request\n            return f\"Response from {url}\"\n\n    async def main():\n        # Limit to 3 concurrent requests\n        semaphore = asyncio.Semaphore(3)\n\n        urls = [f\"https://api.example.com/{i}\" for i in range(10)]\n        tasks = [fetch(url, semaphore) for url in urls]\n        results = await asyncio.gather(*tasks)\n\n        for r in results:\n            print(r)\n\n    asyncio.run(main())\n\nEvents for Signaling\n--------------------\n\n``asyncio.Event`` allows coroutines to wait for a signal from another coroutine.\nThis is useful for coordinating startup, shutdown, or state changes between\nmultiple tasks.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def waiter(event, name):\n        print(f\"{name} waiting for event\")\n        await event.wait()\n        print(f\"{name} got the event!\")\n\n    async def setter(event):\n        print(\"Setting event in 2 seconds...\")\n        await asyncio.sleep(2)\n        event.set()\n        print(\"Event set!\")\n\n    async def main():\n        event = asyncio.Event()\n\n        await asyncio.gather(\n            waiter(event, \"Task 1\"),\n            waiter(event, \"Task 2\"),\n            waiter(event, \"Task 3\"),\n            setter(event),\n        )\n\n    asyncio.run(main())\n\nConditions for Complex Synchronization\n--------------------------------------\n\n``asyncio.Condition`` combines a lock with the ability to wait for a condition.\nThis is useful for producer-consumer patterns where consumers need to wait\nfor specific conditions.\n\n.. code-block:: python\n\n    import asyncio\n\n    class Buffer:\n        def __init__(self, size):\n            self.buffer = []\n            self.size = size\n            self.condition = asyncio.Condition()\n\n        async def put(self, item):\n            async with self.condition:\n                while len(self.buffer) >= self.size:\n                    await self.condition.wait()\n                self.buffer.append(item)\n                self.condition.notify()\n\n        async def get(self):\n            async with self.condition:\n                while not self.buffer:\n                    await self.condition.wait()\n                item = self.buffer.pop(0)\n                self.condition.notify()\n                return item\n\n    async def producer(buffer, name):\n        for i in range(5):\n            await buffer.put(f\"{name}-{i}\")\n            print(f\"Produced: {name}-{i}\")\n            await asyncio.sleep(0.1)\n\n    async def consumer(buffer, name):\n        for _ in range(5):\n            item = await buffer.get()\n            print(f\"{name} consumed: {item}\")\n            await asyncio.sleep(0.2)\n\n    async def main():\n        buffer = Buffer(size=2)\n        await asyncio.gather(\n            producer(buffer, \"P1\"),\n            consumer(buffer, \"C1\"),\n            consumer(buffer, \"C2\"),\n        )\n\n    asyncio.run(main())\n\nQueues for Producer-Consumer\n----------------------------\n\n``asyncio.Queue`` is the preferred way to implement producer-consumer patterns.\nIt handles synchronization internally and provides blocking get/put operations\nwith optional timeouts.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def producer(queue, name):\n        for i in range(5):\n            item = f\"{name}-item-{i}\"\n            await queue.put(item)\n            print(f\"Produced: {item}\")\n            await asyncio.sleep(0.5)\n\n    async def consumer(queue, name):\n        while True:\n            try:\n                item = await asyncio.wait_for(queue.get(), timeout=2.0)\n                print(f\"{name} consumed: {item}\")\n                queue.task_done()\n                await asyncio.sleep(0.1)\n            except asyncio.TimeoutError:\n                print(f\"{name} timed out, exiting\")\n                break\n\n    async def main():\n        queue = asyncio.Queue(maxsize=3)\n\n        producers = [\n            asyncio.create_task(producer(queue, \"P1\")),\n            asyncio.create_task(producer(queue, \"P2\")),\n        ]\n        consumers = [\n            asyncio.create_task(consumer(queue, \"C1\")),\n            asyncio.create_task(consumer(queue, \"C2\")),\n        ]\n\n        await asyncio.gather(*producers)\n        await queue.join()  # Wait for all items to be processed\n\n        for c in consumers:\n            c.cancel()\n\n    asyncio.run(main())\n\nPriority Queue\n--------------\n\n``asyncio.PriorityQueue`` processes items by priority. Lower priority values\nare processed first. Items must be comparable or wrapped in tuples with\npriority as the first element.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def producer(queue):\n        items = [\n            (3, \"low priority\"),\n            (1, \"high priority\"),\n            (2, \"medium priority\"),\n        ]\n        for priority, item in items:\n            await queue.put((priority, item))\n            print(f\"Added: {item} (priority {priority})\")\n\n    async def consumer(queue):\n        while not queue.empty():\n            priority, item = await queue.get()\n            print(f\"Processing: {item} (priority {priority})\")\n            await asyncio.sleep(0.5)\n            queue.task_done()\n\n    async def main():\n        queue = asyncio.PriorityQueue()\n        await producer(queue)\n        await consumer(queue)\n\n    asyncio.run(main())\n\nRunning Subprocesses\n--------------------\n\nAsyncio can run and communicate with subprocesses asynchronously. This is\nuseful for running shell commands, external tools, or parallel processes\nwithout blocking the event loop.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def run_command(cmd):\n        proc = await asyncio.create_subprocess_shell(\n            cmd,\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.PIPE\n        )\n\n        stdout, stderr = await proc.communicate()\n\n        return {\n            'cmd': cmd,\n            'returncode': proc.returncode,\n            'stdout': stdout.decode().strip(),\n            'stderr': stderr.decode().strip()\n        }\n\n    async def main():\n        commands = [\n            \"echo 'Hello World'\",\n            \"python --version\",\n            \"date\",\n        ]\n\n        results = await asyncio.gather(*[run_command(c) for c in commands])\n\n        for r in results:\n            print(f\"Command: {r['cmd']}\")\n            print(f\"Output: {r['stdout']}\")\n            print()\n\n    asyncio.run(main())\n\nSubprocess with Streaming Output\n--------------------------------\n\nFor long-running processes, you can stream output line by line instead of\nwaiting for the process to complete. This is useful for monitoring logs or\nprogress.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def stream_subprocess(cmd):\n        proc = await asyncio.create_subprocess_shell(\n            cmd,\n            stdout=asyncio.subprocess.PIPE,\n            stderr=asyncio.subprocess.STDOUT\n        )\n\n        while True:\n            line = await proc.stdout.readline()\n            if not line:\n                break\n            print(f\"[{cmd[:20]}] {line.decode().strip()}\")\n\n        await proc.wait()\n        return proc.returncode\n\n    async def main():\n        # Run multiple commands and stream their output\n        await asyncio.gather(\n            stream_subprocess(\"for i in 1 2 3; do echo $i; sleep 1; done\"),\n            stream_subprocess(\"for i in a b c; do echo $i; sleep 0.5; done\"),\n        )\n\n    asyncio.run(main())\n\nGraceful Shutdown\n-----------------\n\nProper shutdown handling ensures all tasks complete cleanly and resources\nare released. Use signal handlers to catch SIGINT/SIGTERM and cancel tasks\ngracefully.\n\n.. code-block:: python\n\n    import asyncio\n    import signal\n\n    async def worker(name):\n        try:\n            while True:\n                print(f\"{name} working...\")\n                await asyncio.sleep(1)\n        except asyncio.CancelledError:\n            print(f\"{name} cancelled, cleaning up...\")\n            await asyncio.sleep(0.5)  # Cleanup time\n            print(f\"{name} cleanup done\")\n            raise\n\n    async def main():\n        loop = asyncio.get_event_loop()\n        tasks = [\n            asyncio.create_task(worker(\"Worker-1\")),\n            asyncio.create_task(worker(\"Worker-2\")),\n        ]\n\n        def shutdown():\n            print(\"\\nShutdown requested...\")\n            for task in tasks:\n                task.cancel()\n\n        loop.add_signal_handler(signal.SIGINT, shutdown)\n        loop.add_signal_handler(signal.SIGTERM, shutdown)\n\n        try:\n            await asyncio.gather(*tasks)\n        except asyncio.CancelledError:\n            print(\"All tasks cancelled\")\n\n    asyncio.run(main())\n\nRunning Async Code in Threads\n-----------------------------\n\nWhen you need to run async code from synchronous code (e.g., in a callback\nor from another thread), use ``asyncio.run_coroutine_threadsafe()``.\n\n.. code-block:: python\n\n    import asyncio\n    import threading\n    import time\n\n    async def async_task(value):\n        await asyncio.sleep(1)\n        return value * 2\n\n    def thread_function(loop):\n        # Run async code from a different thread\n        future = asyncio.run_coroutine_threadsafe(\n            async_task(21), loop\n        )\n        result = future.result(timeout=5)\n        print(f\"Thread got result: {result}\")\n\n    async def main():\n        loop = asyncio.get_event_loop()\n\n        # Start a thread that will call async code\n        thread = threading.Thread(target=thread_function, args=(loop,))\n        thread.start()\n\n        # Keep the event loop running\n        await asyncio.sleep(2)\n        thread.join()\n\n    asyncio.run(main())\n\nDebugging Asyncio\n-----------------\n\nEnable debug mode to catch common mistakes like blocking calls, unawaited\ncoroutines, and slow callbacks. Debug mode adds overhead so use it only\nduring development.\n\n.. code-block:: python\n\n    import asyncio\n    import logging\n\n    # Enable debug logging\n    logging.basicConfig(level=logging.DEBUG)\n\n    async def slow_callback():\n        import time\n        time.sleep(0.2)  # This will trigger a warning in debug mode\n\n    async def main():\n        await slow_callback()\n\n    # Method 1: Environment variable\n    # PYTHONASYNCIODEBUG=1 python script.py\n\n    # Method 2: asyncio.run with debug=True\n    asyncio.run(main(), debug=True)\n\nCustom Event Loop\n-----------------\n\nYou can customize the event loop behavior by subclassing or patching. This\nis useful for debugging, profiling, or adding custom functionality.\n\n.. code-block:: python\n\n    import asyncio\n\n    class DebugEventLoop(asyncio.SelectorEventLoop):\n        def _run_once(self):\n            # Track number of scheduled callbacks\n            num_ready = len(self._ready)\n            num_scheduled = len(self._scheduled)\n            if num_ready or num_scheduled:\n                print(f\"Ready: {num_ready}, Scheduled: {num_scheduled}\")\n            super()._run_once()\n\n    async def task(n):\n        await asyncio.sleep(n)\n        print(f\"Task {n} done\")\n\n    # Use custom event loop\n    loop = DebugEventLoop()\n    asyncio.set_event_loop(loop)\n\n    try:\n        loop.run_until_complete(asyncio.gather(\n            task(0.1),\n            task(0.2),\n            task(0.3),\n        ))\n    finally:\n        loop.close()\n\nTimeout Patterns\n----------------\n\nDifferent timeout patterns for various use cases: per-operation timeout,\noverall timeout, and timeout with fallback.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def fetch(url, delay):\n        await asyncio.sleep(delay)\n        return f\"Response from {url}\"\n\n    async def fetch_with_timeout(url, delay, timeout):\n        \"\"\"Per-operation timeout.\"\"\"\n        try:\n            return await asyncio.wait_for(fetch(url, delay), timeout)\n        except asyncio.TimeoutError:\n            return f\"Timeout for {url}\"\n\n    async def fetch_all_with_timeout(urls, timeout):\n        \"\"\"Overall timeout for all operations.\"\"\"\n        async def fetch_all():\n            return await asyncio.gather(*[fetch(u, i) for i, u in enumerate(urls)])\n\n        try:\n            return await asyncio.wait_for(fetch_all(), timeout)\n        except asyncio.TimeoutError:\n            return [\"Overall timeout\"]\n\n    async def fetch_with_fallback(url, delay, timeout, fallback):\n        \"\"\"Timeout with fallback value.\"\"\"\n        try:\n            return await asyncio.wait_for(fetch(url, delay), timeout)\n        except asyncio.TimeoutError:\n            return fallback\n\n    async def main():\n        # Per-operation timeout\n        result = await fetch_with_timeout(\"slow.com\", 5, 1)\n        print(result)\n\n        # Timeout with fallback\n        result = await fetch_with_fallback(\"slow.com\", 5, 1, \"cached response\")\n        print(result)\n\n    asyncio.run(main())\n\nRetry Pattern\n-------------\n\nImplement retry logic for transient failures with exponential backoff.\nThis is essential for robust network clients.\n\n.. code-block:: python\n\n    import asyncio\n    import random\n\n    class RetryError(Exception):\n        pass\n\n    async def unreliable_operation():\n        \"\"\"Simulates an operation that fails randomly.\"\"\"\n        if random.random() < 0.7:\n            raise ConnectionError(\"Network error\")\n        return \"Success!\"\n\n    async def retry(coro_func, max_retries=3, base_delay=1.0):\n        \"\"\"Retry with exponential backoff.\"\"\"\n        last_exception = None\n\n        for attempt in range(max_retries):\n            try:\n                return await coro_func()\n            except Exception as e:\n                last_exception = e\n                if attempt < max_retries - 1:\n                    delay = base_delay * (2 ** attempt)\n                    jitter = random.uniform(0, 0.1 * delay)\n                    print(f\"Attempt {attempt + 1} failed, retrying in {delay:.2f}s\")\n                    await asyncio.sleep(delay + jitter)\n\n        raise RetryError(f\"Failed after {max_retries} attempts\") from last_exception\n\n    async def main():\n        try:\n            result = await retry(unreliable_operation, max_retries=5)\n            print(f\"Result: {result}\")\n        except RetryError as e:\n            print(f\"All retries failed: {e}\")\n\n    asyncio.run(main())\n\nAsync Context Variable\n----------------------\n\nContext variables (Python 3.7+) provide task-local storage, similar to\nthread-local storage but for async tasks. Useful for request IDs, user\ncontext, or database connections.\n\n.. code-block:: python\n\n    import asyncio\n    import contextvars\n\n    # Create context variable\n    request_id = contextvars.ContextVar('request_id', default=None)\n\n    async def process_request(rid):\n        request_id.set(rid)\n        await step1()\n        await step2()\n\n    async def step1():\n        rid = request_id.get()\n        print(f\"[{rid}] Step 1\")\n        await asyncio.sleep(0.1)\n\n    async def step2():\n        rid = request_id.get()\n        print(f\"[{rid}] Step 2\")\n        await asyncio.sleep(0.1)\n\n    async def main():\n        await asyncio.gather(\n            process_request(\"req-001\"),\n            process_request(\"req-002\"),\n            process_request(\"req-003\"),\n        )\n\n    asyncio.run(main())\n\nTaskGroup (Python 3.11+)\n------------------------\n\n``TaskGroup`` provides structured concurrency, ensuring all tasks complete\nor are cancelled together. Exceptions in any task cancel all other tasks\nin the group.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def task(name, delay, should_fail=False):\n        await asyncio.sleep(delay)\n        if should_fail:\n            raise ValueError(f\"{name} failed!\")\n        return f\"{name} done\"\n\n    async def main():\n        try:\n            async with asyncio.TaskGroup() as tg:\n                tg.create_task(task(\"A\", 1))\n                tg.create_task(task(\"B\", 2))\n                tg.create_task(task(\"C\", 0.5, should_fail=True))\n        except* ValueError as eg:\n            for exc in eg.exceptions:\n                print(f\"Caught: {exc}\")\n\n    # Python 3.11+\n    asyncio.run(main())\n"
  },
  {
    "path": "docs/notes/asyncio/python-asyncio-basic.rst",
    "content": ".. meta::\n    :description lang=en: Python asyncio basics - coroutines, tasks, event loop, async/await syntax\n    :keywords: Python, Python3, Asyncio, Coroutines, Event Loop, async await, Asynchronous Programming\n\n================\nAsyncio Basics\n================\n\n:Source: `src/basic/asyncio_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/asyncio_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nThe ``asyncio`` module, introduced in Python 3.4 and significantly improved in\nPython 3.5+ with ``async/await`` syntax, provides a foundation for writing\nasynchronous code. Unlike threads which use preemptive multitasking (the OS\ndecides when to switch), asyncio uses cooperative multitasking where coroutines\nexplicitly yield control using ``await``. This eliminates race conditions common\nin threaded code and makes reasoning about program flow much easier.\n\nKey concepts:\n\n- **Coroutine**: A function defined with ``async def`` that can be paused and resumed\n- **Event Loop**: The central scheduler that runs coroutines and handles I/O events\n- **Task**: A wrapper around a coroutine that schedules it for execution\n- **Future**: A placeholder for a result that will be available later\n\nRunning Coroutines with asyncio.run\n-----------------------------------\n\nThe simplest way to run async code is ``asyncio.run()``, introduced in Python 3.7.\nIt creates an event loop, runs the coroutine until completion, and cleans up\nautomatically. This is the recommended entry point for asyncio programs.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def hello():\n        print(\"Hello\")\n        await asyncio.sleep(1)\n        print(\"World\")\n\n    # Python 3.7+\n    asyncio.run(hello())\n\nFor file I/O or other blocking operations, use ``run_in_executor`` to avoid\nblocking the event loop:\n\n.. code-block:: python\n\n    import asyncio\n    from concurrent.futures import ThreadPoolExecutor\n\n    async def read_file(path):\n        loop = asyncio.get_event_loop()\n        with ThreadPoolExecutor() as pool:\n            with open(path) as f:\n                return await loop.run_in_executor(pool, f.read)\n\n    content = asyncio.run(read_file('/etc/hosts'))\n\nCreating and Managing Tasks\n---------------------------\n\nTasks allow multiple coroutines to run concurrently. When you create a task,\nit's scheduled to run on the event loop immediately. Use ``asyncio.create_task()``\n(Python 3.7+) or ``loop.create_task()`` to create tasks.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def fetch(name, delay):\n        await asyncio.sleep(delay)\n        return f\"{name} done\"\n\n    async def main():\n        # Create tasks - they start running immediately\n        task1 = asyncio.create_task(fetch(\"A\", 2))\n        task2 = asyncio.create_task(fetch(\"B\", 1))\n\n        # Wait for both to complete\n        result1 = await task1\n        result2 = await task2\n        print(result1, result2)\n\n    asyncio.run(main())\n\nGathering Multiple Coroutines\n-----------------------------\n\n``asyncio.gather()`` runs multiple coroutines concurrently and collects their\nresults in order. This is the most common way to run multiple async operations\nin parallel and wait for all of them to complete.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def fetch(url, delay):\n        await asyncio.sleep(delay)\n        return f\"Response from {url}\"\n\n    async def main():\n        urls = [\"site1.com\", \"site2.com\", \"site3.com\"]\n        coros = [fetch(url, i * 0.5) for i, url in enumerate(urls)]\n\n        # Run all concurrently, results in same order as input\n        results = await asyncio.gather(*coros)\n        for r in results:\n            print(r)\n\n    asyncio.run(main())\n\nWaiting with Timeout\n--------------------\n\nUse ``asyncio.wait_for()`` to set a timeout on async operations. This is\nessential for network operations where you don't want to wait indefinitely\nfor a response that may never come.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def slow_operation():\n        await asyncio.sleep(10)\n        return \"done\"\n\n    async def main():\n        try:\n            result = await asyncio.wait_for(slow_operation(), timeout=2.0)\n        except asyncio.TimeoutError:\n            print(\"Operation timed out!\")\n\n    asyncio.run(main())\n\nWaiting for First Completed\n---------------------------\n\n``asyncio.wait()`` provides more control than ``gather()``. You can wait for\nthe first task to complete, first exception, or all tasks. This is useful\nwhen you want to process results as they become available.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def fetch(name, delay):\n        await asyncio.sleep(delay)\n        return f\"{name}: {delay}s\"\n\n    async def main():\n        tasks = [\n            asyncio.create_task(fetch(\"fast\", 1)),\n            asyncio.create_task(fetch(\"slow\", 3)),\n        ]\n\n        # Wait for first to complete\n        done, pending = await asyncio.wait(\n            tasks, return_when=asyncio.FIRST_COMPLETED\n        )\n\n        for task in done:\n            print(f\"Completed: {task.result()}\")\n        print(f\"Still pending: {len(pending)}\")\n\n        # Cancel pending tasks\n        for task in pending:\n            task.cancel()\n\n    asyncio.run(main())\n\nAsynchronous Iteration\n----------------------\n\nAsync iterators allow you to iterate over data that arrives asynchronously,\nsuch as streaming responses or database cursors. Implement ``__aiter__`` and\n``__anext__`` methods to create custom async iterators.\n\n.. code-block:: python\n\n    import asyncio\n\n    class AsyncRange:\n        \"\"\"Async iterator that yields numbers with delays.\"\"\"\n\n        def __init__(self, start, stop):\n            self.current = start\n            self.stop = stop\n\n        def __aiter__(self):\n            return self\n\n        async def __anext__(self):\n            if self.current >= self.stop:\n                raise StopAsyncIteration\n            await asyncio.sleep(0.5)\n            value = self.current\n            self.current += 1\n            return value\n\n    async def main():\n        async for num in AsyncRange(0, 5):\n            print(num)\n\n    asyncio.run(main())\n\nAsynchronous Context Managers\n-----------------------------\n\nAsync context managers are essential for managing resources that require\nasync setup or cleanup, such as database connections, file handles, or\nnetwork sessions. Use ``async with`` to ensure proper resource management.\n\n.. code-block:: python\n\n    import asyncio\n\n    class AsyncConnection:\n        \"\"\"Simulated async database connection.\"\"\"\n\n        async def __aenter__(self):\n            print(\"Connecting...\")\n            await asyncio.sleep(1)\n            print(\"Connected\")\n            return self\n\n        async def __aexit__(self, exc_type, exc_val, exc_tb):\n            print(\"Disconnecting...\")\n            await asyncio.sleep(0.5)\n            print(\"Disconnected\")\n\n        async def query(self, sql):\n            await asyncio.sleep(0.1)\n            return f\"Result of: {sql}\"\n\n    async def main():\n        async with AsyncConnection() as conn:\n            result = await conn.query(\"SELECT * FROM users\")\n            print(result)\n\n    asyncio.run(main())\n\nUsing @asynccontextmanager\n--------------------------\n\nThe ``@asynccontextmanager`` decorator (Python 3.7+) provides a simpler way\nto create async context managers using generator syntax, similar to the\nsynchronous ``@contextmanager`` decorator.\n\n.. code-block:: python\n\n    import asyncio\n    from contextlib import asynccontextmanager\n\n    @asynccontextmanager\n    async def managed_resource(name):\n        print(f\"Acquiring {name}\")\n        await asyncio.sleep(0.5)\n        try:\n            yield name\n        finally:\n            print(f\"Releasing {name}\")\n            await asyncio.sleep(0.2)\n\n    async def main():\n        async with managed_resource(\"database\") as resource:\n            print(f\"Using {resource}\")\n\n    asyncio.run(main())\n\nRunning Blocking Code in Executor\n---------------------------------\n\nWhen you need to call blocking code (file I/O, CPU-intensive operations,\nor libraries without async support), use ``run_in_executor()`` to run it\nin a thread pool without blocking the event loop.\n\n.. code-block:: python\n\n    import asyncio\n    import time\n    from concurrent.futures import ThreadPoolExecutor\n\n    def blocking_io():\n        \"\"\"Simulates blocking I/O operation.\"\"\"\n        time.sleep(2)\n        return \"IO complete\"\n\n    def cpu_bound():\n        \"\"\"Simulates CPU-intensive operation.\"\"\"\n        return sum(i * i for i in range(10**6))\n\n    async def main():\n        loop = asyncio.get_event_loop()\n\n        # Run in default executor (ThreadPoolExecutor)\n        result1 = await loop.run_in_executor(None, blocking_io)\n        print(result1)\n\n        # Run in custom executor\n        with ThreadPoolExecutor(max_workers=4) as pool:\n            result2 = await loop.run_in_executor(pool, cpu_bound)\n            print(result2)\n\n    asyncio.run(main())\n\nAsync Generators\n----------------\n\nAsync generators (Python 3.6+) combine generators with async/await, allowing\nyou to yield values asynchronously. They're useful for streaming data or\nimplementing async iterators more concisely.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def async_range(start, stop):\n        \"\"\"Async generator that yields numbers with delays.\"\"\"\n        for i in range(start, stop):\n            await asyncio.sleep(0.5)\n            yield i\n\n    async def main():\n        async for num in async_range(0, 5):\n            print(num)\n\n        # Async comprehension\n        results = [x async for x in async_range(0, 3)]\n        print(results)\n\n    asyncio.run(main())\n\nException Handling in Tasks\n---------------------------\n\nExceptions in tasks are stored and re-raised when you await the task or\ncall ``result()``. Unhandled exceptions in tasks that are never awaited\nwill be logged but may be silently ignored, so always await your tasks.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def failing_task():\n        await asyncio.sleep(1)\n        raise ValueError(\"Something went wrong\")\n\n    async def main():\n        task = asyncio.create_task(failing_task())\n\n        try:\n            await task\n        except ValueError as e:\n            print(f\"Caught exception: {e}\")\n\n        # Using gather with return_exceptions\n        tasks = [\n            asyncio.create_task(asyncio.sleep(1)),\n            asyncio.create_task(failing_task()),\n        ]\n        results = await asyncio.gather(*tasks, return_exceptions=True)\n        for r in results:\n            if isinstance(r, Exception):\n                print(f\"Task failed: {r}\")\n            else:\n                print(f\"Task succeeded: {r}\")\n\n    asyncio.run(main())\n\nCancelling Tasks\n----------------\n\nTasks can be cancelled using ``task.cancel()``. The cancelled task will\nraise ``asyncio.CancelledError`` at the next await point. Handle this\nexception to perform cleanup when a task is cancelled.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def long_running():\n        try:\n            while True:\n                print(\"Working...\")\n                await asyncio.sleep(1)\n        except asyncio.CancelledError:\n            print(\"Task was cancelled, cleaning up...\")\n            raise  # Re-raise to mark task as cancelled\n\n    async def main():\n        task = asyncio.create_task(long_running())\n\n        await asyncio.sleep(3)\n        task.cancel()\n\n        try:\n            await task\n        except asyncio.CancelledError:\n            print(\"Task cancellation confirmed\")\n\n    asyncio.run(main())\n"
  },
  {
    "path": "docs/notes/asyncio/python-asyncio-guide.rst",
    "content": ".. meta::\n    :description lang=en: A comprehensive guide to understanding asynchronous programming in Python, from blocking I/O to event loops, callbacks, generators, and async/await syntax\n    :keywords: Python, Python3, asyncio, coroutine, event loop, async await, asynchronous programming, C10k problem, non-blocking I/O, selectors, generators, callback\n\n================================================\nA Hitchhiker's Guide to Asynchronous Programming\n================================================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nAbstract\n--------\n\nThe `C10k problem`_ remains a fundamental challenge for programmers seeking to\nhandle massive concurrent connections efficiently. Traditionally, developers\naddress extensive I/O operations using **threads**, **epoll**, or **kqueue** to\nprevent software from blocking on expensive operations. However, developing\nreadable and bug-free concurrent code is challenging due to complexities around\ndata sharing and task dependencies. Even powerful tools like `Valgrind`_ that\nhelp detect deadlocks and race conditions cannot eliminate the time-consuming\ndebugging process as software scales.\n\nTo address these challenges, many programming languages—including Python,\nJavaScript, and C++—have developed better libraries, frameworks, and syntaxes\nto help programmers manage concurrent tasks properly. Rather than focusing on\nhow to use modern parallel APIs, this article concentrates on the **design\nphilosophy** behind asynchronous programming patterns, tracing the evolution\nfrom blocking I/O to the elegant ``async/await`` syntax.\n\nUsing threads is the most natural approach for dispatching tasks without\nblocking the main thread. However, threads introduce performance overhead from\ncontext switching and require careful locking of critical sections for atomic\noperations. While event loops can enhance performance in I/O-bound scenarios,\nwriting readable event-driven code is challenging due to callback complexity\n(commonly known as \"callback hell\"). Fortunately, Python introduced the\n``async/await`` syntax to help developers write understandable code with high\nperformance. The following figure illustrates how ``async/await`` enables\nhandling socket connections with the simplicity of threads but the efficiency\nof event loops.\n\n.. image:: https://raw.githubusercontent.com/crazyguitar/pysheeet/master/docs/_static/appendix/event-loop-vs-thread.png\n\nIntroduction\n------------\n\nHandling I/O operations such as network connections is among the most expensive\ntasks in any program. Consider a simple TCP blocking echo server (shown below).\nIf a client connects without sending any data, it blocks all other connections.\nEven when clients send data promptly, the server cannot handle concurrent\nrequests because it wastes significant time waiting for I/O responses from\nhardware like network interfaces. Thus, socket programming with concurrency\nbecomes essential for managing high request volumes.\n\n.. code-block:: python\n\n    import socket\n\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((\"127.0.0.1\", 5566))\n    s.listen(10)\n\n    while True:\n        conn, addr = s.accept()\n        msg = conn.recv(1024)\n        conn.send(msg)\n\nOne solution to prevent blocking is dispatching tasks to separate threads. The\nfollowing example demonstrates handling connections simultaneously using threads.\nHowever, creating numerous threads consumes computing resources without\nproportional throughput gains. Worse, applications may waste time waiting for\nlocks when processing tasks in critical sections. While threads solve blocking\nissues, factors like CPU utilization and memory overhead remain critical for\nsolving the C10k problem. Without creating unlimited threads, the **event loop**\nprovides an alternative solution for managing connections efficiently.\n\n.. code-block:: python\n\n    import threading\n    import socket\n\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((\"127.0.0.1\", 5566))\n    s.listen(10240)\n\n    def handler(conn):\n        while True:\n            msg = conn.recv(65535)\n            conn.send(msg)\n\n    while True:\n        conn, addr = s.accept()\n        t = threading.Thread(target=handler, args=(conn,))\n        t.start()\n\nA simple event-driven socket server comprises three main components: an **I/O\nmultiplexing module** (e.g., `select`_), a **scheduler** (the loop), and\n**callback functions** (event handlers). The following server uses Python's\nhigh-level I/O multiplexing module, `selectors`_, within a loop to check\nwhether I/O operations are ready. When data becomes available for reading or\nwriting, the loop retrieves I/O events and executes the appropriate callback\nfunctions—``accept``, ``read``, or ``write``—to complete tasks.\n\n.. code-block:: python\n\n    import socket\n    from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE\n    from functools import partial\n\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((\"127.0.0.1\", 5566))\n    s.listen(10240)\n    s.setblocking(False)\n\n    sel = DefaultSelector()\n\n    def accept(s, mask):\n        conn, addr = s.accept()\n        conn.setblocking(False)\n        sel.register(conn, EVENT_READ, read)\n\n    def read(conn, mask):\n        msg = conn.recv(65535)\n        if not msg:\n            sel.unregister(conn)\n            return conn.close()\n        sel.modify(conn, EVENT_WRITE, partial(write, msg=msg))\n\n    def write(conn, mask, msg=None):\n        if msg:\n            conn.send(msg)\n        sel.modify(conn, EVENT_READ, read)\n\n    sel.register(s, EVENT_READ, accept)\n    while True:\n        events = sel.select()\n        for e, m in events:\n            cb = e.data\n            cb(e.fileobj, m)\n\nAlthough managing connections via threads may be inefficient, event-loop-based\nprograms are harder to read and maintain. To enhance code readability, many\nprogramming languages—including Python—introduce abstract concepts such as\n**coroutines**, **futures**, and **async/await** to handle I/O multiplexing\nelegantly. The following sections explore these concepts and the problems they\nsolve.\n\nCallback Functions\n------------------\n\nCallback functions control data flow at runtime when events occur. However,\npreserving state across callbacks is challenging. For example, implementing a\nhandshake protocol over TCP requires storing previous state somewhere accessible\nto subsequent callbacks.\n\n.. code-block:: python\n\n    import socket\n    from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE\n    from functools import partial\n\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((\"127.0.0.1\", 5566))\n    s.listen(10240)\n    s.setblocking(False)\n\n    sel = DefaultSelector()\n    is_hello = {}\n\n    def accept(s, mask):\n        conn, addr = s.accept()\n        conn.setblocking(False)\n        is_hello[conn] = False\n        sel.register(conn, EVENT_READ, read)\n\n    def read(conn, mask):\n        msg = conn.recv(65535)\n        if not msg:\n            sel.unregister(conn)\n            return conn.close()\n\n        # Check whether handshake is successful\n        if is_hello[conn]:\n            sel.modify(conn, EVENT_WRITE, partial(write, msg=msg))\n            return\n\n        # Perform handshake\n        if msg.decode(\"utf-8\").strip() != \"hello\":\n            sel.unregister(conn)\n            return conn.close()\n\n        is_hello[conn] = True\n\n    def write(conn, mask, msg=None):\n        if msg:\n            conn.send(msg)\n        sel.modify(conn, EVENT_READ, read)\n\n    sel.register(s, EVENT_READ, accept)\n    while True:\n        events = sel.select()\n        for e, m in events:\n            cb = e.data\n            cb(e.fileobj, m)\n\nAlthough the ``is_hello`` dictionary stores state to track handshake status,\nthe code becomes difficult to understand. The underlying logic is actually\nsimple—equivalent to this blocking version:\n\n.. code-block:: python\n\n    def accept(s):\n        conn, addr = s.accept()\n        success = handshake(conn)\n        if not success:\n            conn.close()\n\n    def handshake(conn):\n        data = conn.recv(65535)\n        if not data:\n            return False\n        if data.decode('utf-8').strip() != \"hello\":\n            return False\n        conn.send(b\"hello\")\n        return True\n\nTo achieve similar structure in non-blocking code, a function (or task) must\nsnapshot its current state—including arguments, local variables, and execution\nposition—when waiting for I/O operations. The scheduler must then be able to\n**re-enter** the function and execute remaining code after I/O completes.\nUnlike languages like C++, Python achieves this naturally because **generators**\npreserve all state and can be re-entered by calling ``next()``. By utilizing\ngenerators, handling I/O operations in a non-blocking manner with readable,\nlinear code—called *inline callbacks*—becomes possible within an event loop.\n\nEvent Loop\n----------\n\nAn event loop is a user-space scheduler that manages tasks within a program\ninstead of relying on operating system thread scheduling. The following snippet\ndemonstrates a simple event loop handling socket connections asynchronously.\nThe implementation appends tasks to a FIFO job queue and registers with a\n*selector* when I/O operations are not ready. A *generator* preserves task\nstate, allowing execution to resume without callback functions when I/O results\nbecome available. Understanding how this event loop works reveals that a Python\ngenerator is indeed a form of **coroutine**.\n\n.. code-block:: python\n\n    # loop.py\n\n    from selectors import DefaultSelector, EVENT_READ, EVENT_WRITE\n\n    class Loop:\n        def __init__(self):\n            self.sel = DefaultSelector()\n            self.queue = []\n\n        def create_task(self, task):\n            self.queue.append(task)\n\n        def polling(self):\n            for e, m in self.sel.select(0):\n                self.queue.append((e.data, None))\n                self.sel.unregister(e.fileobj)\n\n        def is_registered(self, fileobj):\n            try:\n                self.sel.get_key(fileobj)\n            except KeyError:\n                return False\n            return True\n\n        def register(self, t, data):\n            if not data:\n                return False\n\n            event_type, fileobj = data\n            if event_type in (EVENT_READ, EVENT_WRITE):\n                if self.is_registered(fileobj):\n                    self.sel.modify(fileobj, event_type, t)\n                else:\n                    self.sel.register(fileobj, event_type, t)\n                return True\n            return False\n\n        def accept(self, s):\n            while True:\n                try:\n                    conn, addr = s.accept()\n                except BlockingIOError:\n                    yield (EVENT_READ, s)\n                else:\n                    break\n            return conn, addr\n\n        def recv(self, conn, size):\n            while True:\n                try:\n                    msg = conn.recv(size)\n                except BlockingIOError:\n                    yield (EVENT_READ, conn)\n                else:\n                    break\n            return msg\n\n        def send(self, conn, msg):\n            while True:\n                try:\n                    size = conn.send(msg)\n                except BlockingIOError:\n                    yield (EVENT_WRITE, conn)\n                else:\n                    break\n            return size\n\n        def once(self):\n            self.polling()\n            unfinished = []\n            for t, data in self.queue:\n                try:\n                    data = t.send(data)\n                except StopIteration:\n                    continue\n\n                if self.register(t, data):\n                    unfinished.append((t, None))\n\n            self.queue = unfinished\n\n        def run(self):\n            while self.queue or self.sel.get_map():\n                self.once()\n\nBy assigning jobs to an event loop, the programming pattern resembles using\nthreads but with a user-level scheduler. `PEP 380`_ introduced generator\ndelegation via ``yield from``, allowing a generator to wait for other generators\nto complete. The following snippet is far more intuitive and readable than\ncallback-based I/O handling:\n\n.. code-block:: python\n\n    # server.py\n    # $ python3 server.py &\n    # $ nc localhost 5566\n\n    import socket\n    from loop import Loop\n\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    s.bind((\"127.0.0.1\", 5566))\n    s.listen(10240)\n    s.setblocking(False)\n\n    loop = Loop()\n\n    def handler(conn):\n        while True:\n            msg = yield from loop.recv(conn, 1024)\n            if not msg:\n                conn.close()\n                break\n            yield from loop.send(conn, msg)\n\n    def main():\n        while True:\n            conn, addr = yield from loop.accept(s)\n            conn.setblocking(False)\n            loop.create_task((handler(conn), None))\n\n    loop.create_task((main(), None))\n    loop.run()\n\nUsing an event loop with ``yield from`` manages connections without blocking\nthe main thread—this was how ``asyncio`` worked before Python 3.5. However,\n``yield from`` is ambiguous: why does adding ``@asyncio.coroutine`` transform\na generator into a coroutine? Instead of overloading generator syntax for\nasynchronous operations, `PEP 492`_ proposed that coroutines should become a\n**standalone concept** in Python. This led to the introduction of ``async/await``\nsyntax, dramatically improving readability for asynchronous programming.\n\nWhat is a Coroutine?\n--------------------\n\nPython documentation defines coroutines as \"a generalized form of subroutines.\"\nThis definition, while technically accurate, can be confusing. Based on our\ndiscussion, an event loop schedules generators to perform specific tasks—similar\nto how an OS dispatches jobs to threads. In this context, generators serve as\n\"routine workers.\" A **coroutine** is simply a task scheduled by an event loop\nwithin a program, rather than by the operating system.\n\nThe following snippet illustrates what ``@coroutine`` does. This decorator\ntransforms a function into a generator function and wraps it with\n``types.coroutine`` for backward compatibility:\n\n.. code-block:: python\n\n    import asyncio\n    import inspect\n    import types\n    from functools import wraps\n    from asyncio.futures import Future\n\n    def coroutine(func):\n        \"\"\"Simple prototype of coroutine decorator\"\"\"\n        if inspect.isgeneratorfunction(func):\n            return types.coroutine(func)\n\n        @wraps(func)\n        def coro(*a, **k):\n            res = func(*a, **k)\n            if isinstance(res, Future) or inspect.isgenerator(res):\n                res = yield from res\n            return res\n        return types.coroutine(coro)\n\n    @coroutine\n    def foo():\n        yield from asyncio.sleep(1)\n        print(\"Hello Foo\")\n\n    loop = asyncio.get_event_loop()\n    loop.run_until_complete(loop.create_task(foo()))\n    loop.close()\n\nWith Python 3.5+, the ``async def`` syntax creates native coroutines directly,\nand ``await`` replaces ``yield from`` for suspending execution. This makes the\nintent explicit: ``async def`` declares a coroutine, and ``await`` marks\nsuspension points where the event loop can switch to other tasks.\n\nConclusion\n----------\n\nAsynchronous programming via event loops has become more straightforward and\nreadable thanks to modern syntax and library support. Most programming\nlanguages, including Python, implement libraries that manage task scheduling\nthrough integration with new syntaxes. While ``async/await`` may seem enigmatic\ninitially, it provides a way for programmers to develop logical, linear code\nstructure—similar to using threads—while gaining the performance benefits of\nevent-driven I/O.\n\nWithout callback functions passing state between handlers, programmers no\nlonger need to worry about preserving local variables and arguments across\nasynchronous boundaries. This allows developers to focus on application logic\nrather than spending time troubleshooting concurrency issues. The evolution\nfrom callbacks to generators to ``async/await`` represents a significant\nadvancement in making concurrent programming accessible and maintainable.\n\nReferences\n----------\n\n1. `asyncio — Asynchronous I/O`_\n2. `PEP 342 - Coroutines via Enhanced Generators`_\n3. `PEP 380 - Syntax for Delegating to a Subgenerator`_\n4. `PEP 492 - Coroutines with async and await syntax`_\n\n.. _C10k problem: https://en.wikipedia.org/wiki/C10k_problem\n.. _Valgrind: https://valgrind.org/\n.. _select: https://docs.python.org/3/library/select.html\n.. _selectors: https://docs.python.org/3/library/selectors.html\n.. _asyncio — Asynchronous I/O: https://docs.python.org/3/library/asyncio.html\n.. _PEP 492: https://www.python.org/dev/peps/pep-0492/\n.. _PEP 380: https://www.python.org/dev/peps/pep-0380/\n.. _PEP 342 - Coroutines via Enhanced Generators: https://www.python.org/dev/peps/pep-0342/\n.. _PEP 492 - Coroutines with async and await syntax: https://www.python.org/dev/peps/pep-0492/\n.. _PEP 380 - Syntax for Delegating to a Subgenerator: https://www.python.org/dev/peps/pep-0380/\n"
  },
  {
    "path": "docs/notes/asyncio/python-asyncio-server.rst",
    "content": ".. meta::\n    :description lang=en: Python asyncio networking - TCP/UDP servers, HTTP clients, SSL/TLS, protocols\n    :keywords: Python, Python3, Asyncio, TCP Server, UDP Server, HTTP Client, SSL TLS, Network Programming\n\n===================\nAsyncio Networking\n===================\n\n:Source: `src/basic/asyncio_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/asyncio_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nAsyncio excels at network programming because network I/O is inherently\nasynchronous - you send a request and wait for a response. Instead of blocking\na thread while waiting, asyncio allows other tasks to run. This section covers\nbuilding TCP/UDP servers and clients, HTTP requests, SSL/TLS encryption, and\nthe Transport/Protocol API for low-level control.\n\nTCP Echo Server with Streams\n----------------------------\n\nThe streams API (``asyncio.start_server``, ``open_connection``) provides a\nhigh-level interface for TCP networking. It handles buffering, encoding, and\nconnection management automatically, making it the recommended approach for\nmost applications.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def handle_client(reader, writer):\n        addr = writer.get_extra_info('peername')\n        print(f\"Connected: {addr}\")\n\n        while True:\n            data = await reader.read(1024)\n            if not data:\n                break\n            message = data.decode()\n            print(f\"Received: {message!r} from {addr}\")\n            writer.write(data)\n            await writer.drain()\n\n        print(f\"Disconnected: {addr}\")\n        writer.close()\n        await writer.wait_closed()\n\n    async def main():\n        server = await asyncio.start_server(\n            handle_client, 'localhost', 8888\n        )\n        addr = server.sockets[0].getsockname()\n        print(f\"Serving on {addr}\")\n\n        async with server:\n            await server.serve_forever()\n\n    asyncio.run(main())\n\nTCP Client with Streams\n-----------------------\n\nThe client side uses ``asyncio.open_connection()`` to establish a connection.\nThe returned reader and writer objects provide async methods for sending and\nreceiving data.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def tcp_client(message):\n        reader, writer = await asyncio.open_connection(\n            'localhost', 8888\n        )\n\n        print(f\"Sending: {message!r}\")\n        writer.write(message.encode())\n        await writer.drain()\n\n        data = await reader.read(1024)\n        print(f\"Received: {data.decode()!r}\")\n\n        writer.close()\n        await writer.wait_closed()\n\n    asyncio.run(tcp_client(\"Hello, Server!\"))\n\nLow-Level TCP with Sockets\n--------------------------\n\nFor more control, you can use raw sockets with the event loop's socket methods.\nThis approach is useful when you need fine-grained control over socket options\nor when integrating with existing socket-based code.\n\n.. code-block:: python\n\n    import asyncio\n    import socket\n\n    async def handle_client(loop, conn):\n        while True:\n            data = await loop.sock_recv(conn, 1024)\n            if not data:\n                break\n            await loop.sock_sendall(conn, data)\n        conn.close()\n\n    async def server():\n        loop = asyncio.get_event_loop()\n\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        sock.setblocking(False)\n        sock.bind(('localhost', 8888))\n        sock.listen(100)\n\n        print(\"Server listening on localhost:8888\")\n        while True:\n            conn, addr = await loop.sock_accept(sock)\n            print(f\"Connected: {addr}\")\n            asyncio.create_task(handle_client(loop, conn))\n\n    asyncio.run(server())\n\nUDP Echo Server\n---------------\n\nUDP is connectionless, so the API is different from TCP. Use\n``create_datagram_endpoint()`` with a protocol class to handle UDP packets.\nEach packet is independent and may arrive out of order or not at all.\n\n.. code-block:: python\n\n    import asyncio\n\n    class EchoUDPProtocol(asyncio.DatagramProtocol):\n        def connection_made(self, transport):\n            self.transport = transport\n\n        def datagram_received(self, data, addr):\n            message = data.decode()\n            print(f\"Received {message!r} from {addr}\")\n            self.transport.sendto(data, addr)\n\n    async def main():\n        loop = asyncio.get_event_loop()\n        transport, protocol = await loop.create_datagram_endpoint(\n            EchoUDPProtocol,\n            local_addr=('localhost', 9999)\n        )\n        print(\"UDP server listening on localhost:9999\")\n\n        try:\n            await asyncio.sleep(3600)  # Run for 1 hour\n        finally:\n            transport.close()\n\n    asyncio.run(main())\n\nHTTP Client with SSL\n--------------------\n\nMaking HTTPS requests requires SSL context configuration. This example shows\nhow to fetch web pages using low-level streams with proper SSL verification.\n\n.. code-block:: python\n\n    import asyncio\n    import ssl\n\n    async def fetch_https(host, path=\"/\"):\n        # Create SSL context with certificate verification\n        ctx = ssl.create_default_context()\n\n        reader, writer = await asyncio.open_connection(\n            host, 443, ssl=ctx\n        )\n\n        # Send HTTP request\n        request = f\"GET {path} HTTP/1.1\\r\\nHost: {host}\\r\\nConnection: close\\r\\n\\r\\n\"\n        writer.write(request.encode())\n        await writer.drain()\n\n        # Read response\n        response = await reader.read()\n        writer.close()\n        await writer.wait_closed()\n\n        return response.decode()\n\n    async def main():\n        urls = [\n            (\"www.python.org\", \"/\"),\n            (\"github.com\", \"/\"),\n        ]\n        tasks = [fetch_https(host, path) for host, path in urls]\n        responses = await asyncio.gather(*tasks)\n\n        for (host, _), resp in zip(urls, responses):\n            status = resp.split('\\r\\n')[0]\n            print(f\"{host}: {status}\")\n\n    asyncio.run(main())\n\nHTTPS Server with SSL\n---------------------\n\nCreating an HTTPS server requires SSL certificates. This example shows a\nsimple HTTPS server that serves static content with TLS encryption.\n\n.. code-block:: python\n\n    import asyncio\n    import ssl\n\n    async def handle_request(reader, writer):\n        request = await reader.read(1024)\n\n        response = b\"HTTP/1.1 200 OK\\r\\n\"\n        response += b\"Content-Type: text/html\\r\\n\\r\\n\"\n        response += b\"<html><body><h1>Hello HTTPS!</h1></body></html>\"\n\n        writer.write(response)\n        await writer.drain()\n        writer.close()\n        await writer.wait_closed()\n\n    async def main():\n        # Create SSL context\n        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n        ctx.load_cert_chain('cert.pem', 'key.pem')\n\n        server = await asyncio.start_server(\n            handle_request, 'localhost', 8443, ssl=ctx\n        )\n        print(\"HTTPS server on https://localhost:8443\")\n\n        async with server:\n            await server.serve_forever()\n\n    # Generate self-signed cert:\n    # openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes\n    asyncio.run(main())\n\nTransport and Protocol API\n--------------------------\n\nThe Transport/Protocol API provides low-level control over network connections.\nTransports handle the actual I/O while Protocols handle the data processing.\nThis separation allows for flexible and reusable network code.\n\n.. code-block:: python\n\n    import asyncio\n\n    class EchoProtocol(asyncio.Protocol):\n        def connection_made(self, transport):\n            self.transport = transport\n            peername = transport.get_extra_info('peername')\n            print(f\"Connection from {peername}\")\n\n        def data_received(self, data):\n            print(f\"Received: {data.decode()!r}\")\n            self.transport.write(data)\n\n        def connection_lost(self, exc):\n            print(\"Connection closed\")\n\n    async def main():\n        loop = asyncio.get_event_loop()\n        server = await loop.create_server(\n            EchoProtocol, 'localhost', 8888\n        )\n\n        async with server:\n            await server.serve_forever()\n\n    asyncio.run(main())\n\nDNS Resolution\n--------------\n\nAsyncio provides async DNS resolution through ``getaddrinfo()``. This is\nuseful when you need to resolve hostnames without blocking the event loop.\n\n.. code-block:: python\n\n    import asyncio\n    import socket\n\n    async def resolve_host(host, port=80):\n        loop = asyncio.get_event_loop()\n        infos = await loop.getaddrinfo(\n            host, port,\n            family=socket.AF_UNSPEC,\n            type=socket.SOCK_STREAM\n        )\n\n        for family, type_, proto, canonname, sockaddr in infos:\n            ip, port = sockaddr[:2]\n            family_name = \"IPv4\" if family == socket.AF_INET else \"IPv6\"\n            print(f\"{host} -> {ip} ({family_name})\")\n\n    async def main():\n        hosts = [\"python.org\", \"github.com\", \"google.com\"]\n        await asyncio.gather(*[resolve_host(h) for h in hosts])\n\n    asyncio.run(main())\n\nSimple HTTP Server\n------------------\n\nA minimal HTTP server implementation showing how to parse requests and\nsend responses. For production use, consider frameworks like aiohttp or\nFastAPI.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def handle_http(reader, writer):\n        request = await reader.read(1024)\n        request_line = request.decode().split('\\r\\n')[0]\n        method, path, _ = request_line.split(' ')\n\n        print(f\"{method} {path}\")\n\n        # Simple routing\n        if path == '/':\n            body = b\"<h1>Home</h1>\"\n            status = \"200 OK\"\n        elif path == '/about':\n            body = b\"<h1>About</h1>\"\n            status = \"200 OK\"\n        else:\n            body = b\"<h1>404 Not Found</h1>\"\n            status = \"404 Not Found\"\n\n        response = f\"HTTP/1.1 {status}\\r\\n\"\n        response += f\"Content-Length: {len(body)}\\r\\n\"\n        response += \"Content-Type: text/html\\r\\n\\r\\n\"\n\n        writer.write(response.encode() + body)\n        await writer.drain()\n        writer.close()\n        await writer.wait_closed()\n\n    async def main():\n        server = await asyncio.start_server(\n            handle_http, 'localhost', 8080\n        )\n        print(\"HTTP server on http://localhost:8080\")\n\n        async with server:\n            await server.serve_forever()\n\n    asyncio.run(main())\n\nUsing sendfile for Efficient File Transfer\n------------------------------------------\n\nThe ``sendfile()`` method (Python 3.7+) efficiently transfers file contents\nto a transport using the OS's sendfile syscall, avoiding copying data through\nPython.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def handle_request(reader, writer):\n        await reader.read(1024)  # Read request\n\n        with open('index.html', 'rb') as f:\n            # Get file size\n            f.seek(0, 2)\n            size = f.tell()\n            f.seek(0)\n\n            # Send headers\n            headers = f\"HTTP/1.1 200 OK\\r\\n\"\n            headers += f\"Content-Length: {size}\\r\\n\"\n            headers += \"Content-Type: text/html\\r\\n\\r\\n\"\n            writer.write(headers.encode())\n\n            # Send file efficiently\n            loop = asyncio.get_event_loop()\n            await loop.sendfile(writer.transport, f)\n\n        writer.close()\n        await writer.wait_closed()\n\n    async def main():\n        server = await asyncio.start_server(\n            handle_request, 'localhost', 8080\n        )\n        async with server:\n            await server.serve_forever()\n\n    asyncio.run(main())\n\nConnection Pool\n---------------\n\nConnection pools reuse connections to avoid the overhead of establishing\nnew connections for each request. This is essential for high-performance\nclients that make many requests to the same server.\n\n.. code-block:: python\n\n    import asyncio\n    from collections import deque\n\n    class ConnectionPool:\n        def __init__(self, host, port, size=5):\n            self.host = host\n            self.port = port\n            self.size = size\n            self._pool = deque()\n            self._lock = asyncio.Lock()\n\n        async def get(self):\n            async with self._lock:\n                if self._pool:\n                    return self._pool.popleft()\n\n            # Create new connection\n            reader, writer = await asyncio.open_connection(\n                self.host, self.port\n            )\n            return reader, writer\n\n        async def put(self, reader, writer):\n            async with self._lock:\n                if len(self._pool) < self.size:\n                    self._pool.append((reader, writer))\n                else:\n                    writer.close()\n                    await writer.wait_closed()\n\n        async def close(self):\n            async with self._lock:\n                while self._pool:\n                    reader, writer = self._pool.popleft()\n                    writer.close()\n                    await writer.wait_closed()\n\n    async def fetch(pool, message):\n        reader, writer = await pool.get()\n        try:\n            writer.write(message.encode())\n            await writer.drain()\n            data = await reader.read(1024)\n            return data.decode()\n        finally:\n            await pool.put(reader, writer)\n\n    async def main():\n        pool = ConnectionPool('localhost', 8888, size=3)\n        try:\n            tasks = [fetch(pool, f\"msg{i}\") for i in range(10)]\n            results = await asyncio.gather(*tasks)\n            for r in results:\n                print(r)\n        finally:\n            await pool.close()\n\n    asyncio.run(main())\n"
  },
  {
    "path": "docs/notes/basic/index.rst",
    "content": ".. meta::\n    :description lang=en: Python basics cheat sheet covering syntax, data types, functions, classes, generators, typing, and essential Python programming concepts\n    :keywords: Python, Python3, basics, syntax, data types, functions, classes, generators, typing, list, dict, set, comprehension\n\nQuick Start\n===========\n\nThis cheat sheet is designed to help developers learn Python syntax from the\nground up. It covers the fundamentals while also introducing common patterns\nand idioms that experienced Python developers use, which may feel unfamiliar to\nbeginners. For instance, constructs like ``for ... else ...`` are rarely seen in\nother programming languages. Additionally, we’ll explore interesting topics such\nas ``__future__``, typing, and Unicode—concepts you may have heard of but never\nfully understood. By working through this cheat sheet, you’ll gain a solid\nfoundation in Python and learn to write code that feels truly Pythonic.\n\n.. toctree::\n   :maxdepth: 1\n\n   python-basic\n   python-future\n   python-func\n   python-object\n   python-typing\n   python-list\n   python-set\n   python-dict\n   python-heap\n   python-generator\n   python-unicode\n   python-rexp\n"
  },
  {
    "path": "docs/notes/basic/python-basic.rst",
    "content": ".. meta::\n    :description lang=en: Python basics tutorial covering fundamental syntax, data types, control flow, string formatting, and essential Python programming concepts\n    :keywords: Python, Python3, basics, syntax, data types, control flow, string formatting, variables, operators, conditionals\n\n============\nFrom Scratch\n============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe main goal of this cheat sheet is to collect some common and basic semantics\nor snippets. The cheat sheet includes some syntax, which we have already known\nbut still ambiguous in our mind, or some snippets, which we google them again\nand again. In addition, because **the end Of life date for Python 2** is coming.\nMost of the snippets are mainly based on **Python 3**'s syntax.\n\n\nHello world!\n------------\n\nWhen we start to learn a new language, we usually learn from printing\n**Hello world!**. In Python, we can use another way to print the  message by\nimporting ``__hello__`` module.  The source code can be found on\n`frozen.c <https://github.com/python/cpython/blob/master/Python/frozen.c>`_.\n\n.. code-block:: python\n\n    >>> print(\"Hello world!\")\n    Hello world!\n    >>> import __hello__\n    Hello world!\n    >>> import __phello__\n    Hello world!\n    >>> import __phello__.spam\n    Hello world!\n\n\nPython Version\n--------------\n\nIt is important for a programmer to know current Python version because\nnot every syntax will work in the current version. In this case, we can get the\nPython version by ``python -V`` or using the module, ``sys``.\n\n.. code-block:: python\n\n    >>> import sys\n    >>> print(sys.version)\n    3.7.1 (default, Nov  6 2018, 18:46:03)\n    [Clang 10.0.0 (clang-1000.11.45.5)]\n\nWe can also use ``platform.python_version`` to get Python version.\n\n.. code-block:: python\n\n    >>> import platform\n    >>> platform.python_version()\n    '3.7.1'\n\nSometimes, checking the current Python version is important because we may want\nto enable some features in some specific versions. ``sys.version_info`` provides more\ndetail information about the interpreter. We can use it to compare with the\nversion we want.\n\n.. code-block:: python\n\n    >>> import sys\n    >>> sys.version_info >= (3, 6)\n    True\n    >>> sys.version_info >= (3, 7)\n    False\n\nEllipsis\n--------\n\n`Ellipsis <https://docs.python.org/3/library/constants.html#Ellipsis>`_ is a\nbuilt-in constant. After Python 3.0, we case use ``...`` as ``Ellipsis``. It\nmay be the most enigmatic constant in Python. Based on the official document,\nwe can use it to extend slicing syntax. Nevertheless, there are some other\nconventions in type hinting, stub files, or function expressions.\n\n.. code-block:: python\n\n    >>> ...\n    Ellipsis\n    >>> ... == Ellipsis\n    True\n    >>> type(...)\n    <class 'ellipsis'>\n\nThe following snippet shows that we can use the ellipsis to represent a function\nor a class which has not implemented yet.\n\n.. code-block:: python\n\n    >>> class Foo: ...\n    ...\n    >>> def foo(): ...\n    ...\n\nif ... elif ... else\n--------------------\n\nThe **if statements** are used to control the code flow. Instead of using\n``switch`` or ``case`` statements control the logic of the code, Python uses\n``if ... elif ... else`` sequence. Although someone proposes we can use\n``dict`` to achieve ``switch`` statements, this solution may introduce\nunnecessary overhead such as creating disposable dictionaries and undermine\na readable code. Thus, the solution is not recommended.\n\n.. code-block:: python\n\n    >>> import random\n    >>> num = random.randint(0, 10)\n    >>> if num < 3:\n    ...     print(\"less than 3\")\n    ... elif num < 5:\n    ...     print(\"less than 5\")\n    ... else:\n    ...     print(num)\n    ...\n    less than 3\n\nfor Loop\n--------\n\nIn Python, we can access iterable object's items directly through the\n**for statement**. If we need to get indexes and items of an iterable object\nsuch as list or tuple at the same time, using ``enumerate`` is better than\n``range(len(iterable))``. Further information can be found on\n`Looping Techniques <https://docs.python.org/3/tutorial/datastructures.html#looping-techniques>`_.\n\n.. code-block:: python\n\n    >>> for val in [\"foo\", \"bar\"]:\n    ...     print(val)\n    ...\n    foo\n    bar\n    >>> for idx, val in enumerate([\"foo\", \"bar\", \"baz\"]):\n    ...     print(idx, val)\n    ...\n    (0, 'foo')\n    (1, 'bar')\n    (2, 'baz')\n\nfor ... else ...\n----------------\n\nIt may be a little weird when we see the ``else`` belongs to a ``for`` loop at\nthe first time. The ``else`` clause can assist us to avoid using flag\nvariables in loops. A loop’s ``else`` clause runs when no break occurs.\n\n.. code-block:: python\n\n    >>> for _ in range(5):\n    ...     pass\n    ... else:\n    ...     print(\"no break\")\n    ...\n    no break\n\nThe following snippet shows the difference between using a flag variable and\nthe ``else`` clause to control the loop. We can see that the ``else`` does not\nrun when the ``break`` occurs in the loop.\n\n.. code-block:: python\n\n    >>> is_break = False\n    >>> for x in range(5):\n    ...     if x % 2 == 0:\n    ...         is_break = True\n    ...         break\n    ...\n    >>> if is_break:\n    ...     print(\"break\")\n    ...\n    break\n\n    >>> for x in range(5):\n    ...     if x % 2 == 0:\n    ...         print(\"break\")\n    ...         break\n    ... else:\n    ...     print(\"no break\")\n    ...\n    break\n\nUsing ``range``\n---------------\n\nThe problem of ``range`` in Python 2 is that ``range`` may take up a lot of\nmemory if we need to iterate a loop many times. Consequently, using ``xrange``\nis recommended in Python 2.\n\n.. code-block:: python\n\n    >>> import platform\n    >>> import sys\n    >>> platform.python_version()\n    '2.7.15'\n    >>> sys.getsizeof(range(100000000))\n    800000072\n    >>> sys.getsizeof(xrange(100000000))\n    40\n\nIn Python 3, the built-in function ``range`` returns an iterable **range object**\ninstead of a list. The behavior of ``range`` is the same as the ``xrange`` in\nPython 2. Therefore, using ``range`` do not take up huge memory anymore if we\nwant to run a code block many times within a loop. Further information can be\nfound on PEP `3100 <https://www.python.org/dev/peps/pep-3100>`_.\n\n.. code-block:: python\n\n    >>> import platform\n    >>> import sys\n    >>> platform.python_version()\n    '3.7.1'\n    >>> sys.getsizeof(range(100000000))\n    48\n\nwhile ... else ...\n------------------\n\nThe ``else`` clause belongs to a while loop serves the same purpose as the\n``else`` clause in a for loop. We can observe that the ``else`` does not run\nwhen the ``break`` occurs in the while loop.\n\n.. code-block:: python\n\n    >>> n = 0\n    >>> while n < 5:\n    ...     if n == 3:\n    ...         break\n    ...     n += 1\n    ... else:\n    ...     print(\"no break\")\n    ...\n\nThe ``do while`` Statement\n--------------------------\n\nThere are many programming languages such as C/C++, Ruby, or Javascript,\nprovide the ``do while`` statement. In Python, there is no ``do while``\nstatement. However, we can place the condition and the ``break`` at the end of\na ``while`` loop to achieve the same thing.\n\n.. code-block:: python\n\n    >>> n = 0\n    >>> while True:\n    ...     n += 1\n    ...     if n == 5:\n    ...         break\n    ...\n    >>> n\n    5\n\ntry ... except ... else ...\n---------------------------\n\nMost of the time, we handle errors in ``except`` clause and clean up resources\nin ``finally`` clause. Interestingly, the ``try`` statement also provides an\n``else`` clause for us to avoid catching an exception which was raised by the\ncode that should not be protected by ``try ... except``. The ``else`` clause\nruns when no exception occurs between ``try`` and ``except``.\n\n.. code-block:: python\n\n    >>> try:\n    ...     print(\"No exception\")\n    ... except:\n    ...     pass\n    ... else:\n    ...     print(\"Success\")\n    ...\n    No exception\n    Success\n\nString\n------\n\nUnlike other programming languages, Python does not support string’s item\nassignment directly. Therefore, if it is necessary to manipulate string’s\nitems, e.g., swap items, we have to convert a string to a list and do a join\noperation after a series item assignments finish.\n\n.. code-block:: python\n\n    >>> a = \"Hello Python\"\n    >>> l = list(a)\n    >>> l[0], l[6] = 'h', 'p'\n    >>> ''.join(l)\n    'hello python'\n\nList\n----\n\nLists are versatile containers. Python provides a lot of ways such as\n**negative index**, **slicing statement**, or **list comprehension** to\nmanipulate lists. The following snippet shows some common operations of lists.\n\n.. code-block:: python\n\n    >>> a = [1, 2, 3, 4, 5]\n    >>> a[-1]                     # negative index\n    5\n    >>> a[1:]                     # slicing\n    [2, 3, 4, 5]\n    >>> a[1:-1]\n    [2, 3, 4]\n    >>> a[1:-1:2]\n    [2, 4]\n    >>> a[::-1]                   # reverse\n    [5, 4, 3, 2, 1]\n    >>> a[0] = 0                  # set an item\n    >>> a\n    [0, 2, 3, 4, 5]\n    >>> a.append(6)               # append an item\n    >>> a\n    [0, 2, 3, 4, 5, 6]\n    >>> del a[-1]                 # del an item\n    >>> a\n    [0, 2, 3, 4, 5]\n    >>> b = [x for x in range(3)] # list comprehension\n    >>> b\n    [0, 1, 2]\n    >>> a + b                     # add two lists\n    [0, 2, 3, 4, 5, 0, 1, 2]\n\nDict\n----\n\nDictionaries are key-value pairs containers. Like lists, Python supports many\nways such as **dict comprehensions** to manipulate dictionaries. After\nPython 3.6, dictionaries preserve the insertion order of keys. The Following\nsnippet shows some common operations of dictionaries.\n\n.. code-block:: python\n\n    >>> d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}\n    >>> d\n    {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}\n    >>> d['timmy'] = \"yellow\"        # set data\n    >>> d\n    {'timmy': 'yellow', 'barry': 'green', 'guido': 'blue'}\n    >>> del d['guido']               # del data\n    >>> d\n    >>> 'guido' in d                 # contain data\n    False\n    {'timmy': 'yellow', 'barry': 'green'}\n    >>> {k: v for k ,v in d.items()} # dict comprehension\n    {'timmy': 'yellow', 'barry': 'green'}\n    >>> d.keys()                     # list all keys\n    dict_keys(['timmy', 'barry'])\n    >>> d.values()                   # list all values\n    dict_values(['yellow', 'green'])\n\nFunction\n--------\n\nDefining a function in Python is flexible. We can define a function with\n**function documents**, **default values**, **arbitrary arguments**,\n**keyword arguments**, **keyword-only arguments**, and so on. The Following\nsnippet shows some common expressions to define functions.\n\n.. code-block:: python\n\n    def foo_with_doc():\n        \"\"\"Documentation String.\"\"\"\n\n    def foo_with_arg(arg): ...\n    def foo_with_args(*arg): ...\n    def foo_with_kwarg(a, b=\"foo\"): ...\n    def foo_with_args_kwargs(*args, **kwargs): ...\n    def foo_with_kwonly(a, b, *, k): ...           # python3\n    def foo_with_annotations(a: int) -> int: ...   # python3\n\nFunction Annotations\n--------------------\n\nInstead of writing string documents in functions to hint the type of parameters\nand return values, we can denote types by **function annotations**. Function annotations\nwhich the details can be found on PEP `3017 <https://www.python.org/dev/peps/pep-3107>`_\nand PEP `484 <https://www.python.org/dev/peps/pep-0484/>`_ were introduced in\nPython 3.0. They are an **optional** feature in **Python 3**. Using function\nannotations will lose compatibility in **Python 2**. We can solve this issue\nby stub files. In addition, we can do static type checking through\n`mypy <http://mypy-lang.org/>`_.\n\n.. code-block:: python\n\n    >>> def fib(n: int) -> int:\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         b, a = a + b, b\n    ...     return a\n    ...\n    >>> fib(10)\n    55\n\nGenerators\n----------\n\nPython uses the ``yield`` statement to define a **generator function**. In\nother words, when we call a generator function, the generator function will\nreturn a **generator** instead of return values for creating an **iterator**.\n\n.. code-block:: python\n\n    >>> def fib(n):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         yield a\n    ...         b, a = a + b, b\n    ...\n    >>> g = fib(10)\n    >>> g\n    <generator object fib at 0x10b240c78>\n    >>> for f in fib(5):\n    ...     print(f)\n    ...\n    0\n    1\n    1\n    2\n    3\n\nGenerator Delegation\n--------------------\n\nPython 3.3 introduced ``yield from`` expression. It allows a generator to\ndelegate parts of operations to another generator. In other words, we can\n**yield** a sequence **from** other **generators** in the current **generator function**.\nFurther information can be found on PEP `380 <https://www.python.org/dev/peps/pep-0380>`_.\n\n.. code-block:: python\n\n    >>> def fib(n):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         yield a\n    ...         b, a = a + b, b\n    ...\n    >>> def fibonacci(n):\n    ...     yield from fib(n)\n    ...\n    >>> [f for f in fibonacci(5)]\n    [0, 1, 1, 2, 3]\n\nClass\n-----\n\nPython supports many common features such as **class documents**, **multiple inheritance**,\n**class variables**, **instance variables**, **static method**, **class method**, and so on.\nFurthermore, Python provides some special methods for programmers to implement\n**iterators**, **context manager**, etc. The following snippet displays common definition\nof a class.\n\n.. code-block:: python\n\n    class A: ...\n    class B: ...\n    class Foo(A, B):\n        \"\"\"A class document.\"\"\"\n\n        foo = \"class variable\"\n\n        def __init__(self, v):\n            self.attr = v\n            self.__private = \"private var\"\n\n        @staticmethod\n        def bar_static_method(): ...\n\n        @classmethod\n        def bar_class_method(cls): ...\n\n        def bar(self):\n            \"\"\"A method document.\"\"\"\n\n        def bar_with_arg(self, arg): ...\n        def bar_with_args(self, *args): ...\n        def bar_with_kwarg(self, kwarg=\"bar\"): ...\n        def bar_with_args_kwargs(self, *args, **kwargs): ...\n        def bar_with_kwonly(self, *, k): ...\n        def bar_with_annotations(self, a: int): ...\n\n``async`` / ``await``\n---------------------\n\n``async`` and ``await`` syntax was introduced from Python 3.5. They were\ndesigned to be used with an event loop. Some other features such as the\n**asynchronous generator**  were implemented in later versions.\n\nA **coroutine function**\n(``async def``) are used to create a **coroutine** for an event loop. Python\nprovides a built-in module, **asyncio**, to write a concurrent code through\n``async``/``await`` syntax. The following snippet shows a simple example of\nusing **asyncio**. The code must be run on Python 3.7 or above.\n\n.. code-block:: python\n\n    import asyncio\n\n    async def http_ok(r, w):\n        head = b\"HTTP/1.1 200 OK\\r\\n\"\n        head += b\"Content-Type: text/html\\r\\n\"\n        head += b\"\\r\\n\"\n\n        body = b\"<html>\"\n        body += b\"<body><h1>Hello world!</h1></body>\"\n        body += b\"</html>\"\n\n        _ = await r.read(1024)\n        w.write(head + body)\n        await w.drain()\n        w.close()\n\n    async def main():\n        server = await asyncio.start_server(\n            http_ok, \"127.0.0.1\", 8888\n        )\n\n        async with server:\n            await server.serve_forever()\n\n    asyncio.run(main())\n\nAvoid ``exec`` and ``eval``\n---------------------------\n\nThe following snippet shows how to use the built-in function ``exec``. Yet,\nusing ``exec`` and ``eval`` are not recommended because of some security issues\nand unreadable code for a human. Further reading can be found on\n`Be careful with exec and eval in Python <http://lucumr.pocoo.org/2011/2/1/exec-in-python/>`_\nand `Eval really is dangerous <https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html>`_\n\n\n.. code-block:: python\n\n    >>> py = '''\n    ... def fib(n):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         b, a = b + a, b\n    ...     return a\n    ... print(fib(10))\n    ... '''\n    >>> exec(py, globals(), locals())\n    55\n"
  },
  {
    "path": "docs/notes/basic/python-dict.rst",
    "content": ".. meta::\n    :description lang=en: Python dictionary cheat sheet covering creation, manipulation, merging, comprehensions, defaultdict, OrderedDict, and LRU cache with code examples\n    :keywords: Python, Python3, Python dictionary, Python dict cheat sheet, dict, hashmap, key-value pairs, defaultdict, OrderedDict, dictionary comprehension, LRU cache, dict methods\n\n==========\nDictionary\n==========\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nDictionaries are one of Python's most powerful and frequently used data structures.\nThey store key-value pairs and provide O(1) average time complexity for lookups,\ninsertions, and deletions. Since Python 3.7, dictionaries maintain insertion order\nas a language feature. This cheat sheet covers essential dictionary operations,\nfrom basic manipulation to advanced patterns like emulating dictionary behavior\nwith special methods and implementing an LRU (Least Recently Used) cache.\n\nThe source code is available on `GitHub <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/dict.py>`_.\n\nReferences\n----------\n\n- `Mapping Types — dict <https://docs.python.org/3/library/stdtypes.html#mapping-types-dict>`_\n- `collections — Container datatypes <https://docs.python.org/3/library/collections.html>`_\n- `PEP 584 -- Add Union Operators To dict <https://www.python.org/dev/peps/pep-0584/>`_\n\nGet All Keys with ``dict.keys()``\n---------------------------------\n\nThe ``keys()`` method returns a view object containing all dictionary keys.\nIn Python 3, this is a dynamic view that reflects changes to the dictionary.\n\n.. code-block:: python\n\n    >>> a = {\"1\":1, \"2\":2, \"3\":3}\n    >>> b = {\"2\":2, \"3\":3, \"4\":4}\n    >>> a.keys()\n    ['1', '3', '2']\n\nGet Key-Value Pairs with ``dict.items()``\n-----------------------------------------\n\nThe ``items()`` method returns key-value pairs as tuples, which is useful for\niterating over both keys and values simultaneously.\n\n.. code-block:: python\n\n    >>> a = {\"1\":1, \"2\":2, \"3\":3}\n    >>> a.items()\n\nFind Common Keys Between Dictionaries\n-------------------------------------\n\nFinding keys that exist in multiple dictionaries is a common operation. Using\nset intersection is the most efficient approach.\n\n.. code-block:: python\n\n    >>> a = {\"1\":1, \"2\":2, \"3\":3}\n    >>> b = {\"2\":2, \"3\":3, \"4\":4}\n    >>> [_ for _ in a.keys() if _ in b.keys()]\n    ['3', '2']\n    >>> # better way\n    >>> c = set(a).intersection(set(b))\n    >>> list(c)\n    ['3', '2']\n    >>> # or\n    >>> [_ for _ in a if _ in b]\n    ['3', '2']\n    [('1', 1), ('3', 3), ('2', 2)]\n\nSet Default Values with ``setdefault()`` and ``defaultdict``\n------------------------------------------------------------\n\nWhen working with dictionaries, you often need to set default values for missing\nkeys. Python provides ``setdefault()`` and ``collections.defaultdict`` for this.\n\n.. code-block:: python\n\n    >>> # intuitive but not recommend\n    >>> d = {}\n    >>> key = \"foo\"\n    >>> if key not in d:\n    ...     d[key] = []\n    ...\n\n    # using d.setdefault(key[, default])\n    >>> d = {}\n    >>> key = \"foo\"\n    >>> d.setdefault(key, [])\n    []\n    >>> d[key] = 'bar'\n    >>> d\n    {'foo': 'bar'}\n\n    # using collections.defaultdict\n    >>> from collections import defaultdict\n    >>> d = defaultdict(list)\n    >>> d[\"key\"]\n    []\n    >>> d[\"foo\"]\n    []\n    >>> d[\"foo\"].append(\"bar\")\n    >>> d\n    defaultdict(<class 'list'>, {'key': [], 'foo': ['bar']})\n\n``dict.setdefault(key[, default])`` returns its default value if *key* is not in\nthe dictionary. However, if the key exists in the dictionary, the function will\nreturn its value.\n\n.. code-block:: python\n\n    >>> d = {}\n    >>> d.setdefault(\"key\", [])\n    []\n    >>> d[\"key\"] = \"bar\"\n    >>> d.setdefault(\"key\", [])\n    'bar'\n\nUpdate Dictionary with ``dict.update()``\n----------------------------------------\n\nThe ``update()`` method merges another dictionary into the current one. Keys from\nthe second dictionary overwrite existing keys in the first.\n\n.. code-block:: python\n\n    >>> a = {\"1\":1, \"2\":2, \"3\":3}\n    >>> b = {\"2\":2, \"3\":3, \"4\":4}\n    >>> a.update(b)\n    >>> a\n    {'1': 1, '3': 3, '2': 2, '4': 4}\n\nMerge Two Dictionaries in Python\n--------------------------------\n\nThere are several ways to merge dictionaries depending on your Python version.\nPython 3.9+ also supports the ``|`` operator for dictionary merging.\n\nPython 3.4 or lower\n\n.. code-block:: python\n\n    >>> a = {\"x\": 55, \"y\": 66}\n    >>> b = {\"a\": \"foo\", \"b\": \"bar\"}\n    >>> c = a.copy()\n    >>> c.update(b)\n    >>> c\n    {'y': 66, 'x': 55, 'b': 'bar', 'a': 'foo'}\n\n\nPython 3.5 or above\n\n.. code-block:: python\n\n    >>> a = {\"x\": 55, \"y\": 66}\n    >>> b = {\"a\": \"foo\", \"b\": \"bar\"}\n    >>> c = {**a, **b}\n    >>> c\n    {'x': 55, 'y': 66, 'a': 'foo', 'b': 'bar'}\n\nEmulate a Dictionary with Special Methods\n-----------------------------------------\n\nYou can create dictionary-like objects by implementing special methods:\n``__getitem__``, ``__setitem__``, ``__delitem__``, ``__contains__``, and ``__iter__``.\n\n.. code-block:: python\n\n    >>> class EmuDict(object):\n    ...   def __init__(self, dict_):\n    ...     self._dict = dict_\n    ...   def __repr__(self):\n    ...     return \"EmuDict: \" + repr(self._dict)\n    ...   def __getitem__(self, key):\n    ...     return self._dict[key]\n    ...   def __setitem__(self, key, val):\n    ...     self._dict[key] = val\n    ...   def __delitem__(self, key):\n    ...     del self._dict[key]\n    ...   def __contains__(self, key):\n    ...     return key in self._dict\n    ...   def __iter__(self):\n    ...     return iter(self._dict.keys())\n    ...\n    >>> _ = {\"1\":1, \"2\":2, \"3\":3}\n    >>> emud = EmuDict(_)\n    >>> emud  # __repr__\n    EmuDict: {'1': 1, '2': 2, '3': 3}\n    >>> emud['1']  # __getitem__\n    1\n    >>> emud['5'] = 5  # __setitem__\n    >>> emud\n    EmuDict: {'1': 1, '2': 2, '3': 3, '5': 5}\n    >>> del emud['2']  # __delitem__\n    >>> emud\n    EmuDict: {'1': 1, '3': 3, '5': 5}\n    >>> for _ in emud:\n    ...     print(emud[_], end=' ')  # __iter__\n    ... else:\n    ...     print()\n    ...\n    1 3 5\n    >>> '1' in emud  # __contains__\n    True\n\nImplement LRU Cache with OrderedDict\n------------------------------------\n\nAn LRU (Least Recently Used) cache evicts the least recently accessed items when\nfull. ``OrderedDict.move_to_end()`` makes implementation straightforward.\n\n.. code-block:: python\n\n\tfrom collections import OrderedDict\n\n\n\tclass LRU(object):\n\t\tdef __init__(self, maxsize=128):\n\t\t\tself._maxsize = maxsize\n\t\t\tself._cache = OrderedDict()\n\n\t\tdef get(self, k):\n\t\t\tif k not in self._cache:\n\t\t\t\treturn None\n\n\t\t\tself._cache.move_to_end(k)\n\t\t\treturn self._cache[k]\n\n\t\tdef put(self, k, v):\n\t\t\tif k in self._cache:\n\t\t\t\tself._cache.move_to_end(k)\n\t\t\tself._cache[k] = v\n\t\t\tif len(self._cache) > self._maxsize:\n\t\t\t\tself._cache.popitem(last=False)\n\n\t\tdef __str__(self):\n\t\t\treturn str(self._cache)\n\n\t\tdef __repr__(self):\n\t\t\treturn self.__str__()\n\nNote that dictionaries preserve insertion order from Python 3.7. Moreover,\nupdating a key does not affect the order. Therefore, a dictionary can also\nsimulate an LRU cache, which is similar to using an OrderedDict.\n\n.. code-block:: python\n\n\tclass LRU(object):\n\t\tdef __init__(self, maxsize=128):\n\t\t\tself._maxsize = maxsize\n\t\t\tself._cache = {}\n\n\t\tdef get(self, k):\n\t\t\tif k not in self._cache:\n\t\t\t\treturn None\n\n\t\t\tself.move_to_end(k)\n\t\t\treturn self._cache[k]\n\n\t\tdef put(self, k, v):\n\t\t\tif k in self._cache:\n\t\t\t\tself.move_to_end(k)\n\t\t\tself._cache[k] = v\n\t\t\tif len(self._cache) > self._maxsize:\n\t\t\t\tself.pop()\n\n\t\tdef pop(self):\n\t\t\tit = iter(self._cache.keys())\n\t\t\tdel self._cache[next(it)]\n\n\t\tdef move_to_end(self, k):\n\t\t\tif k not in self._cache:\n\t\t\t\treturn\n\t\t\tv = self._cache[k]\n\t\t\tdel self._cache[k]\n\t\t\tself._cache[k] = v\n\n\t\tdef __str__(self):\n\t\t\treturn str(self._cache)\n\n\t\tdef __repr__(self):\n\t\t\treturn self.__str__()\n"
  },
  {
    "path": "docs/notes/basic/python-func.rst",
    "content": ".. meta::\n    :description lang=en: Python function cheat sheet covering function definitions, arguments, decorators, lambda, closures, and functools with code examples\n    :keywords: Python, Python3, Python function, Python function cheat sheet, decorator, lambda, closure, *args, **kwargs, functools, lru_cache, partial\n\n========\nFunction\n========\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nA function can help programmers to wrap their logic into a task for avoiding\nduplicate code. In Python, the definition of a function is so versatile that\nwe can use many features such as decorator, annotation, docstrings, default\narguments and so on to define a function. In this cheat sheet, it collects\nmany ways to define a function and demystifies some enigmatic syntax in functions.\n\nDocument Functions\n------------------\n\nDocumentation provides programmers hints about how a function is supposed to\nbe used. A docstring gives an expedient way to write a readable document of\nfunctions. The docstring should be placed as the first statement in the function\nbody, enclosed in triple quotes. It can be accessed via the ``__doc__`` attribute\nor the built-in ``help()`` function. PEP `257 <https://www.python.org/dev/peps/pep-0257>`_\ndefines conventions for docstrings, and tools like ``pydocstyle`` can help\nenforce these conventions in your codebase.\n\n.. code-block:: python\n\n    >>> def example():\n    ...     \"\"\"This is an example function.\"\"\"\n    ...     print(\"Example function\")\n    ...\n    >>> example.__doc__\n    'This is an example function.'\n    >>> help(example)\n\nDefault Arguments\n-----------------\n\nDefining a function where the arguments are optional and have a default value\nis quite simple in Python. We can just assign values in the definition and make\nsure the default arguments appear in the end. When calling the function, you can\nomit arguments that have defaults, pass them positionally, or use keyword syntax\nto specify them explicitly. This flexibility makes functions more versatile and\neasier to use in different contexts.\n\n.. code-block:: python\n\n    >>> def add(a, b=0):\n    ...     return a + b\n    ...\n    >>> add(1)\n    1\n    >>> add(1, 2)\n    3\n    >>> add(1, b=2)\n    3\n\n.. warning::\n\n    Avoid using mutable objects (like lists or dictionaries) as default arguments.\n    Default argument values are evaluated only once when the function is defined,\n    not each time the function is called. This means mutable defaults are shared\n    across all calls, which can lead to unexpected behavior where modifications\n    persist between function calls.\n\n    .. code-block:: python\n\n        >>> def bad(items=[]):  # DON'T do this\n        ...     items.append(1)\n        ...     return items\n        ...\n        >>> bad()\n        [1]\n        >>> bad()  # unexpected!\n        [1, 1]\n\n        >>> def good(items=None):  # DO this instead\n        ...     if items is None:\n        ...         items = []\n        ...     items.append(1)\n        ...     return items\n\nVariable Arguments ``*args`` and ``**kwargs``\n---------------------------------------------\n\nPython provides a flexible way to handle functions that need to accept a variable\nnumber of arguments. Use ``*args`` to collect any number of positional arguments\ninto a tuple, and ``**kwargs`` to collect any number of keyword arguments into a\ndictionary. These are commonly used when writing wrapper functions, decorators,\nor functions that need to pass arguments through to other functions. The names\n``args`` and ``kwargs`` are conventions; you can use any valid identifier after\nthe ``*`` or ``**``.\n\n.. code-block:: python\n\n    >>> def example(a, b=None, *args, **kwargs):\n    ...     print(a, b)\n    ...     print(args)\n    ...     print(kwargs)\n    ...\n    >>> example(1, \"var\", 2, 3, word=\"hello\")\n    1 var\n    (2, 3)\n    {'word': 'hello'}\n\nUnpack Arguments\n----------------\n\nWhen calling a function, you can use ``*`` to unpack a sequence (like a list or\ntuple) into separate positional arguments, and ``**`` to unpack a dictionary into\nkeyword arguments. This is the inverse of ``*args`` and ``**kwargs`` in function\ndefinitions. Unpacking is particularly useful when you have data in a collection\nthat you want to pass to a function that expects separate arguments.\n\n.. code-block:: python\n\n    >>> def foo(a, b, c='BAZ'):\n    ...     print(a, b, c)\n    ...\n    >>> foo(*(\"FOO\", \"BAR\"), **{\"c\": \"baz\"})\n    FOO BAR baz\n\n    >>> args = [1, 2, 3]\n    >>> print(*args)\n    1 2 3\n\nKeyword-Only Arguments\n----------------------\n\nArguments that appear after ``*`` or ``*args`` in a function definition are\nkeyword-only, meaning they must be passed by name and cannot be passed positionally.\nThis feature, introduced in Python 3.0, helps prevent errors when functions have\nmany parameters, as it forces callers to be explicit about which argument they're\nproviding. Keyword-only arguments can have default values, making them optional.\n\n**New in Python 3.0**\n\n.. code-block:: python\n\n    >>> def f(a, b, *, kw):\n    ...     print(a, b, kw)\n    ...\n    >>> f(1, 2, kw=3)\n    1 2 3\n    >>> f(1, 2, 3)\n    Traceback (most recent call last):\n    TypeError: f() takes 2 positional arguments but 3 were given\n\n    >>> # keyword-only with default\n    >>> def g(a, *, kw=10):\n    ...     return a + kw\n    ...\n    >>> g(5)\n    15\n\nPositional-Only Arguments\n-------------------------\n\nArguments that appear before ``/`` in a function definition are positional-only,\nmeaning they cannot be passed by keyword name. This feature, introduced in Python\n3.8, is useful when parameter names are not meaningful to callers or when you want\nto reserve the flexibility to change parameter names without breaking existing code.\nMany built-in functions like ``len()`` and ``pow()`` use positional-only parameters.\nYou can combine positional-only (``/``) and keyword-only (``*``) in the same function.\n\n**New in Python 3.8**\n\n.. code-block:: python\n\n    >>> def f(a, b, /, c):\n    ...     print(a, b, c)\n    ...\n    >>> f(1, 2, 3)\n    1 2 3\n    >>> f(1, 2, c=3)\n    1 2 3\n    >>> f(a=1, b=2, c=3)\n    Traceback (most recent call last):\n    TypeError: f() got some positional-only arguments passed as keyword arguments\n\n    >>> # combining positional-only and keyword-only\n    >>> def g(a, /, b, *, c):\n    ...     return a + b + c\n    ...\n    >>> g(1, 2, c=3)\n    6\n\nAnnotations\n-----------\n\nFunction annotations provide a way to attach metadata to function parameters and\nreturn values. While Python doesn't enforce these annotations at runtime, they\nserve as documentation and are used by static type checkers like ``mypy`` to catch\ntype errors before code runs. Annotations are stored in the function's ``__annotations__``\nattribute as a dictionary. The ``typing`` module (Python 3.5+) provides additional\ntypes like ``List``, ``Dict``, ``Optional``, and ``Union`` for more expressive type hints.\n\n**New in Python 3.0**\n\n.. code-block:: python\n\n    >>> def fib(n: int) -> int:\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         b, a = a + b, b\n    ...     return a\n    ...\n    >>> fib(10)\n    55\n    >>> fib.__annotations__\n    {'n': <class 'int'>, 'return': <class 'int'>}\n\nLambda\n------\n\nLambda expressions create small anonymous functions inline. They are syntactically\nrestricted to a single expression, which is implicitly returned. Lambdas are useful\nfor short, throwaway functions, especially as arguments to higher-order functions\nlike ``sorted()``, ``map()``, ``filter()``, and ``reduce()``. While lambdas can make\ncode more concise, complex logic should be written as regular named functions for\nbetter readability and debugging.\n\n.. code-block:: python\n\n    >>> square = lambda x: x ** 2\n    >>> square(5)\n    25\n\n    >>> # lambda with multiple arguments\n    >>> add = lambda a, b: a + b\n    >>> add(2, 3)\n    5\n\n    >>> # lambda with conditional\n    >>> max_val = lambda a, b: a if a > b else b\n    >>> max_val(3, 5)\n    5\n\n    >>> # common use: sorting key\n    >>> pairs = [(1, 'b'), (2, 'a'), (3, 'c')]\n    >>> sorted(pairs, key=lambda x: x[1])\n    [(2, 'a'), (1, 'b'), (3, 'c')]\n\nCallable\n--------\n\nIn Python, any object that implements the ``__call__`` method is callable, meaning\nit can be invoked like a function using parentheses. This includes functions, methods,\nlambdas, classes (calling a class creates an instance), and instances of classes that\ndefine ``__call__``. The built-in ``callable()`` function returns ``True`` if an object\nappears callable, which is useful for checking before attempting to call an object\nto avoid ``TypeError`` exceptions.\n\n.. code-block:: python\n\n    >>> callable(print)\n    True\n    >>> callable(42)\n    False\n\n    >>> class Adder:\n    ...     def __init__(self, n):\n    ...         self.n = n\n    ...     def __call__(self, x):\n    ...         return self.n + x\n    ...\n    >>> add_five = Adder(5)\n    >>> callable(add_five)\n    True\n    >>> add_five(10)\n    15\n\nGet Function Name\n-----------------\n\nFunctions in Python are first-class objects with various attributes that provide\nmetadata about them. The ``__name__`` attribute contains the function's name as\ndefined, ``__doc__`` contains the docstring, ``__module__`` indicates which module\nthe function was defined in, and ``__annotations__`` holds type hints. These\nattributes are useful for debugging, logging, and introspection.\n\n.. code-block:: python\n\n    >>> def example_function():\n    ...     \"\"\"Example docstring.\"\"\"\n    ...     pass\n    ...\n    >>> example_function.__name__\n    'example_function'\n    >>> example_function.__doc__\n    'Example docstring.'\n    >>> example_function.__module__\n    '__main__'\n\nClosure\n-------\n\nA closure is a function that captures and remembers values from its enclosing\nlexical scope even after that scope has finished executing. This happens when\na nested function references variables from its outer function. Closures are\npowerful for creating function factories (functions that return customized\nfunctions), implementing decorators, and maintaining state without using global\nvariables or classes. Use the ``nonlocal`` keyword to modify captured variables\nfrom the enclosing scope.\n\n.. code-block:: python\n\n    >>> def make_multiplier(n):\n    ...     def multiplier(x):\n    ...         return x * n\n    ...     return multiplier\n    ...\n    >>> double = make_multiplier(2)\n    >>> triple = make_multiplier(3)\n    >>> double(5)\n    10\n    >>> triple(5)\n    15\n\n    >>> # closure with mutable state\n    >>> def make_counter():\n    ...     count = 0\n    ...     def counter():\n    ...         nonlocal count\n    ...         count += 1\n    ...         return count\n    ...     return counter\n    ...\n    >>> counter = make_counter()\n    >>> counter()\n    1\n    >>> counter()\n    2\n\nGenerator\n---------\n\nGenerator functions use the ``yield`` statement to produce a sequence of values\nlazily, one at a time, instead of computing all values upfront and storing them\nin memory. When called, a generator function returns a generator iterator that\ncan be iterated over with ``for`` loops or ``next()``. Generators are memory-efficient\nfor large sequences and can represent infinite sequences. Generator expressions\nprovide a concise syntax similar to list comprehensions but with lazy evaluation.\n\n.. code-block:: python\n\n    >>> def fib(n):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         yield a\n    ...         b, a = a + b, b\n    ...\n    >>> list(fib(10))\n    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n    >>> # generator expression\n    >>> squares = (x**2 for x in range(5))\n    >>> list(squares)\n    [0, 1, 4, 9, 16]\n\nDecorator\n---------\n\nDecorators are a powerful pattern for modifying or extending the behavior of\nfunctions without changing their source code. A decorator is a function that\ntakes a function as input and returns a new function (usually a wrapper) that\nadds some functionality before or after calling the original. The ``@decorator``\nsyntax is syntactic sugar for ``func = decorator(func)``. Always use ``@wraps``\nfrom ``functools`` in your wrapper function to preserve the original function's\nmetadata like ``__name__``, ``__doc__``, and ``__annotations__``.\n\n**New in Python 2.4** - PEP `318 <https://www.python.org/dev/peps/pep-0318/>`_\n\n.. code-block:: python\n\n    >>> from functools import wraps\n    >>> def log_calls(func):\n    ...     @wraps(func)\n    ...     def wrapper(*args, **kwargs):\n    ...         print(f\"Calling {func.__name__}\")\n    ...         return func(*args, **kwargs)\n    ...     return wrapper\n    ...\n    >>> @log_calls\n    ... def greet(name):\n    ...     return f\"Hello, {name}!\"\n    ...\n    >>> greet(\"Alice\")\n    Calling greet\n    'Hello, Alice!'\n\n    >>> # equivalent to:\n    >>> # greet = log_calls(greet)\n\n.. note::\n\n    Always use ``@wraps(func)`` in decorators to preserve the original function's\n    ``__name__``, ``__doc__``, and other attributes. Without it, the decorated\n    function will have the wrapper's attributes, which makes debugging harder.\n\nDecorator with Arguments\n------------------------\n\nTo create a decorator that accepts arguments, you need an extra layer of nesting.\nThe outermost function takes the decorator's arguments and returns the actual\ndecorator. The middle function takes the function being decorated and returns\nthe wrapper. The innermost function is the wrapper that executes when the decorated\nfunction is called. This pattern is commonly used for decorators like ``@repeat(3)``\nor ``@route('/path')``.\n\n.. code-block:: python\n\n    >>> from functools import wraps\n    >>> def repeat(times):\n    ...     def decorator(func):\n    ...         @wraps(func)\n    ...         def wrapper(*args, **kwargs):\n    ...             for _ in range(times):\n    ...                 result = func(*args, **kwargs)\n    ...             return result\n    ...         return wrapper\n    ...     return decorator\n    ...\n    >>> @repeat(3)\n    ... def say_hello():\n    ...     print(\"Hello!\")\n    ...\n    >>> say_hello()\n    Hello!\n    Hello!\n    Hello!\n\n    >>> # equivalent to:\n    >>> # say_hello = repeat(3)(say_hello)\n\nClass Decorator\n---------------\n\nDecorators can also be implemented as classes instead of functions. A class-based\ndecorator implements ``__init__`` to receive the decorated function and ``__call__``\nto act as the wrapper. This approach is useful when the decorator needs to maintain\nstate across multiple calls to the decorated function, such as counting calls,\ncaching results, or tracking timing information.\n\n.. code-block:: python\n\n    >>> class CountCalls:\n    ...     def __init__(self, func):\n    ...         self.func = func\n    ...         self.count = 0\n    ...     def __call__(self, *args, **kwargs):\n    ...         self.count += 1\n    ...         return self.func(*args, **kwargs)\n    ...\n    >>> @CountCalls\n    ... def example():\n    ...     return \"result\"\n    ...\n    >>> example()\n    'result'\n    >>> example()\n    'result'\n    >>> example.count\n    2\n\nCache with ``lru_cache``\n------------------------\n\nThe ``lru_cache`` decorator from ``functools`` automatically caches function results\nbased on the arguments passed. When the function is called with the same arguments\nagain, the cached result is returned instead of recomputing it. This is especially\nuseful for expensive computations or recursive functions like Fibonacci. The ``maxsize``\nparameter limits cache size (use ``None`` for unlimited). Use ``cache_info()`` to\nsee hit/miss statistics and ``cache_clear()`` to reset the cache.\n\n**New in Python 3.2**\n\n.. code-block:: python\n\n    >>> from functools import lru_cache\n    >>> @lru_cache(maxsize=None)\n    ... def fib(n):\n    ...     if n < 2:\n    ...         return n\n    ...     return fib(n - 1) + fib(n - 2)\n    ...\n    >>> fib(100)\n    354224848179261915075\n    >>> fib.cache_info()\n    CacheInfo(hits=98, misses=101, maxsize=None, currsize=101)\n    >>> fib.cache_clear()  # clear the cache\n\n**New in Python 3.9** - ``@cache`` is a simpler alias for ``@lru_cache(maxsize=None)``\n\n.. code-block:: python\n\n    >>> from functools import cache\n    >>> @cache\n    ... def factorial(n):\n    ...     return n * factorial(n-1) if n else 1\n\nPartial Functions\n-----------------\n\nThe ``functools.partial`` function creates a new callable with some arguments of\nthe original function pre-filled. This is useful for adapting functions to interfaces\nthat expect fewer arguments, creating specialized versions of general functions,\nor preparing callback functions. The resulting partial object can be called with\nthe remaining arguments. You can pre-fill both positional and keyword arguments.\n\n.. code-block:: python\n\n    >>> from functools import partial\n    >>> def power(base, exponent):\n    ...     return base ** exponent\n    ...\n    >>> square = partial(power, exponent=2)\n    >>> cube = partial(power, exponent=3)\n    >>> square(5)\n    25\n    >>> cube(5)\n    125\n\n    >>> # useful for callbacks\n    >>> from functools import partial\n    >>> def greet(greeting, name):\n    ...     return f\"{greeting}, {name}!\"\n    ...\n    >>> say_hello = partial(greet, \"Hello\")\n    >>> say_hello(\"Alice\")\n    'Hello, Alice!'\n\n``singledispatch`` - Function Overloading\n-----------------------------------------\n\nThe ``singledispatch`` decorator from ``functools`` enables function overloading\nbased on the type of the first argument. You define a base function and then\nregister specialized implementations for different types using the ``@func.register``\ndecorator. When the function is called, Python automatically dispatches to the\nappropriate implementation based on the argument's type. This is useful for writing\ngeneric functions that behave differently for different types.\n\n**New in Python 3.4**\n\n.. code-block:: python\n\n    >>> from functools import singledispatch\n    >>> @singledispatch\n    ... def process(arg):\n    ...     return f\"Default: {arg}\"\n    ...\n    >>> @process.register(int)\n    ... def _(arg):\n    ...     return f\"Integer: {arg * 2}\"\n    ...\n    >>> @process.register(list)\n    ... def _(arg):\n    ...     return f\"List with {len(arg)} items\"\n    ...\n    >>> process(\"hello\")\n    'Default: hello'\n    >>> process(5)\n    'Integer: 10'\n    >>> process([1, 2, 3])\n    'List with 3 items'\n\n``reduce`` - Cumulative Operations\n----------------------------------\n\nThe ``reduce`` function from ``functools`` applies a two-argument function\ncumulatively to the items of a sequence, from left to right, reducing the sequence\nto a single value. For example, ``reduce(f, [a, b, c, d])`` computes ``f(f(f(a, b), c), d)``.\nAn optional third argument provides an initial value. While ``reduce`` can be powerful,\nlist comprehensions or explicit loops are often more readable for simple cases.\n\n.. code-block:: python\n\n    >>> from functools import reduce\n    >>> # sum of list\n    >>> reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])\n    15\n\n    >>> # product of list\n    >>> reduce(lambda x, y: x * y, [1, 2, 3, 4, 5])\n    120\n\n    >>> # with initial value\n    >>> reduce(lambda x, y: x + y, [1, 2, 3], 10)\n    16\n\nHigher-Order Functions\n----------------------\n\nHigher-order functions are functions that take other functions as arguments or\nreturn functions as results. Python provides several built-in higher-order functions\nthat are commonly used for functional programming patterns. ``map()`` applies a\nfunction to every item in an iterable, ``filter()`` keeps items where the function\nreturns ``True``, and ``sorted()``/``min()``/``max()`` accept a ``key`` function\nto customize comparison. These functions return iterators (except ``sorted``),\nso wrap them in ``list()`` if you need a list.\n\n.. code-block:: python\n\n    >>> # map - apply function to each item\n    >>> list(map(lambda x: x**2, [1, 2, 3, 4]))\n    [1, 4, 9, 16]\n\n    >>> # filter - keep items where function returns True\n    >>> list(filter(lambda x: x > 2, [1, 2, 3, 4]))\n    [3, 4]\n\n    >>> # sorted with key function\n    >>> sorted(['banana', 'apple', 'cherry'], key=len)\n    ['apple', 'banana', 'cherry']\n\n    >>> # min/max with key function\n    >>> max(['apple', 'banana', 'cherry'], key=len)\n    'banana'\n"
  },
  {
    "path": "docs/notes/basic/python-future.rst",
    "content": ".. meta::\n    :description lang=en: Python __future__ module guide covering future statements, backward compatibility, and feature backporting from newer Python versions\n    :keywords: Python, __future__, future statements, backward compatibility, print_function, annotations, division\n\n======\nFuture\n======\n\n.. contents:: Table of Contents\n    :backlinks: none\n\n\n`Future statements <https://docs.python.org/3/reference/simple_stmts.html#future>`_\ntell the interpreter to compile some semantics as the semantics which will be\navailable in the future Python version. In other words, Python uses ``from __future__ import feature``\nto backport features from other higher Python versions to the current interpreter.\nIn Python 3, many features such as ``print_function`` are already enabled, but\nwe still leave these future statements for backward compatibility.\n\nFuture statements are **NOT** import statements. Future statements change how\nPython interprets the code. They **MUST** be at the top of the file. Otherwise,\nPython interpreter will raise ``SyntaxError``.\n\nIf you're interested in future statements and want to acquire more explanation,\nfurther information can be found on `PEP 236 - Back to the __future__  <https://www.python.org/dev/peps/pep-0236>`_\n\nList All New Features\n---------------------\n\n`__future__ <https://docs.python.org/3/library/__future__.html>`_ is a Python\nmodule. We can use it to check what kind of future features can import to\ncurrent Python interpreter. The fun is ``import __future__`` is **NOT** a future\nstatement, it is a import statement.\n\n.. code-block:: python\n\n    >>> from pprint import pprint\n    >>> import __future__\n    >>> pprint(__future__.all_feature_names)\n    ['nested_scopes',\n     'generators',\n     'division',\n     'absolute_import',\n     'with_statement',\n     'print_function',\n     'unicode_literals',\n     'barry_as_FLUFL',\n     'generator_stop',\n     'annotations']\n\nFuture statements not only change the behavior of the Python interpreter but\nalso import ``__future__._Feature`` into the current program.\n\n.. code-block:: python\n\n    >>> from __future__ import print_function\n    >>> print_function\n    _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 65536)\n\nPrint Function\n--------------\n\nReplacing **print statement** to  **print function** is one of the most\nnotorious decision in Python history. However, this change brings some\nflexibilities to extend the ability of ``print``. Further information can\nbe found on PEP `3105 <https://www.python.org/dev/peps/pep-3105>`_.\n\n.. code-block:: python\n\n    >>> print \"Hello World\"  # print is a statement\n    Hello World\n    >>> from __future__ import print_function\n    >>> print \"Hello World\"\n      File \"<stdin>\", line 1\n        print \"Hello World\"\n                          ^\n    SyntaxError: invalid syntax\n    >>> print(\"Hello World\") # print become a function\n    Hello World\n\nUnicode\n-------\n\nAs **print function**, making text become Unicode is another infamous decision.\nNevertheless, many modern programming languages’ text is Unicode. This change\ncompels us to decode texts early in order to prevent runtime error after we\nrun programs for a while. Further information can be found on PEP\n`3112 <https://www.python.org/dev/peps/pep-3112>`_.\n\n.. code-block:: python\n\n    >>> type(\"Guido\") # string type is str in python2\n    <type 'str'>\n    >>> from __future__ import unicode_literals\n    >>> type(\"Guido\") # string type become unicode\n    <type 'unicode'>\n\nDivision\n--------\n\nSometimes, it is counterintuitive when the division result is int or long.\nIn this case, Python 3 enables the **true division** by default. However, in\nPython 2, we have to backport ``division`` to the current interpreter. Further\ninformation can be found on  PEP `238 <https://www.python.org/dev/peps/pep-0238>`_.\n\n.. code-block:: python\n\n    >>> 1 / 2\n    0\n    >>> from __future__ import division\n    >>> 1 / 2   # return a float (classic division)\n    0.5\n    >>> 1 // 2  # return a int (floor division)\n    0\n\nAnnotations\n-----------\n\nBefore Python 3.7, we cannot assign annotations in a class or a function if\nit is not available in the current scope. A common situation is the definition\nof a container class.\n\n.. code-block:: python\n\n    class Tree(object):\n\n        def insert(self, tree: Tree): ...\n\nExample\n\n.. code-block:: bash\n\n    $ python3 foo.py\n    Traceback (most recent call last):\n      File \"foo.py\", line 1, in <module>\n        class Tree(object):\n      File \"foo.py\", line 3, in Tree\n        def insert(self, tree: Tree): ...\n    NameError: name 'Tree' is not defined\n\nIn this case, the definition of the class is not available yet. Python interpreter\ncannot parse the annotation during their definition time. To solve this issue,\nPython uses string literals to replace the class.\n\n.. code-block:: python\n\n    class Tree(object):\n\n        def insert(self, tree: 'Tree'): ...\n\nAfter version 3.7, Python introduces the future statement, ``annotations``, to\nperform postponed evaluation. It will become the default feature in Python 4.\nFor further information please refer to PEP `563 <https://www.python.org/dev/peps/pep-0563>`_.\n\n\n.. code-block:: python\n\n    from __future__ import annotations\n\n    class Tree(object):\n\n        def insert(self, tree: Tree): ...\n\nBDFL Retirement\n---------------\n\n**New in Python 3.1**\n\nPEP `401 <https://www.python.org/dev/peps/pep-0401/>`_ is just an Easter egg.\nThis feature brings the current interpreter back to the past. It enables the\ndiamond operator ``<>`` in Python 3.\n\n.. code-block:: python\n\n    >>> 1 != 2\n    True\n    >>> from __future__ import barry_as_FLUFL\n    >>> 1 != 2\n      File \"<stdin>\", line 1\n        1 != 2\n           ^\n    SyntaxError: with Barry as BDFL, use '<>' instead of '!='\n    >>> 1 <> 2\n    True\n\nBraces\n------\n\n``braces`` is an Easter egg. The source code can be found on\n`future.c <https://github.com/python/cpython/blob/master/Python/future.c>`_.\n\n.. code-block:: python\n\n    >>> from __future__ import braces\n      File \"<stdin>\", line 1\n    SyntaxError: not a chance\n"
  },
  {
    "path": "docs/notes/basic/python-generator.rst",
    "content": ".. meta::\n    :description lang=en: Python generator cheat sheet covering generator functions, generator expressions, yield, yield from, send, async generators, and coroutines with code examples\n    :keywords: Python, Python3, Python generator, Python generator cheat sheet, yield, yield from, generator expression, async generator, iterator, coroutine, contextmanager\n\n=========\nGenerator\n=========\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nGenerators are a powerful feature in Python for creating iterators. They allow\nyou to iterate over data without storing the entire sequence in memory, making\nthem ideal for processing large datasets or infinite sequences. This cheat sheet\ncovers generator functions, generator expressions, ``yield``, ``yield from``,\nsending values to generators, and async generators.\n\nGenerator Function vs Generator Expression\n------------------------------------------\n\nA generator function is defined like a normal function but uses ``yield`` to\nproduce a sequence of values. When called, it returns a generator object that\ncan be iterated over. A generator expression is a compact syntax similar to\nlist comprehensions but produces values lazily on demand.\n\n.. code-block:: python\n\n    # generator function\n    >>> def gen_func():\n    ...     yield 5566\n    ...\n    >>> g = gen_func()\n    >>> g\n    <generator object gen_func at 0x...>\n    >>> next(g)\n    5566\n\n    # generator expression\n    >>> g = (x for x in range(3))\n    >>> next(g)\n    0\n    >>> next(g)\n    1\n\nYield Values from Generator\n---------------------------\n\nThe ``yield`` statement produces a value and suspends the generator's execution.\nWhen ``next()`` is called again, execution resumes from where it left off. This\nexample generates prime numbers by checking divisibility for each candidate.\n\n.. code-block:: python\n\n    >>> def prime(n):\n    ...     p = 2\n    ...     while n > 0:\n    ...         for x in range(2, p):\n    ...             if p % x == 0:\n    ...                 break\n    ...         else:\n    ...             yield p\n    ...             n -= 1\n    ...         p += 1\n    ...\n    >>> list(prime(5))\n    [2, 3, 5, 7, 11]\n\nUnpack Generators\n-----------------\n\nPython 3.5+ (PEP 448) allows unpacking generators directly into lists, sets,\nfunction arguments, and variables using the ``*`` operator. This provides a\nconvenient way to consume generator values without explicit iteration.\n\n.. code-block:: python\n\n    # PEP 448 - unpacking inside a list\n    >>> g1 = (x for x in range(3))\n    >>> g2 = (x**2 for x in range(2))\n    >>> [1, *g1, 2, *g2]\n    [1, 0, 1, 2, 2, 0, 1]\n\n    # unpacking inside a set\n    >>> g = (x for x in [5, 5, 6, 6])\n    >>> {*g}\n    {5, 6}\n\n    # unpacking to variables\n    >>> g = (x for x in range(3))\n    >>> a, b, c = g\n    >>> a, b, c\n    (0, 1, 2)\n\n    # extended unpacking\n    >>> g = (x for x in range(6))\n    >>> a, b, *c, d = g\n    >>> a, b, d\n    (0, 1, 5)\n    >>> c\n    [2, 3, 4]\n\n    # unpacking inside a function\n    >>> print(*(x for x in range(3)))\n    0 1 2\n\nIterable Class via Generator\n----------------------------\n\nYou can make a class iterable by implementing ``__iter__`` as a generator method.\nThis approach is cleaner than implementing a separate iterator class. The\n``__reversed__`` method can also be implemented as a generator to support the\nbuilt-in ``reversed()`` function.\n\n.. code-block:: python\n\n    >>> class Count:\n    ...     def __init__(self, n):\n    ...         self._n = n\n    ...     def __iter__(self):\n    ...         n = self._n\n    ...         while n > 0:\n    ...             yield n\n    ...             n -= 1\n    ...     def __reversed__(self):\n    ...         n = 1\n    ...         while n <= self._n:\n    ...             yield n\n    ...             n += 1\n    ...\n    >>> list(Count(5))\n    [5, 4, 3, 2, 1]\n    >>> list(reversed(Count(5)))\n    [1, 2, 3, 4, 5]\n\nSend Values to Generator\n------------------------\n\nGenerators can receive values through the ``send()`` method. The sent value\nbecomes the result of the ``yield`` expression inside the generator. Before\nsending values, you must start the generator by calling ``next()`` or\n``send(None)`` to advance it to the first ``yield``.\n\n.. code-block:: python\n\n    >>> def spam():\n    ...     msg = yield\n    ...     print(\"Message:\", msg)\n    ...\n    >>> g = spam()\n    >>> next(g)  # start generator\n    >>> try:\n    ...     g.send(\"Hello World!\")\n    ... except StopIteration:\n    ...     pass\n    Message: Hello World!\n\nyield from Expression\n---------------------\n\nThe ``yield from`` expression delegates iteration to another generator or\niterable. It automatically handles forwarding ``send()``, ``throw()``, and\n``close()`` calls to the subgenerator, making it ideal for creating generator\npipelines and recursive generators.\n\n.. code-block:: python\n\n    >>> def subgen():\n    ...     try:\n    ...         yield 9527\n    ...     except ValueError:\n    ...         print(\"got ValueError\")\n    ...\n    >>> def delegating_gen():\n    ...     yield from subgen()\n    ...\n    >>> g = delegating_gen()\n    >>> next(g)\n    9527\n    >>> try:\n    ...     g.throw(ValueError)\n    ... except StopIteration:\n    ...     pass\n    got ValueError\n\nYou can chain multiple ``yield from`` expressions together. The\n``inspect.getgeneratorstate()`` function helps track the generator's lifecycle\nthrough its states: GEN_CREATED, GEN_RUNNING, GEN_SUSPENDED, and GEN_CLOSED.\n\n.. code-block:: python\n\n    # yield from + yield from\n    >>> import inspect\n    >>> def subgen():\n    ...     yield from range(3)\n    ...\n    >>> def delegating_gen():\n    ...     yield from subgen()\n    ...\n    >>> g = delegating_gen()\n    >>> inspect.getgeneratorstate(g)\n    'GEN_CREATED'\n    >>> next(g)\n    0\n    >>> inspect.getgeneratorstate(g)\n    'GEN_SUSPENDED'\n    >>> g.close()\n    >>> inspect.getgeneratorstate(g)\n    'GEN_CLOSED'\n\nyield from with Return\n----------------------\n\nGenerators can return a value using the ``return`` statement. The returned value\nis accessible through the ``value`` attribute of the ``StopIteration`` exception.\nWhen using ``yield from``, the return value of the subgenerator becomes the value\nof the ``yield from`` expression.\n\n.. code-block:: python\n\n    >>> def average():\n    ...     total = .0\n    ...     count = 0\n    ...     while True:\n    ...         val = yield\n    ...         if not val:\n    ...             break\n    ...         total += val\n    ...         count += 1\n    ...     return total / count\n    ...\n    >>> g = average()\n    >>> next(g)\n    >>> g.send(3)\n    >>> g.send(5)\n    >>> try:\n    ...     g.send(None)\n    ... except StopIteration as e:\n    ...     print(e.value)\n    4.0\n\n.. code-block:: python\n\n    >>> def subgen():\n    ...     yield 9527\n    ...\n    >>> def delegating_gen():\n    ...     yield from subgen()\n    ...     return 5566\n    ...\n    >>> g = delegating_gen()\n    >>> next(g)\n    9527\n    >>> try:\n    ...     next(g)\n    ... except StopIteration as e:\n    ...     print(e.value)\n    5566\n\nGenerate Sequences\n------------------\n\nThe ``yield from`` expression provides a concise way to yield all values from\nan iterable. This is particularly useful for chaining multiple sequences together\nor flattening nested structures.\n\n.. code-block:: python\n\n    >>> def chain():\n    ...     yield from 'ab'\n    ...     yield from range(3)\n    ...\n    >>> list(chain())\n    ['a', 'b', 0, 1, 2]\n\nWhat ``RES = yield from EXP`` Does\n----------------------------------\n\nThis snippet shows the simplified equivalent of what ``yield from`` does\ninternally, as described in PEP 380. It handles iteration, value passing via\n``send()``, and captures the return value from the subgenerator.\n\n.. code-block:: python\n\n    # Simplified version (ref: PEP 380)\n    >>> def subgen():\n    ...     for x in range(3):\n    ...         yield x\n    ...\n    >>> def delegating_gen():\n    ...     _i = iter(subgen())\n    ...     try:\n    ...         _y = next(_i)\n    ...     except StopIteration as _e:\n    ...         RES = _e.value\n    ...     else:\n    ...         while True:\n    ...             _s = yield _y\n    ...             try:\n    ...                 _y = _i.send(_s)\n    ...             except StopIteration as _e:\n    ...                 RES = _e.value\n    ...                 break\n    ...\n    >>> list(delegating_gen())\n    [0, 1, 2]\n\nCheck Generator Type\n--------------------\n\nUse ``types.GeneratorType`` to check if an object is a generator. This is useful\nfor writing functions that need to handle generators differently from other\niterables.\n\n.. code-block:: python\n\n    >>> from types import GeneratorType\n    >>> def gen_func():\n    ...     yield 5566\n    ...\n    >>> isinstance(gen_func(), GeneratorType)\n    True\n\nCheck Generator State\n---------------------\n\nThe ``inspect.getgeneratorstate()`` function returns the current state of a\ngenerator. This is helpful for debugging and understanding the generator lifecycle.\nThe four possible states are: GEN_CREATED (not started), GEN_RUNNING (currently\nexecuting), GEN_SUSPENDED (paused at yield), and GEN_CLOSED (completed or closed).\n\n.. code-block:: python\n\n    >>> import inspect\n    >>> def gen_func():\n    ...     yield 9527\n    ...\n    >>> g = gen_func()\n    >>> inspect.getgeneratorstate(g)\n    'GEN_CREATED'\n    >>> next(g)\n    9527\n    >>> inspect.getgeneratorstate(g)\n    'GEN_SUSPENDED'\n    >>> g.close()\n    >>> inspect.getgeneratorstate(g)\n    'GEN_CLOSED'\n\nContext Manager via Generator\n-----------------------------\n\nThe ``@contextlib.contextmanager`` decorator transforms a generator function into\na context manager. Code before ``yield`` runs on entering the ``with`` block,\nand code after ``yield`` (typically in ``finally``) runs on exit. The yielded\nvalue is bound to the variable after ``as``.\n\n.. code-block:: python\n\n    >>> import contextlib\n    >>> @contextlib.contextmanager\n    ... def mylist():\n    ...     try:\n    ...         l = [1, 2, 3, 4, 5]\n    ...         yield l\n    ...     finally:\n    ...         print(\"exit scope\")\n    ...\n    >>> with mylist() as l:\n    ...     print(l)\n    [1, 2, 3, 4, 5]\n    exit scope\n\nWhat ``@contextmanager`` Does\n-----------------------------\n\nThis snippet shows a simplified implementation of how ``@contextmanager`` works\ninternally. It wraps a generator in a class that implements the context manager\nprotocol (``__enter__`` and ``__exit__``), handling both normal exit and\nexception propagation.\n\n.. code-block:: python\n\n    class GeneratorCM:\n        def __init__(self, gen):\n            self._gen = gen\n\n        def __enter__(self):\n            return next(self._gen)\n\n        def __exit__(self, *exc_info):\n            try:\n                if exc_info[0] is None:\n                    next(self._gen)\n                else:\n                    self._gen.throw(*exc_info)\n            except StopIteration:\n                return True\n            raise\n\n    def contextmanager(func):\n        def run(*a, **k):\n            return GeneratorCM(func(*a, **k))\n        return run\n\nProfile Code Block\n------------------\n\nA practical example of using generator-based context managers to measure\nexecution time of code blocks. The ``yield`` statement marks the boundary\nbetween setup (recording start time) and teardown (calculating elapsed time).\n\n.. code-block:: python\n\n    >>> import time\n    >>> from contextlib import contextmanager\n    >>> @contextmanager\n    ... def profile(msg):\n    ...     try:\n    ...         s = time.time()\n    ...         yield\n    ...     finally:\n    ...         print(f'{msg} cost: {time.time() - s:.2f}s')\n    ...\n    >>> with profile('block'):\n    ...     time.sleep(0.1)\n    block cost: 0.10s\n\n``yield from`` and ``__iter__``\n-------------------------------\n\nWhen using ``yield from`` with a class instance, Python calls the object's\n``__iter__`` method to get an iterator. This allows custom classes to work\nseamlessly with ``yield from`` delegation, enabling elegant composition of\niterables.\n\n.. code-block:: python\n\n    >>> class FakeGen:\n    ...     def __iter__(self):\n    ...         n = 0\n    ...         while n < 3:\n    ...             yield n\n    ...             n += 1\n    ...     def __reversed__(self):\n    ...         n = 2\n    ...         while n >= 0:\n    ...             yield n\n    ...             n -= 1\n    ...\n    >>> def spam():\n    ...     yield from FakeGen()\n    ...\n    >>> list(spam())\n    [0, 1, 2]\n    >>> list(reversed(FakeGen()))\n    [2, 1, 0]\n\nClosure Using Generator\n-----------------------\n\nGenerators provide an elegant way to implement closures that maintain state\nbetween calls. Each call to ``next()`` resumes execution and can access and\nmodify the enclosed variables. This is often cleaner than using ``nonlocal``\nor class-based approaches.\n\n.. code-block:: python\n\n    # generator version\n    >>> def closure_gen():\n    ...     x = 5566\n    ...     while True:\n    ...         x += 1\n    ...         yield x\n    ...\n    >>> g = closure_gen()\n    >>> next(g)\n    5567\n    >>> next(g)\n    5568\n\nSimple Scheduler\n----------------\n\nThis example demonstrates how generators can be used to implement cooperative\nmultitasking. Each generator represents a task that yields control back to the\nscheduler. The scheduler uses a deque to round-robin between tasks, advancing\neach one step at a time.\n\n.. code-block:: python\n\n    >>> from collections import deque\n    >>> def fib(n):\n    ...     if n <= 2: return 1\n    ...     return fib(n-1) + fib(n-2)\n    ...\n    >>> def g_fib(n):\n    ...     for x in range(1, n + 1):\n    ...         yield fib(x)\n    ...\n    >>> q = deque([g_fib(3), g_fib(5)])\n    >>> def run():\n    ...     while q:\n    ...         try:\n    ...             t = q.popleft()\n    ...             print(next(t))\n    ...             q.append(t)\n    ...         except StopIteration:\n    ...             print(\"Task done\")\n    ...\n    >>> run()\n    1\n    1\n    1\n    1\n    2\n    2\n    Task done\n    3\n    5\n    Task done\n\nSimple Round-Robin with Blocking\n--------------------------------\n\nA more advanced scheduler that handles I/O blocking using ``select()``. Tasks\nyield tuples indicating what operation they're waiting for ('recv' or 'send')\nand which socket. The scheduler moves blocked tasks to wait queues and only\nruns them when their I/O is ready. This is the foundation of async I/O frameworks.\n\n.. code-block:: python\n\n    from collections import deque\n    from select import select\n    import socket\n\n    tasks = deque()\n    w_read = {}\n    w_send = {}\n\n    def run():\n        while any([tasks, w_read, w_send]):\n            while not tasks:\n                can_r, can_s, _ = select(w_read, w_send, [])\n                for _r in can_r:\n                    tasks.append(w_read.pop(_r))\n                for _w in can_s:\n                    tasks.append(w_send.pop(_w))\n            try:\n                task = tasks.popleft()\n                why, what = next(task)\n                if why == 'recv':\n                    w_read[what] = task\n                elif why == 'send':\n                    w_send[what] = task\n            except StopIteration:\n                pass\n\n    def server():\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        sock.bind(('localhost', 5566))\n        sock.listen(5)\n        while True:\n            yield 'recv', sock\n            conn, addr = sock.accept()\n            tasks.append(client_handler(conn))\n\n    def client_handler(conn):\n        while True:\n            yield 'recv', conn\n            msg = conn.recv(1024)\n            if not msg: break\n            yield 'send', conn\n            conn.send(msg)\n        conn.close()\n\n    tasks.append(server())\n    run()\n\nAsync Generator (Python 3.6+)\n-----------------------------\n\nAsync generators combine ``async def`` with ``yield`` to create asynchronous\niterators. They can use ``await`` to pause for async operations between yields.\nUse ``async for`` to iterate over async generators. This is essential for\nstreaming data from async sources like network connections or databases.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def slow_gen(n, t):\n    ...     for x in range(n):\n    ...         await asyncio.sleep(t)\n    ...         yield x\n    ...\n    >>> async def task(n):\n    ...     async for x in slow_gen(n, 0.1):\n    ...         print(x)\n    ...\n    >>> asyncio.run(task(3))\n    0\n    1\n    2\n\nAsync Generator with try..finally\n---------------------------------\n\nAsync generators support ``try..finally`` blocks for cleanup, just like regular\ngenerators. The ``finally`` block executes when the generator is closed or\ngarbage collected, ensuring resources are properly released even if an exception\noccurs during iteration.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def agen(t):\n    ...     try:\n    ...         await asyncio.sleep(t)\n    ...         yield 1 / 0\n    ...     finally:\n    ...         print(\"finally\")\n    ...\n    >>> async def main():\n    ...     try:\n    ...         g = agen(0.1)\n    ...         await g.__anext__()\n    ...     except Exception as e:\n    ...         print(repr(e))\n    ...\n    >>> asyncio.run(main())\n    finally\n    ZeroDivisionError('division by zero')\n\nSend and Throw to Async Generator\n---------------------------------\n\nAsync generators support ``asend()`` to send values and ``athrow()`` to throw\nexceptions, similar to regular generators. These methods are coroutines that\nmust be awaited. This enables two-way communication with async generators for\nbuilding complex async data pipelines.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def agen(n):\n    ...     try:\n    ...         for x in range(n):\n    ...             await asyncio.sleep(0.1)\n    ...             val = yield x\n    ...             print(f'got: {val}')\n    ...     except RuntimeError as e:\n    ...         yield repr(e)\n    ...\n    >>> async def main():\n    ...     g = agen(5)\n    ...     ret = await g.asend(None) + await g.asend('foo')\n    ...     print(ret)\n    ...     ret = await g.athrow(RuntimeError('error'))\n    ...     print(ret)\n    ...\n    >>> asyncio.run(main())\n    got: foo\n    1\n    RuntimeError('error')\n\nAsync Comprehension (Python 3.6+)\n---------------------------------\n\nPEP 530 introduced async comprehensions, allowing ``async for`` in list, set,\nand dict comprehensions. This provides a concise way to collect values from\nasync generators. You can also use ``if`` clauses to filter values and\nconditional expressions for transformations.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def agen(n):\n    ...     for x in range(n):\n    ...         await asyncio.sleep(0.01)\n    ...         yield x\n    ...\n    >>> async def main():\n    ...     ret = [x async for x in agen(5)]\n    ...     print(ret)\n    ...     ret = [x async for x in agen(5) if x < 3]\n    ...     print(ret)\n    ...     ret = {f'{x}': x async for x in agen(3)}\n    ...     print(ret)\n    ...\n    >>> asyncio.run(main())\n    [0, 1, 2, 3, 4]\n    [0, 1, 2]\n    {'0': 0, '1': 1, '2': 2}\n\nSimple Async Round-Robin\n------------------------\n\nThis example shows cooperative multitasking with async generators. Multiple\nasync generators are scheduled in a deque, and the scheduler awaits each one\nin turn using ``__anext__()``. This pattern is useful for interleaving multiple\nasync data streams fairly.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> from collections import deque\n    >>> async def agen(n):\n    ...     for x in range(n):\n    ...         await asyncio.sleep(0.1)\n    ...         yield x\n    ...\n    >>> async def main():\n    ...     q = deque([agen(3), agen(5)])\n    ...     while q:\n    ...         try:\n    ...             g = q.popleft()\n    ...             print(await g.__anext__())\n    ...             q.append(g)\n    ...         except StopAsyncIteration:\n    ...             pass\n    ...\n    >>> asyncio.run(main())\n    0\n    0\n    1\n    1\n    2\n    2\n    3\n    4\n\nAsync Generator vs Async Iterator Performance\n----------------------------------------------\n\nAsync generators have better performance than manually implemented async iterators\nbecause they are optimized at the C level in CPython. This benchmark shows that\nasync generators can be significantly faster for iteration-heavy workloads.\n\n.. code-block:: python\n\n    >>> import time\n    >>> import asyncio\n    >>> class AsyncIter:\n    ...     def __init__(self, n):\n    ...         self._n = n\n    ...     def __aiter__(self):\n    ...         return self\n    ...     async def __anext__(self):\n    ...         ret = self._n\n    ...         if self._n == 0:\n    ...             raise StopAsyncIteration\n    ...         self._n -= 1\n    ...         return ret\n    ...\n    >>> async def agen(n):\n    ...     for i in range(n):\n    ...         yield i\n    ...\n    >>> async def task_agen(n):\n    ...     s = time.time()\n    ...     async for _ in agen(n): pass\n    ...     cost = time.time() - s\n    ...     print(f\"agen cost time: {cost}\")\n    ...\n    >>> async def task_aiter(n):\n    ...     s = time.time()\n    ...     async for _ in AsyncIter(n): pass\n    ...     cost = time.time() - s\n    ...     print(f\"aiter cost time: {cost}\")\n    ...\n    >>> n = 10 ** 7\n    >>> asyncio.run(task_agen(n))\n    agen cost time: 1.2698817253112793\n    >>> asyncio.run(task_aiter(n))\n    aiter cost time: 4.168368101119995\n\n``yield from == await`` Expression\n----------------------------------\n\nBefore Python 3.5 introduced ``async``/``await`` syntax, coroutines were\nimplemented using generators with ``@asyncio.coroutine`` decorator and\n``yield from``. The ``await`` keyword is essentially equivalent to ``yield from``\nfor coroutines. This example shows both the old and new syntax for an echo server.\n\n.. code-block:: python\n\n    import asyncio\n    import socket\n\n    loop = asyncio.get_event_loop()\n    host = 'localhost'\n    port = 5566\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.setblocking(False)\n    sock.bind((host, port))\n    sock.listen(10)\n\n    # old syntax (Python 3.4)\n    @asyncio.coroutine\n    def echo_server():\n        while True:\n            conn, addr = yield from loop.sock_accept(sock)\n            loop.create_task(handler(conn))\n\n    @asyncio.coroutine\n    def handler(conn):\n        while True:\n            msg = yield from loop.sock_recv(conn, 1024)\n            if not msg:\n                break\n            yield from loop.sock_sendall(conn, msg)\n        conn.close()\n\n    # new syntax (Python 3.5+)\n    async def echo_server():\n        while True:\n            conn, addr = await loop.sock_accept(sock)\n            loop.create_task(handler(conn))\n\n    async def handler(conn):\n        while True:\n            msg = await loop.sock_recv(conn, 1024)\n            if not msg:\n                break\n            await loop.sock_sendall(conn, msg)\n        conn.close()\n\n    loop.create_task(echo_server())\n    loop.run_forever()\n\nSimple Compiler Using Generators\n--------------------------------\n\nThis advanced example from David Beazley demonstrates using generators to\nimplement a simple expression compiler. It includes a tokenizer, parser, and\nevaluator using the visitor pattern with generators for stack-based evaluation.\n\n.. code-block:: python\n\n    import re\n    import types\n    from collections import namedtuple\n\n    tokens = [\n        r'(?P<NUMBER>\\d+)',\n        r'(?P<PLUS>\\+)',\n        r'(?P<MINUS>-)',\n        r'(?P<TIMES>\\*)',\n        r'(?P<DIVIDE>/)',\n        r'(?P<WS>\\s+)']\n\n    Token = namedtuple('Token', ['type', 'value'])\n    lex = re.compile('|'.join(tokens))\n\n    def tokenize(text):\n        scan = lex.scanner(text)\n        gen = (Token(m.lastgroup, m.group())\n                for m in iter(scan.match, None) if m.lastgroup != 'WS')\n        return gen\n\n    class Node:\n        _fields = []\n        def __init__(self, *args):\n            for attr, value in zip(self._fields, args):\n                setattr(self, attr, value)\n\n    class Number(Node):\n        _fields = ['value']\n\n    class BinOp(Node):\n        _fields = ['op', 'left', 'right']\n\n    def parse(toks):\n        lookahead, current = next(toks, None), None\n\n        def accept(*toktypes):\n            nonlocal lookahead, current\n            if lookahead and lookahead.type in toktypes:\n                current, lookahead = lookahead, next(toks, None)\n                return True\n\n        def expr():\n            left = term()\n            while accept('PLUS', 'MINUS'):\n                left = BinOp(current.value, left)\n                left.right = term()\n            return left\n\n        def term():\n            left = factor()\n            while accept('TIMES', 'DIVIDE'):\n                left = BinOp(current.value, left)\n                left.right = factor()\n            return left\n\n        def factor():\n            if accept('NUMBER'):\n                return Number(int(current.value))\n            else:\n                raise SyntaxError()\n        return expr()\n\n    class NodeVisitor:\n        def visit(self, node):\n            stack = [self.genvisit(node)]\n            ret = None\n            while stack:\n                try:\n                    node = stack[-1].send(ret)\n                    stack.append(self.genvisit(node))\n                    ret = None\n                except StopIteration as e:\n                    stack.pop()\n                    ret = e.value\n            return ret\n\n        def genvisit(self, node):\n            ret = getattr(self, 'visit_' + type(node).__name__)(node)\n            if isinstance(ret, types.GeneratorType):\n                ret = yield from ret\n            return ret\n\n    class Evaluator(NodeVisitor):\n        def visit_Number(self, node):\n            return node.value\n\n        def visit_BinOp(self, node):\n            leftval = yield node.left\n            rightval = yield node.right\n            if node.op == '+':\n                return leftval + rightval\n            elif node.op == '-':\n                return leftval - rightval\n            elif node.op == '*':\n                return leftval * rightval\n            elif node.op == '/':\n                return leftval / rightval\n\n    def evaluate(exp):\n        toks = tokenize(exp)\n        tree = parse(toks)\n        return Evaluator().visit(tree)\n\n    print(evaluate('2 * 3 + 5 / 2'))  # 8.5\n    print(evaluate('+'.join([str(x) for x in range(10000)])))  # 49995000\n"
  },
  {
    "path": "docs/notes/basic/python-heap.rst",
    "content": ".. meta::\n    :description lang=en: Python heap and priority queue cheat sheet covering heapq module operations, heap sort algorithm, priority queue implementation with custom comparators, and practical examples\n    :keywords: Python, Python Cheat Sheet, heap, heapq, priority queue, heap sort, min heap, max heap, Python heapq, nlargest, nsmallest\n\n====\nHeap\n====\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe heapq module provides an implementation of the heap queue algorithm, also\nknown as the priority queue algorithm. Heaps are binary trees where every parent\nnode has a value less than or equal to any of its children (min-heap). This\ncheat sheet covers heap operations including heap sort, priority queues, merging\nsorted iterables, and finding the n largest or smallest elements efficiently.\n\nThe source code is available on `GitHub <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/heap.py>`_.\n\nReferences\n----------\n\n- `heapq — Heap queue algorithm <https://docs.python.org/3/library/heapq.html>`_\n- `queue.PriorityQueue <https://docs.python.org/3/library/queue.html#queue.PriorityQueue>`_\n\nBasic Heap Operations\n---------------------\n\nThe ``heapq`` module provides functions to create and manipulate heaps. Use\n``heapify`` to convert a list into a heap in-place in O(n) time. Use ``heappush``\nand ``heappop`` to add and remove elements while maintaining the heap property.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> # Convert list to heap in-place\n    >>> h = [5, 1, 3, 2, 6]\n    >>> heapq.heapify(h)\n    >>> h[0]  # smallest element at root\n    1\n    >>> # Push and pop\n    >>> heapq.heappush(h, 0)\n    >>> heapq.heappop(h)\n    0\n    >>> # Push and pop in one operation\n    >>> heapq.heappushpop(h, 4)  # push 4, then pop smallest\n    1\n    >>> # Pop and push in one operation\n    >>> heapq.heapreplace(h, 0)  # pop smallest, then push 0\n    2\n\nImplement Heap Sort with ``heapq``\n----------------------------------\n\nHeap sort works by pushing all elements onto a heap and then popping them off\none by one. Since the heap maintains the min-heap property, elements come out\nin sorted order. The time complexity is O(n log n).\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> a = [5, 1, 3, 2, 6]\n    >>> h = []\n    >>> for x in a:\n    ...     heapq.heappush(h, x)\n    ...\n    >>> x = [heapq.heappop(h) for _ in range(len(a))]\n    >>> x\n    [1, 2, 3, 5, 6]\n\nA more efficient approach uses ``heapify`` to convert the list in-place:\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> def heap_sort(items):\n    ...     h = items.copy()\n    ...     heapq.heapify(h)\n    ...     return [heapq.heappop(h) for _ in range(len(h))]\n    ...\n    >>> heap_sort([5, 1, 3, 2, 6])\n    [1, 2, 3, 5, 6]\n\nImplement Max Heap\n------------------\n\nPython's ``heapq`` only provides a min-heap. To implement a max-heap, negate\nthe values when pushing and negate again when popping.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> # Max heap using negation\n    >>> h = []\n    >>> for x in [5, 1, 3, 2, 6]:\n    ...     heapq.heappush(h, -x)\n    ...\n    >>> [-heapq.heappop(h) for _ in range(len(h))]\n    [6, 5, 3, 2, 1]\n\nFor custom objects, implement ``__lt__`` with reversed comparison:\n\n.. code-block:: python\n\n    import heapq\n\n    class MaxHeapItem:\n        def __init__(self, val):\n            self.val = val\n\n        def __lt__(self, other):\n            return self.val > other.val  # reversed for max heap\n\n    h = []\n    for x in [5, 1, 3]:\n        heapq.heappush(h, MaxHeapItem(x))\n\n    print(heapq.heappop(h).val)  # 5 (largest)\n\nImplement Priority Queue with ``heapq``\n---------------------------------------\n\nA priority queue processes elements based on their priority rather than insertion\norder. Use tuples ``(priority, value)`` where lower numbers indicate higher priority.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> pq = []\n    >>> heapq.heappush(pq, (2, \"medium\"))\n    >>> heapq.heappush(pq, (1, \"high\"))\n    >>> heapq.heappush(pq, (3, \"low\"))\n    >>> [heapq.heappop(pq) for _ in range(len(pq))]\n    [(1, 'high'), (2, 'medium'), (3, 'low')]\n\nFor custom objects, implement the ``__lt__`` method to define comparison behavior:\n\n.. code-block:: python\n\n    import heapq\n\n    class Task:\n        def __init__(self, priority, name):\n            self.priority = priority\n            self.name = name\n\n        def __lt__(self, other):\n            return self.priority < other.priority\n\n        def __repr__(self):\n            return f\"Task({self.priority}, {self.name!r})\"\n\n    h = []\n    heapq.heappush(h, Task(3, \"low\"))\n    heapq.heappush(h, Task(1, \"high\"))\n    heapq.heappush(h, Task(2, \"medium\"))\n\n    while h:\n        print(heapq.heappop(h))\n    # Task(1, 'high')\n    # Task(2, 'medium')\n    # Task(3, 'low')\n\nFind K Largest or Smallest Elements\n-----------------------------------\n\nThe ``nlargest`` and ``nsmallest`` functions efficiently find the k largest or\nsmallest elements. They are more efficient than sorting when k is small relative\nto the list size.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> nums = [5, 1, 8, 3, 9, 2, 7]\n    >>> heapq.nsmallest(3, nums)\n    [1, 2, 3]\n    >>> heapq.nlargest(3, nums)\n    [9, 8, 7]\n\nUse the ``key`` parameter to extract comparison keys from complex objects:\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> data = [\n    ...     {'name': 'Alice', 'score': 85},\n    ...     {'name': 'Bob', 'score': 92},\n    ...     {'name': 'Charlie', 'score': 78},\n    ... ]\n    >>> heapq.nlargest(2, data, key=lambda x: x['score'])\n    [{'name': 'Bob', 'score': 92}, {'name': 'Alice', 'score': 85}]\n\nMerge Sorted Iterables\n----------------------\n\nThe ``merge`` function merges multiple sorted inputs into a single sorted output.\nIt returns an iterator, making it memory-efficient for large datasets.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> a = [1, 3, 5, 7]\n    >>> b = [2, 4, 6, 8]\n    >>> c = [0, 9, 10]\n    >>> list(heapq.merge(a, b, c))\n    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\nUse ``key`` and ``reverse`` parameters for custom merging:\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> # Merge in descending order\n    >>> a = [5, 3, 1]\n    >>> b = [6, 4, 2]\n    >>> list(heapq.merge(a, b, reverse=True))\n    [6, 5, 4, 3, 2, 1]\n\nMaintain a Fixed-Size Heap\n--------------------------\n\nTo maintain a heap of fixed size k (e.g., tracking top k elements), use\n``heappushpop`` or check the size after each push.\n\n.. code-block:: python\n\n    >>> import heapq\n    >>> def top_k(items, k):\n    ...     \"\"\"Keep track of k largest elements using min-heap.\"\"\"\n    ...     h = []\n    ...     for x in items:\n    ...         if len(h) < k:\n    ...             heapq.heappush(h, x)\n    ...         elif x > h[0]:\n    ...             heapq.heapreplace(h, x)\n    ...     return sorted(h, reverse=True)\n    ...\n    >>> top_k([5, 1, 8, 3, 9, 2, 7, 4, 6], 3)\n    [9, 8, 7]\n\nHeap with Index Tracking\n------------------------\n\nWhen you need to update priorities in a heap, use a dictionary to track element\npositions or mark entries as invalid.\n\n.. code-block:: python\n\n    import heapq\n\n    class IndexedHeap:\n        def __init__(self):\n            self.heap = []\n            self.entry_finder = {}\n            self.REMOVED = '<removed>'\n\n        def push(self, item, priority):\n            if item in self.entry_finder:\n                self.remove(item)\n            entry = [priority, item]\n            self.entry_finder[item] = entry\n            heapq.heappush(self.heap, entry)\n\n        def remove(self, item):\n            entry = self.entry_finder.pop(item)\n            entry[-1] = self.REMOVED\n\n        def pop(self):\n            while self.heap:\n                priority, item = heapq.heappop(self.heap)\n                if item is not self.REMOVED:\n                    del self.entry_finder[item]\n                    return item\n            raise KeyError('pop from empty heap')\n\n    # Usage\n    h = IndexedHeap()\n    h.push('task1', 3)\n    h.push('task2', 1)\n    h.push('task1', 0)  # update priority\n    print(h.pop())  # task1 (now has priority 0)\n"
  },
  {
    "path": "docs/notes/basic/python-list.rst",
    "content": ".. meta::\n    :description lang=en: Python list cheat sheet covering list operations, comprehensions, slicing, sorting, filtering, and common list manipulation patterns with code examples\n    :keywords: Python, Python3, Python list, Python list cheat sheet, list comprehension, slicing, sorting, filtering, append, extend, iteration\n\n====\nList\n====\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe list is a common data structure which we use to store objects. Most of the\ntime, programmers concern about getting, setting, searching, filtering, and\nsorting. Furthermore, sometimes, we waltz ourself into common pitfalls of\nthe memory management. Thus, the main goal of this cheat sheet is to collect\nsome common operations and pitfalls.\n\nPython List Basics and Common Operations\n----------------------------------------\n\nThere are so many ways that we can manipulate lists in Python. Before we start\nto learn those versatile manipulations, the following snippet shows the most\ncommon operations of lists.\n\n.. code-block:: python\n\n    >>> a = [1, 2, 3, 4, 5]\n    >>> # contains\n    >>> 2 in a\n    True\n    >>> # positive index\n    >>> a[0]\n    1\n    >>> # negative index\n    >>> a[-1]\n    5\n    >>> # slicing list[start:end:step]\n    >>> a[1:]\n    [2, 3, 4, 5]\n    >>> a[1:-1]\n    [2, 3, 4]\n    >>> a[1:-1:2]\n    [2, 4]\n    >>> # reverse\n    >>> a[::-1]\n    [5, 4, 3, 2, 1]\n    >>> a[:0:-1]\n    [5, 4, 3, 2]\n    >>> # set an item\n    >>> a[0] = 0\n    >>> a\n    [0, 2, 3, 4, 5]\n    >>> # append items to list\n    >>> a.append(6)\n    >>> a\n    [0, 2, 3, 4, 5, 6]\n    >>> a.extend([7, 8, 9])\n    >>> a\n    [0, 2, 3, 4, 5, 6, 7, 8, 9]\n    >>> # delete an item\n    >>> del a[-1]\n    >>> a\n    [0, 2, 3, 4, 5, 6, 7, 8]\n    >>> # list comprehension\n    >>> b = [x for x in range(3)]\n    >>> b\n    [0, 1, 2]\n    >>> # add two lists\n    >>> a + b\n    [0, 2, 3, 4, 5, 6, 7, 8, 0, 1, 2]\n\nInitialize Lists with Multiplication Operator\n---------------------------------------------\n\nGenerally speaking, we can create a list through ``*`` operator if the item in\nthe list expression is an immutable object.\n\n.. code-block:: python\n\n    >>> a = [None] * 3\n    >>> a\n    [None, None, None]\n    >>> a[0] = \"foo\"\n    >>> a\n    ['foo', None, None]\n\nHowever, if the item in the list expression is a mutable object, the ``*``\noperator will copy the reference of the item N times. In order to avoid this\npitfall, we should use a list comprehension to initialize a list.\n\n.. code-block:: python\n\n    >>> a = [[]] * 3\n    >>> b = [[] for _ in range(3)]\n    >>> a[0].append(\"Hello\")\n    >>> a\n    [['Hello'], ['Hello'], ['Hello']]\n    >>> b[0].append(\"Python\")\n    >>> b\n    [['Python'], [], []]\n\nCopy Lists: Shallow vs Deep Copy\n--------------------------------\n\nAssigning a list to a variable is a common pitfall. This assignment does not\ncopy the list to the variable. The variable only refers to the list and increase\nthe reference count of the list.\n\n.. code-block:: python\n\n    import sys\n    >>> a = [1, 2, 3]\n    >>> sys.getrefcount(a)\n    2\n    >>> b = a\n    >>> sys.getrefcount(a)\n    3\n    >>> b[2] = 123456  # a[2] = 123456\n    >>> b\n    [1, 2, 123456]\n    >>> a\n    [1, 2, 123456]\n\nThere are two types of copy. The first one is called *shallow copy* (non-recursive copy)\nand the second one is called *deep copy* (recursive copy). Most of the time, it\nis sufficient for us to copy a list by shallow copy. However, if a list is nested,\nwe have to use a deep copy.\n\n.. code-block:: python\n\n    >>> # shallow copy\n    >>> a = [1, 2]\n    >>> b = list(a)\n    >>> b[0] = 123\n    >>> a\n    [1, 2]\n    >>> b\n    [123, 2]\n    >>> a = [[1], [2]]\n    >>> b = list(a)\n    >>> b[0][0] = 123\n    >>> a\n    [[123], [2]]\n    >>> b\n    [[123], [2]]\n    >>> # deep copy\n    >>> import copy\n    >>> a = [[1], [2]]\n    >>> b = copy.deepcopy(a)\n    >>> b[0][0] = 123\n    >>> a\n    [[1], [2]]\n    >>> b\n    [[123], [2]]\n\nSlice Lists with slice Objects\n------------------------------\n\nSometimes, our data may concatenate as a large segment such as packets. In\nthis case, we will represent the range of data by using ``slice`` objects\nas explaining variables instead of using *slicing expressions*.\n\n.. code-block:: python\n\n    >>> icmp = (\n    ...     b\"080062988e2100005bff49c20005767c\"\n    ...     b\"08090a0b0c0d0e0f1011121314151617\"\n    ...     b\"18191a1b1c1d1e1f2021222324252627\"\n    ...     b\"28292a2b2c2d2e2f3031323334353637\"\n    ... )\n    >>> head = slice(0, 32)\n    >>> data = slice(32, len(icmp))\n    >>> icmp[head]\n    b'080062988e2100005bff49c20005767c'\n\nCreate Lists with List Comprehensions\n-------------------------------------\n\n`List comprehensions <https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions>`_\nwhich was proposed in PEP `202 <https://www.python.org/dev/peps/pep-0202/>`_\nprovides a graceful way to create a new list based on another list, sequence,\nor some object which is iterable. In addition, we can use this expression to\nsubstitute ``map`` and ``filter`` sometimes.\n\n.. code-block:: python\n\n    >>> [x for x in range(10)]\n    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n    >>> [(lambda x: x**2)(i) for i in range(10)]\n    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n    >>> [x for x in range(10) if x > 5]\n    [6, 7, 8, 9]\n    >>> [x if x > 5 else 0 for x in range(10)]\n    [0, 0, 0, 0, 0, 0, 6, 7, 8, 9]\n    >>> [x + 1 if x < 5 else x + 2 if x > 5 else x + 5 for x in range(10)]\n    [1, 2, 3, 4, 5, 10, 8, 9, 10, 11]\n    >>> [(x, y) for x in range(3) for y in range(2)]\n    [(0, 0), (0, 1), (1, 0), (1, 1), (2, 0), (2, 1)]\n\nUnpack Lists into Variables\n---------------------------\n\nSometimes, we want to unpack our list to variables in order to make our code\nbecome more readable. In this case, we assign N elements to N variables as\nfollowing example.\n\n.. code-block:: python\n\n    >>> arr = [1, 2, 3]\n    >>> a, b, c = arr\n    >>> a, b, c\n    (1, 2, 3)\n\nBased on PEP `3132 <https://www.python.org/dev/peps/pep-3132>`_, we can use a\nsingle asterisk to unpack N elements to the number of variables which is less\nthan N in Python 3.\n\n.. code-block:: python\n\n    >>> arr = [1, 2, 3, 4, 5]\n    >>> a, b, *c, d = arr\n    >>> a, b, d\n    (1, 2, 5)\n    >>> c\n    [3, 4]\n\nIterate with Index Using enumerate()\n------------------------------------\n\n``enumerate`` is a built-in function. It helps us to acquire indexes\n(or a count) and elements at the same time without using ``range(len(list))``.\nFurther information can be found on\n`Looping Techniques <https://docs.python.org/3/tutorial/datastructures.html#looping-techniques>`_.\n\n.. code-block:: python\n\n    >>> for i, v in enumerate(range(3)):\n    ...     print(i, v)\n    ...\n    0 0\n    1 1\n    2 2\n    >>> for i, v in enumerate(range(3), 1): # start = 1\n    ...     print(i, v)\n    ...\n    1 0\n    2 1\n    3 2\n\nCombine Lists with zip()\n------------------------\n\n`zip <https://docs.python.org/3/library/functions.html#zip>`_ enables us to\niterate over items contained in multiple lists at a time. Iteration stops\nwhenever one of the lists is exhausted. As a result, the length of the\niteration is the same as the shortest list. If this behavior is not desired,\nwe can use ``itertools.zip_longest`` in **Python 3** or ``itertools.izip_longest``\nin **Python 2**.\n\n.. code-block:: python\n\n    >>> a = [1, 2, 3]\n    >>> b = [4, 5, 6]\n    >>> list(zip(a, b))\n    [(1, 4), (2, 5), (3, 6)]\n    >>> c = [1]\n    >>> list(zip(a, b, c))\n    [(1, 4, 1)]\n    >>> from itertools import zip_longest\n    >>> list(zip_longest(a, b, c))\n    [(1, 4, 1), (2, 5, None), (3, 6, None)]\n\n\nFilter List Items\n-----------------\n\n`filter <https://docs.python.org/3/library/functions.html#filter>`_ is a\nbuilt-in function to assist us to remove unnecessary items. In **Python 2**,\n``filter`` returns a list. However, in **Python 3**, ``filter`` returns an\n*iterable object*. Note that *list comprehension* or *generator\nexpression* provides a more concise way to remove items.\n\n.. code-block:: python\n\n    >>> [x for x in range(5) if x > 1]\n    [2, 3, 4]\n    >>> l = ['1', '2', 3, 'Hello', 4]\n    >>> f = lambda x: isinstance(x, int)\n    >>> filter(f, l)\n    <filter object at 0x10bee2198>\n    >>> list(filter(f, l))\n    [3, 4]\n    >>> list((i for i in l if f(i)))\n    [3, 4]\n\nImplement Stack with List\n-------------------------\n\nThere is no need for an additional data structure, stack, in Python because the\n``list`` provides ``append`` and ``pop`` methods which enable us use a list as\na stack.\n\n.. code-block:: python\n\n    >>> stack = []\n    >>> stack.append(1)\n    >>> stack.append(2)\n    >>> stack.append(3)\n    >>> stack\n    [1, 2, 3]\n    >>> stack.pop()\n    3\n    >>> stack.pop()\n    2\n    >>> stack\n    [1]\n\nCheck Membership with in Operator\n---------------------------------\n\nWe can implement the ``__contains__`` method to make a class do ``in``\noperations. It is a common way for a programmer to emulate\na membership test operations for custom classes.\n\n.. code-block:: python\n\n    class Stack:\n\n        def __init__(self):\n            self.__list = []\n\n        def push(self, val):\n            self.__list.append(val)\n\n        def pop(self):\n            return self.__list.pop()\n\n        def __contains__(self, item):\n            return True if item in self.__list else False\n\n    stack = Stack()\n    stack.push(1)\n    print(1 in stack)\n    print(0 in stack)\n\nExample\n\n.. code-block:: bash\n\n    python stack.py\n    True\n    False\n\nAccess Items with __getitem__ and __setitem__\n---------------------------------------------\n\nMaking custom classes perform get and set operations like lists is simple. We\ncan implement a ``__getitem__`` method and a ``__setitem__`` method to enable\na class to retrieve and overwrite data by index. In addition, if we want to use\nthe function, ``len``, to calculate the number of elements, we can implement a\n``__len__`` method.\n\n.. code-block:: python\n\n    class Stack:\n\n        def __init__(self):\n            self.__list = []\n\n        def push(self, val):\n            self.__list.append(val)\n\n        def pop(self):\n            return self.__list.pop()\n\n        def __repr__(self):\n            return \"{}\".format(self.__list)\n\n        def __len__(self):\n            return len(self.__list)\n\n        def __getitem__(self, idx):\n            return self.__list[idx]\n\n        def __setitem__(self, idx, val):\n            self.__list[idx] = val\n\n\n    stack = Stack()\n    stack.push(1)\n    stack.push(2)\n    print(\"stack:\", stack)\n\n    stack[0] = 3\n    print(\"stack:\", stack)\n    print(\"num items:\", len(stack))\n\nExample\n\n.. code-block:: bash\n\n    $ python stack.py\n    stack: [1, 2]\n    stack: [3, 2]\n    num items: 2\n\nDelegate Iteration with __iter__\n--------------------------------\n\nIf a custom container class holds a list and we want iterations to work on the\ncontainer, we can implement a ``__iter__`` method to delegate iterations to\nthe list. Note that the method, ``__iter__``, should return an *iterator object*,\nso we cannot return the list directly; otherwise, Python raises a ``TypeError``.\n\n.. code-block:: python\n\n    class Stack:\n\n        def __init__(self):\n            self.__list = []\n\n        def push(self, val):\n            self.__list.append(val)\n\n        def pop(self):\n            return self.__list.pop()\n\n        def __iter__(self):\n            return iter(self.__list)\n\n    stack = Stack()\n    stack.push(1)\n    stack.push(2)\n    for s in stack:\n        print(s)\n\nExample\n\n.. code-block:: bash\n\n    $ python stack.py\n    1\n    2\n\nSort Lists with sort() and sorted()\n-----------------------------------\n\nPython list provides a built-in ``list.sort`` method which sorts a list\n`in-place <https://en.wikipedia.org/wiki/In-place_algorithm>`_ without using\nextra memory. Moreover, the return value of ``list.sort`` is ``None`` in\norder to avoid confusion with ``sorted`` and the function can only be used for\n``list``.\n\n.. code-block:: python\n\n    >>> l = [5, 4, 3, 2, 1]\n    >>> l.sort()\n    >>> l\n    [1, 2, 3, 4, 5]\n    >>> l.sort(reverse=True)\n    >>> l\n    [5, 4, 3, 2, 1]\n\nThe ``sorted`` function does not modify any iterable object in-place. Instead,\nit returns a new sorted list. Using ``sorted`` is safer than ``list.sort`` if\nsome list's elements are read-only or immutable. Besides, another difference\nbetween ``list.sort`` and ``sorted`` is that ``sorted`` accepts any **iterable\nobject**.\n\n.. code-block:: python\n\n    >>> l = [5, 4, 3, 2, 1]\n    >>> new = sorted(l)\n    >>> new\n    [1, 2, 3, 4, 5]\n    >>> l\n    [5, 4, 3, 2, 1]\n    >>> d = {3: 'andy', 2: 'david', 1: 'amy'}\n    >>> sorted(d)  # sort iterable\n    [1, 2, 3]\n\nTo sort a list with its elements are tuples, using ``operator.itemgetter`` is\nhelpful because it assigns a key function to the ``sorted`` key parameter. Note\nthat the key should be comparable; otherwise, it will raise a ``TypeError``.\n\n.. code-block:: python\n\n    >>> from operator import itemgetter\n    >>> l = [('andy', 10), ('david', 8), ('amy', 3)]\n    >>> l.sort(key=itemgetter(1))\n    >>> l\n    [('amy', 3), ('david', 8), ('andy', 10)]\n\n``operator.itemgetter`` is useful because the function returns a getter\nmethod which can be applied to other objects with a method ``__getitem__``. For\nexample, sorting a list with its elements are dictionary can be achieved by\nusing ``operator.itemgetter`` due to all elements have ``__getitem__``.\n\n.. code-block:: python\n\n    >>> from pprint import pprint\n    >>> from operator import itemgetter\n    >>> l = [\n    ...     {'name': 'andy', 'age': 10},\n    ...     {'name': 'david', 'age': 8},\n    ...     {'name': 'amy', 'age': 3},\n    ... ]\n    >>> l.sort(key=itemgetter('age'))\n    >>> pprint(l)\n    [{'age': 3, 'name': 'amy'},\n     {'age': 8, 'name': 'david'},\n     {'age': 10, 'name': 'andy'}]\n\nIf it is necessary to sort a list with its elements are neither comparable nor\nhaving ``__getitem__`` method, assigning a customized key function is feasible.\n\n.. code-block:: python\n\n    >>> class Node(object):\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __repr__(self):\n    ...         return f\"Node({self.val})\"\n    ...\n    >>> nodes = [Node(3), Node(2), Node(1)]\n    >>> nodes.sort(key=lambda x: x.val)\n    >>> nodes\n    [Node(1), Node(2), Node(3)]\n    >>> nodes.sort(key=lambda x: x.val, reverse=True)\n    >>> nodes\n    [Node(3), Node(2), Node(1)]\n\nThe above snippet can be simplified by using ``operator.attrgetter``. The\nfunction returns an attribute getter based on the attribute's name. Note that\nthe attribute should be comparable; otherwise, ``sorted`` or ``list.sort`` will\nraise ``TypeError``.\n\n.. code-block:: python\n\n    >>> from operator import attrgetter\n    >>> class Node(object):\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __repr__(self):\n    ...         return f\"Node({self.val})\"\n    ...\n    >>> nodes = [Node(3), Node(2), Node(1)]\n    >>> nodes.sort(key=attrgetter('val'))\n    >>> nodes\n    [Node(1), Node(2), Node(3)]\n\nIf an object has ``__lt__`` method, it means that the object is comparable and\n``sorted`` or ``list.sort`` is not necessary to input a key function to its key\nparameter. A list or an iterable sequence can be sorted directly.\n\n.. code-block:: python\n\n    >>> class Node(object):\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __repr__(self):\n    ...         return f\"Node({self.val})\"\n    ...     def __lt__(self, other):\n    ...         return self.val - other.val < 0\n    ...\n    >>> nodes = [Node(3), Node(2), Node(1)]\n    >>> nodes.sort()\n    >>> nodes\n    [Node(1), Node(2), Node(3)]\n\nIf an object does not have ``__lt__`` method, it is likely to patch the method\nafter a declaration of the object's class. In other words, after the patching,\nthe object becomes comparable.\n\n.. code-block:: python\n\n    >>> class Node(object):\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __repr__(self):\n    ...         return f\"Node({self.val})\"\n    ...\n    >>> Node.__lt__ = lambda s, o: s.val < o.val\n    >>> nodes = [Node(3), Node(2), Node(1)]\n    >>> nodes.sort()\n    >>> nodes\n    [Node(1), Node(2), Node(3)]\n\nNote that ``sorted`` or ``list.sort`` in Python3 does not support ``cmp``\nparameter which is an **ONLY** valid argument in Python2. If it is necessary to\nuse an old comparison function, e.g., some legacy code, ``functools.cmp_to_key``\nis useful since it converts a comparison function to a key function.\n\n.. code-block:: python\n\n    >>> from functools import cmp_to_key\n    >>> class Node(object):\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __repr__(self):\n    ...         return f\"Node({self.val})\"\n    ...\n    >>> nodes = [Node(3), Node(2), Node(1)]\n    >>> nodes.sort(key=cmp_to_key(lambda x,y: x.val - y.val))\n    >>> nodes\n    [Node(1), Node(2), Node(3)]\n\nMaintain Sorted List with bisect\n--------------------------------\n\nThe `bisect <https://docs.python.org/3/library/bisect.html>`_ module provides\nfunctions to maintain a list in sorted order without having to sort the list\nafter each insertion. It uses a binary search algorithm, making insertions\nefficient for large lists.\n\n.. code-block:: python\n\n    import bisect\n\n    class Foo(object):\n        def __init__(self, k):\n            self.k = k\n\n        def __eq__(self, rhs):\n            return self.k == rhs.k\n\n        def __ne__(self, rhs):\n            return self.k != rhs.k\n\n        def __lt__(self, rhs):\n            return self.k < rhs.k\n\n        def __gt__(self, rhs):\n            return self.k > rhs.k\n\n        def __le__(self, rhs):\n            return self.k <= rhs.k\n\n        def __ge__(self, rhs):\n            return self.k >= rhs.k\n\n        def __repr__(self):\n            return f\"Foo({self.k})\"\n\n        def __str__(self):\n            return self.__repr__()\n\n    foo = [Foo(1), Foo(3), Foo(2), Foo(0)]\n    bar = []\n    for x in foo:\n        bisect.insort(bar, x)\n\n    print(bar) # [Foo(0), Foo(1), Foo(2), Foo(3)]\n\nCreate Nested Lists Correctly\n-----------------------------\n\nWhen creating nested lists (2D lists or matrices), we should use list\ncomprehension to ensure each inner list is a separate object. The following\nsnippet shows the correct way to create a 2D list.\n\n.. code-block:: python\n\n    # new a list with size = 3\n\n    >>> [0] * 3\n    [0, 0, 0]\n\n    # new a 2d list with size 3x3\n\n    >>> [[0] * 3 for _ in range(3)]\n    [[0, 0, 0], [0, 0, 0], [0, 0, 0]]\n\nNote that we should avoid creating a multi-dimension list via the following\nsnippet because all objects in the list point to the same address.\n\n.. code-block:: python\n\n    >>> a = [[0] * 3] * 3\n    >>> a\n    [[0, 0, 0], [0, 0, 0], [0, 0, 0]]\n    >>> a[1][1] = 2\n    >>> a\n    [[0, 2, 0], [0, 2, 0], [0, 2, 0]]\n\n\nImplement Circular Buffer with deque\n------------------------------------\n\n`collections.deque <https://docs.python.org/3/library/collections.html#collections.deque>`_\nis a double-ended queue that supports adding and removing elements from both ends\nefficiently. By setting ``maxlen``, we can create a circular buffer that automatically\ndiscards old elements when new ones are added.\n\n.. code-block:: python\n\n    >>> from collections import deque\n    >>> d = deque(maxlen=8)\n    >>> for x in range(9):\n    ...     d.append(x)\n    ...\n    >>> d\n    deque([1, 2, 3, 4, 5, 6, 7, 8], maxlen=8)\n\nThe following example shows how to implement a ``tail`` function similar to\nthe Unix command using ``deque``.\n\n.. code-block:: python\n\n    >>> from collections import deque\n    >>> def tail(path, n=10):\n    ...     with open(path) as f:\n    ...         return deque(f, n)\n    ...\n    >>> tail(\"/etc/hosts\")\n\nSplit List into Chunks\n----------------------\n\nSometimes, we need to split a list into smaller chunks of a specific size.\nThe following generator function yields successive chunks from the list.\n\n.. code-block:: python\n\n    >>> def chunk(lst, n):\n    ...     for i in range(0, len(lst), n):\n    ...         yield lst[i:i+n]\n    ...\n    >>> a = [1, 2, 3, 4, 5, 6, 7, 8]\n    >>> list(chunk(a, 3))\n    [[1, 2, 3], [4, 5, 6], [7, 8]]\n\nGroup Consecutive Elements with itertools.groupby\n-------------------------------------------------\n\n`itertools.groupby <https://docs.python.org/3/library/itertools.html#itertools.groupby>`_\ngroups consecutive elements in an iterable that have the same key. It is useful\nfor run-length encoding or grouping sorted data.\n\n.. code-block:: python\n\n    >>> import itertools\n    >>> s = \"AAABBCCCCC\"\n    >>> for k, v in itertools.groupby(s):\n    ...     print(k, list(v))\n    ...\n    A ['A', 'A', 'A']\n    B ['B', 'B']\n    C ['C', 'C', 'C', 'C', 'C']\n\n    # group by key\n\n    >>> x = [('gp1', 'a'), ('gp2', 'b'), ('gp2', 'c')]\n    >>> for k, v in itertools.groupby(x, lambda x: x[0]):\n    ...     print(k, list(v))\n    ...\n    gp1 [('gp1', 'a')]\n    gp2 [('gp2', 'b'), ('gp2', 'c')]\n\nBinary Search in Sorted List\n----------------------------\n\nBinary search is an efficient algorithm for finding an item in a sorted list.\nThe following snippet shows how to implement binary search using ``bisect_left``.\n\n.. code-block:: python\n\n    >>> def binary_search(arr, x, lo=0, hi=None):\n    ...     if not hi: hi = len(arr)\n    ...     pos = bisect_left(arr, x, lo, hi)\n    ...     return pos if pos != hi and arr[pos] == x else -1\n    ...\n    >>> a = [1, 1, 1, 2, 3]\n    >>> binary_search(a, 1)\n    0\n    >>> binary_search(a, 2)\n    3\n\nFind Lower Bound with bisect_left\n---------------------------------\n\n``bisect_left`` returns the leftmost position where an element can be inserted\nto keep the list sorted. This is equivalent to finding the lower bound.\n\n.. code-block:: python\n\n    >>> import bisect\n    >>> a = [1,2,3,3,4,5]\n    >>> bisect.bisect_left(a, 3)\n    2\n    >>> bisect.bisect_left(a, 3.5)\n    4\n\nFind Upper Bound with bisect_right\n----------------------------------\n\n``bisect_right`` (or ``bisect``) returns the rightmost position where an element\ncan be inserted to keep the list sorted. This is equivalent to finding the upper bound.\n\n.. code-block:: python\n\n    >>> import bisect\n    >>> a = [1,2,3,3,4,5]\n    >>> bisect.bisect_right(a, 3)\n    4\n    >>> bisect.bisect_right(a, 3.5)\n    4\n\nSort Tuples Lexicographically\n-----------------------------\n\nPython compares tuples and lists lexicographically by default. This means it\ncompares the first elements, and if they are equal, it compares the second\nelements, and so on.\n\n.. code-block:: python\n\n    # python compare lists lexicographically\n\n    >>> a = [(1,2), (1,1), (1,0), (2,1)]\n    >>> a.sort()\n    >>> a\n    [(1, 0), (1, 1), (1, 2), (2, 1)]\n\nImplement Trie (Prefix Tree)\n----------------------------\n\nA `Trie <https://en.wikipedia.org/wiki/Trie>`_ (prefix tree) is a tree data\nstructure used for efficient retrieval of keys in a dataset of strings. The\nfollowing snippet shows a compact implementation using ``defaultdict``.\n\n.. code-block:: python\n\n    >>> from functools import reduce\n    >>> from collections import defaultdict\n    >>> Trie = lambda: defaultdict(Trie)\n    >>> prefixes = ['abc', 'de', 'g']\n    >>> trie = Trie()\n    >>> end = True\n    >>> for p in prefixes:\n    ...     reduce(dict.__getitem__, p, trie)[end] = p\n    ...\n\n    # search prefix\n\n    >>> def find(trie, word):\n    ...     curr = trie\n    ...     for c in word:\n    ...         if c not in curr:\n    ...             return False\n    ...         curr = curr[c]\n    ...     return True\n    ...\n    >>> find(trie, \"abcdef\")\n    False\n    >>> find(trie, \"abc\")\n    True\n    >>> find(trie, \"ab\")\n    True\n\n    # search word\n\n    >>> def find(trie, p):\n    ...     curr = trie\n    ...     for c in p:\n    ...         if c not in curr or True in curr:\n    ...             break\n    ...         curr = curr[c]\n    ...     return True if True in curr else False\n    ...\n    >>> find(trie, \"abcdef\")\n    True\n    >>> find(trie, \"abc\")\n    True\n    >>> find(trie, \"ab\")\n    False\n"
  },
  {
    "path": "docs/notes/basic/python-object.rst",
    "content": ".. meta::\n    :description lang=en: Python class cheat sheet covering magic methods, property decorators, inheritance, context managers, and OOP design patterns with code examples\n    :keywords: Python, Python3, Python class, Python OOP cheat sheet, magic methods, property decorator, context manager, singleton, abstract class, descriptor, inheritance\n\n=====\nClass\n=====\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nPython is an object-oriented programming language. This cheat sheet covers\nclass definitions, inheritance, magic methods, property decorators, context\nmanagers, and common design patterns. Understanding these concepts is essential\nfor writing clean, maintainable Python code.\n\nList Attributes with dir()\n--------------------------\n\nThe ``dir()`` function returns a list of all attributes and methods of an object.\nThis is useful for introspection and discovering what operations are available.\n\n.. code-block:: python\n\n    >>> dir(list)  # check all attr of list\n    ['__add__', '__class__', ...]\n\nCheck Type with isinstance()\n----------------------------\n\nUse ``isinstance()`` to check if an object is an instance of a class or its\nsubclasses. This is preferred over ``type()`` comparison because it supports\ninheritance.\n\n.. code-block:: python\n\n    >>> ex = 10\n    >>> isinstance(ex, int)\n    True\n    >>> isinstance(ex, (int, float))  # check multiple types\n    True\n\nCheck Inheritance with issubclass()\n-----------------------------------\n\nUse ``issubclass()`` to check if a class is a subclass of another class.\n\n.. code-block:: python\n\n    >>> class Animal: pass\n    >>> class Dog(Animal): pass\n    >>> issubclass(Dog, Animal)\n    True\n    >>> issubclass(Dog, object)\n    True\n\nGet Class Name\n--------------\n\nAccess the class name through the ``__class__.__name__`` attribute.\n\n.. code-block:: python\n\n    >>> class ExampleClass:\n    ...     pass\n    ...\n    >>> ex = ExampleClass()\n    >>> ex.__class__.__name__\n    'ExampleClass'\n\nHas / Get / Set Attributes\n--------------------------\n\nPython provides built-in functions to dynamically access and modify object\nattributes at runtime.\n\n.. code-block:: python\n\n    >>> class Example:\n    ...     def __init__(self):\n    ...         self.name = \"ex\"\n    ...\n    >>> ex = Example()\n    >>> hasattr(ex, \"name\")\n    True\n    >>> getattr(ex, 'name')\n    'ex'\n    >>> setattr(ex, 'name', 'example')\n    >>> ex.name\n    'example'\n    >>> getattr(ex, 'missing', 'default')  # with default\n    'default'\n\nDeclare Class with type()\n-------------------------\n\nClasses can be created dynamically using ``type()``. This is useful for\nmetaprogramming and creating classes at runtime.\n\n.. code-block:: python\n\n    >>> def greet(self):\n    ...     return f\"Hello, I'm {self.name}\"\n    ...\n    >>> Person = type('Person', (object,), {\n    ...     'name': 'Anonymous',\n    ...     'greet': greet\n    ... })\n    >>> p = Person()\n    >>> p.greet()\n    \"Hello, I'm Anonymous\"\n\nThis is equivalent to:\n\n.. code-block:: python\n\n    >>> class Person:\n    ...     name = 'Anonymous'\n    ...     def greet(self):\n    ...         return f\"Hello, I'm {self.name}\"\n\n__new__ vs __init__\n-------------------\n\n``__new__`` creates the instance, ``__init__`` initializes it. ``__init__`` is\nonly called if ``__new__`` returns an instance of the class.\n\n.. code-block:: python\n\n    >>> class Example:\n    ...     def __new__(cls, arg):\n    ...         print(f'__new__ {arg}')\n    ...         return super().__new__(cls)\n    ...     def __init__(self, arg):\n    ...         print(f'__init__ {arg}')\n    ...\n    >>> o = Example(\"Hello\")\n    __new__ Hello\n    __init__ Hello\n\n__str__ and __repr__\n--------------------\n\n``__str__`` returns a human-readable string, ``__repr__`` returns an unambiguous\nrepresentation for debugging. When ``__str__`` is not defined, ``__repr__`` is used.\n\n.. code-block:: python\n\n    >>> class Vector:\n    ...     def __init__(self, x, y):\n    ...         self.x, self.y = x, y\n    ...     def __repr__(self):\n    ...         return f\"Vector({self.x}, {self.y})\"\n    ...     def __str__(self):\n    ...         return f\"({self.x}, {self.y})\"\n    ...\n    >>> v = Vector(1, 2)\n    >>> repr(v)\n    'Vector(1, 2)'\n    >>> str(v)\n    '(1, 2)'\n    >>> print(v)\n    (1, 2)\n\nComparison Magic Methods\n------------------------\n\nImplement comparison operators by defining magic methods. Use\n``functools.total_ordering`` to generate all comparisons from ``__eq__`` and one other.\n\n.. code-block:: python\n\n    >>> from functools import total_ordering\n    >>> @total_ordering\n    ... class Number:\n    ...     def __init__(self, val):\n    ...         self.val = val\n    ...     def __eq__(self, other):\n    ...         return self.val == other.val\n    ...     def __lt__(self, other):\n    ...         return self.val < other.val\n    ...\n    >>> Number(1) < Number(2)\n    True\n    >>> Number(2) >= Number(1)\n    True\n\nArithmetic Magic Methods\n------------------------\n\nImplement arithmetic operators to make objects work with ``+``, ``-``, ``*``, etc.\n\n.. code-block:: python\n\n    >>> class Vector:\n    ...     def __init__(self, x, y):\n    ...         self.x, self.y = x, y\n    ...     def __add__(self, other):\n    ...         return Vector(self.x + other.x, self.y + other.y)\n    ...     def __mul__(self, scalar):\n    ...         return Vector(self.x * scalar, self.y * scalar)\n    ...     def __repr__(self):\n    ...         return f\"Vector({self.x}, {self.y})\"\n    ...\n    >>> Vector(1, 2) + Vector(3, 4)\n    Vector(4, 6)\n    >>> Vector(1, 2) * 3\n    Vector(3, 6)\n\nCallable with __call__\n----------------------\n\nImplement ``__call__`` to make instances callable like functions. This is useful\nfor creating function-like objects that maintain state.\n\n.. code-block:: python\n\n    >>> class Multiplier:\n    ...     def __init__(self, factor):\n    ...         self.factor = factor\n    ...     def __call__(self, x):\n    ...         return x * self.factor\n    ...\n    >>> double = Multiplier(2)\n    >>> double(5)\n    10\n    >>> callable(double)\n    True\n\n@property Decorator\n-------------------\n\nUse ``@property`` to define getters, setters, and deleters for managed attributes.\nThis allows attribute access syntax while running custom code.\n\n.. code-block:: python\n\n    >>> class Circle:\n    ...     def __init__(self, radius):\n    ...         self._radius = radius\n    ...     @property\n    ...     def radius(self):\n    ...         return self._radius\n    ...     @radius.setter\n    ...     def radius(self, value):\n    ...         if value < 0:\n    ...             raise ValueError(\"Radius must be positive\")\n    ...         self._radius = value\n    ...     @property\n    ...     def area(self):\n    ...         return 3.14159 * self._radius ** 2\n    ...\n    >>> c = Circle(5)\n    >>> c.area\n    78.53975\n    >>> c.radius = 10\n    >>> c.radius\n    10\n\nDescriptor Protocol\n-------------------\n\nDescriptors control attribute access at the class level. They implement\n``__get__``, ``__set__``, and/or ``__delete__`` methods.\n\n.. code-block:: python\n\n    >>> class Positive:\n    ...     def __init__(self, name):\n    ...         self.name = name\n    ...     def __get__(self, obj, objtype=None):\n    ...         return obj.__dict__[self.name]\n    ...     def __set__(self, obj, value):\n    ...         if value < 0:\n    ...             raise ValueError(\"Must be positive\")\n    ...         obj.__dict__[self.name] = value\n    ...\n    >>> class Example:\n    ...     x = Positive('x')\n    ...     def __init__(self, x):\n    ...         self.x = x\n    ...\n    >>> ex = Example(10)\n    >>> ex.x\n    10\n\nContext Manager Protocol\n------------------------\n\nContext managers implement ``__enter__`` and ``__exit__`` to manage resources\nwith the ``with`` statement. This ensures proper cleanup even if exceptions occur.\n\n.. code-block:: python\n\n    class ManagedFile:\n        def __init__(self, filename):\n            self.filename = filename\n\n        def __enter__(self):\n            self.file = open(self.filename, 'r')\n            return self.file\n\n        def __exit__(self, exc_type, exc_val, exc_tb):\n            self.file.close()\n            return False  # don't suppress exceptions\n\n    with ManagedFile('example.txt') as f:\n        content = f.read()\n\nUsing contextlib\n----------------\n\nThe ``contextlib`` module provides utilities for creating context managers\nwithout writing a full class.\n\n.. code-block:: python\n\n    from contextlib import contextmanager\n\n    @contextmanager\n    def managed_file(filename):\n        f = open(filename, 'r')\n        try:\n            yield f\n        finally:\n            f.close()\n\n    with managed_file('example.txt') as f:\n        content = f.read()\n\n@staticmethod and @classmethod\n------------------------------\n\n``@staticmethod`` defines a method that doesn't access instance or class.\n``@classmethod`` receives the class as the first argument, useful for\nalternative constructors.\n\n.. code-block:: python\n\n    >>> class Date:\n    ...     def __init__(self, year, month, day):\n    ...         self.year, self.month, self.day = year, month, day\n    ...     @classmethod\n    ...     def from_string(cls, date_string):\n    ...         year, month, day = map(int, date_string.split('-'))\n    ...         return cls(year, month, day)\n    ...     @staticmethod\n    ...     def is_valid(date_string):\n    ...         try:\n    ...             y, m, d = map(int, date_string.split('-'))\n    ...             return 1 <= m <= 12 and 1 <= d <= 31\n    ...         except:\n    ...             return False\n    ...\n    >>> d = Date.from_string('2024-01-15')\n    >>> d.year\n    2024\n    >>> Date.is_valid('2024-13-01')\n    False\n\nAbstract Base Classes with abc\n------------------------------\n\nUse ``abc`` module to define abstract base classes that cannot be instantiated\nand require subclasses to implement certain methods.\n\n.. code-block:: python\n\n    >>> from abc import ABC, abstractmethod\n    >>> class Shape(ABC):\n    ...     @abstractmethod\n    ...     def area(self):\n    ...         pass\n    ...\n    >>> class Rectangle(Shape):\n    ...     def __init__(self, width, height):\n    ...         self.width, self.height = width, height\n    ...     def area(self):\n    ...         return self.width * self.height\n    ...\n    >>> r = Rectangle(3, 4)\n    >>> r.area()\n    12\n    >>> Shape()  # raises TypeError\n\nThe Diamond Problem (MRO)\n-------------------------\n\nPython uses Method Resolution Order (MRO) to resolve the diamond problem in\nmultiple inheritance. Use ``ClassName.mro()`` to see the resolution order.\n\n.. code-block:: python\n\n    >>> class A:\n    ...     def method(self):\n    ...         return \"A\"\n    ...\n    >>> class B(A):\n    ...     def method(self):\n    ...         return \"B\"\n    ...\n    >>> class C(A):\n    ...     def method(self):\n    ...         return \"C\"\n    ...\n    >>> class D(B, C):\n    ...     pass\n    ...\n    >>> D().method()\n    'B'\n    >>> D.mro()\n    [<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>]\n\nSingleton Pattern\n-----------------\n\nSingleton ensures only one instance of a class exists. Implement using\n``__new__`` or a decorator.\n\n.. code-block:: python\n\n    class Singleton:\n        _instance = None\n\n        def __new__(cls):\n            if cls._instance is None:\n                cls._instance = super().__new__(cls)\n            return cls._instance\n\n    a = Singleton()\n    b = Singleton()\n    print(a is b)  # True\n\nUsing __slots__\n---------------\n\n``__slots__`` restricts instance attributes and reduces memory usage by avoiding\n``__dict__`` per instance.\n\n.. code-block:: python\n\n    >>> class Point:\n    ...     __slots__ = ['x', 'y']\n    ...     def __init__(self, x, y):\n    ...         self.x, self.y = x, y\n    ...\n    >>> p = Point(1, 2)\n    >>> p.x\n    1\n    >>> p.z = 3  # raises AttributeError\n\nCommon Magic Methods Reference\n------------------------------\n\n.. code-block:: python\n\n    # Object Creation and Representation\n    __new__(cls, ...)        # create instance\n    __init__(self, ...)      # initialize instance\n    __del__(self)            # destructor\n    __repr__(self)           # repr(obj)\n    __str__(self)            # str(obj)\n\n    # Comparison\n    __eq__(self, other)      # ==\n    __ne__(self, other)      # !=\n    __lt__(self, other)      # <\n    __le__(self, other)      # <=\n    __gt__(self, other)      # >\n    __ge__(self, other)      # >=\n\n    # Arithmetic\n    __add__(self, other)     # +\n    __sub__(self, other)     # -\n    __mul__(self, other)     # *\n    __truediv__(self, other) # /\n    __floordiv__(self, other)# //\n    __mod__(self, other)     # %\n    __pow__(self, other)     # **\n\n    # Container\n    __len__(self)            # len(obj)\n    __getitem__(self, key)   # obj[key]\n    __setitem__(self, k, v)  # obj[key] = value\n    __delitem__(self, key)   # del obj[key]\n    __contains__(self, item) # item in obj\n    __iter__(self)           # iter(obj)\n\n    # Attribute Access\n    __getattr__(self, name)  # obj.name (when not found)\n    __setattr__(self, n, v)  # obj.name = value\n    __delattr__(self, name)  # del obj.name\n\n    # Callable\n    __call__(self, ...)      # obj()\n\n    # Context Manager\n    __enter__(self)          # with obj\n    __exit__(self, ...)      # exit with block\n\n    # Descriptor\n    __get__(self, obj, type) # descriptor access\n    __set__(self, obj, val)  # descriptor assignment\n    __delete__(self, obj)    # descriptor deletion\n"
  },
  {
    "path": "docs/notes/basic/python-rexp.rst",
    "content": ".. meta::\n    :description lang=en: Python regex cheat sheet covering re module, pattern matching, groups, lookahead, lookbehind, substitution, and common regex patterns with code examples\n    :keywords: Python, Python3, Python regex, Python regex cheat sheet, regular expression, re module, pattern matching, findall, search, match, sub, lookahead, lookbehind, named groups\n\n==================\nRegular Expression\n==================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nRegular expressions (regex) are powerful tools for pattern matching and text\nmanipulation. Python's ``re`` module provides comprehensive support for regex\noperations. This cheat sheet covers basic matching, groups, lookaround assertions,\nsubstitution, and common patterns for validating emails, URLs, IP addresses, etc.\n\nBasic Operations\n----------------\n\nThe ``re`` module provides several functions for pattern matching. Use ``search()``\nto find the first match anywhere in the string, ``match()`` to match at the\nbeginning, and ``fullmatch()`` to match the entire string.\n\n.. code-block:: python\n\n    >>> import re\n    >>> # search - find anywhere in string\n    >>> re.search(r'\\d+', 'abc123def')\n    <re.Match object; span=(3, 6), match='123'>\n\n    >>> # match - match at beginning only\n    >>> re.match(r'\\d+', '123abc')\n    <re.Match object; span=(0, 3), match='123'>\n    >>> re.match(r'\\d+', 'abc123') is None\n    True\n\n    >>> # fullmatch - match entire string\n    >>> re.fullmatch(r'\\d+', '123')\n    <re.Match object; span=(0, 3), match='123'>\n    >>> re.fullmatch(r'\\d+', '123abc') is None\n    True\n\n``re.findall()`` - Find All Matches\n-----------------------------------\n\nThe ``findall()`` function returns all non-overlapping matches as a list of\nstrings. If the pattern has groups, it returns a list of tuples.\n\n.. code-block:: python\n\n    >>> # find all words\n    >>> source = \"Hello World Ker HAHA\"\n    >>> re.findall(r'[\\w]+', source)\n    ['Hello', 'World', 'Ker', 'HAHA']\n\n    >>> # find all digits\n    >>> re.findall(r'\\d+', 'a1b22c333')\n    ['1', '22', '333']\n\n    >>> # with groups - returns tuples\n    >>> re.findall(r'(\\w+)=(\\d+)', 'a=1 b=2 c=3')\n    [('a', '1'), ('b', '2'), ('c', '3')]\n\n``re.split()`` - Split by Pattern\n---------------------------------\n\nThe ``split()`` function splits a string by pattern occurrences. Use ``maxsplit``\nto limit the number of splits.\n\n.. code-block:: python\n\n    >>> re.split(r'\\s+', 'a  b   c')\n    ['a', 'b', 'c']\n\n    >>> re.split(r'[,;]', 'a,b;c,d')\n    ['a', 'b', 'c', 'd']\n\n    >>> re.split(r'(\\s+)', 'a b c')  # keep delimiters\n    ['a', ' ', 'b', ' ', 'c']\n\n    >>> re.split(r'\\s+', 'a b c d', maxsplit=2)\n    ['a', 'b', 'c d']\n\nGroup Matching\n--------------\n\nParentheses ``(...)`` create capturing groups. Use ``group()`` to access matched\ngroups. Group 0 is the entire match, group 1 is the first parenthesized group, etc.\n\n.. code-block:: python\n\n    >>> m = re.search(r'(\\d{4})-(\\d{2})-(\\d{2})', '2016-01-01')\n    >>> m.groups()\n    ('2016', '01', '01')\n    >>> m.group()      # entire match\n    '2016-01-01'\n    >>> m.group(1)     # first group\n    '2016'\n    >>> m.group(2, 3)  # multiple groups\n    ('01', '01')\n\n    # Nested groups - numbered left to right by opening parenthesis\n    >>> m = re.search(r'(((\\d{4})-\\d{2})-\\d{2})', '2016-01-01')\n    >>> m.groups()\n    ('2016-01-01', '2016-01', '2016')\n\nNon-Capturing Group ``(?:...)``\n-------------------------------\n\nUse ``(?:...)`` when you need grouping for alternation or quantifiers but don't\nneed to capture the match. This improves performance and keeps group numbering clean.\n\n.. code-block:: python\n\n    >>> url = 'http://stackoverflow.com/'\n    >>> # non-capturing group for protocol\n    >>> m = re.search(r'(?:http|ftp)://([^/\\r\\n]+)(/[^\\r\\n]*)?', url)\n    >>> m.groups()\n    ('stackoverflow.com', '/')\n\n    >>> # capturing group - protocol is captured\n    >>> m = re.search(r'(http|ftp)://([^/\\r\\n]+)(/[^\\r\\n]*)?', url)\n    >>> m.groups()\n    ('http', 'stackoverflow.com', '/')\n\nNamed Groups ``(?P<name>...)``\n------------------------------\n\nNamed groups make patterns more readable and allow access by name instead of\nnumber. Use ``(?P<name>...)`` to define and ``(?P=name)`` for back reference.\n\n.. code-block:: python\n\n    >>> pattern = r'(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})'\n    >>> m = re.search(pattern, '2016-01-01')\n    >>> m.group('year')\n    '2016'\n    >>> m.group('month')\n    '01'\n    >>> m.groupdict()\n    {'year': '2016', 'month': '01', 'day': '01'}\n\n    # named back reference\n    >>> re.search(r'^(?P<char>[a-z])(?P=char)', 'aa')\n    <re.Match object; span=(0, 2), match='aa'>\n    >>> re.search(r'^(?P<char>[a-z])(?P=char)', 'ab') is None\n    True\n\nBack Reference ``\\1``, ``\\2``\n-----------------------------\n\nBack references match the same text as a previous capturing group. Use ``\\1``\nfor the first group, ``\\2`` for the second, etc.\n\n.. code-block:: python\n\n    >>> # match repeated characters\n    >>> re.search(r'([a-z])\\1', 'aa') is not None\n    True\n    >>> re.search(r'([a-z])\\1', 'ab') is not None\n    False\n\n    >>> # match HTML tags with matching close tag\n    >>> pattern = r'<([^>]+)>[\\s\\S]*?</\\1>'\n    >>> re.search(pattern, '<bold>test</bold>') is not None\n    True\n    >>> re.search(pattern, '<bold>test</h1>') is not None\n    False\n\nSubstitute with ``re.sub()``\n----------------------------\n\nThe ``sub()`` function replaces pattern matches with a replacement string.\nUse ``\\1``, ``\\2`` in the replacement to reference captured groups.\n\n.. code-block:: python\n\n    >>> # basic substitution\n    >>> re.sub(r'[a-z]', ' ', '1a2b3c')\n    '1 2 3 '\n\n    >>> # substitute with group reference\n    >>> re.sub(r'(\\d{4})-(\\d{2})-(\\d{2})', r'\\2/\\3/\\1', '2016-01-01')\n    '01/01/2016'\n\n    >>> # using function as replacement\n    >>> re.sub(r'\\d+', lambda m: str(int(m.group()) * 2), 'a1b2c3')\n    'a2b4c6'\n\n    >>> # camelCase to snake_case\n    >>> def to_snake(s):\n    ...     s = re.sub(r'(.)([A-Z][a-z]+)', r'\\1_\\2', s)\n    ...     return re.sub(r'([a-z])([A-Z])', r'\\1_\\2', s).lower()\n    ...\n    >>> to_snake('CamelCase')\n    'camel_case'\n    >>> to_snake('SimpleHTTPServer')\n    'simple_http_server'\n\nLookahead and Lookbehind\n------------------------\n\nLookaround assertions match a position without consuming characters. They are\nuseful for matching patterns based on context.\n\n+---------------+---------------------+---------------------------+\n| Notation      | Name                | Description               |\n+===============+=====================+===========================+\n| ``(?=...)``   | Positive lookahead  | Followed by ...           |\n+---------------+---------------------+---------------------------+\n| ``(?!...)``   | Negative lookahead  | Not followed by ...       |\n+---------------+---------------------+---------------------------+\n| ``(?<=...)``  | Positive lookbehind | Preceded by ...           |\n+---------------+---------------------+---------------------------+\n| ``(?<!...)``  | Negative lookbehind | Not preceded by ...       |\n+---------------+---------------------+---------------------------+\n\n.. code-block:: python\n\n    >>> # positive lookahead - find word before @\n    >>> re.findall(r'\\w+(?=@)', 'user@example.com')\n    ['user']\n\n    >>> # negative lookahead - find digits not followed by px\n    >>> re.findall(r'\\d+(?!px)', '12px 34em 56')\n    ['1', '34', '56']\n\n    >>> # positive lookbehind - find digits after $\n    >>> re.findall(r'(?<=\\$)\\d+', '$100 $200')\n    ['100', '200']\n\n    >>> # negative lookbehind - find digits not after $\n    >>> re.findall(r'(?<!\\$)\\d+', '$100 200')\n    ['00', '200']\n\n    >>> # insert space before groups of 3 digits from right\n    >>> re.sub(r'(?=(\\d{3})+$)', ' ', '12345678')\n    ' 12 345 678'\n\nCompile Pattern for Reuse\n-------------------------\n\nUse ``re.compile()`` to create a reusable pattern object. This improves\nperformance when the same pattern is used multiple times.\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'\\d{4}-\\d{2}-\\d{2}')\n    >>> pattern.search('Date: 2024-01-15')\n    <re.Match object; span=(6, 16), match='2024-01-15'>\n    >>> pattern.findall('2024-01-15 and 2024-02-20')\n    ['2024-01-15', '2024-02-20']\n\nRegex Flags\n-----------\n\nFlags modify pattern behavior. Common flags include ``re.IGNORECASE`` (``re.I``),\n``re.MULTILINE`` (``re.M``), ``re.DOTALL`` (``re.S``), and ``re.VERBOSE`` (``re.X``).\n\n.. code-block:: python\n\n    >>> # case insensitive\n    >>> re.findall(r'[a-z]+', 'Hello World', re.I)\n    ['Hello', 'World']\n\n    >>> # multiline - ^ and $ match line boundaries\n    >>> re.findall(r'^\\w+', 'line1\\nline2', re.M)\n    ['line1', 'line2']\n\n    >>> # dotall - . matches newline\n    >>> re.search(r'a.b', 'a\\nb', re.S)\n    <re.Match object; span=(0, 3), match='a\\nb'>\n\n    >>> # verbose - allow comments and whitespace\n    >>> pattern = re.compile(r'''\n    ...     \\d{4}  # year\n    ...     -\n    ...     \\d{2}  # month\n    ...     -\n    ...     \\d{2}  # day\n    ... ''', re.X)\n    >>> pattern.match('2024-01-15')\n    <re.Match object; span=(0, 10), match='2024-01-15'>\n\nCompare HTML Tags\n-----------------\n\nCommon patterns for matching different types of HTML tags.\n\n+------------+--------------+--------------+\n| Tag Type   | Pattern      | Example      |\n+============+==============+==============+\n| All tags   | <[^>]+>      | <br />, <a>  |\n+------------+--------------+--------------+\n| Open tag   | <[^/>][^>]*> | <a>, <table> |\n+------------+--------------+--------------+\n| Close tag  | </[^>]+>     | </p>, </a>   |\n+------------+--------------+--------------+\n| Self-close | <[^/>]+/>    | <br />       |\n+------------+--------------+--------------+\n\n.. code-block:: python\n\n    >>> # open tag\n    >>> re.search(r'<[^/>][^>]*>', '<table>') is not None\n    True\n    >>> re.search(r'<[^/>][^>]*>', '</table>') is not None\n    False\n\n    >>> # close tag\n    >>> re.search(r'</[^>]+>', '</table>') is not None\n    True\n\n    >>> # self-closing tag\n    >>> re.search(r'<[^/>]+/>', '<br />') is not None\n    True\n\nMatch Email Address\n-------------------\n\nA pattern for validating email addresses. Note that fully RFC-compliant email\nvalidation is extremely complex; this covers common cases.\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$')\n    >>> pattern.match('hello.world@example.com') is not None\n    True\n    >>> pattern.match('user+tag@sub.domain.org') is not None\n    True\n    >>> pattern.match('invalid@') is not None\n    False\n\nMatch URL\n---------\n\nA pattern for matching URLs with optional protocol, domain, and path.\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'''\n    ...     ^(https?://)?           # optional protocol\n    ...     ([\\da-z.-]+)            # domain\n    ...     \\.([a-z.]{2,6})         # TLD\n    ...     ([/\\w.-]*)*/?$          # path\n    ... ''', re.X | re.I)\n    >>> pattern.match('https://www.example.com/path') is not None\n    True\n    >>> pattern.match('example.com') is not None\n    True\n\nMatch IP Address\n----------------\n\nA pattern for validating IPv4 addresses (0.0.0.0 to 255.255.255.255).\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'''\n    ...     ^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}\n    ...     (?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$\n    ... ''', re.X)\n    >>> pattern.match('192.168.1.1') is not None\n    True\n    >>> pattern.match('255.255.255.0') is not None\n    True\n    >>> pattern.match('256.0.0.0') is not None\n    False\n\nMatch MAC Address\n-----------------\n\nA pattern for validating MAC addresses in colon-separated format.\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'^([0-9a-f]{2}:){5}[0-9a-f]{2}$', re.I)\n    >>> pattern.match('3c:38:51:05:03:1e') is not None\n    True\n    >>> pattern.match('AA:BB:CC:DD:EE:FF') is not None\n    True\n\nMatch Phone Number\n------------------\n\nPatterns for common phone number formats.\n\n.. code-block:: python\n\n    >>> # US phone number\n    >>> pattern = re.compile(r'^(\\+1)?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$')\n    >>> pattern.match('123-456-7890') is not None\n    True\n    >>> pattern.match('(123) 456-7890') is not None\n    True\n    >>> pattern.match('+1 123 456 7890') is not None\n    True\n\nMatch Password Strength\n-----------------------\n\nPattern to validate password with minimum requirements: at least 8 characters,\none uppercase, one lowercase, one digit, and one special character.\n\n.. code-block:: python\n\n    >>> pattern = re.compile(r'''\n    ...     ^(?=.*[a-z])        # at least one lowercase\n    ...     (?=.*[A-Z])         # at least one uppercase\n    ...     (?=.*\\d)            # at least one digit\n    ...     (?=.*[@$!%*?&])     # at least one special char\n    ...     [A-Za-z\\d@$!%*?&]{8,}$  # at least 8 chars\n    ... ''', re.X)\n    >>> pattern.match('Passw0rd!') is not None\n    True\n    >>> pattern.match('weakpass') is not None\n    False\n\nSimple Lexer\n------------\n\nUsing regex to build a simple tokenizer for arithmetic expressions. This\ndemonstrates using named groups and ``scanner()`` for lexical analysis.\n\n.. code-block:: python\n\n    >>> from collections import namedtuple\n    >>> tokens = [\n    ...     r'(?P<NUMBER>\\d+)',\n    ...     r'(?P<PLUS>\\+)',\n    ...     r'(?P<MINUS>-)',\n    ...     r'(?P<TIMES>\\*)',\n    ...     r'(?P<DIVIDE>/)',\n    ...     r'(?P<WS>\\s+)'\n    ... ]\n    >>> lex = re.compile('|'.join(tokens))\n    >>> Token = namedtuple('Token', ['type', 'value'])\n    >>> def tokenize(text):\n    ...     scan = lex.scanner(text)\n    ...     return (Token(m.lastgroup, m.group())\n    ...             for m in iter(scan.match, None) if m.lastgroup != 'WS')\n    ...\n    >>> list(tokenize('9 + 5 * 2'))\n    [Token(type='NUMBER', value='9'), Token(type='PLUS', value='+'), Token(type='NUMBER', value='5'), Token(type='TIMES', value='*'), Token(type='NUMBER', value='2')]\n\nCommon Patterns Reference\n-------------------------\n\n.. code-block:: python\n\n    # Digits only\n    r'^\\d+$'\n\n    # Alphanumeric\n    r'^[a-zA-Z0-9]+$'\n\n    # Username (3-16 chars, alphanumeric, underscore, hyphen)\n    r'^[a-zA-Z0-9_-]{3,16}$'\n\n    # Hex color\n    r'^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$'\n\n    # Date (YYYY-MM-DD)\n    r'^\\d{4}-\\d{2}-\\d{2}$'\n\n    # Time (HH:MM:SS)\n    r'^\\d{2}:\\d{2}:\\d{2}$'\n\n    # Slug (URL-friendly string)\n    r'^[a-z0-9]+(?:-[a-z0-9]+)*$'\n\n    # Remove HTML tags\n    re.sub(r'<[^>]+>', '', html)\n\n    # Extract domain from URL\n    re.search(r'https?://([^/]+)', url).group(1)\n\n    # Find all hashtags\n    re.findall(r'#\\w+', text)\n\n    # Find all @mentions\n    re.findall(r'@\\w+', text)\n"
  },
  {
    "path": "docs/notes/basic/python-set.rst",
    "content": ".. meta::\n    :description lang=en: Python set cheat sheet covering set comprehensions, set operations (union, intersection, difference), removing duplicates, subsets, supersets, and frozenset with code examples\n    :keywords: Python, Python3, Python set, Python set cheat sheet, set comprehension, set operations, union, intersection, difference, symmetric difference, frozenset, subset, superset\n\n===\nSet\n===\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSets are unordered collections of unique elements in Python. They provide O(1)\naverage time complexity for membership testing and support mathematical set\noperations like union, intersection, and difference. This cheat sheet covers\nset comprehensions, common set operations, uniquifying lists, and the immutable\nfrozenset type.\n\nThe source code is available on `GitHub <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/set.py>`_.\n\nReferences\n----------\n\n- `Set Types — set, frozenset <https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset>`_\n- `Sets <https://docs.python.org/3/tutorial/datastructures.html#sets>`_\n\nCreate a Set\n------------\n\nCreate sets using curly braces ``{}`` or the ``set()`` constructor. Note that\nempty curly braces ``{}`` create a dict, not a set.\n\n.. code-block:: python\n\n    >>> s = {1, 2, 3}\n    >>> s\n    {1, 2, 3}\n    >>> s = set([1, 2, 2, 3])\n    >>> s\n    {1, 2, 3}\n    >>> empty = set()  # not {}\n    >>> type(empty)\n    <class 'set'>\n\nCreate Sets with Set Comprehension\n----------------------------------\n\nLike list comprehensions, set comprehensions provide a concise way to create sets.\nThe syntax uses curly braces ``{}`` instead of square brackets.\n\n.. code-block:: python\n\n    >>> a = [1, 2, 5, 6, 6, 6, 7]\n    >>> s = {x for x in a}\n    >>> s\n    {1, 2, 5, 6, 7}\n    >>> s = {x for x in a if x > 3}\n    >>> s\n    {5, 6, 7}\n    >>> s = {x ** 2 for x in range(5)}\n    >>> s\n    {0, 1, 4, 9, 16}\n\nRemove Duplicates from a List\n-----------------------------\n\nConverting a list to a set automatically removes duplicate elements. This is one\nof the most common use cases for sets.\n\n.. code-block:: python\n\n    >>> a = [1, 2, 2, 2, 3, 4, 5, 5]\n    >>> list(set(a))\n    [1, 2, 3, 4, 5]\n\nTo preserve the original order, use ``dict.fromkeys()`` (Python 3.7+):\n\n.. code-block:: python\n\n    >>> a = [3, 1, 2, 1, 3, 2]\n    >>> list(dict.fromkeys(a))\n    [3, 1, 2]\n\nAdd Items to a Set\n------------------\n\nUse ``add()`` to add a single element, or ``update()`` to add multiple elements.\n\n.. code-block:: python\n\n    >>> s = {1, 2, 3}\n    >>> s.add(4)\n    >>> s\n    {1, 2, 3, 4}\n    >>> s.update([5, 6, 7])\n    >>> s\n    {1, 2, 3, 4, 5, 6, 7}\n    >>> s |= {8, 9}  # same as update\n    >>> s\n    {1, 2, 3, 4, 5, 6, 7, 8, 9}\n\nRemove Items from a Set\n-----------------------\n\nUse ``remove()`` to remove an element (raises KeyError if not found), or\n``discard()`` to remove without error. Use ``pop()`` to remove an arbitrary element.\n\n.. code-block:: python\n\n    >>> s = {1, 2, 3, 4, 5}\n    >>> s.remove(3)\n    >>> s\n    {1, 2, 4, 5}\n    >>> s.discard(10)  # no error if not found\n    >>> s.pop()  # remove arbitrary element\n    1\n    >>> s.clear()  # remove all\n    >>> s\n    set()\n\nUnion with ``|`` Operator\n-------------------------\n\nThe union of two sets contains all elements from both sets. Use the ``|`` operator\nor the ``union()`` method.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3}\n    >>> b = {3, 4, 5}\n    >>> a | b\n    {1, 2, 3, 4, 5}\n    >>> a.union(b)\n    {1, 2, 3, 4, 5}\n    >>> a | b | {6, 7}  # multiple sets\n    {1, 2, 3, 4, 5, 6, 7}\n\nIntersection with ``&`` Operator\n--------------------------------\n\nThe intersection of two sets contains only elements that exist in both sets.\nUse the ``&`` operator or the ``intersection()`` method.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3, 4}\n    >>> b = {3, 4, 5, 6}\n    >>> a & b\n    {3, 4}\n    >>> a.intersection(b)\n    {3, 4}\n\nFind Common Elements Between Lists\n----------------------------------\n\nFinding common items between two lists is a practical application of set\nintersection.\n\n.. code-block:: python\n\n    >>> a = [1, 1, 2, 3]\n    >>> b = [3, 5, 5, 6]\n    >>> list(set(a) & set(b))\n    [3]\n\nDifference with ``-`` Operator\n------------------------------\n\nThe difference of two sets contains elements that are in the first set but not\nin the second. Use the ``-`` operator or the ``difference()`` method.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3, 4}\n    >>> b = {3, 4, 5, 6}\n    >>> a - b\n    {1, 2}\n    >>> b - a\n    {5, 6}\n\nSymmetric Difference with ``^`` Operator\n----------------------------------------\n\nThe symmetric difference contains elements that are in either set, but not in\nboth. Use the ``^`` operator or the ``symmetric_difference()`` method.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3}\n    >>> b = {3, 4, 5}\n    >>> a ^ b\n    {1, 2, 4, 5}\n\nCheck Subset with ``<=`` Operator\n---------------------------------\n\nUse ``<=`` or ``issubset()`` to check if all elements of one set are in another.\nUse ``<`` for proper subset (subset but not equal).\n\n.. code-block:: python\n\n    >>> a = {1, 2}\n    >>> b = {1, 2, 3, 4}\n    >>> a <= b  # a is subset of b\n    True\n    >>> a < b   # a is proper subset\n    True\n    >>> a <= a  # equal sets\n    True\n    >>> a < a   # not proper subset\n    False\n\nCheck Superset with ``>=`` Operator\n-----------------------------------\n\nUse ``>=`` or ``issuperset()`` to check if a set contains all elements of another.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3, 4}\n    >>> b = {1, 2}\n    >>> a >= b  # a is superset of b\n    True\n    >>> a > b   # a is proper superset\n    True\n\nCheck Disjoint Sets\n-------------------\n\nTwo sets are disjoint if they have no elements in common. Use ``isdisjoint()``\nto check.\n\n.. code-block:: python\n\n    >>> a = {1, 2, 3}\n    >>> b = {4, 5, 6}\n    >>> a.isdisjoint(b)\n    True\n    >>> c = {3, 4, 5}\n    >>> a.isdisjoint(c)\n    False\n\nMembership Testing\n------------------\n\nSets provide O(1) average time complexity for membership testing, making them\nmuch faster than lists for this operation.\n\n.. code-block:: python\n\n    >>> s = {1, 2, 3, 4, 5}\n    >>> 3 in s\n    True\n    >>> 10 in s\n    False\n    >>> 10 not in s\n    True\n\nFrozenset - Immutable Set\n-------------------------\n\n``frozenset`` is an immutable version of set. It can be used as a dictionary key\nor as an element of another set.\n\n.. code-block:: python\n\n    >>> fs = frozenset([1, 2, 3])\n    >>> fs\n    frozenset({1, 2, 3})\n    >>> fs.add(4)  # raises AttributeError\n    AttributeError: 'frozenset' object has no attribute 'add'\n\nUse frozenset as dictionary key:\n\n.. code-block:: python\n\n    >>> d = {frozenset([1, 2]): \"a\", frozenset([3, 4]): \"b\"}\n    >>> d[frozenset([1, 2])]\n    'a'\n\nUse frozenset in a set:\n\n.. code-block:: python\n\n    >>> s = {frozenset([1, 2]), frozenset([3, 4])}\n    >>> frozenset([1, 2]) in s\n    True\n\nSet Operations Summary\n----------------------\n\n.. code-block:: python\n\n    # Creation\n    s = {1, 2, 3}           # literal\n    s = set([1, 2, 3])      # from iterable\n    s = {x for x in range(5)}  # comprehension\n\n    # Add/Remove\n    s.add(x)                # add single element\n    s.update([x, y])        # add multiple elements\n    s.remove(x)             # remove (KeyError if missing)\n    s.discard(x)            # remove (no error if missing)\n    s.pop()                 # remove arbitrary element\n    s.clear()               # remove all\n\n    # Set Operations\n    a | b                   # union\n    a & b                   # intersection\n    a - b                   # difference\n    a ^ b                   # symmetric difference\n\n    # Comparisons\n    a <= b                  # subset\n    a < b                   # proper subset\n    a >= b                  # superset\n    a > b                   # proper superset\n    a.isdisjoint(b)         # no common elements\n\n    # Membership\n    x in s                  # O(1) lookup\n    x not in s\n"
  },
  {
    "path": "docs/notes/basic/python-typing.rst",
    "content": ".. meta::\n    :description lang=en: Python typing cheat sheet covering type hints, annotations, generics, protocols, TypeVar, and mypy type checking with code examples\n    :keywords: Python, Python3, Python typing, Python type hints cheat sheet, type annotations, generics, Protocol, TypeVar, mypy, static typing\n\n======\nTyping\n======\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nPEP `484 <https://www.python.org/dev/peps/pep-0484/>`_, which provides a\nspecification about what a type system should look like in Python3, introduced\nthe concept of type hints. Moreover, to better understand the type hints design\nphilosophy, it is crucial to read PEP `483 <https://www.python.org/dev/peps/pep-0483/>`_\nthat would be helpful to aid a pythoneer to understand reasons why Python\nintroduce a type system. The main goal of this cheat sheet is to show some\ncommon usage about type hints in Python3.\n\n\nWithout type check\n-------------------\n\n.. code-block:: python\n\n    def fib(n):\n        a, b = 0, 1\n        for _ in range(n):\n            yield a\n            b, a = a + b, b\n\n    print([n for n in fib(3.6)])\n\n\noutput:\n\n.. code-block:: bash\n\n    # errors will not be detected until runtime\n\n    $ python fib.py\n    Traceback (most recent call last):\n      File \"fib.py\", line 8, in <module>\n        print([n for n in fib(3.5)])\n      File \"fib.py\", line 8, in <listcomp>\n        print([n for n in fib(3.5)])\n      File \"fib.py\", line 3, in fib\n        for _ in range(n):\n    TypeError: 'float' object cannot be interpreted as an integer\n\n\nWith type check\n----------------\n\n.. code-block:: python\n\n    # give a type hint\n    from typing import Generator\n\n    def fib(n: int) -> Generator:\n        a: int = 0\n        b: int = 1\n        for _ in range(n):\n            yield a\n            b, a = a + b, b\n\n    print([n for n in fib(3.6)])\n\noutput:\n\n.. code-block:: bash\n\n    # errors will be detected before running\n\n    $ mypy --strict fib.py\n    fib.py:12: error: Argument 1 to \"fib\" has incompatible type \"float\"; expected \"int\"\n\nBasic types\n-----------\n\n.. code-block:: python\n\n    import io\n    import re\n\n    from collections import deque, namedtuple\n    from typing import (\n        Dict,\n        List,\n        Tuple,\n        Set,\n        Deque,\n        NamedTuple,\n        IO,\n        Pattern,\n        Match,\n        Text,\n        Optional,\n        Sequence,\n        Iterable,\n        Mapping,\n        MutableMapping,\n        Any,\n    )\n\n    # without initializing\n    x: int\n\n    # any type\n    y: Any\n    y = 1\n    y = \"1\"\n\n    # built-in\n    var_int: int = 1\n    var_str: str = \"Hello Typing\"\n    var_byte: bytes = b\"Hello Typing\"\n    var_bool: bool = True\n    var_float: float = 1.\n    var_unicode: Text = u'\\u2713'\n\n    # could be none\n    var_could_be_none: Optional[int] = None\n    var_could_be_none = 1\n\n    # collections\n    var_set: Set[int] = {i for i in range(3)}\n    var_dict: Dict[str, str] = {\"foo\": \"Foo\"}\n    var_list: List[int] = [i for i in range(3)]\n    var_static_length_Tuple: Tuple[int, int, int] = (1, 2, 3)\n    var_dynamic_length_Tuple: Tuple[int, ...] = (i for i in range(10, 3))\n    var_deque: Deque = deque([1, 2, 3])\n    var_nametuple: NamedTuple = namedtuple('P', ['x', 'y'])\n\n    # io\n    var_io_str: IO[str] = io.StringIO(\"Hello String\")\n    var_io_byte: IO[bytes] = io.BytesIO(b\"Hello Bytes\")\n    var_io_file_str: IO[str] = open(__file__)\n    var_io_file_byte: IO[bytes] = open(__file__, 'rb')\n\n    # re\n    p: Pattern = re.compile(\"(https?)://([^/\\r\\n]+)(/[^\\r\\n]*)?\")\n    m: Optional[Match] = p.match(\"https://www.python.org/\")\n\n    # duck types: list-like\n    var_seq_list: Sequence[int] = [1, 2, 3]\n    var_seq_tuple: Sequence[int] = (1, 2, 3)\n    var_iter_list: Iterable[int] = [1, 2, 3]\n    var_iter_tuple: Iterable[int] = (1, 2, 3)\n\n    # duck types: dict-like\n    var_map_dict: Mapping[str, str] = {\"foo\": \"Foo\"}\n    var_mutable_dict: MutableMapping[str, str] = {\"bar\": \"Bar\"}\n\nFunctions\n----------\n\n.. code-block:: python\n\n    from typing import Generator, Callable\n\n    # function\n    def gcd(a: int, b: int) -> int:\n        while b:\n            a, b = b, a % b\n        return a\n\n    # callback\n    def fun(cb: Callable[[int, int], int]) -> int:\n        return cb(55, 66)\n\n    # lambda\n    f: Callable[[int], int] = lambda x: x * 2\n\nClasses\n--------\n\n.. code-block:: python\n\n    from typing import ClassVar, Dict, List\n\n    class Foo:\n\n        x: int = 1  # instance variable. default = 1\n        y: ClassVar[str] = \"class var\"  # class variable\n\n        def __init__(self) -> None:\n            self.i: List[int] = [0]\n\n        def foo(self, a: int, b: str) -> Dict[int, str]:\n            return {a: b}\n\n    foo = Foo()\n    foo.x = 123\n\n    print(foo.x)\n    print(foo.i)\n    print(Foo.y)\n    print(foo.foo(1, \"abc\"))\n\nGenerator\n----------\n\n.. code-block:: python\n\n    from typing import Generator\n\n    # Generator[YieldType, SendType, ReturnType]\n    def fib(n: int) -> Generator[int, None, None]:\n        a: int = 0\n        b: int = 1\n        while n > 0:\n            yield a\n            b, a = a + b, b\n            n -= 1\n\n    g: Generator = fib(10)\n    i: Iterator[int] = (x for x in range(3))\n\nAsynchronous Generator\n-----------------------\n\n.. code-block:: python\n\n    import asyncio\n\n    from typing import AsyncGenerator, AsyncIterator\n\n    async def fib(n: int) -> AsyncGenerator:\n        a: int = 0\n        b: int = 1\n        while n > 0:\n            await asyncio.sleep(0.1)\n            yield a\n\n            b, a = a + b, b\n            n -= 1\n\n    async def main() -> None:\n        async for f in fib(10):\n            print(f)\n\n        ag: AsyncIterator = (f async for f in fib(10))\n\n    loop = asyncio.get_event_loop()\n    loop.run_until_complete(main())\n\nContext Manager\n---------------\n\n.. code-block:: python\n\n    from typing import ContextManager, Generator, IO\n    from contextlib import contextmanager\n\n    @contextmanager\n    def open_file(name: str) -> Generator:\n        f = open(name)\n        yield f\n        f.close()\n\n    cm: ContextManager[IO] = open_file(__file__)\n    with cm as f:\n        print(f.read())\n\nAsynchronous Context Manager\n-----------------------------\n\n.. code-block:: python\n\n    import asyncio\n\n    from typing import AsyncContextManager, AsyncGenerator, IO\n    from contextlib import asynccontextmanager\n\n    # need python 3.7 or above\n    @asynccontextmanager\n    async def open_file(name: str) -> AsyncGenerator:\n        await asyncio.sleep(0.1)\n        f = open(name)\n        yield f\n        await asyncio.sleep(0.1)\n        f.close()\n\n    async def main() -> None:\n        acm: AsyncContextManager[IO] = open_file(__file__)\n        async with acm as f:\n            print(f.read())\n\n    loop = asyncio.get_event_loop()\n    loop.run_until_complete(main())\n\nAvoid ``None`` access\n----------------------\n\n.. code-block:: python\n\n    import re\n\n    from typing import Pattern, Dict, Optional\n\n    # like c++\n    # std::regex url(\"(https?)://([^/\\r\\n]+)(/[^\\r\\n]*)?\");\n    # std::regex color(\"^#?([a-f0-9]{6}|[a-f0-9]{3})$\");\n\n    url: Pattern = re.compile(\"(https?)://([^/\\r\\n]+)(/[^\\r\\n]*)?\")\n    color: Pattern = re.compile(\"^#?([a-f0-9]{6}|[a-f0-9]{3})$\")\n\n    x: Dict[str, Pattern] = {\"url\": url, \"color\": color}\n    y: Optional[Pattern] = x.get(\"baz\", None)\n\n    print(y.match(\"https://www.python.org/\"))\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:15: error: Item \"None\" of \"Optional[Pattern[Any]]\" has no attribute \"match\"\n\nPositional-only arguments\n--------------------------\n\n.. code-block:: python\n\n    # define arguments with names beginning with __\n\n    def fib(__n: int) -> int:  # positional only arg\n        a, b = 0, 1\n        for _ in range(__n):\n            b, a = a + b, b\n        return a\n\n\n    def gcd(*, a: int, b: int) -> int:  # keyword only arg\n        while b:\n            a, b = b, a % b\n        return a\n\n\n    print(fib(__n=10))  # error\n    print(gcd(10, 5))   # error\n\noutput:\n\n.. code-block:: bash\n\n    mypy --strict foo.py\n    foo.py:1: note: \"fib\" defined here\n    foo.py:14: error: Unexpected keyword argument \"__n\" for \"fib\"\n    foo.py:15: error: Too many positional arguments for \"gcd\"\n\nMultiple return values\n-----------------------\n\n.. code-block:: python\n\n    from typing import Tuple, Iterable, Union\n\n    def foo(x: int, y: int) -> Tuple[int, int]:\n        return x, y\n\n    # or\n\n    def bar(x: int, y: str) -> Iterable[Union[int, str]]:\n        # XXX: not recommend declaring in this way\n        return x, y\n\n    a: int\n    b: int\n    a, b = foo(1, 2)      # ok\n    c, d = bar(3, \"bar\")  # ok\n\nOptional Type\n----------------------------------\n\n.. code-block:: python\n\n    from typing import List, Union\n\n    def first(l: List[Union[int, None]]) -> Union[int, None]:\n        return None if len(l) == 0 else l[0]\n\n    first([None])\n\n    # equal to\n\n    from typing import List, Optional\n\n    def first(l: List[Optional[int]]) -> Optional[int]:\n        return None if len(l) == 0 else l[0]\n\n    first([None])\n\nBe careful of ``Optional``\n---------------------------\n\n.. code-block:: python\n\n    from typing import cast, Optional\n\n    def fib(n):\n        a, b = 0, 1\n        for _ in range(n):\n            b, a = a + b, b\n        return a\n\n    def cal(n: Optional[int]) -> None:\n        print(fib(n))\n\n    cal(None)\n\noutput:\n\n.. code-block:: bash\n\n    # mypy will not detect errors\n    $ mypy foo.py\n\nExplicitly declare\n\n.. code-block:: python\n\n    from typing import Optional\n\n    def fib(n: int) -> int:  # declare n to be int\n        a, b = 0, 1\n        for _ in range(n):\n            b, a = a + b, b\n        return a\n\n    def cal(n: Optional[int]) -> None:\n        print(fib(n))\n\noutput:\n\n.. code-block:: bash\n\n    # mypy can detect errors even we do not check None\n    $ mypy --strict foo.py\n    foo.py:11: error: Argument 1 to \"fib\" has incompatible type \"Optional[int]\"; expected \"int\"\n\nBe careful of casting\n----------------------\n\n.. code-block:: python\n\n    from typing import cast, Optional\n\n    def gcd(a: int, b: int) -> int:\n        while b:\n            a, b = b, a % b\n        return a\n\n    def cal(a: Optional[int], b: Optional[int]) -> None:\n        # XXX: Avoid casting\n        ca, cb = cast(int, a), cast(int, b)\n        print(gcd(ca, cb))\n\n    cal(None, None)\n\noutput:\n\n.. code-block:: bash\n\n    # mypy will not detect type errors\n    $ mypy --strict foo.py\n\n\nForward references\n-------------------\n\nBased on PEP 484, if we want to reference a type before it has been declared, we\nhave to use **string literal** to imply that there is a type of that name later on\nin the file.\n\n.. code-block:: python\n\n    from typing import Optional\n\n\n    class Tree:\n        def __init__(\n            self, data: int,\n            left: Optional[\"Tree\"],  # Forward references.\n            right: Optional[\"Tree\"]\n        ) -> None:\n            self.data = data\n            self.left = left\n            self.right = right\n\n.. note::\n\n    There are some issues that mypy does not complain about Forward References.\n    Get further information from `Issue#948`_.\n\n.. _Issue\\#948: https://github.com/python/mypy/issues/948\n\n.. code-block:: python\n\n    class A:\n        def __init__(self, a: A) -> None:  # should fail\n            self.a = a\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict type.py\n    $ echo $?\n    0\n    $ python type.py   # get runtime fail\n    Traceback (most recent call last):\n      File \"type.py\", line 1, in <module>\n        class A:\n      File \"type.py\", line 2, in A\n        def __init__(self, a: A) -> None:  # should fail\n    NameError: name 'A' is not defined\n\nPostponed Evaluation of Annotations\n-----------------------------------\n\n**New in Python 3.7**\n\n- PEP 563_ - Postponed Evaluation of Annotations\n\n.. _563: https://www.python.org/dev/peps/pep-0563/\n\nBefore Python 3.7\n\n.. code-block:: python\n\n    >>> class A:\n    ...     def __init__(self, a: A) -> None:\n    ...         self._a = a\n    ...\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n      File \"<stdin>\", line 2, in A\n    NameError: name 'A' is not defined\n\nAfter Python 3.7 (include 3.7)\n\n.. code-block:: python\n\n    >>> from __future__ import annotations\n    >>> class A:\n    ...     def __init__(self, a: A) -> None:\n    ...         self._a = a\n    ...\n\n.. note::\n\n    Annotation can only be used within the scope which names have already\n    existed. Therefore, **forward reference** does not support the case which\n    names are not available in the current scope. **Postponed evaluation\n    of annotations** will become the default behavior in Python 4.0.\n\nType Alias\n----------\n\nLike ``typedef`` or ``using`` in c/c++\n\n.. code-block:: cpp\n\n    #include <iostream>\n    #include <string>\n    #include <regex>\n    #include <vector>\n\n    typedef std::string Url;\n    template<typename T> using Vector = std::vector<T>;\n\n    int main(int argc, char *argv[])\n    {\n        Url url = \"https://python.org\";\n        std::regex p(\"(https?)://([^/\\r\\n]+)(/[^\\r\\n]*)?\");\n        bool m = std::regex_match(url, p);\n        Vector<int> v = {1, 2};\n\n        std::cout << m << std::endl;\n        for (auto it : v) std::cout << it << std::endl;\n        return 0;\n    }\n\nType aliases are defined by simple variable assignments\n\n.. code-block:: python\n\n    import re\n\n    from typing import Pattern, List\n\n    # Like typedef, using in c/c++\n\n    # PEP 484 recommend capitalizing alias names\n    Url = str\n\n    url: Url = \"https://www.python.org/\"\n\n    p: Pattern = re.compile(\"(https?)://([^/\\r\\n]+)(/[^\\r\\n]*)?\")\n    m = p.match(url)\n\n    Vector = List[int]\n    v: Vector = [1., 2.]\n\nUsing NewType\n---------------------\n\nUnlike alias, ``NewType`` returns a separate type but is identical to the original type at runtime.\n\n.. code-block:: python\n\n    from sqlalchemy import Column, String, Integer\n    from sqlalchemy.ext.declarative import declarative_base\n    from typing import NewType, Any\n\n    # check mypy #2477\n    Base: Any = declarative_base()\n\n    # create a new type\n    Id = NewType('Id', int) # not equal alias, it's a 'new type'\n\n    class User(Base):\n        __tablename__ = 'User'\n        id = Column(Integer, primary_key=True)\n        age = Column(Integer, nullable=False)\n        name = Column(String, nullable=False)\n\n        def __init__(self, id: Id, age: int, name: str) -> None:\n            self.id = id\n            self.age = age\n            self.name = name\n\n    # create users\n    user1 = User(Id(1), 62, \"Guido van Rossum\") # ok\n    user2 = User(2, 48, \"David M. Beazley\")     # error\n\noutput:\n\n.. code-block:: bash\n\n    $ python foo.py\n    $ mypy --ignore-missing-imports foo.py\n    foo.py:24: error: Argument 1 to \"User\" has incompatible type \"int\"; expected \"Id\"\n\nFurther reading:\n\n- `Issue\\#1284`_\n\n.. _`Issue\\#1284`: https://github.com/python/mypy/issues/1284\n\n\nUsing ``TypeVar`` as template\n------------------------------\n\nLike c++ ``template <typename T>``\n\n.. code-block:: cpp\n\n    #include <iostream>\n\n    template <typename T>\n    T add(T x, T y) {\n        return x + y;\n    }\n\n    int main(int argc, char *argv[])\n    {\n        std::cout << add(1, 2) << std::endl;\n        std::cout << add(1., 2.) << std::endl;\n        return 0;\n    }\n\nPython using ``TypeVar``\n\n.. code-block:: python\n\n    from typing import TypeVar\n\n    T = TypeVar(\"T\")\n\n    def add(x: T, y: T) -> T:\n        return x + y\n\n    add(1, 2)\n    add(1., 2.)\n\nUsing ``TypeVar`` and ``Generic`` as class template\n----------------------------------------------------\n\nLike c++ ``template <typename T> class``\n\n.. code-block:: cpp\n\n    #include <iostream>\n\n    template<typename T>\n    class Foo {\n    public:\n        Foo(T foo) {\n            foo_ = foo;\n        }\n        T Get() {\n            return foo_;\n        }\n    private:\n        T foo_;\n    };\n\n    int main(int argc, char *argv[])\n    {\n        Foo<int> f(123);\n        std::cout << f.Get() << std::endl;\n        return 0;\n    }\n\nDefine a generic class in Python\n\n.. code-block:: python\n\n    from typing import Generic, TypeVar\n\n    T = TypeVar(\"T\")\n\n    class Foo(Generic[T]):\n        def __init__(self, foo: T) -> None:\n            self.foo = foo\n\n        def get(self) -> T:\n            return self.foo\n\n    f: Foo[str] = Foo(\"Foo\")\n    v: int = f.get()\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:13: error: Incompatible types in assignment (expression has type \"str\", variable has type \"int\")\n\nScoping rules for ``TypeVar``\n------------------------------\n\n- ``TypeVar`` used in different generic function will be inferred to be different types.\n\n.. code-block:: python\n\n    from typing import TypeVar\n\n    T = TypeVar(\"T\")\n\n    def foo(x: T) -> T:\n        return x\n\n    def bar(y: T) -> T:\n        return y\n\n    a: int = foo(1)    # ok: T is inferred to be int\n    b: int = bar(\"2\")  # error: T is inferred to be str\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:12: error: Incompatible types in assignment (expression has type \"str\", variable has type \"int\")\n\n- ``TypeVar`` used in a generic class will be inferred to be same types.\n\n.. code-block:: python\n\n    from typing import TypeVar, Generic\n\n    T = TypeVar(\"T\")\n\n    class Foo(Generic[T]):\n\n        def foo(self, x: T) -> T:\n            return x\n\n        def bar(self, y: T) -> T:\n            return y\n\n    f: Foo[int] = Foo()\n    a: int = f.foo(1)    # ok: T is inferred to be int\n    b: str = f.bar(\"2\")  # error: T is expected to be int\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:15: error: Incompatible types in assignment (expression has type \"int\", variable has type \"str\")\n    foo.py:15: error: Argument 1 to \"bar\" of \"Foo\" has incompatible type \"str\"; expected \"int\"\n\n- ``TypeVar`` used in a method but did not match any parameters which declare in ``Generic`` can be inferred to be different types.\n\n.. code-block:: python\n\n    from typing import TypeVar, Generic\n\n    T = TypeVar(\"T\")\n    S = TypeVar(\"S\")\n\n    class Foo(Generic[T]):    # S does not match params\n\n        def foo(self, x: T, y: S) -> S:\n            return y\n\n        def bar(self, z: S) -> S:\n            return z\n\n    f: Foo[int] = Foo()\n    a: str = f.foo(1, \"foo\")  # S is inferred to be str\n    b: int = f.bar(12345678)  # S is inferred to be int\n\noutput:\n\n.. code-block:: bash\n\n    $  mypy --strict foo.py\n\n- ``TypeVar`` should not appear in body of method/function if it is unbound type.\n\n.. code-block:: python\n\n    from typing import TypeVar, Generic\n\n    T = TypeVar(\"T\")\n    S = TypeVar(\"S\")\n\n    def foo(x: T) -> None:\n        a: T = x    # ok\n        b: S = 123  # error: invalid type\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:8: error: Invalid type \"foo.S\"\n\nRestricting to a fixed set of possible types\n----------------------------------------------\n\n``T = TypeVar('T', ClassA, ...)`` means we create a **type variable with a value restriction**.\n\n.. code-block:: python\n\n    from typing import TypeVar\n\n    # restrict T = int or T = float\n    T = TypeVar(\"T\", int, float)\n\n    def add(x: T, y: T) -> T:\n        return x + y\n\n    add(1, 2)\n    add(1., 2.)\n    add(\"1\", 2)\n    add(\"hello\", \"world\")\n\noutput:\n\n.. code-block:: bash\n\n    # mypy can detect wrong type\n    $ mypy --strict foo.py\n    foo.py:10: error: Value of type variable \"T\" of \"add\" cannot be \"object\"\n    foo.py:11: error: Value of type variable \"T\" of \"add\" cannot be \"str\"\n\n``TypeVar`` with an upper bound\n--------------------------------\n\n``T = TypeVar('T', bound=BaseClass)`` means we create a **type variable with an upper bound**.\nThe concept is similar to **polymorphism** in c++.\n\n.. code-block:: cpp\n\n    #include <iostream>\n\n    class Shape {\n    public:\n        Shape(double width, double height) {\n            width_ = width;\n            height_ = height;\n        };\n        virtual double Area() = 0;\n    protected:\n        double width_;\n        double height_;\n    };\n\n    class Rectangle: public Shape {\n    public:\n        Rectangle(double width, double height)\n        :Shape(width, height)\n        {};\n\n        double Area() {\n            return width_ * height_;\n        };\n    };\n\n    class Triangle: public Shape {\n    public:\n        Triangle(double width, double height)\n        :Shape(width, height)\n        {};\n\n        double Area() {\n            return width_ * height_ / 2;\n        };\n    };\n\n    double Area(Shape &s) {\n        return s.Area();\n    }\n\n    int main(int argc, char *argv[])\n    {\n        Rectangle r(1., 2.);\n        Triangle t(3., 4.);\n\n        std::cout << Area(r) << std::endl;\n        std::cout << Area(t) << std::endl;\n        return 0;\n    }\n\nLike c++, create a base class and ``TypeVar`` which bounds to the base class.\nThen, static type checker will take every subclass as type of base class.\n\n.. code-block:: python\n\n    from typing import TypeVar\n\n\n    class Shape:\n        def __init__(self, width: float, height: float) -> None:\n            self.width = width\n            self.height = height\n\n        def area(self) -> float:\n            return 0\n\n\n    class Rectangle(Shape):\n        def area(self) -> float:\n            width: float = self.width\n            height: float = self.height\n            return width * height\n\n\n    class Triangle(Shape):\n        def area(self) -> float:\n            width: float = self.width\n            height: float = self.height\n            return width * height / 2\n\n\n    S = TypeVar(\"S\", bound=Shape)\n\n\n    def area(s: S) -> float:\n        return s.area()\n\n\n    r: Rectangle = Rectangle(1, 2)\n    t: Triangle = Triangle(3, 4)\n    i: int = 5566\n\n    print(area(r))\n    print(area(t))\n    print(area(i))\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:40: error: Value of type variable \"S\" of \"area\" cannot be \"int\"\n\n@overload\n----------\n\nSometimes, we use ``Union`` to infer that the return of a function has multiple\ndifferent types. However, type checker cannot distinguish which type do we want.\nTherefore, following snippet shows that type checker cannot determine which type\nis correct.\n\n.. code-block:: python\n\n    from typing import List, Union\n\n\n    class Array(object):\n        def __init__(self, arr: List[int]) -> None:\n            self.arr = arr\n\n        def __getitem__(self, i: Union[int, str]) -> Union[int, str]:\n            if isinstance(i, int):\n                return self.arr[i]\n            if isinstance(i, str):\n                return str(self.arr[int(i)])\n\n\n    arr = Array([1, 2, 3, 4, 5])\n    x:int = arr[1]\n    y:str = arr[\"2\"]\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    foo.py:16: error: Incompatible types in assignment (expression has type \"Union[int, str]\", variable has type \"int\")\n    foo.py:17: error: Incompatible types in assignment (expression has type \"Union[int, str]\", variable has type \"str\")\n\nAlthough we can use ``cast`` to solve the problem, it cannot avoid typo and ``cast`` is not safe.\n\n.. code-block:: python\n\n    from typing import  List, Union, cast\n\n\n    class Array(object):\n        def __init__(self, arr: List[int]) -> None:\n            self.arr = arr\n\n        def __getitem__(self, i: Union[int, str]) -> Union[int, str]:\n            if isinstance(i, int):\n                return self.arr[i]\n            if isinstance(i, str):\n                return str(self.arr[int(i)])\n\n\n    arr = Array([1, 2, 3, 4, 5])\n    x: int = cast(int, arr[1])\n    y: str = cast(str, arr[2])  # typo. we want to assign arr[\"2\"]\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    $ echo $?\n    0\n\nUsing ``@overload`` can solve the problem. We can declare the return type explicitly.\n\n.. code-block:: python\n\n    from typing import Generic, List, Union, overload\n\n\n    class Array(object):\n        def __init__(self, arr: List[int]) -> None:\n            self.arr = arr\n\n        @overload\n        def __getitem__(self, i: str) -> str:\n            ...\n\n        @overload\n        def __getitem__(self, i: int) -> int:\n            ...\n\n        def __getitem__(self, i: Union[int, str]) -> Union[int, str]:\n            if isinstance(i, int):\n                return self.arr[i]\n            if isinstance(i, str):\n                return str(self.arr[int(i)])\n\n\n    arr = Array([1, 2, 3, 4, 5])\n    x: int = arr[1]\n    y: str = arr[\"2\"]\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict foo.py\n    $ echo $?\n    0\n\n.. warning::\n\n    Based on PEP 484, the ``@overload`` decorator just **for type checker only**, it does not implement\n    the real overloading like c++/java. Thus, we have to implement one exactly non-``@overload``\n    function. At the runtime, calling the ``@overload`` function will raise ``NotImplementedError``.\n\n.. code-block:: python\n\n    from typing import List, Union, overload\n\n\n    class Array(object):\n        def __init__(self, arr: List[int]) -> None:\n            self.arr = arr\n\n        @overload\n        def __getitem__(self, i: Union[int, str]) -> Union[int, str]:\n            if isinstance(i, int):\n                return self.arr[i]\n            if isinstance(i, str):\n                return str(self.arr[int(i)])\n\n\n    arr = Array([1, 2, 3, 4, 5])\n    try:\n        x: int = arr[1]\n    except NotImplementedError as e:\n        print(\"NotImplementedError\")\n\noutput:\n\n.. code-block:: bash\n\n    $ python foo.py\n    NotImplementedError\n\nStub Files\n----------\n\nStub files just like header files which we usually use to define our interfaces in c/c++.\nIn python, we can define our interfaces in the same module directory or ``export MYPYPATH=${stubs}``\n\nFirst, we need to create a stub file (interface file) for module.\n\n.. code-block:: bash\n\n    $ mkdir fib\n    $ touch fib/__init__.py fib/__init__.pyi\n\nThen, define the interface of the function in ``__init__.pyi`` and implement the module.\n\n.. code-block:: python\n\n    # fib/__init__.pyi\n    def fib(n: int) -> int: ...\n\n    # fib/__init__.py\n\n    def fib(n):\n        a, b = 0, 1\n        for _ in range(n):\n            b, a = a + b, b\n        return a\n\nThen, write a test.py for testing ``fib`` module.\n\n.. code-block:: python\n\n    # touch test.py\n    import sys\n\n    from pathlib import Path\n\n    p = Path(__file__).parent / \"fib\"\n    sys.path.append(str(p))\n\n    from fib import fib\n\n    print(fib(10.0))\n\noutput:\n\n.. code-block:: bash\n\n    $ mypy --strict test.py\n    test.py:10: error: Argument 1 to \"fib\" has incompatible type \"float\"; expected \"int\"\n"
  },
  {
    "path": "docs/notes/basic/python-unicode.rst",
    "content": ".. meta::\n    :description lang=en: Python Unicode tutorial covering string encoding, decoding, UTF-8, ASCII, bytes conversion, and character handling in Python 3\n    :keywords: Python, Python3, Unicode, UTF-8, encoding, decoding, bytes, string, ASCII, character, codec\n\n=======\nUnicode\n=======\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe main goal of this cheat sheet is to collect some common snippets which are\nrelated to Unicode. In Python 3, strings are represented by Unicode instead of\nbytes. Further information can be found on PEP `3100 <https://www.python.org/dev/peps/pep-3100>`_\n\n**ASCII** code is the most well-known standard which defines numeric codes\nfor characters. The numeric values only define 128 characters originally,\nso ASCII only contains control codes, digits, lowercase letters, uppercase\nletters, etc. However, it is not enough for us to represent characters such as\naccented characters, Chinese characters, or emoji existed around the world.\nTherefore, **Unicode** was developed to solve this issue. It defines the\n*code point* to represent various characters like ASCII but the number of\ncharacters is up to 1,111,998.\n\nString\n------\n\nIn Python 2, strings are represented in *bytes*, not *Unicode*. Python provides\ndifferent types of string such as Unicode string, raw string, and so on.\nIn this case, if we want to declare a Unicode string, we add ``u`` prefix for\nstring literals.\n\n.. code-block:: python\n\n    >>> s = 'Café'  # byte string\n    >>> s\n    'Caf\\xc3\\xa9'\n    >>> type(s)\n    <type 'str'>\n    >>> u = u'Café' # unicode string\n    >>> u\n    u'Caf\\xe9'\n    >>> type(u)\n    <type 'unicode'>\n\nIn Python 3, strings are represented in *Unicode*. If we want to represent a\nbyte string, we add the ``b`` prefix for string literals. Note that the early\nPython versions (3.0-3.2) do not support the ``u`` prefix. In order to ease\nthe pain to migrate Unicode aware applications from Python 2, Python 3.3 once\nagain supports the ``u`` prefix for string literals. Further information can\nbe found on PEP `414 <https://www.python.org/dev/peps/pep-0414>`_\n\n.. code-block:: python\n\n    >>> s = 'Café'\n    >>> type(s)\n    <class 'str'>\n    >>> s\n    'Café'\n    >>> s.encode('utf-8')\n    b'Caf\\xc3\\xa9'\n    >>> s.encode('utf-8').decode('utf-8')\n    'Café'\n\nCharacters\n----------\n\nPython 2 takes all string characters as bytes. In this case, the length of\nstrings may be not equivalent to the number of characters. For example,\nthe length of ``Café`` is 5, not 4 because ``é`` is encoded as a 2 bytes\ncharacter.\n\n.. code-block:: python\n\n    >>> s= 'Café'\n    >>> print([_c for _c in s])\n    ['C', 'a', 'f', '\\xc3', '\\xa9']\n    >>> len(s)\n    5\n    >>> s = u'Café'\n    >>> print([_c for _c in s])\n    [u'C', u'a', u'f', u'\\xe9']\n    >>> len(s)\n    4\n\nPython 3 takes all string characters as Unicode code point. The lenght of\na string is always equivalent to the number of characters.\n\n.. code-block:: python\n\n    >>> s = 'Café'\n    >>> print([_c for _c in s])\n    ['C', 'a', 'f', 'é']\n    >>> len(s)\n    4\n    >>> bs = bytes(s, encoding='utf-8')\n    >>> print(bs)\n    b'Caf\\xc3\\xa9'\n    >>> len(bs)\n    5\n\nPorting unicode(s, 'utf-8')\n---------------------------\n\nThe `unicode() <https://docs.python.org/2.7/library/functions.html#unicode>`_\nbuilt-in function was removed in Python 3 so what is the best way to convert\nthe expression ``unicode(s, 'utf-8')`` so it works in both Python 2 and 3?\n\nIn Python 2:\n\n.. code-block:: python\n\n    >>> s = 'Café'\n    >>> unicode(s, 'utf-8')\n    u'Caf\\xe9'\n    >>> s.decode('utf-8')\n    u'Caf\\xe9'\n    >>> unicode(s, 'utf-8') == s.decode('utf-8')\n    True\n\nIn Python 3:\n\n.. code-block:: python\n\n    >>> s = 'Café'\n    >>> s.decode('utf-8')\n    AttributeError: 'str' object has no attribute 'decode'\n\nSo, the real answer is...\n\nUnicode Code Point\n------------------\n\n`ord <https://docs.python.org/3/library/functions.html#ord>`_ is a powerful\nbuilt-in function to get a Unicode code point from a given character.\nConsequently, If we want to check a Unicode code point of a character, we can\nuse ``ord``.\n\n.. code-block:: python\n\n    >>> s = u'Café'\n    >>> for _c in s: print('U+%04x' % ord(_c))\n    ...\n    U+0043\n    U+0061\n    U+0066\n    U+00e9\n    >>> u = '中文'\n    >>> for _c in u: print('U+%04x' % ord(_c))\n    ...\n    U+4e2d\n    U+6587\n\n\nEncoding\n--------\n\nA *Unicode code point* transfers to a *byte string* is called encoding.\n\n.. code-block:: python\n\n    >>> s = u'Café'\n    >>> type(s.encode('utf-8'))\n    <class 'bytes'>\n\nDecoding\n---------\n\nA *byte string* transfers to a *Unicode code point* is called decoding.\n\n.. code-block:: python\n\n    >>> s = bytes('Café', encoding='utf-8')\n    >>> s.decode('utf-8')\n    'Café'\n\nUnicode Normalization\n---------------------\n\nSome characters can be represented in two similar form. For example, the\ncharacter, ``é`` can be written as ``e ́`` (Canonical Decomposition) or ``é``\n(Canonical Composition). In this case, we may acquire unexpected results when we\nare comparing two strings even though they look alike. Therefore, we can\nnormalize a Unicode form to solve the issue.\n\n.. code-block:: python\n\n    # python 3\n    >>> u1 = 'Café'       # unicode string\n    >>> u2 = 'Cafe\\u0301'\n    >>> u1, u2\n    ('Café', 'Café')\n    >>> len(u1), len(u2)\n    (4, 5)\n    >>> u1 == u2\n    False\n    >>> u1.encode('utf-8') # get u1 byte string\n    b'Caf\\xc3\\xa9'\n    >>> u2.encode('utf-8') # get u2 byte string\n    b'Cafe\\xcc\\x81'\n    >>> from unicodedata import normalize\n    >>> s1 = normalize('NFC', u1)  # get u1 NFC format\n    >>> s2 = normalize('NFC', u2)  # get u2 NFC format\n    >>> s1 == s2\n    True\n    >>> s1.encode('utf-8'), s2.encode('utf-8')\n    (b'Caf\\xc3\\xa9', b'Caf\\xc3\\xa9')\n    >>> s1 = normalize('NFD', u1)  # get u1 NFD format\n    >>> s2 = normalize('NFD', u2)  # get u2 NFD format\n    >>> s1, s2\n    ('Café', 'Café')\n    >>> s1 == s2\n    True\n    >>> s1.encode('utf-8'), s2.encode('utf-8')\n    (b'Cafe\\xcc\\x81', b'Cafe\\xcc\\x81')\n\n\nAvoid ``UnicodeDecodeError``\n----------------------------\n\nPython raises `UnicodeDecodeError` when byte strings cannot decode to Unicode\ncode points. If we want to avoid this exception, we can pass *replace*,\n*backslashreplace*, or *ignore* to errors argument in `decode <https://docs.python.org/3/library/stdtypes.html#bytes.decode>`_.\n\n.. code-block:: python\n\n    >>> u = b\"\\xff\"\n    >>> u.decode('utf-8', 'strict')\n        Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n    UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte\n    >>> # use U+FFFD, REPLACEMENT CHARACTER\n    >>> u.decode('utf-8', \"replace\")\n    '\\ufffd'\n    >>> # inserts a \\xNN escape sequence\n    >>> u.decode('utf-8', \"backslashreplace\")\n    '\\\\xff'\n    >>> # leave the character out of the Unicode result\n    >>> u.decode('utf-8', \"ignore\")\n    ''\n\nLong String\n-----------\n\nThe following snippet shows common ways to declare a multi-line string in\nPython.\n\n.. code-block:: python\n\n    # original long string\n    s = 'This is a very very very long python string'\n\n    # Single quote with an escaping backslash\n    s = \"This is a very very very \" \\\n        \"long python string\"\n\n    # Using brackets\n    s = (\n        \"This is a very very very \"\n        \"long python string\"\n    )\n\n    # Using ``+``\n    s = (\n        \"This is a very very very \" +\n        \"long python string\"\n    )\n\n    # Using triple-quote with an escaping backslash\n    s = '''This is a very very very \\\n    long python string'''\n"
  },
  {
    "path": "docs/notes/concurrency/index.rst",
    "content": ".. meta::\n    :description lang=en: Python concurrency tutorial covering threading, multiprocessing, locks, semaphores, queues, process pools, and concurrent.futures\n    :keywords: Python, Python3, threading, multiprocessing, concurrency, parallel, lock, semaphore, queue, ThreadPoolExecutor, ProcessPoolExecutor, GIL\n\nConcurrency\n===========\n\nPython provides multiple approaches for concurrent execution to handle CPU-bound\nand I/O-bound tasks efficiently. The ``threading`` module enables lightweight\nconcurrent execution within a single process, while ``multiprocessing`` bypasses\nthe Global Interpreter Lock (GIL) by using separate processes for true parallelism.\nThe ``concurrent.futures`` module offers a high-level interface that abstracts\nthe differences between threads and processes behind a unified API.\n\nUnderstanding when to use each approach is crucial: threads excel at I/O-bound\ntasks (network requests, file operations) where the GIL is released during\nwaiting, while processes are better for CPU-bound tasks (computation, data\nprocessing) where true parallel execution is needed.\n\n.. toctree::\n   :maxdepth: 1\n\n   python-threading\n   python-multiprocessing\n   python-futures\n"
  },
  {
    "path": "docs/notes/concurrency/python-futures.rst",
    "content": ".. meta::\n    :description lang=en: Python concurrent.futures tutorial covering ThreadPoolExecutor, ProcessPoolExecutor, Future objects, callbacks, and high-level parallel execution patterns\n    :keywords: Python, Python3, concurrent.futures, ThreadPoolExecutor, ProcessPoolExecutor, Future, executor, submit, map, as_completed, parallel\n\n==================\nconcurrent.futures\n==================\n\n:Source: `src/basic/concurrency_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/concurrency_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nThe ``concurrent.futures`` module provides a high-level interface for\nasynchronously executing callables using threads or processes. It abstracts\nthe differences between threading and multiprocessing behind a unified API,\nmaking it easy to switch between them. The module introduces two key concepts:\n**Executors** that manage pools of workers, and **Futures** that represent\nthe eventual result of an asynchronous operation.\n\nThreadPoolExecutor Basics\n-------------------------\n\n``ThreadPoolExecutor`` manages a pool of threads that execute tasks concurrently.\nUse it for I/O-bound tasks like network requests, file operations, or database\nqueries where threads spend time waiting for external resources.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor\n    import time\n\n    def fetch_url(url):\n        \"\"\"Simulate fetching a URL.\"\"\"\n        time.sleep(1)  # Simulate network delay\n        return f\"Content from {url}\"\n\n    urls = [\"http://site1.com\", \"http://site2.com\", \"http://site3.com\"]\n\n    # Sequential - takes ~3 seconds\n    start = time.time()\n    results = [fetch_url(url) for url in urls]\n    print(f\"Sequential: {time.time() - start:.2f}s\")\n\n    # Concurrent - takes ~1 second\n    start = time.time()\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        results = list(executor.map(fetch_url, urls))\n    print(f\"Concurrent: {time.time() - start:.2f}s\")\n\nProcessPoolExecutor Basics\n--------------------------\n\n``ProcessPoolExecutor`` manages a pool of processes for true parallel execution.\nUse it for CPU-bound tasks like data processing, calculations, or image\nmanipulation where you need to utilize multiple CPU cores.\n\n.. code-block:: python\n\n    from concurrent.futures import ProcessPoolExecutor\n    import time\n\n    def cpu_intensive(n):\n        \"\"\"CPU-bound computation.\"\"\"\n        return sum(i * i for i in range(n))\n\n    if __name__ == \"__main__\":\n        numbers = [10**7] * 4\n\n        # Sequential\n        start = time.time()\n        results = [cpu_intensive(n) for n in numbers]\n        print(f\"Sequential: {time.time() - start:.2f}s\")\n\n        # Parallel with processes\n        start = time.time()\n        with ProcessPoolExecutor(max_workers=4) as executor:\n            results = list(executor.map(cpu_intensive, numbers))\n        print(f\"Parallel: {time.time() - start:.2f}s\")\n\nUsing submit() and Future Objects\n---------------------------------\n\nThe ``submit()`` method schedules a callable and returns a ``Future`` object\nimmediately. The Future represents the pending result and provides methods to\ncheck status, get the result, or cancel the task. This gives more control than\n``map()`` for handling individual tasks.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor\n    import time\n\n    def task(name, duration):\n        time.sleep(duration)\n        return f\"{name} completed in {duration}s\"\n\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        # Submit tasks - returns Future immediately\n        future1 = executor.submit(task, \"Task A\", 2)\n        future2 = executor.submit(task, \"Task B\", 1)\n        future3 = executor.submit(task, \"Task C\", 3)\n\n        # Check if done (non-blocking)\n        print(f\"Task A done: {future1.done()}\")\n\n        # Get result (blocking)\n        print(future2.result())  # Waits for completion\n        print(future1.result())\n        print(future3.result())\n\nProcessing Results as They Complete\n-----------------------------------\n\n``as_completed()`` yields futures as they complete, regardless of submission\norder. This is useful when you want to process results as soon as they're\navailable rather than waiting for all tasks to finish.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor, as_completed\n    import time\n    import random\n\n    def fetch_data(source_id):\n        delay = random.uniform(0.5, 2.0)\n        time.sleep(delay)\n        return f\"Data from source {source_id} (took {delay:.2f}s)\"\n\n    sources = range(5)\n\n    with ThreadPoolExecutor(max_workers=5) as executor:\n        # Submit all tasks\n        future_to_source = {\n            executor.submit(fetch_data, src): src\n            for src in sources\n        }\n\n        # Process results as they complete\n        for future in as_completed(future_to_source):\n            source = future_to_source[future]\n            try:\n                result = future.result()\n                print(f\"Source {source}: {result}\")\n            except Exception as e:\n                print(f\"Source {source} failed: {e}\")\n\nUsing wait() for Completion Control\n-----------------------------------\n\n``wait()`` blocks until specified futures complete. You can wait for all tasks,\nthe first task, or the first exception. This provides fine-grained control\nover when to proceed.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED, ALL_COMPLETED\n    import time\n\n    def task(task_id, duration):\n        time.sleep(duration)\n        return f\"Task {task_id} done\"\n\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        futures = [\n            executor.submit(task, 1, 3),\n            executor.submit(task, 2, 1),\n            executor.submit(task, 3, 2),\n        ]\n\n        # Wait for first to complete\n        done, not_done = wait(futures, return_when=FIRST_COMPLETED)\n        print(f\"First completed: {done.pop().result()}\")\n        print(f\"Still running: {len(not_done)}\")\n\n        # Wait for all remaining\n        done, not_done = wait(not_done, return_when=ALL_COMPLETED)\n        for f in done:\n            print(f\"Completed: {f.result()}\")\n\nAdding Callbacks to Futures\n---------------------------\n\nCallbacks are functions that execute automatically when a future completes.\nThey're useful for processing results without blocking the main thread or\nfor chaining operations. The callback receives the future as its argument.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor\n    import time\n\n    def compute(n):\n        time.sleep(1)\n        return n * n\n\n    def on_complete(future):\n        \"\"\"Callback executed when future completes.\"\"\"\n        try:\n            result = future.result()\n            print(f\"Callback: result is {result}\")\n        except Exception as e:\n            print(f\"Callback: task failed with {e}\")\n\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        for i in range(5):\n            future = executor.submit(compute, i)\n            future.add_done_callback(on_complete)\n\n        # Main thread continues while callbacks fire\n        print(\"Main thread: tasks submitted\")\n        time.sleep(2)\n        print(\"Main thread: done waiting\")\n\nException Handling\n------------------\n\nExceptions raised in tasks are captured and re-raised when you call\n``result()``. You can also check for exceptions using ``exception()``.\nAlways handle exceptions to prevent silent failures.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor, as_completed\n\n    def risky_task(n):\n        if n == 3:\n            raise ValueError(f\"Bad value: {n}\")\n        return n * 2\n\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        futures = {executor.submit(risky_task, i): i for i in range(5)}\n\n        for future in as_completed(futures):\n            n = futures[future]\n            try:\n                result = future.result()\n                print(f\"Task {n}: {result}\")\n            except ValueError as e:\n                print(f\"Task {n} failed: {e}\")\n\n        # Alternative: check exception without raising\n        future = executor.submit(risky_task, 3)\n        future.result()  # Wait for completion\n        if future.exception() is not None:\n            print(f\"Exception occurred: {future.exception()}\")\n\nTimeout Handling\n----------------\n\nBoth ``result()`` and ``as_completed()`` accept timeout parameters. If a task\ndoesn't complete within the timeout, a ``TimeoutError`` is raised. This\nprevents indefinite blocking on slow or stuck tasks.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor, TimeoutError, as_completed\n    import time\n\n    def slow_task(duration):\n        time.sleep(duration)\n        return f\"Completed after {duration}s\"\n\n    with ThreadPoolExecutor(max_workers=2) as executor:\n        future = executor.submit(slow_task, 5)\n\n        try:\n            # Wait max 2 seconds for result\n            result = future.result(timeout=2)\n            print(result)\n        except TimeoutError:\n            print(\"Task timed out!\")\n            # Note: task continues running in background\n\n        # Timeout with as_completed\n        futures = [executor.submit(slow_task, i) for i in [1, 3, 5]]\n        try:\n            for future in as_completed(futures, timeout=2):\n                print(future.result())\n        except TimeoutError:\n            print(\"Some tasks didn't complete in time\")\n\nCancelling Tasks\n----------------\n\nTasks can be cancelled before they start executing using ``cancel()``. Once\na task has started, it cannot be cancelled. Check ``cancelled()`` to see if\ncancellation succeeded.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor\n    import time\n\n    def long_task(n):\n        time.sleep(2)\n        return n\n\n    with ThreadPoolExecutor(max_workers=1) as executor:\n        # Submit multiple tasks to single worker\n        future1 = executor.submit(long_task, 1)\n        future2 = executor.submit(long_task, 2)  # Queued, not started\n        future3 = executor.submit(long_task, 3)  # Queued, not started\n\n        time.sleep(0.1)  # Let first task start\n\n        # Try to cancel queued tasks\n        cancelled2 = future2.cancel()\n        cancelled3 = future3.cancel()\n\n        print(f\"Future 2 cancelled: {cancelled2}\")  # True\n        print(f\"Future 3 cancelled: {cancelled3}\")  # True\n        print(f\"Future 1 cancelled: {future1.cancel()}\")  # False (already running)\n\nExecutor Context Manager\n------------------------\n\nUsing executors as context managers (``with`` statement) ensures proper cleanup.\nWhen exiting the context, ``shutdown(wait=True)`` is called automatically,\nwhich waits for all pending tasks to complete before returning.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor\n    import time\n\n    def task(n):\n        time.sleep(1)\n        return n * 2\n\n    # Context manager - automatic cleanup\n    with ThreadPoolExecutor(max_workers=3) as executor:\n        futures = [executor.submit(task, i) for i in range(5)]\n        # Executor waits for all tasks when exiting 'with' block\n\n    print(\"All tasks completed\")\n\n    # Manual management (not recommended)\n    executor = ThreadPoolExecutor(max_workers=3)\n    try:\n        futures = [executor.submit(task, i) for i in range(5)]\n    finally:\n        executor.shutdown(wait=True)  # Must call explicitly\n\nMap with Chunking\n-----------------\n\nFor large iterables, ``map()`` can be more efficient with chunking. The\n``chunksize`` parameter groups items together, reducing overhead from\ninter-process communication when using ``ProcessPoolExecutor``.\n\n.. code-block:: python\n\n    from concurrent.futures import ProcessPoolExecutor\n    import time\n\n    def process_item(x):\n        return x * x\n\n    if __name__ == \"__main__\":\n        items = range(100000)\n\n        # Without chunking - more IPC overhead\n        start = time.time()\n        with ProcessPoolExecutor(max_workers=4) as executor:\n            results = list(executor.map(process_item, items))\n        print(f\"No chunking: {time.time() - start:.2f}s\")\n\n        # With chunking - less IPC overhead\n        start = time.time()\n        with ProcessPoolExecutor(max_workers=4) as executor:\n            results = list(executor.map(process_item, items, chunksize=1000))\n        print(f\"With chunking: {time.time() - start:.2f}s\")\n\nReal-World Example: Parallel Downloads\n--------------------------------------\n\nThis example demonstrates a practical use case: downloading multiple files\nconcurrently with progress tracking, error handling, and timeout management.\n\n.. code-block:: python\n\n    from concurrent.futures import ThreadPoolExecutor, as_completed\n    import urllib.request\n    import time\n\n    def download(url, timeout=10):\n        \"\"\"Download URL content with timeout.\"\"\"\n        try:\n            with urllib.request.urlopen(url, timeout=timeout) as response:\n                content = response.read()\n                return url, len(content), None\n        except Exception as e:\n            return url, 0, str(e)\n\n    urls = [\n        \"https://www.python.org\",\n        \"https://www.github.com\",\n        \"https://www.google.com\",\n        \"https://httpbin.org/delay/5\",  # Slow endpoint\n    ]\n\n    print(\"Starting downloads...\")\n    start = time.time()\n\n    with ThreadPoolExecutor(max_workers=4) as executor:\n        future_to_url = {executor.submit(download, url): url for url in urls}\n\n        for future in as_completed(future_to_url, timeout=15):\n            url, size, error = future.result()\n            if error:\n                print(f\"FAILED: {url} - {error}\")\n            else:\n                print(f\"OK: {url} - {size} bytes\")\n\n    print(f\"Total time: {time.time() - start:.2f}s\")\n"
  },
  {
    "path": "docs/notes/concurrency/python-multiprocessing.rst",
    "content": ".. meta::\n    :description lang=en: Python multiprocessing tutorial covering process creation, pools, shared memory, inter-process communication, and parallel CPU-bound task execution\n    :keywords: Python, Python3, multiprocessing, Process, Pool, Queue, Pipe, shared memory, parallel, CPU-bound, GIL bypass\n\n===============\nMultiprocessing\n===============\n\n:Source: `src/basic/concurrency_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/concurrency_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nThe ``multiprocessing`` module enables true parallel execution by spawning\nseparate Python processes, each with its own Python interpreter and memory\nspace. Unlike threads, processes bypass the Global Interpreter Lock (GIL),\nmaking multiprocessing ideal for CPU-bound tasks that need to utilize multiple\nCPU cores. The trade-off is higher overhead for process creation and\ninter-process communication compared to threads.\n\nCreating Processes\n------------------\n\nCreating processes is similar to creating threads. Each process runs in its\nown memory space, so changes to variables in one process don't affect others.\nUse ``start()`` to begin execution and ``join()`` to wait for completion.\n\n.. code-block:: python\n\n    from multiprocessing import Process\n    import os\n\n    def worker(name):\n        print(f\"Worker {name}, PID: {os.getpid()}\")\n\n    if __name__ == \"__main__\":\n        processes = []\n        for i in range(4):\n            p = Process(target=worker, args=(i,))\n            processes.append(p)\n            p.start()\n\n        for p in processes:\n            p.join()\n\n        print(f\"Main process PID: {os.getpid()}\")\n\nProcess Pool\n------------\n\nA ``Pool`` manages a collection of worker processes and distributes tasks among\nthem. This is more efficient than creating a new process for each task, as\nprocesses are reused. The pool provides methods like ``map()``, ``apply()``,\nand their async variants for different use cases.\n\n.. code-block:: python\n\n    from multiprocessing import Pool\n    import time\n\n    def cpu_intensive(n):\n        \"\"\"Simulate CPU-bound work.\"\"\"\n        total = 0\n        for i in range(n):\n            total += i * i\n        return total\n\n    if __name__ == \"__main__\":\n        numbers = [10**6, 10**6, 10**6, 10**6]\n\n        # Sequential execution\n        start = time.time()\n        results = [cpu_intensive(n) for n in numbers]\n        print(f\"Sequential: {time.time() - start:.2f}s\")\n\n        # Parallel execution with Pool\n        start = time.time()\n        with Pool(4) as pool:\n            results = pool.map(cpu_intensive, numbers)\n        print(f\"Parallel: {time.time() - start:.2f}s\")\n\nPool Methods\n------------\n\nThe Pool class provides several methods for distributing work. ``map()`` applies\na function to each item in an iterable and returns results in order. ``apply()``\ncalls a function with arguments and blocks until complete. The ``_async``\nvariants return immediately with an ``AsyncResult`` object.\n\n.. code-block:: python\n\n    from multiprocessing import Pool\n\n    def square(x):\n        return x * x\n\n    def add(a, b):\n        return a + b\n\n    if __name__ == \"__main__\":\n        with Pool(4) as pool:\n            # map - apply function to iterable\n            results = pool.map(square, range(10))\n            print(f\"map: {results}\")\n\n            # starmap - unpack arguments from iterable\n            pairs = [(1, 2), (3, 4), (5, 6)]\n            results = pool.starmap(add, pairs)\n            print(f\"starmap: {results}\")\n\n            # apply_async - non-blocking single call\n            result = pool.apply_async(square, (10,))\n            print(f\"apply_async: {result.get()}\")\n\n            # map_async - non-blocking map\n            result = pool.map_async(square, range(5))\n            print(f\"map_async: {result.get()}\")\n\nSharing Data with Queue\n-----------------------\n\nProcesses don't share memory by default. ``multiprocessing.Queue`` provides a\nthread and process-safe way to exchange data between processes. It's the\nrecommended approach for most inter-process communication scenarios.\n\n.. code-block:: python\n\n    from multiprocessing import Process, Queue\n\n    def producer(q, items):\n        for item in items:\n            q.put(item)\n            print(f\"Produced: {item}\")\n        q.put(None)  # Sentinel\n\n    def consumer(q):\n        while True:\n            item = q.get()\n            if item is None:\n                break\n            print(f\"Consumed: {item}\")\n\n    if __name__ == \"__main__\":\n        q = Queue()\n        items = list(range(5))\n\n        p1 = Process(target=producer, args=(q, items))\n        p2 = Process(target=consumer, args=(q,))\n\n        p1.start()\n        p2.start()\n        p1.join()\n        p2.join()\n\nSharing Data with Pipe\n----------------------\n\nA ``Pipe`` creates a two-way communication channel between two processes. It's\nsimpler and faster than Queue for point-to-point communication but only\nsupports two endpoints. Each end can send and receive data.\n\n.. code-block:: python\n\n    from multiprocessing import Process, Pipe\n\n    def sender(conn):\n        conn.send(\"Hello from sender\")\n        conn.send([1, 2, 3])\n        response = conn.recv()\n        print(f\"Sender received: {response}\")\n        conn.close()\n\n    def receiver(conn):\n        msg = conn.recv()\n        print(f\"Receiver got: {msg}\")\n        data = conn.recv()\n        print(f\"Receiver got: {data}\")\n        conn.send(\"Thanks!\")\n        conn.close()\n\n    if __name__ == \"__main__\":\n        parent_conn, child_conn = Pipe()\n\n        p1 = Process(target=sender, args=(parent_conn,))\n        p2 = Process(target=receiver, args=(child_conn,))\n\n        p1.start()\n        p2.start()\n        p1.join()\n        p2.join()\n\nShared Memory with Value and Array\n----------------------------------\n\nFor simple shared state, ``Value`` and ``Array`` provide shared memory that\nmultiple processes can access. These are faster than Queue/Pipe for frequently\naccessed data but require careful synchronization to avoid race conditions.\n\n.. code-block:: python\n\n    from multiprocessing import Process, Value, Array\n\n    def increment(counter, lock_needed=True):\n        for _ in range(10000):\n            with counter.get_lock():\n                counter.value += 1\n\n    def modify_array(arr):\n        for i in range(len(arr)):\n            arr[i] = arr[i] * 2\n\n    if __name__ == \"__main__\":\n        # Shared integer\n        counter = Value('i', 0)  # 'i' = signed int\n        processes = [Process(target=increment, args=(counter,)) for _ in range(4)]\n        for p in processes:\n            p.start()\n        for p in processes:\n            p.join()\n        print(f\"Counter: {counter.value}\")  # 40000\n\n        # Shared array\n        arr = Array('d', [1.0, 2.0, 3.0, 4.0])  # 'd' = double\n        p = Process(target=modify_array, args=(arr,))\n        p.start()\n        p.join()\n        print(f\"Array: {list(arr)}\")  # [2.0, 4.0, 6.0, 8.0]\n\nManager for Complex Shared Objects\n----------------------------------\n\nA ``Manager`` provides a way to share more complex Python objects (lists, dicts)\nbetween processes. The manager runs a server process that holds the actual\nobjects, and other processes access them through proxies. This is slower than\nValue/Array but supports arbitrary Python objects.\n\n.. code-block:: python\n\n    from multiprocessing import Process, Manager\n\n    def worker(shared_dict, shared_list, worker_id):\n        shared_dict[worker_id] = worker_id * 10\n        shared_list.append(worker_id)\n\n    if __name__ == \"__main__\":\n        with Manager() as manager:\n            shared_dict = manager.dict()\n            shared_list = manager.list()\n\n            processes = []\n            for i in range(4):\n                p = Process(target=worker, args=(shared_dict, shared_list, i))\n                processes.append(p)\n                p.start()\n\n            for p in processes:\n                p.join()\n\n            print(f\"Dict: {dict(shared_dict)}\")\n            print(f\"List: {list(shared_list)}\")\n\nProcess Synchronization\n-----------------------\n\nMultiprocessing provides the same synchronization primitives as threading:\n``Lock``, ``RLock``, ``Semaphore``, ``Event``, ``Condition``, and ``Barrier``.\nThese work across processes instead of threads.\n\n.. code-block:: python\n\n    from multiprocessing import Process, Lock, Value\n\n    def safe_increment(counter, lock):\n        for _ in range(10000):\n            with lock:\n                counter.value += 1\n\n    if __name__ == \"__main__\":\n        lock = Lock()\n        counter = Value('i', 0)\n\n        processes = [\n            Process(target=safe_increment, args=(counter, lock))\n            for _ in range(4)\n        ]\n\n        for p in processes:\n            p.start()\n        for p in processes:\n            p.join()\n\n        print(f\"Counter: {counter.value}\")  # 40000\n\nDaemon Processes\n----------------\n\nLike daemon threads, daemon processes are terminated when the main process\nexits. They're useful for background tasks that shouldn't prevent program\ntermination. Set ``daemon=True`` before calling ``start()``.\n\n.. code-block:: python\n\n    from multiprocessing import Process\n    import time\n\n    def background_task():\n        while True:\n            print(\"Background process running...\")\n            time.sleep(1)\n\n    if __name__ == \"__main__\":\n        p = Process(target=background_task, daemon=True)\n        p.start()\n\n        time.sleep(3)\n        print(\"Main process exiting, daemon will be terminated\")\n\nHandling Process Termination\n----------------------------\n\nProcesses can be terminated gracefully using ``terminate()`` or forcefully\nusing ``kill()``. Always clean up resources properly and consider using\nsignals for graceful shutdown in production code.\n\n.. code-block:: python\n\n    from multiprocessing import Process\n    import time\n    import signal\n\n    def long_running_task():\n        try:\n            while True:\n                print(\"Working...\")\n                time.sleep(1)\n        except KeyboardInterrupt:\n            print(\"Graceful shutdown\")\n\n    if __name__ == \"__main__\":\n        p = Process(target=long_running_task)\n        p.start()\n\n        time.sleep(3)\n\n        # Graceful termination (SIGTERM)\n        p.terminate()\n        p.join(timeout=2)\n\n        # Force kill if still alive\n        if p.is_alive():\n            p.kill()\n            p.join()\n\n        print(f\"Exit code: {p.exitcode}\")\n\nProcessPoolExecutor\n-------------------\n\n``concurrent.futures.ProcessPoolExecutor`` provides a higher-level interface\nfor process pools that's consistent with ``ThreadPoolExecutor``. It's often\neasier to use than ``multiprocessing.Pool`` and integrates well with the\nfutures pattern.\n\n.. code-block:: python\n\n    from concurrent.futures import ProcessPoolExecutor, as_completed\n\n    def compute(n):\n        return sum(i * i for i in range(n))\n\n    if __name__ == \"__main__\":\n        numbers = [10**6, 10**6, 10**6, 10**6]\n\n        with ProcessPoolExecutor(max_workers=4) as executor:\n            # Submit individual tasks\n            futures = [executor.submit(compute, n) for n in numbers]\n\n            # Process results as they complete\n            for future in as_completed(futures):\n                print(f\"Result: {future.result()}\")\n\n            # Or use map for ordered results\n            results = list(executor.map(compute, numbers))\n            print(f\"All results: {results}\")\n\nComparing Threads vs Processes\n------------------------------\n\nChoose threads for I/O-bound tasks (network, file I/O) where the GIL is\nreleased during waiting. Choose processes for CPU-bound tasks that need true\nparallelism. This example demonstrates the performance difference.\n\n.. code-block:: python\n\n    from threading import Thread\n    from multiprocessing import Process, Pool\n    import time\n\n    def cpu_bound(n):\n        \"\"\"CPU-intensive task.\"\"\"\n        return sum(i * i for i in range(n))\n\n    if __name__ == \"__main__\":\n        n = 10**7\n        count = 4\n\n        # Sequential\n        start = time.time()\n        for _ in range(count):\n            cpu_bound(n)\n        print(f\"Sequential: {time.time() - start:.2f}s\")\n\n        # Threads (limited by GIL)\n        start = time.time()\n        threads = [Thread(target=cpu_bound, args=(n,)) for _ in range(count)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n        print(f\"Threads: {time.time() - start:.2f}s\")\n\n        # Processes (true parallelism)\n        start = time.time()\n        with Pool(count) as pool:\n            pool.map(cpu_bound, [n] * count)\n        print(f\"Processes: {time.time() - start:.2f}s\")\n"
  },
  {
    "path": "docs/notes/concurrency/python-threading.rst",
    "content": ".. meta::\n    :description lang=en: Python threading tutorial covering thread creation, synchronization primitives, locks, semaphores, events, conditions, and thread-safe data structures\n    :keywords: Python, Python3, threading, Thread, Lock, RLock, Semaphore, Event, Condition, synchronization, GIL, concurrent, parallel\n\n=========\nThreading\n=========\n\n:Source: `src/basic/concurrency_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/concurrency_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nThe ``threading`` module provides a high-level interface for creating and\nmanaging threads in Python. Threads are lightweight units of execution that\nshare the same memory space within a process, making them efficient for I/O-bound\ntasks where the program spends time waiting for external resources. However,\ndue to Python's Global Interpreter Lock (GIL), threads cannot achieve true\nparallelism for CPU-bound tasks—only one thread can execute Python bytecode\nat a time. For CPU-intensive work, consider using ``multiprocessing`` instead.\n\nCreating Threads\n----------------\n\nThere are two primary ways to create threads: subclassing ``Thread`` or passing\na target function. The function-based approach is more flexible and commonly\nused, while subclassing is useful when you need to encapsulate thread state\nand behavior in a class.\n\n.. code-block:: python\n\n    from threading import Thread\n\n    # Method 1: Subclass Thread\n    class Worker(Thread):\n        def __init__(self, worker_id):\n            super().__init__()\n            self.worker_id = worker_id\n\n        def run(self):\n            print(f\"Worker {self.worker_id} running\")\n\n    # Method 2: Pass target function (preferred)\n    def task(worker_id):\n        print(f\"Task {worker_id} running\")\n\n    # Using subclass\n    t1 = Worker(1)\n    t1.start()\n    t1.join()\n\n    # Using target function\n    t2 = Thread(target=task, args=(2,))\n    t2.start()\n    t2.join()\n\nThread with Return Value\n------------------------\n\nThreads don't directly return values from their target functions. To get results\nback, you can use shared mutable objects, queues, or store results as instance\nattributes when subclassing Thread.\n\n.. code-block:: python\n\n    from threading import Thread\n    from queue import Queue\n\n    def compute(n, results):\n        \"\"\"Store result in shared dict.\"\"\"\n        results[n] = n * n\n\n    # Using shared dictionary\n    results = {}\n    threads = []\n    for i in range(5):\n        t = Thread(target=compute, args=(i, results))\n        threads.append(t)\n        t.start()\n\n    for t in threads:\n        t.join()\n\n    print(results)  # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}\n\n    # Using Queue (thread-safe)\n    def compute_queue(n, q):\n        q.put((n, n * n))\n\n    q = Queue()\n    threads = []\n    for i in range(5):\n        t = Thread(target=compute_queue, args=(i, q))\n        threads.append(t)\n        t.start()\n\n    for t in threads:\n        t.join()\n\n    while not q.empty():\n        n, result = q.get()\n        print(f\"{n}: {result}\")\n\nDaemon Threads\n--------------\n\nDaemon threads run in the background and are automatically terminated when all\nnon-daemon threads have finished. They're useful for background tasks that\nshouldn't prevent the program from exiting, such as monitoring or cleanup tasks.\n\n.. code-block:: python\n\n    from threading import Thread\n    import time\n\n    def background_task():\n        while True:\n            print(\"Background task running...\")\n            time.sleep(1)\n\n    # Daemon thread - won't prevent program exit\n    t = Thread(target=background_task, daemon=True)\n    t.start()\n\n    # Main thread work\n    time.sleep(3)\n    print(\"Main thread done, daemon will be killed\")\n\nLock - Mutual Exclusion\n-----------------------\n\nA ``Lock`` is the simplest synchronization primitive that prevents multiple\nthreads from accessing a shared resource simultaneously. Always use locks when\nmodifying shared state to prevent race conditions. The context manager syntax\n(``with lock:``) is preferred as it guarantees the lock is released even if\nan exception occurs.\n\n.. code-block:: python\n\n    from threading import Thread, Lock\n\n    counter = 0\n    lock = Lock()\n\n    def increment(n):\n        global counter\n        for _ in range(n):\n            with lock:  # Acquire and release automatically\n                counter += 1\n\n    threads = [Thread(target=increment, args=(100000,)) for _ in range(5)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\n    print(f\"Counter: {counter}\")  # Always 500000 with lock\n\nRLock - Reentrant Lock\n----------------------\n\nAn ``RLock`` (reentrant lock) can be acquired multiple times by the same thread\nwithout causing a deadlock. This is essential when a thread needs to call\nmethods that also acquire the same lock, such as in recursive functions or\nwhen methods call other methods on the same object.\n\n.. code-block:: python\n\n    from threading import Thread, RLock\n\n    class Counter:\n        def __init__(self):\n            self.value = 0\n            self.lock = RLock()\n\n        def increment(self):\n            with self.lock:\n                self.value += 1\n\n        def increment_twice(self):\n            with self.lock:  # First acquisition\n                self.increment()  # Second acquisition - OK with RLock\n                self.increment()\n\n    counter = Counter()\n    threads = [Thread(target=counter.increment_twice) for _ in range(100)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\n    print(f\"Value: {counter.value}\")  # 200\n\nSemaphore - Resource Limiting\n-----------------------------\n\nA ``Semaphore`` limits the number of threads that can access a resource\nconcurrently. Unlike a lock which allows only one thread, a semaphore with\ncount N allows up to N threads to proceed. This is useful for connection pools,\nrate limiting, or controlling access to limited resources.\n\n.. code-block:: python\n\n    from threading import Thread, Semaphore\n    import time\n\n    # Allow max 3 concurrent connections\n    connection_pool = Semaphore(3)\n\n    def access_database(thread_id):\n        print(f\"Thread {thread_id} waiting for connection...\")\n        with connection_pool:\n            print(f\"Thread {thread_id} connected\")\n            time.sleep(1)  # Simulate database work\n            print(f\"Thread {thread_id} disconnected\")\n\n    threads = [Thread(target=access_database, args=(i,)) for i in range(10)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\nEvent - Thread Signaling\n------------------------\n\nAn ``Event`` is a simple signaling mechanism that allows one thread to signal\nother threads that something has happened. Threads can wait for the event to\nbe set, and one thread can set or clear the event. This is useful for\ncoordinating startup, shutdown, or state changes between threads.\n\n.. code-block:: python\n\n    from threading import Thread, Event\n    import time\n\n    ready = Event()\n\n    def worker(worker_id):\n        print(f\"Worker {worker_id} waiting for signal...\")\n        ready.wait()  # Block until event is set\n        print(f\"Worker {worker_id} starting work\")\n\n    def coordinator():\n        print(\"Coordinator preparing...\")\n        time.sleep(2)\n        print(\"Coordinator: All systems go!\")\n        ready.set()  # Signal all waiting threads\n\n    threads = [Thread(target=worker, args=(i,)) for i in range(3)]\n    threads.append(Thread(target=coordinator))\n\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\nCondition - Complex Synchronization\n-----------------------------------\n\nA ``Condition`` combines a lock with the ability to wait for and notify about\nstate changes. It's essential for producer-consumer patterns where threads\nneed to wait for specific conditions (like \"buffer not empty\" or \"buffer not\nfull\") before proceeding.\n\n.. code-block:: python\n\n    from threading import Thread, Condition\n    import time\n\n    items = []\n    condition = Condition()\n\n    def producer():\n        for i in range(5):\n            time.sleep(0.5)\n            with condition:\n                items.append(i)\n                print(f\"Produced: {i}\")\n                condition.notify()  # Wake up one waiting consumer\n\n    def consumer():\n        while True:\n            with condition:\n                while not items:  # Wait until items available\n                    condition.wait()\n                item = items.pop(0)\n                print(f\"Consumed: {item}\")\n                if item == 4:\n                    break\n\n    t1 = Thread(target=producer)\n    t2 = Thread(target=consumer)\n    t1.start()\n    t2.start()\n    t1.join()\n    t2.join()\n\nBarrier - Synchronization Point\n-------------------------------\n\nA ``Barrier`` blocks a specified number of threads until all of them have\nreached the barrier point, then releases them all simultaneously. This is\nuseful when you need multiple threads to complete a phase before any can\nproceed to the next phase.\n\n.. code-block:: python\n\n    from threading import Thread, Barrier\n    import time\n    import random\n\n    barrier = Barrier(3)\n\n    def worker(worker_id):\n        # Phase 1: Initialization\n        print(f\"Worker {worker_id} initializing...\")\n        time.sleep(random.uniform(0.5, 2))\n        print(f\"Worker {worker_id} waiting at barrier\")\n\n        barrier.wait()  # Wait for all threads\n\n        # Phase 2: All threads proceed together\n        print(f\"Worker {worker_id} proceeding\")\n\n    threads = [Thread(target=worker, args=(i,)) for i in range(3)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\nTimer - Delayed Execution\n-------------------------\n\nA ``Timer`` is a thread that executes a function after a specified delay.\nIt can be cancelled before it fires. This is useful for timeouts, delayed\ncleanup, or scheduling one-time tasks.\n\n.. code-block:: python\n\n    from threading import Timer\n\n    def delayed_task():\n        print(\"Task executed after delay\")\n\n    # Execute after 2 seconds\n    timer = Timer(2.0, delayed_task)\n    timer.start()\n\n    # Can be cancelled before it fires\n    # timer.cancel()\n\nThread-Local Data\n-----------------\n\n``threading.local()`` provides thread-local storage where each thread has its\nown independent copy of the data. This is useful for storing per-thread state\nwithout passing it through function arguments, such as database connections\nor request context in web applications.\n\n.. code-block:: python\n\n    from threading import Thread, local\n\n    # Each thread gets its own 'data' attribute\n    thread_data = local()\n\n    def worker(worker_id):\n        thread_data.value = worker_id\n        process()\n\n    def process():\n        # Access thread-local data without passing as argument\n        print(f\"Processing with value: {thread_data.value}\")\n\n    threads = [Thread(target=worker, args=(i,)) for i in range(3)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n\nProducer-Consumer with Queue\n----------------------------\n\nThe ``queue.Queue`` class provides a thread-safe FIFO queue that handles all\nlocking internally. This is the recommended way to communicate between threads\nin a producer-consumer pattern, as it eliminates the need for manual\nsynchronization.\n\n.. code-block:: python\n\n    from threading import Thread\n    from queue import Queue\n    import time\n\n    def producer(q, items):\n        for item in items:\n            time.sleep(0.1)\n            q.put(item)\n            print(f\"Produced: {item}\")\n        q.put(None)  # Sentinel to signal completion\n\n    def consumer(q):\n        while True:\n            item = q.get()\n            if item is None:\n                break\n            print(f\"Consumed: {item}\")\n            q.task_done()\n\n    q = Queue(maxsize=5)  # Bounded queue\n    items = list(range(10))\n\n    t1 = Thread(target=producer, args=(q, items))\n    t2 = Thread(target=consumer, args=(q,))\n\n    t1.start()\n    t2.start()\n    t1.join()\n    t2.join()\n\nDeadlock Example and Prevention\n-------------------------------\n\nDeadlock occurs when two or more threads are waiting for each other to release\nlocks, creating a circular dependency. The classic example is when thread A\nholds lock 1 and waits for lock 2, while thread B holds lock 2 and waits for\nlock 1. Prevent deadlocks by always acquiring locks in a consistent order.\n\n.. code-block:: python\n\n    from threading import Thread, Lock\n    import time\n\n    lock1 = Lock()\n    lock2 = Lock()\n\n    # DEADLOCK EXAMPLE - DON'T DO THIS\n    def task_a_bad():\n        with lock1:\n            print(\"Task A acquired lock1\")\n            time.sleep(0.1)\n            with lock2:  # Waits for lock2\n                print(\"Task A acquired lock2\")\n\n    def task_b_bad():\n        with lock2:\n            print(\"Task B acquired lock2\")\n            time.sleep(0.1)\n            with lock1:  # Waits for lock1 - DEADLOCK!\n                print(\"Task B acquired lock1\")\n\n    # CORRECT - Always acquire locks in same order\n    def task_a_good():\n        with lock1:\n            with lock2:\n                print(\"Task A acquired both locks\")\n\n    def task_b_good():\n        with lock1:  # Same order as task_a\n            with lock2:\n                print(\"Task B acquired both locks\")\n\nUnderstanding the GIL\n---------------------\n\nThe Global Interpreter Lock (GIL) is a mutex that protects access to Python\nobjects, preventing multiple threads from executing Python bytecode\nsimultaneously. This means threads don't provide speedup for CPU-bound tasks.\nHowever, the GIL is released during I/O operations, making threads effective\nfor I/O-bound tasks.\n\n.. code-block:: python\n\n    from threading import Thread\n    import time\n\n    def cpu_bound(n):\n        \"\"\"CPU-bound task - GIL limits parallelism.\"\"\"\n        count = 0\n        for i in range(n):\n            count += i\n        return count\n\n    def io_bound(seconds):\n        \"\"\"I/O-bound task - GIL released during sleep.\"\"\"\n        time.sleep(seconds)\n\n    # CPU-bound: threads won't help (may be slower due to GIL contention)\n    start = time.time()\n    threads = [Thread(target=cpu_bound, args=(10**7,)) for _ in range(4)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n    print(f\"CPU-bound threaded: {time.time() - start:.2f}s\")\n\n    # I/O-bound: threads help significantly\n    start = time.time()\n    threads = [Thread(target=io_bound, args=(1,)) for _ in range(4)]\n    for t in threads:\n        t.start()\n    for t in threads:\n        t.join()\n    print(f\"I/O-bound threaded: {time.time() - start:.2f}s\")  # ~1s, not 4s\n"
  },
  {
    "path": "docs/notes/database/index.rst",
    "content": ".. meta::\n    :description lang=en: Python SQLAlchemy tutorial covering database connections, ORM models, relationships, queries, joins, and advanced query patterns\n    :keywords: Python, Python3, SQLAlchemy, database, SQL, ORM, query, join, relationship, session, model, PostgreSQL, MySQL, SQLite\n\n========\nDatabase\n========\n\nWorking with databases is a core skill for most Python applications, from web\ndevelopment to data analysis. SQLAlchemy is Python's most popular database\ntoolkit, providing both a low-level SQL expression language (Core) and a\nhigh-level Object-Relational Mapper (ORM). This section covers SQLAlchemy from\nbasic connections and queries to advanced patterns like relationships, eager\nloading, and complex joins. Whether you're building a simple script or a\nlarge-scale application, these examples will help you interact with databases\nefficiently and safely.\n\n.. toctree::\n    :maxdepth: 1\n\n    python-sqlalchemy\n    python-sqlalchemy-orm\n    python-sqlalchemy-query\n"
  },
  {
    "path": "docs/notes/database/python-sqlalchemy-orm.rst",
    "content": ".. meta::\n    :description lang=en: SQLAlchemy ORM tutorial covering declarative models, sessions, relationships, and object-relational mapping patterns\n    :keywords: Python, SQLAlchemy, ORM, database, model, session, relationship, declarative, object-relational mapping\n\n==============\nSQLAlchemy ORM\n==============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSQLAlchemy's Object-Relational Mapper (ORM) provides a high-level abstraction that\nallows you to work with database tables as Python classes and rows as objects. The\nORM builds on top of SQLAlchemy Core and adds features like identity mapping, unit\nof work pattern, and relationship management. This approach lets you write database\ncode in a more Pythonic way, focusing on objects and their relationships rather than\nSQL statements. The ORM is ideal for applications with complex domain models where\nyou want to leverage object-oriented programming patterns.\n\nDefine Models with Declarative Base\n-----------------------------------\n\nThe declarative system is the most common way to define ORM models in SQLAlchemy.\nYou create a base class using ``declarative_base()`` and then define your models\nas subclasses. Each model class represents a database table, with class attributes\ndefining columns. The ``__tablename__`` attribute specifies the table name. This\napproach keeps your model definitions clean and readable while providing full\naccess to SQLAlchemy's features.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String\n    >>> from sqlalchemy.orm import declarative_base\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     email = Column(String(100))\n    ...     def __repr__(self):\n    ...         return f\"User(id={self.id}, name='{self.name}')\"\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n\nSession Basics\n--------------\n\nThe ``Session`` is the primary interface for persistence operations in the ORM.\nIt manages a \"holding zone\" for objects you've loaded or associated with it, and\nhandles the communication with the database. Sessions track changes to objects\nand synchronize them with the database when you call ``commit()``. The recommended\npattern is to use ``sessionmaker`` to create a session factory, then create sessions\nas needed. Always close sessions when done to release database connections.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n\n    >>> session = Session()\n    >>> try:\n    ...     user = User(name=\"Alice\")\n    ...     session.add(user)\n    ...     session.commit()\n    ...     print(f\"Created user with id: {user.id}\")\n    ... finally:\n    ...     session.close()\n    Created user with id: 1\n\nAdd and Commit Objects\n----------------------\n\nTo persist new objects to the database, add them to the session with ``add()`` or\n``add_all()`` for multiple objects. Objects remain in a \"pending\" state until you\ncall ``commit()``, which flushes all pending changes to the database in a transaction.\nIf an error occurs, call ``rollback()`` to undo all changes since the last commit.\nAfter commit, auto-generated values like primary keys are available on the objects.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> # Add single object\n    >>> user1 = User(name=\"Alice\")\n    >>> session.add(user1)\n\n    >>> # Add multiple objects\n    >>> users = [User(name=\"Bob\"), User(name=\"Carol\")]\n    >>> session.add_all(users)\n    >>> session.commit()\n\n    >>> print([u.id for u in [user1] + users])\n    [1, 2, 3]\n    >>> session.close()\n\nQuery Objects\n-------------\n\nSQLAlchemy 2.0 uses ``select()`` with ``session.execute()`` for queries, replacing\nthe legacy ``session.query()`` API. The ``select()`` construct accepts model classes\nor specific columns. Use ``scalars()`` to get model instances directly, or ``execute()``\nfor row tuples. The result supports iteration, ``all()`` for a list, ``first()`` for\nthe first result, and ``one()`` when exactly one result is expected.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     age = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     User(name=\"Alice\", age=30),\n    ...     User(name=\"Bob\", age=25),\n    ...     User(name=\"Carol\", age=35)])\n    >>> session.commit()\n\n    >>> # Get all users\n    >>> users = session.execute(select(User)).scalars().all()\n    >>> print([u.name for u in users])\n    ['Alice', 'Bob', 'Carol']\n\n    >>> # Filter with where()\n    >>> user = session.execute(select(User).where(User.age > 28)).scalars().first()\n    >>> print(user.name)\n    Alice\n    >>> session.close()\n\nFilter Queries\n--------------\n\nThe ``where()`` method accepts filter conditions using column comparisons. SQLAlchemy\noverloads Python operators to generate SQL: ``==`` becomes ``=``, ``!=`` becomes ``<>``,\nand so on. For complex conditions, use ``and_()``, ``or_()``, and ``not_()`` from\nSQLAlchemy. Columns also provide methods like ``in_()``, ``like()``, ``between()``,\n``is_()``, and ``isnot()`` for SQL-specific operations.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, and_, or_\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     age = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     User(name=\"Alice\", age=30),\n    ...     User(name=\"Bob\", age=25),\n    ...     User(name=\"Carol\", age=35),\n    ...     User(name=\"Fred\", age=30)])\n    >>> session.commit()\n\n    >>> # AND condition\n    >>> stmt = select(User).where(and_(User.age >= 30, User.name.like(\"A%\")))\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Alice']\n\n    >>> # OR condition\n    >>> stmt = select(User).where(or_(User.name == \"Alice\", User.name == \"Bob\"))\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Alice', 'Bob']\n\n    >>> # IN clause\n    >>> stmt = select(User).where(User.age.in_([25, 35]))\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Bob', 'Carol']\n    >>> session.close()\n\nUpdate Objects\n--------------\n\nTo update objects, simply modify their attributes and call ``commit()``. The session\ntracks changes to loaded objects automatically through a mechanism called \"dirty\ntracking\". When you commit, SQLAlchemy generates UPDATE statements only for changed\nattributes. You can also use bulk updates with ``update()`` for efficiency when\nmodifying many rows without loading them into memory.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, update\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add(User(name=\"Alice\"))\n    >>> session.commit()\n\n    >>> # Update via object modification\n    >>> user = session.execute(select(User).where(User.name == \"Alice\")).scalars().first()\n    >>> user.name = \"Alicia\"\n    >>> session.commit()\n\n    >>> # Verify update\n    >>> user = session.execute(select(User)).scalars().first()\n    >>> print(user.name)\n    Alicia\n    >>> session.close()\n\nDelete Objects\n--------------\n\nTo delete objects, use ``session.delete()`` followed by ``commit()``. The session\nwill generate a DELETE statement for the object. For bulk deletes without loading\nobjects, use the ``delete()`` construct with ``session.execute()``. Be careful with\ncascading deletes when objects have relationships - SQLAlchemy can automatically\ndelete related objects based on your cascade configuration.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, delete\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([User(name=\"Alice\"), User(name=\"Bob\"), User(name=\"Carol\")])\n    >>> session.commit()\n\n    >>> # Delete via object\n    >>> user = session.execute(select(User).where(User.name == \"Bob\")).scalars().first()\n    >>> session.delete(user)\n    >>> session.commit()\n\n    >>> # Verify deletion\n    >>> users = session.execute(select(User)).scalars().all()\n    >>> print([u.name for u in users])\n    ['Alice', 'Carol']\n    >>> session.close()\n\nOne-to-Many Relationship\n------------------------\n\nRelationships define how tables are connected. A one-to-many relationship means one\nrecord in the parent table can have multiple related records in the child table.\nUse ``relationship()`` on the parent side and ``ForeignKey`` on the child side.\nThe ``back_populates`` parameter creates a bidirectional relationship, allowing\nnavigation from both sides. SQLAlchemy handles the foreign key management automatically.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     posts = relationship(\"Post\", back_populates=\"author\")\n\n    >>> class Post(Base):\n    ...     __tablename__ = \"posts\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     title = Column(String(100))\n    ...     user_id = Column(Integer, ForeignKey(\"users.id\"))\n    ...     author = relationship(\"User\", back_populates=\"posts\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> user = User(name=\"Alice\")\n    >>> user.posts.append(Post(title=\"First Post\"))\n    >>> user.posts.append(Post(title=\"Second Post\"))\n    >>> session.add(user)\n    >>> session.commit()\n\n    >>> # Access relationship\n    >>> user = session.execute(select(User)).scalars().first()\n    >>> print([p.title for p in user.posts])\n    ['First Post', 'Second Post']\n    >>> session.close()\n\nMany-to-Many Relationship\n-------------------------\n\nMany-to-many relationships require an association table that contains foreign keys\nto both related tables. Define the association table using ``Table``, then use\n``relationship()`` with the ``secondary`` parameter pointing to it. Both sides can\nhave a relationship, and SQLAlchemy manages the association table entries automatically\nwhen you add or remove items from the relationship collections.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, Table, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> # Association table\n    >>> student_course = Table(\n    ...     \"student_course\", Base.metadata,\n    ...     Column(\"student_id\", Integer, ForeignKey(\"students.id\"), primary_key=True),\n    ...     Column(\"course_id\", Integer, ForeignKey(\"courses.id\"), primary_key=True))\n\n    >>> class Student(Base):\n    ...     __tablename__ = \"students\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     courses = relationship(\"Course\", secondary=student_course, back_populates=\"students\")\n\n    >>> class Course(Base):\n    ...     __tablename__ = \"courses\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     students = relationship(\"Student\", secondary=student_course, back_populates=\"courses\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> math = Course(name=\"Math\")\n    >>> physics = Course(name=\"Physics\")\n    >>> alice = Student(name=\"Alice\", courses=[math, physics])\n    >>> bob = Student(name=\"Bob\", courses=[math])\n    >>> session.add_all([alice, bob])\n    >>> session.commit()\n\n    >>> # Query relationships\n    >>> math = session.execute(select(Course).where(Course.name == \"Math\")).scalars().first()\n    >>> print([s.name for s in math.students])\n    ['Alice', 'Bob']\n    >>> session.close()\n\nSelf-Referential Relationship\n-----------------------------\n\nSelf-referential relationships connect a table to itself, useful for hierarchical\ndata like organizational charts, categories, or threaded comments. Use ``ForeignKey``\npointing to the same table and ``relationship()`` with ``remote_side`` to indicate\nwhich side is the \"parent\". This pattern allows you to model tree structures where\neach node can have a parent and multiple children.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> class Employee(Base):\n    ...     __tablename__ = \"employees\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     manager_id = Column(Integer, ForeignKey(\"employees.id\"))\n    ...     manager = relationship(\"Employee\", remote_side=[id], backref=\"subordinates\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> ceo = Employee(name=\"CEO\")\n    >>> session.add(ceo)\n    >>> session.flush()\n    >>> manager = Employee(name=\"Manager\", manager_id=ceo.id)\n    >>> session.add(manager)\n    >>> session.flush()\n    >>> worker1 = Employee(name=\"Worker1\", manager_id=manager.id)\n    >>> worker2 = Employee(name=\"Worker2\", manager_id=manager.id)\n    >>> session.add_all([worker1, worker2])\n    >>> session.commit()\n\n    >>> # Navigate hierarchy\n    >>> mgr = session.execute(select(Employee).where(Employee.name == \"Manager\")).scalars().first()\n    >>> print(f\"Manager: {mgr.name}, Boss: {mgr.manager.name}\")\n    Manager: Manager, Boss: CEO\n    >>> print(f\"Subordinates: {[e.name for e in mgr.subordinates]}\")\n    Subordinates: ['Worker1', 'Worker2']\n    >>> session.close()\n\nCascade Deletes\n---------------\n\nCascade options control what happens to related objects when a parent is deleted\nor modified. The ``cascade`` parameter on ``relationship()`` accepts a comma-separated\nstring of cascade rules. Common options include ``\"all, delete-orphan\"`` which deletes\nchildren when the parent is deleted and when children are removed from the collection.\nThis ensures referential integrity and prevents orphaned records.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> class Parent(Base):\n    ...     __tablename__ = \"parents\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     children = relationship(\"Child\", back_populates=\"parent\",\n    ...                            cascade=\"all, delete-orphan\")\n\n    >>> class Child(Base):\n    ...     __tablename__ = \"children\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     parent_id = Column(Integer, ForeignKey(\"parents.id\"))\n    ...     parent = relationship(\"Parent\", back_populates=\"children\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> parent = Parent(name=\"Parent1\")\n    >>> parent.children = [Child(name=\"Child1\"), Child(name=\"Child2\")]\n    >>> session.add(parent)\n    >>> session.commit()\n\n    >>> # Delete parent - children are also deleted\n    >>> session.delete(parent)\n    >>> session.commit()\n    >>> children = session.execute(select(Child)).scalars().all()\n    >>> print(len(children))\n    0\n    >>> session.close()\n\nEager Loading\n-------------\n\nBy default, SQLAlchemy uses lazy loading for relationships, executing a new query\nwhen you access related objects. This can cause the \"N+1 query problem\" when iterating\nover many objects. Eager loading fetches related objects in the same query using\nJOIN or subqueries. Use ``joinedload()`` for single objects or small collections,\nand ``selectinload()`` for larger collections to avoid cartesian products.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship, joinedload\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     posts = relationship(\"Post\", back_populates=\"author\")\n\n    >>> class Post(Base):\n    ...     __tablename__ = \"posts\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     title = Column(String(100))\n    ...     user_id = Column(Integer, ForeignKey(\"users.id\"))\n    ...     author = relationship(\"User\", back_populates=\"posts\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> user = User(name=\"Alice\")\n    >>> user.posts = [Post(title=\"Post1\"), Post(title=\"Post2\")]\n    >>> session.add(user)\n    >>> session.commit()\n\n    >>> # Eager load posts with user in single query\n    >>> stmt = select(User).options(joinedload(User.posts))\n    >>> user = session.execute(stmt).scalars().unique().first()\n    >>> print([p.title for p in user.posts])  # No additional query\n    ['Post1', 'Post2']\n    >>> session.close()\n\nHybrid Properties\n-----------------\n\nHybrid properties allow you to define Python properties that work both at the instance\nlevel (in Python) and at the class level (in SQL queries). This is useful for computed\nattributes that you want to filter or sort by in database queries. Use the\n``@hybrid_property`` decorator and optionally ``@property.expression`` to customize\nthe SQL expression.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> from sqlalchemy.ext.hybrid import hybrid_property\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     first_name = Column(String(50))\n    ...     last_name = Column(String(50))\n    ...\n    ...     @hybrid_property\n    ...     def full_name(self):\n    ...         return f\"{self.first_name} {self.last_name}\"\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add(User(first_name=\"Alice\", last_name=\"Smith\"))\n    >>> session.commit()\n\n    >>> user = session.execute(select(User)).scalars().first()\n    >>> print(user.full_name)\n    Alice Smith\n    >>> session.close()\n\nEvent Hooks\n-----------\n\nSQLAlchemy provides an event system that lets you hook into various ORM operations\nlike before/after insert, update, or delete. Use ``@event.listens_for()`` decorator\nto register event handlers. Events are useful for auditing, validation, automatic\ntimestamps, or triggering side effects. Common events include ``before_insert``,\n``after_insert``, ``before_update``, ``after_update``, ``before_delete``, and\n``after_delete``.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, DateTime, select, event\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> from datetime import datetime\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     created_at = Column(DateTime)\n    ...     updated_at = Column(DateTime)\n\n    >>> @event.listens_for(User, \"before_insert\")\n    ... def set_created_at(mapper, connection, target):\n    ...     target.created_at = datetime.now()\n    ...     target.updated_at = datetime.now()\n\n    >>> @event.listens_for(User, \"before_update\")\n    ... def set_updated_at(mapper, connection, target):\n    ...     target.updated_at = datetime.now()\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> user = User(name=\"Alice\")\n    >>> session.add(user)\n    >>> session.commit()\n\n    >>> print(user.created_at is not None)\n    True\n    >>> session.close()\n"
  },
  {
    "path": "docs/notes/database/python-sqlalchemy-query.rst",
    "content": ".. meta::\n    :description lang=en: SQLAlchemy advanced query patterns including joins, subqueries, aggregations, window functions, and performance optimization\n    :keywords: Python, SQLAlchemy, query, join, subquery, aggregate, window function, CTE, performance, optimization\n\n========================\nSQLAlchemy Query Recipes\n========================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis section covers advanced query patterns and recipes for SQLAlchemy. While the\nbasics of querying are covered in the ORM section, real-world applications often\nrequire more sophisticated queries involving joins across multiple tables, subqueries,\naggregations, and performance optimizations. These patterns help you write efficient\ndatabase queries while maintaining readable Python code. Understanding these techniques\nis essential for building scalable applications that interact with relational databases.\n\nOrder By\n--------\n\nThe ``order_by()`` method sorts query results by one or more columns. Pass column\nobjects directly, or use ``desc()`` for descending order. You can chain multiple\ncolumns for secondary sorting. SQLAlchemy also supports ``nullsfirst()`` and\n``nullslast()`` to control how NULL values are sorted, which is particularly useful\nwhen dealing with optional fields.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, desc\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     age = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     User(name=\"Alice\", age=30),\n    ...     User(name=\"Bob\", age=25),\n    ...     User(name=\"Carol\", age=30)])\n    >>> session.commit()\n\n    >>> # Ascending order\n    >>> stmt = select(User).order_by(User.age)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Bob', 'Alice', 'Carol']\n\n    >>> # Descending order\n    >>> stmt = select(User).order_by(desc(User.age), User.name)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Alice', 'Carol', 'Bob']\n    >>> session.close()\n\nLimit and Offset\n----------------\n\nUse ``limit()`` to restrict the number of results and ``offset()`` to skip rows,\nenabling pagination. These methods translate directly to SQL LIMIT and OFFSET clauses.\nFor large datasets, consider using keyset pagination (filtering by the last seen ID)\ninstead of offset-based pagination, as OFFSET can become slow with large offsets\nsince the database must scan and discard rows.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([User(name=f\"User{i}\") for i in range(10)])\n    >>> session.commit()\n\n    >>> # First page (3 items)\n    >>> stmt = select(User).order_by(User.id).limit(3)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['User0', 'User1', 'User2']\n\n    >>> # Second page\n    >>> stmt = select(User).order_by(User.id).limit(3).offset(3)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['User3', 'User4', 'User5']\n    >>> session.close()\n\nGroup By and Aggregates\n-----------------------\n\nUse ``group_by()`` with aggregate functions like ``func.count()``, ``func.sum()``,\n``func.avg()``, ``func.min()``, and ``func.max()`` for grouped calculations. The\n``having()`` method filters groups after aggregation, similar to SQL's HAVING clause.\nWhen selecting both regular columns and aggregates, all non-aggregate columns must\nbe included in the GROUP BY clause.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, func\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class Sale(Base):\n    ...     __tablename__ = \"sales\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     product = Column(String(50))\n    ...     amount = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     Sale(product=\"A\", amount=100),\n    ...     Sale(product=\"A\", amount=150),\n    ...     Sale(product=\"B\", amount=200),\n    ...     Sale(product=\"B\", amount=50)])\n    >>> session.commit()\n\n    >>> # Group by with sum\n    >>> stmt = select(Sale.product, func.sum(Sale.amount).label(\"total\"))\\\n    ...        .group_by(Sale.product)\n    >>> for row in session.execute(stmt):\n    ...     print(f\"{row.product}: {row.total}\")\n    A: 250\n    B: 250\n\n    >>> # Having clause\n    >>> stmt = select(Sale.product, func.count().label(\"count\"))\\\n    ...        .group_by(Sale.product).having(func.count() > 1)\n    >>> print(session.execute(stmt).fetchall())\n    [('A', 2), ('B', 2)]\n    >>> session.close()\n\nJoin Queries\n------------\n\nSQLAlchemy provides several ways to join tables. For ORM models with relationships,\nuse ``join()`` which automatically uses the foreign key. For explicit join conditions,\npass the condition as the second argument. Use ``outerjoin()`` for LEFT OUTER JOIN.\nThe ``select_from()`` method specifies the FROM clause when needed for complex joins.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     orders = relationship(\"Order\", back_populates=\"user\")\n\n    >>> class Order(Base):\n    ...     __tablename__ = \"orders\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     product = Column(String(50))\n    ...     user_id = Column(Integer, ForeignKey(\"users.id\"))\n    ...     user = relationship(\"User\", back_populates=\"orders\")\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> alice = User(name=\"Alice\")\n    >>> alice.orders = [Order(product=\"Book\"), Order(product=\"Pen\")]\n    >>> bob = User(name=\"Bob\")\n    >>> session.add_all([alice, bob])\n    >>> session.commit()\n\n    >>> # Inner join\n    >>> stmt = select(User.name, Order.product).join(Order)\n    >>> print(session.execute(stmt).fetchall())\n    [('Alice', 'Book'), ('Alice', 'Pen')]\n\n    >>> # Left outer join (includes users without orders)\n    >>> stmt = select(User.name, Order.product).outerjoin(Order)\n    >>> print(session.execute(stmt).fetchall())\n    [('Alice', 'Book'), ('Alice', 'Pen'), ('Bob', None)]\n    >>> session.close()\n\nSubqueries\n----------\n\nSubqueries are queries nested inside other queries. Use ``subquery()`` to create\na subquery that can be used in the FROM clause, or ``scalar_subquery()`` for single-value\nsubqueries in SELECT or WHERE clauses. Subqueries are useful for complex filtering,\ncomputing derived values, or when you need to reference aggregated data in the main query.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, func\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     score = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     User(name=\"Alice\", score=85),\n    ...     User(name=\"Bob\", score=90),\n    ...     User(name=\"Carol\", score=75)])\n    >>> session.commit()\n\n    >>> # Scalar subquery: users with above-average score\n    >>> avg_score = select(func.avg(User.score)).scalar_subquery()\n    >>> stmt = select(User).where(User.score > avg_score)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Alice', 'Bob']\n    >>> session.close()\n\nCommon Table Expressions (CTE)\n------------------------------\n\nCTEs (WITH clauses) improve query readability by naming subqueries. They're especially\nuseful for recursive queries and when the same subquery is referenced multiple times.\nUse ``cte()`` to create a CTE from a select statement. CTEs can reference themselves\nfor recursive queries, which is powerful for hierarchical data like organizational\ncharts or category trees.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, func\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class Sale(Base):\n    ...     __tablename__ = \"sales\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     region = Column(String(50))\n    ...     amount = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     Sale(region=\"East\", amount=100),\n    ...     Sale(region=\"East\", amount=200),\n    ...     Sale(region=\"West\", amount=150)])\n    >>> session.commit()\n\n    >>> # CTE for regional totals\n    >>> regional_totals = select(\n    ...     Sale.region,\n    ...     func.sum(Sale.amount).label(\"total\")\n    ... ).group_by(Sale.region).cte(\"regional_totals\")\n\n    >>> # Use CTE in main query\n    >>> stmt = select(regional_totals).where(regional_totals.c.total > 200)\n    >>> print(session.execute(stmt).fetchall())\n    [('East', 300)]\n    >>> session.close()\n\nExists and Correlated Subqueries\n--------------------------------\n\nThe ``exists()`` construct creates an EXISTS subquery, which returns true if the\nsubquery returns any rows. This is efficient for checking the existence of related\nrecords without loading them. Correlated subqueries reference columns from the outer\nquery, allowing row-by-row comparisons. Use ``correlate()`` to explicitly specify\nwhich tables the subquery should correlate with.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, select, exists\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, relationship\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> class Order(Base):\n    ...     __tablename__ = \"orders\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     user_id = Column(Integer, ForeignKey(\"users.id\"))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n    >>> session.commit()\n    >>> alice = session.execute(select(User).where(User.name == \"Alice\")).scalars().first()\n    >>> session.add(Order(user_id=alice.id))\n    >>> session.commit()\n\n    >>> # Users with orders\n    >>> has_orders = exists().where(Order.user_id == User.id)\n    >>> stmt = select(User).where(has_orders)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Alice']\n\n    >>> # Users without orders\n    >>> stmt = select(User).where(~has_orders)\n    >>> print([u.name for u in session.execute(stmt).scalars()])\n    ['Bob']\n    >>> session.close()\n\nUnion Queries\n-------------\n\nUse ``union()`` to combine results from multiple SELECT statements, removing duplicates.\nUse ``union_all()`` to keep all rows including duplicates, which is faster when you\nknow there are no duplicates or don't care about them. The queries must have the same\nnumber of columns with compatible types. Unions are useful for combining data from\ndifferent tables or different filtered views of the same table.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, union_all\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class Customer(Base):\n    ...     __tablename__ = \"customers\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> class Supplier(Base):\n    ...     __tablename__ = \"suppliers\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([Customer(name=\"Alice\"), Customer(name=\"Bob\")])\n    >>> session.add_all([Supplier(name=\"Acme\"), Supplier(name=\"Bob\")])\n    >>> session.commit()\n\n    >>> # Union all names\n    >>> stmt = union_all(\n    ...     select(Customer.name),\n    ...     select(Supplier.name))\n    >>> print(sorted([row[0] for row in session.execute(stmt)]))\n    ['Acme', 'Alice', 'Bob', 'Bob']\n    >>> session.close()\n\nCase Expressions\n----------------\n\nThe ``case()`` construct creates SQL CASE expressions for conditional logic in queries.\nIt's useful for computed columns, conditional aggregations, and data transformations.\nPass a list of (condition, result) tuples, with an optional ``else_`` for the default\nvalue. Case expressions can be used in SELECT, WHERE, ORDER BY, and other clauses.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, case\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     score = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     User(name=\"Alice\", score=95),\n    ...     User(name=\"Bob\", score=75),\n    ...     User(name=\"Carol\", score=55)])\n    >>> session.commit()\n\n    >>> # Grade based on score\n    >>> grade = case(\n    ...     (User.score >= 90, \"A\"),\n    ...     (User.score >= 70, \"B\"),\n    ...     else_=\"C\")\n    >>> stmt = select(User.name, grade.label(\"grade\"))\n    >>> for row in session.execute(stmt):\n    ...     print(f\"{row.name}: {row.grade}\")\n    Alice: A\n    Bob: B\n    Carol: C\n    >>> session.close()\n\nDistinct and Count Distinct\n---------------------------\n\nUse ``distinct()`` to remove duplicate rows from results. For counting unique values,\ncombine ``func.count()`` with ``distinct()``. The ``distinct()`` method can be applied\nto the entire query or to specific columns. This is essential for accurate counting\nwhen dealing with joined tables that may produce duplicate rows.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, func, distinct\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class Order(Base):\n    ...     __tablename__ = \"orders\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     customer = Column(String(50))\n    ...     product = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     Order(customer=\"Alice\", product=\"Book\"),\n    ...     Order(customer=\"Alice\", product=\"Pen\"),\n    ...     Order(customer=\"Bob\", product=\"Book\")])\n    >>> session.commit()\n\n    >>> # Distinct customers\n    >>> stmt = select(Order.customer).distinct()\n    >>> print(session.execute(stmt).fetchall())\n    [('Alice',), ('Bob',)]\n\n    >>> # Count distinct products\n    >>> stmt = select(func.count(distinct(Order.product)))\n    >>> print(session.execute(stmt).scalar())\n    2\n    >>> session.close()\n\nAliased Tables\n--------------\n\nUse ``aliased()`` to create aliases for tables, allowing you to reference the same\ntable multiple times in a query with different names. This is essential for self-joins\nand queries that need to compare rows within the same table. Aliases create independent\nreferences that can have different join conditions and filters.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker, aliased\n    >>> Base = declarative_base()\n\n    >>> class Employee(Base):\n    ...     __tablename__ = \"employees\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n    ...     salary = Column(Integer)\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([\n    ...     Employee(name=\"Alice\", salary=50000),\n    ...     Employee(name=\"Bob\", salary=60000),\n    ...     Employee(name=\"Carol\", salary=55000)])\n    >>> session.commit()\n\n    >>> # Find employees earning more than Alice\n    >>> alice_alias = aliased(Employee, name=\"alice\")\n    >>> stmt = select(Employee.name).select_from(Employee).join(\n    ...     alice_alias, alice_alias.name == \"Alice\"\n    ... ).where(Employee.salary > alice_alias.salary)\n    >>> print([row[0] for row in session.execute(stmt)])\n    ['Bob', 'Carol']\n    >>> session.close()\n\nBulk Operations\n---------------\n\nFor inserting or updating many rows, bulk operations are much faster than adding\nobjects one by one. Use ``session.bulk_insert_mappings()`` for inserts and\n``session.bulk_update_mappings()`` for updates. These methods bypass the ORM's\nunit of work pattern for better performance. For even faster inserts, use Core's\n``insert()`` with ``execute()`` passing a list of dictionaries.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, select, insert\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n\n    >>> # Bulk insert with Core (fastest)\n    >>> data = [{\"name\": f\"User{i}\"} for i in range(100)]\n    >>> session.execute(insert(User), data)\n    >>> session.commit()\n\n    >>> # Verify\n    >>> count = session.execute(select(func.count()).select_from(User)).scalar()\n    >>> print(count)\n    100\n    >>> session.close()\n\nRaw SQL with Text\n-----------------\n\nWhen you need to execute raw SQL that's difficult to express with SQLAlchemy's\nconstructs, use ``text()`` to wrap SQL strings. Always use bound parameters (`:name`\nsyntax) instead of string formatting to prevent SQL injection. The ``text()`` construct\ncan be used with both Core and ORM queries, and results can be mapped to ORM objects\nusing ``from_statement()``.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, Column, Integer, String, text, select\n    >>> from sqlalchemy.orm import declarative_base, sessionmaker\n    >>> Base = declarative_base()\n\n    >>> class User(Base):\n    ...     __tablename__ = \"users\"\n    ...     id = Column(Integer, primary_key=True)\n    ...     name = Column(String(50))\n\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> Base.metadata.create_all(engine)\n    >>> Session = sessionmaker(bind=engine)\n    >>> session = Session()\n    >>> session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n    >>> session.commit()\n\n    >>> # Raw SQL with parameters\n    >>> result = session.execute(\n    ...     text(\"SELECT * FROM users WHERE name = :name\"),\n    ...     {\"name\": \"Alice\"})\n    >>> print(result.fetchall())\n    [(1, 'Alice')]\n\n    >>> # Map raw SQL to ORM objects\n    >>> stmt = select(User).from_statement(text(\"SELECT * FROM users ORDER BY name\"))\n    >>> users = session.execute(stmt).scalars().all()\n    >>> print([u.name for u in users])\n    ['Alice', 'Bob']\n    >>> session.close()\n"
  },
  {
    "path": "docs/notes/database/python-sqlalchemy.rst",
    "content": ".. meta::\n    :description lang=en: SQLAlchemy Core tutorial covering database connections, engine creation, metadata, table definitions, and SQL expression language\n    :keywords: Python, SQLAlchemy, database, SQL, engine, metadata, table, connection, transaction, Core API\n\n=================\nSQLAlchemy Basics\n=================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSQLAlchemy is the most popular database toolkit and Object-Relational Mapping (ORM)\nlibrary for Python. It provides a full suite of well-known enterprise-level persistence\npatterns, designed for efficient and high-performing database access. SQLAlchemy is\ndivided into two main components: the Core (low-level SQL abstraction) and the ORM\n(high-level object mapping). This cheat sheet covers the Core API, which provides a\nSQL Expression Language that allows you to construct SQL statements in Python code\nwhile remaining database-agnostic. The Core is ideal when you need fine-grained control\nover SQL queries or when working with existing database schemas.\n\nCreate an Engine\n----------------\n\nThe ``Engine`` is the starting point for any SQLAlchemy application. It represents\nthe connection pool and dialect for a particular database, managing connectivity\nand translating Python code into database-specific SQL. The ``create_engine()``\nfunction takes a database URL that specifies the database type, credentials, host,\nand database name. SQLAlchemy supports many databases including SQLite, PostgreSQL,\nMySQL, Oracle, and Microsoft SQL Server through different dialects.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine\n    >>> # SQLite in-memory database (great for testing)\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> # SQLite file-based database\n    >>> engine = create_engine(\"sqlite:///mydb.sqlite\")\n    >>> # PostgreSQL\n    >>> engine = create_engine(\"postgresql://user:pass@localhost/dbname\")\n    >>> # MySQL\n    >>> engine = create_engine(\"mysql+pymysql://user:pass@localhost/dbname\")\n\nDatabase URL Format\n-------------------\n\nSQLAlchemy uses RFC-1738 style URLs to specify database connections. The URL format\nprovides a standardized way to specify all connection parameters including the database\ndriver, authentication credentials, host address, port number, and database name.\nUnderstanding this format is essential for configuring connections to different\ndatabase systems. The ``make_url()`` function can parse and construct these URLs\nprogrammatically.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import make_url\n    >>> # Format: dialect+driver://username:password@host:port/database\n    >>> url = make_url(\"postgresql://user:pass@localhost:5432/mydb\")\n    >>> url.drivername\n    'postgresql'\n    >>> url.username\n    'user'\n    >>> url.host\n    'localhost'\n    >>> url.database\n    'mydb'\n\nConnect and Execute Raw SQL\n---------------------------\n\nWhile SQLAlchemy encourages using its SQL Expression Language, you can also execute\nraw SQL strings directly. This is useful for complex queries that are difficult to\nexpress in SQLAlchemy's API, or when migrating existing SQL code. The ``text()``\nfunction wraps raw SQL strings and allows parameter binding for security. Always\nuse parameter binding instead of string formatting to prevent SQL injection attacks.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, text\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> with engine.connect() as conn:\n    ...     conn.execute(text(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\"))\n    ...     conn.execute(text(\"INSERT INTO test (name) VALUES (:name)\"), {\"name\": \"Alice\"})\n    ...     conn.commit()\n    ...     result = conn.execute(text(\"SELECT * FROM test\"))\n    ...     print(result.fetchall())\n    [(1, 'Alice')]\n\nTransaction Management\n----------------------\n\nTransactions ensure that a series of database operations either all succeed or all\nfail together, maintaining data integrity. SQLAlchemy provides several ways to manage\ntransactions: implicit transactions with ``begin()``, context managers for automatic\ncommit/rollback, and manual control with ``commit()`` and ``rollback()``. The ``begin()``\nmethod starts a transaction that will automatically rollback on exceptions and commit\non successful completion when used as a context manager.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, text\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> # Using begin() for automatic commit/rollback\n    >>> with engine.begin() as conn:\n    ...     conn.execute(text(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\"))\n    ...     conn.execute(text(\"INSERT INTO users (name) VALUES ('Bob')\"))\n    ...     # Commits automatically if no exception\n\n    >>> # Manual transaction control\n    >>> with engine.connect() as conn:\n    ...     trans = conn.begin()\n    ...     try:\n    ...         conn.execute(text(\"INSERT INTO users (name) VALUES ('Carol')\"))\n    ...         trans.commit()\n    ...     except:\n    ...         trans.rollback()\n    ...         raise\n\nDefine Tables with Metadata\n---------------------------\n\n``MetaData`` is a container that holds information about database tables and other\nschema constructs. You can define tables programmatically using the ``Table`` class,\nspecifying columns with their types and constraints. This approach is part of\nSQLAlchemy Core and gives you explicit control over the table structure. The metadata\ncan then create all defined tables in the database with ``create_all()``, which\ngenerates the appropriate DDL statements for your database dialect.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\n    ...     \"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)),\n    ...     Column(\"email\", String(100))\n    ... )\n    >>> metadata.create_all(engine)\n    >>> # Check table columns\n    >>> [c.name for c in users.columns]\n    ['id', 'name', 'email']\n\nReflect Existing Tables\n-----------------------\n\nTable reflection allows SQLAlchemy to load table definitions from an existing database\nschema automatically. This is useful when working with legacy databases or when you\nwant to avoid duplicating schema definitions. The ``reflect()`` method on ``MetaData``\nreads the database schema and creates ``Table`` objects for all tables found. You can\nalso reflect individual tables using ``autoload_with`` parameter.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> # Create a table first\n    >>> with engine.begin() as conn:\n    ...     conn.execute(text(\"CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)\"))\n\n    >>> # Reflect the table\n    >>> metadata = MetaData()\n    >>> metadata.reflect(bind=engine)\n    >>> list(metadata.tables.keys())\n    ['products']\n    >>> products = metadata.tables['products']\n    >>> [c.name for c in products.columns]\n    ['id', 'name', 'price']\n\nInspect Database Schema\n-----------------------\n\nThe ``inspect()`` function provides a powerful way to examine database schema details\nat runtime. The inspector can retrieve information about tables, columns, indexes,\nforeign keys, and other database objects. This is particularly useful for database\nadministration tasks, schema migrations, and debugging. The inspector works with\nany database supported by SQLAlchemy and provides a consistent API across different\ndatabase systems.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, inspect, MetaData, Table, Column, Integer, String\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)))\n    >>> metadata.create_all(engine)\n\n    >>> inspector = inspect(engine)\n    >>> inspector.get_table_names()\n    ['users']\n    >>> inspector.get_columns('users')  # doctest: +ELLIPSIS\n    [{'name': 'id', ...}, {'name': 'name', ...}]\n\nInsert Data\n-----------\n\nThe ``insert()`` construct creates an INSERT statement for a table. You can specify\nvalues using the ``values()`` method or pass them as keyword arguments. For bulk\ninserts, pass a list of dictionaries to ``execute()``. SQLAlchemy will generate\nefficient multi-row INSERT statements when possible. The ``returning()`` method\ncan retrieve auto-generated values like primary keys after insertion.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, insert\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)))\n    >>> metadata.create_all(engine)\n\n    >>> # Single insert\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users).values(name=\"Alice\"))\n    ...     # Bulk insert\n    ...     conn.execute(insert(users), [{\"name\": \"Bob\"}, {\"name\": \"Carol\"}])\n\n    >>> with engine.connect() as conn:\n    ...     result = conn.execute(users.select())\n    ...     print(result.fetchall())\n    [(1, 'Alice'), (2, 'Bob'), (3, 'Carol')]\n\nSelect Data\n-----------\n\nThe ``select()`` construct creates SELECT statements with a Pythonic API. You can\nspecify which columns to retrieve, add WHERE clauses with ``where()``, order results\nwith ``order_by()``, and limit results with ``limit()`` and ``offset()``. The SQL\nExpression Language uses Python operators like ``==``, ``!=``, ``>``, ``<`` which\nare overloaded to generate SQL conditions. This provides type safety and prevents\nSQL injection.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, select, insert\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)),\n    ...     Column(\"age\", Integer))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users), [\n    ...         {\"name\": \"Alice\", \"age\": 30},\n    ...         {\"name\": \"Bob\", \"age\": 25},\n    ...         {\"name\": \"Carol\", \"age\": 35}])\n\n    >>> with engine.connect() as conn:\n    ...     # Select all\n    ...     result = conn.execute(select(users))\n    ...     print(result.fetchall())\n    ...     # Select with condition\n    ...     result = conn.execute(select(users).where(users.c.age > 28))\n    ...     print(result.fetchall())\n    [(1, 'Alice', 30), (2, 'Bob', 25), (3, 'Carol', 35)]\n    [(1, 'Alice', 30), (3, 'Carol', 35)]\n\nUpdate Data\n-----------\n\nThe ``update()`` construct creates UPDATE statements. Use ``where()`` to specify\nwhich rows to update and ``values()`` to set new column values. Without a WHERE\nclause, all rows in the table will be updated. The ``returning()`` method can\nretrieve the updated values. For bulk updates with different values per row,\nuse ``bindparam()`` to create parameterized statements.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> from sqlalchemy import select, insert, update\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users), [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}])\n    ...     conn.execute(update(users).where(users.c.name == \"Alice\").values(name=\"Alicia\"))\n\n    >>> with engine.connect() as conn:\n    ...     result = conn.execute(select(users))\n    ...     print(result.fetchall())\n    [(1, 'Alicia'), (2, 'Bob')]\n\nDelete Data\n-----------\n\nThe ``delete()`` construct creates DELETE statements. Always use ``where()`` to\nspecify which rows to delete, unless you intend to delete all rows. Like other\nDML statements, ``delete()`` supports ``returning()`` to retrieve deleted rows.\nBe careful with DELETE statements as they permanently remove data from the database.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> from sqlalchemy import select, insert, delete\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users), [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}, {\"name\": \"Carol\"}])\n    ...     conn.execute(delete(users).where(users.c.name == \"Bob\"))\n\n    >>> with engine.connect() as conn:\n    ...     result = conn.execute(select(users))\n    ...     print(result.fetchall())\n    [(1, 'Alice'), (3, 'Carol')]\n\nSQL Expression Language\n-----------------------\n\nSQLAlchemy's SQL Expression Language provides a Pythonic way to construct SQL\nstatements. Column objects support comparison operators (``==``, ``!=``, ``>``, ``<``),\nlogical operators (``&`` for AND, ``|`` for OR), and methods like ``in_()``,\n``like()``, ``between()``, and ``is_()``. These expressions are composable and\ncan be combined to build complex queries while maintaining readability and type safety.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> from sqlalchemy import select, insert, and_, or_\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)),\n    ...     Column(\"age\", Integer))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users), [\n    ...         {\"name\": \"Alice\", \"age\": 30},\n    ...         {\"name\": \"Bob\", \"age\": 25},\n    ...         {\"name\": \"Carol\", \"age\": 35}])\n\n    >>> with engine.connect() as conn:\n    ...     # AND condition\n    ...     stmt = select(users).where(and_(users.c.age > 25, users.c.age < 35))\n    ...     print(conn.execute(stmt).fetchall())\n    ...     # OR condition\n    ...     stmt = select(users).where(or_(users.c.name == \"Alice\", users.c.name == \"Bob\"))\n    ...     print(conn.execute(stmt).fetchall())\n    ...     # IN clause\n    ...     stmt = select(users).where(users.c.name.in_([\"Alice\", \"Carol\"]))\n    ...     print(conn.execute(stmt).fetchall())\n    [(1, 'Alice', 30)]\n    [(1, 'Alice', 30), (2, 'Bob', 25)]\n    [(1, 'Alice', 30), (3, 'Carol', 35)]\n\nJoin Tables\n-----------\n\nThe ``join()`` method creates JOIN clauses between tables. SQLAlchemy can automatically\ndetermine join conditions based on foreign key relationships, or you can specify\nthem explicitly. Use ``select_from()`` to specify the joined tables in a SELECT\nstatement. SQLAlchemy supports INNER JOIN (default), LEFT OUTER JOIN, RIGHT OUTER\nJOIN, and FULL OUTER JOIN through the ``isouter`` and ``full`` parameters.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey\n    >>> from sqlalchemy import select, insert\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"name\", String(50)))\n    >>> orders = Table(\"orders\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"user_id\", Integer, ForeignKey(\"users.id\")),\n    ...     Column(\"product\", String(50)))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(users), [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}])\n    ...     conn.execute(insert(orders), [\n    ...         {\"user_id\": 1, \"product\": \"Book\"},\n    ...         {\"user_id\": 1, \"product\": \"Pen\"},\n    ...         {\"user_id\": 2, \"product\": \"Laptop\"}])\n\n    >>> with engine.connect() as conn:\n    ...     stmt = select(users.c.name, orders.c.product).select_from(\n    ...         users.join(orders))\n    ...     print(conn.execute(stmt).fetchall())\n    [('Alice', 'Book'), ('Alice', 'Pen'), ('Bob', 'Laptop')]\n\nAggregate Functions\n-------------------\n\nSQLAlchemy provides functions for SQL aggregates like ``count()``, ``sum()``,\n``avg()``, ``min()``, and ``max()`` in the ``sqlalchemy.func`` namespace. These\ncan be used in SELECT statements and combined with ``group_by()`` for grouped\naggregations. The ``func`` object is a special namespace that generates SQL\nfunction calls for any function name you access on it.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String\n    >>> from sqlalchemy import select, insert, func\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> sales = Table(\"sales\", metadata,\n    ...     Column(\"id\", Integer, primary_key=True),\n    ...     Column(\"product\", String(50)),\n    ...     Column(\"amount\", Integer))\n    >>> metadata.create_all(engine)\n\n    >>> with engine.begin() as conn:\n    ...     conn.execute(insert(sales), [\n    ...         {\"product\": \"A\", \"amount\": 100},\n    ...         {\"product\": \"A\", \"amount\": 150},\n    ...         {\"product\": \"B\", \"amount\": 200}])\n\n    >>> with engine.connect() as conn:\n    ...     # Count all rows\n    ...     result = conn.execute(select(func.count()).select_from(sales))\n    ...     print(result.scalar())\n    ...     # Sum with group by\n    ...     stmt = select(sales.c.product, func.sum(sales.c.amount)).group_by(sales.c.product)\n    ...     print(conn.execute(stmt).fetchall())\n    3\n    [('A', 250), ('B', 200)]\n\nDrop Tables\n-----------\n\nTables can be dropped using the ``drop()`` method on a ``Table`` object or\n``drop_all()`` on ``MetaData`` to drop all tables. The ``checkfirst`` parameter\nprevents errors if the table doesn't exist. Be careful with these operations in\nproduction as they permanently delete data and schema. Always backup your database\nbefore dropping tables.\n\n.. code-block:: python\n\n    >>> from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, inspect\n    >>> engine = create_engine(\"sqlite:///:memory:\")\n    >>> metadata = MetaData()\n    >>> users = Table(\"users\", metadata, Column(\"id\", Integer, primary_key=True))\n    >>> products = Table(\"products\", metadata, Column(\"id\", Integer, primary_key=True))\n    >>> metadata.create_all(engine)\n\n    >>> inspector = inspect(engine)\n    >>> sorted(inspector.get_table_names())\n    ['products', 'users']\n\n    >>> # Drop single table\n    >>> users.drop(engine)\n    >>> sorted(inspect(engine).get_table_names())\n    ['products']\n\n    >>> # Drop all tables\n    >>> metadata.drop_all(engine)\n    >>> inspect(engine).get_table_names()\n    []\n"
  },
  {
    "path": "docs/notes/extension/cpp-from-python.rst",
    "content": ".. meta::\n    :description lang=en: Learn modern C++ syntax from Python - side-by-side comparison of Python and C++ code snippets\n    :keywords: C++, Python, C++11, C++14, C++17, C++20, modern C++, syntax comparison, learn C++\n\n=======================\nLearn C++ from Python\n=======================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nModern C++ (C++11, C++14, C++17, C++20) has evolved to include features that make it\nsyntactically similar to Python, making the transition easier for Python developers.\nThis comprehensive guide provides side-by-side comparisons and 1-1 mappings between\nPython and modern C++ code snippets, covering essential programming concepts like\nvariables, data structures, functions, lambdas, classes, and algorithms.\n\nWhether you're a Python developer looking to learn C++ for performance optimization,\nsystem programming, or expanding your programming skills, this tutorial demonstrates\nhow familiar Python patterns translate to modern C++ syntax. Many popular frameworks\nlike PyTorch, TensorFlow, and NumPy use C++ extensions for performance-critical operations,\nespecially in deep learning, LLM training, and CUDA GPU programming. Understanding C++\nenables you to write custom extensions, optimize bottlenecks, and contribute to these\nhigh-performance libraries.\n\nTo learn more about C++ programming, refer to this `C++ cheatsheet <https://cppcheatsheet.com/>`_\nfor additional reference and best practices.\n\n**Complete working examples:** See `cpp_from_py.cpp <https://github.com/crazyguitar/pysheeet/blob/master/src/cpp_from_python/cpp_from_py.cpp>`_\nfor runnable code with integrated Google Test suite. Each function includes Doxygen comments showing the equivalent Python code.\n\nHello World\n-----------\n\nThe traditional first program in any language. Both Python and C++ can print text to\nthe console, though C++ requires including the iostream library and a main function.\n\n**Python**\n\n.. code-block:: python\n\n    print(\"Hello, World!\")\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <iostream>\n\n    int main() {\n        std::cout << \"Hello, World!\" << std::endl;\n        return 0;\n    }\n\nVariables\n---------\n\nModern C++ supports automatic type inference with the ``auto`` keyword, making variable\ndeclarations as concise as Python. The compiler deduces types from initialization values.\n\n**Python**\n\n.. code-block:: python\n\n    x = 10\n    y = 3.14\n    name = \"Alice\"\n    is_valid = True\n\n**C++**\n\n.. code-block:: cpp\n\n    auto x = 10;\n    auto y = 3.14;\n    auto name = \"Alice\";\n    auto is_valid = true;\n\nLists and Vectors\n-----------------\n\nPython lists and C++ vectors are dynamic arrays that can grow and shrink. Both support\nindexing, appending elements, and querying size. C++ vectors require specifying the element\ntype, but modern C++ can infer it from initialization.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [1, 2, 3, 4, 5]\n    numbers.append(6)\n    print(numbers[0])\n    print(len(numbers))\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n    numbers.push_back(6);\n    std::cout << numbers[0] << std::endl;\n    std::cout << numbers.size() << std::endl;\n\nArray Slicing and Access\n-------------------------\n\nPython supports powerful slicing syntax with negative indices and ranges. C++ doesn't have\nbuilt-in slicing, but you can use iterators or create subvectors. Negative indexing requires\nmanual calculation from the end.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n\n    print(numbers[0])\n    print(numbers[-1])\n    print(numbers[2:5])\n    print(numbers[:3])\n    print(numbers[7:])\n    print(numbers[::2])\n    print(numbers[::-1])\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n    std::cout << numbers[0] << std::endl;\n    std::cout << numbers[numbers.size() - 1] << std::endl;\n\n    std::vector<int> slice1(numbers.begin() + 2, numbers.begin() + 5);\n    std::vector<int> slice2(numbers.begin(), numbers.begin() + 3);\n    std::vector<int> slice3(numbers.begin() + 7, numbers.end());\n\n    std::vector<int> every_second;\n    for (size_t i = 0; i < numbers.size(); i += 2) {\n        every_second.push_back(numbers[i]);\n    }\n\n    std::vector<int> reversed(numbers.rbegin(), numbers.rend());\n\nDictionaries and Maps\n---------------------\n\nDictionaries in Python and maps in C++ store key-value pairs. Both allow insertion, lookup,\nand modification using bracket notation. C++ maps keep keys sorted, while Python dicts\nmaintain insertion order (Python 3.7+).\n\n**Python**\n\n.. code-block:: python\n\n    ages = {\"Alice\": 30, \"Bob\": 25}\n    ages[\"Charlie\"] = 35\n    print(ages[\"Alice\"])\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <map>\n    #include <string>\n\n    std::map<std::string, int> ages = {{\"Alice\", 30}, {\"Bob\", 25}};\n    ages[\"Charlie\"] = 35;\n    std::cout << ages[\"Alice\"] << std::endl;\n\nFor Loop\n--------\n\nBoth languages support traditional counting loops and range-based iteration. C++ range-based\nfor loops (C++11) provide syntax similar to Python's for-in loops, making iteration over\ncontainers more readable.\n\n**Python**\n\n.. code-block:: python\n\n    for i in range(5):\n        print(i)\n\n    for item in [1, 2, 3]:\n        print(item)\n\n**C++**\n\n.. code-block:: cpp\n\n    for (int i = 0; i < 5; i++) {\n        std::cout << i << std::endl;\n    }\n\n    for (auto item : {1, 2, 3}) {\n        std::cout << item << std::endl;\n    }\n\nWhile Loop\n----------\n\nWhile loops execute as long as a condition is true. The syntax is nearly identical between\nPython and C++, with C++ requiring parentheses around the condition and braces for the body.\n\n**Python**\n\n.. code-block:: python\n\n    i = 0\n    while i < 5:\n        print(i)\n        i += 1\n\n**C++**\n\n.. code-block:: cpp\n\n    int i = 0;\n    while (i < 5) {\n        std::cout << i << std::endl;\n        i++;\n    }\n\nIf-Else\n-------\n\nConditional statements control program flow based on boolean expressions. C++ requires\nparentheses around conditions and uses braces for blocks, while Python uses indentation.\nBoth support chained conditions with elif/else if.\n\n**Python**\n\n.. code-block:: python\n\n    x = 10\n    if x > 5:\n        print(\"Greater\")\n    elif x == 5:\n        print(\"Equal\")\n    else:\n        print(\"Less\")\n\n**C++**\n\n.. code-block:: cpp\n\n    auto x = 10;\n    if (x > 5) {\n        std::cout << \"Greater\" << std::endl;\n    } else if (x == 5) {\n        std::cout << \"Equal\" << std::endl;\n    } else {\n        std::cout << \"Less\" << std::endl;\n    }\n\nFunctions\n---------\n\nFunctions encapsulate reusable code. Modern C++ supports trailing return type syntax\n(-> type) similar to Python's type hints. The auto keyword allows type inference for\nreturn types when the function body is simple.\n\n**Python**\n\n.. code-block:: python\n\n    def add(a, b):\n        return a + b\n\n    result = add(3, 5)\n\n**C++**\n\n.. code-block:: cpp\n\n    auto add(int a, int b) -> int {\n        return a + b;\n    }\n\n    auto result = add(3, 5);\n\nLambda Functions\n----------------\n\nLambda functions are anonymous functions that can capture variables from their surrounding\nscope. Both Python and C++ support lambdas, making functional programming patterns possible.\nC++ lambdas can specify capture modes (by value, by reference) for more control over variable\nlifetime and performance.\n\n**Python**\n\n.. code-block:: python\n\n    square = lambda x: x * x\n    print(square(5))\n\n    numbers = [1, 2, 3, 4]\n    squared = list(map(lambda x: x * x, numbers))\n\n    # Capturing variables\n    multiplier = 10\n    multiply = lambda x: x * multiplier\n    print(multiply(5))\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    auto square = [](int x) { return x * x; };\n    std::cout << square(5) << std::endl;\n\n    std::vector<int> numbers = {1, 2, 3, 4};\n    std::vector<int> squared;\n    std::transform(numbers.begin(), numbers.end(),\n                   std::back_inserter(squared),\n                   [](int x) { return x * x; });\n\n    // Capturing variables by value [=] or by reference [&]\n    int multiplier = 10;\n    auto multiply = [multiplier](int x) { return x * multiplier; };\n    std::cout << multiply(5) << std::endl;\n\nLambda Capture Modes\n--------------------\n\nC++ lambdas provide explicit control over how variables are captured from the enclosing scope.\nThis is more explicit than Python's implicit closure behavior and allows optimization by\nchoosing between copying values or using references.\n\n**Python**\n\n.. code-block:: python\n\n    x = 10\n    y = 20\n\n    # Implicitly captures x and y\n    add_xy = lambda z: x + y + z\n    print(add_xy(5))\n\n**C++**\n\n.. code-block:: cpp\n\n    int x = 10;\n    int y = 20;\n\n    // Capture by value\n    auto add_xy_val = [x, y](int z) { return x + y + z; };\n    std::cout << add_xy_val(5) << std::endl;\n\n    // Capture by reference\n    auto add_xy_ref = [&x, &y](int z) { return x + y + z; };\n\n    // Capture all by value\n    auto add_all_val = [=](int z) { return x + y + z; };\n\n    // Capture all by reference\n    auto add_all_ref = [&](int z) { return x + y + z; };\n\nList Comprehension\n------------------\n\nPython's list comprehensions provide concise syntax for creating lists. C++ doesn't have\ndirect syntax for this, but you can achieve similar results using loops or STL algorithms\nlike std::transform and std::copy_if.\n\n**Python**\n\n.. code-block:: python\n\n    squares = [x * x for x in range(10)]\n    evens = [x for x in range(10) if x % 2 == 0]\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> squares;\n    for (int x = 0; x < 10; x++) {\n        squares.push_back(x * x);\n    }\n\n    std::vector<int> evens;\n    for (int x = 0; x < 10; x++) {\n        if (x % 2 == 0) {\n            evens.push_back(x);\n        }\n    }\n\nString Operations\n-----------------\n\nBoth languages provide rich string manipulation capabilities. C++ strings are mutable like\nPython strings in terms of concatenation, but individual character access works similarly.\nC++ requires explicit conversion functions for case changes.\n\n**Python**\n\n.. code-block:: python\n\n    s = \"Hello\"\n    s += \" World\"\n    print(len(s))\n    print(s[0])\n    print(s.upper())\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <string>\n    #include <algorithm>\n\n    std::string s = \"Hello\";\n    s += \" World\";\n    std::cout << s.size() << std::endl;\n    std::cout << s[0] << std::endl;\n\n    std::string upper = s;\n    std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper);\n    std::cout << upper << std::endl;\n\nClasses\n-------\n\nObject-oriented programming works similarly in both languages. C++ requires explicit\naccess specifiers (public, private) and constructor initialization lists. Both support\nmember variables and methods, with C++ using :: for scope resolution.\n\n**Python**\n\n.. code-block:: python\n\n    class Person:\n        def __init__(self, name, age):\n            self.name = name\n            self.age = age\n\n        def greet(self):\n            return f\"Hello, I'm {self.name}\"\n\n    p = Person(\"Alice\", 30)\n    print(p.greet())\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <string>\n\n    class Person {\n    public:\n        std::string name;\n        int age;\n\n        Person(std::string name, int age) : name(name), age(age) {}\n\n        std::string greet() {\n            return \"Hello, I'm \" + name;\n        }\n    };\n\n    Person p(\"Alice\", 30);\n    std::cout << p.greet() << std::endl;\n\nOptional Values\n---------------\n\nPython uses None to represent missing values, while C++ (C++17+) provides std::optional\nfor type-safe optional values. This prevents null pointer errors and makes the absence\nof a value explicit in the type system.\n\n**Python**\n\n.. code-block:: python\n\n    def find_value(key):\n        data = {\"a\": 1, \"b\": 2}\n        return data.get(key)\n\n    result = find_value(\"a\")\n    if result is not None:\n        print(result)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <optional>\n    #include <map>\n\n    std::optional<int> find_value(std::string key) {\n        std::map<std::string, int> data = {{\"a\", 1}, {\"b\", 2}};\n        auto it = data.find(key);\n        if (it != data.end()) {\n            return it->second;\n        }\n        return std::nullopt;\n    }\n\n    auto result = find_value(\"a\");\n    if (result.has_value()) {\n        std::cout << result.value() << std::endl;\n    }\n\nSmart Pointers\n--------------\n\nPython handles memory automatically with garbage collection. C++ smart pointers (C++11+)\nprovide automatic memory management through RAII. unique_ptr ensures single ownership,\nwhile shared_ptr allows multiple owners with reference counting.\n\n**Python**\n\n.. code-block:: python\n\n    class Resource:\n        def __init__(self, name):\n            self.name = name\n\n    resource = Resource(\"data\")\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <memory>\n\n    class Resource {\n    public:\n        std::string name;\n        Resource(std::string name) : name(name) {}\n    };\n\n    auto resource = std::make_unique<Resource>(\"data\");\n    auto shared = std::make_shared<Resource>(\"data\");\n\nFile I/O\n--------\n\n**Python**\n\n.. code-block:: python\n\n    with open(\"file.txt\", \"r\") as f:\n        content = f.read()\n\n    with open(\"file.txt\", \"w\") as f:\n        f.write(\"Hello\")\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <fstream>\n    #include <string>\n\n    std::ifstream file(\"file.txt\");\n    std::string content((std::istreambuf_iterator<char>(file)),\n                        std::istreambuf_iterator<char>());\n\n    std::ofstream out(\"file.txt\");\n    out << \"Hello\";\n\nException Handling\n------------------\n\nBoth languages support try-catch exception handling for error management. C++ uses\ntyped exceptions and requires explicit exception types in catch blocks.\n\n**Python**\n\n.. code-block:: python\n\n    try:\n        result = 10 / 0\n    except ZeroDivisionError as e:\n        print(f\"Error: {e}\")\n    finally:\n        print(\"Cleanup\")\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <exception>\n\n    try {\n        if (divisor == 0) {\n            throw std::runtime_error(\"Division by zero\");\n        }\n        result = 10 / divisor;\n    } catch (const std::exception& e) {\n        std::cout << \"Error: \" << e.what() << std::endl;\n    }\n\nTuples\n------\n\nTuples group multiple values together. C++17 introduces structured bindings that allow\ntuple unpacking similar to Python, making it easy to return and destructure multiple values.\n\n**Python**\n\n.. code-block:: python\n\n    point = (10, 20)\n    x, y = point\n    print(x, y)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <tuple>\n\n    auto point = std::make_tuple(10, 20);\n    auto [x, y] = point;\n    std::cout << x << \" \" << y << std::endl;\n\nEnumerate\n---------\n\nPython's enumerate provides index-value pairs during iteration. C++ doesn't have a direct\nequivalent, but you can achieve the same result with traditional indexed loops.\n\n**Python**\n\n.. code-block:: python\n\n    items = [\"a\", \"b\", \"c\"]\n    for i, item in enumerate(items):\n        print(i, item)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <string>\n\n    std::vector<std::string> items = {\"a\", \"b\", \"c\"};\n    for (size_t i = 0; i < items.size(); i++) {\n        std::cout << i << \" \" << items[i] << std::endl;\n    }\n\nFilter and Map\n--------------\n\nPython's filter and map functions apply transformations to sequences. C++ provides\nequivalent functionality through STL algorithms like ``std::copy_if`` and ``std::transform``.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [1, 2, 3, 4, 5]\n    evens = list(filter(lambda x: x % 2 == 0, numbers))\n    doubled = list(map(lambda x: x * 2, numbers))\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n\n    std::vector<int> evens;\n    std::copy_if(numbers.begin(), numbers.end(),\n                 std::back_inserter(evens),\n                 [](int x) { return x % 2 == 0; });\n\n    std::vector<int> doubled;\n    std::transform(numbers.begin(), numbers.end(),\n                   std::back_inserter(doubled),\n                   [](int x) { return x * 2; });\n\nAny and All\n-----------\n\nCheck if any or all elements in a sequence satisfy a condition. C++ provides\n``std::any_of`` and ``std::all_of`` algorithms for these common operations.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [1, 2, 3, 4, 5]\n    has_even = any(x % 2 == 0 for x in numbers)\n    all_positive = all(x > 0 for x in numbers)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n\n    bool has_even = std::any_of(numbers.begin(), numbers.end(),\n                                 [](int x) { return x % 2 == 0; });\n\n    bool all_positive = std::all_of(numbers.begin(), numbers.end(),\n                                     [](int x) { return x > 0; });\n\nSorting\n-------\n\nSort sequences in ascending or descending order. Both languages provide in-place sorting\nand the ability to create sorted copies with custom comparison functions.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [3, 1, 4, 1, 5]\n    numbers.sort()\n\n    sorted_nums = sorted(numbers, reverse=True)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> numbers = {3, 1, 4, 1, 5};\n    std::sort(numbers.begin(), numbers.end());\n\n    std::vector<int> sorted_nums = numbers;\n    std::sort(sorted_nums.begin(), sorted_nums.end(), std::greater<int>());\n\nMin and Max\n-----------\n\nFind the minimum and maximum values in a sequence. C++ uses iterator-based algorithms\nthat return iterators, requiring dereferencing to get the actual values.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [3, 1, 4, 1, 5]\n    print(min(numbers))\n    print(max(numbers))\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <algorithm>\n\n    std::vector<int> numbers = {3, 1, 4, 1, 5};\n    std::cout << *std::min_element(numbers.begin(), numbers.end()) << std::endl;\n    std::cout << *std::max_element(numbers.begin(), numbers.end()) << std::endl;\n\nSum\n---\n\nCalculate the sum of all elements in a sequence. C++ uses ``std::accumulate`` from\nthe numeric library, which can also perform other reduction operations.\n\n**Python**\n\n.. code-block:: python\n\n    numbers = [1, 2, 3, 4, 5]\n    total = sum(numbers)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <numeric>\n\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n    int total = std::accumulate(numbers.begin(), numbers.end(), 0);\n\nZip\n---\n\nIterate over multiple sequences in parallel. Python's zip is built-in, while C++\nrequires manual index-based iteration to achieve the same result.\n\n**Python**\n\n.. code-block:: python\n\n    names = [\"Alice\", \"Bob\"]\n    ages = [30, 25]\n    for name, age in zip(names, ages):\n        print(name, age)\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <vector>\n    #include <string>\n\n    std::vector<std::string> names = {\"Alice\", \"Bob\"};\n    std::vector<int> ages = {30, 25};\n\n    for (size_t i = 0; i < std::min(names.size(), ages.size()); i++) {\n        std::cout << names[i] << \" \" << ages[i] << std::endl;\n    }\n\nDefault Arguments\n-----------------\n\nFunctions can have default parameter values that are used when arguments aren't provided.\nBoth languages support this feature with similar syntax.\n\n**Python**\n\n.. code-block:: python\n\n    def greet(name, greeting=\"Hello\"):\n        return f\"{greeting}, {name}\"\n\n    print(greet(\"Alice\"))\n    print(greet(\"Bob\", \"Hi\"))\n\n**C++**\n\n.. code-block:: cpp\n\n    #include <string>\n\n    std::string greet(std::string name, std::string greeting = \"Hello\") {\n        return greeting + \", \" + name;\n    }\n\n    std::cout << greet(\"Alice\") << std::endl;\n    std::cout << greet(\"Bob\", \"Hi\") << std::endl;\n"
  },
  {
    "path": "docs/notes/extension/index.rst",
    "content": ".. meta::\n    :description lang=en: Python C/C++ extension tutorial covering pybind11, ctypes, cffi, Cython, and the Python C API for high-performance native code\n    :keywords: Python, Python3, pybind11, ctypes, cffi, Cython, C extension, C API, native code, performance, NumPy, shared library\n\nExtension\n=========\n\nPython's flexibility comes at a performance cost. When you need speed for\nnumerical computing, system interfaces, or wrapping existing C/C++ libraries,\nnative extensions bridge the gap. This section covers multiple approaches:\n\n- **ctypes** - Standard library FFI for calling C functions without compilation\n- **Python C API** - Traditional approach with maximum control (legacy)\n- **pybind11** (recommended for C++) - Clean C++11 syntax with automatic type\n  conversions, used by PyTorch, TensorFlow, and SciPy\n- **cffi** - Cleaner alternative to ctypes with PyPy compatibility\n- **Cython** - Python-like syntax that compiles to C for gradual optimization\n\nFor most new projects wrapping C++ code, pybind11 is the recommended choice.\nFor calling existing C libraries without a build step, use ctypes or cffi.\n\n.. toctree::\n   :maxdepth: 1\n\n   python-ctypes\n   python-capi\n   python-cext-modern\n   cpp-from-python\n"
  },
  {
    "path": "docs/notes/extension/python-capi.rst",
    "content": ".. meta::\n    :description lang=en: Comprehensive Python C API tutorial covering native C extension development, module creation, argument parsing, reference counting, GIL management, exception handling, and Python type manipulation including lists, dictionaries, tuples, sets, and strings\n    :keywords: Python, C API, C Extension, PyObject, reference counting, GIL, module, CPython, native extension, PyList, PyDict, PyTuple, PySet, PyUnicode, memory management\n\n============\nPython C API\n============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe Python C API is the traditional and most powerful way to write native extensions\nfor CPython. It provides direct access to Python's internals, giving developers\ncomplete control over memory management, object creation, and interpreter interaction.\nWhile more verbose than modern alternatives like pybind11 or cffi, the C API remains\nessential for maintaining legacy extensions, understanding how Python works internally,\nimplementing performance-critical code paths, or accessing low-level features not\nexposed by higher-level tools. The C API is also the foundation upon which tools like\nCython and pybind11 are built.\n\n.. warning::\n\n    The C extension interface is specific to CPython and may not work on alternative\n    Python implementations like PyPy, Jython, or GraalPython. Additionally, the API\n    can change between Python versions (especially between Python 2 and Python 3),\n    requiring careful version handling and conditional compilation for compatibility.\n\nSimple setup.py\n---------------\n\nBuilding C extensions requires a ``setup.py`` file that tells Python how to compile\nyour C code. The ``distutils`` module (or its modern replacement ``setuptools``)\nhandles cross-platform compilation, linking against the Python library, and generating\nthe correct shared library format (.so on Linux, .dylib on macOS, .pyd on Windows).\nThis minimal example compiles a single C file into a Python-importable module.\n\n.. code-block:: python\n\n    from distutils.core import setup, Extension\n\n    ext = Extension('foo', sources=['foo.c'])\n    setup(name=\"Foo\", version=\"1.0\", ext_modules=[ext])\n\nBuild and install:\n\n.. code-block:: bash\n\n    $ python setup.py build\n    $ python setup.py install\n\nCustomize CFLAGS\n----------------\n\nFor production-quality extensions, you'll want to customize compiler flags to enable\nwarnings, optimizations, or debugging symbols. The ``extra_compile_args`` parameter\npasses flags directly to the C compiler (gcc, clang, or MSVC). Common flags include\n``-Wall`` and ``-Wextra`` for comprehensive warnings, ``-Werror`` to treat warnings\nas errors, ``-O3`` for aggressive optimization, and ``-g`` for debug symbols.\n\n.. code-block:: python\n\n    import sysconfig\n    from distutils.core import setup, Extension\n\n    cflags = sysconfig.get_config_var(\"CFLAGS\")\n    extra_compile_args = cflags.split()\n    extra_compile_args += [\"-Wextra\", \"-Wall\", \"-Werror\"]\n\n    ext = Extension(\n        \"foo\", [\"foo.c\"],\n        extra_compile_args=extra_compile_args\n    )\n\n    setup(name=\"foo\", version=\"1.0\", ext_modules=[ext])\n\nSimple C Extension\n------------------\n\n:Source: `src/cext/capi/simple.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/simple.c>`_\n\nEvery Python C extension follows a standard structure with three essential components:\na module definition (``PyModuleDef``) that describes the module's name and methods,\na method table (``PyMethodDef``) that maps Python function names to C functions, and\nan initialization function (``PyInit_<modulename>``) that Python calls when importing\nthe module. The ``PyDoc_STRVAR`` macro creates docstrings that appear in Python's\n``help()`` system. This example demonstrates a minimal working extension.\n\n**foo.c:**\n\n.. code-block:: c\n\n    #include <Python.h>\n\n    PyDoc_STRVAR(doc_mod, \"Module document\\n\");\n    PyDoc_STRVAR(doc_foo, \"foo() -> None\\n\\nPrint 'foo' to stdout.\");\n\n    static PyObject* foo(PyObject* self)\n    {\n        PyObject* s = PyUnicode_FromString(\"foo\");\n        PyObject_Print(s, stdout, 0);\n        Py_DECREF(s);\n        Py_RETURN_NONE;\n    }\n\n    static PyMethodDef methods[] = {\n        {\"foo\", (PyCFunction)foo, METH_NOARGS, doc_foo},\n        {NULL, NULL, 0, NULL}\n    };\n\n    static struct PyModuleDef module = {\n        PyModuleDef_HEAD_INIT,\n        \"foo\",      /* module name */\n        doc_mod,    /* docstring */\n        -1,         /* size of per-interpreter state (-1 = global) */\n        methods\n    };\n\n    PyMODINIT_FUNC PyInit_foo(void)\n    {\n        return PyModule_Create(&module);\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    $ python setup.py -q build && python setup.py -q install\n    $ python -c \"import foo; foo.foo()\"\n    'foo'\n\nParse Arguments\n---------------\n\n:Source: `src/cext/capi/args.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/args.c>`_\n\nC extension functions receive arguments as ``PyObject*`` pointers and must parse them\ninto C types. The ``PyArg_ParseTuple()`` function handles positional arguments using\nformat codes: ``i`` for int, ``l`` for long, ``d`` for double, ``s`` for string,\n``O`` for any Python object. For keyword arguments, use ``PyArg_ParseTupleAndKeywords()``.\nThe method flags (``METH_NOARGS``, ``METH_O``, ``METH_VARARGS``, ``METH_KEYWORDS``)\ntell Python how to call your function and must match your implementation.\n\n.. code-block:: c\n\n    #include <Python.h>\n\n    // No arguments: METH_NOARGS\n    static PyObject *\n    foo(PyObject *self)\n    {\n        Py_RETURN_NONE;\n    }\n\n    // Single object argument: METH_O\n    static PyObject *\n    bar(PyObject *self, PyObject *arg)\n    {\n        return Py_BuildValue(\"O\", arg);\n    }\n\n    // Multiple positional arguments: METH_VARARGS\n    static PyObject *\n    baz(PyObject *self, PyObject *args)\n    {\n        PyObject *x = NULL, *y = NULL;\n        if (!PyArg_ParseTuple(args, \"OO\", &x, &y)) {\n            return NULL;\n        }\n        return Py_BuildValue(\"OO\", x, y);\n    }\n\n    // Keyword arguments: METH_VARARGS | METH_KEYWORDS\n    static PyObject *\n    qux(PyObject *self, PyObject *args, PyObject *kwargs)\n    {\n        static char *keywords[] = {\"x\", \"y\", NULL};\n        PyObject *x = NULL, *y = NULL;\n        if (!PyArg_ParseTupleAndKeywords(args, kwargs,\n                                         \"O|O\", keywords,\n                                         &x, &y))\n        {\n            return NULL;\n        }\n        if (!y) {\n            y = Py_None;\n        }\n        return Py_BuildValue(\"OO\", x, y);\n    }\n\n    static PyMethodDef methods[] = {\n        {\"foo\", (PyCFunction)foo, METH_NOARGS, NULL},\n        {\"bar\", (PyCFunction)bar, METH_O, NULL},\n        {\"baz\", (PyCFunction)baz, METH_VARARGS, NULL},\n        {\"qux\", (PyCFunction)qux, METH_VARARGS | METH_KEYWORDS, NULL},\n        {NULL, NULL, 0, NULL}\n    };\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import foo\n    >>> foo.foo()\n    >>> foo.bar(3.7)\n    3.7\n    >>> foo.baz(3, 7)\n    (3, 7)\n    >>> foo.qux(x=3, y=7)\n    (3, 7)\n\nRelease the GIL\n---------------\n\n:Source: `src/cext/capi/gil.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/gil.c>`_\n\nThe Global Interpreter Lock (GIL) is a mutex that protects access to Python objects,\npreventing multiple native threads from executing Python bytecode simultaneously.\nWhile the GIL simplifies CPython's implementation, it can become a bottleneck for\nCPU-bound multi-threaded code. For long-running C operations that don't access Python\nobjects (file I/O, network calls, heavy computation), release the GIL using\n``Py_BEGIN_ALLOW_THREADS`` and ``Py_END_ALLOW_THREADS`` macros. This allows other\nPython threads to run concurrently, dramatically improving multi-threaded performance.\n\n.. code-block:: c\n\n    #include <Python.h>\n\n    static PyObject* foo(PyObject* self)\n    {\n        Py_BEGIN_ALLOW_THREADS\n        sleep(3);  // Blocking operation - other threads can run\n        Py_END_ALLOW_THREADS\n        Py_RETURN_NONE;\n    }\n\n**With GIL released** (threads run concurrently):\n\n.. code-block:: bash\n\n    >>> import threading, foo\n    >>> from datetime import datetime\n    >>> def f(n):\n    ...     print(f'{datetime.now()}: thread {n}')\n    ...     foo.foo()\n    >>> ts = [threading.Thread(target=f, args=(n,)) for n in range(3)]\n    >>> [t.start() for t in ts]; [t.join() for t in ts]\n    2018-11-04 20:15:34.860454: thread 0\n    2018-11-04 20:15:34.860592: thread 1  # Same time!\n    2018-11-04 20:15:34.860705: thread 2\n\n**Without GIL release** (threads run sequentially):\n\n.. code-block:: bash\n\n    2018-11-04 20:16:44.055932: thread 0\n    2018-11-04 20:16:47.059718: thread 1  # 3 seconds later\n    2018-11-04 20:16:50.063579: thread 2  # 3 seconds later\n\n.. warning::\n\n    Never call Python C API functions between ``Py_BEGIN_ALLOW_THREADS`` and\n    ``Py_END_ALLOW_THREADS``. The GIL must be held to safely access Python objects.\n\nAcquire the GIL\n---------------\n\n:Source: `src/cext/capi/gil.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/gil.c>`_\n\nWhen threads are created from C code (using pthreads or platform threading APIs),\nthey don't automatically hold the GIL. Before these threads can safely call any\nPython C API function or access Python objects, they must acquire the GIL using\n``PyGILState_Ensure()``. After completing Python operations, release the GIL with\n``PyGILState_Release()`` to allow other threads to run. Failing to acquire the GIL\nbefore accessing Python objects leads to crashes, data corruption, or undefined behavior.\n\n.. code-block:: c\n\n    void *worker_thread(void *arg)\n    {\n        PyObject *result = NULL;\n        PyObject *callback = (PyObject *)arg;\n\n        // Do C work here (no GIL needed)\n        do_heavy_computation();\n\n        // Acquire GIL before calling Python\n        PyGILState_STATE state = PyGILState_Ensure();\n\n        result = PyObject_CallFunction(callback, \"s\", \"Done!\");\n        Py_XDECREF(result);\n\n        // Release GIL\n        PyGILState_Release(state);\n        return NULL;\n    }\n\nRaise Exception\n---------------\n\n:Source: `src/cext/capi/errors.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/errors.c>`_\n\nError handling in C extensions follows a simple pattern: set an exception using\n``PyErr_SetString()`` or ``PyErr_Format()``, then return ``NULL`` to signal failure.\nPython provides built-in exception types as global variables: ``PyExc_ValueError``,\n``PyExc_TypeError``, ``PyExc_RuntimeError``, ``PyExc_KeyError``, ``PyExc_IndexError``,\nand many others. Always check return values from C API functions and propagate errors\nby returning ``NULL`` when an exception is already set.\n\n.. code-block:: c\n\n    static PyObject*\n    foo(PyObject* self)\n    {\n        PyErr_SetString(PyExc_NotImplementedError, \"Not implemented\");\n        return NULL;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import foo; foo.foo()\n    Traceback (most recent call last):\n      File \"<string>\", line 1, in <module>\n    NotImplementedError: Not implemented\n\nCustom Exception\n----------------\n\n:Source: `src/cext/capi/errors.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/errors.c>`_\n\nFor domain-specific errors, create custom exception classes using ``PyErr_NewException()``.\nThe first argument is the fully-qualified name (``\"module.ExceptionName\"``), the second\nis the base class (``NULL`` defaults to ``Exception``), and the third is an optional\ndictionary of class attributes. Register the exception as a module attribute so Python\ncode can catch it with ``except module.ExceptionName``. Remember to ``Py_INCREF()`` the\nexception object before adding it to the module to prevent premature garbage collection.\n\n.. code-block:: c\n\n    static PyObject *FooError;\n\n    static PyObject *\n    foo(PyObject *self)\n    {\n        PyErr_SetString(FooError, \"Something went wrong\");\n        return NULL;\n    }\n\n    PyMODINIT_FUNC PyInit_foo(void)\n    {\n        PyObject *m = PyModule_Create(&module);\n        if (!m) return NULL;\n\n        FooError = PyErr_NewException(\"foo.FooError\", NULL, NULL);\n        Py_INCREF(FooError);\n        PyModule_AddObject(m, \"FooError\", FooError);\n        return m;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import foo; foo.foo()\n    Traceback (most recent call last):\n      File \"<string>\", line 1, in <module>\n    foo.FooError: Something went wrong\n\nReference Counting\n------------------\n\nPython uses reference counting as its primary memory management strategy. Every\n``PyObject*`` maintains a count of how many references point to it. When the count\nreaches zero, the object is deallocated. Use ``Py_INCREF()`` when storing a new\nreference to an object and ``Py_DECREF()`` when you're done with it. The variant\n``Py_XDECREF()`` safely handles ``NULL`` pointers. Understanding reference counting\nis crucial for avoiding memory leaks (forgetting to decref) and use-after-free bugs\n(decrefing too early). Functions that return \"new references\" transfer ownership to\nthe caller, while \"borrowed references\" should not be decrefed.\n\n.. code-block:: c\n\n    static PyObject *\n    getrefcount(PyObject *self, PyObject *a)\n    {\n        return PyLong_FromSsize_t(Py_REFCNT(a));\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import sys, foo\n    >>> l = [1, 2, 3]\n    >>> sys.getrefcount(l[0])\n    104\n    >>> foo.getrefcount(l[0])\n    104\n    >>> i = l[0]  # New reference\n    >>> foo.getrefcount(l[0])\n    105\n\nIterate a List\n--------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nThe Python C API provides two approaches for iterating over sequences. The generic\niterator protocol using ``PyObject_GetIter()`` and ``PyIter_Next()`` works with any\niterable object (lists, tuples, generators, custom iterables). For lists specifically,\nyou can use ``PyList_Size()`` and ``PyList_GetItem()`` for direct indexed access,\nwhich is slightly faster but less flexible. Note that ``PyList_GetItem()`` returns\na borrowed reference, while ``PyIter_Next()`` returns a new reference that must be\ndecrefed after use.\n\n.. code-block:: c\n\n    static PyObject *iter_list(PyObject *self, PyObject *args) {\n        PyObject *list, *iter, *item;\n        if (!PyArg_ParseTuple(args, \"O\", &list)) {\n            return NULL;\n        }\n        iter = PyObject_GetIter(list);\n        if (!iter) return NULL;\n\n        PyObject *result = PyList_New(0);\n        while ((item = PyIter_Next(iter)) != NULL) {\n            PyObject *doubled = PyLong_FromLong(PyLong_AsLong(item) * 2);\n            PyList_Append(result, doubled);\n            Py_DECREF(doubled);\n            Py_DECREF(item);\n        }\n        Py_DECREF(iter);\n        return result;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.iter_list([1, 2, 3])\n    [2, 4, 6]\n\nIterate a Dictionary\n--------------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nDictionary iteration in C uses ``PyDict_Next()`` with a position variable that tracks\nthe iteration state. Initialize ``pos`` to 0 before the loop, and the function updates\nit automatically. The key and value pointers receive borrowed references to the current\nitem on each iteration—do not decref them unless you incref first. This function is\nsafe to use even if the dictionary is modified during iteration (though modifications\nmay cause items to be skipped or visited twice). For read-only iteration, this is the\nmost efficient approach.\n\n.. code-block:: c\n\n    static PyObject *iter_dict(PyObject *self, PyObject *args) {\n        PyObject *dict;\n        if (!PyArg_ParseTuple(args, \"O!\", &PyDict_Type, &dict)) {\n            return NULL;\n        }\n        PyObject *result = PyList_New(0);\n        PyObject *key, *value;\n        Py_ssize_t pos = 0;\n        while (PyDict_Next(dict, &pos, &key, &value)) {\n            PyObject *pair = PyTuple_Pack(2, key, value);\n            PyList_Append(result, pair);\n            Py_DECREF(pair);\n        }\n        return result;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.iter_dict({\"a\": 1, \"b\": 2})\n    [('a', 1), ('b', 2)]\n\nCreate a List\n-------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nCreating Python lists from C involves ``PyList_New()`` to allocate the list and\n``PyList_Append()`` or ``PyList_SetItem()`` to populate it. When using ``PyList_New(n)``\nwith a non-zero size, you must initialize all slots with ``PyList_SetItem()`` before\nthe list is used. ``PyList_SetItem()`` steals a reference to the item, while\n``PyList_Append()`` increments the reference count. For building lists dynamically,\nstart with ``PyList_New(0)`` and use ``PyList_Append()``.\n\n.. code-block:: c\n\n    static PyObject *list_demo(PyObject *self) {\n        PyObject *list = PyList_New(0);\n        PyList_Append(list, PyLong_FromLong(1));\n        PyList_Append(list, PyLong_FromLong(2));\n        PyList_Append(list, PyLong_FromLong(3));\n        return list;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.list_demo()\n    [1, 2, 3]\n\nCreate a Dictionary\n-------------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nPython dictionaries are created with ``PyDict_New()`` and populated using\n``PyDict_SetItem()`` for PyObject keys or ``PyDict_SetItemString()`` for C string keys.\nBoth functions increment the reference count of the value, so you may need to decref\ntemporary objects after insertion. For retrieving values, ``PyDict_GetItem()`` and\n``PyDict_GetItemString()`` return borrowed references (or NULL if the key doesn't exist),\nwhile ``PyDict_GetItemWithError()`` distinguishes between missing keys and errors.\n\n.. code-block:: c\n\n    static PyObject *dict_demo(PyObject *self) {\n        PyObject *dict = PyDict_New();\n        PyDict_SetItemString(dict, \"name\", PyUnicode_FromString(\"Python\"));\n        PyDict_SetItemString(dict, \"version\", PyLong_FromLong(3));\n        return dict;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.dict_demo()\n    {'name': 'Python', 'version': 3}\n\nCreate a Tuple\n--------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nTuples are immutable sequences, and the C API reflects this: once created and populated,\ntuple contents cannot be changed. Use ``PyTuple_New(n)`` to create a tuple of size n,\nthen ``PyTuple_SetItem()`` to fill each slot (this steals a reference). Alternatively,\n``Py_BuildValue()`` with parentheses format creates tuples directly from C values:\n``\"(isd)\"`` creates a tuple of (int, string, double). ``PyTuple_Pack(n, ...)`` is\nanother convenient way to create tuples from existing PyObject pointers.\n\n.. code-block:: c\n\n    static PyObject *tuple_demo(PyObject *self) {\n        return Py_BuildValue(\"(isd)\", 1, \"hello\", 3.14);\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.tuple_demo()\n    (1, 'hello', 3.14)\n\nCreate a Set\n------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nSets are unordered collections of unique hashable objects. Create an empty set with\n``PySet_New(NULL)`` or initialize from an iterable with ``PySet_New(iterable)``.\nAdd elements with ``PySet_Add()``, which returns 0 on success or -1 on error (e.g.,\nif the object is unhashable). Check membership with ``PySet_Contains()``, remove\nelements with ``PySet_Discard()`` (no error if missing) or ``PySet_Pop()`` to remove\nand return an arbitrary element. Duplicate additions are silently ignored.\n\n.. code-block:: c\n\n    static PyObject *set_demo(PyObject *self) {\n        PyObject *set = PySet_New(NULL);\n        PySet_Add(set, PyLong_FromLong(1));\n        PySet_Add(set, PyLong_FromLong(2));\n        PySet_Add(set, PyLong_FromLong(2));  /* duplicate ignored */\n        PySet_Add(set, PyLong_FromLong(3));\n        return set;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.set_demo()\n    {1, 2, 3}\n\nString Operations\n-----------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nPython 3 strings are Unicode objects, created with ``PyUnicode_FromString()`` for\nUTF-8 encoded C strings or ``PyUnicode_FromFormat()`` for printf-style formatting.\nConcatenate strings with ``PyUnicode_Concat()``, which returns a new string object.\nFor extracting C strings, use ``PyUnicode_AsUTF8()`` (returns a borrowed pointer valid\nonly while the object exists) or ``PyUnicode_AsUTF8AndSize()`` to also get the length.\nThe format function supports ``%s`` for C strings, ``%S`` for Python objects (calls str()),\n``%R`` for repr(), and ``%d``, ``%u``, ``%ld`` for integers.\n\n.. code-block:: c\n\n    static PyObject *str_demo(PyObject *self) {\n        PyObject *s1 = PyUnicode_FromString(\"Hello\");\n        PyObject *s2 = PyUnicode_FromString(\" World\");\n        PyObject *result = PyUnicode_Concat(s1, s2);\n        Py_DECREF(s1);\n        Py_DECREF(s2);\n        return result;\n    }\n\n    static PyObject *str_format(PyObject *self, PyObject *args) {\n        const char *name;\n        int age;\n        if (!PyArg_ParseTuple(args, \"si\", &name, &age)) {\n            return NULL;\n        }\n        return PyUnicode_FromFormat(\"%s is %d years old\", name, age);\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.str_demo()\n    'Hello World'\n    >>> types_demo.str_format(\"Alice\", 30)\n    'Alice is 30 years old'\n\nBytes Operations\n----------------\n\n:Source: `src/cext/capi/types_demo.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/types_demo.c>`_\n\nBytes objects represent immutable sequences of bytes, essential for binary data, file I/O,\nand network protocols. Create bytes from C strings with ``PyBytes_FromString()`` (copies\nuntil null terminator) or ``PyBytes_FromStringAndSize()`` for binary data with embedded\nnulls. Access the internal buffer with ``PyBytes_AsString()`` (borrowed pointer) and get\nthe length with ``PyBytes_Size()``. For mutable byte sequences, use ``PyByteArray_*``\nfunctions instead. When parsing arguments, use format code ``s#`` for bytes with length\nor ``S`` to accept only bytes objects.\n\n.. code-block:: c\n\n    static PyObject *bytes_demo(PyObject *self) {\n        return PyBytes_FromString(\"hello bytes\");\n    }\n\n    static PyObject *bytes_len(PyObject *self, PyObject *args) {\n        PyObject *bytes;\n        if (!PyArg_ParseTuple(args, \"S\", &bytes)) {\n            return NULL;\n        }\n        return PyLong_FromSsize_t(PyBytes_Size(bytes));\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import types_demo\n    >>> types_demo.bytes_demo()\n    b'hello bytes'\n    >>> types_demo.bytes_len(b\"hello\")\n    5\n\nSimple Class\n------------\n\nDefining custom Python types in C requires creating a ``PyTypeObject`` structure that\ndescribes the type's behavior, memory layout, and methods. The minimal type needs\n``tp_name`` (fully qualified name like ``\"module.ClassName\"``), ``tp_basicsize`` (size\nof the instance struct), and ``tp_new`` (allocation function, often ``PyType_GenericNew``).\nCall ``PyType_Ready()`` to finalize the type before use, then add it to your module with\n``PyModule_AddObject()``. The ``Py_TPFLAGS_DEFAULT`` flag enables standard type features.\n\n.. code-block:: c\n\n    typedef struct {\n        PyObject_HEAD\n    } FooObject;\n\n    static PyTypeObject FooType = {\n        PyVarObject_HEAD_INIT(NULL, 0)\n        .tp_name = \"foo.Foo\",\n        .tp_doc = \"Foo objects\",\n        .tp_basicsize = sizeof(FooObject),\n        .tp_itemsize = 0,\n        .tp_flags = Py_TPFLAGS_DEFAULT,\n        .tp_new = PyType_GenericNew\n    };\n\n    PyMODINIT_FUNC PyInit_foo(void)\n    {\n        PyObject *m = NULL;\n        if (PyType_Ready(&FooType) < 0)\n            return NULL;\n        if ((m = PyModule_Create(&module)) == NULL)\n            return NULL;\n        Py_INCREF(&FooType);\n        PyModule_AddObject(m, \"Foo\", (PyObject *)&FooType);\n        return m;\n    }\n\nClass with Members and Methods\n------------------------------\n\nFull-featured Python classes in C require implementing several type slots. Use\n``PyMemberDef`` to expose C struct fields as Python attributes (with automatic type\nconversion), and ``PyMethodDef`` for instance methods. Implement ``tp_new`` for memory\nallocation (called before ``__init__``), ``tp_init`` for initialization (the ``__init__``\nmethod), and ``tp_dealloc`` for cleanup (must decref all owned PyObject members and call\n``tp_free``). The ``Py_TPFLAGS_BASETYPE`` flag allows the type to be subclassed in Python.\n\n.. code-block:: c\n\n    #include <Python.h>\n    #include <structmember.h>\n\n    typedef struct {\n        PyObject_HEAD\n        PyObject *foo;\n        PyObject *bar;\n    } FooObject;\n\n    static void\n    Foo_dealloc(FooObject *self)\n    {\n        Py_XDECREF(self->foo);\n        Py_XDECREF(self->bar);\n        Py_TYPE(self)->tp_free((PyObject *)self);\n    }\n\n    static PyObject *\n    Foo_new(PyTypeObject *type, PyObject *args, PyObject *kw)\n    {\n        FooObject *self = (FooObject *)type->tp_alloc(type, 0);\n        if (self) {\n            self->foo = PyUnicode_FromString(\"\");\n            self->bar = PyUnicode_FromString(\"\");\n        }\n        return (PyObject *)self;\n    }\n\n    static int\n    Foo_init(FooObject *self, PyObject *args, PyObject *kw)\n    {\n        static char *keywords[] = {\"foo\", \"bar\", NULL};\n        PyObject *foo = NULL, *bar = NULL;\n\n        if (!PyArg_ParseTupleAndKeywords(args, kw, \"|OO\", keywords, &foo, &bar))\n            return -1;\n\n        if (foo) { Py_INCREF(foo); Py_XDECREF(self->foo); self->foo = foo; }\n        if (bar) { Py_INCREF(bar); Py_XDECREF(self->bar); self->bar = bar; }\n        return 0;\n    }\n\n    static PyMemberDef Foo_members[] = {\n        {\"foo\", T_OBJECT_EX, offsetof(FooObject, foo), 0, \"foo attribute\"},\n        {\"bar\", T_OBJECT_EX, offsetof(FooObject, bar), 0, \"bar attribute\"},\n        {NULL}\n    };\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import foo\n    >>> o = foo.Foo('hello', 'world')\n    >>> o.foo\n    'hello'\n    >>> o.bar\n    'world'\n\nProperties (Getter/Setter)\n--------------------------\n\nFor computed attributes or attributes requiring validation, use ``PyGetSetDef`` to define\nproperties with custom getter and setter functions. The getter receives the instance and\nan optional closure pointer, returning a new reference to the attribute value. The setter\nreceives the instance, new value (or NULL for deletion), and closure, returning 0 on\nsuccess or -1 on error. This provides the same functionality as Python's ``@property``\ndecorator but with C-level control over attribute access.\n\n.. code-block:: c\n\n    static PyObject *\n    Foo_getfoo(FooObject *self, void *closure)\n    {\n        Py_INCREF(self->foo);\n        return self->foo;\n    }\n\n    static int\n    Foo_setfoo(FooObject *self, PyObject *value, void *closure)\n    {\n        if (!value || !PyUnicode_Check(value)) {\n            PyErr_SetString(PyExc_TypeError, \"value must be a string\");\n            return -1;\n        }\n        Py_INCREF(value);\n        Py_XDECREF(self->foo);\n        self->foo = value;\n        return 0;\n    }\n\n    static PyGetSetDef Foo_getsetters[] = {\n        {\"foo\", (getter)Foo_getfoo, (setter)Foo_setfoo, \"foo property\", NULL},\n        {NULL}\n    };\n\nCalling Python from C\n---------------------\n\nC extensions often need to call back into Python code—invoking callbacks, calling methods\non objects, or using Python library functions. Use ``PyObject_CallFunction()`` for calling\nwith C-style format arguments, ``PyObject_CallObject()`` with a tuple of arguments, or\n``PyObject_CallMethod()`` to call a method by name. Always check if the callable is valid\nwith ``PyCallable_Check()`` before calling, and check the return value for NULL (indicating\nan exception was raised). The GIL must be held when calling Python functions.\n\n.. code-block:: c\n\n    static PyObject *\n    call_callback(PyObject *self, PyObject *args)\n    {\n        PyObject *callback = NULL;\n        PyObject *result = NULL;\n\n        if (!PyArg_ParseTuple(args, \"O:callback\", &callback))\n            return NULL;\n\n        if (!PyCallable_Check(callback)) {\n            PyErr_SetString(PyExc_TypeError, \"argument must be callable\");\n            return NULL;\n        }\n\n        // Call: callback(\"Hello from C!\")\n        result = PyObject_CallFunction(callback, \"s\", \"Hello from C!\");\n        return result;\n    }\n\nOutput:\n\n.. code-block:: bash\n\n    >>> import foo\n    >>> foo.call_callback(print)\n    Hello from C!\n\nPerformance Comparison\n----------------------\n\n:Source: `src/cext/capi/simple.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/capi/simple.c>`_\n\nC extensions provide dramatic speedups for CPU-bound operations by eliminating Python's\ninterpreter overhead. This recursive Fibonacci benchmark demonstrates typical performance\ngains of 50-100x compared to pure Python. The speedup comes from avoiding Python object\ncreation, method dispatch, and bytecode interpretation on each function call. For\nnumerical code, the gains can be even larger when combined with SIMD instructions or\nmulti-threading (with GIL released). However, the overhead of crossing the Python/C\nboundary means C extensions are most beneficial for compute-intensive inner loops rather\nthan simple operations.\n\n.. code-block:: c\n\n    static unsigned long fib(unsigned long n)\n    {\n        if (n < 2) return n;\n        return fib(n - 1) + fib(n - 2);\n    }\n\n    static PyObject *\n    py_fib(PyObject *self, PyObject *args)\n    {\n        unsigned long n = 0;\n        if (!PyArg_ParseTuple(args, \"k\", &n)) return NULL;\n        return PyLong_FromUnsignedLong(fib(n));\n    }\n\n.. code-block:: python\n\n    >>> from time import time\n    >>> def py_fib(n):\n    ...     if n < 2: return n\n    ...     return py_fib(n-1) + py_fib(n-2)\n    ...\n    >>> s = time(); _ = py_fib(35); e = time(); e - s\n    4.953313112258911\n    >>> import foo\n    >>> s = time(); _ = foo.fib(35); e = time(); e - s\n    0.04628586769104004\n"
  },
  {
    "path": "docs/notes/extension/python-cext-modern.rst",
    "content": ".. meta::\n    :description lang=en: Comprehensive guide to modern Python C/C++ extensions covering pybind11, ctypes, cffi, and Cython with practical examples for building high-performance Python modules, NumPy integration, GIL management, and class bindings\n    :keywords: Python, Python3, pybind11, C Extension, C++, ctypes, cffi, Cython, NumPy, Performance, GIL, Native Code, PyTorch, TensorFlow, SciPy, shared library\n\n=========================\nModern C/C++ Extensions\n=========================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nPython's flexibility and ease of use come at a performance cost compared to compiled\nlanguages. When you need maximum speed for numerical computing, real-time processing,\nsystem interfaces, or wrapping existing C/C++ libraries, native extensions bridge the\ngap between Python's productivity and C/C++'s performance. This guide covers modern\napproaches to building Python extensions: **pybind11** (the recommended choice for C++\nprojects), **ctypes/cffi** (for calling C libraries without compilation), and **Cython**\n(for Python-like syntax that compiles to C). We compare each approach using the same\nFibonacci benchmark to help you choose the right tool for your specific use case.\n\n.. note::\n\n    For most C++ projects, **pybind11** is the recommended choice. It's used by\n    major machine learning frameworks like PyTorch, TensorFlow, and scientific\n    computing libraries like SciPy. pybind11 provides clean C++11 syntax, automatic\n    type conversions between Python and C++ types, excellent NumPy integration for\n    numerical computing, and seamless exception handling across language boundaries.\n\nComparison of Approaches\n------------------------\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────────────┐\n    │                    C/C++ EXTENSION APPROACHES                           │\n    ├─────────────────────────────────────────────────────────────────────────┤\n    │  APPROACH      │ PROS                      │ CONS                       │\n    ├────────────────┼───────────────────────────┼────────────────────────────┤\n    │  pybind11      │ Clean C++11 syntax        │ Requires C++ compiler      │\n    │  (recommended) │ Automatic type conversion │ Compile step needed        │\n    │                │ NumPy support built-in    │ C++ only (not C)           │\n    │                │ Used by PyTorch, SciPy    │                            │\n    ├────────────────┼───────────────────────────┼────────────────────────────┤\n    │  ctypes        │ No compilation needed     │ Manual type declarations   │\n    │                │ Standard library          │ Error-prone                │\n    │                │ Works with any C library  │ No C++ support             │\n    ├────────────────┼───────────────────────────┼────────────────────────────┤\n    │  cffi          │ No compilation needed     │ Extra dependency           │\n    │                │ Cleaner than ctypes       │ No C++ support             │\n    │                │ PyPy compatible           │                            │\n    ├────────────────┼───────────────────────────┼────────────────────────────┤\n    │  Cython        │ Python-like syntax        │ New language to learn      │\n    │                │ Gradual optimization      │ Build complexity           │\n    │                │ Good NumPy integration    │ Debugging harder           │\n    ├────────────────┼───────────────────────────┼────────────────────────────┤\n    │  Python C API  │ Maximum control           │ Very verbose               │\n    │  (legacy)      │ No dependencies           │ Manual refcounting         │\n    │                │                           │ Error-prone                │\n    └────────────────┴───────────────────────────┴────────────────────────────┘\n\n    When to use what:\n    - Wrapping existing C++ library → pybind11\n    - Wrapping existing C library  → ctypes or cffi\n    - Writing new high-perf code   → pybind11 or Cython\n    - Need PyPy compatibility      → cffi or Cython\n    - Quick prototype              → ctypes\n\npybind11: Getting Started\n-------------------------\n\n:Source: `src/cext/example.cpp <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/example.cpp>`_\n\npybind11 is a lightweight header-only C++ library that creates Python bindings for\nexisting C++ code. Unlike the traditional Python C API, pybind11 uses modern C++11\nfeatures like variadic templates and lambda expressions to provide a clean, intuitive\nsyntax. Installation is simple with ``pip install pybind11``, and it requires only a\nC++11 compatible compiler (GCC 4.8+, Clang 3.3+, MSVC 2015+). The ``PYBIND11_MODULE``\nmacro defines the module entry point, and ``m.def()`` binds C++ functions to Python\nwith automatic type conversion for common types like int, float, string, and STL\ncontainers.\n\n**Simple function binding:**\n\n.. code-block:: cpp\n\n    // example.cpp\n    #include <pybind11/pybind11.h>\n\n    int add(int a, int b) {\n        return a + b;\n    }\n\n    // Fibonacci for performance comparison\n    unsigned long fib(unsigned long n) {\n        if (n < 2) return n;\n        return fib(n - 1) + fib(n - 2);\n    }\n\n    PYBIND11_MODULE(example, m) {\n        m.doc() = \"Example pybind11 module\";\n        m.def(\"add\", &add, \"Add two integers\",\n              pybind11::arg(\"a\"), pybind11::arg(\"b\"));\n        m.def(\"fib\", &fib, \"Compute Fibonacci number\");\n    }\n\n**Build with setup.py:**\n\n.. code-block:: python\n\n    # setup.py\n    from pybind11.setup_helpers import Pybind11Extension, build_ext\n    from setuptools import setup\n\n    ext_modules = [\n        Pybind11Extension(\n            \"example\",\n            [\"example.cpp\"],\n        ),\n    ]\n\n    setup(\n        name=\"example\",\n        ext_modules=ext_modules,\n        cmdclass={\"build_ext\": build_ext},\n    )\n\n.. code-block:: bash\n\n    $ pip install pybind11\n    $ python setup.py build_ext --inplace\n    $ python -c \"import example; print(example.add(1, 2))\"\n    3\n    $ python -c \"import example; print(example.fib(35))\"\n    9227465\n\npybind11: Classes\n-----------------\n\n:Source: `src/cext/vector.cpp <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/vector.cpp>`_\n\npybind11 makes binding C++ classes to Python straightforward with the ``py::class_``\ntemplate. You can expose constructors with ``def(py::init<...>())``, member variables\nwith ``def_readwrite()`` or ``def_readonly()``, and methods with ``def()``. Python\nspecial methods like ``__repr__``, ``__add__``, ``__eq__`` are bound by name, enabling\nnatural Python syntax for your C++ objects. Default argument values are supported via\n``py::arg()``, and you can add docstrings to improve the Python help() experience.\n\n.. code-block:: cpp\n\n    // vector.cpp\n    #include <pybind11/pybind11.h>\n    #include <cmath>\n\n    namespace py = pybind11;\n\n    class Vector2D {\n    public:\n        double x, y;\n\n        Vector2D(double x = 0, double y = 0) : x(x), y(y) {}\n\n        double length() const {\n            return std::sqrt(x * x + y * y);\n        }\n\n        Vector2D operator+(const Vector2D& other) const {\n            return Vector2D(x + other.x, y + other.y);\n        }\n\n        std::string repr() const {\n            return \"Vector2D(\" + std::to_string(x) + \", \" + std::to_string(y) + \")\";\n        }\n    };\n\n    PYBIND11_MODULE(vector, m) {\n        py::class_<Vector2D>(m, \"Vector2D\")\n            .def(py::init<double, double>(),\n                 py::arg(\"x\") = 0, py::arg(\"y\") = 0)\n            .def_readwrite(\"x\", &Vector2D::x)\n            .def_readwrite(\"y\", &Vector2D::y)\n            .def(\"length\", &Vector2D::length)\n            .def(\"__add__\", &Vector2D::operator+)\n            .def(\"__repr__\", &Vector2D::repr);\n    }\n\n.. code-block:: python\n\n    >>> from vector import Vector2D\n    >>> v1 = Vector2D(3, 4)\n    >>> v1.length()\n    5.0\n    >>> v2 = Vector2D(1, 2)\n    >>> v3 = v1 + v2\n    >>> v3\n    Vector2D(4.0, 6.0)\n\npybind11: NumPy Integration\n---------------------------\n\n:Source: `src/cext/numpy_example.cpp <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/numpy_example.cpp>`_\n\npybind11 provides first-class NumPy support through the ``pybind11/numpy.h`` header,\nenabling high-performance numerical computing without data copying. The ``py::array_t<T>``\ntemplate wraps NumPy arrays with type safety, and ``unchecked<N>()`` provides fast,\nbounds-check-free access for performance-critical inner loops. Arrays can be modified\nin-place using ``mutable_unchecked<N>()``, or new arrays can be created and returned.\nThis zero-copy approach is essential for scientific computing where large datasets\nwould be expensive to duplicate.\n\n.. code-block:: cpp\n\n    // numpy_example.cpp\n    #include <pybind11/pybind11.h>\n    #include <pybind11/numpy.h>\n\n    namespace py = pybind11;\n\n    // Element-wise multiply (modifies in place)\n    void multiply_inplace(py::array_t<double> arr, double factor) {\n        auto buf = arr.mutable_unchecked<1>();\n        for (py::ssize_t i = 0; i < buf.shape(0); i++) {\n            buf(i) *= factor;\n        }\n    }\n\n    // Return new array\n    py::array_t<double> add_arrays(py::array_t<double> a, py::array_t<double> b) {\n        auto buf_a = a.unchecked<1>();\n        auto buf_b = b.unchecked<1>();\n\n        if (buf_a.shape(0) != buf_b.shape(0)) {\n            throw std::runtime_error(\"Arrays must have same length\");\n        }\n\n        auto result = py::array_t<double>(buf_a.shape(0));\n        auto buf_r = result.mutable_unchecked<1>();\n\n        for (py::ssize_t i = 0; i < buf_a.shape(0); i++) {\n            buf_r(i) = buf_a(i) + buf_b(i);\n        }\n        return result;\n    }\n\n    PYBIND11_MODULE(numpy_example, m) {\n        m.def(\"multiply_inplace\", &multiply_inplace);\n        m.def(\"add_arrays\", &add_arrays);\n    }\n\n.. code-block:: python\n\n    >>> import numpy as np\n    >>> from numpy_example import multiply_inplace, add_arrays\n    >>> arr = np.array([1.0, 2.0, 3.0])\n    >>> multiply_inplace(arr, 2.0)\n    >>> arr\n    array([2., 4., 6.])\n    >>> a = np.array([1.0, 2.0, 3.0])\n    >>> b = np.array([4.0, 5.0, 6.0])\n    >>> add_arrays(a, b)\n    array([5., 7., 9.])\n\npybind11: Releasing the GIL\n---------------------------\n\n:Source: `src/cext/gil_example.cpp <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/gil_example.cpp>`_\n\nPython's Global Interpreter Lock (GIL) prevents true parallel execution of Python code\nacross threads. For CPU-intensive C++ operations or blocking I/O that doesn't need\nPython objects, releasing the GIL allows other Python threads to run concurrently.\npybind11 provides ``py::gil_scoped_release`` for RAII-style GIL management—the GIL is\nreleased when the object is created and automatically reacquired when it goes out of\nscope. This pattern is essential for multi-threaded applications where C++ code performs\nheavy computation while Python threads handle other tasks like UI updates or network I/O.\n\n.. code-block:: cpp\n\n    #include <pybind11/pybind11.h>\n    #include <thread>\n    #include <chrono>\n\n    namespace py = pybind11;\n\n    // Slow operation that releases GIL\n    void slow_operation(int seconds) {\n        // Release GIL while doing CPU work\n        py::gil_scoped_release release;\n\n        // Simulate slow work\n        std::this_thread::sleep_for(std::chrono::seconds(seconds));\n    }\n\n    // CPU-intensive work\n    unsigned long fib_nogil(unsigned long n) {\n        py::gil_scoped_release release;\n\n        std::function<unsigned long(unsigned long)> fib_impl;\n        fib_impl = [&](unsigned long n) -> unsigned long {\n            if (n < 2) return n;\n            return fib_impl(n - 1) + fib_impl(n - 2);\n        };\n        return fib_impl(n);\n    }\n\n    PYBIND11_MODULE(gil_example, m) {\n        m.def(\"slow_operation\", &slow_operation);\n        m.def(\"fib_nogil\", &fib_nogil);\n    }\n\n.. code-block:: python\n\n    import threading\n    from datetime import datetime\n    from gil_example import slow_operation\n\n    def worker(n):\n        print(f\"{datetime.now()}: Thread {n} starting\")\n        slow_operation(1)\n        print(f\"{datetime.now()}: Thread {n} done\")\n\n    # Threads run in parallel because GIL is released\n    threads = [threading.Thread(target=worker, args=(i,)) for i in range(3)]\n    for t in threads: t.start()\n    for t in threads: t.join()\n\nctypes: Quick C Library Access\n------------------------------\n\n:Source: `src/cext/fib.c <https://github.com/crazyguitar/pysheeet/blob/master/src/cext/fib.c>`_\n\nctypes is Python's built-in foreign function interface (FFI) that lets you call C\nfunctions from shared libraries (.so, .dylib, .dll) without writing any C wrapper code\nor compiling Python extensions. It's ideal for quick prototyping, accessing system\nlibraries, or wrapping existing C code when you don't want a build step. The key\nrequirement is declaring function signatures with ``argtypes`` and ``restype``—without\nthese declarations, ctypes assumes all arguments and return values are C ``int``, which\ncauses silent bugs or crashes with other types like ``double`` or pointers.\n\n.. code-block:: c\n\n    // fib.c - Compile: gcc -shared -fPIC -o libfib.so fib.c\n    unsigned long fib(unsigned long n) {\n        if (n < 2) return n;\n        return fib(n - 1) + fib(n - 2);\n    }\n\n    double add_doubles(double a, double b) {\n        return a + b;\n    }\n\n.. code-block:: python\n\n    import ctypes\n    from ctypes import c_ulong, c_double\n\n    # Load shared library\n    # Linux: libfib.so, macOS: libfib.dylib, Windows: fib.dll\n    lib = ctypes.CDLL(\"./libfib.so\")\n\n    # Declare function signatures (important for non-int types!)\n    lib.fib.argtypes = [c_ulong]\n    lib.fib.restype = c_ulong\n\n    lib.add_doubles.argtypes = [c_double, c_double]\n    lib.add_doubles.restype = c_double\n\n    # Call functions\n    print(lib.fib(35))        # 9227465\n    print(lib.add_doubles(1.5, 2.5))  # 4.0\n\nctypes: Structures and Pointers\n-------------------------------\n\nWorking with C structures and pointers in ctypes requires careful type declarations\nthat mirror the C memory layout exactly. Define structures by subclassing ``Structure``\nand specifying ``_fields_`` as a list of (name, type) tuples in the same order as the\nC struct. Use ``POINTER(Type)`` to create pointer types and ``byref(obj)`` to pass\nobjects by reference (equivalent to ``&obj`` in C). This approach is more error-prone\nthan pybind11 but works without any compilation step.\n\n.. code-block:: c\n\n    // point.c\n    typedef struct {\n        double x;\n        double y;\n    } Point;\n\n    double distance(Point* p1, Point* p2) {\n        double dx = p2->x - p1->x;\n        double dy = p2->y - p1->y;\n        return sqrt(dx*dx + dy*dy);\n    }\n\n    void scale_point(Point* p, double factor) {\n        p->x *= factor;\n        p->y *= factor;\n    }\n\n.. code-block:: python\n\n    import ctypes\n    from ctypes import Structure, c_double, POINTER, byref\n    import math\n\n    class Point(Structure):\n        _fields_ = [(\"x\", c_double), (\"y\", c_double)]\n\n    lib = ctypes.CDLL(\"./libpoint.so\")\n\n    lib.distance.argtypes = [POINTER(Point), POINTER(Point)]\n    lib.distance.restype = c_double\n\n    lib.scale_point.argtypes = [POINTER(Point), c_double]\n    lib.scale_point.restype = None\n\n    # Create points\n    p1 = Point(0, 0)\n    p2 = Point(3, 4)\n\n    # Pass by reference\n    dist = lib.distance(byref(p1), byref(p2))\n    print(f\"Distance: {dist}\")  # 5.0\n\n    # Modify in place\n    lib.scale_point(byref(p2), 2.0)\n    print(f\"Scaled: ({p2.x}, {p2.y})\")  # (6.0, 8.0)\n\ncffi: Cleaner Foreign Function Interface\n----------------------------------------\n\ncffi (C Foreign Function Interface) provides a cleaner, more Pythonic API than ctypes\nfor calling C code. Instead of Python type objects, you declare C function signatures\nusing actual C syntax in ``ffi.cdef()``, which can often be copied directly from header\nfiles. cffi handles type conversions automatically and provides better error messages.\nIt's also the recommended FFI for PyPy, where it runs significantly faster than ctypes.\nInstall with ``pip install cffi``.\n\n.. code-block:: python\n\n    from cffi import FFI\n\n    ffi = FFI()\n\n    # Declare C functions (copy from header file)\n    ffi.cdef(\"\"\"\n        unsigned long fib(unsigned long n);\n        double add_doubles(double a, double b);\n    \"\"\")\n\n    # Load library\n    lib = ffi.dlopen(\"./libfib.so\")\n\n    # Call functions - types are automatic!\n    print(lib.fib(35))              # 9227465\n    print(lib.add_doubles(1.5, 2.5))  # 4.0\n\n**cffi with inline C code (ABI mode):**\n\n.. code-block:: python\n\n    from cffi import FFI\n\n    ffi = FFI()\n    ffi.cdef(\"unsigned long fib(unsigned long n);\")\n\n    # Compile C code inline\n    ffi.set_source(\"_fib_cffi\", \"\"\"\n        unsigned long fib(unsigned long n) {\n            if (n < 2) return n;\n            return fib(n - 1) + fib(n - 2);\n        }\n    \"\"\")\n\n    ffi.compile()\n\n    # Now import and use\n    from _fib_cffi import lib\n    print(lib.fib(35))\n\nCython: Python-like Syntax\n--------------------------\n\nCython is a programming language that combines Python syntax with C data types,\ncompiling to efficient C code. It's excellent for gradual optimization: start with\npure Python code, then add type declarations to critical sections for dramatic speedups.\nCython supports three levels of optimization: pure Python (``def``), typed Python\n(``def`` with type hints), and pure C functions (``cdef``). The ``cdef`` functions\nrun at C speed but can only be called from other Cython code, so you typically wrap\nthem with a ``def`` function for Python access. Install with ``pip install cython``.\n\n.. code-block:: cython\n\n    # fib.pyx\n    def fib_py(n):\n        \"\"\"Pure Python - slow\"\"\"\n        if n < 2:\n            return n\n        return fib_py(n - 1) + fib_py(n - 2)\n\n    def fib_typed(long n):\n        \"\"\"With type hints - faster\"\"\"\n        if n < 2:\n            return n\n        return fib_typed(n - 1) + fib_typed(n - 2)\n\n    cdef unsigned long _fib_c(unsigned long n):\n        \"\"\"C function - fastest\"\"\"\n        if n < 2:\n            return n\n        return _fib_c(n - 1) + _fib_c(n - 2)\n\n    def fib_c(unsigned long n):\n        \"\"\"Python wrapper for C function\"\"\"\n        return _fib_c(n)\n\n.. code-block:: python\n\n    # setup.py\n    from setuptools import setup\n    from Cython.Build import cythonize\n\n    setup(\n        ext_modules=cythonize(\"fib.pyx\"),\n    )\n\n.. code-block:: bash\n\n    $ python setup.py build_ext --inplace\n    $ python -c \"from fib import fib_c; print(fib_c(35))\"\n    9227465\n\nPerformance Comparison\n----------------------\n\nUnderstanding the performance characteristics of each approach helps you choose the\nright tool. This benchmark compares all approaches using recursive Fibonacci (n=35),\na CPU-bound task that highlights the overhead of Python's interpreter. Native code\nachieves 50-100x speedups by eliminating Python object creation, method dispatch, and\nbytecode interpretation. The actual speedup varies by workload—numerical code with\nNumPy integration can see even larger gains, while I/O-bound code benefits less.\n\n.. code-block:: python\n\n    from time import time\n\n    def benchmark(func, n=35, runs=3):\n        times = []\n        for _ in range(runs):\n            start = time()\n            result = func(n)\n            times.append(time() - start)\n        return min(times), result\n\n    # Pure Python\n    def fib_python(n):\n        if n < 2: return n\n        return fib_python(n - 1) + fib_python(n - 2)\n\n    # Results (approximate, varies by system):\n    #\n    # | Approach          | Time (s) | Speedup |\n    # |-------------------|----------|---------|\n    # | Pure Python       | 2.50     | 1x      |\n    # | Cython (typed)    | 0.08     | 31x     |\n    # | Cython (cdef)     | 0.05     | 50x     |\n    # | ctypes            | 0.05     | 50x     |\n    # | cffi              | 0.05     | 50x     |\n    # | pybind11          | 0.04     | 62x     |\n    # | pybind11 (no GIL) | 0.04     | 62x     |\n\nBest Practices\n--------------\n\nFollowing these guidelines will help you write efficient, maintainable, and safe\nnative extensions. The most common mistakes are holding the GIL during long operations\n(blocking other threads), copying large arrays unnecessarily (killing performance),\nand ignoring error handling (causing crashes instead of Python exceptions).\n\n**Do:**\n\n- Use pybind11 for new C++ bindings\n- Release the GIL for CPU-intensive operations\n- Use NumPy arrays for numerical data (zero-copy with pybind11)\n- Declare types in ctypes/cffi (avoid silent bugs)\n- Profile before optimizing—find the real bottleneck\n\n**Don't:**\n\n- Write Python C API code for new projects (use pybind11)\n- Hold the GIL during blocking I/O or long computations\n- Copy large arrays between Python and C (use views)\n- Ignore error handling in C code\n- Optimize prematurely—Python is often fast enough\n\n**Error handling:**\n\n.. code-block:: cpp\n\n    // pybind11 - exceptions automatically convert to Python\n    double divide(double a, double b) {\n        if (b == 0) {\n            throw std::runtime_error(\"Division by zero\");\n        }\n        return a / b;\n    }\n\n    // In Python: raises RuntimeError\n\n**Memory management:**\n\n.. code-block:: cpp\n\n    // pybind11 handles Python object refcounting automatically\n    // For raw pointers, use return value policies:\n\n    py::class_<Parent>(m, \"Parent\")\n        .def(\"get_child\", &Parent::get_child,\n             py::return_value_policy::reference_internal);  // Child tied to Parent lifetime\n"
  },
  {
    "path": "docs/notes/extension/python-ctypes.rst",
    "content": ".. meta::\n    :description lang=en: Python ctypes tutorial for loading shared libraries, calling C functions, handling pointers, structures, and error handling\n    :keywords: Python, ctypes, shared library, .so, .dylib, .dll, C library, FFI, foreign function interface, pointers, structures\n\n======\nctypes\n======\n\n:Source: `src/basic/cext_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/cext_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nctypes is Python's built-in foreign function interface (FFI) library that allows\ncalling functions in shared libraries (.so on Linux, .dylib on macOS, .dll on\nWindows) without writing any C code or compiling extensions. It's ideal for quick\nprototyping, accessing system libraries, or wrapping existing C libraries when you\ndon't want a compilation step. However, ctypes requires manual type declarations\nand careful memory management, making it more error-prone than alternatives like\ncffi or pybind11 for complex use cases.\n\nLoading Shared Libraries\n------------------------\n\nctypes provides platform-specific loaders for shared libraries. Use ``CDLL`` for\nstandard C calling convention or ``WinDLL`` on Windows for stdcall convention.\nThe library search follows system conventions: ``LD_LIBRARY_PATH`` on Linux,\n``DYLD_LIBRARY_PATH`` on macOS, and ``PATH`` on Windows.\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL\n\n    # Load platform-specific C library\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\")\n    elif platform.system() == \"Linux\":\n        libc = CDLL(\"libc.so.6\")\n    else:\n        from ctypes import windll\n        libc = windll.msvcrt\n\n    # Call printf\n    libc.printf(b\"Hello from C: %d\\n\", 42)\n\nLoading Custom Libraries\n------------------------\n\nFor your own compiled C libraries, provide the full path or ensure the library\nis in the system's library search path. The ``use_errno=True`` parameter enables\nproper errno handling for error detection.\n\n.. code-block:: python\n\n    from ctypes import CDLL\n    from ctypes.util import find_library\n    import os\n\n    # Load from current directory\n    lib_path = os.path.join(os.path.dirname(__file__), \"libfoo.so\")\n    lib = CDLL(lib_path, use_errno=True)\n\n    # Or use find_library for system libraries\n    libm_path = find_library(\"m\")  # finds libm.so or libm.dylib\n    if libm_path:\n        libm = CDLL(libm_path)\n\nBasic Type Mapping\n------------------\n\nctypes provides Python equivalents for C types. Always declare argument types\n(``argtypes``) and return types (``restype``) explicitly to avoid crashes and\nensure correct data conversion. Without these declarations, ctypes assumes all\narguments and return values are C ``int``.\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, c_double, c_char_p\n\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\")\n    else:\n        libc = CDLL(\"libc.so.6\")\n\n    # Declare function signature: double atof(const char *)\n    libc.atof.argtypes = [c_char_p]\n    libc.atof.restype = c_double\n\n    result = libc.atof(b\"3.14159\")\n    print(result)  # 3.14159\n\n    # Common type mappings:\n    # c_int      -> int\n    # c_long     -> long\n    # c_double   -> double\n    # c_char_p   -> char* (bytes in Python)\n    # c_void_p   -> void*\n    # c_bool     -> _Bool\n\nCalling strlen and abs\n----------------------\n\nSimple examples calling standard C library functions with proper type declarations.\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, c_char_p, c_size_t\n\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\")\n    else:\n        libc = CDLL(\"libc.so.6\")\n\n    # strlen\n    libc.strlen.argtypes = [c_char_p]\n    libc.strlen.restype = c_size_t\n    assert libc.strlen(b\"hello\") == 5\n\n    # abs (default int types work)\n    assert libc.abs(-42) == 42\n\nCalling sqrt from libm\n----------------------\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, c_double\n\n    if platform.system() == \"Darwin\":\n        libm = CDLL(\"libm.dylib\")\n    else:\n        libm = CDLL(\"libm.so.6\")\n\n    libm.sqrt.argtypes = [c_double]\n    libm.sqrt.restype = c_double\n\n    result = libm.sqrt(16.0)\n    assert abs(result - 4.0) < 1e-10\n\nCalling C Functions\n-------------------\n\nThis example shows a complete workflow: compile a C library, load it with ctypes,\nand call functions with proper type declarations. The Fibonacci function\ndemonstrates the performance benefit of C code called from Python.\n\n**C source (fib.c):**\n\n.. code-block:: c\n\n    // Compile:\n    //   Linux: gcc -shared -fPIC -o libfib.so fib.c\n    //   macOS: clang -shared -fPIC -o libfib.dylib fib.c\n\n    unsigned long fib(unsigned long n) {\n        if (n < 2) return n;\n        return fib(n - 1) + fib(n - 2);\n    }\n\n**Python usage:**\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, c_ulong\n\n    # Load the library\n    if platform.system() == \"Darwin\":\n        lib = CDLL(\"./libfib.dylib\")\n    else:\n        lib = CDLL(\"./libfib.so\")\n\n    # Declare types\n    lib.fib.argtypes = [c_ulong]\n    lib.fib.restype = c_ulong\n\n    # Call the function\n    print(lib.fib(35))  # 9227465\n\n**Performance comparison:**\n\n.. code-block:: python\n\n    >>> from time import time\n    >>> def py_fib(n):\n    ...     if n < 2: return n\n    ...     return py_fib(n - 1) + py_fib(n - 2)\n    ...\n    >>> s = time(); _ = py_fib(35); e = time(); e - s\n    4.918856859207153\n    >>> s = time(); _ = lib.fib(35); e = time(); e - s\n    0.07283687591552734\n\nPointers and byref\n------------------\n\nUse ``byref()`` to pass arguments by reference (like ``&var`` in C) and\n``POINTER()`` to create pointer types. ``byref()`` is more efficient than\n``pointer()`` when you only need to pass a reference to a function.\n\n.. code-block:: python\n\n    from ctypes import c_int, byref, pointer, POINTER\n\n    # Pointer to integer\n    value = c_int(42)\n    ptr = pointer(value)\n    assert ptr.contents.value == 42\n\n    # Modify through pointer\n    ptr.contents.value = 100\n    assert value.value == 100\n\n    # byref creates a lightweight pointer for passing to C functions\n    ref = byref(value)\n\n    # Create pointer type and array\n    IntPtr = POINTER(c_int)\n    arr = (c_int * 3)(1, 2, 3)\n\nStructures\n----------\n\nDefine C structures by subclassing ``Structure`` and specifying ``_fields_``.\nField order must match the C struct exactly. Use ``_pack_`` to control alignment\nif needed (e.g., ``_pack_ = 1`` for packed structs).\n\n.. code-block:: python\n\n    import ctypes\n    import math\n\n    class Point(ctypes.Structure):\n        _fields_ = [\n            (\"x\", ctypes.c_double),\n            (\"y\", ctypes.c_double),\n        ]\n\n    # Create and use\n    p = Point(3.0, 4.0)\n    assert p.x == 3.0\n    assert p.y == 4.0\n\n    # Calculate distance\n    distance = math.sqrt(p.x ** 2 + p.y ** 2)\n    assert abs(distance - 5.0) < 1e-10\n\nNested Structures\n-----------------\n\n.. code-block:: python\n\n    import ctypes\n\n    class Point(ctypes.Structure):\n        _fields_ = [\n            (\"x\", ctypes.c_double),\n            (\"y\", ctypes.c_double),\n        ]\n\n    class Rectangle(ctypes.Structure):\n        _fields_ = [\n            (\"top_left\", Point),\n            (\"bottom_right\", Point),\n        ]\n\n    rect = Rectangle(Point(0, 10), Point(10, 0))\n    assert rect.top_left.x == 0\n    assert rect.top_left.y == 10\n    assert rect.bottom_right.x == 10\n\nArrays\n------\n\nCreate C arrays using the multiplication syntax ``type * size``. Arrays can be\ninitialized with values and accessed like Python lists. They automatically\nconvert to pointers when passed to C functions.\n\n.. code-block:: python\n\n    from ctypes import c_int, c_double, c_char\n\n    # Integer array\n    IntArray5 = c_int * 5\n    arr = IntArray5(1, 2, 3, 4, 5)\n    assert arr[0] == 1\n    assert arr[4] == 5\n\n    # Modify elements\n    arr[0] = 100\n    assert list(arr) == [100, 2, 3, 4, 5]\n\n    # Character array (C string buffer)\n    buf = (c_char * 256)()\n    buf.value = b\"Hello\"\n    assert buf.value == b\"Hello\"\n\n    # Double array for numerical work\n    data = (c_double * 3)(1.1, 2.2, 3.3)\n    assert abs(sum(data) - 6.6) < 1e-10\n\nArray in Structure\n------------------\n\nStructures can contain fixed-size arrays as members.\n\n.. code-block:: python\n\n    import ctypes\n\n    class Data(ctypes.Structure):\n        _fields_ = [\n            (\"values\", ctypes.c_int * 5),\n            (\"count\", ctypes.c_int)\n        ]\n\n    d = Data()\n    d.count = 5\n    for i in range(5):\n        d.values[i] = i * 10\n\n    assert list(d.values) == [0, 10, 20, 30, 40]\n    assert d.count == 5\n\nUsing cffi\n----------\n\ncffi is a cleaner alternative to ctypes with better PyPy compatibility. It uses\nC-like declarations instead of Python type objects.\n\n.. code-block:: python\n\n    import platform\n    from cffi import FFI\n\n    ffi = FFI()\n    ffi.cdef(\"\"\"\n        int abs(int x);\n        size_t strlen(const char *s);\n        double sqrt(double x);\n    \"\"\")\n\n    if platform.system() == \"Darwin\":\n        libc = ffi.dlopen(\"libc.dylib\")\n        libm = ffi.dlopen(\"libm.dylib\")\n    else:\n        libc = ffi.dlopen(\"libc.so.6\")\n        libm = ffi.dlopen(\"libm.so.6\")\n\n    assert libc.abs(-42) == 42\n    assert libc.strlen(b\"hello\") == 5\n    assert abs(libm.sqrt(16.0) - 4.0) < 1e-10\n\nError Handling\n--------------\n\nWhen calling C functions that set errno on failure, use ``use_errno=True`` when\nloading the library and ``get_errno()`` to retrieve the error code. This is\nessential for proper error handling with system calls.\n\n.. code-block:: python\n\n    import os\n    import platform\n    from ctypes import CDLL, get_errno\n\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\", use_errno=True)\n    else:\n        libc = CDLL(\"libc.so.6\", use_errno=True)\n\n    # Try to open a non-existent file\n    fd = libc.open(b\"/nonexistent/path\", 0)\n    if fd == -1:\n        errno = get_errno()\n        errmsg = f\"open failed: {os.strerror(errno)}\"\n        print(errmsg)  # open failed: No such file or directory\n\nCallbacks\n---------\n\nctypes can create C-callable function pointers from Python functions using\n``CFUNCTYPE``. This is useful for C libraries that accept callback functions,\nsuch as ``qsort()`` or event handlers.\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, CFUNCTYPE, POINTER, c_int, c_void_p, cast, sizeof\n\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\")\n    else:\n        libc = CDLL(\"libc.so.6\")\n\n    # Define callback type: int (*compare)(const void*, const void*)\n    CMPFUNC = CFUNCTYPE(c_int, c_void_p, c_void_p)\n\n    def py_compare(a, b):\n        \"\"\"Compare function for qsort\"\"\"\n        a_val = cast(a, POINTER(c_int)).contents.value\n        b_val = cast(b, POINTER(c_int)).contents.value\n        return a_val - b_val\n\n    # Create C callback from Python function\n    c_compare = CMPFUNC(py_compare)\n\n    # Use with qsort\n    arr = (c_int * 5)(5, 2, 8, 1, 9)\n    libc.qsort(arr, len(arr), sizeof(c_int), c_compare)\n    print(list(arr))  # [1, 2, 5, 8, 9]\n\nString Handling\n---------------\n\nC strings require careful handling in ctypes. Use ``c_char_p`` for immutable\nstrings and ``create_string_buffer()`` for mutable buffers. Always use bytes\n(``b\"string\"``) not str when passing to C functions.\n\n.. code-block:: python\n\n    import platform\n    from ctypes import CDLL, c_char_p, c_int, create_string_buffer\n\n    if platform.system() == \"Darwin\":\n        libc = CDLL(\"libc.dylib\")\n    else:\n        libc = CDLL(\"libc.so.6\")\n\n    # Immutable string (c_char_p)\n    libc.puts.argtypes = [c_char_p]\n    libc.puts(b\"Hello, World!\")\n\n    # Mutable buffer for functions that modify strings\n    buf = create_string_buffer(100)\n    libc.strcpy(buf, b\"Hello\")\n    libc.strcat(buf, b\", World!\")\n    print(buf.value)  # b'Hello, World!'\n\n    # Get string length\n    libc.strlen.argtypes = [c_char_p]\n    libc.strlen.restype = c_int\n    print(libc.strlen(b\"Hello\"))  # 5\n"
  },
  {
    "path": "docs/notes/hpc/index.rst",
    "content": ".. meta::\n    :description lang=en: High-Performance Computing (HPC) cheat sheet covering Slurm job scheduling, cluster management, distributed computing, and GPU workloads\n    :keywords: HPC, High-Performance Computing, Slurm, job scheduler, cluster computing, distributed training, GPU cluster, supercomputing, workload management\n\nHPC\n===\n\nHigh-Performance Computing (HPC) enables large-scale computational workloads\nacross clusters of powerful machines. This section covers essential HPC tools\nand workflows, with a focus on Slurm—the most widely used job scheduler in\nHPC environments. Whether you're running distributed machine learning training,\nscientific simulations, or batch processing jobs, these guides will help you\nefficiently manage and schedule workloads on HPC clusters.\n\n.. toctree::\n   :maxdepth: 1\n\n   slurm\n"
  },
  {
    "path": "docs/notes/hpc/slurm.rst",
    "content": ".. meta::\n    :description lang=en: Slurm cheat sheet for HPC job scheduling, batch jobs, distributed training, MPI, Enroot containers, and cluster management commands\n    :keywords: Slurm, HPC, job scheduler, sbatch, srun, salloc, distributed training, MPI, Enroot, Pyxis, cluster computing, workload manager, GPU cluster, machine learning, PyTorch distributed\n\n=====\nSlurm\n=====\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSlurm (Simple Linux Utility for Resource Management) is an open-source job scheduling\nand workload management system widely used in high-performance computing (HPC) clusters.\nIt is designed to efficiently allocate resources, manage queues, and dispatch jobs across\nlarge numbers of compute nodes. Slurm is the de facto standard for HPC job scheduling,\npowering many of the world's largest supercomputers and GPU clusters used for scientific\ncomputing, machine learning, and AI research.\n\nFor machine learning engineers, Slurm provides a straightforward way to launch distributed\ntraining jobs for large language models (LLMs) and deep learning workloads sharded across\nmultiple nodes. Unlike container orchestration systems like Kubernetes—which often require\nadditional components such as Kubeflow for ML workload scheduling—Slurm provides a simpler,\nHPC-focused workflow. Users can submit and manage jobs directly with commands like ``srun``,\n``sbatch``, and ``squeue``, without needing to configure complex orchestration layers.\n\nThis cheat sheet covers essential Slurm commands and workflows for submitting jobs, managing\nresources, running distributed training with PyTorch, launching MPI applications, and using\ncontainers with Enroot and Pyxis.\n\nSlurm Info\n----------\n\n``sinfo`` is a command used to display general information about a Slurm-managed\ncluster, such as the number of available nodes and partitions. It also allows\nusers to check the status of nodes, including identifying nodes that are down or\nin an error state.\n\n.. code-block:: bash\n\n    # show slurm general info\n    sinfo\n\n    # show partition info\n    sinfo -s\n    sinfo --summarize\n\n    # show partition info\n    PARTITION=dev\n    sinfo -p ${PARTITION}\n\n    # show nodes in idle state\n    sinfo --state=idle\n\n    # show nodes in specific format\n    sinfo -o \"%n %P %t %C\"  # node, partition, state, CPUs\n\n    # show GPU info (if configured)\n    sinfo -o \"%n %G\"\n\nNode Info\n---------\n\n``scontrol show node`` provides detailed information about specific nodes in the\ncluster, including CPU count, memory, GPU resources, and current state. This is\nuseful for debugging node issues or verifying hardware configurations.\n\n.. code-block:: bash\n\n    # show all nodes info\n    scontrol show nodes\n\n    # show specific node info\n    scontrol show node compute-01\n\n    # show node in parseable format\n    scontrol show node compute-01 --oneliner\n\n    # list all hostnames\n    scontrol show hostnames\n\n    # expand node range to list\n    scontrol show hostnames compute-[0-5]\n\n    # show nodes in idle state\n    sinfo --state=idle\n\nSubmit Jobs\n-----------\n\nLaunching a job across multiple nodes in the foreground is straightforward with\n``srun``. For example, running ``srun hostname`` will execute the ``hostname`` command\non multiple allocated nodes and wait for all nodes to return results. With ``srun``,\nusers can easily specify:\n\n* Number of nodes to run the job on (``--nodes``)\n* Partition or queue to submit the job to (``--partition``)\n* Time limit for the job (``--time``), ensuring compute resources are automatically released when the job finishes or reaches its time limit\n\nBy default, ``srun`` runs interactively in the foreground, making it ideal for quick\ntests or debugging. For longer or batch jobs, users typically pair srun with job\nscripts submitted via ``sbatch``.\n\n.. code-block:: bash\n\n    # Submit a job to a compute node\n    srun -N1 hostname\n\n    # Submit a job on specific nodes\n    srun --nodelist=compute-[0-5] hostname\n\n    # Submit a job to a specific partition\n    PARTITION=dev\n    srun -p ${PARTITION} --nodelist=compute-[0-5] hostname\n\n    # Submit a job via srun on 2 nodes (using dd to simulate a high CPU consume job)\n    srun -N2 dd if=/dev/zero of=/dev/null\n\n    # Submit a job with time constrain.\n    # - minute\n    # - minute:second\n    # - hours:minutes:seconds\n    # - days-hours\n    # - days-hours:minutes\n    # - days-hours:minutes:seconds\n    #\n    # ex: The following job will be timeout after 1m30s\n    srun -N2 --time=01:30 dd if=/dev/zero of=/dev/null\n\n    # login to a node\n    srun -N 1 --pty /bin/bash\n\nAlloc Nodes\n-----------\n\nIn some scenarios, users may need exclusive, interactive access to specific\nnodes for experiments or testing. For instance, a researcher running benchmarking\ntests might require all benchmarks to execute on the same fixed nodes to ensure\nconsistent and reproducible results. The salloc command is used to request and\nallocate resources interactively. By using ``salloc``, users can reserve a specific\nnumber of nodes, ensuring that no other jobs are scheduled on them during the\nexperiment. This isolation helps avoid resource contention that could affect\nbenchmarking or performance measurements. For example, the following command\nallocates 2 nodes for an interactive session:\n\n.. code-block:: bash\n\n    # Allocte 2 nodes and submit a job on those allocated nodes\n    salloc -N 2\n    srun hostname\n    exit # release allocated nodes\n\n\n    # Allocate nodes on a specific partition\n    PARTITION=dev\n    salloc -N 2 -p ${PARTITION}\n\n.. image:: images/salloc.svg\n\n\n.. note::\n\n    ``salloc`` is particularly useful for:\n\n    * Interactive debugging\n    * Benchmarking and performance testing\n    * Running exploratory workloads without writing a full job script\n\nCancel Jobs\n-----------\n\nUsers may occasionally need to cancel their jobs for various reasons. For example,\na cluster administrator may announce maintenance (such as upgrading system libraries),\nrequiring users to terminate running jobs. In other cases, a job might hang or\nconsume compute resources unnecessarily, making cancellation necessary. Slurm\nprovides the ``scancel`` command to terminate jobs cleanly. Example usage:\n\n.. code-block:: bash\n\n    # cancel a job\n    scancel \"${jobid}\"\n\n    # cancel a job and disable warnings\n    scancel -q \"${jobid}\"\n\n    # cancel all jobs which are belong to an account\n    scancel --account=\"${account}\"\n\n    # cancel all jobs which are belong to a partition\n    scancel --partition=\"${partition}\"\n\n    # cancel all pending jobs\n    scancel --state=\"PENDING\"\n\n    # cancel all running jobs\n    scancel --state=\"RUNNING\"\n\n    # cancel all jobs\n    squeue -l | awk '{ print $ 1}' | grep '[[:digit:]].*' | xargs scancel\n\n    # cancel all jobs (using state option)\n    for s in \"RUNNING\" \"PENDING\" \"SUSPAND\"; do scancel --state=\"$s\"; done\n\n\nSubmit Batch Jobs\n-----------------\n\n``sbatch`` is a Slurm command used to submit batch jobs for execution on a\ncluster. Unlike ``srun``, which typically runs jobs interactively in the foreground,\n``sbatch`` is designed for running long, non-interactive workloads in the background.\nThis allows users to submit jobs without maintaining an active SSH session to the\ncluster's head node, making it ideal for large-scale or time-consuming tasks.\n\nA typical workflow involves writing a Slurm job script containing job specifications\n(such as the number of nodes, time limits, and partitions) and one or more srun\ncommands to execute programs. Submitting this script with sbatch queues the job,\nand Slurm automatically schedules it based on available resources. Example sbatch\nscript:\n\n.. code-block:: bash\n\n    #!/bin/bash\n    #SBATCH --nodelist=compute-[0-1]\n    #SBATCH --output=logs/%x_%j.out\n    #SBATCH --error=logs/%x_%j.out\n    #SBATCH --ntasks-per-node=8\n\n    master_addr=\"$(scontrol show hostnames | sort | head -n 1)\"\n    srun hostname\n    srun torchrun \\\n      --nproc-per-node=\"$SLURM_NPROCS\" \\\n      --nnodes=\"$SLURM_NNODES\"\n      --master-addr=\"${master_addr}\" \\\n      --master-port=29500 \\\n      ${PWD}/train.py\n\n    # sbatch job.sh\n\nSubmit mpirun\n-------------\n\nIn some HPC environments, users may not be able to load the MPI module directly\non the head (login) node due to security restrictions, minimal software installations,\nor site policies that restrict heavy workloads on login nodes. In such cases,\nthe workflow is to use Slurm to allocate compute nodes and launch ``mpirun`` from\nwithin one of those nodes. From there, mpirun orchestrates the execution of the\nMPI program across all allocated nodes.\n\n.. image:: images/mpirun.svg\n\n.. code-block:: bash\n\n    #!/bin/bash\n\n    # Usage:\n    #\n    # rank_per_node=8\n    # salloc -N 4\n    # ./mpirun.sh ${rank_per_node} ${binary}\n\n    launch() {\n      local rank_per_node=\"${1}\"\n      local args=(\"${@:2}\")\n      local arr\n      local hosts\n      local cmd\n\n      mapfile -t arr < <(scontrol show hostnames | sort)\n      OLDIFS=\"${IFS}\"\n      IFS=\",\"\n      hosts=\"${arr[*]}\"\n      IFS=\"${OLDIFS}\"\n\n      cmd=\"$(cat <<EOF\n\n      mpirun \\\n      -N \"${rank_per_node}\" \\\n      --allow-run-as-root \\\n      --host \"${hosts}\" \\\n      --mca pml ^cm --mca plm_rsh_no_tree_spawn 1 \\\n      --mca btl_tcp_if_exclude lo,docker0,veth_def_agent \\\n      --mca plm_rsh_num_concurrent \"${#arr[@]}\" \\\n      --mca btl_vader_single_copy_mechanism none \\\n      --oversubscribe \\\n      --tag-output \\\n      ${args[@]}\n\n    EOF\n    )\"\n\n      # submit a mpirun job to a single node because mpirun will launch jobs on\n      # other nodes. Therfore, it is required to spcify -N 1 when using srun.\n      srun -N 1 bash -c \"${cmd}\"\n    }\n\n    launch \"$@\"\n\nSubmit Jobs with Enroot\n-----------------------\n\nSometimes, users need to run jobs with custom dependencies that differ from the\ncluster’s system-wide environment. For example, if the cluster is configured with\nNCCL 2.23 but a user wants to benchmark NCCL 2.27, it’s often impractical to ask\nadministrators to upgrade or modify system libraries for a single experiment.\nOne workaround is to create a custom container (e.g., Docker image) with the\nrequired dependencies and launch jobs from that environment. However, running\ncontainers in HPC environments often requires extra setup and special flags\ndue to namespace isolation and security restrictions.\n\nTo simplify this process, `Enroot <https://github.com/NVIDIA/enroot>`_ provides\na lightweight alternative to traditional container runtimes. It allows users to\nrun isolated filesystem in an HPC setting with minimal overhead, similar to\n``chroot``, while still granting direct access to system hardware (e.g., GPUs, interconnects).\nThis makes it ideal for ML and HPC workflows that require fine-tuned performance.\n\nBuilding on Enroot, `Pyxis <https://github.com/NVIDIA/pyxis>`_ is a Slurm plugin\nthat enables launching jobs inside Enroot containers without writing additional\nwrapper scripts. Users can specify Enroot squash file and runtime options directly\nin their sbatch or srun commands, integrating container workflows seamlessly into\nSlurm job submission. The following snippet shows serveral to launch a job through\nEnroot and Pyxis.\n\n.. code-block:: bash\n\n   # build an enroot sqsh file\n   $ enroot import -o \"${output_sqsh}\" \"dockerd://${image}\"\n\n   # submit a job with enroot\n   srun --container-image \"${output_sqsh}\" \\\n     --container-mounts \"/fsx:/fsx,/nfs:/nfs\" \\\n     --ntasks-per-node=8 \\\n     ${cmd}\n\n   # submit a mpi job with enroot\n   srun --container-image \"${output_sqsh}\" \\\n     --container-mounts \"/fsx:/fsx,/nfs:/nfs\" \\\n     --ntasks-per-node=8 \\\n     --mpi=pmix \\\n     ${cmd}\n\nJob Status\n----------\n\nTo monitor the status of jobs in a Slurm-managed cluster, users can use the\n``squeue`` command. This tool shows essential details about submitted jobs, such\nas job IDs, job names, partitions, allocated nodes, and job states. Common job\nstates include:\n\n* RUNNING – The job is actively running on allocated resources.\n* PENDING – The job is waiting in the queue for resources to become available.\n* FAILED – The job has failed due to errors or unmet conditions.\n\nIf a job is stuck, fails, or behaves unexpectedly, you can terminate it with\nthe ``scancel`` command and resubmit after fixing the issue.\n\n.. code-block:: bash\n\n   # check all Slurm jobs status\n   squeue\n\n   # check user's job status\n   squeue --user=${USER}\n\nReservation\n-----------\n\nFrom an administrator’s perspective, it may be necessary to reserve specific\nnodes to prevent Slurm from scheduling jobs on them. For example, nodes\nexperiencing hardware or software issues—such as network failures or disk\nerrors—should be reserved to avoid job failures. Reserving nodes allows\nadministrators to troubleshoot, repair, or perform maintenance without\ninterfering with active workloads. The following snippet demonstrates how to\ncreate reservations through ``scontrol`` for nodes and check their reservation status.\n\n.. code-block:: bash\n\n    # reserve nodes for a user to test\n    # - minute\n    # - minute:second\n    # - hours:minutes:seconds\n    # - days-hours\n    # - days-hours:minutes\n    # - days-hours:minutes:seconds\n    #\n    # ex: reserve all nodes 120m for maintenance\n    scontrol create reservation ReservationName=maintenance \\\n        starttime=now duration=120 user=root flags=maint,ignore_jobs nodes=ALL\n\n    # must specify reservation; otherwise, the job will not run\n    srun --reservation=maintain ping 8.8.8.8 2>&1 > /dev/null\n\n    # show reservations\n    scontrol show res\n\n    # delete a reservation\n    scontrol delete ReservationName=maintain\n\n    # drain nodes for maintenance. ex: nodes=compute-[01-02],compute-08\n    scontrol update NodeName=compute-[01-02],compute-08 State=DOWN Reason=”maintenance”\n\n    # resume nodes\n    scontrol update NodeName=compute-[01-02],compute-08 State=Resume\n\nAccounting\n----------\n\nSlurm includes a powerful accounting and resource management system that allows\nadministrators to control how computing resources are allocated and ensure fair\nusage across all users. Through this system, administrators can configure fairshare\nscheduling, job priority policies, and resource limits to prevent individual\nusers or groups from monopolizing cluster resources for extended periods.\n\nWith ``fairshare``, Slurm dynamically adjusts job priorities based on historical\nresource usage, ensuring that users who have consumed fewer resources get higher\npriority in the job queue, while heavy users may experience lower priority until\nusage balances out. This helps maintain equitable access in multi-user HPC environments.\nAdministrators manage these policies through Slurm’s database-backed accounting\nsystem (``slurmdbd``) and commands like:\n\n.. code-block:: bash\n\n    # create a cluster (the clustername should be identical to ClusterName in slurm.conf)\n    sacctmgr add cluster clustername\n\n    # create an account\n    sacctmgr -i add account worker description=\"worker account\" Organization=\"your.org\"\n\n    # create an user and add to an account\n    sacctmgr create user name=worker DefaultAccount=default\n\n    # create an user and add to additional accounts\n    sacctmgr -i create user \"worker\" account=\"worker\" adminlevel=\"None\"\n\n    # modify user fairshare configuration\n    sacctmgr modify user where name=\"worker\" account=\"worker\" set fairshare=0\n\n    # remove an user from an account\n    sacctmgr remove user \"worker\" where account=\"worker\"\n\n    # show all users\n    sacctmgr show account\n\n    # show all users with associations\n    sacctmgr show account -s\n\n\nJob History\n-----------\n\n``sacct`` displays accounting data for completed and running jobs. This is essential\nfor analyzing job performance, debugging failed jobs, and tracking resource usage\nover time. Unlike ``squeue`` which only shows active jobs, ``sacct`` can retrieve\nhistorical job information from the Slurm accounting database.\n\n.. code-block:: bash\n\n    # show recent jobs for current user\n    sacct\n\n    # show jobs from specific date range\n    sacct --starttime=2024-01-01 --endtime=2024-01-31\n\n    # show specific job details\n    sacct -j ${jobid} --format=JobID,JobName,Partition,State,ExitCode,Elapsed\n\n    # show detailed resource usage\n    sacct -j ${jobid} --format=JobID,MaxRSS,MaxVMSize,AveRSS,AveCPU\n\n    # show all fields\n    sacct -j ${jobid} --format=ALL\n\n    # show jobs with specific state\n    sacct --state=FAILED --starttime=2024-01-01\n\n    # common format for debugging\n    sacct -j ${jobid} --format=JobID,JobName,State,ExitCode,DerivedExitCode,Comment\n\nEnvironment Variables\n---------------------\n\nSlurm sets various environment variables when a job runs, providing information\nabout the job's allocation and configuration. These variables are essential for\nwriting portable job scripts that adapt to different resource allocations.\n\n.. code-block:: bash\n\n    # Common Slurm environment variables\n    echo $SLURM_JOB_ID          # Job ID\n    echo $SLURM_JOB_NAME        # Job name\n    echo $SLURM_JOB_NODELIST    # List of allocated nodes\n    echo $SLURM_JOB_NUM_NODES   # Number of nodes allocated\n    echo $SLURM_NNODES          # Same as SLURM_JOB_NUM_NODES\n    echo $SLURM_NTASKS          # Total number of tasks\n    echo $SLURM_NTASKS_PER_NODE # Tasks per node\n    echo $SLURM_CPUS_PER_TASK   # CPUs per task\n    echo $SLURM_PROCID          # MPI rank (global)\n    echo $SLURM_LOCALID         # Local task ID on node\n    echo $SLURM_NODEID          # Node ID in allocation\n    echo $SLURM_SUBMIT_DIR      # Directory where job was submitted\n    echo $SLURM_GPUS            # Number of GPUs (if allocated)\n    echo $SLURM_GPUS_PER_NODE   # GPUs per node\n\nGPU Jobs\n--------\n\nFor machine learning and deep learning workloads, requesting GPU resources is\nessential. Slurm supports GPU scheduling through the Generic Resource (GRES)\nplugin. Users can request specific numbers of GPUs, GPU types, or GPUs per task.\n\n.. code-block:: bash\n\n    # request 1 GPU\n    srun --gres=gpu:1 nvidia-smi\n\n    # request 4 GPUs\n    srun --gres=gpu:4 python train.py\n\n    # request specific GPU type (if configured)\n    srun --gres=gpu:a100:2 python train.py\n\n    # request GPUs per task\n    srun --ntasks=4 --gres=gpu:4 --gpus-per-task=1 python train.py\n\n    # sbatch example with GPUs\n    #!/bin/bash\n    #SBATCH --nodes=2\n    #SBATCH --ntasks-per-node=8\n    #SBATCH --gres=gpu:8\n    #SBATCH --cpus-per-task=12\n\n    srun python train.py\n\nPyTorch Distributed Training\n----------------------------\n\nLaunching distributed PyTorch training jobs on Slurm requires coordinating\nmultiple processes across nodes. The ``torchrun`` launcher simplifies this by\nhandling process spawning and environment setup. Here's a complete example for\nmulti-node distributed training.\n\n.. code-block:: bash\n\n    #!/bin/bash\n    #SBATCH --job-name=distributed-train\n    #SBATCH --nodes=4\n    #SBATCH --ntasks-per-node=1\n    #SBATCH --gpus-per-node=8\n    #SBATCH --cpus-per-task=96\n    #SBATCH --output=logs/%x_%j.out\n    #SBATCH --error=logs/%x_%j.err\n\n    # Get master node address\n    MASTER_ADDR=$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1)\n    MASTER_PORT=29500\n\n    # Set environment variables for distributed training\n    export NCCL_DEBUG=INFO\n    export NCCL_IB_DISABLE=0\n    export NCCL_NET_GDR_LEVEL=2\n\n    srun torchrun \\\n        --nnodes=$SLURM_NNODES \\\n        --nproc_per_node=8 \\\n        --rdzv_id=$SLURM_JOB_ID \\\n        --rdzv_backend=c10d \\\n        --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \\\n        train.py --batch-size=32 --epochs=100\n\nFor containerized environments using Enroot and Pyxis, add ``--container-image``\nto run training inside a custom container with specific CUDA, PyTorch, or NCCL versions.\nUse ``--container-env`` to pass environment variables into the container:\n\n.. code-block:: bash\n\n    #!/bin/bash\n    #SBATCH --job-name=distributed-train\n    #SBATCH --nodes=4\n    #SBATCH --ntasks-per-node=1\n    #SBATCH --gpus-per-node=8\n    #SBATCH --cpus-per-task=96\n    #SBATCH --output=logs/%x_%j.out\n\n    MASTER_ADDR=$(scontrol show hostnames $SLURM_JOB_NODELIST | head -n 1)\n    MASTER_PORT=29500\n\n    srun --container-image=/path/to/pytorch-24.01.sqsh \\\n         --container-mounts=\"/data:/data,${PWD}:/workspace\" \\\n         --container-workdir=/workspace \\\n         --container-env=\"NCCL_DEBUG=INFO,NCCL_IB_DISABLE=0,NCCL_NET_GDR_LEVEL=2\" \\\n         torchrun \\\n            --nnodes=$SLURM_NNODES \\\n            --nproc_per_node=8 \\\n            --rdzv_id=$SLURM_JOB_ID \\\n            --rdzv_backend=c10d \\\n            --rdzv_endpoint=$MASTER_ADDR:$MASTER_PORT \\\n            train.py --batch-size=32 --epochs=100\n\nArray Jobs\n----------\n\nArray jobs allow submitting multiple similar jobs with a single ``sbatch`` command.\nEach job in the array runs independently with a unique ``SLURM_ARRAY_TASK_ID``,\nmaking it ideal for parameter sweeps, hyperparameter tuning, or processing multiple\ndatasets.\n\n.. code-block:: bash\n\n    #!/bin/bash\n    #SBATCH --job-name=array-job\n    #SBATCH --array=0-9\n    #SBATCH --output=logs/array_%A_%a.out\n\n    # SLURM_ARRAY_JOB_ID  - Job array's master job ID\n    # SLURM_ARRAY_TASK_ID - Job array index (0-9 in this case)\n\n    echo \"Array task ID: $SLURM_ARRAY_TASK_ID\"\n    python train.py --seed=$SLURM_ARRAY_TASK_ID\n\n    # Submit array job\n    # sbatch array_job.sh\n\n    # Submit with step size (0, 2, 4, 6, 8)\n    #SBATCH --array=0-9:2\n\n    # Submit with max concurrent tasks\n    #SBATCH --array=0-99%10  # max 10 running at once\n\n    # Cancel specific array tasks\n    scancel ${jobid}_5      # cancel task 5\n    scancel ${jobid}_[1-3]  # cancel tasks 1-3\n\nJob Dependencies\n----------------\n\nJob dependencies allow you to control the execution order of jobs. A job can\nwait for another job to complete, succeed, or fail before starting. This is\nuseful for creating pipelines where preprocessing must finish before training.\n\n.. code-block:: bash\n\n    # Submit first job\n    JOB1=$(sbatch --parsable preprocess.sh)\n\n    # Submit second job after first completes successfully\n    JOB2=$(sbatch --parsable --dependency=afterok:$JOB1 train.sh)\n\n    # Submit third job after second completes (regardless of status)\n    sbatch --dependency=afterany:$JOB2 postprocess.sh\n\n    # Dependency types:\n    # after:jobid       - start after job begins\n    # afterok:jobid     - start after job completes successfully\n    # afternotok:jobid  - start after job fails\n    # afterany:jobid    - start after job completes (any status)\n    # singleton         - only one job with same name runs at a time\n\n    # Multiple dependencies\n    sbatch --dependency=afterok:$JOB1:$JOB2 final.sh\n\nResource Limits\n---------------\n\nSetting appropriate resource limits helps ensure fair cluster usage and prevents\njobs from consuming excessive resources. Slurm allows specifying memory, CPU,\nand time limits per job.\n\n.. code-block:: bash\n\n    # Memory per node\n    srun --mem=64G python train.py\n\n    # Memory per CPU\n    srun --mem-per-cpu=4G python train.py\n\n    # CPU limit\n    srun --cpus-per-task=8 python train.py\n\n    # Time limit (job killed if exceeded)\n    srun --time=24:00:00 python train.py\n\n    # Exclusive node access (no sharing)\n    srun --exclusive python train.py\n\n    # sbatch example with limits\n    #!/bin/bash\n    #SBATCH --mem=128G\n    #SBATCH --cpus-per-task=32\n    #SBATCH --time=48:00:00\n    #SBATCH --exclusive\n\nDebugging Failed Jobs\n---------------------\n\nWhen jobs fail, Slurm provides several tools to diagnose the issue. Common\nproblems include out-of-memory errors, time limits exceeded, and node failures.\n\n.. code-block:: bash\n\n    # Check job exit code and state\n    sacct -j ${jobid} --format=JobID,State,ExitCode,DerivedExitCode\n\n    # Common exit codes:\n    # 0     - Success\n    # 1     - General error\n    # 137   - OOM killed (128 + 9 SIGKILL)\n    # 143   - Time limit (128 + 15 SIGTERM)\n\n    # Check job's stderr/stdout\n    cat slurm-${jobid}.out\n\n    # Check why job is pending\n    squeue -j ${jobid} --format=\"%i %r\"\n\n    # Common pending reasons:\n    # Resources    - Waiting for resources\n    # Priority     - Lower priority than other jobs\n    # Dependency   - Waiting for dependent job\n    # QOSMaxJobsPerUserLimit - User job limit reached\n\n    # Show detailed job info\n    scontrol show job ${jobid}\n\n    # Check node health where job ran\n    scontrol show node ${nodename}\n"
  },
  {
    "path": "docs/notes/llm/index.rst",
    "content": ".. meta::\n    :description lang=en: Large Language Models (LLM) cheat sheet — PyTorch, distributed training, vLLM/SGLang serving, and benchmarking for GPU clusters.\n    :keywords: LLM, Large Language Models, PyTorch, vLLM, SGLang, distributed training, model inference, model serving, GPU optimization, CUDA, transformer models, LLM tutorial, LLM cheat sheet\n\nLLM\n===\n\nLarge Language Models (LLM) training, inference, and optimization. Covers PyTorch\nfor model development, distributed training across GPUs, vLLM and SGLang for\nhigh-performance LLM inference and serving, and benchmarking tools for measuring\nserving performance.\n\n.. toctree::\n   :maxdepth: 1\n\n   pytorch\n   megatron\n   llm-serving\n   llm-bench\n"
  },
  {
    "path": "docs/notes/llm/llm-bench.rst",
    "content": ".. meta::\n    :description lang=en: LLM benchmark suite — measure throughput, TTFT, ITL, latency for vLLM, SGLang, and TensorRT-LLM serving performance.\n    :keywords: LLM benchmark, vLLM benchmark, SGLang benchmark, TensorRT-LLM benchmark, serving benchmark, throughput, latency, TTFT, time to first token, ITL, inter-token latency, prefill, decode, concurrency, ShareGPT, GPU benchmark, tokens per second\n\n=============\nLLM Benchmark\n=============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nBenchmark suites for measuring LLM serving performance with vLLM, SGLang, and\nTensorRT-LLM. All use similar methodology — same test categories, workloads, and\nmetrics — for easy comparison between the three inference engines.\n\n- **vLLM:** ``vllm bench serve`` via `bench.sh <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/bench.sh>`__\n- **SGLang:** ``python -m sglang.bench_serving`` via `bench.sh <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/sglang/bench.sh>`__\n- **TensorRT-LLM:** ``python -m tensorrt_llm.serve.scripts.benchmark_serving`` via `bench.sh <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/tensorrt-llm/bench.sh>`__\n\nThe scripts handle Docker image loading and container management automatically. If the\nCLI is not available on the host, they load the Docker image and re-execute inside the\ncontainer. When running under a SLURM allocation, they use ``srun`` to dispatch to the\ncompute node.\n\nQuick Start\n-----------\n\nLaunch a server in one terminal, then run benchmarks from another. The benchmark script\nauto-detects the model from the server.\n\n**vLLM:**\n\n.. code-block:: bash\n\n    # Terminal 1: start server\n    vllm serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    # Terminal 2: run benchmarks\n    bash bench.sh -H localhost -i vllm-serve:latest\n    bash bench.sh -H localhost --type throughput,prefill\n\n**SGLang:**\n\n.. code-block:: bash\n\n    # Terminal 1: start server\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 30000\n\n    # Terminal 2: run benchmarks\n    bash bench.sh -H localhost -i sglang-serve:latest\n    bash bench.sh -H localhost --type throughput,prefill\n\n**TensorRT-LLM:**\n\n.. code-block:: bash\n\n    # Terminal 1: start server\n    trtllm-serve /path/to/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    # Terminal 2: run benchmarks (requires -m for tokenizer loading)\n    bash bench.sh -H localhost -m /path/to/Qwen2.5-7B-Instruct -i tensorrt-llm-serve:latest\n    bash bench.sh -H localhost -m /path/to/Qwen2.5-7B-Instruct --type throughput,prefill\n\nMulti-Node with Slurm\n---------------------\n\nFor benchmarking larger models (e.g., DeepSeek-V3, Llama-3.1-405B) that cannot fit on a\nsingle node, refer to `Distributed Serving on SLURM <https://www.pythonsheets.com/notes/llm/llm-serving.html#distributed-serving-on-slurm>`__\nfor how to deploy multi-node serving with different parallelism strategies. Once the\nserver is running, benchmark using ``bench.sh`` as shown in the Quick Start above.\n\nThroughput\n----------\n\nMeasures peak output tokens/sec by saturating the server with requests. Uses\n``request-rate=inf`` to send all prompts immediately, forcing the scheduler to batch\naggressively. This reveals the server's maximum throughput under full load.\n\n``512in/256out`` is a moderate workload that exercises both the prefill phase (processing\nthe input) and the decode phase (generating tokens).\n\n.. code-block:: bash\n\n    # vLLM\n    vllm bench serve --dataset-name random --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --request-rate inf\n\n    # SGLang\n    python -m sglang.bench_serving --dataset-name random --random-input 512 --random-output 256 \\\n        --num-prompts 100 --request-rate inf\n\n    # TensorRT-LLM\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name random --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --max-concurrency 100\n\nPrefill (TTFT)\n--------------\n\nMeasures Time to First Token — how fast the server processes the input prompt before\ngenerating the first output token. ``output-len=1`` isolates prefill from decode since\nnearly all compute goes to processing the input.\n\nSweeping input length (128→16K) reveals how TTFT scales with context size. Prefill\ncompute is O(n) per layer, so TTFT should grow roughly linearly. ``rate=4`` keeps the\nserver lightly loaded so TTFT reflects compute time, not queueing delay.\n\n.. code-block:: bash\n\n    # vLLM - sweeps input length: 128, 512, 2048, 4096, 16384\n    vllm bench serve --dataset-name random --random-input-len $LEN --random-output-len 1 \\\n        --num-prompts 100 --request-rate 4\n\n    # SGLang\n    python -m sglang.bench_serving --dataset-name random --random-input $LEN --random-output 1 \\\n        --num-prompts 100 --request-rate 4\n\n    # TensorRT-LLM\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name random --random-input-len $LEN --random-output-len 1 \\\n        --num-prompts 100 --max-concurrency 4\n\nDecode (ITL)\n------------\n\nMeasures Inter-Token Latency — the time between consecutive output tokens during\nautoregressive generation. ``input-len=128`` keeps prefill minimal so the benchmark\nfocuses on the decode phase.\n\nSweeping output length (128→1024) reveals how ITL changes as the KV cache grows. Longer\nsequences increase memory pressure and may trigger PagedAttention block allocation or\npreemption. ``rate=4`` avoids batching interference so ITL reflects single-request\ndecode speed.\n\n.. code-block:: bash\n\n    # vLLM - sweeps output length: 128, 256, 512, 1024\n    vllm bench serve --dataset-name random --random-input-len 128 --random-output-len $LEN \\\n        --num-prompts 100 --request-rate 4\n\n    # SGLang\n    python -m sglang.bench_serving --dataset-name random --random-input 128 --random-output $LEN \\\n        --num-prompts 100 --request-rate 4\n\n    # TensorRT-LLM\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name random --random-input-len 128 --random-output-len $LEN \\\n        --num-prompts 100 --max-concurrency 4\n\nLatency (E2E)\n-------------\n\nMeasures end-to-end request latency under minimal load — the \"single user\" experience.\n``rate=1`` ensures requests are mostly processed alone with no batching, giving the\nbaseline best-case latency (similar to ChatGPT-style usage where one user waits for a\ncomplete response).\n\nThree size classes (short/medium/long) show how total latency scales with request size.\nE2E latency = TTFT + (output_tokens × ITL).\n\n.. code-block:: bash\n\n    # vLLM - tests short (128/128), medium (512/256), long (4096/512)\n    vllm bench serve --dataset-name random --random-input-len $IN --random-output-len $OUT \\\n        --num-prompts 100 --request-rate 1\n\n    # SGLang\n    python -m sglang.bench_serving --dataset-name random --random-input $IN --random-output $OUT \\\n        --num-prompts 100 --request-rate 1\n\n    # TensorRT-LLM\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name random --random-input-len $IN --random-output-len $OUT \\\n        --num-prompts 100 --max-concurrency 1\n\nConcurrency\n-----------\n\nFinds the server's saturation point by sweeping the number of concurrent requests.\n``request-rate=inf`` with ``max-concurrency=N`` caps how many requests run in parallel,\ndecoupling arrival rate from concurrency.\n\nAt low concurrency (1–4), latency is good but throughput is low (GPU underutilized).\nAt high concurrency (64–256), throughput plateaus and latency degrades (queueing). The\n\"knee\" where throughput stops improving is the optimal operating point — it tells you\nhow many concurrent users the server can handle before quality degrades.\n\n.. code-block:: bash\n\n    # vLLM - sweeps concurrency: 1, 4, 16, 64, 256\n    vllm bench serve --dataset-name random --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --request-rate inf --max-concurrency $C\n\n    # SGLang\n    python -m sglang.bench_serving --dataset-name random --random-input 512 --random-output 256 \\\n        --num-prompts 100 --request-rate inf --max-concurrency $C\n\n    # TensorRT-LLM\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name random --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --max-concurrency $C\n\nShareGPT\n--------\n\nRealistic conversational workload from real user conversations with variable input/output\nlengths. Unlike random datasets with fixed lengths, ShareGPT captures the natural\ndistribution of short and long prompts from actual ChatGPT conversations, making it the\nbest proxy for production chat traffic.\n\nShareGPT is the standard dataset used by vLLM CI, GPUStack perf lab, and most published\nbenchmarks. The dataset is auto-downloaded from HuggingFace if not present locally.\n\n.. code-block:: bash\n\n    # vLLM - throughput mode\n    vllm bench serve --dataset-name sharegpt --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \\\n        --num-prompts 100 --request-rate inf\n\n    # vLLM - realistic load\n    vllm bench serve --dataset-name sharegpt --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \\\n        --num-prompts 100 --request-rate 4\n\n    # SGLang - throughput mode\n    python -m sglang.bench_serving --dataset-name sharegpt \\\n        --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \\\n        --num-prompts 100 --request-rate inf\n\n    # TensorRT-LLM - throughput mode\n    python -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --dataset-name sharegpt --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \\\n        --num-prompts 100 --max-concurrency 100\n\nSonnet (Prefix Caching)\n-----------------------\n\nThe sonnet dataset uses Shakespeare's sonnets with a shared prefix across all prompts.\nThis tests prefix caching — if enabled, the shared prefix KV cache is computed once and\nreused across requests, reducing TTFT.\n\n.. code-block:: bash\n\n    # Download sonnet dataset\n    wget -q https://raw.githubusercontent.com/vllm-project/vllm/main/benchmarks/sonnet.txt\n\n    # vLLM - prefill-heavy (short output isolates prefill)\n    vllm bench serve --dataset-name sonnet --dataset-path sonnet.txt \\\n        --sonnet-input-len 550 --sonnet-output-len 150 --sonnet-prefix-len 200 \\\n        --num-prompts 100 --request-rate inf\n\n    # vLLM - realistic load\n    vllm bench serve --dataset-name sonnet --dataset-path sonnet.txt \\\n        --sonnet-input-len 550 --sonnet-output-len 150 --sonnet-prefix-len 200 \\\n        --num-prompts 100 --request-rate 4\n\nBoth ShareGPT and sonnet are used by the vLLM team's\n`v0.6.0 performance blog <https://blog.vllm.ai/2024/09/05/perf-update.html>`__ to\nbenchmark serving engines. To learn more about the methodology, see the\n`reproduction steps <https://github.com/vllm-project/vllm/issues/8176>`__ and SGLang's\n`counter-benchmark <https://github.com/sgl-project/sglang/blob/main/benchmark/benchmark_vllm_060/README.md>`__,\nwhich uses ``sglang.bench_serving`` to compare both engines:\n\n.. code-block:: bash\n\n    # Launch servers\n    # vLLM with multi-step scheduling\n    python -m vllm.entrypoints.openai.api_server \\\n        --model meta-llama/Llama-3.1-8B-Instruct \\\n        --disable-log-requests --num-scheduler-steps 10 --max_model_len 4096\n\n    # SGLang\n    python -m sglang.launch_server \\\n        --model-path meta-llama/Llama-3.1-8B-Instruct \\\n        --enable-torch-compile --disable-radix-cache\n\n    # Online benchmark (realistic load)\n    python3 -m sglang.bench_serving --backend sglang --dataset-name sharegpt \\\n        --num-prompts 1200 --request-rate 4\n    python3 -m sglang.bench_serving --backend vllm --dataset-name sharegpt \\\n        --num-prompts 1200 --request-rate 4\n\n    # Offline benchmark (max throughput)\n    python3 -m sglang.bench_serving --backend sglang --dataset-name sharegpt \\\n        --num-prompts 5000\n    python3 -m sglang.bench_serving --backend vllm --dataset-name sharegpt \\\n        --num-prompts 5000\n\nKey Metrics\n-----------\n\n- **TTFT** (Time to First Token): Time from request arrival to first generated token.\n  Dominated by prefill compute. Lower is better for interactive use.\n- **ITL** (Inter-Token Latency): Time between consecutive tokens. Reflects decode speed\n  and consistency.\n- **TPOT** (Time Per Output Token): Average time per generated token. Similar to ITL\n  but averaged across all tokens.\n- **E2E Latency**: Total time from request to completion. E2E ≈ TTFT + (tokens × ITL).\n- **Throughput**: Output tokens/sec across all requests. Higher is better for batch\n  workloads.\n\nCLI Differences\n---------------\n\n.. list-table::\n   :widths: 20 27 27 26\n   :header-rows: 1\n\n   * - Parameter\n     - vLLM\n     - SGLang\n     - TensorRT-LLM\n   * - Input length\n     - ``--random-input-len``\n     - ``--random-input``\n     - ``--random-input-len``\n   * - Output length\n     - ``--random-output-len``\n     - ``--random-output``\n     - ``--random-output-len``\n   * - Max rate\n     - ``--request-rate inf``\n     - ``--request-rate inf``\n     - ``--max-concurrency``\n   * - Random dataset\n     - (works by default)\n     - (works by default)\n     - ``--random-ids --random-prefix-len 0``\n   * - Model flag\n     - auto-detected\n     - auto-detected\n     - ``-m`` required (tokenizer)\n   * - Results\n     - ``--result-dir ./results``\n     - ``--output-file ./results/out.json``\n     - ``--result-dir ./results``\n\nProfiling\n---------\n\nBenchmark runs can be combined with profiling to correlate performance metrics with\nGPU-level traces. Two profiling approaches are available for vLLM:\n\n**PyTorch profiler** — vLLM's built-in profiler triggered via REST endpoints. Start the\nserver with ``--profile`` (or ``--profiler-config``), then pass ``--profile`` to the\nbenchmark client to call ``/start_profile`` and ``/stop_profile`` around the workload.\n\n.. code-block:: bash\n\n    # Server — start with profiling enabled\n    bash run.sbatch --profile \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8\n\n    # Client — benchmark with profiling\n    bash bench.sh -H <server-host> --type throughput --profile\n\nView traces at https://ui.perfetto.dev/ (supports ``.gz`` files directly).\n\n**Nsight Systems** — wraps ``vllm serve`` with ``nsys profile`` for CUDA kernel,\nNVTX, and memory tracing. Combine with ``--profiler-config '{\"profiler\": \"cuda\"}'``\nto also capture vLLM's internal CUDA profiler markers.\n\n.. code-block:: bash\n\n    # Server — enable nsys + CUDA profiler (terminal 0)\n    bash run.sbatch --nsys \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel \\\n      --profiler-config '{\"profiler\": \"cuda\"}'\n\n    # Client — benchmark with profiling (terminal 1)\n    bash bench.sh -H <server-host> --type throughput --profile\n\n    # Stop server with Ctrl+C (terminal 0)\n    # Nsys finalizes profiles (~30s)\n    # Profile files: nsys-vllm/profile-node*.nsys-rep\n\nOpen ``.nsys-rep`` files with `Nsight Systems <https://developer.nvidia.com/nsight-systems>`_.\n\nSee the `vLLM Serving Guide <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/README.rst>`_\nfor full ``run.sbatch`` flag reference and the\n`vLLM Profiling Guide <https://docs.vllm.ai/en/latest/contributing/profiling/>`_\nfor more details.\n\nOffline Benchmarking\n--------------------\n\nvLLM also supports offline benchmarking to measure raw inference performance without\nAPI server overhead. This is useful for:\n\n- Measuring peak throughput without network/serialization overhead\n- Multi-node distributed inference testing\n- Profiling with Nsight Systems or PyTorch profiler\n- Testing with custom datasets (ShareGPT, random prompts)\n\nFor complete offline benchmarking documentation, see the\n`vLLM Offline Benchmark Guide <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/README.rst#offline-benchmarking>`_.\n"
  },
  {
    "path": "docs/notes/llm/llm-serving.rst",
    "content": ".. meta::\n    :description lang=en: LLM serving guide — vLLM, SGLang, and TensorRT-LLM for single-node, multi-node SLURM, tensor/pipeline/data/expert parallelism on GPU clusters.\n    :keywords: vLLM, SGLang, TensorRT-LLM, LLM serving, LLM inference, model serving, distributed inference, tensor parallelism, pipeline parallelism, data parallelism, expert parallelism, MoE serving, GPU inference, OpenAI compatible API, multi-node GPU, SLURM, HPC, EFA, NCCL, PagedAttention, RadixAttention, continuous batching, Docker, Qwen, Llama, DeepSeek\n\n===========\nLLM Serving\n===========\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis guide covers LLM inference serving with three high-performance engines:\n\n- **vLLM** — High-throughput inference engine with PagedAttention for efficient KV cache\n  memory management, continuous batching for maximizing GPU utilization, and optimized\n  CUDA kernels. Provides an OpenAI-compatible API as a drop-in replacement for OpenAI\n  services.\n\n- **SGLang** — Fast inference engine with RadixAttention for efficient prefix caching\n  across requests with shared prompts. Optimized for multi-turn conversations and\n  workloads with common system prompts.\n\n- **TensorRT-LLM** — NVIDIA's inference engine with PyTorch backend, optimized CUDA\n  kernels, and FP8/INT4 quantization. Uses ``trtllm-serve`` for OpenAI-compatible\n  serving. Supports TP, PP, EP, and attention DP via YAML config.\n\nAll support distributed inference across multiple GPUs and nodes with tensor parallelism\n(TP), pipeline parallelism (PP), data parallelism (DP), and expert parallelism (EP) for\nMixture-of-Experts (MoE) models. This guide covers everything from basic single-GPU\ndeployment to advanced multi-node distributed serving on SLURM clusters.\n\nScripts and examples:\n\n- vLLM: `src/llm/vllm/ <https://github.com/crazyguitar/pysheeet/tree/master/src/llm/vllm>`_\n- SGLang: `src/llm/sglang/ <https://github.com/crazyguitar/pysheeet/tree/master/src/llm/sglang>`_\n- TensorRT-LLM: `src/llm/tensorrt-llm/ <https://github.com/crazyguitar/pysheeet/tree/master/src/llm/tensorrt-llm>`_\n\nQuick Start\n-----------\n\nGet started in minutes. Install the package, launch a server, and query it with standard\nHTTP requests. Both engines expose OpenAI-compatible ``/v1/chat/completions`` and\n``/v1/completions`` endpoints.\n\n**vLLM** (port 8000):\n\n.. code-block:: bash\n\n    pip install vllm\n    vllm serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    curl -X POST http://localhost:8000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"model\": \"Qwen/Qwen2.5-7B-Instruct\", \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}], \"max_tokens\": 50}'\n\n**SGLang** (port 30000):\n\n.. code-block:: bash\n\n    pip install \"sglang[all]\"\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 30000\n\n    curl -X POST http://localhost:30000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"model\": \"Qwen/Qwen2.5-7B-Instruct\", \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}], \"max_tokens\": 50}'\n\n**TensorRT-LLM** (port 8000):\n\n.. code-block:: bash\n\n    pip install tensorrt-llm\n    trtllm-serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    curl -X POST http://localhost:8000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"model\": \"Qwen/Qwen2.5-7B-Instruct\", \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}], \"max_tokens\": 50}'\n\nTensor Parallel (TP)\n--------------------\n\nTensor parallelism splits individual model layers across multiple GPUs, with each GPU\nholding a portion of the weight matrices. All GPUs participate in every forward pass,\ncommunicating via all-reduce operations. Essential for models that don't fit in a single\nGPU's memory.\n\n**Use when:** Model doesn't fit on a single GPU, or you need to reduce per-GPU memory.\n\n.. code-block:: bash\n\n    # vLLM\n    vllm serve Qwen/Qwen2.5-14B-Instruct --tensor-parallel-size 8\n\n    # SGLang\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-14B-Instruct --tp 8\n\n    # TensorRT-LLM\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --tp_size 8\n\nPipeline Parallel (PP)\n----------------------\n\nPipeline parallelism divides the model into sequential stages, with each stage assigned\nto different GPUs. Unlike tensor parallelism where all GPUs work on every layer, pipeline\nparallelism processes different parts of the model on different GPUs. This reduces\ncommunication overhead since GPUs only pass activations between stages.\n\n**Use when:** You want to reduce inter-GPU communication or scale across nodes with\nslower interconnects.\n\n.. code-block:: bash\n\n    # vLLM: PP=2 splits model into 2 stages, TP=4 within each\n    vllm serve Qwen/Qwen2.5-14B-Instruct --tensor-parallel-size 4 --pipeline-parallel-size 2\n\n    # SGLang\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-14B-Instruct --tp 4 --pp 2\n\n    # TensorRT-LLM\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --tp_size 4 --pp_size 2\n\nData Parallel (DP)\n------------------\n\nData parallelism creates multiple independent replicas of the model, each processing\ndifferent requests simultaneously. This is the most effective way to increase throughput\nwhen you have sufficient GPU memory for multiple model copies. Each replica can use\ntensor parallelism internally.\n\n**Use when:** You need higher request throughput and have enough GPUs to replicate the model.\n\n.. code-block:: bash\n\n    # vLLM: 2 replicas, each using 8 GPUs\n    vllm serve Qwen/Qwen2.5-14B-Instruct --tensor-parallel-size 8 --data-parallel-size 2\n\n    # SGLang: multi-node DP requires --enable-dp-attention\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-14B-Instruct --tp 8 --dp 2 --enable-dp-attention\n\n    # TensorRT-LLM: DP via multi-node with TP=8 per node\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --tp_size 8\n\nExpert Parallel (EP)\n--------------------\n\nExpert parallelism is specifically designed for Mixture-of-Experts (MoE) models, where\nthe model contains multiple expert sub-networks and a gating mechanism routes tokens to\ndifferent experts. EP shards the experts across GPUs, allowing each GPU to hold a subset\nof experts.\n\n**vLLM:** EP is computed automatically (``EP = DP × TP``).\n\n**SGLang:** EP is a subdivision of TP. With ``--tp 8 --ep 2``, the 8 TP GPUs split into\n2 expert groups of 4 GPUs each.\n\n**TensorRT-LLM:** EP subdivides TP (same as SGLang). With ``--tp_size 8 --ep_size 2``,\nexperts are sharded across 2 groups of 4 GPUs.\n\n.. code-block:: bash\n\n    # vLLM: EP auto-computed\n    vllm serve Qwen/Qwen3-30B-A3B-FP8 --tensor-parallel-size 8 --enable-expert-parallel\n\n    # SGLang: EP subdivides TP\n    python -m sglang.launch_server --model-path Qwen/Qwen1.5-MoE-A2.7B --tp 8 --ep 2\n\n    # TensorRT-LLM: EP subdivides TP\n    trtllm-serve Qwen/Qwen1.5-MoE-A2.7B --tp_size 8 --ep_size 2\n\nParallelism Formulas\n--------------------\n\nBoth engines use the same formula for computing total GPU requirements:\n\n.. code-block:: text\n\n    Total GPUs = TP × PP × DP\n\nExpert parallelism (EP) is handled differently:\n\n- **vLLM**: EP is auto-computed (``EP = TP × DP``) when ``--enable-expert-parallel`` is set.\n  All GPUs in the world participate in expert parallelism automatically.\n- **SGLang**: EP explicitly subdivides TP. For example, ``--tp 8 --ep 2`` splits the 8 TP\n  GPUs into 2 expert groups of 4 GPUs each. Each group handles different experts while\n  all 8 GPUs still perform tensor parallelism for non-expert layers.\n- **TensorRT-LLM**: EP subdivides TP (same as SGLang). ``--tp_size 8 --ep_size 2`` splits\n  experts across 2 groups. Constraint: ``moe_tp × ep = tp_size``.\n\n.. _distributed-serving-on-slurm:\n\nDistributed Serving on SLURM\n----------------------------\n\nSome large models (e.g., DeepSeek-V3, Llama-3.1-405B) may not fit into a single node.\nAll three engines support serving across multiple nodes with different parallelism\nstrategies (TP, PP, EP, DP). Multi-node deployment can be tricky at the beginning —\nthe ``run.sbatch`` examples below show how to use ``salloc`` with each engine to get\nstarted quickly on Slurm. The scripts handle Docker image distribution to all nodes,\ncontainer launch with EFA/GPU passthrough, worker coordination, and health checking.\nThe server runs until you stop it with ``Ctrl+C`` or ``scancel``.\n\n**vLLM:**\n\n.. code-block:: bash\n\n    salloc -N 2 --gpus-per-node=8 --exclusive\n\n    # MoE with expert parallelism\n    bash run.sbatch Qwen/Qwen3-30B-A3B-FP8 --tensor-parallel-size 8 --enable-expert-parallel\n\n    # Dense with pipeline parallelism\n    bash run.sbatch deepseek-ai/DeepSeek-V2-Lite --tensor-parallel-size 8 --pipeline-parallel-size 2\n\n**SGLang:**\n\n.. code-block:: bash\n\n    salloc -N 2 --gpus-per-node=8 --exclusive\n\n    # Large model with TP=8 (uses 8 GPUs on first node)\n    bash run.sbatch --model-path Qwen/Qwen2.5-72B-Instruct --tp 8\n\n    # MoE with expert parallelism (TP=8, EP=2 across 2 nodes)\n    bash run.sbatch --model-path Qwen/Qwen1.5-MoE-A2.7B --tp 8 --ep 2\n\n**TensorRT-LLM:**\n\n.. code-block:: bash\n\n    salloc -N 2 --gpus-per-node=8 --exclusive\n\n    # MoE with expert parallelism\n    bash run.sbatch /path/to/Qwen1.5-MoE-A2.7B --tp_size 8 --ep_size 2\n\n    # Dense model\n    bash run.sbatch /path/to/Qwen2.5-14B-Instruct --tp_size 8\n\nSee the READMEs for full script options:\n\n- `vLLM README <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/vllm/README.rst>`_\n- `SGLang README <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/sglang/README.rst>`_\n- `TensorRT-LLM README <https://github.com/crazyguitar/pysheeet/blob/master/src/llm/tensorrt-llm/README.rst>`_\n"
  },
  {
    "path": "docs/notes/llm/megatron.rst",
    "content": ".. meta::\n    :description lang=en: Megatron Bridge cheat sheet — pretrain recipes, Nsys profiling, and distributed training on SLURM with EFA.\n    :keywords: Megatron, Megatron-LM, Megatron Bridge, distributed training, pretrain, Nsys, profiling, EFA, SLURM, DeepSeek, MoE, NCCL, GPU, LLM\n\n===========\nMegatron-LM\n===========\n\n.. contents:: Table of Contents\n    :backlinks: none\n\n`Megatron-LM <https://github.com/NVIDIA/Megatron-LM/tree/main>`_ is NVIDIA's framework\nfor training and fine-tuning large transformer models with tensor, pipeline, and expert\nparallelism. `Megatron Bridge <https://github.com/NVIDIA-NeMo/Megatron-Bridge/tree/main>`_\nsits on top of Megatron-LM and provides a recipe-based interface — instead of passing\ndozens of CLI flags, you write a short Python recipe that returns a config object.\nRecipes can load `HuggingFace <https://huggingface.co/models>`_ pretrained weights\ndirectly via the ``hf_path`` parameter, so you can start from any checkpoint without\nmanual conversion.\n\nIn this note, we demonstrate how to use Megatron Bridge to load HuggingFace pretrained\nweights and train with Megatron-LM. The examples use the scripts in\n`src/megatron <https://github.com/crazyguitar/pysheeet/blob/master/src/megatron>`__.\n\nHow to Use Megatron Bridge\n--------------------------\n\nThe container image is built with Docker and exported as an\n`enroot <https://github.com/NVIDIA/enroot>`_ squashfs (``.sqsh``) file. Enroot is a\nlightweight container runtime designed for HPC — it converts Docker images into\nunprivileged sandboxes that integrate with SLURM via the\n`pyxis <https://github.com/NVIDIA/pyxis>`_ plugin. When ``srun.sh`` runs, it passes\n``--container-image`` and ``--container-mounts`` to ``srun``, and pyxis handles importing\nthe ``.sqsh`` and launching each task inside the container.\n\n.. code-block:: bash\n\n    # Build Docker image and export to enroot sqsh\n    make build\n\n    # This produces megatron-lm+latest.sqsh in the current directory.\n    # srun.sh picks it up via the SQSH env var (default: ./megatron-lm+latest.sqsh).\n\nYou can override the image path and container mounts with environment variables:\n\n.. list-table::\n   :header-rows: 1\n\n   * - Variable\n     - Default\n     - Description\n   * - ``SQSH``\n     - ``./megatron-lm+latest.sqsh``\n     - Path to enroot image\n   * - ``MOUNT``\n     - ``.:/workspace/megatron,/fsx:/fsx``\n     - Container mounts\n   * - ``GPUS_PER_NODE``\n     - ``8``\n     - GPUs per node\n\nA recipe is a Python file that calls a Megatron Bridge config function and returns a\n``ConfigContainer``. For example,\n`deepseek_v2_lite_pretrain.py <https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/recipes/deepseek_v2_lite_pretrain.py>`__:\n\n.. code-block:: python\n\n    from megatron.bridge.recipes.deepseek.deepseek_v2 import (\n        deepseek_v2_lite_pretrain_config,\n    )\n\n\n    def configure(hf_path=None, moe_token_dispatcher_type=None):\n        cfg = deepseek_v2_lite_pretrain_config(\n            **({\"hf_path\": hf_path} if hf_path else {}),\n            tensor_model_parallel_size=8,\n            pipeline_model_parallel_size=1,\n            expert_model_parallel_size=2,\n            sequence_parallel=True,\n            seq_length=4096,\n            train_iters=500,\n            global_batch_size=64,\n            micro_batch_size=1,\n            eval_interval=100,\n            lr_warmup_iters=50,\n            save_interval=0,\n        )\n        cfg.model.moe_permute_fusion = False\n        if moe_token_dispatcher_type == \"deepep\":\n            cfg.model.moe_token_dispatcher_type = \"flex\"\n            cfg.model.moe_flex_dispatcher_backend = \"deepep\"\n            cfg.model.moe_enable_deepep = True\n            cfg.model.moe_shared_expert_overlap = False\n        return cfg\n\nLaunch a pretrain job with ``srun.sh``, which wraps ``srun`` + pyxis to run inside the\nenroot container:\n\n.. code-block:: bash\n\n    # 2-node DeepSeek V2 Lite pretrain\n    salloc -N 2\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite\n\n    # Override config with Hydra-style args\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        train.train_iters=1000\n\nThe `entrypoint.py <https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/entrypoint.py>`__\nloads the recipe, applies CLI overrides, and calls ``pretrain()``. The ``srun.sh`` script\nsets up EFA environment variables (``FI_PROVIDER=efa``, ``NCCL_NET_PLUGIN``, etc.) and\nmaps ``SLURM_PROCID`` / ``SLURM_LOCALID`` to ``RANK`` / ``LOCAL_RANK`` so that\nMegatron's distributed init works without ``torchrun``.\n\nHow to Enable Nsys Profiling\n----------------------------\n\nPass ``--nsys`` to ``srun.sh`` and set the profiling overrides in the recipe:\n\n.. code-block:: bash\n\n    ./srun.sh --nsys recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        profiling.use_nsys_profiler=true \\\n        profiling.profile_step_start=10 \\\n        profiling.profile_step_end=15 \\\n        profiling.profile_ranks=[0]\n\nWhen ``--nsys`` is enabled, ``srun.sh`` prepends ``nsys profile`` with\n``--capture-range=cudaProfilerApi`` so that only the steps between\n``profile_step_start`` and ``profile_step_end`` are captured. The output\n``.nsys-rep`` files are written to ``nsys-megatron/`` inside the container mount.\n\nOn AWS, if you want to monitor EFA network traffic in the Nsys timeline, add\n``--enable efa_metrics`` to the nsys command (already included in ``srun.sh``). For a\ndetailed walkthrough on monitoring EFA with NCCL GIN and Nsys, refer to\n:doc:`/notes/appendix/megatron-efa-monitoring`.\n\nWhy ``python`` Instead of ``torchrun``\n--------------------------------------\n\nThe ``srun.sh`` script launches ``python3 entrypoint.py`` directly rather than using\n``torchrun``. This is intentional. ``torchrun`` spawns worker processes via\n``multiprocessing``, and the spawn boundary can interfere with Nsys profiling — the\nprofiler sometimes fails to capture the ``cudaProfilerStart`` and ``cudaProfilerStop``\ncalls issued by the child process, resulting in empty or incomplete traces.\n\nBy running ``python`` directly under ``srun --ntasks-per-node=<GPUS>``, each GPU gets\nits own process managed by SLURM. The ``RANK``, ``LOCAL_RANK``, and ``WORLD_SIZE``\nenvironment variables are derived from ``SLURM_PROCID``, ``SLURM_LOCALID``, and\n``SLURM_NTASKS`` respectively. This avoids the spawn layer entirely, giving Nsys (and\nother profilers like VizTracer) a clean, single-process view of each rank.\n\nCustom Profilers (VizTracer)\n----------------------------\n\nMegatron Bridge's profiling hooks can be extended to support custom profilers. The\n`viztracer_plugin.py <https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/viztracer_plugin.py>`__\nshows how to do this by monkey-patching ``megatron.bridge.training.profiling``:\n\n1. Add a new field (e.g. ``use_viztracer``) to ``ProfilingConfig`` via\n   ``dataclasses.field`` so OmegaConf recognizes it as a valid override.\n2. Patch ``handle_profiling_step`` to start the profiler at ``profile_step_start``.\n3. Patch ``handle_profiling_stop`` to stop and save at ``profile_step_end``.\n\nThe plugin is loaded in\n`entrypoint.py <https://github.com/crazyguitar/pysheeet/blob/master/src/megatron/entrypoint.py>`__\nbefore any Megatron imports:\n\n.. code-block:: python\n\n    import viztracer_plugin\n    viztracer_plugin.install()\n\nThen pass the override on the command line:\n\n.. code-block:: bash\n\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n        profiling.use_viztracer=true \\\n        profiling.profile_step_start=10 \\\n        profiling.profile_step_end=15 \\\n        profiling.profile_ranks=[0]\n\nThe same pattern works for any profiler — implement ``start()`` / ``stop()`` / ``save()``\nin the patched hooks and register a config field so it can be toggled via Hydra overrides.\n"
  },
  {
    "path": "docs/notes/llm/pytorch.rst",
    "content": ".. meta::\n    :description lang=en: PyTorch cheat sheet covering tensors, operations, gradients, GPU usage, neural networks, and NumPy interoperability\n    :keywords: Python, Python3, PyTorch, tensor, deep learning, GPU, CUDA, gradient, autograd, numpy, neural network\n\n=======\nPyTorch\n=======\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nPyTorch is an open-source machine learning framework developed by Meta AI. It provides\na flexible and intuitive interface for building and training neural networks with strong\nGPU acceleration support. PyTorch uses dynamic computation graphs, making it easier to\ndebug and experiment with different model architectures compared to static graph frameworks.\n\nCheck CUDA\n----------\n\nBefore running GPU-accelerated computations, verify that CUDA is properly installed and\naccessible. These commands help check GPU availability and configure device settings.\n\n.. code-block:: python\n\n    >>> import torch\n    >>> torch.cuda.is_available()\n    True\n    >>> torch.cuda.nccl.version()\n    (2, 26, 2)\n    >>> torch.cuda.device_count()\n    2\n    >>> torch.cuda.get_device_name(0)\n    'NVIDIA GeForce RTX 3090'\n    >>> torch.cuda.set_device(0)\n    >>> torch.cuda.current_device()\n    0\n\nCheck Device\n------------\n\nDetermine where a tensor is stored (CPU or GPU) to ensure computations run on the intended device.\n\n.. code-block:: python\n\n    >>> tensor = torch.tensor([0, 1, 2, 3, 4, 5])\n    >>> tensor.device\n    device(type='cpu')\n    >>> tensor = tensor.to('cuda')\n    >>> tensor.device\n    device(type='cuda', index=0)\n\nCreate Tensors\n--------------\n\nTensors are the fundamental data structure in PyTorch, similar to NumPy arrays but with\nGPU acceleration support. You can create tensors from Python lists, with specific values,\nor using various initialization methods.\n\n.. code-block:: python\n\n    >>> x = torch.tensor([0, 1, 2, 3, 4, 5])\n    >>> x = torch.empty(2, 2)\n    >>> x = torch.rand(2, 2)\n    >>> x = torch.randn(2, 2)\n    >>> x = torch.ones(2, 2)\n    >>> x = torch.zeros(2, 2)\n    >>> x = torch.arange(0, 10, 2)\n    >>> x = torch.linspace(0, 1, 5)\n\n    >>> device = torch.device(\"cuda:0\")\n    >>> x = torch.tensor([1, 2, 3, 4, 5], device=device)\n    >>> x\n    tensor([1, 2, 3, 4, 5], device='cuda:0')\n\nTensor Properties\n-----------------\n\nUnderstanding tensor properties like shape, data type, and device location is crucial\nfor debugging and ensuring compatibility between operations.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3, 4)\n    >>> x.shape\n    torch.Size([2, 3, 4])\n    >>> x.size()\n    torch.Size([2, 3, 4])\n    >>> x.dtype\n    torch.float32\n    >>> x.device\n    device(type='cpu')\n    >>> x.numel()\n    24\n\nContiguous Tensors\n------------------\n\nTensors must be stored in contiguous memory blocks for certain operations. After operations\nlike ``transpose`` or ``permute``, tensors may not be contiguous. Use ``contiguous()`` to\ncreate a contiguous copy when needed.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3)\n    >>> x.is_contiguous()\n    True\n    >>> y = x.transpose(0, 1)\n    >>> y.is_contiguous()\n    False\n    >>> z = y.contiguous()\n    >>> z.is_contiguous()\n    True\n\nView vs Reshape\n---------------\n\n``view()`` requires tensors to be contiguous and returns a view sharing the same memory.\n``reshape()`` works on both contiguous and non-contiguous tensors, creating a copy if needed.\nUse ``view()`` when you know the tensor is contiguous for better performance.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3, 4)\n    >>> x.is_contiguous()\n    True\n    >>> x.view(2, 12).shape\n    torch.Size([2, 12])\n\n    >>> y = x.transpose(0, 1)\n    >>> y.is_contiguous()\n    False\n    >>> y.view(3, 8)\n    RuntimeError: view size is not compatible with input tensor's size and stride\n\n    >>> y.reshape(3, 8).shape\n    torch.Size([3, 8])\n    >>> y.contiguous().view(3, 8).shape\n    torch.Size([3, 8])\n\nReshape Tensors\n---------------\n\nCommon reshaping operations for preparing data for neural network layers.\n\n.. code-block:: python\n\n    x = torch.randn(2, 3, 4)\n    x.reshape(6, 4)\n    x.flatten()\n    x.squeeze()\n    x.unsqueeze(0)\n\nMove Tensors\n------------\n\nTransfer tensors between CPU and GPU, or between different GPU devices. This is necessary\nwhen working with models and data on different devices.\n\n.. code-block:: python\n\n    x = torch.randn(2, 3)\n    x_gpu = x.to('cuda')\n    x_gpu = x.cuda()\n    x_cpu = x_gpu.to('cpu')\n    x_cpu = x_gpu.cpu()\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    x = x.to(device)\n\nArithmetic\n----------\n\nPyTorch supports element-wise operations and matrix operations. Most operations have\nboth functional and method forms, and support broadcasting for tensors of different shapes.\n\n.. code-block:: python\n\n    >>> x = torch.rand(2, 2, device=0)\n    >>> y = torch.rand(2, 2, device=0)\n    >>> x\n    tensor([[0.2171, 0.7797],\n            [0.7265, 0.6759]], device='cuda:0')\n    >>> y\n    tensor([[0.6766, 0.1862],\n            [0.2438, 0.0076]], device='cuda:0')\n\n    >>> x + y\n    tensor([[0.8937, 0.9659],\n            [0.9703, 0.6834]], device='cuda:0')\n    >>> torch.add(x, y)\n    tensor([[0.8937, 0.9659],\n            [0.9703, 0.6834]], device='cuda:0')\n    >>> x - y\n    tensor([[-0.4594,  0.5935],\n            [ 0.4827,  0.6683]], device='cuda:0')\n    >>> x * y\n    tensor([[0.1469, 0.1452],\n            [0.1771, 0.0051]], device='cuda:0')\n    >>> x / y\n    tensor([[ 0.3209,  4.1880],\n            [ 2.9796, 89.4011]], device='cuda:0')\n    >>> x ** 2\n    tensor([[0.0471, 0.6079],\n            [0.5278, 0.4568]], device='cuda:0')\n    >>> x @ y\n    tensor([[0.3370, 0.0463],\n            [0.6563, 0.1404]], device='cuda:0')\n    >>> torch.matmul(x, y)\n    tensor([[0.3370, 0.0463],\n            [0.6563, 0.1404]], device='cuda:0')\n\nIn-place Operations\n-------------------\n\nOperations ending with ``_`` modify tensors directly without creating new tensors,\nsaving memory. Use these carefully as they can affect gradient computation.\n\n.. code-block:: python\n\n    x = torch.rand(2, 2, device=0)\n    y = torch.rand(2, 2, device=0)\n    y.add_(x)\n    y.sub_(x)\n    y.mul_(x)\n    y.div_(x)\n    y.zero_()\n    y.fill_(5)\n\nTranspose\n---------\n\nSwap dimensions of multi-dimensional tensors. This is commonly used when preparing\ndata for different neural network layers that expect specific input shapes.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 6, 2, device=0)\n    >>> x.shape\n    torch.Size([2, 6, 2])\n    >>> y = x.transpose(1, 2)\n    >>> y.shape\n    torch.Size([2, 2, 6])\n    >>> x.T.shape\n    torch.Size([2, 2, 6])\n    >>> x.permute(2, 0, 1).shape\n    torch.Size([2, 2, 6])\n\nMatrix Multiplication\n---------------------\n\nPerform batch matrix operations on high-dimensional tensors. This is fundamental for\nneural network computations where you process multiple samples simultaneously.\n\n.. code-block:: python\n\n    >>> x = torch.randn(1, 2, 3, 4, device=0)\n    >>> x @ x.transpose(2, 3)\n    tensor([[[[ 1.0950, -0.1160, -1.4840],\n              [-0.1160,  2.4025,  2.8093],\n              [-1.4840,  2.8093,  5.9335]],\n\n             [[ 3.2498, -0.3049, -0.5129],\n              [-0.3049,  0.1591, -0.2596],\n              [-0.5129, -0.2596,  2.9863]]]], device='cuda:0')\n\n    >>> torch.bmm(x, x.transpose(2, 3))\n    >>> torch.einsum('bijk,bikl->bijl', x, x.transpose(2, 3))\n\nAggregation\n-----------\n\nReduce tensors along specified dimensions using aggregation functions. These operations\nare essential for computing statistics and reducing dimensionality.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3, 4)\n    >>> x.sum()\n    tensor(5.2341)\n    >>> x.sum(dim=0).shape\n    torch.Size([3, 4])\n    >>> x.sum(dim=-1).shape\n    torch.Size([2, 3])\n    >>> x.mean()\n    tensor(0.2181)\n    >>> x.std()\n    tensor(1.0234)\n    >>> x.max()\n    tensor(2.3456)\n    >>> x.min()\n    tensor(-1.8765)\n    >>> x.argmax()\n    tensor(7)\n    >>> x.argmin()\n    tensor(15)\n\n    >>> x.max(dim=1)\n    torch.return_types.max(\n    values=tensor([[1.2345, 0.9876, 1.5432, 0.8765],\n                   [0.7654, 1.3456, 0.6543, 1.2345]]),\n    indices=tensor([[1, 2, 0, 1],\n                    [2, 0, 1, 2]]))\n\nSlicing\n-------\n\nExtract parts of tensors using NumPy-style indexing. Slicing is essential for accessing\nspecific elements, rows, columns, or sub-tensors without copying data.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3, device=0)\n    >>> x\n    tensor([[-1.3921,  0.0475,  0.7571],\n            [-0.1469, -0.3882,  0.2149]], device='cuda:0')\n    >>> x[:, 1]\n    tensor([ 0.0475, -0.3882], device='cuda:0')\n    >>> x[1, :]\n    tensor([-0.1469, -0.3882,  0.2149], device='cuda:0')\n    >>> x[1, 1].item()\n    -0.3882044851779938\n\n    >>> x = torch.triu(torch.ones(5, 5))\n    >>> x[:3, :3]\n    tensor([[1., 1., 1.],\n            [0., 1., 1.],\n            [0., 0., 1.]])\n\nAdvanced Indexing\n-----------------\n\nUse boolean masks and fancy indexing to select elements based on conditions or\nspecific index arrays.\n\n.. code-block:: python\n\n    >>> x = torch.randn(3, 4)\n    >>> mask = x > 0\n    >>> x[mask]\n    tensor([0.5234, 1.2345, 0.8765, 0.3456, 1.5678])\n\n    x[x > 0] = 0\n\n    indices = torch.tensor([0, 2])\n    x[indices]\n    x[[0, 1], [1, 2]]\n\nConcatenation\n-------------\n\nCombine multiple tensors along existing or new dimensions. This is useful for\nbuilding batches or combining features from different sources.\n\n.. code-block:: python\n\n    >>> x = torch.randn(2, 3)\n    >>> y = torch.randn(2, 3)\n    >>> torch.cat([x, y], dim=0).shape\n    torch.Size([4, 3])\n    >>> torch.cat([x, y], dim=1).shape\n    torch.Size([2, 6])\n    >>> torch.stack([x, y], dim=0).shape\n    torch.Size([2, 2, 3])\n    >>> torch.vstack([x, y]).shape\n    torch.Size([4, 3])\n    >>> torch.hstack([x, y]).shape\n    torch.Size([2, 6])\n\nSplitting\n---------\n\nSplit tensors into multiple chunks or along specific dimensions. Useful for\ndistributing data across multiple GPUs or processing in smaller batches.\n\n.. code-block:: python\n\n    >>> x = torch.randn(6, 4)\n    >>> chunks = torch.split(x, 2, dim=0)\n    >>> len(chunks)\n    3\n    >>> chunks = torch.chunk(x, 3, dim=0)\n    >>> len(chunks)\n    3\n    >>> tensors = x.unbind(dim=0)\n    >>> len(tensors)\n    6\n\nGradient\n--------\n\nAutomatic differentiation is PyTorch's core feature for training neural networks.\nEnable gradient tracking on tensors to compute derivatives during backpropagation.\n\n.. code-block:: python\n\n    >>> x = torch.randn(3, requires_grad=True, device=0)\n    >>> x\n    tensor([-1.1442, -0.8709, -0.2581], device='cuda:0', requires_grad=True)\n\n    >>> y = x.detach()\n    >>> y\n    tensor([-1.1442, -0.8709, -0.2581], device='cuda:0')\n\n    >>> x.requires_grad_(False)\n    tensor([-1.1442, -0.8709, -0.2581], device='cuda:0')\n\nDisable Gradient\n----------------\n\nTemporarily disable gradient computation for inference or when you don't need gradients.\nThis reduces memory usage and speeds up computations.\n\n.. code-block:: python\n\n    >>> x = torch.randn(3, requires_grad=True, device=0)\n    >>> with torch.no_grad():\n    ...     y = x + 1\n    ...     print(y)\n    ...\n    tensor([1.2969, 1.5251, 0.7915], device='cuda:0')\n\n    with torch.inference_mode():\n        y = x * 2\n\n    @torch.no_grad()\n    def predict(x):\n        return model(x)\n\nBackpropagation\n---------------\n\nCompute gradients using automatic differentiation. PyTorch builds a computation graph\nduring the forward pass and computes gradients during the backward pass using the chain rule.\n\n.. code-block:: python\n\n    >>> x = torch.randn(3, requires_grad=True)\n    >>> y = x + 1\n    >>> z = y * y * 3\n    >>> z = z.mean()\n    >>> z.backward()\n    >>> x.grad\n    tensor([1.2036, 5.0103, 0.5143])\n\n    x.grad.zero_()\n    z.backward()\n\nGradient Accumulation\n---------------------\n\nAccumulate gradients over multiple backward passes. This is useful for simulating\nlarger batch sizes when GPU memory is limited.\n\n.. code-block:: python\n\n    optimizer.zero_grad()\n    for i, (inputs, targets) in enumerate(dataloader):\n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        if (i + 1) % accumulation_steps == 0:\n            optimizer.step()\n            optimizer.zero_grad()\n\nNeural Network Module\n---------------------\n\nDefine neural networks by subclassing ``nn.Module``. This provides a clean interface\nfor building complex models with automatic parameter management.\n\n.. code-block:: python\n\n    import torch.nn as nn\n\n    class Net(nn.Module):\n        def __init__(self):\n            super().__init__()\n            self.fc1 = nn.Linear(784, 128)\n            self.fc2 = nn.Linear(128, 10)\n\n        def forward(self, x):\n            x = torch.relu(self.fc1(x))\n            x = self.fc2(x)\n            return x\n\n    model = Net()\n    model.to('cuda')\n\nCommon Layers\n-------------\n\nPyTorch provides a wide variety of pre-built layers for constructing neural networks.\n\n.. code-block:: python\n\n    import torch.nn as nn\n\n    linear = nn.Linear(10, 5)\n    conv2d = nn.Conv2d(3, 64, kernel_size=3, padding=1)\n    maxpool = nn.MaxPool2d(2, 2)\n    dropout = nn.Dropout(0.5)\n    batchnorm = nn.BatchNorm2d(64)\n    relu = nn.ReLU()\n    sigmoid = nn.Sigmoid()\n    softmax = nn.Softmax(dim=1)\n\n    lstm = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)\n    gru = nn.GRU(input_size=10, hidden_size=20)\n    embedding = nn.Embedding(1000, 128)\n\nLoss Functions\n--------------\n\nLoss functions measure how well the model's predictions match the target values.\nChoose the appropriate loss function based on your task.\n\n.. code-block:: python\n\n    import torch.nn as nn\n\n    mse_loss = nn.MSELoss()\n    cross_entropy = nn.CrossEntropyLoss()\n    bce_loss = nn.BCELoss()\n    bce_with_logits = nn.BCEWithLogitsLoss()\n    l1_loss = nn.L1Loss()\n    nll_loss = nn.NLLLoss()\n\n    outputs = model(inputs)\n    loss = cross_entropy(outputs, targets)\n\nOptimizers\n----------\n\nOptimizers update model parameters based on computed gradients. Different optimizers\nuse different strategies for parameter updates.\n\n.. code-block:: python\n\n    import torch.optim as optim\n\n    optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)\n    optimizer = optim.Adam(model.parameters(), lr=0.001)\n    optimizer = optim.AdamW(model.parameters(), lr=0.001, weight_decay=0.01)\n    optimizer = optim.RMSprop(model.parameters(), lr=0.01)\n\n    optimizer.zero_grad()\n    loss.backward()\n    optimizer.step()\n\nLearning Rate Scheduler\n-----------------------\n\nAdjust the learning rate during training to improve convergence and model performance.\n\n.. code-block:: python\n\n    from torch.optim.lr_scheduler import StepLR, ReduceLROnPlateau\n\n    scheduler = StepLR(optimizer, step_size=30, gamma=0.1)\n    scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=10)\n\n    for epoch in range(num_epochs):\n        train(...)\n        val_loss = validate(...)\n        scheduler.step(val_loss)\n\nTraining Loop\n-------------\n\nA typical training loop involves forward pass, loss computation, backward pass,\nand parameter updates.\n\n.. code-block:: python\n\n    model.train()\n    for epoch in range(num_epochs):\n        for inputs, targets in train_loader:\n            inputs, targets = inputs.to(device), targets.to(device)\n\n            optimizer.zero_grad()\n            outputs = model(inputs)\n            loss = criterion(outputs, targets)\n            loss.backward()\n            optimizer.step()\n\n            print(f'Loss: {loss.item():.4f}')\n\nEvaluation Mode\n---------------\n\nSwitch between training and evaluation modes to control behavior of layers like\ndropout and batch normalization.\n\n.. code-block:: python\n\n    model.eval()\n    with torch.no_grad():\n        for inputs, targets in test_loader:\n            inputs = inputs.to(device)\n            outputs = model(inputs)\n            predictions = outputs.argmax(dim=1)\n\nSave and Load Models\n--------------------\n\nSave trained models to disk and load them later for inference or continued training.\n\n.. code-block:: python\n\n    torch.save(model.state_dict(), 'model.pth')\n    model.load_state_dict(torch.load('model.pth'))\n\n    torch.save({\n        'epoch': epoch,\n        'model_state_dict': model.state_dict(),\n        'optimizer_state_dict': optimizer.state_dict(),\n        'loss': loss,\n    }, 'checkpoint.pth')\n\n    checkpoint = torch.load('checkpoint.pth')\n    model.load_state_dict(checkpoint['model_state_dict'])\n    optimizer.load_state_dict(checkpoint['optimizer_state_dict'])\n\nDataLoader\n----------\n\nEfficiently load and batch data for training. DataLoader handles shuffling, batching,\nand parallel data loading.\n\n.. code-block:: python\n\n    from torch.utils.data import DataLoader, TensorDataset\n\n    dataset = TensorDataset(x_train, y_train)\n    loader = DataLoader(dataset, batch_size=32, shuffle=True, num_workers=4)\n\n    for batch_x, batch_y in loader:\n        outputs = model(batch_x)\n        loss = criterion(outputs, batch_y)\n\nCustom Dataset\n--------------\n\nCreate custom datasets by subclassing ``Dataset`` for loading data from various sources.\n\n.. code-block:: python\n\n    from torch.utils.data import Dataset\n\n    class CustomDataset(Dataset):\n        def __init__(self, data, labels):\n            self.data = data\n            self.labels = labels\n\n        def __len__(self):\n            return len(self.data)\n\n        def __getitem__(self, idx):\n            return self.data[idx], self.labels[idx]\n\n    dataset = CustomDataset(x_train, y_train)\n    loader = DataLoader(dataset, batch_size=32)\n\nNumPy Conversion\n----------------\n\nConvert between PyTorch tensors and NumPy arrays for interoperability with other\nlibraries. Note that tensors on GPU must be moved to CPU first.\n\n.. code-block:: python\n\n    >>> x = torch.randn([1, 2, 3], device=0)\n    >>> y = x.cpu().numpy()\n    >>> y\n    array([[[-0.11979043,  0.13762406, -1.2633433 ],\n            [-0.380241  ,  1.5320604 , -1.0828359 ]]], dtype=float32)\n\n    import numpy as np\n    arr = np.array([[1, 2], [3, 4]])\n    tensor = torch.from_numpy(arr)\n\nShared Memory\n-------------\n\nCPU tensors and NumPy arrays can share the same underlying memory, so modifications\nto one will affect the other.\n\n.. code-block:: python\n\n    >>> x = torch.randn(1, 2, 3)\n    >>> y = x.numpy()\n    >>> x.add_(1)\n    tensor([[[ 1.8195,  3.0259,  0.6733],\n             [ 2.6539,  1.1562, -0.9821]]])\n    >>> y\n    array([[[ 1.8194908 ,  3.0258512 ,  0.67326605],\n            [ 2.6539469 ,  1.1561831 , -0.98211455]]], dtype=float32)\n\nRandom Seed\n-----------\n\nSet random seeds for reproducibility across different runs. This ensures consistent\nresults when debugging or comparing experiments.\n\n.. code-block:: python\n\n    torch.manual_seed(42)\n    torch.cuda.manual_seed(42)\n    torch.cuda.manual_seed_all(42)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\n    import numpy as np\n    import random\n    np.random.seed(42)\n    random.seed(42)\n\nGPU Memory Management\n---------------------\n\nMonitor and manage GPU memory usage to avoid out-of-memory errors and optimize performance.\n\n.. code-block:: python\n\n    >>> torch.cuda.empty_cache()\n    >>> torch.cuda.memory_allocated()\n    1073741824\n    >>> torch.cuda.memory_reserved()\n    2147483648\n    >>> torch.cuda.max_memory_allocated()\n    3221225472\n\n    torch.cuda.reset_peak_memory_stats()\n    print(torch.cuda.memory_summary())\n\n\nDistributed Training\n--------------------\n\nNCCL (NVIDIA Collective Communication Library) enables efficient multi-GPU and multi-node\ntraining. Initialize the distributed process group and check its status.\n\n.. code-block:: python\n\n    >>> import torch.distributed as dist\n    >>> dist.is_available()\n    True\n    >>> dist.is_nccl_available()\n    True\n    >>> dist.is_initialized()\n    False\n\n    dist.init_process_group(backend='nccl')\n\n    >>> dist.is_initialized()\n    True\n    >>> dist.get_rank()\n    0\n    >>> dist.get_world_size()\n    4\n    >>> dist.get_backend()\n    'nccl'\n\nLaunch with torchrun\n--------------------\n\nUse ``torchrun`` to launch distributed training across multiple GPUs or nodes. It automatically\nsets up environment variables for distributed training.\n\n.. code-block:: bash\n\n    # single node, multiple GPUs\n    torchrun --nproc_per_node=4 train.py\n\n    # multiple nodes\n    torchrun --nproc_per_node=4 \\\n             --nnodes=2 \\\n             --node_rank=0 \\\n             --master_addr=\"192.168.1.1\" \\\n             --master_port=29500 \\\n             train.py\n\nLaunch with Slurm\n-----------------\n\nSubmit distributed training jobs to Slurm clusters. Slurm manages resource allocation\nand node assignment. See :doc:`../hpc/slurm` for more Slurm examples.\n\n.. code-block:: bash\n\n    #!/bin/bash\n    #SBATCH --job-name=pytorch_ddp\n    #SBATCH --nodes=2\n    #SBATCH --ntasks-per-node=4\n    #SBATCH --gres=gpu:4\n    #SBATCH --time=24:00:00\n\n    export MASTER_ADDR=$(scontrol show hostname $SLURM_NODELIST | head -n 1)\n    export MASTER_PORT=29500\n\n    srun torchrun --nproc_per_node=4 \\\n                  --nnodes=$SLURM_NNODES \\\n                  --node_rank=$SLURM_NODEID \\\n                  --master_addr=$MASTER_ADDR \\\n                  --master_port=$MASTER_PORT \\\n                  train.py\n\nDistributedDataParallel\n------------------------\n\nWrap your model with DDP for multi-GPU training. DDP uses NCCL for efficient gradient\nsynchronization across GPUs.\n\n.. code-block:: python\n\n    import torch.nn as nn\n    from torch.nn.parallel import DistributedDataParallel as DDP\n\n    model = Net().to(device)\n    model = DDP(model, device_ids=[local_rank])\n\n    for inputs, targets in train_loader:\n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n\nCollective Operations\n---------------------\n\nNCCL provides collective communication operations for distributed training.\n\n.. code-block:: python\n\n    import torch.distributed as dist\n\n    tensor = torch.randn(2, 3).cuda()\n\n    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)\n    dist.broadcast(tensor, src=0)\n    dist.all_gather(tensor_list, tensor)\n    dist.reduce(tensor, dst=0, op=dist.ReduceOp.SUM)\n    dist.barrier()\n"
  },
  {
    "path": "docs/notes/network/index.rst",
    "content": ".. meta::\n    :description lang=en: Python network programming tutorial covering sockets, TCP/UDP servers, async I/O, SSL/TLS, packet sniffing, and SSH tunneling\n    :keywords: Python, Python3, socket, networking, TCP, UDP, SSL, TLS, async, select, epoll, SSH, packet sniffing, server, client\n\n=======\nNetwork\n=======\n\nNetwork programming is fundamental to modern software development, enabling\ncommunication between processes across machines, data centers, and the internet.\nThis section covers Python's networking capabilities from low-level socket\nprogramming to secure communications. You'll find practical examples for building\nTCP/UDP servers, handling multiple connections with async I/O (select, poll, epoll),\nimplementing TLS/SSL encryption, packet sniffing for network analysis, and SSH\nfor secure remote access and tunneling. Whether you're building web services,\nIoT applications, network tools, or automation scripts, these cheat sheets provide\nthe essential patterns and code snippets you need.\n\n.. toctree::\n    :maxdepth: 1\n\n    python-socket\n    python-socket-server\n    python-socket-async\n    python-socket-ssl\n    python-socket-sniffer\n    python-ssh\n"
  },
  {
    "path": "docs/notes/network/python-socket-async.rst",
    "content": ".. meta::\n    :description lang=en: Python asynchronous socket programming tutorial with select, poll, epoll, kqueue, and selectors module for building high-performance non-blocking servers\n    :keywords: Python, socket, async, select, poll, epoll, kqueue, selectors, non-blocking, event-driven, I/O multiplexing, concurrent connections, high-performance server\n\n=================\nAsync Socket I/O\n=================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nAsynchronous I/O is essential for building high-performance network servers that can\nhandle thousands of concurrent connections efficiently. Traditional blocking I/O\nrequires one thread per connection, which doesn't scale well due to memory overhead\nand context switching costs. Asynchronous I/O solves this by allowing a single thread\nto handle multiple connections using I/O multiplexing—the program monitors multiple\nsockets simultaneously and processes whichever ones are ready for reading or writing.\n\nThis section covers the evolution of I/O multiplexing in Python, from the classic\n``select()`` system call (portable but limited) to modern high-performance mechanisms\nlike ``epoll`` (Linux) and ``kqueue`` (BSD/macOS) that can efficiently handle tens of\nthousands of connections. We also cover the ``selectors`` module, which provides a\nhigh-level, platform-independent interface that automatically uses the best available\nmechanism. Understanding these primitives is valuable even if you use higher-level\nframeworks like ``asyncio``, as they build upon these same concepts.\n\nAsync TCP Server - select\n-------------------------\n\n``select()`` is the oldest and most portable I/O multiplexing mechanism, available on\nvirtually all platforms including Windows, Linux, and macOS. It monitors file descriptors\nfor three conditions: readability (data available to read), writability (buffer space\navailable to write), and exceptional conditions (errors). While portable, ``select()``\nhas limitations: it typically supports only up to 1024 file descriptors and has O(n)\nperformance as it must scan all monitored descriptors on each call.\n\n.. code-block:: python\n\n    from select import select\n    import socket\n\n    host = ('localhost', 5566)\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(host)\n    sock.listen(5)\n\n    read_list = [sock]\n    write_list = []\n    messages = {}\n\n    try:\n        while True:\n            readable, writable, _ = select(read_list, write_list, [])\n\n            for s in readable:\n                if s == sock:\n                    conn, addr = sock.accept()\n                    read_list.append(conn)\n                else:\n                    msg = s.recv(1024)\n                    if msg:\n                        messages[s.fileno()] = msg\n                        write_list.append(s)\n                    else:\n                        read_list.remove(s)\n                        s.close()\n\n            for s in writable:\n                msg = messages.pop(s.fileno(), None)\n                if msg:\n                    s.send(msg)\n                write_list.remove(s)\n    except KeyboardInterrupt:\n        sock.close()\n\nAsync TCP Server - poll\n-----------------------\n\n``poll()`` is similar to ``select()`` but more efficient for large numbers of\nfile descriptors. Available on Unix systems.\n\n.. code-block:: python\n\n    import socket\n    import select\n    import contextlib\n\n    host = 'localhost'\n    port = 5566\n\n    connections = {}\n    requests = {}\n    responses = {}\n\n    @contextlib.contextmanager\n    def create_server(host, port):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        s.setblocking(False)\n        s.bind((host, port))\n        s.listen(10)\n        try:\n            yield s\n        finally:\n            s.close()\n\n    def accept(server, poll):\n        conn, addr = server.accept()\n        conn.setblocking(False)\n        fd = conn.fileno()\n        poll.register(fd, select.POLLIN)\n        requests[fd] = conn\n        connections[fd] = conn\n\n    def recv(fd, poll):\n        conn = requests.pop(fd, None)\n        if not conn:\n            return\n        msg = conn.recv(1024)\n        if msg:\n            responses[fd] = msg\n            poll.modify(fd, select.POLLOUT)\n        else:\n            poll.unregister(fd)\n            conn.close()\n            connections.pop(fd, None)\n\n    def send(fd, poll):\n        conn = connections.get(fd)\n        msg = responses.pop(fd, None)\n        if conn and msg:\n            conn.send(msg)\n            requests[fd] = conn\n            poll.modify(fd, select.POLLIN)\n\n    with create_server(host, port) as server:\n        poll = select.poll()\n        poll.register(server.fileno(), select.POLLIN)\n\n        try:\n            while True:\n                events = poll.poll(1000)\n                for fd, event in events:\n                    if fd == server.fileno():\n                        accept(server, poll)\n                    elif event & (select.POLLIN | select.POLLPRI):\n                        recv(fd, poll)\n                    elif event & select.POLLOUT:\n                        send(fd, poll)\n        except KeyboardInterrupt:\n            pass\n\nAsync TCP Server - epoll\n------------------------\n\n``epoll`` is Linux-specific and the most efficient for handling thousands of\nconnections. It uses edge-triggered or level-triggered notifications.\n\n.. code-block:: python\n\n    import socket\n    import select\n    import contextlib\n\n    host = 'localhost'\n    port = 5566\n\n    connections = {}\n    requests = {}\n    responses = {}\n\n    @contextlib.contextmanager\n    def create_server(host, port):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        s.setblocking(False)\n        s.bind((host, port))\n        s.listen(10)\n        try:\n            yield s\n        finally:\n            s.close()\n\n    def accept(server, epoll):\n        conn, addr = server.accept()\n        conn.setblocking(False)\n        fd = conn.fileno()\n        epoll.register(fd, select.EPOLLIN)\n        requests[fd] = conn\n        connections[fd] = conn\n\n    def recv(fd, epoll):\n        conn = requests.pop(fd, None)\n        if not conn:\n            return\n        msg = conn.recv(1024)\n        if msg:\n            responses[fd] = msg\n            epoll.modify(fd, select.EPOLLOUT)\n        else:\n            epoll.unregister(fd)\n            conn.close()\n            connections.pop(fd, None)\n\n    def send(fd, epoll):\n        conn = connections.get(fd)\n        msg = responses.pop(fd, None)\n        if conn and msg:\n            conn.send(msg)\n            requests[fd] = conn\n            epoll.modify(fd, select.EPOLLIN)\n\n    with create_server(host, port) as server:\n        epoll = select.epoll()\n        epoll.register(server.fileno(), select.EPOLLIN)\n\n        try:\n            while True:\n                events = epoll.poll(1)\n                for fd, event in events:\n                    if fd == server.fileno():\n                        accept(server, epoll)\n                    elif event & select.EPOLLIN:\n                        recv(fd, epoll)\n                    elif event & select.EPOLLOUT:\n                        send(fd, epoll)\n        except KeyboardInterrupt:\n            pass\n        finally:\n            epoll.close()\n\nAsync TCP Server - kqueue\n-------------------------\n\n``kqueue`` is the BSD/macOS equivalent of epoll, providing efficient event\nnotification for large numbers of file descriptors.\n\n.. code-block:: python\n\n    import socket\n    import select\n    import contextlib\n\n    if not hasattr(select, 'kqueue'):\n        print(\"kqueue not supported on this platform\")\n        exit(1)\n\n    host = 'localhost'\n    port = 5566\n\n    connections = {}\n    requests = {}\n    responses = {}\n\n    @contextlib.contextmanager\n    def create_server(host, port):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        s.setblocking(False)\n        s.bind((host, port))\n        s.listen(10)\n        try:\n            yield s\n        finally:\n            s.close()\n\n    def accept(server, kq):\n        conn, addr = server.accept()\n        conn.setblocking(False)\n        fd = conn.fileno()\n        ke = select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)\n        kq.control([ke], 0)\n        requests[fd] = conn\n        connections[fd] = conn\n\n    def recv(fd, kq):\n        conn = requests.pop(fd, None)\n        if not conn:\n            return\n        msg = conn.recv(1024)\n        if msg:\n            responses[fd] = msg\n            # Switch from read to write\n            ke_del = select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)\n            ke_add = select.kevent(fd, select.KQ_FILTER_WRITE, select.KQ_EV_ADD)\n            kq.control([ke_del, ke_add], 0)\n            requests[fd] = conn\n        else:\n            ke = select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_DELETE)\n            kq.control([ke], 0)\n            conn.close()\n            connections.pop(fd, None)\n\n    def send(fd, kq):\n        conn = connections.get(fd)\n        msg = responses.pop(fd, None)\n        if conn and msg:\n            conn.send(msg)\n            # Switch from write to read\n            ke_del = select.kevent(fd, select.KQ_FILTER_WRITE, select.KQ_EV_DELETE)\n            ke_add = select.kevent(fd, select.KQ_FILTER_READ, select.KQ_EV_ADD)\n            kq.control([ke_del, ke_add], 0)\n            requests[fd] = conn\n\n    with create_server(host, port) as server:\n        kq = select.kqueue()\n        ke = select.kevent(server.fileno(), select.KQ_FILTER_READ, select.KQ_EV_ADD)\n        kq.control([ke], 0)\n\n        try:\n            while True:\n                events = kq.control(None, 1024, 1)\n                for e in events:\n                    fd = e.ident\n                    if fd == server.fileno():\n                        accept(server, kq)\n                    elif e.filter == select.KQ_FILTER_READ:\n                        recv(fd, kq)\n                    elif e.filter == select.KQ_FILTER_WRITE:\n                        send(fd, kq)\n        except KeyboardInterrupt:\n            pass\n        finally:\n            kq.close()\n\nHigh-Level API - selectors\n--------------------------\n\nThe ``selectors`` module (Python 3.4+) provides a high-level, platform-independent\ninterface that automatically uses the best available mechanism (epoll, kqueue, etc.).\n\n.. code-block:: python\n\n    import selectors\n    import socket\n    import contextlib\n\n    @contextlib.contextmanager\n    def create_server(host, port):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        s.bind((host, port))\n        s.listen(10)\n        sel = selectors.DefaultSelector()\n        try:\n            yield s, sel\n        finally:\n            s.close()\n            sel.close()\n\n    def accept_handler(sock, sel):\n        conn, addr = sock.accept()\n        sel.register(conn, selectors.EVENT_READ, read_handler)\n\n    def read_handler(conn, sel):\n        msg = conn.recv(1024)\n        if msg:\n            conn.send(msg)\n        else:\n            sel.unregister(conn)\n            conn.close()\n\n    host = 'localhost'\n    port = 5566\n\n    with create_server(host, port) as (sock, sel):\n        sel.register(sock, selectors.EVENT_READ, accept_handler)\n        try:\n            while True:\n                events = sel.select()\n                for key, mask in events:\n                    handler = key.data\n                    handler(key.fileobj, sel)\n        except KeyboardInterrupt:\n            pass\n\nComparison of I/O Multiplexing Methods\n--------------------------------------\n\n+------------+------------+------------------+---------------------------+\n| Method     | Platform   | Scalability      | Notes                     |\n+============+============+==================+===========================+\n| select     | All        | O(n) - Limited   | Max ~1024 FDs             |\n+------------+------------+------------------+---------------------------+\n| poll       | Unix       | O(n) - Better    | No FD limit               |\n+------------+------------+------------------+---------------------------+\n| epoll      | Linux      | O(1) - Excellent | Edge/level triggered      |\n+------------+------------+------------------+---------------------------+\n| kqueue     | BSD/macOS  | O(1) - Excellent | Similar to epoll          |\n+------------+------------+------------------+---------------------------+\n| selectors  | All        | Best available   | Recommended for new code  |\n+------------+------------+------------------+---------------------------+\n\n.. note::\n\n    For new code, use the ``selectors`` module or ``asyncio`` for async I/O.\n    The low-level APIs (select, poll, epoll, kqueue) are mainly useful for\n    understanding how async I/O works or when you need fine-grained control.\n"
  },
  {
    "path": "docs/notes/network/python-socket-server.rst",
    "content": ".. meta::\n    :description lang=en: Python TCP and UDP server tutorial with examples for echo servers, IPv6 dual-stack, Unix domain sockets, SocketServer module, threaded servers, and zero-copy sendfile\n    :keywords: Python, socket, TCP server, UDP server, echo server, IPv6, dual-stack, Unix domain socket, SocketServer, network programming, threaded server, sendfile, IPC\n\n==============\nSocket Servers\n==============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nBuilding network servers is one of the most common applications of socket programming.\nA server listens for incoming connections on a specific port, accepts client connections,\nand processes requests. Python's socket module provides all the primitives needed to\nbuild robust TCP and UDP servers, from simple single-threaded echo servers to complex\nmulti-client applications.\n\nThis section covers building TCP and UDP servers in Python using the socket module,\nincluding simple echo servers for learning the basics, IPv6 and dual-stack servers\nfor modern network compatibility, Unix domain sockets for high-performance local IPC,\nthe SocketServer module for rapid development, threaded servers for handling multiple\nclients, and zero-copy file transfer with sendfile. These patterns form the foundation\nfor building production-ready network services.\n\nSimple TCP Echo Server\n----------------------\n\nA basic TCP echo server demonstrates the fundamental server pattern: create a socket,\nbind to an address, listen for connections, accept clients, and process data. This\nexample uses Python's context manager protocol for proper resource cleanup, ensuring\nthe socket is closed even if an exception occurs. The ``SO_REUSEADDR`` option allows\nthe server to restart immediately without waiting for the TIME_WAIT state to expire.\n\n.. code-block:: python\n\n    import socket\n\n    class Server:\n        def __init__(self, host, port):\n            self._host = host\n            self._port = port\n\n        def __enter__(self):\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            sock.bind((self._host, self._port))\n            sock.listen(10)\n            self._sock = sock\n            return self._sock\n\n        def __exit__(self, *exc_info):\n            if exc_info[0]:\n                import traceback\n                traceback.print_exception(*exc_info)\n            self._sock.close()\n\n    if __name__ == '__main__':\n        with Server('localhost', 5566) as s:\n            while True:\n                conn, addr = s.accept()\n                msg = conn.recv(1024)\n                conn.send(msg)\n                conn.close()\n\nOutput:\n\n.. code-block:: console\n\n    $ nc localhost 5566\n    Hello World\n    Hello World\n\nTCP Echo Server via IPv6\n------------------------\n\nIPv6 server using ``AF_INET6`` address family. Note the different address tuple\nformat which includes flow info and scope ID.\n\n.. code-block:: python\n\n    import contextlib\n    import socket\n\n    host = \"::1\"\n    port = 5566\n\n    @contextlib.contextmanager\n    def server(host, port):\n        s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)\n        try:\n            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            s.bind((host, port))\n            s.listen(10)\n            yield s\n        finally:\n            s.close()\n\n    with server(host, port) as s:\n        try:\n            while True:\n                conn, addr = s.accept()\n                msg = conn.recv(1024)\n                if msg:\n                    conn.send(msg)\n                conn.close()\n        except KeyboardInterrupt:\n            pass\n\nOutput:\n\n.. code-block:: bash\n\n    $ python3 ipv6.py &\n    $ nc -6 ::1 5566\n    Hello IPv6\n    Hello IPv6\n\nDual-Stack Server (IPv4 and IPv6)\n---------------------------------\n\nA server that accepts both IPv4 and IPv6 connections by binding to ``::`` and\ndisabling ``IPV6_V6ONLY``. IPv4 clients appear as IPv4-mapped IPv6 addresses.\n\n.. code-block:: python\n\n    import contextlib\n    import socket\n\n    host = \"::\"\n    port = 5566\n\n    @contextlib.contextmanager\n    def server(host, port):\n        s = socket.socket(socket.AF_INET6, socket.SOCK_STREAM, 0)\n        try:\n            s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)\n            s.bind((host, port))\n            s.listen(10)\n            yield s\n        finally:\n            s.close()\n\n    with server(host, port) as s:\n        try:\n            while True:\n                conn, addr = s.accept()\n                print(f\"Connection from: {conn.getpeername()}\")\n                msg = conn.recv(1024)\n                if msg:\n                    conn.send(msg)\n                conn.close()\n        except KeyboardInterrupt:\n            pass\n\nOutput:\n\n.. code-block:: bash\n\n    $ python3 dual_stack.py &\n    $ nc -4 127.0.0.1 5566\n    Connection from: ('::ffff:127.0.0.1', 42604, 0, 0)\n    Hello IPv4\n    Hello IPv4\n    $ nc -6 ::1 5566\n    Connection from: ('::1', 50882, 0, 0)\n    Hello IPv6\n    Hello IPv6\n\nTCP Server via SocketServer\n---------------------------\n\nThe ``socketserver`` module provides a higher-level interface for building servers.\nIt handles the socket setup and connection management automatically.\n\n.. code-block:: python\n\n    import socketserver\n\n    class EchoHandler(socketserver.BaseRequestHandler):\n        def handle(self):\n            data = self.request.recv(1024)\n            print(f\"Client: {self.client_address}\")\n            self.request.sendall(data)\n\n    if __name__ == '__main__':\n        with socketserver.TCPServer(('localhost', 5566), EchoHandler) as server:\n            server.serve_forever()\n\nSimple UDP Echo Server\n----------------------\n\nUDP servers use ``SOCK_DGRAM`` and don't require connection handling. Each\n``recvfrom()`` returns the data and sender address.\n\n.. code-block:: python\n\n    import socket\n\n    class UDPServer:\n        def __init__(self, host, port):\n            self._host = host\n            self._port = port\n\n        def __enter__(self):\n            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n            sock.bind((self._host, self._port))\n            self._sock = sock\n            return sock\n\n        def __exit__(self, *exc_info):\n            if exc_info[0]:\n                import traceback\n                traceback.print_exception(*exc_info)\n            self._sock.close()\n\n    if __name__ == '__main__':\n        with UDPServer('localhost', 5566) as s:\n            while True:\n                msg, addr = s.recvfrom(1024)\n                s.sendto(msg, addr)\n\nOutput:\n\n.. code-block:: console\n\n    $ nc -u localhost 5566\n    Hello World\n    Hello World\n\nUDP Server via SocketServer\n---------------------------\n\n.. code-block:: python\n\n    import socketserver\n\n    class UDPHandler(socketserver.BaseRequestHandler):\n        def handle(self):\n            data, sock = self.request\n            print(f\"Client: {self.client_address}\")\n            sock.sendto(data, self.client_address)\n\n    if __name__ == '__main__':\n        with socketserver.UDPServer(('localhost', 5566), UDPHandler) as server:\n            server.serve_forever()\n\nUDP Client - Sender\n-------------------\n\nSimple UDP client that sends periodic messages.\n\n.. code-block:: python\n\n    import socket\n    import time\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n    host = ('localhost', 5566)\n\n    while True:\n        sock.sendto(b\"Hello\\n\", host)\n        time.sleep(5)\n\nBroadcast UDP Packets\n---------------------\n\nSend UDP packets to all hosts on the local network using broadcast address.\n\n.. code-block:: python\n\n    import socket\n    import time\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n    sock.bind(('', 0))\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)\n\n    while True:\n        msg = f'{time.time()}\\n'.encode()\n        sock.sendto(msg, ('<broadcast>', 5566))\n        time.sleep(5)\n\nOutput:\n\n.. code-block:: console\n\n    $ nc -k -w 1 -ul 5566\n    1431473025.72\n\nUnix Domain Socket\n------------------\n\nUnix domain sockets provide inter-process communication on the same machine,\nfaster than TCP/IP loopback as they bypass the network stack.\n\n.. code-block:: python\n\n    import socket\n    import contextlib\n    import os\n\n    @contextlib.contextmanager\n    def domain_server(addr):\n        try:\n            if os.path.exists(addr):\n                os.unlink(addr)\n            sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\n            sock.bind(addr)\n            sock.listen(10)\n            yield sock\n        finally:\n            sock.close()\n            if os.path.exists(addr):\n                os.unlink(addr)\n\n    addr = \"./domain.sock\"\n    with domain_server(addr) as sock:\n        while True:\n            conn, _ = sock.accept()\n            msg = conn.recv(1024)\n            conn.send(msg)\n            conn.close()\n\nOutput:\n\n.. code-block:: console\n\n    $ nc -U ./domain.sock\n    Hello\n    Hello\n\nSocket Pair for IPC\n-------------------\n\n``socketpair()`` creates a pair of connected sockets, useful for bidirectional\ncommunication between parent and child processes.\n\n.. code-block:: python\n\n    import os\n    import socket\n\n    child_sock, parent_sock = socket.socketpair()\n    pid = os.fork()\n\n    try:\n        if pid == 0:\n            # Child process\n            parent_sock.close()\n            child_sock.send(b'Hello Parent!')\n            msg = child_sock.recv(1024)\n            print(f'Parent says: {msg}')\n        else:\n            # Parent process\n            child_sock.close()\n            msg = parent_sock.recv(1024)\n            print(f'Child says: {msg}')\n            parent_sock.send(b'Hello Child!')\n            os.wait()\n    finally:\n        child_sock.close()\n        parent_sock.close()\n\nOutput:\n\n.. code-block:: bash\n\n    $ python socketpair_demo.py\n    Child says: b'Hello Parent!'\n    Parent says: b'Hello Child!'\n\nThreaded TCP Server\n-------------------\n\nHandle multiple clients concurrently using threads.\n\n.. code-block:: python\n\n    from threading import Thread\n    import socket\n\n    def handle_client(conn):\n        while True:\n            msg = conn.recv(1024)\n            if not msg:\n                break\n            conn.send(msg)\n        conn.close()\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(('localhost', 5566))\n    sock.listen(5)\n\n    while True:\n        conn, addr = sock.accept()\n        t = Thread(target=handle_client, args=(conn,))\n        t.daemon = True\n        t.start()\n\nUsing sendfile for Zero-Copy Transfer\n-------------------------------------\n\n``os.sendfile()`` efficiently transfers file data to a socket without copying\nthrough user space (zero-copy).\n\n.. code-block:: python\n\n    import os\n    import socket\n    import contextlib\n\n    @contextlib.contextmanager\n    def server(host, port):\n        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        s.bind((host, port))\n        s.listen(10)\n        try:\n            yield s\n        finally:\n            s.close()\n\n    def send_file(conn, filepath):\n        with open(filepath, 'rb') as f:\n            fd = f.fileno()\n            size = os.fstat(fd).st_size\n            offset = 0\n            while size > 0:\n                sent = os.sendfile(conn.fileno(), fd, offset, min(size, 65536))\n                offset += sent\n                size -= sent\n\n    # Server that sends a file to each client\n    with server('localhost', 5566) as s:\n        while True:\n            conn, addr = s.accept()\n            send_file(conn, 'large_file.bin')\n            conn.close()\n"
  },
  {
    "path": "docs/notes/network/python-socket-sniffer.rst",
    "content": ".. meta::\n    :description lang=en: Python packet sniffing and raw socket programming tutorial for capturing and parsing IP, TCP, UDP, ARP packets, network analysis, and Linux kernel crypto API\n    :keywords: Python, socket, raw socket, packet sniffer, IP header, TCP header, ARP, network analysis, ctypes, struct, Wireshark, tcpdump, AF_ALG, kernel crypto, scapy\n\n================\nPacket Sniffing\n================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nRaw sockets provide direct access to network packets at the IP layer and below,\nbypassing the normal TCP/UDP protocol stack. This low-level access is essential for\nbuilding network analysis tools, intrusion detection systems, custom protocol\nimplementations, and security research tools. While libraries like Wireshark and\ntcpdump are commonly used for packet capture, understanding how to parse packets\nin Python gives you the flexibility to build custom analysis tools tailored to\nyour specific needs.\n\nThis section covers capturing and parsing network packets using Python's raw socket\ninterface, the ``ctypes`` module for defining C-compatible data structures, and the\n``struct`` module for binary data parsing. You'll learn to decode IP headers to\nextract source and destination addresses, parse TCP headers to analyze connection\nstates and flags, capture ARP packets to monitor address resolution, and use the\nLinux kernel's AF_ALG interface for hardware-accelerated cryptography. These\ntechniques form the foundation for building tools like network monitors, protocol\nanalyzers, and security scanners.\n\n.. warning::\n\n    Raw socket operations typically require root/administrator privileges on most\n    operating systems. Use these techniques responsibly and only on networks you\n    own or have explicit permission to analyze. Unauthorized packet sniffing may\n    violate laws and regulations in your jurisdiction.\n\nSniffer IP Packets\n------------------\n\nCapturing IP packets requires creating a raw socket with ``SOCK_RAW`` and specifying\nthe protocol to capture (e.g., ``IPPROTO_ICMP`` for ICMP packets). The IP header is\na 20-byte structure (without options) containing version, header length, type of\nservice, total length, identification, flags, fragment offset, TTL, protocol, checksum,\nand source/destination addresses. Using ``ctypes.Structure``, we can define a Python\nclass that maps directly to this binary layout for easy field access.\n\n.. code-block:: python\n\n    from ctypes import Structure, c_ubyte, c_uint8, c_uint16, c_uint32\n    import socket\n    import struct\n\n    # IP protocol numbers\n    PROTO_MAP = {\n        1: \"ICMP\",\n        2: \"IGMP\",\n        6: \"TCP\",\n        17: \"UDP\",\n        27: \"RDP\"\n    }\n\n    class IP(Structure):\n        \"\"\"IP header structure (20 bytes).\"\"\"\n        _fields_ = [\n            (\"ip_hl\", c_ubyte, 4),   # Header length\n            (\"ip_v\", c_ubyte, 4),    # Version\n            (\"ip_tos\", c_uint8),     # Type of service\n            (\"ip_len\", c_uint16),    # Total length\n            (\"ip_id\", c_uint16),     # Identification\n            (\"ip_off\", c_uint16),    # Fragment offset\n            (\"ip_ttl\", c_uint8),     # Time to live\n            (\"ip_p\", c_uint8),       # Protocol\n            (\"ip_sum\", c_uint16),    # Checksum\n            (\"ip_src\", c_uint32),    # Source address\n            (\"ip_dst\", c_uint32),    # Destination address\n        ]\n\n        def __new__(cls, buf=None):\n            return cls.from_buffer_copy(buf)\n\n        def __init__(self, buf=None):\n            src = struct.pack(\"<L\", self.ip_src)\n            self.src = socket.inet_ntoa(src)\n            dst = struct.pack(\"<L\", self.ip_dst)\n            self.dst = socket.inet_ntoa(dst)\n            self.proto = PROTO_MAP.get(self.ip_p, f\"Unknown({self.ip_p})\")\n\n    # Create raw socket for ICMP\n    host = '0.0.0.0'\n    s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP)\n    s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)\n    s.bind((host, 0))\n\n    print(\"Sniffer start... (Ctrl+C to stop)\")\n    try:\n        while True:\n            buf = s.recvfrom(65535)[0]\n            ip_header = IP(buf[:20])\n            print(f'{ip_header.proto}: {ip_header.src} -> {ip_header.dst}')\n    except KeyboardInterrupt:\n        s.close()\n\nOutput:\n\n.. code-block:: console\n\n    $ sudo python sniffer.py\n    Sniffer start...\n    ICMP: 127.0.0.1 -> 127.0.0.1\n    ICMP: 127.0.0.1 -> 127.0.0.1\n\nSniffer TCP Packets\n-------------------\n\nParse TCP headers to extract port numbers, sequence numbers, and flags.\n\n.. code-block:: python\n\n    import socket\n    import platform\n    from struct import unpack\n    from contextlib import contextmanager\n\n    if platform.system() != \"Linux\":\n        print(\"This example requires Linux\")\n        exit(1)\n\n    @contextmanager\n    def create_socket():\n        s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)\n        try:\n            yield s\n        finally:\n            s.close()\n\n    def parse_tcp_packet(pkt):\n        # IP header (first 20 bytes, variable length)\n        iphdr = unpack('!BBHHHBBH4s4s', pkt[0:20])\n        iplen = (iphdr[0] & 0xf) * 4\n\n        # TCP header (next 20 bytes minimum)\n        tcphdr = unpack('!HHLLBBHHH', pkt[iplen:iplen+20])\n\n        return {\n            'src_port': tcphdr[0],\n            'dst_port': tcphdr[1],\n            'seq': tcphdr[2],\n            'ack': tcphdr[3],\n            'data_offset': tcphdr[4] >> 4,\n            'flags': {\n                'FIN': tcphdr[5] & 0x01,\n                'SYN': tcphdr[5] & 0x02,\n                'RST': tcphdr[5] & 0x04,\n                'PSH': tcphdr[5] & 0x08,\n                'ACK': tcphdr[5] & 0x10,\n                'URG': tcphdr[5] & 0x20,\n            },\n            'window': tcphdr[6],\n            'checksum': tcphdr[7],\n        }\n\n    try:\n        with create_socket() as s:\n            print(\"TCP Sniffer started...\")\n            while True:\n                pkt, addr = s.recvfrom(65535)\n                tcp = parse_tcp_packet(pkt)\n\n                # Skip packets without data\n                iplen = (pkt[0] & 0xf) * 4\n                tcplen = tcp['data_offset'] * 4\n                data = pkt[iplen + tcplen:]\n                if not data:\n                    continue\n\n                flags = [k for k, v in tcp['flags'].items() if v]\n                print(f\"Port {tcp['src_port']} -> {tcp['dst_port']} \"\n                      f\"[{','.join(flags)}] Seq={tcp['seq']}\")\n                print(f\"Data: {data[:50]}...\")\n    except KeyboardInterrupt:\n        pass\n\nSniffer ARP Packets\n-------------------\n\nCapture ARP (Address Resolution Protocol) packets to see MAC-to-IP mappings.\n\n.. code-block:: python\n\n    import socket\n    import struct\n    import binascii\n\n    # Create raw socket for all packets\n    rawSocket = socket.socket(socket.AF_PACKET, socket.SOCK_RAW,\n                              socket.htons(0x0003))\n\n    print(\"ARP Sniffer started...\")\n    while True:\n        packet = rawSocket.recvfrom(2048)\n\n        # Ethernet header (14 bytes)\n        ethhdr = packet[0][0:14]\n        eth = struct.unpack(\"!6s6s2s\", ethhdr)\n\n        # Check if ARP packet (0x0806)\n        if eth[2] != b'\\x08\\x06':\n            continue\n\n        # ARP header (28 bytes)\n        arphdr = packet[0][14:42]\n        arp = struct.unpack(\"2s2s1s1s2s6s4s6s4s\", arphdr)\n\n        print(\"=\" * 50)\n        print(\"ETHERNET FRAME\")\n        print(f\"  Dest MAC:   {binascii.hexlify(eth[0]).decode()}\")\n        print(f\"  Source MAC: {binascii.hexlify(eth[1]).decode()}\")\n        print(\"ARP HEADER\")\n        print(f\"  Hardware:   {binascii.hexlify(arp[0]).decode()}\")\n        print(f\"  Protocol:   {binascii.hexlify(arp[1]).decode()}\")\n        print(f\"  Opcode:     {binascii.hexlify(arp[4]).decode()} \"\n              f\"({'Request' if arp[4] == b'\\\\x00\\\\x01' else 'Reply'})\")\n        print(f\"  Sender MAC: {binascii.hexlify(arp[5]).decode()}\")\n        print(f\"  Sender IP:  {socket.inet_ntoa(arp[6])}\")\n        print(f\"  Target MAC: {binascii.hexlify(arp[7]).decode()}\")\n        print(f\"  Target IP:  {socket.inet_ntoa(arp[8])}\")\n\nParse Packet with struct\n------------------------\n\nUsing ``struct`` module for flexible packet parsing.\n\n.. code-block:: python\n\n    import struct\n    import socket\n\n    def parse_ip_header(data):\n        \"\"\"Parse IP header from raw bytes.\"\"\"\n        # ! = network byte order (big-endian)\n        # B = unsigned char, H = unsigned short, 4s = 4-byte string\n        fields = struct.unpack('!BBHHHBBH4s4s', data[:20])\n\n        return {\n            'version': fields[0] >> 4,\n            'ihl': fields[0] & 0x0F,\n            'tos': fields[1],\n            'total_length': fields[2],\n            'identification': fields[3],\n            'flags': fields[4] >> 13,\n            'fragment_offset': fields[4] & 0x1FFF,\n            'ttl': fields[5],\n            'protocol': fields[6],\n            'checksum': fields[7],\n            'src_ip': socket.inet_ntoa(fields[8]),\n            'dst_ip': socket.inet_ntoa(fields[9]),\n        }\n\n    def parse_udp_header(data):\n        \"\"\"Parse UDP header from raw bytes.\"\"\"\n        fields = struct.unpack('!HHHH', data[:8])\n        return {\n            'src_port': fields[0],\n            'dst_port': fields[1],\n            'length': fields[2],\n            'checksum': fields[3],\n        }\n\n    # Example usage with captured packet\n    # ip_data = ... (raw IP packet bytes)\n    # ip = parse_ip_header(ip_data)\n    # if ip['protocol'] == 17:  # UDP\n    #     udp = parse_udp_header(ip_data[ip['ihl']*4:])\n\nLinux Kernel Crypto API (AF_ALG)\n--------------------------------\n\nUse Linux kernel's cryptographic API through sockets for hardware-accelerated\nencryption. Requires Linux 2.6.38+ and Python 3.6+.\n\n.. code-block:: python\n\n    import socket\n    import hashlib\n    import contextlib\n\n    @contextlib.contextmanager\n    def create_alg(typ, name):\n        s = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)\n        try:\n            s.bind((typ, name))\n            yield s\n        finally:\n            s.close()\n\n    # SHA-256 hash using kernel crypto\n    msg = b'Python is awesome!'\n\n    with create_alg('hash', 'sha256') as algo:\n        op, _ = algo.accept()\n        with op:\n            op.sendall(msg)\n            digest = op.recv(512)\n            print(f\"AF_ALG SHA256: {digest.hex()}\")\n\n            # Verify against hashlib\n            expected = hashlib.sha256(msg).digest()\n            assert digest == expected\n\nAES-CBC Encryption via AF_ALG\n-----------------------------\n\n.. code-block:: python\n\n    import socket\n    import os\n\n    BS = 16  # Block size\n    pad = lambda s: s + (BS - len(s) % BS) * bytes([BS - len(s) % BS])\n    unpad = lambda s: s[:-s[-1]]\n\n    def aes_cbc_encrypt(plaintext, key, iv):\n        with socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) as algo:\n            algo.bind(('skcipher', 'cbc(aes)'))\n            algo.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, key)\n            op, _ = algo.accept()\n            with op:\n                plaintext = pad(plaintext)\n                op.sendmsg_afalg([plaintext],\n                                op=socket.ALG_OP_ENCRYPT,\n                                iv=iv)\n                return op.recv(len(plaintext))\n\n    def aes_cbc_decrypt(ciphertext, key, iv):\n        with socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0) as algo:\n            algo.bind(('skcipher', 'cbc(aes)'))\n            algo.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, key)\n            op, _ = algo.accept()\n            with op:\n                op.sendmsg_afalg([ciphertext],\n                                op=socket.ALG_OP_DECRYPT,\n                                iv=iv)\n                return unpad(op.recv(len(ciphertext)))\n\n    # Example\n    key = os.urandom(32)  # AES-256\n    iv = os.urandom(16)\n    plaintext = b\"Secret message!\"\n\n    ciphertext = aes_cbc_encrypt(plaintext, key, iv)\n    decrypted = aes_cbc_decrypt(ciphertext, key, iv)\n\n    print(f\"Ciphertext: {ciphertext.hex()}\")\n    print(f\"Decrypted: {decrypted}\")\n\nUseful Tools for Packet Analysis\n--------------------------------\n\nWhile raw sockets are educational, consider these tools for production use:\n\n.. code-block:: python\n\n    # Scapy - powerful packet manipulation library\n    # pip install scapy\n    from scapy.all import sniff, IP, TCP\n\n    def packet_callback(pkt):\n        if IP in pkt and TCP in pkt:\n            print(f\"{pkt[IP].src}:{pkt[TCP].sport} -> \"\n                  f\"{pkt[IP].dst}:{pkt[TCP].dport}\")\n\n    # Sniff 10 TCP packets\n    sniff(filter=\"tcp\", prn=packet_callback, count=10)\n\n    # dpkt - fast packet parsing\n    # pip install dpkt\n    import dpkt\n\n    with open('capture.pcap', 'rb') as f:\n        pcap = dpkt.pcap.Reader(f)\n        for ts, buf in pcap:\n            eth = dpkt.ethernet.Ethernet(buf)\n            if isinstance(eth.data, dpkt.ip.IP):\n                ip = eth.data\n                print(f\"{dpkt.utils.inet_to_str(ip.src)} -> \"\n                      f\"{dpkt.utils.inet_to_str(ip.dst)}\")\n"
  },
  {
    "path": "docs/notes/network/python-socket-ssl.rst",
    "content": ".. meta::\n    :description lang=en: Python TLS/SSL socket programming tutorial covering secure servers, certificate handling, cipher configuration, mutual TLS (mTLS), and non-blocking SSL for HTTPS and encrypted communication\n    :keywords: Python, socket, SSL, TLS, secure socket, certificate, cipher, HTTPS, encryption, network security, mTLS, mutual TLS, X.509, OpenSSL, cryptography\n\n==================\nSSL/TLS Sockets\n==================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nTransport Layer Security (TLS), formerly known as Secure Sockets Layer (SSL), is the\nstandard protocol for encrypting network communication. TLS provides three essential\nsecurity properties: confidentiality (data is encrypted and cannot be read by\neavesdroppers), integrity (data cannot be modified in transit without detection),\nand authentication (parties can verify each other's identity using certificates).\nEvery HTTPS connection, secure email, and VPN uses TLS under the hood.\n\nPython's ``ssl`` module provides a comprehensive interface for TLS, allowing you to\nwrap regular sockets with encryption. This section covers creating TLS-enabled servers\nand clients, configuring cipher suites for security compliance, handling X.509\ncertificates, implementing mutual TLS (mTLS) for client authentication, and building\nnon-blocking TLS servers for high-performance applications. Whether you're building\na secure API server, implementing certificate pinning, or debugging TLS handshake\nissues, these examples provide the foundation you need.\n\nSimple TLS Echo Server\n----------------------\n\nA basic TLS server wraps accepted TCP connections with an SSL context to provide\nencryption. The server requires a certificate (public key) and private key, which\ncan be self-signed for testing or obtained from a Certificate Authority (CA) for\nproduction use. The ``SSLContext`` object manages all TLS settings including protocol\nversion, cipher suites, and certificate verification options.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(('localhost', 5566))\n    sock.listen(10)\n\n    sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    sslctx.load_cert_chain(certfile='cert.pem', keyfile='key.pem')\n\n    try:\n        while True:\n            conn, addr = sock.accept()\n            sslconn = sslctx.wrap_socket(conn, server_side=True)\n            msg = sslconn.recv(1024)\n            if msg:\n                sslconn.send(msg)\n            sslconn.close()\n    finally:\n        sock.close()\n\nGenerate self-signed certificate and test:\n\n.. code-block:: bash\n\n    # Generate private key and self-signed certificate\n    $ openssl genrsa -out key.pem 2048\n    $ openssl req -x509 -new -nodes -key key.pem -days 365 -out cert.pem\n\n    # Run server\n    $ python3 ssl_server.py &\n\n    # Test with openssl client\n    $ openssl s_client -connect localhost:5566\n    Hello SSL\n    Hello SSL\n\nTLS Server with Cipher Configuration\n------------------------------------\n\nConfigure specific cipher suites for security compliance or compatibility.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n    import json\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(('localhost', 5566))\n    sock.listen(10)\n\n    sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    sslctx.load_cert_chain(certfile='cert.pem', keyfile='key.pem')\n\n    # Set specific ciphers (TLS 1.2)\n    sslctx.set_ciphers('ECDHE+AESGCM:DHE+AESGCM')\n\n    # Print configured ciphers\n    print(json.dumps(sslctx.get_ciphers(), indent=2))\n\n    try:\n        while True:\n            conn, addr = sock.accept()\n            sslconn = sslctx.wrap_socket(conn, server_side=True)\n            print(f\"Cipher: {sslconn.cipher()}\")\n            msg = sslconn.recv(1024)\n            if msg:\n                sslconn.send(msg)\n            sslconn.close()\n    finally:\n        sock.close()\n\nTLS Client\n----------\n\nConnect to a TLS server with certificate verification.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    hostname = 'www.google.com'\n    port = 443\n\n    # Create default SSL context (verifies certificates)\n    context = ssl.create_default_context()\n\n    with socket.create_connection((hostname, port)) as sock:\n        with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n            print(f\"TLS version: {ssock.version()}\")\n            print(f\"Cipher: {ssock.cipher()}\")\n\n            # Send HTTP request\n            ssock.send(b\"GET / HTTP/1.1\\r\\nHost: www.google.com\\r\\n\\r\\n\")\n            response = ssock.recv(4096)\n            print(response[:200])\n\nTLS Client with Custom CA\n-------------------------\n\nVerify server certificate against a custom Certificate Authority.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    hostname = 'localhost'\n    port = 5566\n\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    context.load_verify_locations('ca-cert.pem')  # CA certificate\n    context.check_hostname = True\n    context.verify_mode = ssl.CERT_REQUIRED\n\n    with socket.create_connection((hostname, port)) as sock:\n        with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n            ssock.send(b\"Hello\")\n            print(ssock.recv(1024))\n\nMutual TLS (mTLS)\n-----------------\n\nBoth client and server present certificates for mutual authentication.\n\nServer:\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(('localhost', 5566))\n    sock.listen(10)\n\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n    context.load_cert_chain('server-cert.pem', 'server-key.pem')\n    context.load_verify_locations('ca-cert.pem')\n    context.verify_mode = ssl.CERT_REQUIRED  # Require client cert\n\n    try:\n        while True:\n            conn, addr = sock.accept()\n            sslconn = context.wrap_socket(conn, server_side=True)\n            # Get client certificate info\n            cert = sslconn.getpeercert()\n            print(f\"Client: {cert.get('subject')}\")\n            msg = sslconn.recv(1024)\n            if msg:\n                sslconn.send(msg)\n            sslconn.close()\n    finally:\n        sock.close()\n\nClient:\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)\n    context.load_cert_chain('client-cert.pem', 'client-key.pem')\n    context.load_verify_locations('ca-cert.pem')\n\n    with socket.create_connection(('localhost', 5566)) as sock:\n        with context.wrap_socket(sock, server_hostname='localhost') as ssock:\n            ssock.send(b\"Hello mTLS\")\n            print(ssock.recv(1024))\n\nNon-blocking TLS with selectors\n-------------------------------\n\nHandle TLS handshake and I/O asynchronously using the selectors module.\n\n.. code-block:: python\n\n    import socket\n    import selectors\n    import ssl\n    from functools import partial\n\n    sslctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)\n    sslctx.load_cert_chain(certfile=\"cert.pem\", keyfile=\"key.pem\")\n\n    def accept(sock, sel):\n        conn, addr = sock.accept()\n        sslconn = sslctx.wrap_socket(conn,\n                                     server_side=True,\n                                     do_handshake_on_connect=False)\n        sel.register(sslconn, selectors.EVENT_READ, do_handshake)\n\n    def do_handshake(sslconn, sel):\n        try:\n            sslconn.do_handshake()\n            sel.modify(sslconn, selectors.EVENT_READ, read)\n        except ssl.SSLWantReadError:\n            pass  # Need more data, wait for next event\n        except ssl.SSLWantWriteError:\n            sel.modify(sslconn, selectors.EVENT_WRITE, do_handshake)\n\n    def read(sslconn, sel):\n        try:\n            msg = sslconn.recv(1024)\n            if msg:\n                sel.modify(sslconn, selectors.EVENT_WRITE,\n                          partial(write, msg=msg))\n            else:\n                sel.unregister(sslconn)\n                sslconn.close()\n        except ssl.SSLWantReadError:\n            pass\n\n    def write(sslconn, sel, msg=None):\n        try:\n            if msg:\n                sslconn.send(msg)\n            sel.modify(sslconn, selectors.EVENT_READ, read)\n        except ssl.SSLWantWriteError:\n            pass\n\n    # Main server loop\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n    sock.bind(('localhost', 5566))\n    sock.listen(10)\n\n    sel = selectors.DefaultSelector()\n    sel.register(sock, selectors.EVENT_READ, accept)\n\n    try:\n        while True:\n            events = sel.select()\n            for key, mask in events:\n                handler = key.data\n                handler(key.fileobj, sel)\n    except KeyboardInterrupt:\n        pass\n    finally:\n        sock.close()\n        sel.close()\n\nGet Certificate Information\n---------------------------\n\nRetrieve and inspect server certificate details.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n    import pprint\n\n    hostname = 'www.google.com'\n    port = 443\n\n    context = ssl.create_default_context()\n\n    with socket.create_connection((hostname, port)) as sock:\n        with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n            cert = ssock.getpeercert()\n            pprint.pprint(cert)\n\n            # Get specific fields\n            print(f\"Subject: {dict(x[0] for x in cert['subject'])}\")\n            print(f\"Issuer: {dict(x[0] for x in cert['issuer'])}\")\n            print(f\"Not Before: {cert['notBefore']}\")\n            print(f\"Not After: {cert['notAfter']}\")\n\n            # Get certificate in DER format\n            der_cert = ssock.getpeercert(binary_form=True)\n            print(f\"Certificate size: {len(der_cert)} bytes\")\n\nTLS Version and Security Settings\n---------------------------------\n\nConfigure minimum TLS version and security options.\n\n.. code-block:: python\n\n    import ssl\n\n    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n\n    # Set minimum TLS version (TLS 1.2+)\n    context.minimum_version = ssl.TLSVersion.TLSv1_2\n\n    # Disable older protocols explicitly\n    context.options |= ssl.OP_NO_SSLv2\n    context.options |= ssl.OP_NO_SSLv3\n    context.options |= ssl.OP_NO_TLSv1\n    context.options |= ssl.OP_NO_TLSv1_1\n\n    # Disable compression (CRIME attack mitigation)\n    context.options |= ssl.OP_NO_COMPRESSION\n\n    # Use server's cipher preference\n    context.options |= ssl.OP_CIPHER_SERVER_PREFERENCE\n\n    # Load certificate\n    context.load_cert_chain('cert.pem', 'key.pem')\n\n    # Set strong ciphers only\n    context.set_ciphers('ECDHE+AESGCM:DHE+AESGCM:!aNULL:!MD5:!DSS')\n\n    print(f\"Min version: {context.minimum_version}\")\n    print(f\"Ciphers: {len(context.get_ciphers())}\")\n"
  },
  {
    "path": "docs/notes/network/python-socket.rst",
    "content": ".. meta::\n    :description lang=en: Python socket programming tutorial with examples for DNS resolution, TCP/UDP clients, IP address conversion, network byte order, timeouts, multicast, and SOCKS proxy\n    :keywords: Python, socket, networking, TCP, UDP, DNS, IP address, hostname, getaddrinfo, inet_aton, inet_ntoa, network programming, timeout, multicast, SOCKS proxy, HTTP client\n\n==============\nSocket Basics\n==============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSocket programming is the foundation of network communication in Python and virtually\nall networked applications. A socket is an endpoint for sending and receiving data\nacross a network, providing a bidirectional communication channel between processes\non the same machine or across different machines over the Internet. While Python\nprovides high-level networking interfaces like ``urllib``, ``requests``, and ``asyncio``,\nunderstanding low-level socket operations is essential for building custom protocols,\ndebugging network issues, implementing network tools, and interfacing with system-level\nnetworking APIs.\n\nThis cheat sheet covers the fundamentals of socket programming in Python, including\nhostname and DNS resolution, IP address manipulation, network byte order conversion,\ntimeout handling, multicast communication, and proxy support. Whether you're building\na simple client-server application, implementing a custom protocol, or troubleshooting\nnetwork connectivity issues, these examples provide the building blocks you need.\n\nGet Hostname\n------------\n\nThe ``socket.gethostname()`` function returns the current machine's hostname as\nconfigured in the operating system, while ``socket.gethostbyname()`` performs a\nDNS lookup to resolve a hostname to its IPv4 address. These functions are the most\nbasic building blocks for network programming, allowing your application to identify\nitself and resolve other hosts on the network. Note that ``gethostbyname()`` only\nreturns IPv4 addresses; use ``getaddrinfo()`` for IPv6 support.\n\n.. code-block:: python\n\n    >>> import socket\n    >>> socket.gethostname()\n    'MacBookPro-4380.local'\n    >>> hostname = socket.gethostname()\n    >>> socket.gethostbyname(hostname)\n    '172.20.10.4'\n    >>> socket.gethostbyname('localhost')\n    '127.0.0.1'\n\nGet Address Info (DNS Resolution)\n---------------------------------\n\n``socket.getaddrinfo()`` is the most versatile and recommended function for DNS\nresolution in modern Python code. Unlike ``gethostbyname()``, it supports both IPv4\nand IPv6 addresses, returns multiple results when available, and provides complete\ninformation including address family, socket type, protocol, canonical name, and\nsocket address. This function is essential for writing protocol-agnostic code that\nworks seamlessly with both IPv4 and IPv6 networks.\n\n.. code-block:: python\n\n    import socket\n    import sys\n\n    try:\n        for res in socket.getaddrinfo(sys.argv[1], None,\n                                      proto=socket.IPPROTO_TCP):\n            family = res[0]\n            sockaddr = res[4]\n            print(family, sockaddr)\n    except socket.gaierror:\n        print(\"Invalid\")\n\nOutput:\n\n.. code-block:: console\n\n    $ python gai.py 192.0.2.244\n    AddressFamily.AF_INET ('192.0.2.244', 0)\n    $ python gai.py 2001:db8:f00d::1:d\n    AddressFamily.AF_INET6 ('2001:db8:f00d::1:d', 0, 0, 0)\n    $ python gai.py www.google.com\n    AddressFamily.AF_INET6 ('2607:f8b0:4006:818::2004', 0, 0, 0)\n    AddressFamily.AF_INET ('172.217.10.132', 0)\n\nIt handles unusual cases, valid and invalid:\n\n.. code-block:: console\n\n    $ python gai.py 10.0.0.256  # octet overflow\n    Invalid\n    $ python gai.py not-exist.example.com  # unresolvable\n    Invalid\n    $ python gai.py fe80::1%eth0  # scoped\n    AddressFamily.AF_INET6 ('fe80::1%eth0', 0, 0, 2)\n    $ python gai.py ::ffff:192.0.2.128  # IPv4-Mapped\n    AddressFamily.AF_INET6 ('::ffff:192.0.2.128', 0, 0, 0)\n    $ python gai.py 0xc000027b  # IPv4 in hex\n    AddressFamily.AF_INET ('192.0.2.123', 0)\n\nAdvanced DNS Queries\n--------------------\n\nWhile ``socket.getaddrinfo()`` handles basic hostname resolution, many applications\nrequire more advanced DNS operations like querying specific record types. MX records\nidentify mail servers for a domain, TXT records store SPF and DKIM data for email\nauthentication, NS records list authoritative name servers, and SRV records enable\nservice discovery. The ``dnspython`` library provides a comprehensive DNS toolkit\nthat supports all record types, custom nameservers, DNSSEC validation, and zone\ntransfers. This is essential for building email validation systems, service discovery\nmechanisms, and DNS monitoring tools.\n\n.. code-block:: python\n\n    # pip install dnspython\n    import dns.resolver\n\n    # Query MX records (mail servers)\n    answers = dns.resolver.resolve('google.com', 'MX')\n    for rdata in answers:\n        print(f\"MX: {rdata.exchange} (priority: {rdata.preference})\")\n\n    # Query TXT records (SPF, DKIM, etc.)\n    answers = dns.resolver.resolve('google.com', 'TXT')\n    for rdata in answers:\n        print(f\"TXT: {rdata}\")\n\n    # Query NS records (name servers)\n    answers = dns.resolver.resolve('google.com', 'NS')\n    for rdata in answers:\n        print(f\"NS: {rdata}\")\n\n    # Query A records with custom nameserver\n    resolver = dns.resolver.Resolver()\n    resolver.nameservers = ['8.8.8.8']  # Use Google DNS\n    answers = resolver.resolve('example.com', 'A')\n    for rdata in answers:\n        print(f\"A: {rdata}\")\n\nReverse DNS Lookup\n------------------\n\nReverse DNS (rDNS) lookup converts an IP address back to its associated hostname by\nquerying PTR records in the in-addr.arpa (IPv4) or ip6.arpa (IPv6) domains. This is\ncommonly used for logging to make IP addresses human-readable, security analysis to\nverify that a connecting client's IP matches its claimed hostname, spam filtering\nto check if mail servers have valid reverse DNS, and network troubleshooting to\nidentify devices on a network.\n\n.. code-block:: python\n\n    >>> import socket\n    >>> # Reverse lookup returns (hostname, aliases, addresses)\n    >>> socket.gethostbyaddr('8.8.8.8')\n    ('dns.google', [], ['8.8.8.8'])\n    >>> socket.gethostbyaddr('140.82.112.4')\n    ('github.com', [], ['140.82.112.4'])\n\n    >>> # Using getfqdn for fully qualified domain name\n    >>> socket.getfqdn('8.8.8.8')\n    'dns.google'\n\nNetwork Byte Order Conversion\n-----------------------------\n\nNetwork protocols universally use big-endian byte order (most significant byte first),\nalso called \"network byte order,\" while most modern CPUs (x86, ARM in little-endian\nmode) use little-endian (least significant byte first). When sending multi-byte\nintegers over the network, you must convert from host byte order to network byte\norder, and vice versa when receiving. The ``htons``/``htonl`` functions convert host\nto network order for short (16-bit) and long (32-bit) integers, while ``ntohs``/``ntohl``\nconvert network to host order. Failing to perform these conversions causes subtle\nbugs where values appear corrupted on machines with different endianness.\n\n.. code-block:: python\n\n    # little-endian machine\n    >>> import socket\n    >>> a = 1  # host endian\n    >>> socket.htons(a)  # host to network short (16-bit)\n    256\n    >>> socket.htonl(a)  # host to network long (32-bit)\n    16777216\n    >>> socket.ntohs(256)  # network to host short\n    1\n    >>> socket.ntohl(16777216)  # network to host long\n    1\n\nIP Address String/Binary Conversion\n-----------------------------------\n\nIP addresses are typically displayed as human-readable strings (dotted-quad for IPv4\nlike \"192.168.1.1\", or colon-hex for IPv6 like \"2001:db8::1\"), but network protocols\ntransmit them as binary data (4 bytes for IPv4, 16 bytes for IPv6). The ``inet_aton``\nand ``inet_ntoa`` functions convert between string and binary formats for IPv4 only.\nFor code that needs to support both IPv4 and IPv6, use ``inet_pton`` (presentation\nto network) and ``inet_ntop`` (network to presentation), which take an address family\nparameter to specify the IP version.\n\n.. code-block:: python\n\n    >>> import socket\n    >>> # IPv4: string to binary\n    >>> addr = socket.inet_aton('127.0.0.1')\n    >>> addr\n    b'\\x7f\\x00\\x00\\x01'\n    >>> # IPv4: binary to string\n    >>> socket.inet_ntoa(addr)\n    '127.0.0.1'\n\n    >>> # IPv4/IPv6: use inet_pton/inet_ntop\n    >>> socket.inet_pton(socket.AF_INET, '192.168.1.1')\n    b'\\xc0\\xa8\\x01\\x01'\n    >>> socket.inet_pton(socket.AF_INET6, '::1')\n    b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01'\n    >>> socket.inet_ntop(socket.AF_INET6, b'\\x00' * 15 + b'\\x01')\n    '::1'\n\nMAC Address Conversion\n----------------------\n\nMAC (Media Access Control) addresses are 48-bit hardware identifiers assigned to\nnetwork interface cards, typically displayed as six colon-separated hexadecimal\npairs like \"00:11:22:33:44:55\". When working with raw Ethernet frames or ARP packets,\nyou need to convert between this human-readable format and the 6-byte binary format\nused in network protocols. The ``binascii`` module provides ``hexlify`` and ``unhexlify``\nfunctions for this conversion.\n\n.. code-block:: python\n\n    >>> import binascii\n    >>> mac = '00:11:32:3c:c3:0b'\n    >>> byte = binascii.unhexlify(mac.replace(':', ''))\n    >>> byte\n    b'\\x00\\x112<\\xc3\\x0b'\n    >>> binascii.hexlify(byte)\n    b'0011323cc30b'\n    >>> # Format back to colon-separated\n    >>> ':'.join(f'{b:02x}' for b in byte)\n    '00:11:32:3c:c3:0b'\n\nCheck Port Availability\n-----------------------\n\nBefore starting a server, you often need to verify that the desired port is available\nfor binding. Similarly, network monitoring tools need to check if remote services are\nreachable. The ``is_port_open`` function attempts a TCP connection to test remote\nservice availability, while ``is_port_available`` tries to bind locally to check if\na port is free. These checks are essential for service health monitoring, port\nscanning, and avoiding \"Address already in use\" errors when starting servers.\n\n.. code-block:: python\n\n    import socket\n\n    def is_port_open(host, port, timeout=3):\n        \"\"\"Check if a port is open on a remote host.\"\"\"\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.settimeout(timeout)\n        try:\n            sock.connect((host, port))\n            return True\n        except (socket.timeout, ConnectionRefusedError, OSError):\n            return False\n        finally:\n            sock.close()\n\n    def is_port_available(port):\n        \"\"\"Check if a local port is available for binding.\"\"\"\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        try:\n            sock.bind(('', port))\n            return True\n        except OSError:\n            return False\n        finally:\n            sock.close()\n\n    # Usage\n    print(is_port_open('google.com', 443))  # True\n    print(is_port_available(8080))  # True if not in use\n\nGet Network Interfaces\n----------------------\n\nMulti-homed servers (machines with multiple network interfaces) need to discover\ntheir available IP addresses to bind to specific interfaces or advertise their\naddresses to clients. The basic approach uses ``getaddrinfo`` on the hostname, but\nfor detailed interface information including interface names, netmasks, and broadcast\naddresses, the ``netifaces`` library provides a cross-platform solution. This is\nuseful for network configuration tools, service discovery, and building applications\nthat need to select specific network interfaces.\n\n.. code-block:: python\n\n    import socket\n\n    def get_local_ips():\n        \"\"\"Get all local IP addresses.\"\"\"\n        ips = []\n        hostname = socket.gethostname()\n        try:\n            # Get all addresses for hostname\n            for info in socket.getaddrinfo(hostname, None):\n                ip = info[4][0]\n                if ip not in ips:\n                    ips.append(ip)\n        except socket.gaierror:\n            pass\n        return ips\n\n    # For more detailed interface info, use netifaces\n    # pip install netifaces\n    import netifaces\n\n    for iface in netifaces.interfaces():\n        addrs = netifaces.ifaddresses(iface)\n        if netifaces.AF_INET in addrs:\n            for addr in addrs[netifaces.AF_INET]:\n                print(f\"{iface}: {addr['addr']}\")\n\nSocket Options\n--------------\n\nSocket options control low-level socket behavior and are essential for building\nrobust network applications. ``SO_REUSEADDR`` allows immediate restart of servers\nwithout waiting for TIME_WAIT to expire. ``SO_REUSEPORT`` enables multiple processes\nto bind to the same port for load balancing. Buffer size options (``SO_SNDBUF``,\n``SO_RCVBUF``) tune throughput for high-bandwidth applications. ``SO_KEEPALIVE``\ndetects dead connections by sending periodic probes. Understanding these options\nhelps you optimize performance and handle edge cases in production systems.\n\n.. code-block:: python\n\n    import socket\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n\n    # Reuse address (avoid \"Address already in use\" error)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n\n    # Reuse port (multiple processes can bind to same port)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)\n\n    # Set send/receive buffer sizes\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 65536)\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 65536)\n\n    # Enable TCP keepalive\n    sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n\n    # Set timeout (seconds)\n    sock.settimeout(10.0)\n\n    # Non-blocking mode\n    sock.setblocking(False)\n\n    # Get current option value\n    print(sock.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))\n\nTroubleshooting: Connection Refused\n-----------------------------------\n\n\"Connection refused\" is one of the most common network errors, but its cause isn't\nalways obvious. It can mean the target port has no listening service, a firewall is\nactively rejecting connections, or the service crashed. Other errors like \"Connection\ntimed out\" suggest the host is unreachable or a firewall is silently dropping packets,\nwhile \"Network unreachable\" indicates routing problems. This diagnostic function\ncategorizes different error types to help identify the root cause, which is essential\nfor debugging network connectivity issues in development and production environments.\n\n.. code-block:: python\n\n    import socket\n    import errno\n\n    def diagnose_connection(host, port):\n        \"\"\"Diagnose connection issues.\"\"\"\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.settimeout(5)\n        try:\n            sock.connect((host, port))\n            print(f\"✓ Connected to {host}:{port}\")\n        except socket.timeout:\n            print(f\"✗ Timeout - host may be unreachable or firewalled\")\n        except ConnectionRefusedError:\n            print(f\"✗ Connection refused - no service on {port}\")\n        except socket.gaierror as e:\n            print(f\"✗ DNS error - cannot resolve {host}: {e}\")\n        except OSError as e:\n            if e.errno == errno.ENETUNREACH:\n                print(f\"✗ Network unreachable\")\n            elif e.errno == errno.EHOSTUNREACH:\n                print(f\"✗ Host unreachable\")\n            else:\n                print(f\"✗ OS error: {e}\")\n        finally:\n            sock.close()\n\n    diagnose_connection('localhost', 8080)\n\n\nTimeout Handling\n----------------\n\nNetwork operations can block indefinitely if a remote host becomes unresponsive,\na network path fails, or packets are lost. Without proper timeout handling, your\napplication may hang forever waiting for data that will never arrive. Python sockets\nsupport timeouts at multiple levels: a global timeout via ``settimeout()`` that applies\nto all operations, or per-operation timeouts using ``select()`` for more precise control.\nAlways set appropriate timeouts based on your application's requirements—too short\ncauses false failures, too long delays error detection.\n\n.. code-block:: python\n\n    import socket\n    import errno\n\n    def connect_with_timeout(host, port, timeout=5):\n        \"\"\"Connect with timeout and proper error handling.\"\"\"\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.settimeout(timeout)\n        try:\n            sock.connect((host, port))\n            return sock\n        except socket.timeout:\n            print(f\"Connection to {host}:{port} timed out\")\n            sock.close()\n            return None\n        except OSError as e:\n            print(f\"Connection failed: {e}\")\n            sock.close()\n            return None\n\n    def recv_with_timeout(sock, bufsize=4096, timeout=10):\n        \"\"\"Receive data with timeout.\"\"\"\n        sock.settimeout(timeout)\n        try:\n            return sock.recv(bufsize)\n        except socket.timeout:\n            return None  # Timeout, no data\n\n    # Per-operation timeout using select\n    import select\n\n    def recv_timeout(sock, bufsize, timeout):\n        \"\"\"Receive with timeout using select (more precise).\"\"\"\n        ready, _, _ = select.select([sock], [], [], timeout)\n        if ready:\n            return sock.recv(bufsize)\n        raise socket.timeout(\"recv timed out\")\n\nGraceful Shutdown\n-----------------\n\nSimply calling ``close()`` on a socket may lose data still in transit. The TCP\nprotocol requires a proper four-way handshake (FIN-ACK sequence) to ensure both\nsides have finished sending. The ``shutdown()`` method provides fine-grained control:\n``SHUT_WR`` sends a FIN packet signaling \"I'm done sending\" while still allowing\nreads, ``SHUT_RD`` stops receiving, and ``SHUT_RDWR`` does both. For clean termination,\ncall ``shutdown(SHUT_WR)`` first, drain any remaining incoming data, then ``close()``.\nThis pattern is especially important for protocols where the server waits for client\nEOF before sending its final response.\n\n.. code-block:: python\n\n    import socket\n\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    sock.connect(('example.com', 80))\n    sock.send(b'GET / HTTP/1.0\\r\\nHost: example.com\\r\\n\\r\\n')\n\n    # Shutdown write side - signals EOF to server\n    sock.shutdown(socket.SHUT_WR)\n\n    # Read remaining data\n    response = b''\n    while True:\n        data = sock.recv(4096)\n        if not data:\n            break\n        response += data\n\n    # Now close the socket\n    sock.close()\n\n    # shutdown options:\n    # SHUT_RD   - no more reads\n    # SHUT_WR   - no more writes (sends FIN)\n    # SHUT_RDWR - no more reads or writes\n\nMulticast UDP\n-------------\n\nMulticast is a one-to-many communication model where a single packet is delivered\nto multiple receivers simultaneously. Unlike broadcast (which floods the entire\nnetwork) or unicast (one sender, one receiver), multicast uses special IP addresses\n(224.0.0.0 to 239.255.255.255) and IGMP protocol to efficiently route packets only\nto interested receivers. Receivers must explicitly join a multicast group to receive\ntraffic. The TTL (Time To Live) controls how far packets travel—TTL=1 stays on the\nlocal subnet, higher values cross routers. Multicast is ideal for streaming media,\nreal-time data feeds, and service discovery where the same data goes to many clients.\n\n.. code-block:: python\n\n    import socket\n    import struct\n\n    MCAST_GROUP = '224.1.1.1'\n    MCAST_PORT = 5007\n\n    # Sender\n    def multicast_sender():\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n        sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)\n        sock.sendto(b'Hello Multicast!', (MCAST_GROUP, MCAST_PORT))\n        sock.close()\n\n    # Receiver\n    def multicast_receiver():\n        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        sock.bind(('', MCAST_PORT))\n\n        # Join multicast group\n        mreq = struct.pack('4sl', socket.inet_aton(MCAST_GROUP), socket.INADDR_ANY)\n        sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)\n\n        data, addr = sock.recvfrom(1024)\n        print(f\"Received: {data} from {addr}\")\n        sock.close()\n\nHTTP Client with Sockets\n------------------------\n\nWhile high-level libraries like ``urllib`` and ``requests`` handle HTTP elegantly,\nunderstanding raw HTTP over sockets is invaluable for debugging, implementing custom\nprotocols, or working in constrained environments. HTTP/1.1 is a text-based protocol:\nyou send a request line (``GET /path HTTP/1.1``), headers (key-value pairs), a blank\nline, and optionally a body. The server responds similarly. Key headers include\n``Host`` (required in HTTP/1.1), ``Connection: close`` (to signal single request),\nand ``Content-Length`` for bodies. This low-level approach reveals exactly what's\nhappening on the wire, making it easier to diagnose issues like malformed headers,\nencoding problems, or TLS handshake failures.\n\n.. code-block:: python\n\n    import socket\n    import ssl\n\n    def http_get(host, path='/', port=80, use_ssl=False):\n        \"\"\"Simple HTTP GET using raw sockets.\"\"\"\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n\n        if use_ssl:\n            context = ssl.create_default_context()\n            sock = context.wrap_socket(sock, server_hostname=host)\n            port = 443\n\n        sock.connect((host, port))\n\n        request = f\"GET {path} HTTP/1.1\\r\\nHost: {host}\\r\\nConnection: close\\r\\n\\r\\n\"\n        sock.send(request.encode())\n\n        response = b''\n        while True:\n            data = sock.recv(4096)\n            if not data:\n                break\n            response += data\n\n        sock.close()\n\n        # Split headers and body\n        header_end = response.find(b'\\r\\n\\r\\n')\n        headers = response[:header_end].decode()\n        body = response[header_end + 4:]\n\n        return headers, body\n\n    # Usage\n    headers, body = http_get('example.com', '/', use_ssl=True)\n    print(headers)\n\nSOCKS Proxy Support\n-------------------\n\nSOCKS (Socket Secure) is a protocol that routes network traffic through a proxy\nserver, providing anonymity and the ability to bypass firewalls or geographic\nrestrictions. Unlike HTTP proxies that only handle HTTP traffic, SOCKS operates\nat a lower level and can proxy any TCP (and with SOCKS5, UDP) traffic. SOCKS5\nadds authentication and IPv6 support. Common use cases include routing traffic\nthrough Tor (which uses SOCKS5 on port 9050), accessing internal networks via\nSSH tunnels (``ssh -D``), or corporate proxy requirements. The ``PySocks`` library\nmakes it easy to route Python socket connections through SOCKS proxies, either\nglobally (patching all sockets) or per-connection.\n\n.. code-block:: python\n\n    # pip install PySocks\n    import socks\n    import socket\n\n    # Method 1: Patch all sockets globally\n    socks.set_default_proxy(socks.SOCKS5, \"localhost\", 9050)\n    socket.socket = socks.socksocket\n\n    # Now all socket connections go through the proxy\n    s = socket.socket()\n    s.connect((\"example.com\", 80))\n\n    # Method 2: Create proxy socket directly\n    s = socks.socksocket()\n    s.set_proxy(socks.SOCKS5, \"localhost\", 9050)\n    s.connect((\"example.com\", 80))\n\n    # Method 3: With authentication\n    s = socks.socksocket()\n    s.set_proxy(socks.SOCKS5, \"proxy.example.com\", 1080,\n                username=\"user\", password=\"pass\")\n"
  },
  {
    "path": "docs/notes/network/python-ssh.rst",
    "content": ".. meta::\n    :description lang=en: Comprehensive SSH cheat sheet for Python developers covering Paramiko library, SSH tunneling (local, reverse, dynamic), port forwarding, SFTP file transfers, jump hosts, and key management with practical examples\n    :keywords: Python, Python3, SSH, Paramiko, SFTP, SSH Tunnel, Port Forwarding, Reverse Tunnel, Jump Host, ProxyJump, Bastion Host, SOCKS Proxy, SSH Agent, Key Authentication, Remote Execution\n\n======================\nSSH and Secure Tunnels\n======================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nSSH (Secure Shell) is the standard protocol for secure remote access, providing\nencrypted communication between machines for command execution, file transfer,\nand network tunneling. Originally developed as a secure replacement for telnet\nand rsh, SSH has become essential infrastructure for system administration,\ndeployment automation, and secure network access. Python's ``paramiko`` library\nprovides a complete implementation of SSHv2 protocol, enabling programmatic SSH\nconnections, SFTP file transfers, and sophisticated port forwarding scenarios.\nThis cheat sheet covers the full spectrum of SSH operations—from basic password\nand key authentication to advanced tunneling techniques like reverse tunnels for\nNAT traversal, jump hosts for accessing isolated networks, and dynamic SOCKS\nproxies for routing arbitrary traffic through secure channels.\n\nBasic SSH Connection\n--------------------\n\nThe foundation of SSH is establishing a secure, authenticated connection to a\nremote host. The ``SSHClient`` class in Paramiko manages the entire connection\nlifecycle including TCP connection, cryptographic handshake, host key verification,\nuser authentication, and channel multiplexing. Once connected, you can execute\ncommands, open interactive shells, or establish SFTP sessions. The context manager\npattern (``with`` statement) ensures connections are properly closed even if\nexceptions occur, preventing resource leaks in long-running applications.\n\n.. code-block:: python\n\n    from paramiko.client import SSHClient\n\n    # Basic password authentication\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"example.com\", username=\"user\", password=\"secret\")\n        stdin, stdout, stderr = ssh.exec_command(\"uname -a\")\n        print(stdout.read().decode())\n\n.. code-block:: python\n\n    # Connect on non-standard port\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"example.com\", port=2222, username=\"user\", password=\"secret\")\n        stdin, stdout, stderr = ssh.exec_command(\"hostname\")\n        print(stdout.read().decode())\n\nHost Key Verification\n---------------------\n\nSSH's security model relies on verifying the server's identity before sending\ncredentials. Each SSH server has a unique host key pair, and clients store known\nhost public keys in ``~/.ssh/known_hosts``. On first connection, SSH warns about\nunknown hosts—this is the \"fingerprint\" prompt you see. Blindly accepting unknown\nkeys defeats this protection and enables man-in-the-middle attacks where an\nattacker intercepts your connection. For automation, ``AutoAddPolicy`` is\nconvenient but should only be used in trusted networks or with additional\nverification. In production, pre-populate known_hosts or use certificate-based\nhost authentication.\n\n.. code-block:: python\n\n    import paramiko\n    from paramiko.client import SSHClient\n\n    # Auto-add unknown host keys (use cautiously)\n    # Equivalent to: ssh -o StrictHostKeyChecking=no\n    with SSHClient() as ssh:\n        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())\n        ssh.connect(\"example.com\", username=\"user\", password=\"secret\")\n        stdin, stdout, stderr = ssh.exec_command(\"whoami\")\n        print(stdout.read().decode())\n\n    # Reject unknown hosts (default, most secure)\n    with SSHClient() as ssh:\n        ssh.set_missing_host_key_policy(paramiko.RejectPolicy())\n        ssh.load_system_host_keys()  # Load ~/.ssh/known_hosts\n        ssh.connect(\"example.com\", username=\"user\", password=\"secret\")\n\nKey-Based Authentication\n------------------------\n\nSSH key pairs provide significantly stronger security than passwords while\nenabling passwordless automation. A key pair consists of a private key (kept\nsecret on your machine) and a public key (copied to servers you want to access).\nAuthentication works by proving you possess the private key without transmitting\nit. Modern best practice recommends Ed25519 keys for their security and performance,\nthough RSA (4096-bit) remains widely compatible. Protect private keys with a\npassphrase for defense-in-depth—if the key file is stolen, the passphrase provides\nan additional barrier. Use ``ssh-agent`` to cache decrypted keys in memory,\navoiding repeated passphrase entry.\n\n.. code-block:: python\n\n    from paramiko.client import SSHClient\n\n    # Using private key file\n    # ssh-keygen -t ed25519 -f mykey\n    # ssh-copy-id -i mykey.pub user@example.com\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"example.com\", username=\"user\", key_filename=\"mykey\")\n        stdin, stdout, stderr = ssh.exec_command(\"id\")\n        print(stdout.read().decode())\n\n.. code-block:: python\n\n    # Key with passphrase\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\n            \"example.com\",\n            username=\"user\",\n            key_filename=\"mykey\",\n            passphrase=\"my-key-passphrase\"\n        )\n\n.. code-block:: python\n\n    # Using RSAKey object directly\n    from paramiko import RSAKey\n\n    pkey = RSAKey.from_private_key_file(\"mykey\", password=\"passphrase\")\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"example.com\", username=\"user\", pkey=pkey)\n\nSFTP File Transfer\n------------------\n\nSFTP (SSH File Transfer Protocol) runs over an SSH connection, providing secure,\nencrypted file operations without requiring a separate service or port. Unlike\nFTP which sends credentials in plaintext and requires complex firewall rules for\npassive mode, SFTP tunnels everything through the existing SSH connection on port\n22. Paramiko's SFTP client supports the full range of file operations: uploading,\ndownloading, directory listing, file metadata, permissions, and remote file\nmanipulation. For large transfers, SFTP handles resume and provides progress\ncallbacks. It's the standard choice for automated file transfers in deployment\nscripts, backup systems, and data pipelines where security is required.\n\n.. code-block:: python\n\n    from paramiko.client import SSHClient\n\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"example.com\", username=\"user\", key_filename=\"mykey\")\n        sftp = ssh.open_sftp()\n\n        # Upload file\n        sftp.put(\"local_file.txt\", \"/remote/path/file.txt\")\n\n        # Download file\n        sftp.get(\"/remote/path/file.txt\", \"downloaded.txt\")\n\n        # List directory\n        for entry in sftp.listdir(\"/remote/path\"):\n            print(entry)\n\n        # File operations\n        sftp.mkdir(\"/remote/newdir\")\n        sftp.rename(\"/remote/old.txt\", \"/remote/new.txt\")\n        sftp.remove(\"/remote/unwanted.txt\")\n\n        # Get file stats\n        stat = sftp.stat(\"/remote/file.txt\")\n        print(f\"Size: {stat.st_size}, Modified: {stat.st_mtime}\")\n\n        sftp.close()\n\nSSH Tunneling Overview\n----------------------\n\nSSH tunneling (port forwarding) is one of SSH's most powerful features, allowing\nyou to securely route network traffic through an encrypted SSH connection. This\nenables accessing services behind firewalls, encrypting otherwise insecure\nprotocols, and bypassing network restrictions. There are three types: local\nforwarding brings a remote service to your machine, remote (reverse) forwarding\nexposes your local service to the remote network, and dynamic forwarding creates\na SOCKS proxy for routing arbitrary traffic. Understanding these patterns is\nessential for secure access to databases, internal web applications, and services\nin private networks. The diagrams below illustrate the traffic flow for each type.\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────┐\n    │                    SSH TUNNEL TYPES                             │\n    ├─────────────────────────────────────────────────────────────────┤\n    │                                                                 │\n    │  LOCAL FORWARDING (-L)                                          │\n    │  Access remote service through local port                       │\n    │                                                                 │\n    │    [You] ──► localhost:8080 ══SSH══► [Server] ──► db:5432       │\n    │                                                                 │\n    │    ssh -L 8080:database.internal:5432 user@server               │\n    │    Then connect to localhost:8080 to reach database             │\n    │                                                                 │\n    ├─────────────────────────────────────────────────────────────────┤\n    │                                                                 │\n    │  REMOTE/REVERSE FORWARDING (-R)                                 │\n    │  Expose local service to remote server                          │\n    │                                                                 │\n    │    [You:3000] ◄── [Server]:9000 ◄══SSH══◄ [You initiate]        │\n    │                                                                 │\n    │    ssh -R 9000:localhost:3000 user@server                       │\n    │    Server's port 9000 forwards to your localhost:3000           │\n    │                                                                 │\n    ├─────────────────────────────────────────────────────────────────┤\n    │                                                                 │\n    │  DYNAMIC FORWARDING (-D) - SOCKS Proxy                          │\n    │  Route any traffic through SSH server                           │\n    │                                                                 │\n    │    [You] ──► localhost:1080 ══SSH══► [Server] ──► anywhere      │\n    │                                                                 │\n    │    ssh -D 1080 user@server                                      │\n    │    Configure browser/app to use SOCKS5 proxy localhost:1080     │\n    │                                                                 │\n    └─────────────────────────────────────────────────────────────────┘\n\nLocal Port Forwarding\n---------------------\n\nLocal forwarding (``-L``) is the most common tunnel type, binding a port on your\nlocal machine that forwards traffic through the SSH server to a destination host.\nThis is invaluable for accessing services in private networks—databases, internal\nweb applications, admin interfaces—that aren't exposed to the internet. The SSH\nserver acts as a relay: your local application connects to ``localhost:port``,\nSSH encrypts and forwards the traffic to the server, which then connects to the\nfinal destination. The destination doesn't need to be the SSH server itself; it\ncan be any host reachable from the server, making this perfect for bastion/jump\nhost scenarios where you SSH to a gateway machine to reach internal resources.\n\n::\n\n    Scenario: Access internal database through bastion host\n\n    ┌──────────┐      ┌──────────────┐      ┌────────────────┐\n    │  Your    │ SSH  │   Bastion    │      │   Database     │\n    │  Machine │─────►│   Server     │─────►│   (internal)   │\n    │          │      │              │      │   db:5432      │\n    └──────────┘      └──────────────┘      └────────────────┘\n         │\n         │ Connect to localhost:5432\n         │ Traffic tunneled to db:5432\n         ▼\n\n    Command: ssh -L 5432:db.internal:5432 user@bastion\n    Then:    psql -h localhost -p 5432 mydb\n\n.. code-block:: python\n\n    # Local port forwarding with Paramiko\n    import paramiko\n    from paramiko.client import SSHClient\n    import socket\n    import select\n    import threading\n\n    def forward_tunnel(local_port, remote_host, remote_port, ssh_client):\n        \"\"\"Forward local_port to remote_host:remote_port via SSH.\"\"\"\n        transport = ssh_client.get_transport()\n\n        # Create local listening socket\n        server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        server.bind(('127.0.0.1', local_port))\n        server.listen(5)\n        print(f\"Forwarding localhost:{local_port} -> {remote_host}:{remote_port}\")\n\n        while True:\n            client, addr = server.accept()\n            # Open channel to remote destination\n            channel = transport.open_channel(\n                'direct-tcpip',\n                (remote_host, remote_port),\n                client.getpeername()\n            )\n            if channel is None:\n                client.close()\n                continue\n\n            # Bidirectional forwarding in thread\n            threading.Thread(\n                target=_forward_data, args=(client, channel), daemon=True\n            ).start()\n\n    def _forward_data(sock, channel):\n        \"\"\"Forward data between socket and SSH channel.\"\"\"\n        while True:\n            r, w, x = select.select([sock, channel], [], [])\n            if sock in r:\n                data = sock.recv(4096)\n                if not data:\n                    break\n                channel.send(data)\n            if channel in r:\n                data = channel.recv(4096)\n                if not data:\n                    break\n                sock.send(data)\n        sock.close()\n        channel.close()\n\n    # Usage\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"bastion.example.com\", username=\"user\", key_filename=\"mykey\")\n        # Forward localhost:5432 to internal-db:5432\n        forward_tunnel(5432, \"internal-db.local\", 5432, ssh)\n\nReverse Port Forwarding\n-----------------------\n\nReverse forwarding (``-R``) solves the opposite problem: exposing a service on\nyour local machine to the remote server's network. This is essential when you're\nbehind NAT, a corporate firewall, or any network that blocks incoming connections.\nYou initiate an outbound SSH connection (which firewalls typically allow), and\nthe SSH server opens a listening port that tunnels back to your machine. Common\nuse cases include sharing a local development server with remote colleagues,\nproviding temporary access to a local service for debugging, or creating a\n\"callback\" channel when direct inbound connections are impossible. Note that by\ndefault, the server only binds to ``127.0.0.1``; to allow external access, the\nserver's ``sshd_config`` needs ``GatewayPorts yes``.\n\n::\n\n    Scenario: Expose local dev server to public server\n\n    ┌──────────────┐                    ┌──────────────┐\n    │  Your Machine│   SSH Connection   │ Public Server│\n    │  (behind NAT)│═══════════════════►│              │\n    │              │   You initiate     │              │\n    │  localhost   │                    │  0.0.0.0     │\n    │    :3000     │◄───────────────────│    :9000     │\n    │  (your app)  │   Tunnel back      │  (exposed)   │\n    └──────────────┘                    └──────────────┘\n\n    Command: ssh -R 9000:localhost:3000 user@public-server\n    Result:  Anyone connecting to public-server:9000\n             reaches your localhost:3000\n\n    Note: Server needs \"GatewayPorts yes\" in sshd_config\n          to allow binding to 0.0.0.0 (not just 127.0.0.1)\n\n.. code-block:: python\n\n    # Reverse tunnel with Paramiko\n    import paramiko\n    from paramiko.client import SSHClient\n    import socket\n    import select\n    import threading\n\n    def reverse_tunnel(server_port, local_host, local_port, ssh_client):\n        \"\"\"Expose local_host:local_port on SSH server's server_port.\"\"\"\n        transport = ssh_client.get_transport()\n\n        # Request remote port forwarding\n        transport.request_port_forward('', server_port)\n        print(f\"Reverse tunnel: server:{server_port} -> {local_host}:{local_port}\")\n\n        while True:\n            # Accept forwarded connection from server\n            channel = transport.accept(1000)\n            if channel is None:\n                continue\n\n            # Connect to local service\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            try:\n                sock.connect((local_host, local_port))\n            except Exception as e:\n                print(f\"Local connection failed: {e}\")\n                channel.close()\n                continue\n\n            # Bidirectional forwarding\n            threading.Thread(\n                target=_forward_data, args=(sock, channel), daemon=True\n            ).start()\n\n    # Usage: Expose local web server on remote port 9000\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"public-server.com\", username=\"user\", key_filename=\"mykey\")\n        reverse_tunnel(9000, \"localhost\", 3000, ssh)\n\nDynamic Port Forwarding (SOCKS Proxy)\n-------------------------------------\n\nDynamic forwarding (``-D``) creates a local SOCKS proxy server that routes traffic\nthrough the SSH connection. Unlike local forwarding where you specify a fixed\ndestination, dynamic forwarding lets applications connect to any host reachable\nfrom the SSH server—the destination is determined per-connection by the SOCKS\nprotocol. This is incredibly versatile: configure your browser to use the SOCKS\nproxy and all web traffic flows through the SSH server, effectively browsing from\nthat server's network location. Use cases include accessing geo-restricted content,\nbrowsing internal websites from outside the office, or encrypting traffic on\nuntrusted networks (coffee shop WiFi). SOCKS5 supports both TCP and UDP, plus\nauthentication, making it more capable than HTTP proxies.\n\n::\n\n    ┌──────────┐      ┌──────────────┐      ┌─────────────┐\n    │  Your    │ SSH  │   SSH        │      │  Any        │\n    │  Machine │═════►│   Server     │─────►│  Destination│\n    │          │      │              │      │             │\n    └──────────┘      └──────────────┘      └─────────────┘\n         │\n         │ SOCKS5 proxy on localhost:1080\n         │ Browser/apps route traffic through it\n         ▼\n\n    Command: ssh -D 1080 user@server\n    Config:  Set browser proxy to SOCKS5 localhost:1080\n             All browsing now goes through SSH server\n\n.. code-block:: bash\n\n    # Command line usage\n    ssh -D 1080 -N -f user@server\n\n    # -D 1080: Dynamic forwarding on port 1080\n    # -N: No remote command (just forwarding)\n    # -f: Background after authentication\n\n    # Use with curl\n    curl --socks5 localhost:1080 http://internal-site.local\n\nJump Hosts (ProxyJump)\n----------------------\n\nJump hosts (also called bastion hosts or gateway servers) are hardened machines\nthat provide the only entry point into a private network. Instead of exposing\ninternal servers directly to the internet, organizations route all SSH access\nthrough a jump host that can be heavily monitored and secured. SSH's ``ProxyJump``\n(``-J``) option makes this seamless—you specify the jump host, and SSH automatically\nchains the connections, authenticating to each hop. The connection to the final\ndestination is end-to-end encrypted; the jump host only sees encrypted traffic\npassing through. You can chain multiple jump hosts for deeply segmented networks.\nThis pattern is fundamental to secure infrastructure access in cloud environments\nwhere production servers should never have public IP addresses.\n\n::\n\n    ┌──────────┐      ┌──────────────┐      ┌──────────────┐\n    │  Your    │ SSH  │   Bastion    │ SSH  │   Internal   │\n    │  Machine │═════►│   (jump)     │═════►│   Server     │\n    │          │      │              │      │              │\n    └──────────┘      └──────────────┘      └──────────────┘\n\n    Command: ssh -J user@bastion user@internal-server\n\n    Or in ~/.ssh/config:\n    Host internal-server\n        HostName 10.0.0.50\n        User admin\n        ProxyJump user@bastion.example.com\n\n.. code-block:: python\n\n    # Jump host with Paramiko\n    from paramiko.client import SSHClient\n\n    # Connect to bastion first\n    bastion = SSHClient()\n    bastion.load_system_host_keys()\n    bastion.connect(\"bastion.example.com\", username=\"user\", key_filename=\"mykey\")\n\n    # Get transport and open channel to internal host\n    bastion_transport = bastion.get_transport()\n    dest_addr = (\"internal-server.local\", 22)\n    local_addr = (\"127.0.0.1\", 0)\n    channel = bastion_transport.open_channel(\"direct-tcpip\", dest_addr, local_addr)\n\n    # Connect to internal server through the channel\n    internal = SSHClient()\n    internal.load_system_host_keys()\n    internal.connect(\n        \"internal-server.local\",\n        username=\"admin\",\n        key_filename=\"mykey\",\n        sock=channel  # Use bastion channel as socket\n    )\n\n    # Execute command on internal server\n    stdin, stdout, stderr = internal.exec_command(\"hostname\")\n    print(stdout.read().decode())\n\n    internal.close()\n    bastion.close()\n\nSSH Config File\n---------------\n\nThe SSH config file (``~/.ssh/config``) eliminates repetitive command-line options\nby defining per-host settings. Instead of typing ``ssh -i ~/.ssh/mykey -p 2222\nuser@long.hostname.example.com``, you define a host alias and simply type\n``ssh myserver``. The config file supports wildcards, allowing you to set defaults\nfor groups of hosts (all ``*.internal`` hosts use a specific jump server). You can\nalso define automatic port forwarding, so connecting to a host automatically sets\nup your database tunnels. For teams, a shared config file ensures everyone uses\nconsistent, secure settings. The config is processed top-to-bottom with first\nmatch winning, so put specific hosts before wildcards.\n\n.. code-block:: text\n\n    # ~/.ssh/config\n\n    # Default settings for all hosts\n    Host *\n        ServerAliveInterval 60\n        ServerAliveCountMax 3\n        AddKeysToAgent yes\n\n    # Simple host alias\n    Host myserver\n        HostName server.example.com\n        User admin\n        Port 2222\n        IdentityFile ~/.ssh/mykey\n\n    # Jump host configuration\n    Host bastion\n        HostName bastion.example.com\n        User jumpuser\n        IdentityFile ~/.ssh/bastion_key\n\n    Host internal-*\n        ProxyJump bastion\n        User admin\n        IdentityFile ~/.ssh/internal_key\n\n    Host internal-db\n        HostName 10.0.0.50\n\n    Host internal-web\n        HostName 10.0.0.51\n\n    # Local port forwarding on connect\n    Host db-tunnel\n        HostName bastion.example.com\n        User admin\n        LocalForward 5432 db.internal:5432\n        LocalForward 6379 redis.internal:6379\n\nSSH Agent Forwarding\n--------------------\n\nSSH agent forwarding lets you use your local private keys on remote servers without\ncopying the keys there. When you SSH to a server with agent forwarding enabled\n(``-A``), the remote server can request signatures from your local ssh-agent for\nsubsequent SSH connections. This is essential for workflows like cloning private\ngit repositories from a server or hopping through multiple machines. However, agent\nforwarding has security implications: anyone with root access on the remote server\ncan use your forwarded agent to authenticate as you to other systems while you're\nconnected. For sensitive environments, consider ``ProxyJump`` instead, which keeps\nyour keys local, or use per-host deploy keys.\n\n.. code-block:: bash\n\n    # Enable agent forwarding\n    ssh -A user@server\n\n    # On server, your local keys are available\n    git clone git@github.com:user/repo.git  # Uses forwarded key\n\n.. code-block:: python\n\n    # Agent forwarding with Paramiko\n    from paramiko.client import SSHClient\n    from paramiko.agent import Agent\n\n    # Get keys from local SSH agent\n    agent = Agent()\n    agent_keys = agent.get_keys()\n\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        # Connect using agent key\n        ssh.connect(\"server.example.com\", username=\"user\", pkey=agent_keys[0])\n\n        # Enable agent forwarding for this session\n        transport = ssh.get_transport()\n        paramiko.agent.AgentRequestHandler(transport.open_session())\n\nKeepalive and Connection Stability\n----------------------------------\n\nSSH connections can silently die due to network issues, NAT gateway timeouts, or\nstateful firewalls that drop idle connections. Without keepalives, you won't know\nthe connection is dead until you try to use it—resulting in hung terminals or\nfailed operations. SSH provides two keepalive mechanisms: ``TCPKeepAlive`` uses\nTCP-level keepalive packets (can be blocked by some firewalls), while\n``ServerAliveInterval`` sends SSH-protocol messages through the encrypted channel\n(more reliable). ``ServerAliveCountMax`` determines how many missed responses\ntrigger disconnect. For reliable long-running connections—tunnels, interactive\nsessions, or automation—configure both client and server keepalives. A 30-60\nsecond interval works well for most NAT environments.\n\n.. code-block:: python\n\n    from paramiko.client import SSHClient\n\n    with SSHClient() as ssh:\n        ssh.load_system_host_keys()\n        ssh.connect(\"server.example.com\", username=\"user\", key_filename=\"mykey\")\n\n        # Configure keepalive\n        transport = ssh.get_transport()\n        transport.set_keepalive(30)  # Send keepalive every 30 seconds\n\n        # Long-running operations...\n\n.. code-block:: text\n\n    # In ~/.ssh/config\n    Host *\n        ServerAliveInterval 30\n        ServerAliveCountMax 3\n        TCPKeepAlive yes\n\nCommon SSH Commands Reference\n-----------------------------\n\nA quick reference for essential SSH commands covering connections, tunneling, key\nmanagement, and file transfer. These commands form the foundation of secure remote\nadministration and are worth committing to memory. The verbose flags (``-v`` to\n``-vvv``) are invaluable for debugging connection issues, showing the authentication\nmethods tried, key exchanges, and where failures occur.\n\n.. code-block:: bash\n\n    # Basic connection\n    ssh user@host\n    ssh -p 2222 user@host              # Custom port\n    ssh -i ~/.ssh/mykey user@host      # Specific key\n\n    # Tunneling\n    ssh -L 8080:localhost:80 user@host # Local forward\n    ssh -R 9000:localhost:3000 user@host # Remote forward\n    ssh -D 1080 user@host              # SOCKS proxy\n\n    # Jump hosts\n    ssh -J jump@bastion user@internal  # ProxyJump\n    ssh -o ProxyCommand=\"ssh -W %h:%p jump@bastion\" user@internal\n\n    # Background tunnels\n    ssh -N -f -L 5432:db:5432 user@host  # -N no command, -f background\n\n    # Key management\n    ssh-keygen -t ed25519 -C \"comment\"   # Generate key (ed25519 recommended)\n    ssh-keygen -t rsa -b 4096            # RSA 4096-bit\n    ssh-copy-id -i ~/.ssh/mykey user@host # Copy public key to server\n    ssh-add ~/.ssh/mykey                 # Add key to agent\n\n    # Debugging\n    ssh -v user@host                     # Verbose\n    ssh -vvv user@host                   # Very verbose\n\n    # File transfer\n    scp local.txt user@host:/path/       # Copy to remote\n    scp user@host:/path/file.txt .       # Copy from remote\n    scp -r dir/ user@host:/path/         # Recursive copy\n    rsync -avz -e ssh dir/ user@host:/path/  # Efficient sync\n"
  },
  {
    "path": "docs/notes/os/index.rst",
    "content": ".. meta::\n    :description lang=en: Python system programming tutorial covering file operations, datetime, process management, environment variables, and path manipulation\n    :keywords: Python, Python3, os, file, directory, datetime, subprocess, pathlib, environment, process, system, platform\n\n======\nSystem\n======\n\nPython provides powerful modules for interacting with the operating system,\nmaking it an excellent choice for system administration, automation, and\nscripting tasks. The ``os`` module offers portable access to file systems,\nprocesses, and environment variables, while ``datetime`` handles time and date\noperations. The ``pathlib`` module provides an object-oriented interface for\nfilesystem paths, and ``subprocess`` enables running external commands. This\nsection covers essential system operations including file manipulation, directory\ntraversal, process management, and working with dates and times across different\nplatforms.\n\n.. toctree::\n   :maxdepth: 1\n\n   python-date\n   python-os\n   python-io\n"
  },
  {
    "path": "docs/notes/os/python-date.rst",
    "content": ".. meta::\n    :description lang=en: Python datetime tutorial covering timestamps, date formatting, parsing, timezones, timedelta calculations, calendar operations, and time arithmetic\n    :keywords: Python, Python3, datetime, date, time, timestamp, timezone, timedelta, strftime, strptime, calendar, UTC, ISO 8601, dateutil, zoneinfo\n\n========\nDatetime\n========\n\n:Source: `src/basic/datetime_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/datetime_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nPython's ``datetime`` module provides classes for manipulating dates and times.\nThe module includes ``date`` for calendar dates, ``time`` for clock times,\n``datetime`` for combined date and time, ``timedelta`` for durations, and\n``timezone`` for UTC offsets. Python 3.9+ also includes ``zoneinfo`` for\nIANA timezone support. Understanding these classes is essential for logging,\nscheduling, data analysis, and any application that works with temporal data.\n\nCurrent Date and Time\n---------------------\n\nGetting the current date and time is one of the most common operations. Use\n``datetime.now()`` for local time or ``datetime.utcnow()`` for UTC. In Python\n3.11+, prefer ``datetime.now(timezone.utc)`` over ``utcnow()`` which is\ndeprecated.\n\n.. code-block:: python\n\n    from datetime import datetime, date, time, timezone\n\n    # Current local datetime\n    now = datetime.now()\n    print(now)  # 2024-01-15 10:30:45.123456\n\n    # Current UTC datetime (Python 3.11+ preferred)\n    utc_now = datetime.now(timezone.utc)\n    print(utc_now)  # 2024-01-15 02:30:45.123456+00:00\n\n    # Current date only\n    today = date.today()\n    print(today)  # 2024-01-15\n\n    # Current time only\n    current_time = datetime.now().time()\n    print(current_time)  # 10:30:45.123456\n\nCreating Datetime Objects\n-------------------------\n\nYou can create datetime objects by specifying year, month, day, and optionally\nhour, minute, second, and microsecond. The ``date`` and ``time`` classes work\nsimilarly for date-only or time-only values.\n\n.. code-block:: python\n\n    from datetime import datetime, date, time\n\n    # Create specific datetime\n    dt = datetime(2024, 1, 15, 10, 30, 45)\n    print(dt)  # 2024-01-15 10:30:45\n\n    # Create date only\n    d = date(2024, 1, 15)\n    print(d)  # 2024-01-15\n\n    # Create time only\n    t = time(10, 30, 45)\n    print(t)  # 10:30:45\n\n    # Combine date and time\n    combined = datetime.combine(d, t)\n    print(combined)  # 2024-01-15 10:30:45\n\n    # Get date or time from datetime\n    print(dt.date())  # 2024-01-15\n    print(dt.time())  # 10:30:45\n\nTimestamps\n----------\n\nUnix timestamps represent seconds since January 1, 1970 (the Unix epoch).\nConverting between timestamps and datetime objects is common when working\nwith APIs, databases, and log files.\n\n.. code-block:: python\n\n    import time\n    from datetime import datetime, timezone\n\n    # Current timestamp\n    ts = time.time()\n    print(ts)  # 1705312245.123456\n\n    # Timestamp to datetime (local time)\n    dt = datetime.fromtimestamp(ts)\n    print(dt)  # 2024-01-15 10:30:45.123456\n\n    # Timestamp to datetime (UTC)\n    dt_utc = datetime.fromtimestamp(ts, tz=timezone.utc)\n    print(dt_utc)  # 2024-01-15 02:30:45.123456+00:00\n\n    # Datetime to timestamp\n    ts_back = dt.timestamp()\n    print(ts_back)  # 1705312245.123456\n\n    # Millisecond timestamp (common in JavaScript/APIs)\n    ts_ms = int(ts * 1000)\n    print(ts_ms)  # 1705312245123\n\nFormatting Dates (strftime)\n---------------------------\n\nThe ``strftime()`` method formats datetime objects as strings using format\ncodes. This is essential for displaying dates to users, generating filenames,\nor formatting data for APIs.\n\n.. code-block:: python\n\n    from datetime import datetime\n\n    dt = datetime(2024, 1, 15, 14, 30, 45)\n\n    # Common formats\n    print(dt.strftime(\"%Y-%m-%d\"))           # 2024-01-15\n    print(dt.strftime(\"%d/%m/%Y\"))           # 15/01/2024\n    print(dt.strftime(\"%B %d, %Y\"))          # January 15, 2024\n    print(dt.strftime(\"%Y-%m-%d %H:%M:%S\"))  # 2024-01-15 14:30:45\n    print(dt.strftime(\"%I:%M %p\"))           # 02:30 PM\n    print(dt.strftime(\"%A, %B %d\"))          # Monday, January 15\n\n    # ISO 8601 format\n    print(dt.isoformat())                    # 2024-01-15T14:30:45\n\n    # For filenames (no special characters)\n    print(dt.strftime(\"%Y%m%d_%H%M%S\"))      # 20240115_143045\n\nCommon format codes:\n\n- ``%Y`` - 4-digit year (2024)\n- ``%m`` - Month as zero-padded number (01-12)\n- ``%d`` - Day as zero-padded number (01-31)\n- ``%H`` - Hour 24-hour format (00-23)\n- ``%I`` - Hour 12-hour format (01-12)\n- ``%M`` - Minute (00-59)\n- ``%S`` - Second (00-59)\n- ``%p`` - AM/PM\n- ``%A`` - Full weekday name\n- ``%B`` - Full month name\n- ``%z`` - UTC offset (+0000)\n- ``%Z`` - Timezone name\n\nParsing Dates (strptime)\n------------------------\n\nThe ``strptime()`` method parses strings into datetime objects. The format\nstring must match the input exactly. This is commonly used when reading\ndates from files, user input, or APIs.\n\n.. code-block:: python\n\n    from datetime import datetime\n\n    # Parse various formats\n    dt1 = datetime.strptime(\"2024-01-15\", \"%Y-%m-%d\")\n    dt2 = datetime.strptime(\"15/01/2024\", \"%d/%m/%Y\")\n    dt3 = datetime.strptime(\"January 15, 2024\", \"%B %d, %Y\")\n    dt4 = datetime.strptime(\"2024-01-15 14:30:45\", \"%Y-%m-%d %H:%M:%S\")\n\n    print(dt1)  # 2024-01-15 00:00:00\n    print(dt4)  # 2024-01-15 14:30:45\n\n    # Parse ISO 8601 format\n    dt5 = datetime.fromisoformat(\"2024-01-15T14:30:45\")\n    print(dt5)  # 2024-01-15 14:30:45\n\n    # Parse with timezone (Python 3.11+)\n    dt6 = datetime.fromisoformat(\"2024-01-15T14:30:45+00:00\")\n    print(dt6)  # 2024-01-15 14:30:45+00:00\n\nDate Arithmetic with timedelta\n------------------------------\n\nThe ``timedelta`` class represents a duration—the difference between two dates\nor times. Use it to add or subtract time from datetime objects, or to calculate\nthe difference between two dates.\n\n.. code-block:: python\n\n    from datetime import datetime, timedelta\n\n    now = datetime.now()\n\n    # Add time\n    tomorrow = now + timedelta(days=1)\n    next_week = now + timedelta(weeks=1)\n    in_2_hours = now + timedelta(hours=2)\n    in_90_minutes = now + timedelta(minutes=90)\n\n    # Subtract time\n    yesterday = now - timedelta(days=1)\n    last_month = now - timedelta(days=30)\n\n    # Combine units\n    future = now + timedelta(days=5, hours=3, minutes=30)\n\n    # Calculate difference between dates\n    date1 = datetime(2024, 1, 1)\n    date2 = datetime(2024, 12, 31)\n    diff = date2 - date1\n    print(diff.days)          # 365\n    print(diff.total_seconds())  # 31536000.0\n\n    # Compare dates\n    print(date2 > date1)      # True\n\nTimezones\n---------\n\nWorking with timezones correctly is crucial for applications serving users\nacross different regions. Python 3.9+ includes ``zoneinfo`` for IANA timezone\nsupport. For earlier versions, use ``pytz`` or ``dateutil``.\n\n.. code-block:: python\n\n    from datetime import datetime, timezone, timedelta\n\n    # UTC timezone\n    utc = timezone.utc\n    dt_utc = datetime.now(utc)\n    print(dt_utc)  # 2024-01-15 02:30:45.123456+00:00\n\n    # Fixed offset timezone\n    pst = timezone(timedelta(hours=-8))\n    dt_pst = datetime.now(pst)\n    print(dt_pst)  # 2024-01-14 18:30:45.123456-08:00\n\n    # Convert between timezones\n    dt_converted = dt_utc.astimezone(pst)\n    print(dt_converted)\n\n    # Python 3.9+ with zoneinfo\n    from zoneinfo import ZoneInfo\n\n    eastern = ZoneInfo(\"America/New_York\")\n    tokyo = ZoneInfo(\"Asia/Tokyo\")\n\n    dt_eastern = datetime.now(eastern)\n    dt_tokyo = dt_eastern.astimezone(tokyo)\n    print(dt_tokyo)\n\n    # Make naive datetime timezone-aware\n    naive = datetime(2024, 1, 15, 10, 30)\n    aware = naive.replace(tzinfo=utc)\n\nComparing Dates\n---------------\n\nDatetime objects support comparison operators. When comparing timezone-aware\nand naive datetimes, Python raises a TypeError to prevent subtle bugs.\n\n.. code-block:: python\n\n    from datetime import datetime, date, timedelta\n\n    dt1 = datetime(2024, 1, 15, 10, 0)\n    dt2 = datetime(2024, 1, 15, 14, 0)\n    dt3 = datetime(2024, 1, 16, 10, 0)\n\n    # Comparisons\n    print(dt1 < dt2)   # True\n    print(dt1 == dt2)  # False\n    print(dt3 > dt2)   # True\n\n    # Check if date is in range\n    start = datetime(2024, 1, 1)\n    end = datetime(2024, 12, 31)\n    check = datetime(2024, 6, 15)\n    print(start <= check <= end)  # True\n\n    # Check if date is today\n    today = date.today()\n    some_date = date(2024, 1, 15)\n    print(some_date == today)\n\n    # Days until a date\n    future = date(2024, 12, 25)\n    days_until = (future - today).days\n    print(f\"Days until: {days_until}\")\n\nWorking with Weeks\n------------------\n\nGetting week numbers, weekdays, and working with ISO week dates is common\nfor reporting and scheduling applications.\n\n.. code-block:: python\n\n    from datetime import datetime, date, timedelta\n\n    dt = datetime(2024, 1, 15)\n\n    # Day of week (0=Monday, 6=Sunday)\n    print(dt.weekday())     # 0 (Monday)\n    print(dt.isoweekday())  # 1 (Monday, ISO format 1-7)\n\n    # Week number\n    print(dt.isocalendar())  # (2024, 3, 1) - year, week, weekday\n\n    # Get start of week (Monday)\n    start_of_week = dt - timedelta(days=dt.weekday())\n    print(start_of_week)  # 2024-01-15 00:00:00\n\n    # Get end of week (Sunday)\n    end_of_week = start_of_week + timedelta(days=6)\n    print(end_of_week)  # 2024-01-21 00:00:00\n\n    # Check if weekend\n    is_weekend = dt.weekday() >= 5\n    print(is_weekend)  # False\n\nStart and End of Day/Month/Year\n-------------------------------\n\nGetting the start or end of a time period is useful for date range queries\nand reporting.\n\n.. code-block:: python\n\n    from datetime import datetime, date, time, timedelta\n    import calendar\n\n    dt = datetime(2024, 1, 15, 14, 30, 45)\n\n    # Start of day\n    start_of_day = datetime.combine(dt.date(), time.min)\n    print(start_of_day)  # 2024-01-15 00:00:00\n\n    # End of day\n    end_of_day = datetime.combine(dt.date(), time.max)\n    print(end_of_day)  # 2024-01-15 23:59:59.999999\n\n    # Start of month\n    start_of_month = dt.replace(day=1, hour=0, minute=0, second=0, microsecond=0)\n    print(start_of_month)  # 2024-01-01 00:00:00\n\n    # End of month\n    last_day = calendar.monthrange(dt.year, dt.month)[1]\n    end_of_month = dt.replace(day=last_day, hour=23, minute=59, second=59)\n    print(end_of_month)  # 2024-01-31 23:59:59\n\n    # Start of year\n    start_of_year = dt.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0)\n    print(start_of_year)  # 2024-01-01 00:00:00\n\nCalendar Operations\n-------------------\n\nThe ``calendar`` module provides functions for working with calendars,\nincluding checking leap years, getting month ranges, and generating\ncalendar displays.\n\n.. code-block:: python\n\n    import calendar\n    from datetime import date\n\n    # Check leap year\n    print(calendar.isleap(2024))  # True\n    print(calendar.isleap(2023))  # False\n\n    # Days in month\n    print(calendar.monthrange(2024, 2))  # (3, 29) - weekday of 1st, days in month\n    days_in_feb = calendar.monthrange(2024, 2)[1]\n    print(days_in_feb)  # 29\n\n    # Generate month calendar\n    print(calendar.month(2024, 1))\n\n    # Iterate through month days\n    cal = calendar.Calendar()\n    for day in cal.itermonthdays(2024, 1):\n        if day != 0:\n            print(day, end=\" \")  # 1 2 3 ... 31\n\nDate Ranges\n-----------\n\nGenerating sequences of dates is useful for reports, charts, and scheduling.\n\n.. code-block:: python\n\n    from datetime import datetime, date, timedelta\n\n    def date_range(start, end, step=timedelta(days=1)):\n        \"\"\"Generate dates from start to end.\"\"\"\n        current = start\n        while current <= end:\n            yield current\n            current += step\n\n    # Daily dates\n    start = date(2024, 1, 1)\n    end = date(2024, 1, 7)\n    for d in date_range(start, end):\n        print(d)\n\n    # Weekly dates\n    for d in date_range(start, date(2024, 1, 31), timedelta(weeks=1)):\n        print(d)\n\n    # Business days (skip weekends)\n    def business_days(start, end):\n        current = start\n        while current <= end:\n            if current.weekday() < 5:  # Monday=0 to Friday=4\n                yield current\n            current += timedelta(days=1)\n\nAge Calculation\n---------------\n\nCalculating age from a birthdate requires handling the edge case where the\nbirthday hasn't occurred yet this year.\n\n.. code-block:: python\n\n    from datetime import date\n\n    def calculate_age(birthdate):\n        \"\"\"Calculate age in years from birthdate.\"\"\"\n        today = date.today()\n        age = today.year - birthdate.year\n        # Subtract 1 if birthday hasn't occurred this year\n        if (today.month, today.day) < (birthdate.month, birthdate.day):\n            age -= 1\n        return age\n\n    birthdate = date(1990, 6, 15)\n    age = calculate_age(birthdate)\n    print(f\"Age: {age}\")\n\n    # Days until next birthday\n    def days_until_birthday(birthdate):\n        today = date.today()\n        next_birthday = birthdate.replace(year=today.year)\n        if next_birthday < today:\n            next_birthday = next_birthday.replace(year=today.year + 1)\n        return (next_birthday - today).days\n\nHuman Readable Time Differences\n-------------------------------\n\nConverting timedelta to human-readable strings like \"2 hours ago\" or\n\"in 3 days\" improves user experience.\n\n.. code-block:: python\n\n    from datetime import datetime, timedelta\n\n    def time_ago(dt):\n        \"\"\"Convert datetime to human-readable relative time.\"\"\"\n        now = datetime.now()\n        diff = now - dt\n\n        seconds = diff.total_seconds()\n        if seconds < 60:\n            return \"just now\"\n        elif seconds < 3600:\n            minutes = int(seconds // 60)\n            return f\"{minutes} minute{'s' if minutes != 1 else ''} ago\"\n        elif seconds < 86400:\n            hours = int(seconds // 3600)\n            return f\"{hours} hour{'s' if hours != 1 else ''} ago\"\n        elif seconds < 604800:\n            days = int(seconds // 86400)\n            return f\"{days} day{'s' if days != 1 else ''} ago\"\n        else:\n            return dt.strftime(\"%B %d, %Y\")\n\n    # Examples\n    print(time_ago(datetime.now() - timedelta(minutes=5)))   # 5 minutes ago\n    print(time_ago(datetime.now() - timedelta(hours=2)))     # 2 hours ago\n    print(time_ago(datetime.now() - timedelta(days=3)))      # 3 days ago\n\nUsing dateutil for Flexible Parsing\n-----------------------------------\n\nThe ``python-dateutil`` library provides powerful parsing that handles many\ndate formats automatically, plus relative delta calculations.\n\n.. code-block:: python\n\n    # pip install python-dateutil\n    from dateutil import parser\n    from dateutil.relativedelta import relativedelta\n    from datetime import datetime\n\n    # Flexible parsing - handles many formats automatically\n    dt1 = parser.parse(\"January 15, 2024\")\n    dt2 = parser.parse(\"15/01/2024\")\n    dt3 = parser.parse(\"2024-01-15T14:30:45Z\")\n    dt4 = parser.parse(\"Jan 15 2024 2:30 PM\")\n\n    # Relative delta - handles months and years correctly\n    now = datetime.now()\n\n    # Add 1 month (handles varying month lengths)\n    next_month = now + relativedelta(months=1)\n\n    # Add 1 year\n    next_year = now + relativedelta(years=1)\n\n    # Last day of next month\n    last_of_next_month = now + relativedelta(months=1, day=31)\n\n    # Complex relative: 2 months and 3 days ago\n    past = now - relativedelta(months=2, days=3)\n"
  },
  {
    "path": "docs/notes/os/python-io.rst",
    "content": ".. meta::\n    :description lang=en: Python file I/O tutorial covering reading, writing, binary files, pathlib, context managers, file modes, temporary files, and efficient file handling patterns\n    :keywords: Python, Python3, file, I/O, read, write, binary, text, pathlib, Path, context manager, open, with statement, encoding, shutil, tempfile, glob\n\n=============\nFiles and I/O\n=============\n\n:Source: `src/basic/fileio_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/fileio_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nPython provides comprehensive support for file operations and filesystem\nmanipulation through several built-in modules. The ``open()`` function is the\nfoundation for reading and writing files, supporting text and binary modes with\nconfigurable encoding. The ``pathlib`` module (Python 3.4+) offers a modern,\nobject-oriented interface for path manipulation that works consistently across\noperating systems. For high-level operations like copying directory trees or\nmoving files across filesystems, the ``shutil`` module provides convenient\nfunctions. The ``tempfile`` module handles creation of temporary files and\ndirectories with automatic cleanup, essential for secure handling of\nintermediate data. Together, these modules cover virtually all file I/O needs\nfrom simple text processing to complex filesystem operations.\n\nReading Files\n-------------\n\nThe ``open()`` function returns a file object that supports multiple read\nmethods. Always use the ``with`` statement (context manager) to ensure files\nare properly closed even if an exception occurs. The ``read()`` method loads\nthe entire file into memory, while iterating over the file object processes\none line at a time, which is more memory-efficient for large files. Always\nspecify ``encoding=\"utf-8\"`` explicitly to avoid platform-dependent behavior.\n\n.. code-block:: python\n\n    # Read entire file as string\n    with open(\"example.txt\", encoding=\"utf-8\") as f:\n        content = f.read()\n\n    # Read all lines as list\n    with open(\"example.txt\", encoding=\"utf-8\") as f:\n        lines = f.readlines()\n\n    # Iterate line by line (memory efficient)\n    with open(\"example.txt\", encoding=\"utf-8\") as f:\n        for line in f:\n            print(line.rstrip())\n\n    # Read specific number of characters\n    with open(\"example.txt\", encoding=\"utf-8\") as f:\n        first_100 = f.read(100)\n\n    # Read single line\n    with open(\"example.txt\", encoding=\"utf-8\") as f:\n        first_line = f.readline()\n\nWriting Files\n-------------\n\nPython offers several modes for writing files. Mode ``\"w\"`` creates a new file\nor truncates an existing one, ``\"a\"`` appends to the end without truncating,\nand ``\"x\"`` creates exclusively (raising ``FileExistsError`` if the file\nalready exists). The ``write()`` method writes a single string, while\n``writelines()`` writes an iterable of strings. Note that neither method adds\nnewlines automatically—you must include ``\\n`` in your strings. You can also\nredirect ``print()`` output to a file using the ``file`` parameter.\n\n.. code-block:: python\n\n    # Write string to file (overwrites)\n    with open(\"output.txt\", \"w\", encoding=\"utf-8\") as f:\n        f.write(\"Hello, World!\\n\")\n\n    # Write multiple lines\n    lines = [\"line 1\\n\", \"line 2\\n\", \"line 3\\n\"]\n    with open(\"output.txt\", \"w\", encoding=\"utf-8\") as f:\n        f.writelines(lines)\n\n    # Append to file\n    with open(\"output.txt\", \"a\", encoding=\"utf-8\") as f:\n        f.write(\"Appended line\\n\")\n\n    # Create new file (fails if exists)\n    with open(\"new_file.txt\", \"x\", encoding=\"utf-8\") as f:\n        f.write(\"New content\")\n\n    # Print to file\n    with open(\"output.txt\", \"w\", encoding=\"utf-8\") as f:\n        print(\"Hello\", \"World\", sep=\", \", file=f)\n\nBinary Files\n------------\n\nBinary mode (``\"rb\"``, ``\"wb\"``) reads and writes raw bytes without any\nencoding or newline translation. This is essential for non-text files like\nimages, PDFs, executables, or any file where byte-level accuracy matters.\nBinary data is represented as ``bytes`` objects in Python. When processing\nlarge binary files, read in chunks to avoid loading the entire file into\nmemory at once.\n\n.. code-block:: python\n\n    # Read binary file\n    with open(\"image.png\", \"rb\") as f:\n        data = f.read()\n    print(type(data))  # <class 'bytes'>\n\n    # Write binary file\n    with open(\"copy.png\", \"wb\") as f:\n        f.write(data)\n\n    # Read binary in chunks\n    chunk_size = 8192\n    with open(\"large_file.bin\", \"rb\") as f:\n        while chunk := f.read(chunk_size):\n            process(chunk)\n\nFile Modes\n----------\n\nThe ``open()`` function accepts a mode string that controls how the file is\nopened. The mode combines access type (read, write, append) with content type\n(text or binary). Text mode performs encoding/decoding and newline translation,\nwhile binary mode works with raw bytes. The ``+`` modifier enables both reading\nand writing on the same file handle.\n\nCommon file modes:\n\n- ``\"r\"`` - Read text (default)\n- ``\"w\"`` - Write text (truncates)\n- ``\"a\"`` - Append text\n- ``\"x\"`` - Exclusive create (fails if exists)\n- ``\"rb\"`` - Read binary\n- ``\"wb\"`` - Write binary\n- ``\"r+\"`` - Read and write\n- ``\"w+\"`` - Write and read (truncates)\n\nReading File in Chunks\n----------------------\n\nWhen processing files larger than available memory, reading in chunks prevents\nmemory exhaustion. A generator function that yields chunks is memory-efficient\nand works well with streaming processing. The walrus operator (``:=``) provides\na clean way to read until the file is exhausted. The ``iter()`` function with\na sentinel value offers an alternative pattern for chunk-based reading.\n\n.. code-block:: python\n\n    def read_chunks(filepath, chunk_size=8192):\n        \"\"\"Read file in chunks.\"\"\"\n        with open(filepath, \"rb\") as f:\n            while chunk := f.read(chunk_size):\n                yield chunk\n\n    # Process large file\n    for chunk in read_chunks(\"large_file.bin\"):\n        process(chunk)\n\n    # Using iter with sentinel\n    with open(\"file.txt\", encoding=\"utf-8\") as f:\n        for chunk in iter(lambda: f.read(1024), \"\"):\n            print(chunk, end=\"\")\n\npathlib Basics\n--------------\n\nThe ``pathlib`` module, introduced in Python 3.4, provides an object-oriented\napproach to filesystem paths. Unlike string-based path manipulation, ``Path``\nobjects handle platform differences automatically (forward slashes on Unix,\nbackslashes on Windows). The ``/`` operator joins path components intuitively,\nand methods like ``resolve()`` convert relative paths to absolute. ``Path``\nobjects are the recommended way to work with filesystem paths in modern Python.\n\n.. code-block:: python\n\n    from pathlib import Path\n\n    # Create path objects\n    p = Path(\"folder/file.txt\")\n    p = Path.home() / \"Documents\" / \"file.txt\"\n\n    # Current and home directories\n    cwd = Path.cwd()\n    home = Path.home()\n\n    # Absolute path\n    abs_path = Path(\"file.txt\").resolve()\n\nPath Properties\n---------------\n\n``Path`` objects expose various properties to extract components of a path.\nThe ``name`` property returns the final component, ``stem`` returns the name\nwithout the suffix, and ``suffix`` returns the file extension including the\ndot. The ``parent`` property returns the directory containing the path, and\n``parts`` returns a tuple of all path components. Methods like ``with_suffix()``\nand ``with_name()`` create new paths with modified components without affecting\nthe original.\n\n.. code-block:: python\n\n    from pathlib import Path\n\n    p = Path(\"/home/user/documents/report.pdf\")\n\n    print(p.name)      # report.pdf\n    print(p.stem)      # report\n    print(p.suffix)    # .pdf\n    print(p.parent)    # /home/user/documents\n    print(p.parts)     # ('/', 'home', 'user', 'documents', 'report.pdf')\n    print(p.anchor)    # /\n\n    # Multiple suffixes\n    p2 = Path(\"archive.tar.gz\")\n    print(p2.suffixes)  # ['.tar', '.gz']\n\n    # Change suffix\n    p3 = p.with_suffix(\".txt\")\n    print(p3)  # /home/user/documents/report.txt\n\n    # Change name\n    p4 = p.with_name(\"summary.pdf\")\n    print(p4)  # /home/user/documents/summary.pdf\n\nPath Operations\n---------------\n\n``Path`` objects provide methods to check file existence and type, retrieve\nfile metadata, and perform read/write operations. The ``exists()``, ``is_file()``,\nand ``is_dir()`` methods test path status without raising exceptions. The\n``stat()`` method returns detailed file information including size and\nmodification time. For simple file operations, ``read_text()``, ``write_text()``,\n``read_bytes()``, and ``write_bytes()`` provide convenient one-liner alternatives\nto the ``open()`` context manager pattern.\n\n.. code-block:: python\n\n    from pathlib import Path\n\n    p = Path(\"example.txt\")\n\n    # Check existence and type\n    p.exists()      # True/False\n    p.is_file()     # True if regular file\n    p.is_dir()      # True if directory\n    p.is_symlink()  # True if symbolic link\n\n    # File stats\n    stat = p.stat()\n    print(stat.st_size)   # File size in bytes\n    print(stat.st_mtime)  # Modification time\n\n    # Read and write with pathlib\n    content = p.read_text(encoding=\"utf-8\")\n    p.write_text(\"New content\", encoding=\"utf-8\")\n\n    # Binary read/write\n    data = p.read_bytes()\n    p.write_bytes(b\"binary data\")\n\nListing Directories\n-------------------\n\nPython offers several ways to list directory contents, each with different\ntrade-offs. The ``pathlib`` method ``iterdir()`` returns an iterator of ``Path``\nobjects, allowing you to check file types and access properties directly. The\n``os.scandir()`` function is highly efficient because it retrieves file type\ninformation during directory iteration without additional system calls. The\nsimpler ``os.listdir()`` returns just filenames as strings, requiring additional\ncalls to get file information.\n\n.. code-block:: python\n\n    from pathlib import Path\n    import os\n\n    # pathlib - iterate directory\n    p = Path(\".\")\n    for item in p.iterdir():\n        print(item.name, \"dir\" if item.is_dir() else \"file\")\n\n    # pathlib - glob patterns\n    for py_file in Path(\".\").glob(\"*.py\"):\n        print(py_file)\n\n    # Recursive glob\n    for py_file in Path(\".\").rglob(\"*.py\"):\n        print(py_file)\n\n    # os.scandir (efficient, returns DirEntry)\n    with os.scandir(\".\") as entries:\n        for entry in entries:\n            print(entry.name, entry.is_file())\n\n    # os.listdir (simple list)\n    files = os.listdir(\".\")\n\nGlob Patterns\n-------------\n\nGlob patterns provide a shell-like syntax for matching multiple files. The\n``*`` wildcard matches any characters except path separators, ``?`` matches\na single character, and ``**`` matches any number of directories recursively.\nThe ``pathlib`` methods ``glob()`` and ``rglob()`` (recursive glob) return\niterators of matching ``Path`` objects. Note that ``pathlib`` glob doesn't\nsupport brace expansion like ``{py,txt}``—use multiple glob calls or the\n``glob`` module for complex patterns.\n\n.. code-block:: python\n\n    from pathlib import Path\n\n    # All Python files in current directory\n    list(Path(\".\").glob(\"*.py\"))\n\n    # All Python files recursively\n    list(Path(\".\").rglob(\"*.py\"))\n\n    # Multiple extensions\n    list(Path(\".\").glob(\"*.{py,txt}\"))  # Won't work\n    # Use instead:\n    py_files = list(Path(\".\").glob(\"*.py\"))\n    txt_files = list(Path(\".\").glob(\"*.txt\"))\n\n    # Single character wildcard\n    list(Path(\".\").glob(\"file?.txt\"))  # file1.txt, file2.txt\n\n    # Using glob module\n    import glob\n    glob.glob(\"**/*.py\", recursive=True)\n\nCreating Directories\n--------------------\n\nCreating directories is straightforward with both ``pathlib`` and ``os``. The\n``mkdir()`` method creates a single directory, raising ``FileExistsError`` if\nit already exists. The ``parents=True`` parameter creates all intermediate\ndirectories (like ``mkdir -p`` in Unix), and ``exist_ok=True`` suppresses the\nerror if the directory already exists. These options together make directory\ncreation idempotent and safe for concurrent execution.\n\n.. code-block:: python\n\n    from pathlib import Path\n    import os\n\n    # pathlib - create directory\n    Path(\"new_dir\").mkdir()\n\n    # Create with parents (like mkdir -p)\n    Path(\"path/to/nested/dir\").mkdir(parents=True, exist_ok=True)\n\n    # os.makedirs\n    os.makedirs(\"path/to/dir\", exist_ok=True)\n\nshutil - High-Level File Operations\n------------------------------------\n\nThe ``shutil`` module provides high-level operations for copying, moving,\nand removing files and directory trees.\n\n**Copying Files:**\n\n.. code-block:: python\n\n    import shutil\n\n    # Copy file (content only)\n    shutil.copy(\"source.txt\", \"dest.txt\")\n\n    # Copy file preserving metadata (timestamps, permissions)\n    shutil.copy2(\"source.txt\", \"dest.txt\")\n\n    # Copy to directory (keeps original filename)\n    shutil.copy(\"file.txt\", \"backup/\")  # -> backup/file.txt\n\n    # Copy file object to file object\n    with open(\"src.txt\", \"rb\") as src, open(\"dst.txt\", \"wb\") as dst:\n        shutil.copyfileobj(src, dst)\n\n    # Copy only file content (no metadata)\n    shutil.copyfile(\"source.txt\", \"dest.txt\")\n\n**Copying Directory Trees:**\n\n.. code-block:: python\n\n    import shutil\n\n    # Copy entire directory tree\n    shutil.copytree(\"source_dir\", \"dest_dir\")\n\n    # Copy with ignore patterns\n    shutil.copytree(\n        \"source\",\n        \"dest\",\n        ignore=shutil.ignore_patterns(\"*.pyc\", \"__pycache__\", \".git\")\n    )\n\n    # Copy into existing directory (Python 3.8+)\n    shutil.copytree(\"source\", \"existing_dest\", dirs_exist_ok=True)\n\n    # Custom ignore function\n    def ignore_large_files(directory, files):\n        \"\"\"Ignore files larger than 1MB.\"\"\"\n        ignored = []\n        for f in files:\n            path = os.path.join(directory, f)\n            if os.path.isfile(path) and os.path.getsize(path) > 1_000_000:\n                ignored.append(f)\n        return ignored\n\n    shutil.copytree(\"source\", \"dest\", ignore=ignore_large_files)\n\n    # Copy with symlinks preserved\n    shutil.copytree(\"source\", \"dest\", symlinks=True)\n\n**Moving Files and Directories:**\n\n.. code-block:: python\n\n    import shutil\n    from pathlib import Path\n\n    # Move file (works across filesystems)\n    shutil.move(\"old_name.txt\", \"new_name.txt\")\n\n    # Move file to directory\n    shutil.move(\"file.txt\", \"archive/\")  # -> archive/file.txt\n\n    # Move entire directory\n    shutil.move(\"old_dir\", \"new_dir\")\n\n    # pathlib rename (same filesystem only)\n    Path(\"old.txt\").rename(\"new.txt\")\n\n    # pathlib replace (overwrites destination)\n    Path(\"source.txt\").replace(\"dest.txt\")\n\n**Removing Files and Directories:**\n\n.. code-block:: python\n\n    import shutil\n    from pathlib import Path\n\n    # Delete entire directory tree\n    shutil.rmtree(\"dir_with_contents\")\n\n    # Delete with error handler\n    def on_error(func, path, exc_info):\n        print(f\"Error deleting {path}: {exc_info[1]}\")\n\n    shutil.rmtree(\"dir\", onerror=on_error)\n\n    # Delete ignoring errors\n    shutil.rmtree(\"dir\", ignore_errors=True)\n\n    # Delete file\n    Path(\"file.txt\").unlink()\n    Path(\"file.txt\").unlink(missing_ok=True)  # No error if missing\n\n    # Delete empty directory\n    Path(\"empty_dir\").rmdir()\n\n**Disk Usage:**\n\n.. code-block:: python\n\n    import shutil\n\n    # Get disk usage statistics\n    usage = shutil.disk_usage(\"/\")\n    print(f\"Total: {usage.total // (1024**3)} GB\")\n    print(f\"Used: {usage.used // (1024**3)} GB\")\n    print(f\"Free: {usage.free // (1024**3)} GB\")\n\n**Finding Executables:**\n\n.. code-block:: python\n\n    import shutil\n\n    # Find executable in PATH\n    python_path = shutil.which(\"python\")\n    print(python_path)  # /usr/bin/python\n\n    # Returns None if not found\n    result = shutil.which(\"nonexistent\")\n    print(result)  # None\n\n**Archiving:**\n\n.. code-block:: python\n\n    import shutil\n\n    # Create archive (zip, tar, gztar, bztar, xztar)\n    shutil.make_archive(\"backup\", \"zip\", \"source_dir\")  # -> backup.zip\n    shutil.make_archive(\"backup\", \"gztar\", \"source_dir\")  # -> backup.tar.gz\n\n    # Extract archive\n    shutil.unpack_archive(\"backup.zip\", \"extract_dir\")\n    shutil.unpack_archive(\"backup.tar.gz\", \"extract_dir\")\n\n    # List supported formats\n    print(shutil.get_archive_formats())\n    print(shutil.get_unpack_formats())\n\nTemporary Files\n---------------\n\nThe ``tempfile`` module creates temporary files and directories with unique\nnames in a secure manner. ``NamedTemporaryFile`` creates a file that is\nautomatically deleted when closed (unless ``delete=False``). The ``suffix``\nparameter adds a file extension, useful when other programs need to identify\nthe file type. ``TemporaryDirectory`` creates a directory that is recursively\ndeleted when the context manager exits, perfect for test fixtures or\nintermediate processing. These functions use the system's temp directory by\ndefault, which you can query with ``gettempdir()``.\n\n.. code-block:: python\n\n    import tempfile\n    from pathlib import Path\n\n    # Temporary file (auto-deleted when closed)\n    with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".txt\", delete=True) as f:\n        f.write(\"temporary content\")\n        print(f.name)  # /tmp/tmpXXXXXX.txt\n\n    # Temporary file that persists\n    with tempfile.NamedTemporaryFile(mode=\"w\", delete=False) as f:\n        temp_path = f.name\n        f.write(\"persistent temp\")\n    # Clean up manually later\n    Path(temp_path).unlink()\n\n    # Temporary directory\n    with tempfile.TemporaryDirectory() as tmpdir:\n        temp_file = Path(tmpdir) / \"file.txt\"\n        temp_file.write_text(\"content\")\n        # Directory deleted when exiting context\n\n    # Get temp directory path\n    print(tempfile.gettempdir())  # /tmp\n\nSymbolic Links\n--------------\n\nSymbolic links (symlinks) are special files that point to another file or\ndirectory. They're useful for creating shortcuts, managing multiple versions,\nor organizing files without duplication. The ``symlink_to()`` method creates\na symlink pointing to the specified target. The ``is_symlink()`` method checks\nif a path is a symlink, and ``readlink()`` returns the target path. The\n``resolve()`` method follows all symlinks to return the canonical absolute path.\n\n.. code-block:: python\n\n    from pathlib import Path\n    import os\n\n    # Create symlink with pathlib\n    Path(\"link_name\").symlink_to(\"target_file\")\n\n    # Create symlink with os\n    os.symlink(\"target\", \"link\")\n\n    # Read symlink target\n    target = Path(\"link_name\").readlink()\n    target = os.readlink(\"link\")\n\n    # Check if symlink\n    Path(\"link_name\").is_symlink()\n\n    # Resolve symlink to real path\n    real_path = Path(\"link_name\").resolve()\n\nFile Permissions\n----------------\n\nUnix-like systems use permission bits to control file access. The ``stat()``\nmethod returns file metadata including the permission mode. The ``chmod()``\nmethod modifies permissions using octal notation (e.g., ``0o644`` for\nowner read/write, group/other read-only) or by combining ``stat`` module\nconstants. The ``os.access()`` function checks if the current process has\nspecific permissions on a file, useful for pre-flight checks before attempting\noperations.\n\n.. code-block:: python\n\n    from pathlib import Path\n    import os\n    import stat\n\n    p = Path(\"script.sh\")\n\n    # Get permissions\n    mode = p.stat().st_mode\n    print(oct(mode))  # 0o100644\n\n    # Make executable\n    p.chmod(p.stat().st_mode | stat.S_IXUSR)\n\n    # Set specific permissions (owner rw, group r, other r)\n    p.chmod(0o644)\n\n    # Check if readable/writable\n    os.access(\"file.txt\", os.R_OK)  # Readable\n    os.access(\"file.txt\", os.W_OK)  # Writable\n    os.access(\"file.txt\", os.X_OK)  # Executable\n\nWorking with CSV Files\n----------------------\n\nCSV (Comma-Separated Values) is a common format for tabular data exchange.\nPython's ``csv`` module handles the complexities of CSV parsing, including\nquoted fields, different delimiters, and proper escaping. The ``writer``\nobject writes rows as lists, while ``DictWriter`` writes dictionaries using\ncolumn headers as keys. Similarly, ``reader`` yields rows as lists, and\n``DictReader`` yields dictionaries. Always open CSV files with ``newline=\"\"``\nto let the csv module handle line endings correctly across platforms.\n\n.. code-block:: python\n\n    import csv\n\n    # Write CSV\n    with open(\"data.csv\", \"w\", newline=\"\", encoding=\"utf-8\") as f:\n        writer = csv.writer(f)\n        writer.writerow([\"name\", \"age\", \"city\"])\n        writer.writerow([\"Alice\", 30, \"NYC\"])\n        writer.writerows([[\"Bob\", 25, \"LA\"], [\"Carol\", 35, \"Chicago\"]])\n\n    # Read CSV\n    with open(\"data.csv\", newline=\"\", encoding=\"utf-8\") as f:\n        reader = csv.reader(f)\n        header = next(reader)\n        for row in reader:\n            print(row)\n\n    # DictReader/DictWriter\n    with open(\"data.csv\", newline=\"\", encoding=\"utf-8\") as f:\n        reader = csv.DictReader(f)\n        for row in reader:\n            print(row[\"name\"], row[\"age\"])\n\nWorking with JSON Files\n-----------------------\n\nJSON (JavaScript Object Notation) is the standard format for data interchange\nin web APIs and configuration files. Python's ``json`` module serializes Python\nobjects (dicts, lists, strings, numbers, booleans, None) to JSON strings and\ndeserializes JSON back to Python objects. The ``dump()`` and ``load()`` functions\nwork directly with file objects, while ``dumps()`` and ``loads()`` work with\nstrings. The ``indent`` parameter produces human-readable formatted output.\n\n.. code-block:: python\n\n    import json\n    from pathlib import Path\n\n    data = {\"name\": \"Alice\", \"scores\": [95, 87, 92]}\n\n    # Write JSON\n    with open(\"data.json\", \"w\", encoding=\"utf-8\") as f:\n        json.dump(data, f, indent=2)\n\n    # Read JSON\n    with open(\"data.json\", encoding=\"utf-8\") as f:\n        loaded = json.load(f)\n\n    # pathlib shorthand\n    Path(\"data.json\").write_text(json.dumps(data, indent=2))\n    loaded = json.loads(Path(\"data.json\").read_text())\n\nCompressed Files\n----------------\n\nPython supports several compression formats through dedicated modules. The\n``gzip`` module handles gzip compression, commonly used for log files and web\ncontent. Use ``\"rt\"`` and ``\"wt\"`` modes for text, ``\"rb\"`` and ``\"wb\"`` for\nbinary. The ``zipfile`` module creates and extracts ZIP archives, supporting\nmultiple files in a single archive. The ``writestr()`` method adds content\ndirectly from strings without creating temporary files. Both modules integrate\nseamlessly with Python's file handling patterns.\n\n.. code-block:: python\n\n    import gzip\n    import zipfile\n    from pathlib import Path\n\n    # Write gzip file\n    with gzip.open(\"file.txt.gz\", \"wt\", encoding=\"utf-8\") as f:\n        f.write(\"compressed content\")\n\n    # Read gzip file\n    with gzip.open(\"file.txt.gz\", \"rt\", encoding=\"utf-8\") as f:\n        content = f.read()\n\n    # Create zip archive\n    with zipfile.ZipFile(\"archive.zip\", \"w\") as zf:\n        zf.write(\"file1.txt\")\n        zf.write(\"file2.txt\")\n        zf.writestr(\"new.txt\", \"content from string\")\n\n    # Extract zip archive\n    with zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n        zf.extractall(\"output_dir\")\n        # Extract single file\n        zf.extract(\"file1.txt\", \"output_dir\")\n\n    # List zip contents\n    with zipfile.ZipFile(\"archive.zip\", \"r\") as zf:\n        print(zf.namelist())\n\nFile Locking\n------------\n\nFile locking prevents data corruption when multiple processes access the same\nfile. On Unix systems, ``fcntl.flock()`` provides advisory locking—processes\nmust cooperatively check locks. ``LOCK_EX`` requests an exclusive lock for\nwriting, while ``LOCK_SH`` allows shared read access. The ``LOCK_NB`` flag\nmakes the call non-blocking, raising ``BlockingIOError`` if the lock isn't\nimmediately available. Always release locks in a ``finally`` block to prevent\ndeadlocks. Note that Windows uses different locking mechanisms (``msvcrt``).\n\n.. code-block:: python\n\n    import fcntl\n    import time\n\n    # Exclusive lock (Unix)\n    with open(\"data.txt\", \"w\") as f:\n        fcntl.flock(f.fileno(), fcntl.LOCK_EX)\n        try:\n            f.write(\"exclusive write\")\n            time.sleep(1)  # Simulate work\n        finally:\n            fcntl.flock(f.fileno(), fcntl.LOCK_UN)\n\n    # Non-blocking lock attempt\n    with open(\"data.txt\", \"w\") as f:\n        try:\n            fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)\n            f.write(\"got lock\")\n        except BlockingIOError:\n            print(\"File is locked by another process\")\n\nWatching File Changes (inotify)\n-------------------------------\n\nThe Linux inotify API provides efficient filesystem event monitoring without\npolling. Applications can watch directories for file creation, deletion,\nmodification, and other events. This is useful for auto-reloading configuration\nfiles, triggering builds on source changes, or synchronizing directories.\nThe example below demonstrates direct inotify access via ``ctypes``; for\nproduction use, consider the ``watchdog`` library which provides a\ncross-platform abstraction.\n\n.. code-block:: python\n\n    import ctypes\n    import os\n    import struct\n    import selectors\n    from ctypes.util import find_library\n    from pathlib import Path\n\n    # inotify constants\n    IN_CREATE = 0x00000100\n    IN_DELETE = 0x00000200\n    IN_MODIFY = 0x00000002\n\n    libc = ctypes.CDLL(find_library(\"c\"))\n\n    class Inotify:\n        def __init__(self, path, mask=IN_CREATE | IN_DELETE | IN_MODIFY):\n            self.path = path\n            self.mask = mask\n            self.fd = None\n            self.wd = None\n\n        def __enter__(self):\n            self.fd = libc.inotify_init()\n            path_bytes = str(self.path).encode(\"utf-8\")\n            self.wd = libc.inotify_add_watch(self.fd, path_bytes, self.mask)\n            return self\n\n        def __exit__(self, *args):\n            libc.inotify_rm_watch(self.fd, self.wd)\n            os.close(self.fd)\n\n        def read_events(self):\n            data = os.read(self.fd, 4096)\n            offset = 0\n            while offset < len(data):\n                wd, mask, cookie, length = struct.unpack_from(\"iIII\", data, offset)\n                offset += 16\n                name = data[offset:offset + length].rstrip(b\"\\0\").decode(\"utf-8\")\n                offset += length\n                yield mask, name\n\n    # Usage\n    with Inotify(Path(\"/tmp\")) as inotify:\n        for mask, filename in inotify.read_events():\n            print(f\"Event {mask}: {filename}\")\n"
  },
  {
    "path": "docs/notes/os/python-os.rst",
    "content": ".. meta::\n    :description lang=en: Python operating system interface tutorial covering file operations, process management, environment variables, path manipulation, system information, and cross-platform OS interactions\n    :keywords: Python, os module, operating system, file system, process, environment variables, path, directory, subprocess, platform, system info, CPU, memory\n\n================\nOperating System\n================\n\n:Source: `src/basic/os_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/basic/os_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nPython's ``os`` module provides a portable way to interact with the operating system,\nabstracting platform-specific details behind a consistent API. Whether you're managing\nfiles and directories, spawning processes, reading environment variables, or querying\nsystem information, the ``os`` module handles the differences between Windows, Linux,\nand macOS. For path manipulation, the ``os.path`` submodule (or the modern ``pathlib``)\nprovides cross-platform path handling. This cheat sheet covers common OS operations\nwith practical examples.\n\nGet System Information\n----------------------\n\nRetrieve basic information about the operating system, platform, and current process.\nThese functions are useful for writing cross-platform code that adapts to the runtime\nenvironment.\n\n.. code-block:: python\n\n    import os\n    import platform\n\n    # Operating system name\n    os.name                    # 'posix' (Linux/macOS) or 'nt' (Windows)\n\n    # Platform details\n    platform.system()          # 'Linux', 'Darwin', 'Windows'\n    platform.release()         # '5.15.0-generic', '22.1.0', '10'\n    platform.machine()         # 'x86_64', 'arm64', 'AMD64'\n    platform.processor()       # 'x86_64', 'arm', ''\n    platform.python_version()  # '3.12.0'\n\n    # Current process\n    os.getpid()               # Process ID\n    os.getppid()              # Parent process ID\n    os.getcwd()               # Current working directory\n    os.getlogin()             # Current username\n\nGet Number of CPUs\n------------------\n\nDetermine the number of CPU cores available for parallel processing. This is essential\nfor configuring thread pools, multiprocessing workers, or understanding system capacity.\nNote that ``cpu_count()`` returns logical cores (including hyperthreading), not physical\ncores.\n\n.. code-block:: python\n\n    import os\n\n    # Number of logical CPUs\n    cpu_count = os.cpu_count()\n    print(f\"CPUs: {cpu_count}\")  # CPUs: 8\n\n    # For physical cores (Linux only)\n    # cat /proc/cpuinfo | grep \"cpu cores\" | uniq\n\nSet CPU Affinity\n----------------\n\nCPU affinity binds a process to specific CPU cores, useful for performance optimization,\nreducing cache misses, or isolating workloads. This feature is Linux-specific and not\navailable on macOS or Windows through the ``os`` module.\n\n.. code-block:: python\n\n    import os\n\n    # Linux only - set process to run on specific CPUs\n    pid = os.getpid()\n    affinity = {0, 1}  # Run on CPU 0 and 1 only\n    os.sched_setaffinity(pid, affinity)\n\n    # Get current affinity\n    current = os.sched_getaffinity(pid)\n    print(f\"Running on CPUs: {current}\")  # Running on CPUs: {0, 1}\n\nEnvironment Variables\n---------------------\n\nEnvironment variables store configuration that persists across process invocations.\nUse ``os.environ`` as a dictionary to read, set, or delete variables. Changes only\naffect the current process and its children, not the parent shell.\n\n.. code-block:: python\n\n    import os\n\n    # Read environment variable\n    home = os.environ.get('HOME')           # Returns None if not set\n    path = os.environ['PATH']               # Raises KeyError if not set\n    debug = os.getenv('DEBUG', 'false')     # With default value\n\n    # Set environment variable\n    os.environ['MY_VAR'] = 'my_value'\n\n    # Delete environment variable\n    del os.environ['MY_VAR']\n    os.unsetenv('MY_VAR')  # Alternative\n\n    # List all environment variables\n    for key, value in os.environ.items():\n        print(f\"{key}={value}\")\n\nPath Operations\n---------------\n\nPath manipulation is one of the most common OS tasks. The ``os.path`` module provides\ncross-platform functions that handle path separators (``/`` vs ``\\``) automatically.\nFor modern Python (3.4+), consider using ``pathlib`` for an object-oriented approach.\n\n.. code-block:: python\n\n    import os\n\n    # Join paths (handles separators automatically)\n    path = os.path.join('/home', 'user', 'file.txt')\n    # Linux: '/home/user/file.txt'\n    # Windows: '\\\\home\\\\user\\\\file.txt'\n\n    # Split path components\n    dirname = os.path.dirname('/home/user/file.txt')   # '/home/user'\n    basename = os.path.basename('/home/user/file.txt') # 'file.txt'\n    name, ext = os.path.splitext('file.txt')           # ('file', '.txt')\n\n    # Absolute and relative paths\n    abs_path = os.path.abspath('.')           # Full path\n    rel_path = os.path.relpath('/home/user')  # Relative to cwd\n    real_path = os.path.realpath('link')      # Resolve symlinks\n\n    # Path checks\n    os.path.exists('/path/to/file')    # True if exists\n    os.path.isfile('/path/to/file')    # True if regular file\n    os.path.isdir('/path/to/dir')      # True if directory\n    os.path.islink('/path/to/link')    # True if symbolic link\n\n    # Path info\n    os.path.getsize('/path/to/file')   # Size in bytes\n    os.path.getmtime('/path/to/file')  # Modification time (timestamp)\n\nDirectory Operations\n--------------------\n\nCreate, remove, and navigate directories. These operations are fundamental for file\nmanagement, build systems, and data processing pipelines.\n\n.. code-block:: python\n\n    import os\n\n    # Create directory\n    os.mkdir('new_dir')                    # Single directory\n    os.makedirs('path/to/new_dir')         # Create parent dirs too\n    os.makedirs('path/to/dir', exist_ok=True)  # Don't error if exists\n\n    # Remove directory\n    os.rmdir('empty_dir')                  # Must be empty\n    # For non-empty: use shutil.rmtree()\n\n    # Change directory\n    os.chdir('/path/to/dir')\n    print(os.getcwd())                     # Print current directory\n\n    # List directory contents\n    entries = os.listdir('.')              # List of names\n    for entry in entries:\n        print(entry)\n\n    # Walk directory tree\n    for root, dirs, files in os.walk('.'):\n        for file in files:\n            path = os.path.join(root, file)\n            print(path)\n\nFile Operations\n---------------\n\nLow-level file operations using file descriptors. For most use cases, Python's built-in\n``open()`` function is preferred, but ``os`` functions are useful for special cases like\nnon-blocking I/O or when you need precise control over file descriptors.\n\n.. code-block:: python\n\n    import os\n\n    # Rename/move file\n    os.rename('old_name.txt', 'new_name.txt')\n    os.replace('src.txt', 'dst.txt')  # Atomic, overwrites dst\n\n    # Remove file\n    os.remove('file.txt')\n    os.unlink('file.txt')  # Same as remove\n\n    # File permissions (Unix)\n    os.chmod('file.txt', 0o644)  # rw-r--r--\n    os.chown('file.txt', uid, gid)  # Change owner\n\n    # Create symbolic link\n    os.symlink('target', 'link_name')\n\n    # Low-level file operations\n    fd = os.open('file.txt', os.O_RDONLY)\n    data = os.read(fd, 1024)  # Read up to 1024 bytes\n    os.close(fd)\n\nExecute Commands\n----------------\n\nRun external commands and programs. For simple cases, ``os.system()`` works, but\n``subprocess`` module is recommended for more control over input/output and error\nhandling.\n\n.. code-block:: python\n\n    import os\n    import subprocess\n\n    # Simple command (returns exit code)\n    exit_code = os.system('ls -la')\n\n    # Better: use subprocess\n    result = subprocess.run(['ls', '-la'], capture_output=True, text=True)\n    print(result.stdout)\n    print(result.returncode)\n\n    # Run and capture output\n    output = subprocess.check_output(['date'], text=True)\n    print(output.strip())\n\n    # Run with input\n    result = subprocess.run(\n        ['grep', 'pattern'],\n        input='line1\\npattern here\\nline3',\n        capture_output=True,\n        text=True\n    )\n\nProcess Management\n------------------\n\nCreate and manage child processes. The ``os.fork()`` function is Unix-specific; for\ncross-platform process creation, use the ``multiprocessing`` module instead.\n\n.. code-block:: python\n\n    import os\n\n    # Fork process (Unix only)\n    pid = os.fork()\n    if pid == 0:\n        # Child process\n        print(f\"Child PID: {os.getpid()}\")\n        os._exit(0)\n    else:\n        # Parent process\n        print(f\"Parent PID: {os.getpid()}, Child PID: {pid}\")\n        os.waitpid(pid, 0)  # Wait for child\n\n    # Execute new program (replaces current process)\n    # os.execv('/bin/ls', ['ls', '-la'])\n\n    # Send signal to process\n    os.kill(pid, signal.SIGTERM)\n\nTemporary Files\n---------------\n\nCreate temporary files and directories that are automatically cleaned up. The\n``tempfile`` module provides secure, cross-platform temporary file handling.\n\n.. code-block:: python\n\n    import tempfile\n    import os\n\n    # Temporary file (auto-deleted when closed)\n    with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:\n        f.write('temporary data')\n        temp_path = f.name\n    print(f\"Temp file: {temp_path}\")\n    os.unlink(temp_path)  # Manual cleanup if delete=False\n\n    # Temporary directory\n    with tempfile.TemporaryDirectory() as tmpdir:\n        temp_file = os.path.join(tmpdir, 'file.txt')\n        with open(temp_file, 'w') as f:\n            f.write('data')\n        # Directory and contents deleted after with block\n\n    # Get temp directory path\n    print(tempfile.gettempdir())  # /tmp or C:\\Users\\...\\Temp\n\nUsing pathlib (Modern Alternative)\n----------------------------------\n\nThe ``pathlib`` module (Python 3.4+) provides an object-oriented interface to paths,\nmaking code more readable and less error-prone than string-based ``os.path`` operations.\n\n.. code-block:: python\n\n    from pathlib import Path\n\n    # Create path objects\n    p = Path('/home/user/file.txt')\n    p = Path.home() / 'documents' / 'file.txt'  # Use / operator\n\n    # Path components\n    p.name          # 'file.txt'\n    p.stem          # 'file'\n    p.suffix        # '.txt'\n    p.parent        # Path('/home/user')\n    p.parts         # ('/', 'home', 'user', 'file.txt')\n\n    # Path checks\n    p.exists()\n    p.is_file()\n    p.is_dir()\n\n    # Read/write files\n    content = p.read_text()\n    p.write_text('new content')\n    data = p.read_bytes()\n\n    # Directory operations\n    Path('new_dir').mkdir(parents=True, exist_ok=True)\n    for child in Path('.').iterdir():\n        print(child)\n\n    # Glob patterns\n    for py_file in Path('.').glob('**/*.py'):\n        print(py_file)\n\n\nSystem Monitoring with psutil\n-----------------------------\n\nThe ``psutil`` (process and system utilities) library provides cross-platform access\nto system monitoring data that isn't available through the standard ``os`` module.\nIt covers CPU, memory, disk, network, and process information. Install with\n``pip install psutil``.\n\n.. code-block:: python\n\n    import psutil\n\n    # CPU information\n    psutil.cpu_count()                    # Logical CPUs\n    psutil.cpu_count(logical=False)       # Physical cores\n    psutil.cpu_percent(interval=1)        # CPU usage %\n    psutil.cpu_percent(percpu=True)       # Per-CPU usage\n    psutil.cpu_freq()                     # CPU frequency\n\n    # Memory information\n    mem = psutil.virtual_memory()\n    mem.total                             # Total RAM in bytes\n    mem.available                         # Available RAM\n    mem.percent                           # Usage percentage\n    mem.used                              # Used RAM\n\n    # Swap memory\n    swap = psutil.swap_memory()\n    swap.total, swap.used, swap.percent\n\n    # Disk information\n    psutil.disk_partitions()              # List partitions\n    usage = psutil.disk_usage('/')\n    usage.total, usage.used, usage.percent\n\n    # Network information\n    psutil.net_io_counters()              # Bytes sent/received\n    psutil.net_connections()              # Active connections\n    psutil.net_if_addrs()                 # Interface addresses\n\n    # Process information\n    for proc in psutil.process_iter(['pid', 'name', 'cpu_percent']):\n        print(proc.info)\n\n    # Current process\n    p = psutil.Process()\n    p.pid\n    p.name()\n    p.cpu_percent()\n    p.memory_info()\n    p.num_threads()\n\n    # System boot time\n    import datetime\n    boot = datetime.datetime.fromtimestamp(psutil.boot_time())\n"
  },
  {
    "path": "docs/notes/python-new-py3.rst",
    "content": ".. meta::\n    :description lang=en: Python 3 new features guide covering f-strings, walrus operator, dataclasses, async/await, type hints, and major improvements from Python 2\n    :keywords: Python, Python3, new features, f-strings, walrus operator, dataclasses, async await, type hints, pathlib, migration\n\n==============\nNew in Python3\n==============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThe source code can be found on `py3.py <https://github.com/crazyguitar/pysheeet/blob/master/src/new_py3/py3.py>`_.\n\nType Parameter Syntax\n----------------------\n\n**New in Python 3.12**\n\n- PEP 695_ - Type Parameter Syntax\n\nPython 3.12 introduces a cleaner, more intuitive syntax for defining generic classes\nand functions. Instead of importing ``TypeVar`` and ``Generic`` from the typing module,\nyou can now use the ``[]`` bracket notation directly in class and function definitions.\nThis makes generic code more readable and reduces boilerplate significantly.\n\n.. code-block:: python\n\n    # Old way (before Python 3.12)\n    from typing import TypeVar, Generic\n    T = TypeVar('T')\n    class Box(Generic[T]):\n        def __init__(self, item: T) -> None:\n            self.item = item\n\n    # New way (Python 3.12+)\n    class Box[T]:\n        def __init__(self, item: T) -> None:\n            self.item = item\n\n    # Generic functions\n    def first[T](items: list[T]) -> T:\n        return items[0]\n\n\nf-string Improvements\n----------------------\n\n**New in Python 3.12**\n\n- PEP 701_ - Syntactic formalization of f-strings\n\nF-strings have been significantly improved in Python 3.12. They now support nested\nquotes of the same type, backslash escapes, and multi-line expressions without any\nlimitations. This makes f-strings much more flexible and eliminates many edge cases\nthat previously required workarounds.\n\n.. code-block:: python\n\n    >>> songs = [\"Take me back to Eden\", \"&\", \"Satellite\"]\n    >>> f\"This is the playlist: {\", \".join(songs)}\"\n    'This is the playlist: Take me back to Eden, &, Satellite'\n\n    # Nested quotes now work\n    >>> f\"He said {\"hello\"}\"\n    'He said hello'\n\n\nException Groups\n-----------------\n\n**New in Python 3.11**\n\n- PEP 654_ - Exception Groups and except*\n\nException groups allow you to raise and handle multiple unrelated exceptions\nsimultaneously. This is particularly useful for concurrent operations where\nmultiple tasks might fail independently. The new ``except*`` syntax lets you\nhandle specific exception types from a group while letting others propagate.\n\n.. code-block:: python\n\n    >>> def raise_multiple():\n    ...     raise ExceptionGroup(\"multiple errors\", [\n    ...         ValueError(\"invalid value\"),\n    ...         TypeError(\"wrong type\"),\n    ...     ])\n    ...\n    >>> try:\n    ...     raise_multiple()\n    ... except* ValueError as e:\n    ...     print(f\"ValueError: {e.exceptions}\")\n    ... except* TypeError as e:\n    ...     print(f\"TypeError: {e.exceptions}\")\n    ...\n    ValueError: (ValueError('invalid value'),)\n    TypeError: (TypeError('wrong type'),)\n\n\nStructural Pattern Matching\n----------------------------\n\n**New in Python 3.10**\n\n- PEP 634_ - Structural Pattern Matching: Specification\n- PEP 635_ - Structural Pattern Matching: Motivation and Rationale\n\nPattern matching provides a powerful way to destructure and match complex data\nstructures. It's similar to switch statements in other languages but far more\nexpressive, supporting sequence patterns, mapping patterns, class patterns,\nand guards. The wildcard ``_`` matches anything and serves as a default case.\n\n.. code-block:: python\n\n    >>> def http_status(status):\n    ...     match status:\n    ...         case 200:\n    ...             return \"OK\"\n    ...         case 404:\n    ...             return \"Not Found\"\n    ...         case 500:\n    ...             return \"Internal Server Error\"\n    ...         case _:\n    ...             return \"Unknown\"\n    ...\n    >>> http_status(200)\n    'OK'\n    >>> http_status(404)\n    'Not Found'\n\n    # Pattern matching with destructuring\n    >>> def describe_point(point):\n    ...     match point:\n    ...         case (0, 0):\n    ...             return \"Origin\"\n    ...         case (x, 0):\n    ...             return f\"On x-axis at {x}\"\n    ...         case (0, y):\n    ...             return f\"On y-axis at {y}\"\n    ...         case (x, y):\n    ...             return f\"Point at ({x}, {y})\"\n    ...\n    >>> describe_point((0, 0))\n    'Origin'\n    >>> describe_point((5, 0))\n    'On x-axis at 5'\n\n\nDictionary Merge\n----------------\n\n**New in Python 3.9**\n\n- PEP 584_  - Add Union Operators To dict\n\nThe ``|`` operator provides a cleaner, more intuitive way to merge dictionaries.\nThe ``|=`` operator updates a dictionary in place. This is more readable than\nusing ``{**a, **b}`` or ``dict.update()`` and follows the pattern of set operations.\n\n.. code-block:: python\n\n    >>> a = {\"foo\": \"Foo\"}\n    >>> b = {\"bar\": \"Bar\"}\n\n    # old way\n    >>> {**a, **b}\n    {'foo': 'Foo', 'bar': 'Bar'}\n    >>> a.update(b)\n    >>> a\n    {'foo': 'Foo', 'bar': 'Bar'}\n\n    # new way\n    >>> a = {\"foo\": \"Foo\"}\n    >>> a | b\n    {'foo': 'Foo', 'bar': 'Bar'}\n    >>> a |= b\n    >>> a\n    {'foo': 'Foo', 'bar': 'Bar'}\n\n\nPositional-only parameters\n---------------------------\n\n**New in Python 3.8**\n\n- PEP 570_ - Python Positional-Only Parameters\n\nParameters before the ``/`` marker must be passed positionally and cannot be used\nas keyword arguments. This gives library authors more flexibility in API design\nand allows parameter names to be changed without breaking backward compatibility.\n\n.. code-block:: python\n\n    >>> def f(a, b, /, c, d):\n    ...     print(a, b, c, d)\n    ...\n    >>> f(1, 2, 3, 4)\n    1 2 3 4\n    >>> f(1, 2, c=3, d=4)\n    1 2 3 4\n    >>> f(1, b=2, c=3, d=4)\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n    TypeError: f() got some positional-only arguments passed as keyword arguments: 'b'\n\n\nThe walrus operator\n--------------------\n\n**New in Python 3.8**\n\n- PEP 572_ - Assignment Expressions\n\nThe walrus operator ``:=`` allows you to assign values to variables as part of an\nexpression. This reduces code duplication when you need to both compute a value\nand use it in a condition. After completing PEP 572, Guido van Rossum, commonly\nknown as BDFL, decided to resign as Python's dictator.\n\n.. code-block:: python\n\n    >>> f = (0, 1)\n    >>> [(f := (f[1], sum(f)))[0] for i in range(10)]\n    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]\n\n    # Useful in while loops\n    >>> while (line := input(\"Enter: \")) != \"quit\":\n    ...     print(f\"You entered: {line}\")\n\n    # Useful in if statements\n    >>> if (n := len(\"hello\")) > 3:\n    ...     print(f\"Length {n} is greater than 3\")\n\n\nData Classes\n-------------\n\n**New in Python 3.7**\n\n- PEP 557_ - Data Classes\n\nDataclasses automatically generate boilerplate code like ``__init__``, ``__repr__``,\n``__eq__``, and optionally ``__hash__`` for classes that primarily store data.\nThis reduces repetitive code and makes data-holding classes more concise and readable.\n\nMutable Data Class\n\n.. code-block:: python\n\n    >>> from dataclasses import dataclass\n    >>> @dataclass\n    ... class DCls(object):\n    ...     x: str\n    ...     y: str\n    ...\n    >>> d = DCls(\"foo\", \"bar\")\n    >>> d\n    DCls(x='foo', y='bar')\n    >>> d = DCls(x=\"foo\", y=\"baz\")\n    >>> d\n    DCls(x='foo', y='baz')\n    >>> d.z = \"bar\"\n\nImmutable Data Class\n\n.. code-block:: python\n\n    >>> from dataclasses import dataclass\n    >>> from dataclasses import FrozenInstanceError\n    >>> @dataclass(frozen=True)\n    ... class DCls(object):\n    ...     x: str\n    ...     y: str\n    ...\n    >>> try:\n    ...     d.x = \"baz\"\n    ... except FrozenInstanceError as e:\n    ...     print(e)\n    ...\n    cannot assign to field 'x'\n    >>> try:\n    ...     d.z = \"baz\"\n    ... except FrozenInstanceError as e:\n    ...     print(e)\n    ...\n    cannot assign to field 'z'\n\n\nBuilt-in ``breakpoint()``\n--------------------------\n\n**New in Python 3.7**\n\n- PEP 553_ - Built-in breakpoint()\n\nThe ``breakpoint()`` function provides a convenient way to drop into the debugger.\nIt respects the ``PYTHONBREAKPOINT`` environment variable, allowing you to customize\nor disable debugging behavior without modifying code.\n\n.. code-block:: python\n\n    >>> for x in range(3):\n    ...     print(x)\n    ...     breakpoint()\n    ...\n    0\n    > <stdin>(1)<module>()->None\n    (Pdb) c\n    1\n    > <stdin>(1)<module>()->None\n    (Pdb) c\n    2\n    > <stdin>(1)<module>()->None\n    (Pdb) c\n\n\nCore support for typing module and generic types\n-------------------------------------------------\n\n**New in Python 3.7**\n\n- PEP 560_ - Core support for typing module and generic types\n\nPython 3.7 added core support for the typing module, making generic types faster\nand enabling classes to customize how they're subscripted via ``__class_getitem__``.\n\nBefore Python 3.7\n\n.. code-block:: python\n\n    >>> from typing import Generic, TypeVar\n    >>> from typing import Iterable\n    >>> T = TypeVar('T')\n    >>> class C(Generic[T]): ...\n    ...\n    >>> def func(l: Iterable[C[int]]) -> None:\n    ...     for i in l:\n    ...         print(i)\n    ...\n    >>> func([1,2,3])\n    1\n    2\n    3\n\nPython 3.7 or above\n\n.. code-block:: python\n\n    >>> from typing import Iterable\n    >>> class C:\n    ...     def __class_getitem__(cls, item):\n    ...         return f\"{cls.__name__}[{item.__name__}]\"\n    ...\n    >>> def func(l: Iterable[C[int]]) -> None:\n    ...     for i in l:\n    ...         print(i)\n    ...\n    >>> func([1,2,3])\n    1\n    2\n    3\n\n\nVariable annotations\n--------------------\n\n**New in Python 3.6**\n\n- PEP 526_ - Syntax for Variable Annotations\n\nVariables can now be annotated with types using the ``:`` syntax, even without\nimmediate assignment. This enables better static analysis and IDE support.\n\n.. code-block:: python\n\n    >>> from typing import List\n    >>> x: List[int] = [1, 2, 3]\n    >>> x\n    [1, 2, 3]\n\n    >>> from typing import List, Dict\n    >>> class Cls(object):\n    ...     x: List[int] = [1, 2, 3]\n    ...     y: Dict[str, str] = {\"foo\": \"bar\"}\n    ...\n    >>> o = Cls()\n    >>> o.x\n    [1, 2, 3]\n    >>> o.y\n    {'foo': 'bar'}\n\n\nf-string\n---------\n\n**New in Python 3.6**\n\n- PEP 498_ - Literal String Interpolation\n\nF-strings (formatted string literals) provide a concise and readable way to embed\nexpressions inside string literals. They are faster than ``%`` formatting and\n``str.format()`` because they are evaluated at runtime.\n\n.. code-block:: python\n\n    >>> py = \"Python3\"\n    >>> f'Awesome {py}'\n    'Awesome Python3'\n    >>> x = [1, 2, 3, 4, 5]\n    >>> f'{x}'\n    '[1, 2, 3, 4, 5]'\n    >>> def foo(x:int) -> int:\n    ...     return x + 1\n    ...\n    >>> f'{foo(0)}'\n    '1'\n    >>> f'{123.567:1.3}'\n    '1.24e+02'\n\n\nAsynchronous generators\n------------------------\n\n**New in Python 3.6**\n\n- PEP 525_ - Asynchronous Generators\n\nAsynchronous generators combine the power of generators with async/await syntax,\nallowing you to yield values asynchronously. This is useful for streaming data\nfrom async sources.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def fib(n: int):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         await asyncio.sleep(1)\n    ...         yield a\n    ...         b, a = a + b , b\n    ...\n    >>> async def coro(n: int):\n    ...     ag = fib(n)\n    ...     f = await ag.asend(None)\n    ...     print(f)\n    ...     f = await ag.asend(None)\n    ...     print(f)\n    ...\n    >>> loop = asyncio.get_event_loop()\n    >>> loop.run_until_complete(coro(5))\n    0\n    1\n\n\nAsynchronous comprehensions\n----------------------------\n\n**New in Python 3.6**\n\n- PEP 530_ - Asynchronous Comprehensions\n\nAsync comprehensions allow using ``async for`` in list, set, dict comprehensions\nand generator expressions. You can also use ``await`` expressions within comprehensions.\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def fib(n: int):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         await asyncio.sleep(1)\n    ...         yield a\n    ...         b, a = a + b , b\n    ...\n\n    # async for ... else\n\n    >>> async def coro(n: int):\n    ...     async for f in fib(n):\n    ...         print(f, end=\" \")\n    ...     else:\n    ...         print()\n    ...\n    >>> loop = asyncio.get_event_loop()\n    >>> loop.run_until_complete(coro(5))\n    0 1 1 2 3\n\n    # async for in list\n\n    >>> async def coro(n: int):\n    ...     return [f async for f in fib(n)]\n    ...\n    >>> loop.run_until_complete(coro(5))\n    [0, 1, 1, 2, 3]\n\n    # await in list\n\n    >>> async def slowfmt(n: int) -> str:\n    ...     await asyncio.sleep(0.5)\n    ...     return f'{n}'\n    ...\n    >>> async def coro(n: int):\n    ...     return [await slowfmt(f) async for f in fib(n)]\n    ...\n    >>> loop.run_until_complete(coro(5))\n    ['0', '1', '1', '2', '3']\n\n\nNew dict implementation\n------------------------\n\n**New in Python 3.6**\n\n- PEP 468_ - Preserving the order of \\*\\*kwargs in a function\n- PEP 520_ - Preserving Class Attribute Definition Order\n- bpo 27350_ - More compact dictionaries with faster iteration\n\nPython 3.6 introduced a new dictionary implementation that uses 20-25% less memory\nand preserves insertion order. This was an implementation detail in 3.6 but became\na language guarantee in Python 3.7.\n\nBefore Python 3.5\n\n.. code-block:: python\n\n    >>> import sys\n    >>> sys.getsizeof({str(i):i for i in range(1000)})\n    49248\n\n    >>> d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}\n    >>> d   # without order-preserving\n    {'barry': 'green', 'timmy': 'red', 'guido': 'blue'}\n\nPython 3.6\n\n- Memory usage is smaller than Python 3.5\n- Preserve insertion ordered\n\n.. code-block:: python\n\n    >>> import sys\n    >>> sys.getsizeof({str(i):i for i in range(1000)})\n    36968\n\n    >>> d = {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}\n    >>> d   # preserve insertion ordered\n    {'timmy': 'red', 'barry': 'green', 'guido': 'blue'}\n\n\n``async`` and ``await`` syntax\n-------------------------------\n\n**New in Python 3.5**\n\n- PEP 492_ - Coroutines with async and await syntax\n\nThe ``async`` and ``await`` keywords provide native syntax for writing coroutines,\nmaking asynchronous code much more readable than the previous generator-based approach.\nThis is the foundation of modern Python async programming.\n\nBefore Python 3.5\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> @asyncio.coroutine\n    ... def fib(n: int):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         b, a = a + b, b\n    ...     return a\n    ...\n    >>> @asyncio.coroutine\n    ... def coro(n: int):\n    ...     for x in range(n):\n    ...         yield from asyncio.sleep(1)\n    ...         f = yield from fib(x)\n    ...         print(f)\n    ...\n    >>> loop = asyncio.get_event_loop()\n    >>> loop.run_until_complete(coro(3))\n    0\n    1\n    1\n\nPython 3.5 or above\n\n.. code-block:: python\n\n    >>> import asyncio\n    >>> async def fib(n: int):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         b, a = a + b, b\n    ...     return a\n    ...\n    >>> async def coro(n: int):\n    ...     for x in range(n):\n    ...         await asyncio.sleep(1)\n    ...         f = await fib(x)\n    ...         print(f)\n    ...\n    >>> loop = asyncio.get_event_loop()\n    >>> loop.run_until_complete(coro(3))\n    0\n    1\n    1\n\n\nGeneral unpacking\n------------------\n\n**New in Python 3.5**\n\n- PEP 448_ - Additional Unpacking Generalizations\n\nPython 3.5 extended the ``*`` and ``**`` unpacking operators to work in more contexts,\nincluding function calls with multiple unpacking operations and in list/dict literals.\n\nPython 2\n\n.. code-block:: python\n\n    >>> def func(*a, **k):\n    ...     print(a)\n    ...     print(k)\n    ...\n    >>> func(*[1,2,3,4,5], **{\"foo\": \"bar\"})\n    (1, 2, 3, 4, 5)\n    {'foo': 'bar'}\n\nPython 3\n\n.. code-block:: python\n\n    >>> print(*[1, 2, 3], 4, *[5, 6])\n    1 2 3 4 5 6\n    >>> [*range(4), 4]\n    [0, 1, 2, 3, 4]\n    >>> {\"foo\": \"Foo\", \"bar\": \"Bar\", **{\"baz\": \"baz\"}}\n    {'foo': 'Foo', 'bar': 'Bar', 'baz': 'baz'}\n    >>> def func(*a, **k):\n    ...     print(a)\n    ...     print(k)\n    ...\n    >>> func(*[1], *[4,5], **{\"foo\": \"FOO\"}, **{\"bar\": \"BAR\"})\n    (1, 4, 5)\n    {'foo': 'FOO', 'bar': 'BAR'}\n\n\nMatrix multiplication\n----------------------\n\n**New in Python 3.5**\n\n- PEP 465_ - A dedicated infix operator for matrix multiplication\n\nThe ``@`` operator was added for matrix multiplication, primarily benefiting\nscientific computing libraries like NumPy. Classes can implement ``__matmul__``\nand ``__imatmul__`` to support this operator.\n\n.. code-block:: python\n\n    >>> # \"@\" represent matrix multiplication\n    >>> class Arr:\n    ...     def __init__(self, *arg):\n    ...         self._arr = arg\n    ...     def __matmul__(self, other):\n    ...         if not isinstance(other, Arr):\n    ...             raise TypeError\n    ...         if len(self) != len(other):\n    ...             raise ValueError\n    ...         return sum([x*y for x, y in zip(self._arr, other._arr)])\n    ...     def __imatmul__(self, other):\n    ...         if not isinstance(other, Arr):\n    ...             raise TypeError\n    ...         if len(self) != len(other):\n    ...             raise ValueError\n    ...         res = sum([x*y for x, y in zip(self._arr, other._arr)])\n    ...         self._arr = [res]\n    ...         return self\n    ...     def __len__(self):\n    ...         return len(self._arr)\n    ...     def __str__(self):\n    ...         return self.__repr__()\n    ...     def __repr__(self):\n    ...         return \"Arr({})\".format(repr(self._arr))\n    ...\n    >>> a = Arr(9, 5, 2, 7)\n    >>> b = Arr(5, 5, 6, 6)\n    >>> a @ b  # __matmul__\n    124\n    >>> a @= b  # __imatmul__\n    >>> a\n    Arr([124])\n\n\nFormat byte string\n-------------------\n\n**New in Python 3.5**\n\n- PEP 461_ - Adding ``%`` formatting to bytes and bytearray\n\nThe ``%`` formatting operator now works with bytes and bytearray objects,\nmaking it easier to work with binary protocols and formats.\n\n.. code-block:: python\n\n    >>> b'abc %b %b' % (b'foo', b'bar')\n    b'abc foo bar'\n    >>> b'%d %f' % (1, 3.14)\n    b'1 3.140000'\n    >>> class Cls(object):\n    ...     def __repr__(self):\n    ...         return \"repr\"\n    ...     def __str__(self):\n    ...         return \"str\"\n    ...\n    'repr'\n    >>> b'%a' % Cls()\n    b'repr'\n\n\nSuppressing exception\n----------------------\n\n**New in Python 3.3**\n\n- PEP 409_ - Suppressing exception context\n\nWhen re-raising exceptions, Python shows the chain of exceptions by default.\nUsing ``raise ... from None`` suppresses the context, showing only the new exception.\n\nWithout ``raise Exception from None``\n\n.. code-block:: python\n\n    >>> def func():\n    ...     try:\n    ...         1 / 0\n    ...     except ZeroDivisionError:\n    ...         raise ArithmeticError\n    ...\n    >>> func()\n    Traceback (most recent call last):\n      File \"<stdin>\", line 3, in func\n    ZeroDivisionError: division by zero\n\n    During handling of the above exception, another exception occurred:\n\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n      File \"<stdin>\", line 5, in func\n    ArithmeticError\n\nWith ``raise Exception from None``\n\n.. code-block:: python\n\n    >>> def func():\n    ...     try:\n    ...         1 / 0\n    ...     except ZeroDivisionError:\n    ...         raise ArithmeticError from None\n    ...\n    >>> func()\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n      File \"<stdin>\", line 5, in func\n    ArithmeticError\n\n    # debug\n\n    >>> try:\n    ...     func()\n    ... except ArithmeticError as e:\n    ...     print(e.__context__)\n    ...\n    division by zero\n\n\nGenerator delegation\n----------------------\n\n**New in Python 3.3**\n\n- PEP 380_ - Syntax for Delegating to a Subgenerator\n\nThe ``yield from`` expression allows a generator to delegate part of its operations\nto another generator. This simplifies writing generators that consume other generators.\n\n.. code-block:: python\n\n    >>> def fib(n: int):\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         yield a\n    ...         b, a = a + b, b\n    ...\n    >>> def delegate(n: int):\n    ...     yield from fib(n)\n    ...\n    >>> list(delegate(10))\n    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n\nBDFL retirement\n---------------\n\n**New in Python 3.1**\n\n- PEP 401_ - BDFL Retirement\n\nAn April Fools' joke PEP that added a humorous easter egg. When you import\n``barry_as_FLUFL`` from ``__future__``, the ``!=`` operator is replaced with ``<>``.\n\n.. code-block:: python\n\n    >>> from __future__ import barry_as_FLUFL\n    >>> 1 != 2\n      File \"<stdin>\", line 1\n        1 != 2\n           ^\n    SyntaxError: with Barry as BDFL, use '<>' instead of '!='\n    >>> 1 <> 2\n    True\n\n\nFunction annotations\n--------------------\n\n**New in Python 3.0**\n\n- PEP 3107_ - Function Annotations\n- PEP 484_ - Type Hints\n- PEP 483_ - The Theory of Type Hints\n\nFunction annotations allow attaching metadata to function parameters and return\nvalues. While Python doesn't enforce these at runtime, they enable static type\nchecking tools and better IDE support.\n\n.. code-block:: python\n\n    >>> import types\n    >>> generator = types.GeneratorType\n    >>> def fib(n: int) -> generator:\n    ...     a, b = 0, 1\n    ...     for _ in range(n):\n    ...         yield a\n    ...         b, a = a + b, b\n    ...\n    >>> [f for f in fib(10)]\n    [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n\nExtended iterable unpacking\n----------------------------\n\n**New in Python 3.0**\n\n- PEP 3132_ - Extended Iterable Unpacking\n\nThe ``*`` operator in unpacking captures remaining items into a list. This works\nin assignments and for loops, making it easy to split sequences.\n\n.. code-block:: python\n\n    >>> a, *b, c = range(5)\n    >>> a, b, c\n    (0, [1, 2, 3], 4)\n    >>> for a, *b in [(1, 2, 3), (4, 5, 6, 7)]:\n    ...     print(a, b)\n    ...\n    1 [2, 3]\n    4 [5, 6, 7]\n\n\nKeyword-Only Arguments\n-----------------------\n\n**New in Python 3.0**\n\n- PEP 3102_ - Keyword-Only Arguments\n\nParameters defined after ``*`` in a function signature must be passed as keyword\narguments. This improves API clarity and prevents accidental positional usage.\n\n.. code-block:: python\n\n    >>> def f(a, b, *, kw):\n    ...     print(a, b, kw)\n    ...\n    >>> f(1, 2, 3)\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n    TypeError: f() takes 2 positional arguments but 3 were given\n    >>> f(1, 2)\n    Traceback (most recent call last):\n      File \"<stdin>\", line 1, in <module>\n    TypeError: f() missing 1 required keyword-only argument: 'kw'\n    >>> f(1, 2, kw=3)\n    1 2 3\n\n\nNew Super\n----------\n\n**New in Python 3.0**\n\n- PEP 3135_ - New Super\n\nPython 3 simplified the ``super()`` call by making it work without arguments in\nmost cases. The interpreter automatically determines the class and instance.\n\nPython 2\n\n.. code-block:: python\n\n    >>> class ParentCls(object):\n    ...     def foo(self):\n    ...         print \"call parent\"\n    ...\n    >>> class ChildCls(ParentCls):\n    ...     def foo(self):\n    ...         super(ChildCls, self).foo()\n    ...         print \"call child\"\n    ...\n    >>> p = ParentCls()\n    >>> c = ChildCls()\n    >>> p.foo()\n    call parent\n    >>> c.foo()\n    call parent\n    call child\n\nPython 3\n\n.. code-block:: python\n\n    >>> class ParentCls(object):\n    ...     def foo(self):\n    ...         print(\"call parent\")\n    ...\n    >>> class ChildCls(ParentCls):\n    ...     def foo(self):\n    ...         super().foo()\n    ...         print(\"call child\")\n    ...\n    >>> p = ParentCls()\n    >>> c = ChildCls()\n    >>> p.foo()\n    call parent\n    >>> c.foo()\n    call parent\n    call child\n\n\nAdd ``nonlocal`` keyword\n-------------------------\n\n**New in Python 3.0**\n\n- PEP 3104_ - Access to Names in Outer Scopes\n\nThe ``nonlocal`` keyword allows assigning to variables in an enclosing (but non-global)\nscope. This is useful for closures that need to modify outer variables.\n\n.. code-block:: python\n\n    >>> def outf():\n    ...     o = \"out\"\n    ...     def inf():\n    ...         nonlocal o\n    ...         o = \"change out\"\n    ...     inf()\n    ...     print(o)\n    ...\n    >>> outf()\n    change out\n\n\nNot allow ``from module import *`` inside function\n---------------------------------------------------\n\n**New in Python 3.0**\n\nStar imports are now only allowed at module level. This prevents namespace pollution\nand makes code more predictable.\n\n.. code-block:: python\n\n    >>> def f():\n    ...     from os import *\n    ...\n      File \"<stdin>\", line 1\n    SyntaxError: import * only allowed at module level\n\n\nRemove ``<>``\n--------------\n\n**New in Python 3.0**\n\nThe ``<>`` operator (alternative to ``!=``) was removed in Python 3 to simplify\nthe language. Use ``!=`` for inequality comparisons.\n\nPython 2\n\n.. code-block:: python\n\n    >>> a = \"Python2\"\n    >>> a <> \"Python3\"\n    True\n\n    # equal to !=\n    >>> a != \"Python3\"\n    True\n\nPython 3\n\n.. code-block:: python\n\n    >>> a = \"Python3\"\n    >>> a != \"Python2\"\n    True\n\n\n``print`` is a function\n-------------------------\n\n**New in Python 3.0**\n\n- PEP 3105_ - Make print a function\n\nIn Python 3, ``print`` became a function instead of a statement. This allows\nmore flexibility with keyword arguments like ``end``, ``sep``, and ``file``.\n\nPython 2\n\n.. code-block:: python\n\n    >>> print \"print is a statement\"\n    print is a statement\n    >>> for x in range(3):\n    ...     print x,\n    ...\n    0 1 2\n\nPython 3\n\n.. code-block:: python\n\n    >>> print(\"print is a function\")\n    print is a function\n    >>> print()\n    >>> for x in range(3):\n    ...     print(x, end=' ')\n    ... else:\n    ...     print()\n    ...\n    0 1 2\n\n\nString is unicode\n-------------------\n\n**New in Python 3.0**\n\n- PEP 3138_ - String representation in Python 3000\n- PEP 3120_ - Using UTF-8 as the default source encoding\n- PEP 3131_ - Supporting Non-ASCII Identifiers\n\nIn Python 3, all strings are Unicode by default. The ``str`` type represents\nUnicode text, while ``bytes`` represents binary data. This eliminates many\nencoding-related bugs common in Python 2.\n\nPython 2\n\n.. code-block:: python\n\n    >>> s = 'Café'  # byte string\n    >>> s\n    'Caf\\xc3\\xa9'\n    >>> type(s)\n    <type 'str'>\n    >>> u = u'Café' # unicode string\n    >>> u\n    u'Caf\\xe9'\n    >>> type(u)\n    <type 'unicode'>\n    >>> len([_c for _c in 'Café'])\n    5\n\nPython 3\n\n.. code-block:: python\n\n    >>> s = 'Café'\n    >>> s\n    'Café'\n    >>> type(s)\n    <class 'str'>\n    >>> s.encode('utf-8')\n    b'Caf\\xc3\\xa9'\n    >>> s.encode('utf-8').decode('utf-8')\n    'Café'\n    >>> len([_c for _c in 'Café'])\n    4\n\n\nDivision Operator\n------------------\n\n**New in Python 3.0**\n\n- PEP 238_ - Changing the Division Operator\n\nIn Python 3, the ``/`` operator always performs true division (returning a float),\nwhile ``//`` performs floor division. This eliminates a common source of bugs.\n\nPython 2\n\n.. code-block:: python\n\n    >>> 1 / 2\n    0\n    >>> 1 // 2\n    0\n    >>> 1. / 2\n    0.5\n\n    # back port \"true division\" to python2\n\n    >>> from __future__ import division\n    >>> 1 / 2\n    0.5\n    >>> 1 // 2\n    0\n\nPython 3\n\n.. code-block:: python\n\n    >>> 1 / 2\n    0.5\n    >>> 1 // 2\n    0\n\n\n.. _695: https://www.python.org/dev/peps/pep-0695/\n.. _701: https://www.python.org/dev/peps/pep-0701/\n.. _654: https://www.python.org/dev/peps/pep-0654/\n.. _634: https://www.python.org/dev/peps/pep-0634/\n.. _635: https://www.python.org/dev/peps/pep-0635/\n.. _584: https://www.python.org/dev/peps/pep-0584/\n.. _570: https://www.python.org/dev/peps/pep-0570/\n.. _572: https://www.python.org/dev/peps/pep-0572/\n.. _557: https://www.python.org/dev/peps/pep-0557/\n.. _553: https://www.python.org/dev/peps/pep-0553/\n.. _560: https://www.python.org/dev/peps/pep-0560/\n.. _526: https://www.python.org/dev/peps/pep-0526/\n.. _498: https://www.python.org/dev/peps/pep-0498/\n.. _525: https://www.python.org/dev/peps/pep-0525/\n.. _530: https://www.python.org/dev/peps/pep-0530/\n.. _468: https://www.python.org/dev/peps/pep-0468/\n.. _520: https://www.python.org/dev/peps/pep-0520/\n.. _27350: https://bugs.python.org/issue27350\n.. _492: https://www.python.org/dev/peps/pep-0492/\n.. _448: https://www.python.org/dev/peps/pep-0448/\n.. _465: https://www.python.org/dev/peps/pep-0465/\n.. _461: https://www.python.org/dev/peps/pep-0461/\n.. _409: https://www.python.org/dev/peps/pep-0409/\n.. _380: https://www.python.org/dev/peps/pep-0380/\n.. _401: https://www.python.org/dev/peps/pep-0401/\n.. _3107: https://www.python.org/dev/peps/pep-3107/\n.. _484: https://www.python.org/dev/peps/pep-0484/\n.. _483: https://www.python.org/dev/peps/pep-0483/\n.. _3132: https://www.python.org/dev/peps/pep-3132/\n.. _3102: https://www.python.org/dev/peps/pep-3102/\n.. _3135: https://www.python.org/dev/peps/pep-3135/\n.. _3104: https://www.python.org/dev/peps/pep-3104/\n.. _3105: https://www.python.org/dev/peps/pep-3105/\n.. _3138: https://www.python.org/dev/peps/pep-3138/\n.. _3120: https://www.python.org/dev/peps/pep-3120/\n.. _3131: https://www.python.org/dev/peps/pep-3131/\n.. _238: https://www.python.org/dev/peps/pep-0238/\n"
  },
  {
    "path": "docs/notes/security/index.rst",
    "content": ".. meta::\n    :description lang=en: Python security and cryptography guide covering modern encryption, TLS/SSL, common vulnerabilities, and secure coding practices\n    :keywords: Python, Python3, security, cryptography, encryption, AES, RSA, TLS, SSL, vulnerability, padding oracle, injection, secure coding\n\n========\nSecurity\n========\n\nSecurity is essential for protecting data in transit and at rest. This section\ncovers modern cryptographic practices using well-maintained libraries like\n``cryptography`` and ``argon2-cffi``, as well as common security vulnerabilities\nand how to avoid them. We emphasize secure defaults: authenticated encryption\n(AES-GCM), proper key derivation (PBKDF2, Argon2), secure signatures (Ed25519,\nRSA-PSS), and correct TLS configuration.\n\nUnderstanding vulnerabilities is equally important—knowing why legacy patterns\nlike AES-CBC without authentication or PKCS#1 v1.5 padding are dangerous helps\nyou recognize and fix insecure code in existing systems.\n\n.. toctree::\n    :maxdepth: 1\n\n    python-crypto\n    python-tls\n    python-vulnerability\n"
  },
  {
    "path": "docs/notes/security/python-crypto.rst",
    "content": ".. meta::\n    :description lang=en: Modern Python cryptography guide covering symmetric encryption (AES-GCM), asymmetric encryption (RSA-OAEP), digital signatures, key derivation, and secure random generation using the cryptography library\n    :keywords: Python, Python3, Cryptography, AES-GCM, RSA, OAEP, Digital Signature, PBKDF2, Argon2, Key Derivation, Encryption, Decryption, HMAC, Hashing\n\n===================\nModern Cryptography\n===================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis guide covers modern cryptographic practices in Python using the ``cryptography``\nlibrary, which is the recommended choice for new projects. The library provides both\nhigh-level recipes (Fernet) for common use cases and low-level primitives for advanced\nneeds. We focus on secure defaults: AES-GCM for symmetric encryption (provides both\nconfidentiality and integrity), RSA-OAEP for asymmetric encryption, Ed25519 for\nsignatures, and proper key derivation functions. Avoid deprecated libraries like\nPyCrypto—use ``cryptography`` or ``PyCryptodome`` instead.\n\n.. warning::\n\n    Cryptography is difficult to implement correctly. Prefer high-level APIs like\n    Fernet when possible. Never invent your own cryptographic schemes. Always use\n    authenticated encryption (AES-GCM, ChaCha20-Poly1305) instead of unauthenticated\n    modes (AES-CBC, AES-CTR alone).\n\nAlgorithm Recommendations\n-------------------------\n\nQuick reference for choosing secure algorithms. When in doubt, use the recommended\noptions—they represent current best practices as of 2024.\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────────────┐\n    │                    ALGORITHM RECOMMENDATIONS                            │\n    ├─────────────────────────────────────────────────────────────────────────┤\n    │  USE CASE              │ RECOMMENDED          │ AVOID                   │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Symmetric Encryption  │ AES-256-GCM          │ AES-CBC, AES-ECB,       │\n    │                        │ ChaCha20-Poly1305    │ DES, 3DES, Blowfish,    │\n    │                        │                      │ RC4                     │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Asymmetric Encryption │ RSA-OAEP (≥3072-bit) │ RSA PKCS#1 v1.5,        │\n    │                        │ ECIES                │ RSA < 2048-bit,         │\n    │                        │                      │ ElGamal                 │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Digital Signatures    │ Ed25519              │ RSA PKCS#1 v1.5,        │\n    │                        │ RSA-PSS (≥3072-bit)  │ DSA, ECDSA with P-256   │\n    │                        │ Ed448                │ (if possible)           │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Key Exchange          │ X25519               │ DH < 2048-bit,          │\n    │                        │ X448                 │ Static DH               │\n    │                        │ ECDH (P-384+)        │                         │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Password Hashing      │ Argon2id             │ MD5, SHA-1, SHA-256,    │\n    │                        │ scrypt               │ bcrypt (less preferred),│\n    │                        │ PBKDF2 (≥600k iter)  │ plain hashes            │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  General Hashing       │ SHA-256, SHA-3       │ MD5, SHA-1              │\n    │                        │ BLAKE2, BLAKE3       │                         │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  MAC                   │ HMAC-SHA256          │ HMAC-MD5, HMAC-SHA1     │\n    │                        │ KMAC, Poly1305       │                         │\n    ├────────────────────────┼──────────────────────┼─────────────────────────┤\n    │  Random Generation     │ secrets module       │ random module           │\n    │                        │ os.urandom()         │ time-based seeds        │\n    └────────────────────────┴──────────────────────┴─────────────────────────┘\n\nKey Size Recommendations\n------------------------\n\nMinimum key sizes for security through 2030+. Larger keys provide more security\nmargin but slower performance.\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────────────┐\n    │                       KEY SIZE GUIDELINES                               │\n    ├─────────────────────────────────────────────────────────────────────────┤\n    │  ALGORITHM             │ MINIMUM    │ RECOMMENDED  │ NOTES              │\n    ├────────────────────────┼────────────┼──────────────┼────────────────────┤\n    │  AES                   │ 128-bit    │ 256-bit      │ 256 for long-term  │\n    │  ChaCha20              │ 256-bit    │ 256-bit      │ Only size available│\n    │  RSA (encryption)      │ 2048-bit   │ 3072-4096    │ 4096 for long-term │\n    │  RSA (signatures)      │ 2048-bit   │ 3072-4096    │ 4096 for long-term │\n    │  ECDSA/ECDH            │ P-256      │ P-384        │ Prefer Ed25519     │\n    │  Ed25519               │ 256-bit    │ 256-bit      │ Fixed size         │\n    │  X25519                │ 256-bit    │ 256-bit      │ Fixed size         │\n    │  HMAC key              │ 256-bit    │ 256-bit      │ Match hash output  │\n    │  Salt (password)       │ 128-bit    │ 128-bit      │ 16 bytes minimum   │\n    │  Nonce (AES-GCM)       │ 96-bit     │ 96-bit       │ 12 bytes, unique!  │\n    │  IV (AES-CBC)          │ 128-bit    │ 128-bit      │ 16 bytes, random   │\n    └────────────────────────┴────────────┴──────────────┴────────────────────┘\n\nCommon Mistakes (Don't Do This)\n-------------------------------\n\nExamples of insecure patterns to avoid. Each \"BAD\" example shows a common mistake;\nthe \"GOOD\" example shows the secure alternative.\n\n**❌ Using random module for security:**\n\n.. code-block:: python\n\n    # BAD: Predictable random - can be reverse-engineered!\n    import random\n    token = ''.join(random.choices('abcdef0123456789', k=32))\n    key = bytes([random.randint(0, 255) for _ in range(32)])\n\n    # GOOD: Cryptographically secure random\n    import secrets\n    token = secrets.token_hex(16)\n    key = secrets.token_bytes(32)\n\n**❌ Using ECB mode:**\n\n.. code-block:: python\n\n    # BAD: ECB mode reveals patterns in data!\n    from Crypto.Cipher import AES\n    cipher = AES.new(key, AES.MODE_ECB)  # NEVER use ECB\n\n    # GOOD: Use authenticated encryption\n    from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n    aesgcm = AESGCM(key)\n    ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n\n**❌ AES-CBC without authentication:**\n\n.. code-block:: python\n\n    # BAD: No integrity check - vulnerable to padding oracle attacks!\n    from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes\n    cipher = Cipher(algorithms.AES(key), modes.CBC(iv))\n    encryptor = cipher.encryptor()\n    ciphertext = encryptor.update(padded_data) + encryptor.finalize()\n    # Attacker can modify ciphertext without detection!\n\n    # GOOD: AES-GCM provides authentication\n    from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n    aesgcm = AESGCM(key)\n    ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n    # Any modification will be detected during decryption\n\n**❌ Reusing nonces:**\n\n.. code-block:: python\n\n    # BAD: Reusing nonce completely breaks AES-GCM security!\n    nonce = b'fixed_nonce!'  # NEVER do this\n    ct1 = aesgcm.encrypt(nonce, msg1, None)\n    ct2 = aesgcm.encrypt(nonce, msg2, None)  # Catastrophic!\n\n    # GOOD: Generate unique nonce for each encryption\n    import os\n    nonce1 = os.urandom(12)\n    ct1 = aesgcm.encrypt(nonce1, msg1, None)\n    nonce2 = os.urandom(12)\n    ct2 = aesgcm.encrypt(nonce2, msg2, None)\n\n**❌ RSA with PKCS#1 v1.5 padding:**\n\n.. code-block:: python\n\n    # BAD: Vulnerable to Bleichenbacher's attack!\n    from Crypto.Cipher import PKCS1_v1_5\n    cipher = PKCS1_v1_5.new(key)\n    ciphertext = cipher.encrypt(message)\n\n    # GOOD: Use OAEP padding\n    from cryptography.hazmat.primitives.asymmetric import padding\n    ciphertext = public_key.encrypt(\n        message,\n        padding.OAEP(\n            mgf=padding.MGF1(algorithm=hashes.SHA256()),\n            algorithm=hashes.SHA256(),\n            label=None,\n        ),\n    )\n\n**❌ Hashing passwords with SHA-256:**\n\n.. code-block:: python\n\n    # BAD: Too fast - billions of guesses per second!\n    import hashlib\n    password_hash = hashlib.sha256(password.encode()).hexdigest()\n\n    # GOOD: Use slow password hashing function\n    from argon2 import PasswordHasher\n    ph = PasswordHasher()\n    password_hash = ph.hash(password)\n\n**❌ Comparing MACs with ==:**\n\n.. code-block:: python\n\n    # BAD: Timing attack reveals information about correct MAC!\n    if received_mac == computed_mac:  # VULNERABLE\n        process_message()\n\n    # GOOD: Constant-time comparison\n    import hmac\n    if hmac.compare_digest(received_mac, computed_mac):\n        process_message()\n\n**❌ Hardcoding keys/secrets:**\n\n.. code-block:: python\n\n    # BAD: Keys in source code end up in version control!\n    SECRET_KEY = \"super_secret_key_12345\"\n    API_KEY = \"ak_live_xxxxxxxxxxxxx\"\n\n    # GOOD: Use environment variables or secret management\n    import os\n    SECRET_KEY = os.environ.get('SECRET_KEY')\n    # Or use: AWS Secrets Manager, HashiCorp Vault, etc.\n\nSecurity Checklist\n------------------\n\nUse this checklist when implementing cryptography in your application.\n\n**Before You Start:**\n\n.. code-block:: text\n\n    □ Do I really need custom crypto? (Consider existing solutions first)\n    □ Am I using a well-maintained library? (cryptography, not PyCrypto)\n    □ Have I read the library's security documentation?\n\n**Key Management:**\n\n.. code-block:: text\n\n    □ Keys generated with cryptographically secure random (secrets/os.urandom)\n    □ Keys are appropriate size (AES-256, RSA ≥3072-bit)\n    □ Keys stored securely (not in source code, use env vars or secret manager)\n    □ Key rotation plan in place\n    □ Different keys for different purposes (encryption vs signing)\n    □ Keys protected at rest (encrypted with master key or HSM)\n\n**Encryption:**\n\n.. code-block:: text\n\n    □ Using authenticated encryption (AES-GCM or ChaCha20-Poly1305)\n    □ Nonces are unique per encryption (random 12 bytes for AES-GCM)\n    □ Not reusing key+nonce combinations\n    □ Associated data (AAD) used where appropriate\n    □ Ciphertext includes nonce for decryption\n    □ Using RSA-OAEP (not PKCS#1 v1.5) for asymmetric encryption\n\n**Signatures:**\n\n.. code-block:: text\n\n    □ Using Ed25519 or RSA-PSS (not PKCS#1 v1.5)\n    □ Signing the right data (include all relevant fields)\n    □ Verifying signatures before trusting data\n    □ Handling verification failures securely\n\n**Password Storage:**\n\n.. code-block:: text\n\n    □ Using Argon2id, scrypt, or PBKDF2 (not plain hashes)\n    □ Unique salt per password (≥16 bytes)\n    □ Sufficient iterations/memory cost (tune for ~100ms-500ms)\n    □ Rehashing when parameters change\n    □ Not logging passwords or hashes\n\n**TLS/Network:**\n\n.. code-block:: text\n\n    □ TLS 1.2 or 1.3 only (no SSL, TLS 1.0, TLS 1.1)\n    □ Certificate validation enabled\n    □ Using trusted CA certificates\n    □ Hostname verification enabled\n    □ Certificate pinning for high-security apps\n\n**General:**\n\n.. code-block:: text\n\n    □ No sensitive data in logs\n    □ Secure memory handling (clear keys after use where possible)\n    □ Error messages don't leak sensitive information\n    □ Timing-safe comparisons for secrets\n    □ Dependencies up to date (check for CVEs)\n\nSecure Random Generation\n------------------------\n\nCryptographic operations require unpredictable random numbers. Python's ``secrets``\nmodule (Python 3.6+) provides cryptographically secure random generation, suitable\nfor tokens, passwords, and keys. Never use the ``random`` module for security-sensitive\napplications—it uses a predictable PRNG (Mersenne Twister) that can be reverse-engineered\nfrom observed outputs.\n\n.. code-block:: python\n\n    import secrets\n    import os\n\n    # Generate random bytes (for keys, IVs, salts)\n    key = secrets.token_bytes(32)      # 256-bit key\n    iv = secrets.token_bytes(16)       # 128-bit IV\n\n    # Generate URL-safe token (for session IDs, API keys)\n    token = secrets.token_urlsafe(32)  # ~43 characters\n\n    # Generate hex token\n    hex_token = secrets.token_hex(16)  # 32 hex characters\n\n    # Secure random integer\n    n = secrets.randbelow(100)         # 0 <= n < 100\n\n    # Secure choice from sequence\n    password_char = secrets.choice('abcdefghijklmnopqrstuvwxyz0123456789')\n\n    # Alternative: os.urandom (works on all Python versions)\n    key = os.urandom(32)\n\nCryptographic Hashing\n---------------------\n\nHash functions produce fixed-size digests from arbitrary data. Use SHA-256 or SHA-3\nfor general hashing. For password storage, use dedicated password hashing functions\n(see Key Derivation section). Hash functions are one-way: you cannot recover the\noriginal data from a hash. They're used for data integrity verification, digital\nsignatures, and as building blocks for other cryptographic operations.\n\n.. code-block:: python\n\n    import hashlib\n\n    data = b\"Hello, World!\"\n\n    # SHA-256 (recommended for general use)\n    digest = hashlib.sha256(data).hexdigest()\n    print(f\"SHA-256: {digest}\")\n\n    # SHA-3 (newer, different internal structure)\n    digest = hashlib.sha3_256(data).hexdigest()\n    print(f\"SHA3-256: {digest}\")\n\n    # BLAKE2 (fast, secure, supports keying)\n    digest = hashlib.blake2b(data, digest_size=32).hexdigest()\n    print(f\"BLAKE2b: {digest}\")\n\n    # Keyed BLAKE2 (MAC without separate HMAC construction)\n    key = b\"secret-key-here!\"\n    mac = hashlib.blake2b(data, key=key, digest_size=32).hexdigest()\n\n    # Incremental hashing (for large files)\n    h = hashlib.sha256()\n    with open(\"largefile.bin\", \"rb\") as f:\n        for chunk in iter(lambda: f.read(8192), b\"\"):\n            h.update(chunk)\n    print(f\"File hash: {h.hexdigest()}\")\n\nHMAC (Hash-based Message Authentication Code)\n---------------------------------------------\n\nHMAC provides message authentication—verifying both integrity and authenticity.\nUnlike plain hashes, HMAC requires a secret key, so only parties with the key can\ncreate or verify the MAC. Use HMAC when you need to ensure data hasn't been tampered\nwith and came from someone who knows the secret. Always use constant-time comparison\nto prevent timing attacks when verifying MACs.\n\n.. code-block:: python\n\n    import hmac\n    import hashlib\n    import secrets\n\n    key = secrets.token_bytes(32)\n    message = b\"Important message\"\n\n    # Create HMAC\n    mac = hmac.new(key, message, hashlib.sha256).digest()\n\n    # Verify HMAC (constant-time comparison)\n    received_mac = mac  # In practice, received from sender\n    if hmac.compare_digest(mac, received_mac):\n        print(\"Message is authentic\")\n    else:\n        print(\"Message was tampered with!\")\n\n    # HMAC with hexdigest\n    mac_hex = hmac.new(key, message, hashlib.sha256).hexdigest()\n\nKey Derivation Functions\n------------------------\n\nKey derivation functions (KDFs) derive cryptographic keys from passwords or other\nkey material. For passwords, use slow KDFs (PBKDF2, Argon2, scrypt) that resist\nbrute-force attacks. For deriving multiple keys from a master key, use HKDF. Never\nuse plain hashes (SHA-256) for password storage—they're too fast, allowing billions\nof guesses per second.\n\n.. code-block:: python\n\n    import os\n    from cryptography.hazmat.primitives import hashes\n    from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC\n    from cryptography.hazmat.primitives.kdf.hkdf import HKDF\n    from cryptography.hazmat.primitives.kdf.scrypt import Scrypt\n\n    # PBKDF2 - widely supported, use >= 600,000 iterations (OWASP 2023)\n    password = b\"user-password\"\n    salt = os.urandom(16)\n\n    kdf = PBKDF2HMAC(\n        algorithm=hashes.SHA256(),\n        length=32,\n        salt=salt,\n        iterations=600_000,\n    )\n    key = kdf.derive(password)\n\n    # To verify a password, derive again and compare\n    kdf = PBKDF2HMAC(\n        algorithm=hashes.SHA256(),\n        length=32,\n        salt=salt,  # Must use same salt\n        iterations=600_000,\n    )\n    try:\n        kdf.verify(password, key)\n        print(\"Password correct\")\n    except Exception:\n        print(\"Password incorrect\")\n\n    # Scrypt - memory-hard, better resistance to GPU/ASIC attacks\n    kdf = Scrypt(salt=salt, length=32, n=2**17, r=8, p=1)\n    key = kdf.derive(password)\n\n    # HKDF - for deriving multiple keys from master key (not for passwords)\n    master_key = os.urandom(32)\n    hkdf = HKDF(\n        algorithm=hashes.SHA256(),\n        length=32,\n        salt=salt,\n        info=b\"encryption-key\",\n    )\n    derived_key = hkdf.derive(master_key)\n\nSymmetric Encryption: AES-GCM\n-----------------------------\n\nAES-GCM (Galois/Counter Mode) is the recommended symmetric encryption mode. It\nprovides authenticated encryption: both confidentiality (data is encrypted) and\nintegrity (tampering is detected). The authentication tag ensures ciphertext hasn't\nbeen modified. Always use a unique nonce (number used once) for each encryption\nwith the same key—reusing nonces completely breaks security.\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────┐\n    │                     AES-GCM ENCRYPTION                          │\n    ├─────────────────────────────────────────────────────────────────┤\n    │                                                                 │\n    │   Plaintext ──┬──► AES-GCM ──► Ciphertext                       │\n    │               │       │                                         │\n    │   Key ────────┤       ├──► Authentication Tag (16 bytes)        │\n    │               │       │                                         │\n    │   Nonce ──────┘       └──► Associated Data (AAD) authenticated  │\n    │   (12 bytes)                but not encrypted                   │\n    │                                                                 │\n    │   Security: Nonce MUST be unique per key. Random 12-byte        │\n    │   nonce is safe for ~2^32 encryptions per key.                  │\n    │                                                                 │\n    └─────────────────────────────────────────────────────────────────┘\n\n.. code-block:: python\n\n    import os\n    from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n\n    # Generate a random 256-bit key\n    key = AESGCM.generate_key(bit_length=256)\n\n    # Create cipher\n    aesgcm = AESGCM(key)\n\n    # Encrypt\n    nonce = os.urandom(12)  # 96-bit nonce, MUST be unique per encryption\n    plaintext = b\"Secret message\"\n    associated_data = b\"header\"  # Authenticated but not encrypted (optional)\n\n    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)\n    # ciphertext includes the 16-byte authentication tag\n\n    # Decrypt\n    decrypted = aesgcm.decrypt(nonce, ciphertext, associated_data)\n    assert decrypted == plaintext\n\n    # Tampering detection - modifying ciphertext raises exception\n    try:\n        tampered = bytearray(ciphertext)\n        tampered[0] ^= 1  # Flip one bit\n        aesgcm.decrypt(nonce, bytes(tampered), associated_data)\n    except Exception as e:\n        print(f\"Tampering detected: {e}\")\n\nSymmetric Encryption: ChaCha20-Poly1305\n---------------------------------------\n\nChaCha20-Poly1305 is an alternative to AES-GCM, offering similar security with\nbetter performance on systems without AES hardware acceleration (common on mobile\ndevices and older CPUs). It's used by TLS 1.3, WireGuard, and many modern protocols.\nLike AES-GCM, it provides authenticated encryption with associated data (AEAD).\n\n.. code-block:: python\n\n    import os\n    from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305\n\n    # Generate key (256-bit)\n    key = ChaCha20Poly1305.generate_key()\n\n    # Create cipher\n    chacha = ChaCha20Poly1305(key)\n\n    # Encrypt\n    nonce = os.urandom(12)  # 96-bit nonce\n    plaintext = b\"Secret message\"\n    aad = b\"additional authenticated data\"\n\n    ciphertext = chacha.encrypt(nonce, plaintext, aad)\n\n    # Decrypt\n    decrypted = chacha.decrypt(nonce, ciphertext, aad)\n    assert decrypted == plaintext\n\nHigh-Level Encryption: Fernet\n-----------------------------\n\nFernet provides a high-level, easy-to-use symmetric encryption API. It uses AES-128-CBC\nwith HMAC for authentication, handles IV generation, and includes timestamp for\noptional TTL-based expiration. Use Fernet when you need simple, secure encryption\nwithout worrying about low-level details. The downside is slightly larger ciphertext\nand no associated data support.\n\n.. code-block:: python\n\n    from cryptography.fernet import Fernet, InvalidToken\n    import time\n\n    # Generate key (store this securely!)\n    key = Fernet.generate_key()\n    print(f\"Key: {key.decode()}\")  # Base64-encoded\n\n    # Create Fernet instance\n    f = Fernet(key)\n\n    # Encrypt\n    plaintext = b\"Secret message\"\n    token = f.encrypt(plaintext)\n    print(f\"Token: {token.decode()}\")\n\n    # Decrypt\n    decrypted = f.decrypt(token)\n    assert decrypted == plaintext\n\n    # Decrypt with TTL (time-to-live in seconds)\n    try:\n        # Token must have been created within last 60 seconds\n        decrypted = f.decrypt(token, ttl=60)\n    except InvalidToken:\n        print(\"Token expired or invalid\")\n\n    # Key rotation with MultiFernet\n    from cryptography.fernet import MultiFernet\n\n    old_key = Fernet.generate_key()\n    new_key = Fernet.generate_key()\n\n    # MultiFernet tries keys in order for decryption\n    # Always encrypts with first key\n    multi = MultiFernet([Fernet(new_key), Fernet(old_key)])\n\n    # Can decrypt tokens from either key\n    old_token = Fernet(old_key).encrypt(b\"old data\")\n    decrypted = multi.decrypt(old_token)  # Works!\n\n    # Re-encrypt with new key\n    new_token = multi.rotate(old_token)\n\nRSA Key Generation\n------------------\n\nRSA is an asymmetric algorithm using public/private key pairs. The public key\nencrypts data or verifies signatures; the private key decrypts or signs. Modern\nrecommendations: use at least 2048-bit keys (3072 or 4096 for long-term security),\npublic exponent 65537, and OAEP padding for encryption or PSS for signatures.\nFor new systems, consider Ed25519 (signatures) or X25519 (key exchange) instead.\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric import rsa\n    from cryptography.hazmat.primitives import serialization\n\n    # Generate RSA key pair\n    private_key = rsa.generate_private_key(\n        public_exponent=65537,\n        key_size=4096,  # 2048 minimum, 4096 for long-term\n    )\n    public_key = private_key.public_key()\n\n    # Serialize private key (PEM format)\n    private_pem = private_key.private_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PrivateFormat.PKCS8,\n        encryption_algorithm=serialization.BestAvailableEncryption(b\"passphrase\"),\n    )\n\n    # Serialize private key without encryption\n    private_pem_unencrypted = private_key.private_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PrivateFormat.PKCS8,\n        encryption_algorithm=serialization.NoEncryption(),\n    )\n\n    # Serialize public key\n    public_pem = public_key.public_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PublicFormat.SubjectPublicKeyInfo,\n    )\n\n    # Save to files\n    with open(\"private_key.pem\", \"wb\") as f:\n        f.write(private_pem)\n    with open(\"public_key.pem\", \"wb\") as f:\n        f.write(public_pem)\n\n    # Load keys from files\n    with open(\"private_key.pem\", \"rb\") as f:\n        loaded_private = serialization.load_pem_private_key(\n            f.read(),\n            password=b\"passphrase\",\n        )\n\n    with open(\"public_key.pem\", \"rb\") as f:\n        loaded_public = serialization.load_pem_public_key(f.read())\n\nRSA Encryption (OAEP)\n---------------------\n\nRSA encryption should always use OAEP (Optimal Asymmetric Encryption Padding).\nNever use PKCS#1 v1.5 padding for new applications—it's vulnerable to padding\noracle attacks. RSA can only encrypt small amounts of data (key_size/8 - padding\noverhead), so it's typically used to encrypt a symmetric key, which then encrypts\nthe actual data (hybrid encryption).\n\n::\n\n    ┌─────────────────────────────────────────────────────────────────┐\n    │                    RSA-OAEP ENCRYPTION                          │\n    ├─────────────────────────────────────────────────────────────────┤\n    │                                                                 │\n    │   Plaintext ──► OAEP Padding ──► RSA ──► Ciphertext             │\n    │                      │            │                             │\n    │                      │      Public Key                          │\n    │                      │                                          │\n    │   OAEP uses:                                                    │\n    │   - MGF1 (Mask Generation Function)                             │\n    │   - Hash algorithm (SHA-256 recommended)                        │\n    │   - Optional label                                              │\n    │                                                                 │\n    │   Max plaintext size: key_bytes - 2*hash_bytes - 2              │\n    │   For 4096-bit key with SHA-256: 512 - 64 - 2 = 446 bytes       │\n    │                                                                 │\n    └─────────────────────────────────────────────────────────────────┘\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric import rsa, padding\n    from cryptography.hazmat.primitives import hashes\n\n    # Generate keys\n    private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)\n    public_key = private_key.public_key()\n\n    # Encrypt with public key (OAEP padding)\n    plaintext = b\"Secret message for RSA\"\n    ciphertext = public_key.encrypt(\n        plaintext,\n        padding.OAEP(\n            mgf=padding.MGF1(algorithm=hashes.SHA256()),\n            algorithm=hashes.SHA256(),\n            label=None,\n        ),\n    )\n\n    # Decrypt with private key\n    decrypted = private_key.decrypt(\n        ciphertext,\n        padding.OAEP(\n            mgf=padding.MGF1(algorithm=hashes.SHA256()),\n            algorithm=hashes.SHA256(),\n            label=None,\n        ),\n    )\n    assert decrypted == plaintext\n\nHybrid Encryption\n-----------------\n\nRSA has size limits and is slow. Hybrid encryption combines RSA's key distribution\nbenefits with symmetric encryption's speed: generate a random symmetric key, encrypt\nthe data with AES-GCM, then encrypt the symmetric key with RSA. The recipient\ndecrypts the symmetric key with their RSA private key, then decrypts the data.\n\n.. code-block:: python\n\n    import os\n    from cryptography.hazmat.primitives.asymmetric import rsa, padding\n    from cryptography.hazmat.primitives import hashes\n    from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n\n    def hybrid_encrypt(public_key, plaintext):\n        \"\"\"Encrypt data using hybrid RSA + AES-GCM.\"\"\"\n        # Generate random AES key and nonce\n        aes_key = AESGCM.generate_key(bit_length=256)\n        nonce = os.urandom(12)\n\n        # Encrypt data with AES-GCM\n        aesgcm = AESGCM(aes_key)\n        ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n\n        # Encrypt AES key with RSA-OAEP\n        encrypted_key = public_key.encrypt(\n            aes_key,\n            padding.OAEP(\n                mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                algorithm=hashes.SHA256(),\n                label=None,\n            ),\n        )\n\n        return encrypted_key, nonce, ciphertext\n\n    def hybrid_decrypt(private_key, encrypted_key, nonce, ciphertext):\n        \"\"\"Decrypt data using hybrid RSA + AES-GCM.\"\"\"\n        # Decrypt AES key with RSA\n        aes_key = private_key.decrypt(\n            encrypted_key,\n            padding.OAEP(\n                mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                algorithm=hashes.SHA256(),\n                label=None,\n            ),\n        )\n\n        # Decrypt data with AES-GCM\n        aesgcm = AESGCM(aes_key)\n        return aesgcm.decrypt(nonce, ciphertext, None)\n\n    # Usage\n    private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)\n    public_key = private_key.public_key()\n\n    message = b\"This message can be arbitrarily long!\" * 1000\n    encrypted_key, nonce, ciphertext = hybrid_encrypt(public_key, message)\n    decrypted = hybrid_decrypt(private_key, encrypted_key, nonce, ciphertext)\n    assert decrypted == message\n\nDigital Signatures: RSA-PSS\n---------------------------\n\nDigital signatures prove authenticity and integrity. The signer uses their private\nkey to create a signature; anyone with the public key can verify it. Use PSS\n(Probabilistic Signature Scheme) padding for RSA signatures—it's provably secure\nunlike PKCS#1 v1.5. For new applications, consider Ed25519 instead of RSA.\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric import rsa, padding\n    from cryptography.hazmat.primitives import hashes\n\n    # Generate keys\n    private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)\n    public_key = private_key.public_key()\n\n    message = b\"Message to sign\"\n\n    # Sign with private key (PSS padding)\n    signature = private_key.sign(\n        message,\n        padding.PSS(\n            mgf=padding.MGF1(hashes.SHA256()),\n            salt_length=padding.PSS.MAX_LENGTH,\n        ),\n        hashes.SHA256(),\n    )\n\n    # Verify with public key\n    try:\n        public_key.verify(\n            signature,\n            message,\n            padding.PSS(\n                mgf=padding.MGF1(hashes.SHA256()),\n                salt_length=padding.PSS.MAX_LENGTH,\n            ),\n            hashes.SHA256(),\n        )\n        print(\"Signature valid\")\n    except Exception:\n        print(\"Signature invalid!\")\n\nDigital Signatures: Ed25519\n---------------------------\n\nEd25519 is a modern signature algorithm based on elliptic curves. It offers\nexcellent security with small keys (32 bytes) and signatures (64 bytes), fast\noperations, and resistance to many implementation pitfalls. Prefer Ed25519 over\nRSA for new applications unless you need compatibility with legacy systems.\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey\n    from cryptography.hazmat.primitives import serialization\n\n    # Generate key pair\n    private_key = Ed25519PrivateKey.generate()\n    public_key = private_key.public_key()\n\n    # Sign message\n    message = b\"Message to sign\"\n    signature = private_key.sign(message)\n\n    # Verify signature\n    try:\n        public_key.verify(signature, message)\n        print(\"Signature valid\")\n    except Exception:\n        print(\"Signature invalid!\")\n\n    # Serialize keys\n    private_bytes = private_key.private_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PrivateFormat.PKCS8,\n        encryption_algorithm=serialization.NoEncryption(),\n    )\n\n    public_bytes = public_key.public_bytes(\n        encoding=serialization.Encoding.PEM,\n        format=serialization.PublicFormat.SubjectPublicKeyInfo,\n    )\n\n    # OpenSSH format for public key\n    public_ssh = public_key.public_bytes(\n        encoding=serialization.Encoding.OpenSSH,\n        format=serialization.PublicFormat.OpenSSH,\n    )\n    print(public_ssh.decode())  # ssh-ed25519 AAAA...\n\nElliptic Curve Diffie-Hellman (ECDH)\n------------------------------------\n\nECDH allows two parties to establish a shared secret over an insecure channel.\nEach party generates a key pair, exchanges public keys, and derives the same\nshared secret. Use X25519 (Curve25519) for modern applications—it's fast, secure,\nand resistant to timing attacks. The shared secret should be passed through a KDF\nbefore use as an encryption key.\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey\n    from cryptography.hazmat.primitives.kdf.hkdf import HKDF\n    from cryptography.hazmat.primitives import hashes\n\n    # Alice generates her key pair\n    alice_private = X25519PrivateKey.generate()\n    alice_public = alice_private.public_key()\n\n    # Bob generates his key pair\n    bob_private = X25519PrivateKey.generate()\n    bob_public = bob_private.public_key()\n\n    # Exchange public keys (over insecure channel)\n    # Alice computes shared secret\n    alice_shared = alice_private.exchange(bob_public)\n\n    # Bob computes shared secret\n    bob_shared = bob_private.exchange(alice_public)\n\n    # Both arrive at the same shared secret\n    assert alice_shared == bob_shared\n\n    # Derive encryption key from shared secret using HKDF\n    def derive_key(shared_secret, info):\n        hkdf = HKDF(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=None,\n            info=info,\n        )\n        return hkdf.derive(shared_secret)\n\n    encryption_key = derive_key(alice_shared, b\"encryption\")\n    mac_key = derive_key(alice_shared, b\"authentication\")\n\nPassword Hashing with Argon2\n----------------------------\n\nArgon2 is the winner of the Password Hashing Competition (2015) and the recommended\nalgorithm for password storage. It's memory-hard, making GPU/ASIC attacks expensive.\nUse the ``argon2-cffi`` library for Python. Store the full hash string (includes\nsalt and parameters) in your database.\n\n.. code-block:: python\n\n    # pip install argon2-cffi\n    from argon2 import PasswordHasher\n    from argon2.exceptions import VerifyMismatchError\n\n    ph = PasswordHasher(\n        time_cost=3,        # Number of iterations\n        memory_cost=65536,  # Memory usage in KiB (64 MB)\n        parallelism=4,      # Number of parallel threads\n    )\n\n    # Hash a password (for storage)\n    password = \"user-password\"\n    hash_str = ph.hash(password)\n    print(f\"Hash: {hash_str}\")\n    # $argon2id$v=19$m=65536,t=3,p=4$...\n\n    # Verify a password (during login)\n    try:\n        ph.verify(hash_str, password)\n        print(\"Password correct\")\n\n        # Check if rehash needed (parameters changed)\n        if ph.check_needs_rehash(hash_str):\n            new_hash = ph.hash(password)\n            # Update stored hash\n    except VerifyMismatchError:\n        print(\"Password incorrect\")\n"
  },
  {
    "path": "docs/notes/security/python-tls.rst",
    "content": ".. meta::\n    :description lang=en: Python TLS/SSL and X.509 certificate guide covering secure HTTPS servers, certificate generation, CSR creation, and certificate verification using the cryptography library\n    :keywords: Python, Python3, TLS, SSL, HTTPS, X.509, Certificate, CSR, Certificate Authority, Self-Signed, SSLContext, cryptography\n\n========================\nTLS/SSL and Certificates\n========================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nTransport Layer Security (TLS) provides encrypted, authenticated communication\nover networks. This guide covers creating secure HTTPS servers, generating\ncertificates, and proper TLS configuration in Python. We use the ``ssl`` module's\n``SSLContext`` API (not the deprecated ``wrap_socket``) and the ``cryptography``\nlibrary for certificate operations. Always use TLS 1.2 or 1.3—older versions have\nknown vulnerabilities.\n\n.. warning::\n\n    For production, always use certificates from a trusted Certificate Authority\n    (CA) like Let's Encrypt. Self-signed certificates are only for development\n    and testing. Never disable certificate verification in production code.\n\nSecure HTTPS Server\n-------------------\n\nCreate an HTTPS server using ``SSLContext`` with secure defaults. The context\nconfigures TLS version, cipher suites, and certificate verification. Always\nload both the certificate chain and private key. For production, use certificates\nfrom a real CA.\n\n.. code-block:: python\n\n    import ssl\n    from http.server import HTTPServer, SimpleHTTPRequestHandler\n\n    def create_secure_context(certfile, keyfile):\n        \"\"\"Create SSLContext with secure defaults.\"\"\"\n        # TLS 1.2+ only, secure ciphers\n        context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)\n        context.minimum_version = ssl.TLSVersion.TLSv1_2\n\n        # Load certificate and private key\n        context.load_cert_chain(certfile=certfile, keyfile=keyfile)\n\n        # Disable insecure options\n        context.options |= ssl.OP_NO_SSLv2\n        context.options |= ssl.OP_NO_SSLv3\n        context.options |= ssl.OP_NO_TLSv1\n        context.options |= ssl.OP_NO_TLSv1_1\n\n        return context\n\n    # Create server\n    host, port = \"localhost\", 8443\n    context = create_secure_context(\"cert.pem\", \"key.pem\")\n\n    httpd = HTTPServer((host, port), SimpleHTTPRequestHandler)\n    httpd.socket = context.wrap_socket(httpd.socket, server_side=True)\n\n    print(f\"Serving HTTPS on https://{host}:{port}\")\n    httpd.serve_forever()\n\nSecure HTTPS Client\n-------------------\n\nWhen making HTTPS requests, Python verifies certificates by default. For custom\nCA certificates or client authentication, configure an ``SSLContext``. Never\nset ``verify=False`` or disable hostname checking in production.\n\n.. code-block:: python\n\n    import ssl\n    import urllib.request\n\n    # Default secure context (verifies certificates)\n    context = ssl.create_default_context()\n\n    # Make HTTPS request\n    url = \"https://example.com\"\n    with urllib.request.urlopen(url, context=context) as response:\n        print(response.read().decode())\n\n    # Custom CA certificate (e.g., internal CA)\n    context = ssl.create_default_context()\n    context.load_verify_locations(\"internal-ca.pem\")\n\n    # Client certificate authentication (mTLS)\n    context = ssl.create_default_context()\n    context.load_cert_chain(certfile=\"client.pem\", keyfile=\"client-key.pem\")\n\n    # Using requests library (recommended for HTTP)\n    import requests\n\n    # Default (secure)\n    response = requests.get(\"https://example.com\")\n\n    # Custom CA\n    response = requests.get(\"https://internal.example.com\", verify=\"internal-ca.pem\")\n\n    # Client certificate\n    response = requests.get(\n        \"https://secure.example.com\",\n        cert=(\"client.pem\", \"client-key.pem\"),\n    )\n\nGenerate Self-Signed Certificate\n--------------------------------\n\nSelf-signed certificates are useful for development and testing. The certificate\nis signed by its own private key rather than a CA. Browsers will show warnings\nfor self-signed certificates. Use the ``cryptography`` library for certificate\ngeneration—it's more Pythonic than calling OpenSSL.\n\n.. code-block:: python\n\n    import ipaddress\n    from datetime import datetime, timedelta\n    from cryptography import x509\n    from cryptography.x509.oid import NameOID\n    from cryptography.hazmat.primitives import hashes\n    from cryptography.hazmat.primitives.asymmetric import rsa\n    from cryptography.hazmat.primitives import serialization\n\n    # Generate private key\n    private_key = rsa.generate_private_key(\n        public_exponent=65537,\n        key_size=4096,\n    )\n\n    # Certificate subject and issuer (same for self-signed)\n    subject = issuer = x509.Name([\n        x509.NameAttribute(NameOID.COUNTRY_NAME, \"US\"),\n        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, \"California\"),\n        x509.NameAttribute(NameOID.LOCALITY_NAME, \"San Francisco\"),\n        x509.NameAttribute(NameOID.ORGANIZATION_NAME, \"My Organization\"),\n        x509.NameAttribute(NameOID.COMMON_NAME, \"localhost\"),\n    ])\n\n    # Build certificate\n    cert = (\n        x509.CertificateBuilder()\n        .subject_name(subject)\n        .issuer_name(issuer)\n        .public_key(private_key.public_key())\n        .serial_number(x509.random_serial_number())\n        .not_valid_before(datetime.utcnow())\n        .not_valid_after(datetime.utcnow() + timedelta(days=365))\n        .add_extension(\n            x509.SubjectAlternativeName([\n                x509.DNSName(\"localhost\"),\n                x509.DNSName(\"*.localhost\"),\n                x509.IPAddress(ipaddress.IPv4Address(\"127.0.0.1\")),\n            ]),\n            critical=False,\n        )\n        .add_extension(\n            x509.BasicConstraints(ca=False, path_length=None),\n            critical=True,\n        )\n        .sign(private_key, hashes.SHA256())\n    )\n\n    # Save private key\n    with open(\"key.pem\", \"wb\") as f:\n        f.write(private_key.private_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PrivateFormat.TraditionalOpenSSL,\n            encryption_algorithm=serialization.NoEncryption(),\n        ))\n\n    # Save certificate\n    with open(\"cert.pem\", \"wb\") as f:\n        f.write(cert.public_bytes(serialization.Encoding.PEM))\n\n    print(\"Generated key.pem and cert.pem\")\n\nGenerate Certificate Signing Request (CSR)\n------------------------------------------\n\nA CSR is sent to a Certificate Authority to obtain a signed certificate. It\ncontains your public key and identity information. The CA verifies your identity\nand returns a signed certificate. Keep your private key secret—never send it\nto the CA.\n\n.. code-block:: python\n\n    from cryptography import x509\n    from cryptography.x509.oid import NameOID\n    from cryptography.hazmat.primitives import hashes\n    from cryptography.hazmat.primitives.asymmetric import rsa\n    from cryptography.hazmat.primitives import serialization\n\n    # Generate private key (keep this secret!)\n    private_key = rsa.generate_private_key(\n        public_exponent=65537,\n        key_size=4096,\n    )\n\n    # Build CSR\n    csr = (\n        x509.CertificateSigningRequestBuilder()\n        .subject_name(x509.Name([\n            x509.NameAttribute(NameOID.COUNTRY_NAME, \"US\"),\n            x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, \"California\"),\n            x509.NameAttribute(NameOID.LOCALITY_NAME, \"San Francisco\"),\n            x509.NameAttribute(NameOID.ORGANIZATION_NAME, \"My Company\"),\n            x509.NameAttribute(NameOID.COMMON_NAME, \"www.example.com\"),\n        ]))\n        .add_extension(\n            x509.SubjectAlternativeName([\n                x509.DNSName(\"www.example.com\"),\n                x509.DNSName(\"example.com\"),\n                x509.DNSName(\"api.example.com\"),\n            ]),\n            critical=False,\n        )\n        .sign(private_key, hashes.SHA256())\n    )\n\n    # Save private key\n    with open(\"private.key\", \"wb\") as f:\n        f.write(private_key.private_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PrivateFormat.TraditionalOpenSSL,\n            encryption_algorithm=serialization.NoEncryption(),\n        ))\n\n    # Save CSR (send this to CA)\n    with open(\"request.csr\", \"wb\") as f:\n        f.write(csr.public_bytes(serialization.Encoding.PEM))\n\n    print(\"Generated private.key and request.csr\")\n    print(\"Send request.csr to your CA, keep private.key secret!\")\n\nRead Certificate Information\n----------------------------\n\nParse and inspect X.509 certificates to view subject, issuer, validity period,\nextensions, and other attributes. Useful for debugging certificate issues.\n\n.. code-block:: python\n\n    from cryptography import x509\n    from cryptography.hazmat.primitives import serialization\n\n    # Load certificate from file\n    with open(\"cert.pem\", \"rb\") as f:\n        cert = x509.load_pem_x509_certificate(f.read())\n\n    # Basic information\n    print(f\"Subject: {cert.subject}\")\n    print(f\"Issuer: {cert.issuer}\")\n    print(f\"Serial: {cert.serial_number}\")\n    print(f\"Not Before: {cert.not_valid_before}\")\n    print(f\"Not After: {cert.not_valid_after}\")\n\n    # Get specific subject attributes\n    cn = cert.subject.get_attributes_for_oid(x509.oid.NameOID.COMMON_NAME)\n    if cn:\n        print(f\"Common Name: {cn[0].value}\")\n\n    # Check extensions\n    try:\n        san = cert.extensions.get_extension_for_oid(\n            x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME\n        )\n        print(f\"SANs: {san.value.get_values_for_type(x509.DNSName)}\")\n    except x509.ExtensionNotFound:\n        print(\"No SAN extension\")\n\n    # Check if self-signed\n    is_self_signed = cert.subject == cert.issuer\n    print(f\"Self-signed: {is_self_signed}\")\n\n    # Verify certificate signature (self-signed only)\n    if is_self_signed:\n        public_key = cert.public_key()\n        try:\n            # This verifies the certificate was signed by its own key\n            public_key.verify(\n                cert.signature,\n                cert.tbs_certificate_bytes,\n                cert.signature_algorithm_parameters,\n            )\n            print(\"Signature valid\")\n        except Exception as e:\n            print(f\"Signature invalid: {e}\")\n\nCreate a Certificate Authority\n------------------------------\n\nFor internal use, you can create your own CA to sign certificates. The CA\ncertificate is distributed to clients, which then trust any certificate signed\nby the CA. This is useful for development environments or internal services.\n\n.. code-block:: python\n\n    import ipaddress\n    from datetime import datetime, timedelta\n    from cryptography import x509\n    from cryptography.x509.oid import NameOID\n    from cryptography.hazmat.primitives import hashes\n    from cryptography.hazmat.primitives.asymmetric import rsa\n    from cryptography.hazmat.primitives import serialization\n\n    def create_ca():\n        \"\"\"Create a Certificate Authority.\"\"\"\n        # Generate CA private key\n        ca_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=4096,\n        )\n\n        # CA certificate (self-signed)\n        ca_name = x509.Name([\n            x509.NameAttribute(NameOID.COUNTRY_NAME, \"US\"),\n            x509.NameAttribute(NameOID.ORGANIZATION_NAME, \"My Internal CA\"),\n            x509.NameAttribute(NameOID.COMMON_NAME, \"My Internal Root CA\"),\n        ])\n\n        ca_cert = (\n            x509.CertificateBuilder()\n            .subject_name(ca_name)\n            .issuer_name(ca_name)\n            .public_key(ca_key.public_key())\n            .serial_number(x509.random_serial_number())\n            .not_valid_before(datetime.utcnow())\n            .not_valid_after(datetime.utcnow() + timedelta(days=3650))  # 10 years\n            .add_extension(\n                x509.BasicConstraints(ca=True, path_length=0),\n                critical=True,\n            )\n            .add_extension(\n                x509.KeyUsage(\n                    digital_signature=True,\n                    key_cert_sign=True,\n                    crl_sign=True,\n                    key_encipherment=False,\n                    content_commitment=False,\n                    data_encipherment=False,\n                    key_agreement=False,\n                    encipher_only=False,\n                    decipher_only=False,\n                ),\n                critical=True,\n            )\n            .sign(ca_key, hashes.SHA256())\n        )\n\n        return ca_key, ca_cert\n\n    def sign_csr(ca_key, ca_cert, csr_path, days=365):\n        \"\"\"Sign a CSR with the CA.\"\"\"\n        with open(csr_path, \"rb\") as f:\n            csr = x509.load_pem_x509_csr(f.read())\n\n        cert = (\n            x509.CertificateBuilder()\n            .subject_name(csr.subject)\n            .issuer_name(ca_cert.subject)\n            .public_key(csr.public_key())\n            .serial_number(x509.random_serial_number())\n            .not_valid_before(datetime.utcnow())\n            .not_valid_after(datetime.utcnow() + timedelta(days=days))\n            .add_extension(\n                x509.BasicConstraints(ca=False, path_length=None),\n                critical=True,\n            )\n        )\n\n        # Copy extensions from CSR\n        for ext in csr.extensions:\n            cert = cert.add_extension(ext.value, ext.critical)\n\n        return cert.sign(ca_key, hashes.SHA256())\n\n    # Create CA\n    ca_key, ca_cert = create_ca()\n\n    # Save CA files\n    with open(\"ca-key.pem\", \"wb\") as f:\n        f.write(ca_key.private_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PrivateFormat.TraditionalOpenSSL,\n            encryption_algorithm=serialization.BestAvailableEncryption(b\"ca-password\"),\n        ))\n\n    with open(\"ca-cert.pem\", \"wb\") as f:\n        f.write(ca_cert.public_bytes(serialization.Encoding.PEM))\n\n    print(\"Created ca-key.pem (keep secret!) and ca-cert.pem (distribute to clients)\")\n\nTLS Version and Cipher Information\n----------------------------------\n\nInspect TLS connection details including protocol version, cipher suite, and\npeer certificate. Useful for debugging and security auditing.\n\n.. code-block:: python\n\n    import ssl\n    import socket\n\n    def get_tls_info(hostname, port=443):\n        \"\"\"Get TLS connection information for a host.\"\"\"\n        context = ssl.create_default_context()\n\n        with socket.create_connection((hostname, port)) as sock:\n            with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n                print(f\"TLS Version: {ssock.version()}\")\n                print(f\"Cipher: {ssock.cipher()}\")\n\n                # Peer certificate\n                cert = ssock.getpeercert()\n                print(f\"Subject: {dict(x[0] for x in cert['subject'])}\")\n                print(f\"Issuer: {dict(x[0] for x in cert['issuer'])}\")\n                print(f\"Not Before: {cert['notBefore']}\")\n                print(f\"Not After: {cert['notAfter']}\")\n\n                # Subject Alternative Names\n                if 'subjectAltName' in cert:\n                    sans = [x[1] for x in cert['subjectAltName']]\n                    print(f\"SANs: {sans}\")\n\n    get_tls_info(\"www.google.com\")\n\nCertificate Pinning\n-------------------\n\nCertificate pinning adds an extra layer of security by verifying the server's\ncertificate matches an expected value. This prevents attacks using fraudulently\nissued certificates. Pin the public key (SPKI) rather than the certificate to\nsurvive certificate renewals.\n\n.. code-block:: python\n\n    import ssl\n    import socket\n    import hashlib\n    from cryptography import x509\n    from cryptography.hazmat.primitives import serialization\n\n    def get_certificate_pin(hostname, port=443):\n        \"\"\"Get the SPKI pin for a certificate.\"\"\"\n        context = ssl.create_default_context()\n\n        with socket.create_connection((hostname, port)) as sock:\n            with context.wrap_socket(sock, server_hostname=hostname) as ssock:\n                # Get certificate in DER format\n                der_cert = ssock.getpeercert(binary_form=True)\n\n        # Parse certificate\n        cert = x509.load_der_x509_certificate(der_cert)\n\n        # Get public key in DER format (SPKI)\n        spki = cert.public_key().public_bytes(\n            encoding=serialization.Encoding.DER,\n            format=serialization.PublicFormat.SubjectPublicKeyInfo,\n        )\n\n        # SHA-256 hash of SPKI\n        pin = hashlib.sha256(spki).digest()\n        return pin\n\n    def verify_pin(hostname, expected_pin, port=443):\n        \"\"\"Verify certificate matches expected pin.\"\"\"\n        actual_pin = get_certificate_pin(hostname, port)\n        if actual_pin != expected_pin:\n            raise ssl.SSLError(f\"Certificate pin mismatch for {hostname}\")\n        print(f\"Pin verified for {hostname}\")\n\n    # Get pin (do this once, store the result)\n    pin = get_certificate_pin(\"www.google.com\")\n    print(f\"Pin (base64): {__import__('base64').b64encode(pin).decode()}\")\n\n    # Verify pin on subsequent connections\n    verify_pin(\"www.google.com\", pin)\n"
  },
  {
    "path": "docs/notes/security/python-vulnerability.rst",
    "content": ".. meta::\n    :description lang=en: Common Python security vulnerabilities and why legacy cryptographic patterns are insecure, with attack demonstrations\n    :keywords: Python, Python3, security, vulnerability, PyCrypto, padding oracle, PKCS1 v1.5, AES-CBC, timing attack, insecure\n\n===============================\nCommon Security Vulnerabilities\n===============================\n\n:Source: `src/security/vulnerability_.py <https://github.com/crazyguitar/pysheeet/blob/master/src/security/vulnerability_.py>`_\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nIntroduction\n------------\n\nThis page explains why certain cryptographic patterns are insecure and how\nattackers can exploit them. Understanding these vulnerabilities helps you\nrecognize dangerous code in legacy systems and avoid introducing similar\nweaknesses in new projects. For secure implementations, see\n:doc:`python-crypto` and :doc:`python-tls`.\n\nAES-CBC Without Authentication (Padding Oracle)\n-----------------------------------------------\n\nAES-CBC mode encrypts data but provides no integrity protection. An attacker\nwho can modify ciphertext and observe whether decryption succeeds can recover\nthe plaintext byte-by-byte through a **padding oracle attack**. This attack\nexploits the PKCS#7 padding validation to leak information.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: AES-CBC without authentication\n    from Crypto.Cipher import AES\n\n    def encrypt_cbc(key, iv, plaintext):\n        cipher = AES.new(key, AES.MODE_CBC, iv)\n        # Manual PKCS#7 padding\n        pad_len = 16 - (len(plaintext) % 16)\n        padded = plaintext + bytes([pad_len] * pad_len)\n        return cipher.encrypt(padded)\n\n    def decrypt_cbc(key, iv, ciphertext):\n        cipher = AES.new(key, AES.MODE_CBC, iv)\n        padded = cipher.decrypt(ciphertext)\n        # VULNERABLE: Padding validation leaks information\n        pad_len = padded[-1]\n        if not all(b == pad_len for b in padded[-pad_len:]):\n            raise ValueError(\"Invalid padding\")  # Oracle!\n        return padded[:-pad_len]\n\n**Why It's Vulnerable:**\n\nThe padding validation error reveals whether the decrypted padding is valid.\nAn attacker can:\n\n1. Intercept a ciphertext block\n2. Modify the previous block's last byte\n3. Submit to the server and observe if padding error occurs\n4. Repeat 256 times to determine one plaintext byte\n5. Continue for all bytes\n\n.. code-block:: python\n\n    # Simplified padding oracle attack concept\n    def padding_oracle_attack(ciphertext, oracle_func):\n        \"\"\"\n        oracle_func returns True if padding is valid, False otherwise.\n        This leaks enough information to decrypt without the key.\n        \"\"\"\n        # For each block, XOR previous block to control decrypted value\n        # Try all 256 values until padding is valid\n        # Valid padding reveals the intermediate state\n        # XOR with known value gives plaintext\n        pass  # Full implementation is complex but well-documented\n\n**Secure Alternative:** Use AES-GCM which provides authenticated encryption:\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.ciphers.aead import AESGCM\n\n    key = AESGCM.generate_key(bit_length=256)\n    aesgcm = AESGCM(key)\n    nonce = os.urandom(12)\n    # Encryption includes authentication tag - tampering is detected\n    ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)\n\nRSA PKCS#1 v1.5 Padding (Bleichenbacher Attack)\n-----------------------------------------------\n\nRSA with PKCS#1 v1.5 padding is vulnerable to the **Bleichenbacher attack**\n(also called the \"million message attack\"). If a server reveals whether\ndecryption produced valid PKCS#1 v1.5 padding, an attacker can decrypt\nmessages or forge signatures.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: PKCS#1 v1.5 padding\n    from Crypto.Cipher import PKCS1_v1_5\n    from Crypto.PublicKey import RSA\n\n    def decrypt_rsa_v15(private_key_pem, ciphertext):\n        key = RSA.import_key(private_key_pem)\n        cipher = PKCS1_v1_5.new(key)\n        # VULNERABLE: Different errors for padding vs other failures\n        plaintext = cipher.decrypt(ciphertext, sentinel=None)\n        if plaintext is None:\n            raise ValueError(\"Decryption failed\")  # Oracle!\n        return plaintext\n\n**Why It's Vulnerable:**\n\nPKCS#1 v1.5 padding has a specific structure: ``0x00 0x02 [random] 0x00 [message]``.\nWhen decryption fails due to invalid padding vs. other reasons, the different\nerror responses create an oracle. An attacker can:\n\n1. Choose a ciphertext ``c``\n2. Compute ``c' = c * s^e mod n`` for various ``s`` values\n3. Submit ``c'`` and check if padding is valid\n4. Use valid/invalid responses to narrow down the plaintext\n\n**Secure Alternative:** Use RSA-OAEP padding:\n\n.. code-block:: python\n\n    from cryptography.hazmat.primitives.asymmetric import padding\n    from cryptography.hazmat.primitives import hashes\n\n    ciphertext = public_key.encrypt(\n        plaintext,\n        padding.OAEP(\n            mgf=padding.MGF1(algorithm=hashes.SHA256()),\n            algorithm=hashes.SHA256(),\n            label=None\n        )\n    )\n\nTiming Attacks on String Comparison\n-----------------------------------\n\nComparing secrets using ``==`` is vulnerable to **timing attacks**. The\ncomparison stops at the first different byte, so the time taken reveals\nhow many bytes match. An attacker can guess secrets byte-by-byte.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: Regular string comparison\n    def verify_token(user_token, stored_token):\n        return user_token == stored_token  # Timing leak!\n\n    def verify_signature(computed_sig, provided_sig):\n        return computed_sig == provided_sig  # Timing leak!\n\n**Why It's Vulnerable:**\n\n.. code-block:: python\n\n    # Demonstration of timing difference\n    import time\n\n    secret = b\"correct_secret_token_here\"\n\n    def insecure_compare(a, b):\n        if len(a) != len(b):\n            return False\n        for x, y in zip(a, b):\n            if x != y:\n                return False  # Returns early - timing leak\n        return True\n\n    # Attacker measures time for different guesses:\n    # \"a...\" - fails fast (wrong first byte)\n    # \"c...\" - takes slightly longer (first byte correct)\n    # \"co...\" - even longer (two bytes correct)\n    # Eventually recovers entire secret\n\n**Secure Alternative:** Use constant-time comparison:\n\n.. code-block:: python\n\n    import hmac\n\n    def verify_token(user_token, stored_token):\n        # hmac.compare_digest runs in constant time\n        return hmac.compare_digest(user_token, stored_token)\n\nWeak Random Number Generation\n-----------------------------\n\nUsing ``random`` module for security purposes is dangerous. It uses a\ndeterministic PRNG (Mersenne Twister) that can be predicted if an attacker\nobserves enough outputs.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: Using random for security\n    import random\n    import string\n\n    def generate_token():\n        # VULNERABLE: Predictable after ~624 outputs observed\n        chars = string.ascii_letters + string.digits\n        return ''.join(random.choice(chars) for _ in range(32))\n\n    def generate_session_id():\n        # VULNERABLE: Can be predicted\n        return random.randint(0, 2**64)\n\n**Why It's Vulnerable:**\n\nMersenne Twister has 624 32-bit state values. After observing 624 outputs,\nan attacker can reconstruct the internal state and predict all future outputs.\n\n.. code-block:: python\n\n    # Mersenne Twister state recovery (conceptual)\n    # After collecting 624 consecutive 32-bit outputs,\n    # attacker can \"untemper\" them to recover internal state\n    # Then predict all future random() calls\n\n**Secure Alternative:** Use ``secrets`` module:\n\n.. code-block:: python\n\n    import secrets\n\n    def generate_token():\n        return secrets.token_urlsafe(32)\n\n    def generate_session_id():\n        return secrets.token_hex(16)\n\nHardcoded Secrets and Keys\n--------------------------\n\nEmbedding secrets in source code exposes them through version control,\nlogs, error messages, and decompilation.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: Hardcoded secrets\n    API_KEY = \"sk_live_abc123xyz789\"  # Exposed in git history!\n    DB_PASSWORD = \"super_secret_password\"\n    ENCRYPTION_KEY = b\"0123456789abcdef\"\n\n    def connect_to_api():\n        return requests.get(url, headers={\"Authorization\": API_KEY})\n\n**Why It's Vulnerable:**\n\n- Secrets in git history persist even after deletion\n- Error messages may include variable values\n- Compiled Python (.pyc) can be decompiled\n- Logs may capture the values\n\n**Secure Alternative:** Use environment variables or secret managers:\n\n.. code-block:: python\n\n    import os\n\n    API_KEY = os.environ.get(\"API_KEY\")\n    if not API_KEY:\n        raise RuntimeError(\"API_KEY environment variable required\")\n\n    # Or use a secrets manager\n    from aws_secretsmanager import get_secret\n    secrets = get_secret(\"my-app/production\")\n\nSQL Injection\n-------------\n\nBuilding SQL queries with string concatenation allows attackers to inject\nmalicious SQL commands.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: String concatenation in SQL\n    def get_user(username):\n        query = f\"SELECT * FROM users WHERE username = '{username}'\"\n        cursor.execute(query)  # SQL injection!\n        return cursor.fetchone()\n\n    # Attacker input: \"admin' OR '1'='1\"\n    # Results in: SELECT * FROM users WHERE username = 'admin' OR '1'='1'\n    # Returns all users!\n\n    # Worse: \"admin'; DROP TABLE users; --\"\n    # Deletes the entire table!\n\n**Secure Alternative:** Use parameterized queries:\n\n.. code-block:: python\n\n    def get_user(username):\n        query = \"SELECT * FROM users WHERE username = ?\"\n        cursor.execute(query, (username,))  # Safe - parameterized\n        return cursor.fetchone()\n\n    # Or with SQLAlchemy\n    from sqlalchemy import select\n    stmt = select(User).where(User.username == username)\n\nCommand Injection\n-----------------\n\nPassing user input to shell commands allows arbitrary command execution.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: Shell injection\n    import os\n    import subprocess\n\n    def ping_host(hostname):\n        os.system(f\"ping -c 1 {hostname}\")  # Command injection!\n\n    # Attacker input: \"google.com; rm -rf /\"\n    # Executes: ping -c 1 google.com; rm -rf /\n\n    def get_file_info(filename):\n        # Also vulnerable with subprocess and shell=True\n        result = subprocess.run(\n            f\"file {filename}\",\n            shell=True,  # DANGEROUS\n            capture_output=True\n        )\n\n**Secure Alternative:** Avoid shell, use argument lists:\n\n.. code-block:: python\n\n    import subprocess\n    import shlex\n\n    def ping_host(hostname):\n        # Validate input first\n        if not hostname.replace('.', '').replace('-', '').isalnum():\n            raise ValueError(\"Invalid hostname\")\n        # Use list of arguments, not shell string\n        subprocess.run([\"ping\", \"-c\", \"1\", hostname], check=True)\n\n    def get_file_info(filename):\n        # shell=False (default) prevents injection\n        result = subprocess.run(\n            [\"file\", filename],\n            capture_output=True,\n            check=True\n        )\n\nInsecure Deserialization (Pickle)\n---------------------------------\n\nPython's ``pickle`` module can execute arbitrary code during deserialization.\nNever unpickle data from untrusted sources.\n\n**Vulnerable Code:**\n\n.. code-block:: python\n\n    # INSECURE: Unpickling untrusted data\n    import pickle\n\n    def load_user_data(data):\n        return pickle.loads(data)  # Remote code execution!\n\n    # Attacker can craft malicious pickle:\n    import os\n    class Exploit:\n        def __reduce__(self):\n            return (os.system, (\"rm -rf /\",))\n\n    malicious = pickle.dumps(Exploit())\n    # When unpickled, executes: os.system(\"rm -rf /\")\n\n**Secure Alternative:** Use safe formats like JSON:\n\n.. code-block:: python\n\n    import json\n\n    def load_user_data(data):\n        return json.loads(data)  # Safe - no code execution\n\n    # If you must use pickle, restrict classes\n    import pickle\n    import io\n\n    class RestrictedUnpickler(pickle.Unpickler):\n        ALLOWED_CLASSES = {('mymodule', 'SafeClass')}\n\n        def find_class(self, module, name):\n            if (module, name) not in self.ALLOWED_CLASSES:\n                raise pickle.UnpicklingError(f\"Forbidden: {module}.{name}\")\n            return super().find_class(module, name)\n\nSummary: Legacy vs Modern\n-------------------------\n\n+------------------------+---------------------------+---------------------------+\n| Vulnerability          | Legacy (Insecure)         | Modern (Secure)           |\n+========================+===========================+===========================+\n| Symmetric Encryption   | AES-CBC without auth      | AES-GCM                   |\n+------------------------+---------------------------+---------------------------+\n| RSA Padding            | PKCS#1 v1.5               | OAEP                      |\n+------------------------+---------------------------+---------------------------+\n| Secret Comparison      | ``==``                    | ``hmac.compare_digest``   |\n+------------------------+---------------------------+---------------------------+\n| Random Numbers         | ``random``                | ``secrets``               |\n+------------------------+---------------------------+---------------------------+\n| Password Hashing       | MD5, SHA1                 | Argon2, bcrypt            |\n+------------------------+---------------------------+---------------------------+\n| Crypto Library         | PyCrypto                  | ``cryptography``          |\n+------------------------+---------------------------+---------------------------+\n| SSL/TLS                | ``ssl.wrap_socket``       | ``SSLContext``            |\n+------------------------+---------------------------+---------------------------+\n"
  },
  {
    "path": "requirements.txt",
    "content": "coverage==7.13.4\ncryptography==46.0.5\nargon2-cffi==25.1.0\ncffi==2.0.0\nSQLAlchemy==2.0.48\nbandit==1.9.4\ncoveralls==4.1.0\nFlask==3.1.3\nFlask-SSLify==0.1.5\nFlask-Testing==0.8.1\nFlask-SeaSurf==2.0.0\nflask-talisman==1.1.0\ngunicorn==25.1.0\npycodestyle==2.14.0\npydocstyle==6.3.0\npytest==9.0.2\nrequests==2.32.5\nSphinx==8.1.3\nWerkzeug==3.1.6\nsetuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability\njinja2>=3.1.3 # not directly required, pinned by Snyk to avoid a vulnerability\nzipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability\nmyst-parser==4.0.1\nsphinx-copybutton==0.5.2\nsphinx_design==0.6.1\nsphinx-book-theme==1.1.4\nurllib3>=2.6.3 # not directly required, pinned by Snyk to avoid a vulnerability\n"
  },
  {
    "path": "runtime.txt",
    "content": "python-3.12\n"
  },
  {
    "path": "skills/py/SKILL.md",
    "content": "---\nname: py\ndescription: Comprehensive Python programming reference covering syntax, concurrency, networking, databases, ML/LLM development, and HPC. Use for: Python questions, debugging, performance optimization, async patterns, library examples, code review, best practices, MLOps workflows, distributed computing, security implementations, and any Python development tasks.\n---\n\n# Python Cheat Sheets (/py)\n\nHelp users write functional, correct Python code and answer Python questions by fetching proven patterns and examples from pythonsheets.com.\n\n## How It Works\n\nWhen a user asks a Python question or wants to write a Python script:\n\n1. Look up the relevant topic(s) in [Structure](references/structure.md) to find the matching URL(s)\n2. **Always fetch** the URL(s) using WebFetch to get real examples and patterns from the site\n3. Use the fetched content to:\n   - **Write code**: Apply the patterns to produce functional, correct code that solves the user's task\n   - **Answer questions**: Provide thorough explanations backed by the examples and information from the site\n4. Follow the [Guidelines](references/guidelines.md) for code quality\n\n## Key Principle\n\n**Functionality first, cleanliness second.** The code must work correctly and handle the task properly. Fetching from pythonsheets.com ensures solutions use battle-tested patterns rather than guessing. The site contains rich examples covering edge cases, common pitfalls, and practical usage that go beyond basic documentation.\n\n## Coverage Areas\n\n**Core:** Syntax, typing, OOP, functions, data structures, sets, heap, regex, unicode\n**System:** File I/O, datetime, OS interfaces\n**Concurrency:** Threading, multiprocessing, asyncio\n**Network:** Sockets, SSL/TLS, SSH, async I/O, packet sniffing\n**Database:** SQLAlchemy ORM, queries, transactions\n**Security:** Cryptography, TLS, vulnerabilities\n**Extensions:** C/C++ integration, pybind11, Cython\n**ML/LLM:** PyTorch, Megatron, distributed training, inference, serving, benchmarking\n**HPC:** Slurm, cluster computing, job scheduling, EFA monitoring, NCCL\n**Appendix:** Walrus operator, GDB debugging, disaggregated prefill/decode\n\n## References\n\n- **[Structure](references/structure.md)** - Topic-to-URL map for fetching examples\n- **[Guidelines](references/guidelines.md)** - Code quality standards to apply after ensuring correctness\n\n## Examples\n\n- \"How does asyncio work?\" → Fetch https://www.pythonsheets.com/notes/asyncio/python-asyncio-guide.html and explain with the site's examples\n- \"Write a socket server\" → Fetch https://www.pythonsheets.com/notes/network/python-socket-server.html, use the patterns to write a working server\n- \"What's the walrus operator?\" → Fetch https://www.pythonsheets.com/notes/appendix/python-walrus.html and explain with practical examples\n- \"Set up Megatron distributed training\" → Fetch https://www.pythonsheets.com/notes/llm/megatron.html, use the patterns to write a correct training script\n"
  },
  {
    "path": "skills/py/references/guidelines.md",
    "content": "# Python Development Guidelines\n\nAlways fetch relevant examples from pythonsheets.com first to ensure correctness, then apply these guidelines when writing code.\n\n## Choosing the Right Approach\n\n- **Concurrency model**: Use `asyncio` for I/O-bound tasks with many connections, `threading` for simpler I/O-bound work, `multiprocessing` for CPU-bound work\n- **Data structures**: Use `set` for membership tests, `heapq` for priority queues, `deque` for FIFO queues, `defaultdict` to avoid key existence checks\n- **Database access**: Use SQLAlchemy ORM for complex queries, parameterized queries for raw SQL — never string-interpolate SQL\n- **File operations**: Use `pathlib.Path` instead of `os.path` for cleaner, cross-platform file handling\n- **Network programming**: Use `asyncio` streams for high-concurrency servers, `socket` for low-level control, `ssl` context for secure connections\n\n## Writing Correct Code\n\n- Handle errors at the right level — catch specific exceptions where you can recover, let others propagate\n- Use context managers (`with`) for files, connections, locks, and any resource that needs cleanup\n- Avoid mutable default arguments (`def f(x=[])`) — use `None` and initialize inside the function\n- Use `logging` instead of `print` for diagnostics — it supports levels, formatting, and output routing\n- Validate inputs at system boundaries (user input, external APIs), trust internal code\n\n## Writing Clean Code\n\n- Use type hints for function signatures to clarify intent and enable static analysis\n- Use f-strings for string formatting\n- Use `dataclasses` or `NamedTuple` for structured data instead of raw dicts or tuples\n- Use `enum.Enum` for fixed sets of values\n- Prefer early returns over deep nesting\n- Keep functions short and focused on a single responsibility\n\n## Performance Considerations\n\n- Profile before optimizing — use `cProfile`, `timeit`, or `line_profiler` to identify actual bottlenecks\n- Consider memory usage: generators for large sequences, `__slots__` for memory-heavy classes\n- Use connection pooling for database and network connections\n- Use appropriate caching (`functools.lru_cache`, `functools.cache`) for expensive pure functions\n- For CPU-bound hot paths, consider C extensions via `ctypes`, `pybind11`, or Cython\n\n## Security Practices\n\n- Use `secrets` module for tokens and passwords, not `random`\n- Always use parameterized queries to prevent SQL injection\n- Use `ssl.create_default_context()` for TLS — don't disable certificate verification\n- Avoid `pickle` for untrusted data — use `json` or other safe serialization\n- Store secrets in environment variables or secret managers, never in code\n\n## Related Documentation\n\nThis skill is based on the comprehensive Python reference available at https://www.pythonsheets.com/ which includes working code snippets, performance benchmarks, real-world patterns, and integration guides. The reference is continuously updated with the latest Python features and best practices.\n"
  },
  {
    "path": "skills/py/references/structure.md",
    "content": "# Python Topics Reference Map\n\nComplete reference guide organized by topic, with direct links to live documentation.\n\n## Core Python\n- **Basics** → https://www.pythonsheets.com/notes/basic/python-basic.html\n- **Type Hints** → https://www.pythonsheets.com/notes/basic/python-typing.html\n- **Classes & OOP** → https://www.pythonsheets.com/notes/basic/python-object.html\n- **Functions** → https://www.pythonsheets.com/notes/basic/python-func.html\n- **Lists** → https://www.pythonsheets.com/notes/basic/python-list.html\n- **Dictionaries** → https://www.pythonsheets.com/notes/basic/python-dict.html\n- **Sets** → https://www.pythonsheets.com/notes/basic/python-set.html\n- **Generators** → https://www.pythonsheets.com/notes/basic/python-generator.html\n- **Heap** → https://www.pythonsheets.com/notes/basic/python-heap.html\n- **Regular Expressions** → https://www.pythonsheets.com/notes/basic/python-rexp.html\n- **Unicode** → https://www.pythonsheets.com/notes/basic/python-unicode.html\n- **__future__** → https://www.pythonsheets.com/notes/basic/python-future.html\n\n## What's New\n- **What's New in Python 3** → https://www.pythonsheets.com/notes/python-new-py3.html\n\n## System Programming\n- **Date/Time** → https://www.pythonsheets.com/notes/os/python-date.html\n- **File I/O** → https://www.pythonsheets.com/notes/os/python-io.html\n- **OS Interfaces** → https://www.pythonsheets.com/notes/os/python-os.html\n\n## Concurrency\n- **Threading** → https://www.pythonsheets.com/notes/concurrency/python-threading.html\n- **Multiprocessing** → https://www.pythonsheets.com/notes/concurrency/python-multiprocessing.html\n- **Futures** → https://www.pythonsheets.com/notes/concurrency/python-futures.html\n\n## Asyncio\n- **Async Guide** → https://www.pythonsheets.com/notes/asyncio/python-asyncio-guide.html\n- **Async Basics** → https://www.pythonsheets.com/notes/asyncio/python-asyncio-basic.html\n- **Async Servers** → https://www.pythonsheets.com/notes/asyncio/python-asyncio-server.html\n- **Async Advanced** → https://www.pythonsheets.com/notes/asyncio/python-asyncio-advanced.html\n\n## Network Programming\n- **Socket Basics** → https://www.pythonsheets.com/notes/network/python-socket.html\n- **Socket Servers** → https://www.pythonsheets.com/notes/network/python-socket-server.html\n- **Async Sockets** → https://www.pythonsheets.com/notes/network/python-socket-async.html\n- **Packet Sniffer** → https://www.pythonsheets.com/notes/network/python-socket-sniffer.html\n- **SSL/TLS** → https://www.pythonsheets.com/notes/network/python-socket-ssl.html\n- **SSH** → https://www.pythonsheets.com/notes/network/python-ssh.html\n\n## Database\n- **SQLAlchemy Basics** → https://www.pythonsheets.com/notes/database/python-sqlalchemy.html\n- **SQLAlchemy ORM** → https://www.pythonsheets.com/notes/database/python-sqlalchemy-orm.html\n- **Query Patterns** → https://www.pythonsheets.com/notes/database/python-sqlalchemy-query.html\n\n## Security\n- **Cryptography** → https://www.pythonsheets.com/notes/security/python-crypto.html\n- **TLS/SSL** → https://www.pythonsheets.com/notes/security/python-tls.html\n- **Vulnerabilities** → https://www.pythonsheets.com/notes/security/python-vulnerability.html\n\n## C/C++ Extensions\n- **ctypes** → https://www.pythonsheets.com/notes/extension/python-ctypes.html\n- **C API** → https://www.pythonsheets.com/notes/extension/python-capi.html\n- **Modern Extensions** → https://www.pythonsheets.com/notes/extension/python-cext-modern.html\n- **C++ from Python** → https://www.pythonsheets.com/notes/extension/cpp-from-python.html\n\n## LLM & Machine Learning\n- **PyTorch** → https://www.pythonsheets.com/notes/llm/pytorch.html\n- **Megatron / Distributed Training** → https://www.pythonsheets.com/notes/llm/megatron.html\n- **LLM Serving** → https://www.pythonsheets.com/notes/llm/llm-serving.html\n- **LLM Benchmarking** → https://www.pythonsheets.com/notes/llm/llm-bench.html\n\n## High-Performance Computing\n- **Slurm HPC** → https://www.pythonsheets.com/notes/hpc/slurm.html\n\n## Appendix\n- **Disaggregated Prefill/Decode** → https://www.pythonsheets.com/notes/appendix/disaggregated-prefill-decode.html\n- **Megatron EFA Monitoring** → https://www.pythonsheets.com/notes/appendix/megatron-efa-monitoring.html\n- **NCCL GIN** → https://www.pythonsheets.com/notes/appendix/nccl-gin.html\n- **Walrus Operator** → https://www.pythonsheets.com/notes/appendix/python-walrus.html\n- **Python GDB Debugging** → https://www.pythonsheets.com/notes/appendix/python-gdb.html"
  },
  {
    "path": "src/basic/asyncio_.py",
    "content": "\"\"\"Tests for asyncio examples.\"\"\"\n\nimport asyncio\nimport pytest\n\n\nclass TestAsyncioBasics:\n    \"\"\"Test basic asyncio operations.\"\"\"\n\n    def test_asyncio_run(self):\n        \"\"\"Test basic coroutine execution.\"\"\"\n\n        async def hello():\n            return \"hello\"\n\n        result = asyncio.run(hello())\n        assert result == \"hello\"\n\n    def test_create_task(self):\n        \"\"\"Test task creation and execution.\"\"\"\n\n        async def compute(x):\n            await asyncio.sleep(0.01)\n            return x * 2\n\n        async def main():\n            task = asyncio.create_task(compute(5))\n            return await task\n\n        result = asyncio.run(main())\n        assert result == 10\n\n    def test_gather(self):\n        \"\"\"Test gathering multiple coroutines.\"\"\"\n\n        async def fetch(n):\n            await asyncio.sleep(0.01)\n            return n\n\n        async def main():\n            return await asyncio.gather(fetch(1), fetch(2), fetch(3))\n\n        results = asyncio.run(main())\n        assert results == [1, 2, 3]\n\n    def test_wait_for_timeout(self):\n        \"\"\"Test timeout handling.\"\"\"\n\n        async def slow():\n            await asyncio.sleep(10)\n\n        async def main():\n            await asyncio.wait_for(slow(), timeout=0.01)\n\n        with pytest.raises(asyncio.TimeoutError):\n            asyncio.run(main())\n\n    def test_wait_first_completed(self):\n        \"\"\"Test waiting for first completed task.\"\"\"\n\n        async def fast():\n            await asyncio.sleep(0.01)\n            return \"fast\"\n\n        async def slow():\n            await asyncio.sleep(1)\n            return \"slow\"\n\n        async def main():\n            tasks = [asyncio.create_task(fast()), asyncio.create_task(slow())]\n            done, pending = await asyncio.wait(\n                tasks, return_when=asyncio.FIRST_COMPLETED\n            )\n            for task in pending:\n                task.cancel()\n            return len(done), len(pending)\n\n        done_count, pending_count = asyncio.run(main())\n        assert done_count == 1\n        assert pending_count == 1\n\n\nclass TestAsyncIterator:\n    \"\"\"Test async iterators.\"\"\"\n\n    def test_async_iterator(self):\n        \"\"\"Test custom async iterator.\"\"\"\n\n        class AsyncRange:\n            def __init__(self, stop):\n                self.current = 0\n                self.stop = stop\n\n            def __aiter__(self):\n                return self\n\n            async def __anext__(self):\n                if self.current >= self.stop:\n                    raise StopAsyncIteration\n                await asyncio.sleep(0.001)\n                value = self.current\n                self.current += 1\n                return value\n\n        async def main():\n            results = []\n            async for num in AsyncRange(3):\n                results.append(num)\n            return results\n\n        results = asyncio.run(main())\n        assert results == [0, 1, 2]\n\n    def test_async_generator(self):\n        \"\"\"Test async generator.\"\"\"\n\n        async def async_range(stop):\n            for i in range(stop):\n                await asyncio.sleep(0.001)\n                yield i\n\n        async def main():\n            return [x async for x in async_range(3)]\n\n        results = asyncio.run(main())\n        assert results == [0, 1, 2]\n\n\nclass TestAsyncContextManager:\n    \"\"\"Test async context managers.\"\"\"\n\n    def test_async_context_manager(self):\n        \"\"\"Test custom async context manager.\"\"\"\n        state = {\"entered\": False, \"exited\": False}\n\n        class AsyncCtx:\n            async def __aenter__(self):\n                await asyncio.sleep(0.001)\n                state[\"entered\"] = True\n                return self\n\n            async def __aexit__(self, *args):\n                await asyncio.sleep(0.001)\n                state[\"exited\"] = True\n\n        async def main():\n            async with AsyncCtx():\n                assert state[\"entered\"]\n                assert not state[\"exited\"]\n\n        asyncio.run(main())\n        assert state[\"exited\"]\n\n    def test_asynccontextmanager_decorator(self):\n        \"\"\"Test @asynccontextmanager decorator.\"\"\"\n        from contextlib import asynccontextmanager\n\n        @asynccontextmanager\n        async def managed():\n            await asyncio.sleep(0.001)\n            yield \"resource\"\n            await asyncio.sleep(0.001)\n\n        async def main():\n            async with managed() as r:\n                return r\n\n        result = asyncio.run(main())\n        assert result == \"resource\"\n\n\nclass TestSynchronization:\n    \"\"\"Test asyncio synchronization primitives.\"\"\"\n\n    def test_lock(self):\n        \"\"\"Test asyncio.Lock.\"\"\"\n\n        async def main():\n            lock = asyncio.Lock()\n            counter = [0]\n\n            async def increment():\n                async with lock:\n                    current = counter[0]\n                    await asyncio.sleep(0.001)\n                    counter[0] = current + 1\n\n            await asyncio.gather(*[increment() for _ in range(10)])\n            return counter[0]\n\n        result = asyncio.run(main())\n        assert result == 10\n\n    def test_semaphore(self):\n        \"\"\"Test asyncio.Semaphore for rate limiting.\"\"\"\n\n        async def main():\n            semaphore = asyncio.Semaphore(2)\n            concurrent = [0]\n            max_concurrent = [0]\n\n            async def task():\n                async with semaphore:\n                    concurrent[0] += 1\n                    max_concurrent[0] = max(max_concurrent[0], concurrent[0])\n                    await asyncio.sleep(0.01)\n                    concurrent[0] -= 1\n\n            await asyncio.gather(*[task() for _ in range(5)])\n            return max_concurrent[0]\n\n        max_conc = asyncio.run(main())\n        assert max_conc <= 2\n\n    def test_event(self):\n        \"\"\"Test asyncio.Event for signaling.\"\"\"\n\n        async def main():\n            event = asyncio.Event()\n            results = []\n\n            async def waiter(name):\n                await event.wait()\n                results.append(name)\n\n            async def setter():\n                await asyncio.sleep(0.01)\n                event.set()\n\n            await asyncio.gather(waiter(\"A\"), waiter(\"B\"), setter())\n            return results\n\n        results = asyncio.run(main())\n        assert set(results) == {\"A\", \"B\"}\n\n\nclass TestQueue:\n    \"\"\"Test asyncio queues.\"\"\"\n\n    def test_queue(self):\n        \"\"\"Test asyncio.Queue.\"\"\"\n\n        async def main():\n            queue = asyncio.Queue()\n            results = []\n\n            async def producer():\n                for i in range(3):\n                    await queue.put(i)\n\n            async def consumer():\n                for _ in range(3):\n                    item = await queue.get()\n                    results.append(item)\n                    queue.task_done()\n\n            await asyncio.gather(producer(), consumer())\n            return results\n\n        results = asyncio.run(main())\n        assert results == [0, 1, 2]\n\n    def test_priority_queue(self):\n        \"\"\"Test asyncio.PriorityQueue.\"\"\"\n\n        async def main():\n            queue = asyncio.PriorityQueue()\n\n            await queue.put((3, \"low\"))\n            await queue.put((1, \"high\"))\n            await queue.put((2, \"medium\"))\n\n            results = []\n            while not queue.empty():\n                _, item = await queue.get()\n                results.append(item)\n            return results\n\n        results = asyncio.run(main())\n        assert results == [\"high\", \"medium\", \"low\"]\n\n\nclass TestExceptionHandling:\n    \"\"\"Test exception handling in asyncio.\"\"\"\n\n    def test_task_exception(self):\n        \"\"\"Test exception propagation from tasks.\"\"\"\n\n        async def failing():\n            raise ValueError(\"test error\")\n\n        async def main():\n            task = asyncio.create_task(failing())\n            await task\n\n        with pytest.raises(ValueError, match=\"test error\"):\n            asyncio.run(main())\n\n    def test_gather_return_exceptions(self):\n        \"\"\"Test gather with return_exceptions.\"\"\"\n\n        async def ok():\n            return \"ok\"\n\n        async def fail():\n            raise ValueError(\"error\")\n\n        async def main():\n            return await asyncio.gather(ok(), fail(), return_exceptions=True)\n\n        results = asyncio.run(main())\n        assert results[0] == \"ok\"\n        assert isinstance(results[1], ValueError)\n\n\nclass TestCancellation:\n    \"\"\"Test task cancellation.\"\"\"\n\n    def test_cancel_task(self):\n        \"\"\"Test cancelling a task.\"\"\"\n\n        async def main():\n            cleanup_done = [False]\n\n            async def long_running():\n                try:\n                    await asyncio.sleep(10)\n                except asyncio.CancelledError:\n                    cleanup_done[0] = True\n                    raise\n\n            task = asyncio.create_task(long_running())\n            await asyncio.sleep(0.01)\n            task.cancel()\n\n            try:\n                await task\n            except asyncio.CancelledError:\n                pass\n\n            return cleanup_done[0]\n\n        result = asyncio.run(main())\n        assert result\n\n\nclass TestExecutor:\n    \"\"\"Test running blocking code in executor.\"\"\"\n\n    def test_run_in_executor(self):\n        \"\"\"Test run_in_executor for blocking code.\"\"\"\n        import time\n\n        def blocking():\n            time.sleep(0.01)\n            return \"done\"\n\n        async def main():\n            loop = asyncio.get_event_loop()\n            return await loop.run_in_executor(None, blocking)\n\n        result = asyncio.run(main())\n        assert result == \"done\"\n\n\nclass TestSubprocess:\n    \"\"\"Test asyncio subprocess.\"\"\"\n\n    def test_subprocess(self):\n        \"\"\"Test running subprocess.\"\"\"\n\n        async def main():\n            proc = await asyncio.create_subprocess_shell(\n                \"echo hello\",\n                stdout=asyncio.subprocess.PIPE,\n                stderr=asyncio.subprocess.PIPE,\n            )\n            stdout, _ = await proc.communicate()\n            return stdout.decode().strip(), proc.returncode\n\n        output, code = asyncio.run(main())\n        assert output == \"hello\"\n        assert code == 0\n\n\nclass TestTimeout:\n    \"\"\"Test timeout patterns.\"\"\"\n\n    def test_wait_for_timeout(self):\n        \"\"\"Test asyncio.wait_for timeout.\"\"\"\n\n        async def slow():\n            await asyncio.sleep(10)\n\n        async def main():\n            try:\n                await asyncio.wait_for(slow(), timeout=0.01)\n                return False\n            except asyncio.TimeoutError:\n                return True\n\n        result = asyncio.run(main())\n        assert result\n"
  },
  {
    "path": "src/basic/basic.py",
    "content": "\"\"\"Python Basics Examples\n\nSource code for docs/notes/basic/python-basic.rst\n\"\"\"\n\nimport sys\nimport platform\nimport pytest\n\n\n# Python Version\ndef get_version_info() -> tuple:\n    \"\"\"Get Python version info.\"\"\"\n    return sys.version_info[:3]\n\n\ndef get_version_string() -> str:\n    \"\"\"Get Python version as string.\"\"\"\n    return platform.python_version()\n\n\ndef check_version(major: int, minor: int) -> bool:\n    \"\"\"Check if Python version is at least major.minor.\"\"\"\n    return sys.version_info >= (major, minor)\n\n\n# Control Flow\ndef classify_number(x: int) -> str:\n    \"\"\"Classify number as negative, zero, or positive.\"\"\"\n    if x < 0:\n        return \"negative\"\n    elif x == 0:\n        return \"zero\"\n    else:\n        return \"positive\"\n\n\ndef is_even(x: int) -> bool:\n    \"\"\"Check if number is even using ternary.\"\"\"\n    return True if x % 2 == 0 else False\n\n\n# Loops\ndef sum_range(n: int) -> int:\n    \"\"\"Sum numbers from 0 to n-1.\"\"\"\n    total = 0\n    for i in range(n):\n        total += i\n    return total\n\n\ndef find_first_even(numbers: list) -> int | None:\n    \"\"\"Find first even number, demonstrating break.\"\"\"\n    for n in numbers:\n        if n % 2 == 0:\n            return n\n    return None\n\n\ndef sum_odd_only(numbers: list) -> int:\n    \"\"\"Sum only odd numbers, demonstrating continue.\"\"\"\n    total = 0\n    for n in numbers:\n        if n % 2 == 0:\n            continue\n        total += n\n    return total\n\n\ndef loop_completed(items: list, target) -> bool:\n    \"\"\"Check if loop completed without finding target (for-else).\"\"\"\n    for item in items:\n        if item == target:\n            return False\n    return True\n\n\n# Exception Handling\ndef safe_divide(a: float, b: float) -> float | None:\n    \"\"\"Divide with exception handling.\"\"\"\n    try:\n        return a / b\n    except ZeroDivisionError:\n        return None\n\n\ndef parse_int(s: str) -> int | None:\n    \"\"\"Parse string to int with error handling.\"\"\"\n    try:\n        return int(s)\n    except ValueError:\n        return None\n\n\ndef divide_or_raise(a: float, b: float) -> float:\n    \"\"\"Divide or raise ValueError.\"\"\"\n    if b == 0:\n        raise ValueError(\"divisor cannot be zero\")\n    return a / b\n\n\n# Comprehensions\ndef squares(n: int) -> list:\n    \"\"\"List of squares using comprehension.\"\"\"\n    return [x**2 for x in range(n)]\n\n\ndef even_numbers(n: int) -> list:\n    \"\"\"Even numbers using comprehension with filter.\"\"\"\n    return [x for x in range(n) if x % 2 == 0]\n\n\ndef square_dict(n: int) -> dict:\n    \"\"\"Dict comprehension.\"\"\"\n    return {x: x**2 for x in range(n)}\n\n\n# Truthiness\ndef is_truthy(value) -> bool:\n    \"\"\"Check if value is truthy.\"\"\"\n    return bool(value)\n\n\n# Multiple Assignment\ndef swap(a, b) -> tuple:\n    \"\"\"Swap two values.\"\"\"\n    return b, a\n\n\ndef first_and_rest(items: list) -> tuple:\n    \"\"\"Split into first and rest.\"\"\"\n    first, *rest = items\n    return first, rest\n\n\n# Tests\nclass TestVersion:\n    def test_get_version_info(self):\n        info = get_version_info()\n        assert len(info) == 3\n        assert info[0] >= 3\n\n    def test_check_version(self):\n        assert check_version(3, 0)\n        assert not check_version(99, 0)\n\n\nclass TestControlFlow:\n    def test_classify_number(self):\n        assert classify_number(-5) == \"negative\"\n        assert classify_number(0) == \"zero\"\n        assert classify_number(5) == \"positive\"\n\n    def test_is_even(self):\n        assert is_even(4)\n        assert not is_even(3)\n\n\nclass TestLoops:\n    def test_sum_range(self):\n        assert sum_range(5) == 10  # 0+1+2+3+4\n\n    def test_find_first_even(self):\n        assert find_first_even([1, 3, 4, 6]) == 4\n        assert find_first_even([1, 3, 5]) is None\n\n    def test_sum_odd_only(self):\n        assert sum_odd_only([1, 2, 3, 4, 5]) == 9  # 1+3+5\n\n    def test_loop_completed(self):\n        assert loop_completed([1, 2, 3], 5)\n        assert not loop_completed([1, 2, 3], 2)\n\n\nclass TestExceptions:\n    def test_safe_divide(self):\n        assert safe_divide(10, 2) == 5.0\n        assert safe_divide(10, 0) is None\n\n    def test_parse_int(self):\n        assert parse_int(\"42\") == 42\n        assert parse_int(\"abc\") is None\n\n    def test_divide_or_raise(self):\n        assert divide_or_raise(10, 2) == 5.0\n        with pytest.raises(ValueError):\n            divide_or_raise(10, 0)\n\n\nclass TestComprehensions:\n    def test_squares(self):\n        assert squares(5) == [0, 1, 4, 9, 16]\n\n    def test_even_numbers(self):\n        assert even_numbers(10) == [0, 2, 4, 6, 8]\n\n    def test_square_dict(self):\n        assert square_dict(3) == {0: 0, 1: 1, 2: 4}\n\n\nclass TestTruthiness:\n    def test_falsy(self):\n        assert not is_truthy(None)\n        assert not is_truthy(0)\n        assert not is_truthy(\"\")\n        assert not is_truthy([])\n\n    def test_truthy(self):\n        assert is_truthy(1)\n        assert is_truthy(\"text\")\n        assert is_truthy([1])\n\n\nclass TestAssignment:\n    def test_swap(self):\n        assert swap(1, 2) == (2, 1)\n\n    def test_first_and_rest(self):\n        first, rest = first_and_rest([1, 2, 3, 4])\n        assert first == 1\n        assert rest == [2, 3, 4]\n"
  },
  {
    "path": "src/basic/cext_.py",
    "content": "\"\"\"\nTests for C extension examples (ctypes and cffi).\n\nThese tests demonstrate calling C code from Python without\nrequiring compilation of pybind11/Cython modules.\n\"\"\"\n\nimport ctypes\nimport math\nimport os\nimport platform\nimport subprocess\nimport tempfile\n\nimport pytest\n\n\n# Skip all tests if no C compiler available\ndef has_c_compiler():\n    \"\"\"Check if gcc or clang is available.\"\"\"\n    for compiler in [\"gcc\", \"clang\", \"cc\"]:\n        try:\n            result = subprocess.run(\n                [compiler, \"--version\"], capture_output=True, timeout=5\n            )\n            if result.returncode == 0:\n                return True\n        except (FileNotFoundError, subprocess.TimeoutExpired):\n            continue\n    return False\n\n\nrequires_compiler = pytest.mark.skipif(\n    not has_c_compiler(), reason=\"No C compiler available\"\n)\n\n\nclass TestCtypesBasic:\n    \"\"\"Test ctypes with standard library functions.\"\"\"\n\n    def test_libc_strlen(self):\n        \"\"\"Test calling strlen from libc.\"\"\"\n        if platform.system() == \"Darwin\":\n            libc = ctypes.CDLL(\"libc.dylib\")\n        elif platform.system() == \"Linux\":\n            libc = ctypes.CDLL(\"libc.so.6\")\n        else:\n            pytest.skip(\"Unsupported platform\")\n\n        libc.strlen.argtypes = [ctypes.c_char_p]\n        libc.strlen.restype = ctypes.c_size_t\n\n        result = libc.strlen(b\"hello\")\n        assert result == 5\n\n    def test_libc_abs(self):\n        \"\"\"Test calling abs from libc.\"\"\"\n        if platform.system() == \"Darwin\":\n            libc = ctypes.CDLL(\"libc.dylib\")\n        elif platform.system() == \"Linux\":\n            libc = ctypes.CDLL(\"libc.so.6\")\n        else:\n            pytest.skip(\"Unsupported platform\")\n\n        assert libc.abs(-42) == 42\n        assert libc.abs(42) == 42\n\n    def test_math_sqrt(self):\n        \"\"\"Test calling sqrt from libm.\"\"\"\n        if platform.system() == \"Darwin\":\n            libm = ctypes.CDLL(\"libm.dylib\")\n        elif platform.system() == \"Linux\":\n            libm = ctypes.CDLL(\"libm.so.6\")\n        else:\n            pytest.skip(\"Unsupported platform\")\n\n        libm.sqrt.argtypes = [ctypes.c_double]\n        libm.sqrt.restype = ctypes.c_double\n\n        result = libm.sqrt(16.0)\n        assert abs(result - 4.0) < 1e-10\n\n\nclass TestCtypesStructures:\n    \"\"\"Test ctypes with structures.\"\"\"\n\n    def test_simple_structure(self):\n        \"\"\"Test creating and using a ctypes Structure.\"\"\"\n\n        class Point(ctypes.Structure):\n            _fields_ = [(\"x\", ctypes.c_double), (\"y\", ctypes.c_double)]\n\n        p = Point(3.0, 4.0)\n        assert p.x == 3.0\n        assert p.y == 4.0\n\n        # Calculate distance manually\n        distance = math.sqrt(p.x**2 + p.y**2)\n        assert abs(distance - 5.0) < 1e-10\n\n    def test_nested_structure(self):\n        \"\"\"Test nested structures.\"\"\"\n\n        class Point(ctypes.Structure):\n            _fields_ = [(\"x\", ctypes.c_double), (\"y\", ctypes.c_double)]\n\n        class Rectangle(ctypes.Structure):\n            _fields_ = [(\"top_left\", Point), (\"bottom_right\", Point)]\n\n        rect = Rectangle(Point(0, 10), Point(10, 0))\n        assert rect.top_left.x == 0\n        assert rect.top_left.y == 10\n        assert rect.bottom_right.x == 10\n        assert rect.bottom_right.y == 0\n\n    def test_array_in_structure(self):\n        \"\"\"Test arrays within structures.\"\"\"\n\n        class Data(ctypes.Structure):\n            _fields_ = [(\"values\", ctypes.c_int * 5), (\"count\", ctypes.c_int)]\n\n        d = Data()\n        d.count = 5\n        for i in range(5):\n            d.values[i] = i * 10\n\n        assert list(d.values) == [0, 10, 20, 30, 40]\n        assert d.count == 5\n\n\nclass TestCtypesPointers:\n    \"\"\"Test ctypes pointer operations.\"\"\"\n\n    def test_pointer_to_int(self):\n        \"\"\"Test pointer to integer.\"\"\"\n        value = ctypes.c_int(42)\n        ptr = ctypes.pointer(value)\n        assert ptr.contents.value == 42\n\n        # Modify through pointer\n        ptr.contents.value = 100\n        assert value.value == 100\n\n    def test_byref(self):\n        \"\"\"Test byref for passing by reference.\"\"\"\n        value = ctypes.c_int(42)\n        # byref creates a lightweight pointer for passing to C functions\n        ref = ctypes.byref(value)\n        # byref returns a CArgObject, not a full pointer\n        assert ref is not None\n\n\n@requires_compiler\nclass TestCtypesCustomLibrary:\n    \"\"\"Test ctypes with custom compiled C code.\"\"\"\n\n    @pytest.fixture\n    def fib_library(self, tmp_path):\n        \"\"\"Compile a simple Fibonacci library.\"\"\"\n        c_code = \"\"\"\n        unsigned long fib(unsigned long n) {\n            if (n < 2) return n;\n            return fib(n - 1) + fib(n - 2);\n        }\n\n        int add(int a, int b) {\n            return a + b;\n        }\n\n        double multiply(double a, double b) {\n            return a * b;\n        }\n        \"\"\"\n\n        c_file = tmp_path / \"fib.c\"\n        c_file.write_text(c_code)\n\n        if platform.system() == \"Darwin\":\n            lib_file = tmp_path / \"libfib.dylib\"\n            cmd = [\n                \"clang\",\n                \"-shared\",\n                \"-fPIC\",\n                \"-o\",\n                str(lib_file),\n                str(c_file),\n            ]\n        else:\n            lib_file = tmp_path / \"libfib.so\"\n            cmd = [\"gcc\", \"-shared\", \"-fPIC\", \"-o\", str(lib_file), str(c_file)]\n\n        result = subprocess.run(cmd, capture_output=True)\n        if result.returncode != 0:\n            pytest.skip(f\"Compilation failed: {result.stderr.decode()}\")\n\n        lib = ctypes.CDLL(str(lib_file))\n\n        # Set up function signatures\n        lib.fib.argtypes = [ctypes.c_ulong]\n        lib.fib.restype = ctypes.c_ulong\n\n        lib.add.argtypes = [ctypes.c_int, ctypes.c_int]\n        lib.add.restype = ctypes.c_int\n\n        lib.multiply.argtypes = [ctypes.c_double, ctypes.c_double]\n        lib.multiply.restype = ctypes.c_double\n\n        return lib\n\n    def test_fib(self, fib_library):\n        \"\"\"Test Fibonacci function.\"\"\"\n        assert fib_library.fib(0) == 0\n        assert fib_library.fib(1) == 1\n        assert fib_library.fib(10) == 55\n        assert fib_library.fib(20) == 6765\n\n    def test_add(self, fib_library):\n        \"\"\"Test add function.\"\"\"\n        assert fib_library.add(1, 2) == 3\n        assert fib_library.add(-5, 10) == 5\n        assert fib_library.add(0, 0) == 0\n\n    def test_multiply(self, fib_library):\n        \"\"\"Test multiply function with doubles.\"\"\"\n        assert abs(fib_library.multiply(2.5, 4.0) - 10.0) < 1e-10\n        assert abs(fib_library.multiply(-1.5, 2.0) - (-3.0)) < 1e-10\n\n\nclass TestCffi:\n    \"\"\"Test cffi if available.\"\"\"\n\n    @pytest.fixture\n    def cffi_available(self):\n        \"\"\"Check if cffi is installed.\"\"\"\n        try:\n            import cffi\n\n            return cffi.FFI()\n        except ImportError:\n            pytest.skip(\"cffi not installed\")\n\n    def test_cffi_libc(self, cffi_available):\n        \"\"\"Test cffi with libc.\"\"\"\n        ffi = cffi_available\n\n        ffi.cdef(\n            \"\"\"\n            int abs(int x);\n            size_t strlen(const char *s);\n        \"\"\"\n        )\n\n        if platform.system() == \"Darwin\":\n            libc = ffi.dlopen(\"libc.dylib\")\n        elif platform.system() == \"Linux\":\n            libc = ffi.dlopen(\"libc.so.6\")\n        else:\n            pytest.skip(\"Unsupported platform\")\n\n        assert libc.abs(-42) == 42\n        assert libc.strlen(b\"hello\") == 5\n\n    def test_cffi_math(self, cffi_available):\n        \"\"\"Test cffi with libm.\"\"\"\n        ffi = cffi_available\n\n        ffi.cdef(\n            \"\"\"\n            double sqrt(double x);\n            double pow(double x, double y);\n        \"\"\"\n        )\n\n        if platform.system() == \"Darwin\":\n            libm = ffi.dlopen(\"libm.dylib\")\n        elif platform.system() == \"Linux\":\n            libm = ffi.dlopen(\"libm.so.6\")\n        else:\n            pytest.skip(\"Unsupported platform\")\n\n        assert abs(libm.sqrt(16.0) - 4.0) < 1e-10\n        assert abs(libm.pow(2.0, 10.0) - 1024.0) < 1e-10\n\n\nclass TestPythonPerformance:\n    \"\"\"Test pure Python implementations for comparison.\"\"\"\n\n    def test_python_fib(self):\n        \"\"\"Test pure Python Fibonacci.\"\"\"\n\n        def fib(n):\n            if n < 2:\n                return n\n            return fib(n - 1) + fib(n - 2)\n\n        assert fib(0) == 0\n        assert fib(1) == 1\n        assert fib(10) == 55\n        assert fib(20) == 6765\n\n    def test_python_fib_iterative(self):\n        \"\"\"Test iterative Fibonacci (faster).\"\"\"\n\n        def fib_iter(n):\n            if n < 2:\n                return n\n            a, b = 0, 1\n            for _ in range(n - 1):\n                a, b = b, a + b\n            return b\n\n        assert fib_iter(0) == 0\n        assert fib_iter(1) == 1\n        assert fib_iter(10) == 55\n        assert fib_iter(50) == 12586269025\n\n    def test_python_fib_memoized(self):\n        \"\"\"Test memoized Fibonacci.\"\"\"\n        from functools import lru_cache\n\n        @lru_cache(maxsize=None)\n        def fib_memo(n):\n            if n < 2:\n                return n\n            return fib_memo(n - 1) + fib_memo(n - 2)\n\n        assert fib_memo(0) == 0\n        assert fib_memo(1) == 1\n        assert fib_memo(10) == 55\n        assert fib_memo(50) == 12586269025\n"
  },
  {
    "path": "src/basic/concurrency_.py",
    "content": "\"\"\"Tests for concurrency examples.\"\"\"\n\nimport pytest\nimport time\nfrom threading import Thread, Lock, RLock, Semaphore, Event, Condition, Barrier\nfrom queue import Queue\nfrom concurrent.futures import ThreadPoolExecutor, as_completed\n\n\n# Module-level functions for multiprocessing (must be picklable)\ndef _mp_square(x):\n    return x * x\n\n\ndef _mp_add(a, b):\n    return a + b\n\n\ndef _mp_worker(q, n):\n    q.put(n * n)\n\n\ndef _mp_increment(counter):\n    for _ in range(100):\n        with counter.get_lock():\n            counter.value += 1\n\n\ndef _mp_double(arr):\n    for i in range(len(arr)):\n        arr[i] *= 2\n\n\nclass TestThreading:\n    \"\"\"Test threading operations.\"\"\"\n\n    def test_thread_creation(self):\n        \"\"\"Test basic thread creation.\"\"\"\n        results = []\n\n        def task(n):\n            results.append(n)\n\n        threads = [Thread(target=task, args=(i,)) for i in range(5)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        assert sorted(results) == [0, 1, 2, 3, 4]\n\n    def test_thread_with_return_value(self):\n        \"\"\"Test getting return values from threads.\"\"\"\n        results = {}\n\n        def compute(n, res):\n            res[n] = n * n\n\n        threads = [Thread(target=compute, args=(i, results)) for i in range(5)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        assert results == {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}\n\n    def test_lock(self):\n        \"\"\"Test Lock for mutual exclusion.\"\"\"\n        counter = [0]\n        lock = Lock()\n\n        def increment():\n            for _ in range(1000):\n                with lock:\n                    counter[0] += 1\n\n        threads = [Thread(target=increment) for _ in range(10)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        assert counter[0] == 10000\n\n    def test_rlock(self):\n        \"\"\"Test RLock for reentrant locking.\"\"\"\n        lock = RLock()\n        results = []\n\n        def outer():\n            with lock:\n                results.append(\"outer\")\n                inner()\n\n        def inner():\n            with lock:  # Same thread can acquire again\n                results.append(\"inner\")\n\n        t = Thread(target=outer)\n        t.start()\n        t.join()\n\n        assert results == [\"outer\", \"inner\"]\n\n    def test_semaphore(self):\n        \"\"\"Test Semaphore for resource limiting.\"\"\"\n        max_concurrent = [0]\n        current = [0]\n        sem = Semaphore(3)\n\n        def task():\n            with sem:\n                current[0] += 1\n                max_concurrent[0] = max(max_concurrent[0], current[0])\n                time.sleep(0.01)\n                current[0] -= 1\n\n        threads = [Thread(target=task) for _ in range(10)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        assert max_concurrent[0] <= 3\n\n    def test_event(self):\n        \"\"\"Test Event for thread signaling.\"\"\"\n        event = Event()\n        results = []\n\n        def waiter(n):\n            event.wait()\n            results.append(n)\n\n        threads = [Thread(target=waiter, args=(i,)) for i in range(3)]\n        for t in threads:\n            t.start()\n\n        time.sleep(0.1)\n        assert len(results) == 0  # All waiting\n\n        event.set()\n        for t in threads:\n            t.join()\n\n        assert sorted(results) == [0, 1, 2]\n\n    def test_condition(self):\n        \"\"\"Test Condition for complex synchronization.\"\"\"\n        items = []\n        condition = Condition()\n        consumed = []\n\n        def producer():\n            for i in range(3):\n                with condition:\n                    items.append(i)\n                    condition.notify()\n\n        def consumer():\n            for _ in range(3):\n                with condition:\n                    while not items:\n                        condition.wait()\n                    consumed.append(items.pop(0))\n\n        t1 = Thread(target=producer)\n        t2 = Thread(target=consumer)\n        t2.start()\n        time.sleep(0.01)\n        t1.start()\n        t1.join()\n        t2.join()\n\n        assert consumed == [0, 1, 2]\n\n    def test_barrier(self):\n        \"\"\"Test Barrier for synchronization point.\"\"\"\n        barrier = Barrier(3)\n        order = []\n\n        def worker(n):\n            order.append(f\"before_{n}\")\n            barrier.wait()\n            order.append(f\"after_{n}\")\n\n        threads = [Thread(target=worker, args=(i,)) for i in range(3)]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        # All \"before\" should come before all \"after\"\n        before_count = sum(1 for x in order[:3] if x.startswith(\"before\"))\n        assert before_count == 3\n\n    def test_queue(self):\n        \"\"\"Test thread-safe Queue.\"\"\"\n        q = Queue()\n        results = []\n\n        def producer():\n            for i in range(5):\n                q.put(i)\n\n        def consumer():\n            for _ in range(5):\n                results.append(q.get())\n                q.task_done()\n\n        t1 = Thread(target=producer)\n        t2 = Thread(target=consumer)\n        t1.start()\n        t2.start()\n        t1.join()\n        t2.join()\n\n        assert sorted(results) == [0, 1, 2, 3, 4]\n\n\nclass TestMultiprocessing:\n    \"\"\"Test multiprocessing operations.\"\"\"\n\n    def test_process_creation(self):\n        \"\"\"Test basic process creation.\"\"\"\n        from multiprocessing import Process, Queue as MPQueue\n\n        q = MPQueue()\n        processes = [Process(target=_mp_worker, args=(q, i)) for i in range(4)]\n        for p in processes:\n            p.start()\n        for p in processes:\n            p.join()\n\n        results = [q.get() for _ in range(4)]\n        assert sorted(results) == [0, 1, 4, 9]\n\n    def test_pool_map(self):\n        \"\"\"Test Pool.map for parallel execution.\"\"\"\n        from multiprocessing import Pool\n\n        with Pool(2) as pool:\n            results = pool.map(_mp_square, range(5))\n\n        assert results == [0, 1, 4, 9, 16]\n\n    def test_pool_starmap(self):\n        \"\"\"Test Pool.starmap for multiple arguments.\"\"\"\n        from multiprocessing import Pool\n\n        with Pool(2) as pool:\n            results = pool.starmap(_mp_add, [(1, 2), (3, 4), (5, 6)])\n\n        assert results == [3, 7, 11]\n\n    def test_shared_value(self):\n        \"\"\"Test shared Value between processes.\"\"\"\n        from multiprocessing import Process, Value\n\n        counter = Value(\"i\", 0)\n        processes = [\n            Process(target=_mp_increment, args=(counter,)) for _ in range(4)\n        ]\n        for p in processes:\n            p.start()\n        for p in processes:\n            p.join()\n\n        assert counter.value == 400\n\n    def test_shared_array(self):\n        \"\"\"Test shared Array between processes.\"\"\"\n        from multiprocessing import Process, Array\n\n        arr = Array(\"i\", [1, 2, 3, 4])\n        p = Process(target=_mp_double, args=(arr,))\n        p.start()\n        p.join()\n\n        assert list(arr) == [2, 4, 6, 8]\n\n\nclass TestConcurrentFutures:\n    \"\"\"Test concurrent.futures operations.\"\"\"\n\n    def test_thread_pool_map(self):\n        \"\"\"Test ThreadPoolExecutor.map.\"\"\"\n\n        def square(x):\n            return x * x\n\n        with ThreadPoolExecutor(max_workers=3) as executor:\n            results = list(executor.map(square, range(5)))\n\n        assert results == [0, 1, 4, 9, 16]\n\n    def test_thread_pool_submit(self):\n        \"\"\"Test ThreadPoolExecutor.submit.\"\"\"\n\n        def compute(x):\n            return x * 2\n\n        with ThreadPoolExecutor(max_workers=2) as executor:\n            futures = [executor.submit(compute, i) for i in range(5)]\n            results = [f.result() for f in futures]\n\n        assert results == [0, 2, 4, 6, 8]\n\n    def test_as_completed(self):\n        \"\"\"Test as_completed for processing results.\"\"\"\n\n        def task(n):\n            time.sleep(0.1 - n * 0.02)  # Varying delays\n            return n\n\n        with ThreadPoolExecutor(max_workers=3) as executor:\n            futures = [executor.submit(task, i) for i in range(3)]\n            results = [f.result() for f in as_completed(futures)]\n\n        # Results may be in any order\n        assert sorted(results) == [0, 1, 2]\n\n    def test_future_callback(self):\n        \"\"\"Test Future.add_done_callback.\"\"\"\n        results = []\n\n        def on_complete(future):\n            results.append(future.result())\n\n        def compute(x):\n            return x * x\n\n        with ThreadPoolExecutor(max_workers=2) as executor:\n            for i in range(3):\n                future = executor.submit(compute, i)\n                future.add_done_callback(on_complete)\n\n        time.sleep(0.1)  # Wait for callbacks\n        assert sorted(results) == [0, 1, 4]\n\n    def test_future_exception(self):\n        \"\"\"Test exception handling in futures.\"\"\"\n\n        def failing_task():\n            raise ValueError(\"test error\")\n\n        with ThreadPoolExecutor(max_workers=1) as executor:\n            future = executor.submit(failing_task)\n\n            with pytest.raises(ValueError, match=\"test error\"):\n                future.result()\n\n    def test_future_timeout(self):\n        \"\"\"Test timeout on future.result().\"\"\"\n\n        def slow_task():\n            time.sleep(10)\n\n        with ThreadPoolExecutor(max_workers=1) as executor:\n            future = executor.submit(slow_task)\n\n            from concurrent.futures import TimeoutError\n\n            with pytest.raises(TimeoutError):\n                future.result(timeout=0.1)\n\n    def test_future_cancel(self):\n        \"\"\"Test cancelling a future.\"\"\"\n\n        def slow_task():\n            time.sleep(10)\n\n        with ThreadPoolExecutor(max_workers=1) as executor:\n            # First task blocks the worker\n            future1 = executor.submit(slow_task)\n            # Second task is queued\n            future2 = executor.submit(slow_task)\n\n            time.sleep(0.01)  # Let first task start\n\n            # Can cancel queued task\n            assert future2.cancel() == True\n            assert future2.cancelled() == True\n\n    def test_process_pool_map(self):\n        \"\"\"Test ProcessPoolExecutor.map.\"\"\"\n        from concurrent.futures import ProcessPoolExecutor\n\n        with ProcessPoolExecutor(max_workers=2) as executor:\n            results = list(executor.map(_mp_square, range(5)))\n\n        assert results == [0, 1, 4, 9, 16]\n\n\nclass TestProducerConsumer:\n    \"\"\"Test producer-consumer patterns.\"\"\"\n\n    def test_basic_producer_consumer(self):\n        \"\"\"Test basic producer-consumer with Queue.\"\"\"\n        q = Queue()\n        produced = []\n        consumed = []\n\n        def producer():\n            for i in range(5):\n                produced.append(i)\n                q.put(i)\n            q.put(None)  # Sentinel\n\n        def consumer():\n            while True:\n                item = q.get()\n                if item is None:\n                    break\n                consumed.append(item)\n\n        t1 = Thread(target=producer)\n        t2 = Thread(target=consumer)\n        t1.start()\n        t2.start()\n        t1.join()\n        t2.join()\n\n        assert produced == consumed\n\n    def test_multiple_consumers(self):\n        \"\"\"Test multiple consumers.\"\"\"\n        q = Queue()\n        consumed = []\n        lock = Lock()\n\n        def producer():\n            for i in range(10):\n                q.put(i)\n            for _ in range(3):  # Sentinels for each consumer\n                q.put(None)\n\n        def consumer():\n            while True:\n                item = q.get()\n                if item is None:\n                    break\n                with lock:\n                    consumed.append(item)\n\n        producer_thread = Thread(target=producer)\n        consumer_threads = [Thread(target=consumer) for _ in range(3)]\n\n        producer_thread.start()\n        for t in consumer_threads:\n            t.start()\n\n        producer_thread.join()\n        for t in consumer_threads:\n            t.join()\n\n        assert sorted(consumed) == list(range(10))\n"
  },
  {
    "path": "src/basic/crypto_.py",
    "content": "\"\"\"\nTests for modern cryptography examples.\n\"\"\"\n\nimport hashlib\nimport hmac\nimport os\nimport secrets\n\nimport pytest\nfrom cryptography.fernet import Fernet\nfrom cryptography.hazmat.primitives import hashes, serialization\nfrom cryptography.hazmat.primitives.asymmetric import rsa, padding, ed25519\nfrom cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey\nfrom cryptography.hazmat.primitives.ciphers.aead import (\n    AESGCM,\n    ChaCha20Poly1305,\n)\nfrom cryptography.hazmat.primitives.kdf.hkdf import HKDF\nfrom cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC\n\n\nclass TestSecureRandom:\n    \"\"\"Test secure random generation.\"\"\"\n\n    def test_token_bytes(self):\n        key = secrets.token_bytes(32)\n        assert len(key) == 32\n        # Should be different each time\n        assert key != secrets.token_bytes(32)\n\n    def test_token_urlsafe(self):\n        token = secrets.token_urlsafe(32)\n        assert len(token) >= 32  # Base64 encoding makes it longer\n\n    def test_token_hex(self):\n        token = secrets.token_hex(16)\n        assert len(token) == 32  # 16 bytes = 32 hex chars\n\n    def test_randbelow(self):\n        for _ in range(100):\n            n = secrets.randbelow(100)\n            assert 0 <= n < 100\n\n\nclass TestHashing:\n    \"\"\"Test cryptographic hashing.\"\"\"\n\n    def test_sha256(self):\n        data = b\"Hello, World!\"\n        digest = hashlib.sha256(data).hexdigest()\n        assert len(digest) == 64  # 256 bits = 64 hex chars\n        # Same input = same output\n        assert digest == hashlib.sha256(data).hexdigest()\n\n    def test_sha3_256(self):\n        data = b\"Hello, World!\"\n        digest = hashlib.sha3_256(data).hexdigest()\n        assert len(digest) == 64\n\n    def test_blake2b(self):\n        data = b\"Hello, World!\"\n        digest = hashlib.blake2b(data, digest_size=32).hexdigest()\n        assert len(digest) == 64\n\n    def test_blake2b_keyed(self):\n        data = b\"Hello, World!\"\n        key = b\"secret-key-here!\"\n        mac = hashlib.blake2b(data, key=key, digest_size=32).hexdigest()\n        assert len(mac) == 64\n        # Different key = different MAC\n        other_mac = hashlib.blake2b(\n            data, key=b\"other-key-here!!\", digest_size=32\n        ).hexdigest()\n        assert mac != other_mac\n\n\nclass TestHMAC:\n    \"\"\"Test HMAC operations.\"\"\"\n\n    def test_hmac_create_verify(self):\n        key = secrets.token_bytes(32)\n        message = b\"Important message\"\n        mac = hmac.new(key, message, hashlib.sha256).digest()\n        assert hmac.compare_digest(\n            mac, hmac.new(key, message, hashlib.sha256).digest()\n        )\n\n    def test_hmac_tamper_detection(self):\n        key = secrets.token_bytes(32)\n        message = b\"Important message\"\n        mac = hmac.new(key, message, hashlib.sha256).digest()\n        tampered = b\"Tampered message\"\n        tampered_mac = hmac.new(key, tampered, hashlib.sha256).digest()\n        assert not hmac.compare_digest(mac, tampered_mac)\n\n\nclass TestKeyDerivation:\n    \"\"\"Test key derivation functions.\"\"\"\n\n    def test_pbkdf2(self):\n        password = b\"user-password\"\n        salt = os.urandom(16)\n        kdf = PBKDF2HMAC(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=salt,\n            iterations=100_000,  # Lower for tests\n        )\n        key = kdf.derive(password)\n        assert len(key) == 32\n\n    def test_pbkdf2_verify(self):\n        password = b\"user-password\"\n        salt = os.urandom(16)\n        kdf = PBKDF2HMAC(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=salt,\n            iterations=100_000,\n        )\n        key = kdf.derive(password)\n\n        # Verify with new KDF instance\n        kdf2 = PBKDF2HMAC(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=salt,\n            iterations=100_000,\n        )\n        kdf2.verify(password, key)  # Should not raise\n\n    def test_hkdf(self):\n        master_key = os.urandom(32)\n        salt = os.urandom(16)\n        hkdf = HKDF(\n            algorithm=hashes.SHA256(),\n            length=32,\n            salt=salt,\n            info=b\"encryption-key\",\n        )\n        derived = hkdf.derive(master_key)\n        assert len(derived) == 32\n\n\nclass TestAESGCM:\n    \"\"\"Test AES-GCM authenticated encryption.\"\"\"\n\n    def test_encrypt_decrypt(self):\n        key = AESGCM.generate_key(bit_length=256)\n        aesgcm = AESGCM(key)\n        nonce = os.urandom(12)\n        plaintext = b\"Secret message\"\n        ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n        decrypted = aesgcm.decrypt(nonce, ciphertext, None)\n        assert decrypted == plaintext\n\n    def test_with_associated_data(self):\n        key = AESGCM.generate_key(bit_length=256)\n        aesgcm = AESGCM(key)\n        nonce = os.urandom(12)\n        plaintext = b\"Secret message\"\n        aad = b\"header\"\n        ciphertext = aesgcm.encrypt(nonce, plaintext, aad)\n        decrypted = aesgcm.decrypt(nonce, ciphertext, aad)\n        assert decrypted == plaintext\n\n    def test_tamper_detection(self):\n        key = AESGCM.generate_key(bit_length=256)\n        aesgcm = AESGCM(key)\n        nonce = os.urandom(12)\n        plaintext = b\"Secret message\"\n        ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n        tampered = bytearray(ciphertext)\n        tampered[0] ^= 1\n        with pytest.raises(Exception):\n            aesgcm.decrypt(nonce, bytes(tampered), None)\n\n\nclass TestChaCha20Poly1305:\n    \"\"\"Test ChaCha20-Poly1305 authenticated encryption.\"\"\"\n\n    def test_encrypt_decrypt(self):\n        key = ChaCha20Poly1305.generate_key()\n        chacha = ChaCha20Poly1305(key)\n        nonce = os.urandom(12)\n        plaintext = b\"Secret message\"\n        ciphertext = chacha.encrypt(nonce, plaintext, None)\n        decrypted = chacha.decrypt(nonce, ciphertext, None)\n        assert decrypted == plaintext\n\n\nclass TestFernet:\n    \"\"\"Test Fernet high-level encryption.\"\"\"\n\n    def test_encrypt_decrypt(self):\n        key = Fernet.generate_key()\n        f = Fernet(key)\n        plaintext = b\"Secret message\"\n        token = f.encrypt(plaintext)\n        decrypted = f.decrypt(token)\n        assert decrypted == plaintext\n\n    def test_different_tokens(self):\n        key = Fernet.generate_key()\n        f = Fernet(key)\n        plaintext = b\"Secret message\"\n        token1 = f.encrypt(plaintext)\n        token2 = f.encrypt(plaintext)\n        # Same plaintext produces different tokens (random IV)\n        assert token1 != token2\n\n\nclass TestRSA:\n    \"\"\"Test RSA operations.\"\"\"\n\n    def test_key_generation(self):\n        private_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=2048,\n        )\n        public_key = private_key.public_key()\n        assert private_key is not None\n        assert public_key is not None\n\n    def test_key_serialization(self):\n        private_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=2048,\n        )\n        pem = private_key.private_bytes(\n            encoding=serialization.Encoding.PEM,\n            format=serialization.PrivateFormat.PKCS8,\n            encryption_algorithm=serialization.NoEncryption(),\n        )\n        assert pem.startswith(b\"-----BEGIN PRIVATE KEY-----\")\n\n    def test_oaep_encrypt_decrypt(self):\n        private_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=2048,\n        )\n        public_key = private_key.public_key()\n        plaintext = b\"Secret message\"\n        ciphertext = public_key.encrypt(\n            plaintext,\n            padding.OAEP(\n                mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                algorithm=hashes.SHA256(),\n                label=None,\n            ),\n        )\n        decrypted = private_key.decrypt(\n            ciphertext,\n            padding.OAEP(\n                mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                algorithm=hashes.SHA256(),\n                label=None,\n            ),\n        )\n        assert decrypted == plaintext\n\n    def test_pss_sign_verify(self):\n        private_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=2048,\n        )\n        public_key = private_key.public_key()\n        message = b\"Message to sign\"\n        signature = private_key.sign(\n            message,\n            padding.PSS(\n                mgf=padding.MGF1(hashes.SHA256()),\n                salt_length=padding.PSS.MAX_LENGTH,\n            ),\n            hashes.SHA256(),\n        )\n        # Should not raise\n        public_key.verify(\n            signature,\n            message,\n            padding.PSS(\n                mgf=padding.MGF1(hashes.SHA256()),\n                salt_length=padding.PSS.MAX_LENGTH,\n            ),\n            hashes.SHA256(),\n        )\n\n\nclass TestEd25519:\n    \"\"\"Test Ed25519 signatures.\"\"\"\n\n    def test_sign_verify(self):\n        private_key = ed25519.Ed25519PrivateKey.generate()\n        public_key = private_key.public_key()\n        message = b\"Message to sign\"\n        signature = private_key.sign(message)\n        # Should not raise\n        public_key.verify(signature, message)\n\n    def test_invalid_signature(self):\n        private_key = ed25519.Ed25519PrivateKey.generate()\n        public_key = private_key.public_key()\n        message = b\"Message to sign\"\n        signature = private_key.sign(message)\n        with pytest.raises(Exception):\n            public_key.verify(signature, b\"Different message\")\n\n\nclass TestX25519:\n    \"\"\"Test X25519 key exchange.\"\"\"\n\n    def test_key_exchange(self):\n        alice_private = X25519PrivateKey.generate()\n        alice_public = alice_private.public_key()\n        bob_private = X25519PrivateKey.generate()\n        bob_public = bob_private.public_key()\n\n        alice_shared = alice_private.exchange(bob_public)\n        bob_shared = bob_private.exchange(alice_public)\n\n        assert alice_shared == bob_shared\n        assert len(alice_shared) == 32\n\n\nclass TestHybridEncryption:\n    \"\"\"Test hybrid RSA + AES-GCM encryption.\"\"\"\n\n    def test_hybrid_encrypt_decrypt(self):\n        def hybrid_encrypt(public_key, plaintext):\n            aes_key = AESGCM.generate_key(bit_length=256)\n            nonce = os.urandom(12)\n            aesgcm = AESGCM(aes_key)\n            ciphertext = aesgcm.encrypt(nonce, plaintext, None)\n            encrypted_key = public_key.encrypt(\n                aes_key,\n                padding.OAEP(\n                    mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                    algorithm=hashes.SHA256(),\n                    label=None,\n                ),\n            )\n            return encrypted_key, nonce, ciphertext\n\n        def hybrid_decrypt(private_key, encrypted_key, nonce, ciphertext):\n            aes_key = private_key.decrypt(\n                encrypted_key,\n                padding.OAEP(\n                    mgf=padding.MGF1(algorithm=hashes.SHA256()),\n                    algorithm=hashes.SHA256(),\n                    label=None,\n                ),\n            )\n            aesgcm = AESGCM(aes_key)\n            return aesgcm.decrypt(nonce, ciphertext, None)\n\n        private_key = rsa.generate_private_key(\n            public_exponent=65537,\n            key_size=2048,\n        )\n        public_key = private_key.public_key()\n        message = b\"This is a longer message that exceeds RSA limits\" * 100\n        encrypted_key, nonce, ciphertext = hybrid_encrypt(public_key, message)\n        decrypted = hybrid_decrypt(\n            private_key, encrypted_key, nonce, ciphertext\n        )\n        assert decrypted == message\n"
  },
  {
    "path": "src/basic/datetime_.py",
    "content": "\"\"\"Tests for datetime operations.\"\"\"\n\nimport calendar\nimport time\nfrom datetime import date, datetime, time as dt_time, timedelta, timezone\n\n\ndef test_current_datetime():\n    \"\"\"Get current date and time.\"\"\"\n    now = datetime.now()\n    assert isinstance(now, datetime)\n\n    utc_now = datetime.now(timezone.utc)\n    assert utc_now.tzinfo == timezone.utc\n\n    today = date.today()\n    assert isinstance(today, date)\n\n\ndef test_create_datetime():\n    \"\"\"Create datetime objects.\"\"\"\n    dt = datetime(2024, 1, 15, 10, 30, 45)\n    assert dt.year == 2024\n    assert dt.month == 1\n    assert dt.day == 15\n    assert dt.hour == 10\n\n    d = date(2024, 1, 15)\n    t = dt_time(10, 30, 45)\n    combined = datetime.combine(d, t)\n    assert combined == dt\n\n\ndef test_timestamp_conversion():\n    \"\"\"Convert between timestamps and datetime.\"\"\"\n    ts = time.time()\n    dt = datetime.fromtimestamp(ts)\n    ts_back = dt.timestamp()\n    assert abs(ts - ts_back) < 0.001\n\n    dt_utc = datetime.fromtimestamp(ts, tz=timezone.utc)\n    assert dt_utc.tzinfo == timezone.utc\n\n\ndef test_strftime_formatting():\n    \"\"\"Format datetime as string.\"\"\"\n    dt = datetime(2024, 1, 15, 14, 30, 45)\n\n    assert dt.strftime(\"%Y-%m-%d\") == \"2024-01-15\"\n    assert dt.strftime(\"%d/%m/%Y\") == \"15/01/2024\"\n    assert dt.strftime(\"%Y-%m-%d %H:%M:%S\") == \"2024-01-15 14:30:45\"\n    assert dt.strftime(\"%Y%m%d_%H%M%S\") == \"20240115_143045\"\n    assert dt.isoformat() == \"2024-01-15T14:30:45\"\n\n\ndef test_strptime_parsing():\n    \"\"\"Parse string to datetime.\"\"\"\n    dt1 = datetime.strptime(\"2024-01-15\", \"%Y-%m-%d\")\n    assert dt1.year == 2024 and dt1.month == 1 and dt1.day == 15\n\n    dt2 = datetime.strptime(\"15/01/2024\", \"%d/%m/%Y\")\n    assert dt2.day == 15\n\n    dt3 = datetime.fromisoformat(\"2024-01-15T14:30:45\")\n    assert dt3.hour == 14 and dt3.minute == 30\n\n\ndef test_timedelta_arithmetic():\n    \"\"\"Date arithmetic with timedelta.\"\"\"\n    now = datetime(2024, 1, 15, 12, 0, 0)\n\n    tomorrow = now + timedelta(days=1)\n    assert tomorrow.day == 16\n\n    yesterday = now - timedelta(days=1)\n    assert yesterday.day == 14\n\n    in_2_hours = now + timedelta(hours=2)\n    assert in_2_hours.hour == 14\n\n    date1 = datetime(2024, 1, 1)\n    date2 = datetime(2024, 12, 31)\n    diff = date2 - date1\n    assert diff.days == 365\n\n\ndef test_timezone_operations():\n    \"\"\"Work with timezones.\"\"\"\n    utc = timezone.utc\n    dt_utc = datetime(2024, 1, 15, 12, 0, 0, tzinfo=utc)\n    assert dt_utc.tzinfo == utc\n\n    pst = timezone(timedelta(hours=-8))\n    dt_pst = dt_utc.astimezone(pst)\n    assert dt_pst.hour == 4  # 12:00 UTC = 04:00 PST\n\n    naive = datetime(2024, 1, 15, 10, 30)\n    aware = naive.replace(tzinfo=utc)\n    assert aware.tzinfo == utc\n\n\ndef test_date_comparison():\n    \"\"\"Compare datetime objects.\"\"\"\n    dt1 = datetime(2024, 1, 15, 10, 0)\n    dt2 = datetime(2024, 1, 15, 14, 0)\n    dt3 = datetime(2024, 1, 16, 10, 0)\n\n    assert dt1 < dt2\n    assert dt3 > dt2\n    assert dt1 != dt2\n\n    start = datetime(2024, 1, 1)\n    end = datetime(2024, 12, 31)\n    check = datetime(2024, 6, 15)\n    assert start <= check <= end\n\n\ndef test_weekday_operations():\n    \"\"\"Work with weekdays and weeks.\"\"\"\n    dt = datetime(2024, 1, 15)  # Monday\n\n    assert dt.weekday() == 0  # Monday = 0\n    assert dt.isoweekday() == 1  # Monday = 1 (ISO)\n\n    year, week, weekday = dt.isocalendar()\n    assert year == 2024 and week == 3 and weekday == 1\n\n    start_of_week = dt - timedelta(days=dt.weekday())\n    assert start_of_week.weekday() == 0\n\n\ndef test_start_end_of_day():\n    \"\"\"Get start and end of day.\"\"\"\n    dt = datetime(2024, 1, 15, 14, 30, 45)\n\n    start_of_day = datetime.combine(dt.date(), dt_time.min)\n    assert start_of_day.hour == 0 and start_of_day.minute == 0\n\n    end_of_day = datetime.combine(dt.date(), dt_time.max)\n    assert end_of_day.hour == 23 and end_of_day.minute == 59\n\n\ndef test_start_end_of_month():\n    \"\"\"Get start and end of month.\"\"\"\n    dt = datetime(2024, 1, 15, 14, 30, 45)\n\n    start_of_month = dt.replace(\n        day=1, hour=0, minute=0, second=0, microsecond=0\n    )\n    assert start_of_month.day == 1\n\n    last_day = calendar.monthrange(dt.year, dt.month)[1]\n    end_of_month = dt.replace(day=last_day)\n    assert end_of_month.day == 31\n\n\ndef test_calendar_operations():\n    \"\"\"Calendar module operations.\"\"\"\n    assert calendar.isleap(2024) is True\n    assert calendar.isleap(2023) is False\n\n    weekday, days = calendar.monthrange(2024, 2)\n    assert days == 29  # Leap year\n\n\ndef test_date_range():\n    \"\"\"Generate date ranges.\"\"\"\n\n    def date_range(start, end, step=timedelta(days=1)):\n        current = start\n        while current <= end:\n            yield current\n            current += step\n\n    start = date(2024, 1, 1)\n    end = date(2024, 1, 7)\n    dates = list(date_range(start, end))\n    assert len(dates) == 7\n    assert dates[0] == start\n    assert dates[-1] == end\n\n\ndef test_age_calculation():\n    \"\"\"Calculate age from birthdate.\"\"\"\n\n    def calculate_age(birthdate, reference_date=None):\n        if reference_date is None:\n            reference_date = date.today()\n        age = reference_date.year - birthdate.year\n        if (reference_date.month, reference_date.day) < (\n            birthdate.month,\n            birthdate.day,\n        ):\n            age -= 1\n        return age\n\n    birthdate = date(1990, 6, 15)\n    reference = date(2024, 1, 15)\n    assert calculate_age(birthdate, reference) == 33\n\n    reference_after = date(2024, 7, 1)\n    assert calculate_age(birthdate, reference_after) == 34\n\n\ndef test_time_ago():\n    \"\"\"Human readable time differences.\"\"\"\n\n    def time_ago(dt, now=None):\n        if now is None:\n            now = datetime.now()\n        diff = now - dt\n        seconds = diff.total_seconds()\n        if seconds < 60:\n            return \"just now\"\n        elif seconds < 3600:\n            minutes = int(seconds // 60)\n            return f\"{minutes} minute{'s' if minutes != 1 else ''} ago\"\n        elif seconds < 86400:\n            hours = int(seconds // 3600)\n            return f\"{hours} hour{'s' if hours != 1 else ''} ago\"\n        else:\n            days = int(seconds // 86400)\n            return f\"{days} day{'s' if days != 1 else ''} ago\"\n\n    now = datetime(2024, 1, 15, 12, 0, 0)\n    assert time_ago(now - timedelta(seconds=30), now) == \"just now\"\n    assert time_ago(now - timedelta(minutes=5), now) == \"5 minutes ago\"\n    assert time_ago(now - timedelta(hours=2), now) == \"2 hours ago\"\n    assert time_ago(now - timedelta(days=3), now) == \"3 days ago\"\n\n\ndef test_business_days():\n    \"\"\"Generate business days (skip weekends).\"\"\"\n\n    def business_days(start, end):\n        current = start\n        while current <= end:\n            if current.weekday() < 5:\n                yield current\n            current += timedelta(days=1)\n\n    start = date(2024, 1, 15)  # Monday\n    end = date(2024, 1, 21)  # Sunday\n    bdays = list(business_days(start, end))\n    assert len(bdays) == 5  # Mon-Fri\n    assert all(d.weekday() < 5 for d in bdays)\n"
  },
  {
    "path": "src/basic/dict.py",
    "content": "\"\"\"Python Dictionary Examples\n\nSource code for docs/notes/basic/python-dict.rst\n\"\"\"\n\nimport pytest\nfrom collections import defaultdict, OrderedDict\nfrom functools import lru_cache\n\n\n# Create a Dictionary\ndef create_dict_literal():\n    \"\"\"Create dict using literal syntax.\"\"\"\n    return {\"key\": \"value\", \"num\": 42}\n\n\ndef create_dict_constructor():\n    \"\"\"Create dict using constructor.\"\"\"\n    return dict(key=\"value\", num=42)\n\n\ndef create_dict_comprehension(n: int) -> dict:\n    \"\"\"Create dict using comprehension.\"\"\"\n    return {x: x**2 for x in range(n)}\n\n\n# Get Keys, Values, Items\ndef get_keys(d: dict) -> list:\n    \"\"\"Get all keys from dictionary.\"\"\"\n    return list(d.keys())\n\n\ndef get_values(d: dict) -> list:\n    \"\"\"Get all values from dictionary.\"\"\"\n    return list(d.values())\n\n\ndef get_items(d: dict) -> list:\n    \"\"\"Get all key-value pairs.\"\"\"\n    return list(d.items())\n\n\n# Find Common Keys\ndef find_common_keys(a: dict, b: dict) -> set:\n    \"\"\"Find keys that exist in both dictionaries.\"\"\"\n    return a.keys() & b.keys()\n\n\n# Set Default Value\ndef setdefault_example():\n    \"\"\"Use setdefault to initialize missing keys.\"\"\"\n    d = {}\n    d.setdefault(\"key\", []).append(\"value\")\n    return d\n\n\ndef defaultdict_example():\n    \"\"\"Use defaultdict for automatic default values.\"\"\"\n    d = defaultdict(list)\n    d[\"key\"].append(\"value\")\n    return dict(d)\n\n\n# Merge Dictionaries\ndef merge_dicts_operator(a: dict, b: dict) -> dict:\n    \"\"\"Merge dicts using | operator (Python 3.9+).\"\"\"\n    return a | b\n\n\ndef merge_dicts_unpack(a: dict, b: dict) -> dict:\n    \"\"\"Merge dicts using unpacking (Python 3.5+).\"\"\"\n    return {**a, **b}\n\n\n# Dictionary Comprehension\ndef dict_comprehension_filter(n: int) -> dict:\n    \"\"\"Dict comprehension with filter.\"\"\"\n    return {x: x**2 for x in range(n) if x % 2 == 0}\n\n\ndef swap_keys_values(d: dict) -> dict:\n    \"\"\"Swap dictionary keys and values.\"\"\"\n    return {v: k for k, v in d.items()}\n\n\n# Emulating a Dictionary\nclass EmuDict:\n    \"\"\"Custom dictionary-like class.\"\"\"\n\n    def __init__(self, data=None):\n        self._dict = data or {}\n\n    def __repr__(self):\n        return f\"EmuDict({self._dict})\"\n\n    def __getitem__(self, key):\n        return self._dict[key]\n\n    def __setitem__(self, key, val):\n        self._dict[key] = val\n\n    def __delitem__(self, key):\n        del self._dict[key]\n\n    def __contains__(self, key):\n        return key in self._dict\n\n    def __iter__(self):\n        return iter(self._dict)\n\n    def __len__(self):\n        return len(self._dict)\n\n\n# LRU Cache\nclass LRUCache:\n    \"\"\"LRU Cache implementation using OrderedDict.\"\"\"\n\n    def __init__(self, maxsize=128):\n        self._maxsize = maxsize\n        self._cache = OrderedDict()\n\n    def get(self, key):\n        if key not in self._cache:\n            return None\n        self._cache.move_to_end(key)\n        return self._cache[key]\n\n    def put(self, key, value):\n        if key in self._cache:\n            self._cache.move_to_end(key)\n        self._cache[key] = value\n        if len(self._cache) > self._maxsize:\n            self._cache.popitem(last=False)\n\n\n@lru_cache(maxsize=128)\ndef fibonacci(n: int) -> int:\n    \"\"\"Fibonacci with LRU cache decorator.\"\"\"\n    if n < 2:\n        return n\n    return fibonacci(n - 1) + fibonacci(n - 2)\n\n\n# Tests\nclass TestDictCreation:\n    def test_literal(self):\n        assert create_dict_literal() == {\"key\": \"value\", \"num\": 42}\n\n    def test_constructor(self):\n        assert create_dict_constructor() == {\"key\": \"value\", \"num\": 42}\n\n    def test_comprehension(self):\n        assert create_dict_comprehension(3) == {0: 0, 1: 1, 2: 4}\n\n\nclass TestDictAccess:\n    def test_get_keys(self):\n        assert get_keys({\"a\": 1, \"b\": 2}) == [\"a\", \"b\"]\n\n    def test_get_values(self):\n        assert get_values({\"a\": 1, \"b\": 2}) == [1, 2]\n\n    def test_get_items(self):\n        assert get_items({\"a\": 1}) == [(\"a\", 1)]\n\n\nclass TestDictOperations:\n    def test_find_common_keys(self):\n        assert find_common_keys({\"a\": 1, \"b\": 2}, {\"b\": 3, \"c\": 4}) == {\"b\"}\n\n    def test_setdefault(self):\n        assert setdefault_example() == {\"key\": [\"value\"]}\n\n    def test_defaultdict(self):\n        assert defaultdict_example() == {\"key\": [\"value\"]}\n\n    def test_merge_operator(self):\n        assert merge_dicts_operator({\"a\": 1}, {\"b\": 2}) == {\"a\": 1, \"b\": 2}\n\n    def test_merge_unpack(self):\n        assert merge_dicts_unpack({\"a\": 1}, {\"b\": 2}) == {\"a\": 1, \"b\": 2}\n\n\nclass TestDictComprehension:\n    def test_filter(self):\n        assert dict_comprehension_filter(6) == {0: 0, 2: 4, 4: 16}\n\n    def test_swap(self):\n        assert swap_keys_values({\"a\": 1, \"b\": 2}) == {1: \"a\", 2: \"b\"}\n\n\nclass TestEmuDict:\n    def test_getitem(self):\n        d = EmuDict({\"a\": 1})\n        assert d[\"a\"] == 1\n\n    def test_setitem(self):\n        d = EmuDict()\n        d[\"a\"] = 1\n        assert d[\"a\"] == 1\n\n    def test_contains(self):\n        d = EmuDict({\"a\": 1})\n        assert \"a\" in d\n        assert \"b\" not in d\n\n    def test_len(self):\n        assert len(EmuDict({\"a\": 1, \"b\": 2})) == 2\n\n\nclass TestLRUCache:\n    def test_get_put(self):\n        cache = LRUCache(maxsize=2)\n        cache.put(\"a\", 1)\n        cache.put(\"b\", 2)\n        assert cache.get(\"a\") == 1\n\n    def test_eviction(self):\n        cache = LRUCache(maxsize=2)\n        cache.put(\"a\", 1)\n        cache.put(\"b\", 2)\n        cache.put(\"c\", 3)  # evicts \"a\"\n        assert cache.get(\"a\") is None\n\n    def test_fibonacci(self):\n        assert fibonacci(10) == 55\n        assert fibonacci(20) == 6765\n"
  },
  {
    "path": "src/basic/fileio_.py",
    "content": "\"\"\"Tests for file I/O operations.\"\"\"\n\nimport csv\nimport gzip\nimport json\nimport tempfile\nimport zipfile\nfrom pathlib import Path\n\n\ndef test_read_write_text(tmp_path):\n    \"\"\"Read and write text files.\"\"\"\n    p = tmp_path / \"test.txt\"\n    content = \"Hello, World!\\nLine 2\"\n\n    with open(p, \"w\", encoding=\"utf-8\") as f:\n        f.write(content)\n\n    with open(p, encoding=\"utf-8\") as f:\n        result = f.read()\n\n    assert result == content\n\n\ndef test_read_lines(tmp_path):\n    \"\"\"Read file line by line.\"\"\"\n    p = tmp_path / \"lines.txt\"\n    p.write_text(\"line1\\nline2\\nline3\\n\")\n\n    lines = []\n    with open(p, encoding=\"utf-8\") as f:\n        for line in f:\n            lines.append(line.rstrip())\n\n    assert lines == [\"line1\", \"line2\", \"line3\"]\n\n\ndef test_write_modes(tmp_path):\n    \"\"\"Test different write modes.\"\"\"\n    p = tmp_path / \"modes.txt\"\n\n    # Write mode\n    with open(p, \"w\", encoding=\"utf-8\") as f:\n        f.write(\"first\")\n\n    # Append mode\n    with open(p, \"a\", encoding=\"utf-8\") as f:\n        f.write(\" second\")\n\n    assert p.read_text() == \"first second\"\n\n\ndef test_binary_files(tmp_path):\n    \"\"\"Read and write binary files.\"\"\"\n    p = tmp_path / \"binary.bin\"\n    data = b\"\\x00\\x01\\x02\\xff\"\n\n    with open(p, \"wb\") as f:\n        f.write(data)\n\n    with open(p, \"rb\") as f:\n        result = f.read()\n\n    assert result == data\n\n\ndef test_pathlib_properties(tmp_path):\n    \"\"\"Test pathlib path properties.\"\"\"\n    p = tmp_path / \"folder\" / \"report.pdf\"\n\n    assert p.name == \"report.pdf\"\n    assert p.stem == \"report\"\n    assert p.suffix == \".pdf\"\n    assert p.parent == tmp_path / \"folder\"\n\n\ndef test_pathlib_with_suffix():\n    \"\"\"Change path suffix.\"\"\"\n    p = Path(\"/home/user/doc.txt\")\n    new_p = p.with_suffix(\".md\")\n    assert new_p.suffix == \".md\"\n    assert new_p.stem == \"doc\"\n\n\ndef test_pathlib_read_write(tmp_path):\n    \"\"\"Read and write with pathlib.\"\"\"\n    p = tmp_path / \"pathlib.txt\"\n\n    p.write_text(\"pathlib content\", encoding=\"utf-8\")\n    content = p.read_text(encoding=\"utf-8\")\n\n    assert content == \"pathlib content\"\n\n\ndef test_pathlib_bytes(tmp_path):\n    \"\"\"Read and write bytes with pathlib.\"\"\"\n    p = tmp_path / \"bytes.bin\"\n    data = b\"binary data\"\n\n    p.write_bytes(data)\n    result = p.read_bytes()\n\n    assert result == data\n\n\ndef test_list_directory(tmp_path):\n    \"\"\"List directory contents.\"\"\"\n    (tmp_path / \"file1.txt\").touch()\n    (tmp_path / \"file2.txt\").touch()\n    (tmp_path / \"subdir\").mkdir()\n\n    items = list(tmp_path.iterdir())\n    assert len(items) == 3\n\n    files = [i for i in items if i.is_file()]\n    dirs = [i for i in items if i.is_dir()]\n    assert len(files) == 2\n    assert len(dirs) == 1\n\n\ndef test_glob_pattern(tmp_path):\n    \"\"\"Find files with glob patterns.\"\"\"\n    (tmp_path / \"a.py\").touch()\n    (tmp_path / \"b.py\").touch()\n    (tmp_path / \"c.txt\").touch()\n\n    py_files = list(tmp_path.glob(\"*.py\"))\n    assert len(py_files) == 2\n\n\ndef test_recursive_glob(tmp_path):\n    \"\"\"Recursive glob pattern.\"\"\"\n    (tmp_path / \"a.py\").touch()\n    subdir = tmp_path / \"sub\"\n    subdir.mkdir()\n    (subdir / \"b.py\").touch()\n\n    py_files = list(tmp_path.rglob(\"*.py\"))\n    assert len(py_files) == 2\n\n\ndef test_mkdir_parents(tmp_path):\n    \"\"\"Create nested directories.\"\"\"\n    nested = tmp_path / \"a\" / \"b\" / \"c\"\n    nested.mkdir(parents=True, exist_ok=True)\n\n    assert nested.exists()\n    assert nested.is_dir()\n\n\ndef test_path_exists(tmp_path):\n    \"\"\"Check path existence and type.\"\"\"\n    file_path = tmp_path / \"file.txt\"\n    file_path.touch()\n\n    dir_path = tmp_path / \"dir\"\n    dir_path.mkdir()\n\n    assert file_path.exists()\n    assert file_path.is_file()\n    assert not file_path.is_dir()\n\n    assert dir_path.exists()\n    assert dir_path.is_dir()\n    assert not dir_path.is_file()\n\n\ndef test_temporary_file():\n    \"\"\"Create temporary file.\"\"\"\n    with tempfile.NamedTemporaryFile(\n        mode=\"w\", suffix=\".txt\", delete=True\n    ) as f:\n        f.write(\"temp content\")\n        f.flush()\n        assert Path(f.name).exists()\n        assert f.name.endswith(\".txt\")\n\n\ndef test_temporary_directory():\n    \"\"\"Create temporary directory.\"\"\"\n    with tempfile.TemporaryDirectory() as tmpdir:\n        p = Path(tmpdir)\n        assert p.exists()\n        (p / \"file.txt\").write_text(\"content\")\n        assert (p / \"file.txt\").exists()\n    # Directory should be deleted after context\n    assert not p.exists()\n\n\ndef test_csv_read_write(tmp_path):\n    \"\"\"Read and write CSV files.\"\"\"\n    csv_path = tmp_path / \"data.csv\"\n\n    # Write\n    with open(csv_path, \"w\", newline=\"\", encoding=\"utf-8\") as f:\n        writer = csv.writer(f)\n        writer.writerow([\"name\", \"age\"])\n        writer.writerow([\"Alice\", 30])\n        writer.writerow([\"Bob\", 25])\n\n    # Read\n    rows = []\n    with open(csv_path, newline=\"\", encoding=\"utf-8\") as f:\n        reader = csv.reader(f)\n        for row in reader:\n            rows.append(row)\n\n    assert rows[0] == [\"name\", \"age\"]\n    assert rows[1] == [\"Alice\", \"30\"]\n\n\ndef test_csv_dictreader(tmp_path):\n    \"\"\"Read CSV with DictReader.\"\"\"\n    csv_path = tmp_path / \"data.csv\"\n    csv_path.write_text(\"name,age\\nAlice,30\\nBob,25\")\n\n    with open(csv_path, newline=\"\", encoding=\"utf-8\") as f:\n        reader = csv.DictReader(f)\n        rows = list(reader)\n\n    assert rows[0][\"name\"] == \"Alice\"\n    assert rows[0][\"age\"] == \"30\"\n\n\ndef test_json_read_write(tmp_path):\n    \"\"\"Read and write JSON files.\"\"\"\n    json_path = tmp_path / \"data.json\"\n    data = {\"name\": \"Alice\", \"scores\": [95, 87, 92]}\n\n    with open(json_path, \"w\", encoding=\"utf-8\") as f:\n        json.dump(data, f)\n\n    with open(json_path, encoding=\"utf-8\") as f:\n        loaded = json.load(f)\n\n    assert loaded == data\n\n\ndef test_gzip_read_write(tmp_path):\n    \"\"\"Read and write gzip files.\"\"\"\n    gz_path = tmp_path / \"file.txt.gz\"\n    content = \"compressed content\"\n\n    with gzip.open(gz_path, \"wt\", encoding=\"utf-8\") as f:\n        f.write(content)\n\n    with gzip.open(gz_path, \"rt\", encoding=\"utf-8\") as f:\n        result = f.read()\n\n    assert result == content\n\n\ndef test_zipfile_create_extract(tmp_path):\n    \"\"\"Create and extract zip archives.\"\"\"\n    zip_path = tmp_path / \"archive.zip\"\n    file1 = tmp_path / \"file1.txt\"\n    file1.write_text(\"content 1\")\n\n    # Create zip\n    with zipfile.ZipFile(zip_path, \"w\") as zf:\n        zf.write(file1, \"file1.txt\")\n        zf.writestr(\"file2.txt\", \"content 2\")\n\n    # List contents\n    with zipfile.ZipFile(zip_path, \"r\") as zf:\n        names = zf.namelist()\n        assert \"file1.txt\" in names\n        assert \"file2.txt\" in names\n\n    # Extract\n    extract_dir = tmp_path / \"extracted\"\n    extract_dir.mkdir()\n    with zipfile.ZipFile(zip_path, \"r\") as zf:\n        zf.extractall(extract_dir)\n\n    assert (extract_dir / \"file1.txt\").read_text() == \"content 1\"\n    assert (extract_dir / \"file2.txt\").read_text() == \"content 2\"\n\n\ndef test_symlink(tmp_path):\n    \"\"\"Create and read symbolic links.\"\"\"\n    target = tmp_path / \"target.txt\"\n    target.write_text(\"target content\")\n\n    link = tmp_path / \"link.txt\"\n    link.symlink_to(target)\n\n    assert link.is_symlink()\n    assert link.read_text() == \"target content\"\n    assert link.resolve() == target.resolve()\n\n\ndef test_file_stat(tmp_path):\n    \"\"\"Get file statistics.\"\"\"\n    p = tmp_path / \"file.txt\"\n    p.write_text(\"some content\")\n\n    stat = p.stat()\n    assert stat.st_size == len(\"some content\")\n    assert stat.st_mtime > 0\n\n\ndef test_shutil_copy(tmp_path):\n    \"\"\"Copy files with shutil.\"\"\"\n    import shutil\n\n    src = tmp_path / \"source.txt\"\n    src.write_text(\"content\")\n\n    # copy - content only\n    dst1 = tmp_path / \"dest1.txt\"\n    shutil.copy(src, dst1)\n    assert dst1.read_text() == \"content\"\n\n    # copy2 - preserves metadata\n    dst2 = tmp_path / \"dest2.txt\"\n    shutil.copy2(src, dst2)\n    assert dst2.read_text() == \"content\"\n\n    # copy to directory\n    subdir = tmp_path / \"subdir\"\n    subdir.mkdir()\n    shutil.copy(src, subdir)\n    assert (subdir / \"source.txt\").exists()\n\n\ndef test_shutil_copytree(tmp_path):\n    \"\"\"Copy directory tree with shutil.\"\"\"\n    import shutil\n\n    # Create source structure\n    src = tmp_path / \"source\"\n    src.mkdir()\n    (src / \"file1.txt\").write_text(\"content1\")\n    (src / \"sub\").mkdir()\n    (src / \"sub\" / \"file2.txt\").write_text(\"content2\")\n\n    # Copy tree\n    dst = tmp_path / \"dest\"\n    shutil.copytree(src, dst)\n\n    assert (dst / \"file1.txt\").read_text() == \"content1\"\n    assert (dst / \"sub\" / \"file2.txt\").read_text() == \"content2\"\n\n\ndef test_shutil_copytree_ignore(tmp_path):\n    \"\"\"Copy directory tree with ignore patterns.\"\"\"\n    import shutil\n\n    src = tmp_path / \"source\"\n    src.mkdir()\n    (src / \"keep.txt\").write_text(\"keep\")\n    (src / \"ignore.pyc\").write_text(\"ignore\")\n\n    dst = tmp_path / \"dest\"\n    shutil.copytree(src, dst, ignore=shutil.ignore_patterns(\"*.pyc\"))\n\n    assert (dst / \"keep.txt\").exists()\n    assert not (dst / \"ignore.pyc\").exists()\n\n\ndef test_shutil_copytree_dirs_exist_ok(tmp_path):\n    \"\"\"Copy into existing directory.\"\"\"\n    import shutil\n\n    src = tmp_path / \"source\"\n    src.mkdir()\n    (src / \"new.txt\").write_text(\"new\")\n\n    dst = tmp_path / \"dest\"\n    dst.mkdir()\n    (dst / \"existing.txt\").write_text(\"existing\")\n\n    shutil.copytree(src, dst, dirs_exist_ok=True)\n\n    assert (dst / \"new.txt\").exists()\n    assert (dst / \"existing.txt\").exists()\n\n\ndef test_shutil_move(tmp_path):\n    \"\"\"Move files and directories with shutil.\"\"\"\n    import shutil\n\n    # Move file\n    src = tmp_path / \"source.txt\"\n    src.write_text(\"content\")\n    dst = tmp_path / \"dest.txt\"\n    shutil.move(src, dst)\n\n    assert not src.exists()\n    assert dst.read_text() == \"content\"\n\n    # Move to directory\n    subdir = tmp_path / \"subdir\"\n    subdir.mkdir()\n    shutil.move(dst, subdir)\n    assert (subdir / \"dest.txt\").exists()\n\n\ndef test_shutil_rmtree(tmp_path):\n    \"\"\"Remove directory tree with shutil.\"\"\"\n    import shutil\n\n    d = tmp_path / \"to_delete\"\n    d.mkdir()\n    (d / \"file.txt\").write_text(\"content\")\n    (d / \"sub\").mkdir()\n    (d / \"sub\" / \"nested.txt\").write_text(\"nested\")\n\n    shutil.rmtree(d)\n    assert not d.exists()\n\n\ndef test_shutil_disk_usage(tmp_path):\n    \"\"\"Get disk usage statistics.\"\"\"\n    import shutil\n\n    usage = shutil.disk_usage(tmp_path)\n    assert usage.total > 0\n    assert usage.free > 0\n    assert usage.used >= 0\n\n\ndef test_shutil_which():\n    \"\"\"Find executable in PATH.\"\"\"\n    import shutil\n\n    python = shutil.which(\"python\")\n    assert python is not None\n\n    nonexistent = shutil.which(\"nonexistent_command_xyz\")\n    assert nonexistent is None\n\n\ndef test_shutil_make_archive(tmp_path):\n    \"\"\"Create archives with shutil.\"\"\"\n    import shutil\n\n    # Create source\n    src = tmp_path / \"source\"\n    src.mkdir()\n    (src / \"file.txt\").write_text(\"content\")\n\n    # Create zip archive\n    archive = shutil.make_archive(str(tmp_path / \"backup\"), \"zip\", src)\n    assert Path(archive).exists()\n    assert archive.endswith(\".zip\")\n\n\ndef test_shutil_unpack_archive(tmp_path):\n    \"\"\"Extract archives with shutil.\"\"\"\n    import shutil\n\n    # Create and pack\n    src = tmp_path / \"source\"\n    src.mkdir()\n    (src / \"file.txt\").write_text(\"content\")\n    archive = shutil.make_archive(str(tmp_path / \"backup\"), \"zip\", src)\n\n    # Unpack\n    extract = tmp_path / \"extracted\"\n    shutil.unpack_archive(archive, extract)\n    assert (extract / \"file.txt\").read_text() == \"content\"\n"
  },
  {
    "path": "src/basic/func.py",
    "content": "\"\"\"Python Function Examples\n\nSource code for docs/notes/basic/python-func.rst\n\"\"\"\n\nfrom functools import lru_cache, partial, reduce, singledispatch, wraps\n\nimport pytest\n\n\n# Default Arguments\ndef greet(name: str, greeting: str = \"Hello\") -> str:\n    \"\"\"Greet with optional greeting.\"\"\"\n    return f\"{greeting}, {name}!\"\n\n\ndef good_default(items=None):\n    \"\"\"Correct way to use mutable default.\"\"\"\n    if items is None:\n        items = []\n    items.append(1)\n    return items\n\n\n# Variable Arguments\ndef sum_all(*args) -> int:\n    \"\"\"Sum all positional arguments.\"\"\"\n    return sum(args)\n\n\ndef format_info(**kwargs) -> str:\n    \"\"\"Format keyword arguments.\"\"\"\n    return \", \".join(f\"{k}={v}\" for k, v in kwargs.items())\n\n\ndef mixed_args(a, b=None, *args, **kwargs):\n    \"\"\"Function with mixed argument types.\"\"\"\n    return {\"a\": a, \"b\": b, \"args\": args, \"kwargs\": kwargs}\n\n\n# Keyword-Only Arguments\ndef keyword_only(a, b, *, kw):\n    \"\"\"Function with keyword-only argument.\"\"\"\n    return a + b + kw\n\n\ndef keyword_only_default(a, *, kw=10):\n    \"\"\"Keyword-only with default value.\"\"\"\n    return a + kw\n\n\n# Positional-Only Arguments (Python 3.8+)\ndef positional_only(a, b, /, c):\n    \"\"\"Function with positional-only arguments.\"\"\"\n    return a + b + c\n\n\ndef combined_args(a, /, b, *, c):\n    \"\"\"Positional-only and keyword-only combined.\"\"\"\n    return a + b + c\n\n\n# Lambda\nsquare = lambda x: x**2\nadd = lambda a, b: a + b\nmax_val = lambda a, b: a if a > b else b\n\n\n# Closure\ndef make_multiplier(n: int):\n    \"\"\"Create a multiplier function.\"\"\"\n\n    def multiplier(x: int) -> int:\n        return x * n\n\n    return multiplier\n\n\ndef make_counter():\n    \"\"\"Create a counter with mutable state.\"\"\"\n    count = 0\n\n    def counter():\n        nonlocal count\n        count += 1\n        return count\n\n    return counter\n\n\n# Generator\ndef fibonacci(n: int):\n    \"\"\"Generate fibonacci sequence.\"\"\"\n    a, b = 0, 1\n    for _ in range(n):\n        yield a\n        a, b = b, a + b\n\n\n# Decorator\ndef log_calls(func):\n    \"\"\"Decorator that logs function calls.\"\"\"\n\n    @wraps(func)\n    def wrapper(*args, **kwargs):\n        wrapper.call_count += 1\n        return func(*args, **kwargs)\n\n    wrapper.call_count = 0\n    return wrapper\n\n\n# Decorator with Arguments\ndef repeat(times: int):\n    \"\"\"Decorator that repeats function calls.\"\"\"\n\n    def decorator(func):\n        @wraps(func)\n        def wrapper(*args, **kwargs):\n            result = None\n            for _ in range(times):\n                result = func(*args, **kwargs)\n            return result\n\n        return wrapper\n\n    return decorator\n\n\n# Class Decorator\nclass CountCalls:\n    \"\"\"Decorator class that counts calls.\"\"\"\n\n    def __init__(self, func):\n        self.func = func\n        self.count = 0\n        wraps(func)(self)\n\n    def __call__(self, *args, **kwargs):\n        self.count += 1\n        return self.func(*args, **kwargs)\n\n\n# Cache\n@lru_cache(maxsize=None)\ndef fib_cached(n: int) -> int:\n    \"\"\"Fibonacci with caching.\"\"\"\n    if n < 2:\n        return n\n    return fib_cached(n - 1) + fib_cached(n - 2)\n\n\n# Partial\ndef power(base: int, exponent: int) -> int:\n    \"\"\"Raise base to exponent.\"\"\"\n    return base**exponent\n\n\nsquare_partial = partial(power, exponent=2)\ncube_partial = partial(power, exponent=3)\n\n\n# Singledispatch\n@singledispatch\ndef process(arg):\n    \"\"\"Generic function with type dispatch.\"\"\"\n    return f\"Default: {arg}\"\n\n\n@process.register(int)\ndef _(arg):\n    return f\"Integer: {arg * 2}\"\n\n\n@process.register(list)\ndef _(arg):\n    return f\"List with {len(arg)} items\"\n\n\n# Callable Class\nclass Adder:\n    \"\"\"Callable class that adds a fixed value.\"\"\"\n\n    def __init__(self, n: int):\n        self.n = n\n\n    def __call__(self, x: int) -> int:\n        return self.n + x\n\n\n# Higher-order functions\ndef apply_twice(func, x):\n    \"\"\"Apply function twice.\"\"\"\n    return func(func(x))\n\n\n# Tests\nclass TestDefaultArguments:\n    def test_default(self):\n        assert greet(\"Alice\") == \"Hello, Alice!\"\n\n    def test_custom(self):\n        assert greet(\"Bob\", \"Hi\") == \"Hi, Bob!\"\n\n    def test_mutable_default(self):\n        assert good_default() == [1]\n        assert good_default() == [1]  # not [1, 1]\n\n\nclass TestVariableArguments:\n    def test_args(self):\n        assert sum_all(1, 2, 3, 4, 5) == 15\n\n    def test_kwargs(self):\n        result = format_info(name=\"Alice\", age=30)\n        assert \"name=Alice\" in result\n        assert \"age=30\" in result\n\n    def test_mixed(self):\n        result = mixed_args(1, 2, 3, 4, x=5)\n        assert result[\"a\"] == 1\n        assert result[\"b\"] == 2\n        assert result[\"args\"] == (3, 4)\n        assert result[\"kwargs\"] == {\"x\": 5}\n\n\nclass TestKeywordOnly:\n    def test_keyword_only(self):\n        assert keyword_only(1, 2, kw=3) == 6\n\n    def test_keyword_only_default(self):\n        assert keyword_only_default(5) == 15\n        assert keyword_only_default(5, kw=20) == 25\n\n\nclass TestPositionalOnly:\n    def test_positional_only(self):\n        assert positional_only(1, 2, 3) == 6\n        assert positional_only(1, 2, c=3) == 6\n\n    def test_combined(self):\n        assert combined_args(1, 2, c=3) == 6\n        assert combined_args(1, b=2, c=3) == 6\n\n\nclass TestLambda:\n    def test_square(self):\n        assert square(5) == 25\n\n    def test_add(self):\n        assert add(2, 3) == 5\n\n    def test_conditional(self):\n        assert max_val(3, 5) == 5\n        assert max_val(7, 2) == 7\n\n\nclass TestClosure:\n    def test_multiplier(self):\n        double = make_multiplier(2)\n        triple = make_multiplier(3)\n        assert double(5) == 10\n        assert triple(5) == 15\n\n    def test_counter(self):\n        counter = make_counter()\n        assert counter() == 1\n        assert counter() == 2\n        assert counter() == 3\n\n\nclass TestGenerator:\n    def test_fibonacci(self):\n        assert list(fibonacci(10)) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n\nclass TestDecorator:\n    def test_log_calls(self):\n        @log_calls\n        def example():\n            return \"result\"\n\n        example()\n        example()\n        assert example.call_count == 2\n        assert example.__name__ == \"example\"\n\n    def test_repeat(self):\n        counter = {\"count\": 0}\n\n        @repeat(3)\n        def increment():\n            counter[\"count\"] += 1\n\n        increment()\n        assert counter[\"count\"] == 3\n\n\nclass TestClassDecorator:\n    def test_count_calls(self):\n        @CountCalls\n        def example():\n            return \"result\"\n\n        example()\n        example()\n        assert example.count == 2\n\n\nclass TestCache:\n    def test_fib_cached(self):\n        fib_cached.cache_clear()\n        assert fib_cached(10) == 55\n        assert fib_cached(20) == 6765\n        info = fib_cached.cache_info()\n        assert info.hits > 0\n\n\nclass TestPartial:\n    def test_square(self):\n        assert square_partial(5) == 25\n\n    def test_cube(self):\n        assert cube_partial(5) == 125\n\n\nclass TestSingledispatch:\n    def test_default(self):\n        assert process(\"hello\") == \"Default: hello\"\n\n    def test_int(self):\n        assert process(5) == \"Integer: 10\"\n\n    def test_list(self):\n        assert process([1, 2, 3]) == \"List with 3 items\"\n\n\nclass TestCallable:\n    def test_adder(self):\n        add_five = Adder(5)\n        assert add_five(10) == 15\n        assert callable(add_five)\n\n\nclass TestHigherOrder:\n    def test_apply_twice(self):\n        assert apply_twice(lambda x: x * 2, 3) == 12\n\n    def test_map(self):\n        assert list(map(square, [1, 2, 3])) == [1, 4, 9]\n\n    def test_filter(self):\n        assert list(filter(lambda x: x > 2, [1, 2, 3, 4])) == [3, 4]\n\n    def test_reduce(self):\n        assert reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]) == 15\n        assert reduce(lambda x, y: x * y, [1, 2, 3, 4, 5]) == 120\n"
  },
  {
    "path": "src/basic/future_.py",
    "content": "\"\"\"Python Future Examples\n\nSource code for docs/notes/basic/python-future.rst\n\"\"\"\n\nfrom __future__ import annotations\nimport __future__\nimport sys\nimport pytest\n\n\n# List Future Features\ndef get_all_features() -> list[str]:\n    \"\"\"Get all available future features.\"\"\"\n    return __future__.all_feature_names\n\n\ndef get_feature_info(name: str) -> tuple:\n    \"\"\"Get feature info (optional, mandatory versions).\"\"\"\n    feature = getattr(__future__, name, None)\n    if feature:\n        return (feature.optional, feature.mandatory)\n    return None\n\n\n# Annotations Example\nclass Node:\n    \"\"\"Example using forward reference with annotations.\"\"\"\n\n    def __init__(self, value: int, next: Node | None = None):\n        self.value = value\n        self.next = next\n\n    def append(self, value: int) -> Node:\n        \"\"\"Append value and return new node.\"\"\"\n        new_node = Node(value)\n        self.next = new_node\n        return new_node\n\n\ndef get_annotations(func) -> dict:\n    \"\"\"Get function annotations as strings.\"\"\"\n    return func.__annotations__\n\n\n# Division\ndef true_division(a: int, b: int) -> float:\n    \"\"\"True division (/).\"\"\"\n    return a / b\n\n\ndef floor_division(a: int, b: int) -> int:\n    \"\"\"Floor division (//).\"\"\"\n    return a // b\n\n\n# Version Check\ndef check_version(major: int, minor: int) -> bool:\n    \"\"\"Check if Python version is at least major.minor.\"\"\"\n    return sys.version_info >= (major, minor)\n\n\ndef get_version_string() -> str:\n    \"\"\"Get Python version as string.\"\"\"\n    return f\"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}\"\n\n\n# Tests\nclass TestFutureFeatures:\n    def test_get_all_features(self):\n        features = get_all_features()\n        assert \"annotations\" in features\n        assert \"division\" in features\n        assert \"print_function\" in features\n\n    def test_get_feature_info(self):\n        info = get_feature_info(\"annotations\")\n        assert info is not None\n        assert len(info) == 2\n\n\nclass TestAnnotations:\n    def test_node_creation(self):\n        node = Node(1)\n        assert node.value == 1\n        assert node.next is None\n\n    def test_node_append(self):\n        node = Node(1)\n        node2 = node.append(2)\n        assert node.next is node2\n        assert node2.value == 2\n\n    def test_annotations_are_strings(self):\n        # With from __future__ import annotations,\n        # annotations are stored as strings\n        annotations = get_annotations(Node.__init__)\n        assert \"value\" in annotations\n        assert \"next\" in annotations\n\n\nclass TestDivision:\n    def test_true_division(self):\n        assert true_division(5, 2) == 2.5\n        assert true_division(4, 2) == 2.0\n\n    def test_floor_division(self):\n        assert floor_division(5, 2) == 2\n        assert floor_division(7, 3) == 2\n\n\nclass TestVersionCheck:\n    def test_check_version(self):\n        # We're running Python 3.x\n        assert check_version(3, 0)\n        # Future version should be False\n        assert not check_version(99, 0)\n\n    def test_get_version_string(self):\n        version = get_version_string()\n        assert version.startswith(\"3.\")\n        parts = version.split(\".\")\n        assert len(parts) == 3\n"
  },
  {
    "path": "src/basic/generator.py",
    "content": "\"\"\"Python Generator Examples\n\nSource code for docs/notes/basic/python-generator.rst\n\"\"\"\n\nimport inspect\nfrom contextlib import contextmanager\nfrom types import GeneratorType\n\nimport pytest\n\n\n# Generator Function\ndef simple_gen():\n    \"\"\"Simple generator yielding values.\"\"\"\n    yield 1\n    yield 2\n    yield 3\n\n\ndef countdown(n: int):\n    \"\"\"Countdown generator.\"\"\"\n    while n > 0:\n        yield n\n        n -= 1\n\n\ndef fibonacci(n: int):\n    \"\"\"Generate fibonacci sequence.\"\"\"\n    a, b = 0, 1\n    for _ in range(n):\n        yield a\n        a, b = b, a + b\n\n\ndef infinite_counter(start: int = 0):\n    \"\"\"Infinite counter generator.\"\"\"\n    n = start\n    while True:\n        yield n\n        n += 1\n\n\n# Generator Expression\ndef gen_expr_sum(n: int) -> int:\n    \"\"\"Sum using generator expression.\"\"\"\n    return sum(x**2 for x in range(n))\n\n\n# Send Values\ndef accumulator():\n    \"\"\"Accumulator that receives values via send.\"\"\"\n    total = 0\n    while True:\n        value = yield total\n        if value is not None:\n            total += value\n\n\n# Generator with Return\ndef average():\n    \"\"\"Calculate average, return via StopIteration.\"\"\"\n    total = 0.0\n    count = 0\n    while True:\n        value = yield\n        if value is None:\n            break\n        total += value\n        count += 1\n    return total / count if count else 0\n\n\n# yield from\ndef chain(*iterables):\n    \"\"\"Chain multiple iterables.\"\"\"\n    for it in iterables:\n        yield from it\n\n\ndef flatten(nested):\n    \"\"\"Flatten nested lists.\"\"\"\n    for item in nested:\n        if isinstance(item, list):\n            yield from flatten(item)\n        else:\n            yield item\n\n\n# Iterable Class\nclass Range:\n    \"\"\"Custom range class using generator.\"\"\"\n\n    def __init__(self, start: int, end: int):\n        self.start = start\n        self.end = end\n\n    def __iter__(self):\n        n = self.start\n        while n < self.end:\n            yield n\n            n += 1\n\n    def __reversed__(self):\n        n = self.end - 1\n        while n >= self.start:\n            yield n\n            n -= 1\n\n\n# Pipeline\ndef filter_positive(nums):\n    \"\"\"Filter positive numbers.\"\"\"\n    for n in nums:\n        if n > 0:\n            yield n\n\n\ndef double(nums):\n    \"\"\"Double each number.\"\"\"\n    for n in nums:\n        yield n * 2\n\n\ndef read_lines(lines):\n    \"\"\"Strip whitespace from lines.\"\"\"\n    for line in lines:\n        yield line.strip()\n\n\ndef filter_comments(lines):\n    \"\"\"Filter out comment lines.\"\"\"\n    for line in lines:\n        if not line.startswith(\"#\"):\n            yield line\n\n\n# Throw and Close\ndef gen_with_exception():\n    \"\"\"Generator that handles exceptions.\"\"\"\n    try:\n        yield 1\n        yield 2\n    except ValueError:\n        yield \"caught\"\n\n\ndef gen_with_cleanup():\n    \"\"\"Generator with cleanup in finally.\"\"\"\n    try:\n        yield 1\n        yield 2\n    finally:\n        pass  # cleanup would go here\n\n\n# Context Manager\n@contextmanager\ndef capture_output():\n    \"\"\"Context manager using generator.\"\"\"\n    output = []\n    yield output\n\n\n# Tests\nclass TestGeneratorBasics:\n    def test_simple_gen(self):\n        assert list(simple_gen()) == [1, 2, 3]\n\n    def test_countdown(self):\n        assert list(countdown(3)) == [3, 2, 1]\n\n    def test_fibonacci(self):\n        assert list(fibonacci(10)) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n    def test_infinite_counter(self):\n        from itertools import islice\n\n        assert list(islice(infinite_counter(), 5)) == [0, 1, 2, 3, 4]\n        assert list(islice(infinite_counter(10), 3)) == [10, 11, 12]\n\n\nclass TestGeneratorExpression:\n    def test_gen_expr_sum(self):\n        assert gen_expr_sum(5) == 0 + 1 + 4 + 9 + 16\n\n    def test_unpack(self):\n        g = (x for x in range(3))\n        assert [*g] == [0, 1, 2]\n\n    def test_unpack_multiple(self):\n        g1 = (x for x in range(2))\n        g2 = (x**2 for x in range(2))\n        assert [*g1, *g2] == [0, 1, 0, 1]\n\n\nclass TestSend:\n    def test_accumulator(self):\n        acc = accumulator()\n        assert next(acc) == 0\n        assert acc.send(10) == 10\n        assert acc.send(20) == 30\n        assert acc.send(5) == 35\n\n\nclass TestGeneratorReturn:\n    def test_average(self):\n        g = average()\n        next(g)\n        g.send(10)\n        g.send(20)\n        g.send(30)\n        try:\n            g.send(None)\n        except StopIteration as e:\n            assert e.value == 20.0\n\n\nclass TestYieldFrom:\n    def test_chain(self):\n        assert list(chain([1, 2], [3, 4])) == [1, 2, 3, 4]\n\n    def test_flatten(self):\n        assert list(flatten([1, [2, [3, 4], 5], 6])) == [1, 2, 3, 4, 5, 6]\n\n\nclass TestIterableClass:\n    def test_range(self):\n        assert list(Range(1, 5)) == [1, 2, 3, 4]\n\n    def test_reversed_range(self):\n        assert list(reversed(Range(1, 5))) == [4, 3, 2, 1]\n\n\nclass TestPipeline:\n    def test_pipeline(self):\n        nums = [-1, 2, -3, 4, 5]\n        result = list(double(filter_positive(nums)))\n        assert result == [4, 8, 10]\n\n    def test_text_pipeline(self):\n        data = [\"  hello  \", \"# comment\", \"  world  \"]\n        result = list(filter_comments(read_lines(data)))\n        assert result == [\"hello\", \"world\"]\n\n\nclass TestThrowClose:\n    def test_throw(self):\n        g = gen_with_exception()\n        assert next(g) == 1\n        assert g.throw(ValueError) == \"caught\"\n\n    def test_close(self):\n        g = gen_with_cleanup()\n        assert next(g) == 1\n        g.close()  # should not raise\n\n\nclass TestGeneratorState:\n    def test_states(self):\n        def gen():\n            yield 1\n\n        g = gen()\n        assert inspect.getgeneratorstate(g) == \"GEN_CREATED\"\n        next(g)\n        assert inspect.getgeneratorstate(g) == \"GEN_SUSPENDED\"\n        try:\n            next(g)\n        except StopIteration:\n            pass\n        assert inspect.getgeneratorstate(g) == \"GEN_CLOSED\"\n\n\nclass TestGeneratorType:\n    def test_isinstance(self):\n        def gen():\n            yield 1\n\n        assert isinstance(gen(), GeneratorType)\n        assert not isinstance([1, 2, 3], GeneratorType)\n\n\nclass TestContextManager:\n    def test_capture(self):\n        with capture_output() as out:\n            out.append(\"test\")\n        assert out == [\"test\"]\n\n\n# Prime Generator\ndef prime(n: int):\n    \"\"\"Generate n prime numbers.\"\"\"\n    p = 2\n    while n > 0:\n        for x in range(2, p):\n            if p % x == 0:\n                break\n        else:\n            yield p\n            n -= 1\n        p += 1\n\n\n# Closure using Generator\ndef closure_gen(start: int = 0):\n    \"\"\"Closure implemented as generator.\"\"\"\n    x = start\n    while True:\n        x += 1\n        yield x\n\n\n# Simple Scheduler\ndef fib(n: int) -> int:\n    \"\"\"Fibonacci for scheduler example.\"\"\"\n    if n <= 2:\n        return 1\n    return fib(n - 1) + fib(n - 2)\n\n\ndef g_fib(n: int):\n    \"\"\"Generator yielding fibonacci numbers.\"\"\"\n    for x in range(1, n + 1):\n        yield fib(x)\n\n\ndef run_scheduler(tasks: list) -> list:\n    \"\"\"Simple round-robin scheduler.\"\"\"\n    from collections import deque\n\n    q = deque(tasks)\n    results = []\n    while q:\n        try:\n            t = q.popleft()\n            results.append(next(t))\n            q.append(t)\n        except StopIteration:\n            results.append(\"done\")\n    return results\n\n\n# Compiler Components\nimport re\nfrom collections import namedtuple\n\nToken = namedtuple(\"Token\", [\"type\", \"value\"])\n\n\ndef tokenize(text: str):\n    \"\"\"Tokenize arithmetic expression.\"\"\"\n    tokens = [\n        r\"(?P<NUMBER>\\d+)\",\n        r\"(?P<PLUS>\\+)\",\n        r\"(?P<MINUS>-)\",\n        r\"(?P<TIMES>\\*)\",\n        r\"(?P<DIVIDE>/)\",\n        r\"(?P<WS>\\s+)\",\n    ]\n    lex = re.compile(\"|\".join(tokens))\n    scan = lex.scanner(text)\n    return (\n        Token(m.lastgroup, m.group())\n        for m in iter(scan.match, None)\n        if m.lastgroup != \"WS\"\n    )\n\n\nclass Node:\n    _fields = []\n\n    def __init__(self, *args):\n        for attr, value in zip(self._fields, args):\n            setattr(self, attr, value)\n\n\nclass Number(Node):\n    _fields = [\"value\"]\n\n\nclass BinOp(Node):\n    _fields = [\"op\", \"left\", \"right\"]\n\n\ndef parse(toks):\n    \"\"\"Parse tokens into AST.\"\"\"\n    lookahead, current = next(toks, None), None\n\n    def accept(*toktypes):\n        nonlocal lookahead, current\n        if lookahead and lookahead.type in toktypes:\n            current, lookahead = lookahead, next(toks, None)\n            return True\n\n    def expr():\n        left = term()\n        while accept(\"PLUS\", \"MINUS\"):\n            left = BinOp(current.value, left)\n            left.right = term()\n        return left\n\n    def term():\n        left = factor()\n        while accept(\"TIMES\", \"DIVIDE\"):\n            left = BinOp(current.value, left)\n            left.right = factor()\n        return left\n\n    def factor():\n        if accept(\"NUMBER\"):\n            return Number(int(current.value))\n        raise SyntaxError()\n\n    return expr()\n\n\nimport types\n\n\nclass NodeVisitor:\n    \"\"\"Visitor using generators for stack-based evaluation.\"\"\"\n\n    def visit(self, node):\n        stack = [self.genvisit(node)]\n        ret = None\n        while stack:\n            try:\n                node = stack[-1].send(ret)\n                stack.append(self.genvisit(node))\n                ret = None\n            except StopIteration as e:\n                stack.pop()\n                ret = e.value\n        return ret\n\n    def genvisit(self, node):\n        ret = getattr(self, \"visit_\" + type(node).__name__)(node)\n        if isinstance(ret, types.GeneratorType):\n            ret = yield from ret\n        return ret\n\n\nclass Evaluator(NodeVisitor):\n    \"\"\"Evaluate AST using generator-based visitor.\"\"\"\n\n    def visit_Number(self, node):\n        return node.value\n\n    def visit_BinOp(self, node):\n        leftval = yield node.left\n        rightval = yield node.right\n        ops = {\n            \"+\": lambda a, b: a + b,\n            \"-\": lambda a, b: a - b,\n            \"*\": lambda a, b: a * b,\n            \"/\": lambda a, b: a / b,\n        }\n        return ops[node.op](leftval, rightval)\n\n\ndef evaluate(exp: str):\n    \"\"\"Evaluate arithmetic expression.\"\"\"\n    toks = tokenize(exp)\n    tree = parse(toks)\n    return Evaluator().visit(tree)\n\n\n# Async Iterator for comparison\nclass AsyncIter:\n    \"\"\"Async iterator for performance comparison.\"\"\"\n\n    def __init__(self, n):\n        self._n = n\n\n    def __aiter__(self):\n        return self\n\n    async def __anext__(self):\n        if self._n == 0:\n            raise StopAsyncIteration\n        self._n -= 1\n        return self._n\n\n\nasync def agen(n: int):\n    \"\"\"Async generator for performance comparison.\"\"\"\n    for i in range(n):\n        yield i\n\n\n# Additional Tests\nclass TestPrime:\n    def test_prime(self):\n        assert list(prime(5)) == [2, 3, 5, 7, 11]\n\n\nclass TestClosure:\n    def test_closure_gen(self):\n        g = closure_gen(5566)\n        assert next(g) == 5567\n        assert next(g) == 5568\n        assert next(g) == 5569\n\n\nclass TestScheduler:\n    def test_round_robin(self):\n        results = run_scheduler([g_fib(3), g_fib(3)])\n        assert results == [1, 1, 1, 1, 2, 2, \"done\", \"done\"]\n\n\nclass TestCompiler:\n    def test_tokenize(self):\n        tokens = list(tokenize(\"2 + 3\"))\n        assert tokens[0] == Token(\"NUMBER\", \"2\")\n        assert tokens[1] == Token(\"PLUS\", \"+\")\n        assert tokens[2] == Token(\"NUMBER\", \"3\")\n\n    def test_evaluate_simple(self):\n        assert evaluate(\"2 + 3\") == 5\n        assert evaluate(\"2 * 3\") == 6\n        assert evaluate(\"10 - 4\") == 6\n        assert evaluate(\"8 / 2\") == 4.0\n\n    def test_evaluate_complex(self):\n        assert evaluate(\"2 * 3 + 5\") == 11\n        assert evaluate(\"2 + 3 * 5\") == 17\n        assert evaluate(\"2 * 3 + 5 / 2\") == 8.5\n\n\nclass TestAsyncGen:\n    def test_async_iter(self):\n        import asyncio\n\n        async def collect():\n            return [x async for x in agen(5)]\n\n        assert asyncio.run(collect()) == [0, 1, 2, 3, 4]\n\n    def test_async_iter_class(self):\n        import asyncio\n\n        async def collect():\n            return [x async for x in AsyncIter(5)]\n\n        assert asyncio.run(collect()) == [4, 3, 2, 1, 0]\n"
  },
  {
    "path": "src/basic/heap.py",
    "content": "\"\"\"Python Heap Examples\n\nSource code for docs/notes/basic/python-heap.rst\n\"\"\"\n\nimport heapq\n\nimport pytest\n\n\n# Basic Heap Operations\ndef heapify_list(items: list) -> list:\n    \"\"\"Convert list to heap in-place.\"\"\"\n    h = items.copy()\n    heapq.heapify(h)\n    return h\n\n\ndef heap_push(h: list, item) -> list:\n    \"\"\"Push item onto heap.\"\"\"\n    heapq.heappush(h, item)\n    return h\n\n\ndef heap_pop(h: list):\n    \"\"\"Pop smallest item from heap.\"\"\"\n    return heapq.heappop(h)\n\n\ndef heap_pushpop(h: list, item):\n    \"\"\"Push item then pop smallest.\"\"\"\n    return heapq.heappushpop(h, item)\n\n\ndef heap_replace(h: list, item):\n    \"\"\"Pop smallest then push item.\"\"\"\n    return heapq.heapreplace(h, item)\n\n\n# Heap Sort\ndef heap_sort(items: list) -> list:\n    \"\"\"Sort using heap.\"\"\"\n    h = items.copy()\n    heapq.heapify(h)\n    return [heapq.heappop(h) for _ in range(len(h))]\n\n\n# Max Heap\ndef max_heap_sort(items: list) -> list:\n    \"\"\"Sort descending using negated values.\"\"\"\n    h = [-x for x in items]\n    heapq.heapify(h)\n    return [-heapq.heappop(h) for _ in range(len(h))]\n\n\nclass MaxHeapItem:\n    \"\"\"Wrapper for max heap behavior.\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def __lt__(self, other):\n        return self.val > other.val\n\n\n# Priority Queue\ndef priority_queue_example():\n    \"\"\"Priority queue using tuples.\"\"\"\n    pq = []\n    heapq.heappush(pq, (2, \"medium\"))\n    heapq.heappush(pq, (1, \"high\"))\n    heapq.heappush(pq, (3, \"low\"))\n    return [heapq.heappop(pq) for _ in range(len(pq))]\n\n\n# Custom Objects\nclass Task:\n    \"\"\"Task with priority for heap.\"\"\"\n\n    def __init__(self, priority: int, name: str):\n        self.priority = priority\n        self.name = name\n\n    def __lt__(self, other):\n        return self.priority < other.priority\n\n    def __repr__(self):\n        return f\"Task({self.priority}, {self.name!r})\"\n\n\ndef task_queue():\n    \"\"\"Process tasks by priority.\"\"\"\n    h = []\n    heapq.heappush(h, Task(3, \"low\"))\n    heapq.heappush(h, Task(1, \"high\"))\n    heapq.heappush(h, Task(2, \"medium\"))\n    return [heapq.heappop(h) for _ in range(len(h))]\n\n\n# K Smallest/Largest\ndef k_smallest(items: list, k: int) -> list:\n    \"\"\"Find k smallest elements.\"\"\"\n    return heapq.nsmallest(k, items)\n\n\ndef k_largest(items: list, k: int) -> list:\n    \"\"\"Find k largest elements.\"\"\"\n    return heapq.nlargest(k, items)\n\n\ndef k_largest_by_key(items: list, k: int, key) -> list:\n    \"\"\"Find k largest by key function.\"\"\"\n    return heapq.nlargest(k, items, key=key)\n\n\n# Merge Sorted\ndef merge_sorted(*iterables) -> list:\n    \"\"\"Merge sorted iterables.\"\"\"\n    return list(heapq.merge(*iterables))\n\n\ndef merge_sorted_reverse(*iterables) -> list:\n    \"\"\"Merge sorted iterables in reverse.\"\"\"\n    return list(heapq.merge(*iterables, reverse=True))\n\n\n# Fixed-Size Heap\ndef top_k(items: list, k: int) -> list:\n    \"\"\"Keep track of k largest elements.\"\"\"\n    h = []\n    for x in items:\n        if len(h) < k:\n            heapq.heappush(h, x)\n        elif x > h[0]:\n            heapq.heapreplace(h, x)\n    return sorted(h, reverse=True)\n\n\n# Indexed Heap\nclass IndexedHeap:\n    \"\"\"Heap with priority updates.\"\"\"\n\n    REMOVED = \"<removed>\"\n\n    def __init__(self):\n        self.heap = []\n        self.entry_finder = {}\n\n    def push(self, item, priority):\n        if item in self.entry_finder:\n            self.remove(item)\n        entry = [priority, item]\n        self.entry_finder[item] = entry\n        heapq.heappush(self.heap, entry)\n\n    def remove(self, item):\n        entry = self.entry_finder.pop(item)\n        entry[-1] = self.REMOVED\n\n    def pop(self):\n        while self.heap:\n            priority, item = heapq.heappop(self.heap)\n            if item is not self.REMOVED:\n                del self.entry_finder[item]\n                return item\n        raise KeyError(\"pop from empty heap\")\n\n\n# Tests\nclass TestBasicHeap:\n    def test_heapify(self):\n        h = heapify_list([5, 1, 3, 2, 6])\n        assert h[0] == 1\n\n    def test_push_pop(self):\n        h = []\n        heap_push(h, 3)\n        heap_push(h, 1)\n        heap_push(h, 2)\n        assert heap_pop(h) == 1\n        assert heap_pop(h) == 2\n\n    def test_pushpop(self):\n        h = [1, 3, 5]\n        heapq.heapify(h)\n        assert heap_pushpop(h, 2) == 1\n\n    def test_replace(self):\n        h = [1, 3, 5]\n        heapq.heapify(h)\n        assert heap_replace(h, 2) == 1\n        assert h[0] == 2\n\n\nclass TestHeapSort:\n    def test_sort(self):\n        assert heap_sort([5, 1, 3, 2, 6]) == [1, 2, 3, 5, 6]\n\n    def test_max_sort(self):\n        assert max_heap_sort([5, 1, 3, 2, 6]) == [6, 5, 3, 2, 1]\n\n\nclass TestPriorityQueue:\n    def test_priority_order(self):\n        result = priority_queue_example()\n        assert result[0] == (1, \"high\")\n        assert result[1] == (2, \"medium\")\n        assert result[2] == (3, \"low\")\n\n\nclass TestCustomObjects:\n    def test_max_heap_item(self):\n        h = []\n        for x in [5, 1, 3]:\n            heapq.heappush(h, MaxHeapItem(x))\n        assert heapq.heappop(h).val == 5\n\n    def test_task_queue(self):\n        tasks = task_queue()\n        assert tasks[0].name == \"high\"\n        assert tasks[1].name == \"medium\"\n        assert tasks[2].name == \"low\"\n\n\nclass TestKElements:\n    def test_k_smallest(self):\n        assert k_smallest([5, 1, 8, 3, 9], 3) == [1, 3, 5]\n\n    def test_k_largest(self):\n        assert k_largest([5, 1, 8, 3, 9], 3) == [9, 8, 5]\n\n    def test_k_largest_by_key(self):\n        data = [{\"score\": 85}, {\"score\": 92}, {\"score\": 78}]\n        result = k_largest_by_key(data, 2, key=lambda x: x[\"score\"])\n        assert result[0][\"score\"] == 92\n        assert result[1][\"score\"] == 85\n\n\nclass TestMerge:\n    def test_merge_sorted(self):\n        assert merge_sorted([1, 3, 5], [2, 4, 6]) == [1, 2, 3, 4, 5, 6]\n\n    def test_merge_three(self):\n        assert merge_sorted([1, 3], [2, 4], [0, 5]) == [0, 1, 2, 3, 4, 5]\n\n    def test_merge_reverse(self):\n        assert merge_sorted_reverse([5, 3, 1], [6, 4, 2]) == [6, 5, 4, 3, 2, 1]\n\n\nclass TestTopK:\n    def test_top_k(self):\n        assert top_k([5, 1, 8, 3, 9, 2, 7, 4, 6], 3) == [9, 8, 7]\n\n\nclass TestIndexedHeap:\n    def test_push_pop(self):\n        h = IndexedHeap()\n        h.push(\"a\", 3)\n        h.push(\"b\", 1)\n        h.push(\"c\", 2)\n        assert h.pop() == \"b\"\n\n    def test_update_priority(self):\n        h = IndexedHeap()\n        h.push(\"task1\", 3)\n        h.push(\"task2\", 1)\n        h.push(\"task1\", 0)  # update priority\n        assert h.pop() == \"task1\"\n"
  },
  {
    "path": "src/basic/list.py",
    "content": "\"\"\"Python List Examples\n\nSource code for docs/notes/basic/python-list.rst\n\"\"\"\n\nimport bisect\nimport copy\nimport itertools\nfrom collections import defaultdict, deque\nfrom functools import reduce\n\nimport pytest\n\n\n# Initialize\ndef init_immutable(n: int) -> list:\n    \"\"\"Initialize list with immutable objects.\"\"\"\n    return [0] * n\n\n\ndef init_mutable(n: int) -> list:\n    \"\"\"Initialize list with mutable objects (correct way).\"\"\"\n    return [[] for _ in range(n)]\n\n\n# Copy\ndef shallow_copy(lst: list) -> list:\n    \"\"\"Create shallow copy of list.\"\"\"\n    return lst.copy()\n\n\ndef deep_copy(lst: list) -> list:\n    \"\"\"Create deep copy of nested list.\"\"\"\n    return copy.deepcopy(lst)\n\n\n# List Comprehensions\ndef squares(n: int) -> list:\n    \"\"\"List of squares.\"\"\"\n    return [x**2 for x in range(n)]\n\n\ndef filter_even(nums: list) -> list:\n    \"\"\"Filter even numbers.\"\"\"\n    return [x for x in nums if x % 2 == 0]\n\n\ndef flatten(nested: list) -> list:\n    \"\"\"Flatten nested list.\"\"\"\n    return [x for sublist in nested for x in sublist]\n\n\n# Unpacking\ndef extended_unpack(lst: list) -> tuple:\n    \"\"\"Extended unpacking with *.\"\"\"\n    first, *middle, last = lst\n    return first, middle, last\n\n\n# Enumerate and Zip\ndef enumerate_example(items: list) -> list:\n    \"\"\"Enumerate with index.\"\"\"\n    return [(i, v) for i, v in enumerate(items)]\n\n\ndef zip_to_dict(keys: list, values: list) -> dict:\n    \"\"\"Create dict from two lists.\"\"\"\n    return dict(zip(keys, values))\n\n\ndef unzip(pairs: list) -> tuple:\n    \"\"\"Unzip list of pairs.\"\"\"\n    return tuple(zip(*pairs))\n\n\n# Sorting\ndef sort_by_key(items: list, key_func) -> list:\n    \"\"\"Sort by custom key.\"\"\"\n    return sorted(items, key=key_func)\n\n\ndef sort_dicts(dicts: list, key: str) -> list:\n    \"\"\"Sort list of dicts by key.\"\"\"\n    return sorted(dicts, key=lambda x: x[key])\n\n\n# Stack\nclass Stack:\n    \"\"\"Stack implementation using list.\"\"\"\n\n    def __init__(self):\n        self._items = []\n\n    def push(self, item):\n        self._items.append(item)\n\n    def pop(self):\n        return self._items.pop()\n\n    def peek(self):\n        return self._items[-1] if self._items else None\n\n    def is_empty(self):\n        return len(self._items) == 0\n\n    def __len__(self):\n        return len(self._items)\n\n\n# Tests\nclass TestInitialize:\n    def test_immutable(self):\n        a = init_immutable(3)\n        a[0] = 1\n        assert a == [1, 0, 0]\n\n    def test_mutable(self):\n        a = init_mutable(3)\n        a[0].append(1)\n        assert a == [[1], [], []]\n\n\nclass TestCopy:\n    def test_shallow(self):\n        a = [1, 2, 3]\n        b = shallow_copy(a)\n        b[0] = 99\n        assert a == [1, 2, 3]\n\n    def test_deep(self):\n        a = [[1, 2], [3, 4]]\n        b = deep_copy(a)\n        b[0][0] = 99\n        assert a == [[1, 2], [3, 4]]\n\n\nclass TestComprehensions:\n    def test_squares(self):\n        assert squares(5) == [0, 1, 4, 9, 16]\n\n    def test_filter_even(self):\n        assert filter_even([1, 2, 3, 4, 5, 6]) == [2, 4, 6]\n\n    def test_flatten(self):\n        assert flatten([[1, 2], [3, 4]]) == [1, 2, 3, 4]\n\n\nclass TestUnpacking:\n    def test_extended(self):\n        first, middle, last = extended_unpack([1, 2, 3, 4, 5])\n        assert first == 1\n        assert middle == [2, 3, 4]\n        assert last == 5\n\n\nclass TestEnumerateZip:\n    def test_enumerate(self):\n        assert enumerate_example([\"a\", \"b\"]) == [(0, \"a\"), (1, \"b\")]\n\n    def test_zip_to_dict(self):\n        assert zip_to_dict([\"a\", \"b\"], [1, 2]) == {\"a\": 1, \"b\": 2}\n\n    def test_unzip(self):\n        nums, chars = unzip([(1, \"a\"), (2, \"b\")])\n        assert nums == (1, 2)\n        assert chars == (\"a\", \"b\")\n\n\nclass TestSorting:\n    def test_sort_by_key(self):\n        assert sort_by_key([\"bb\", \"a\", \"ccc\"], len) == [\"a\", \"bb\", \"ccc\"]\n\n    def test_sort_dicts(self):\n        data = [{\"n\": 2}, {\"n\": 1}]\n        assert sort_dicts(data, \"n\") == [{\"n\": 1}, {\"n\": 2}]\n\n\nclass TestStack:\n    def test_push_pop(self):\n        s = Stack()\n        s.push(1)\n        s.push(2)\n        assert s.pop() == 2\n        assert s.pop() == 1\n\n    def test_peek(self):\n        s = Stack()\n        s.push(1)\n        assert s.peek() == 1\n        assert len(s) == 1\n\n    def test_is_empty(self):\n        s = Stack()\n        assert s.is_empty()\n        s.push(1)\n        assert not s.is_empty()\n\n\n# Bisect - Maintain Sorted List\ndef bisect_insort(items: list) -> list:\n    \"\"\"Insert items maintaining sorted order.\"\"\"\n    result = []\n    for x in items:\n        bisect.insort(result, x)\n    return result\n\n\ndef bisect_left_example(lst: list, x) -> int:\n    \"\"\"Find lower bound position.\"\"\"\n    return bisect.bisect_left(lst, x)\n\n\ndef bisect_right_example(lst: list, x) -> int:\n    \"\"\"Find upper bound position.\"\"\"\n    return bisect.bisect_right(lst, x)\n\n\ndef binary_search(arr: list, x, lo: int = 0, hi: int = None) -> int:\n    \"\"\"Binary search in sorted list.\"\"\"\n    if hi is None:\n        hi = len(arr)\n    pos = bisect.bisect_left(arr, x, lo, hi)\n    return pos if pos != hi and arr[pos] == x else -1\n\n\n# Nested Lists\ndef create_2d_list(rows: int, cols: int) -> list:\n    \"\"\"Create 2D list correctly.\"\"\"\n    return [[0] * cols for _ in range(rows)]\n\n\n# Deque - Circular Buffer\ndef circular_buffer(items: list, maxlen: int) -> deque:\n    \"\"\"Create circular buffer with deque.\"\"\"\n    d = deque(maxlen=maxlen)\n    for x in items:\n        d.append(x)\n    return d\n\n\n# Chunk List\ndef chunk(lst: list, n: int) -> list:\n    \"\"\"Split list into chunks of size n.\"\"\"\n    return [lst[i : i + n] for i in range(0, len(lst), n)]\n\n\n# Groupby\ndef groupby_example(s: str) -> list:\n    \"\"\"Group consecutive elements.\"\"\"\n    return [(k, list(v)) for k, v in itertools.groupby(s)]\n\n\n# Trie\ndef create_trie(words: list) -> dict:\n    \"\"\"Create trie from list of words.\"\"\"\n    Trie = lambda: defaultdict(Trie)\n    trie = Trie()\n    end = True\n    for word in words:\n        reduce(dict.__getitem__, word, trie)[end] = word\n    return trie\n\n\ndef trie_has_prefix(trie: dict, prefix: str) -> bool:\n    \"\"\"Check if trie has prefix.\"\"\"\n    curr = trie\n    for c in prefix:\n        if c not in curr:\n            return False\n        curr = curr[c]\n    return True\n\n\nclass TestBisect:\n    def test_insort(self):\n        assert bisect_insort([3, 1, 2, 0]) == [0, 1, 2, 3]\n\n    def test_bisect_left(self):\n        a = [1, 2, 3, 3, 4, 5]\n        assert bisect_left_example(a, 3) == 2\n\n    def test_bisect_right(self):\n        a = [1, 2, 3, 3, 4, 5]\n        assert bisect_right_example(a, 3) == 4\n\n    def test_binary_search(self):\n        a = [1, 1, 1, 2, 3]\n        assert binary_search(a, 1) == 0\n        assert binary_search(a, 2) == 3\n        assert binary_search(a, 99) == -1\n\n\nclass TestNestedLists:\n    def test_create_2d(self):\n        grid = create_2d_list(2, 3)\n        grid[0][0] = 1\n        assert grid == [[1, 0, 0], [0, 0, 0]]\n\n\nclass TestDeque:\n    def test_circular_buffer(self):\n        d = circular_buffer(range(5), 3)\n        assert list(d) == [2, 3, 4]\n\n\nclass TestChunk:\n    def test_chunk(self):\n        assert chunk([1, 2, 3, 4, 5, 6, 7, 8], 3) == [\n            [1, 2, 3],\n            [4, 5, 6],\n            [7, 8],\n        ]\n\n\nclass TestGroupby:\n    def test_groupby(self):\n        result = groupby_example(\"AAABBC\")\n        assert result == [\n            (\"A\", [\"A\", \"A\", \"A\"]),\n            (\"B\", [\"B\", \"B\"]),\n            (\"C\", [\"C\"]),\n        ]\n\n\nclass TestTrie:\n    def test_create_and_search(self):\n        trie = create_trie([\"abc\", \"de\", \"g\"])\n        assert trie_has_prefix(trie, \"ab\")\n        assert trie_has_prefix(trie, \"abc\")\n        assert not trie_has_prefix(trie, \"xyz\")\n"
  },
  {
    "path": "src/basic/object.py",
    "content": "\"\"\"Python OOP Examples\n\nSource code for docs/notes/basic/python-object.rst\n\"\"\"\n\nfrom abc import ABC, abstractmethod\nfrom functools import total_ordering\n\nimport pytest\n\n\n# Basic Class\nclass Person:\n    \"\"\"Basic class with __init__.\"\"\"\n\n    def __init__(self, name: str, age: int):\n        self.name = name\n        self.age = age\n\n    def greet(self) -> str:\n        return f\"Hello, I'm {self.name}\"\n\n\n# Class and Instance Attributes\nclass Counter:\n    \"\"\"Class with class and instance attributes.\"\"\"\n\n    count = 0\n\n    def __init__(self):\n        Counter.count += 1\n        self.id = Counter.count\n\n\n# Inheritance\nclass Animal:\n    \"\"\"Base class for inheritance.\"\"\"\n\n    def __init__(self, name: str):\n        self.name = name\n\n    def speak(self) -> str:\n        raise NotImplementedError\n\n\nclass Dog(Animal):\n    def speak(self) -> str:\n        return f\"{self.name} says Woof!\"\n\n\nclass Cat(Animal):\n    def speak(self) -> str:\n        return f\"{self.name} says Meow!\"\n\n\n# Magic Methods - repr and str\nclass Vector:\n    \"\"\"Class with magic methods.\"\"\"\n\n    def __init__(self, x: int, y: int):\n        self.x, self.y = x, y\n\n    def __repr__(self):\n        return f\"Vector({self.x}, {self.y})\"\n\n    def __str__(self):\n        return f\"({self.x}, {self.y})\"\n\n    def __add__(self, other):\n        return Vector(self.x + other.x, self.y + other.y)\n\n    def __mul__(self, scalar):\n        return Vector(self.x * scalar, self.y * scalar)\n\n    def __eq__(self, other):\n        return self.x == other.x and self.y == other.y\n\n\n# Comparison with total_ordering\n@total_ordering\nclass Number:\n    \"\"\"Class with comparison methods.\"\"\"\n\n    def __init__(self, val):\n        self.val = val\n\n    def __eq__(self, other):\n        return self.val == other.val\n\n    def __lt__(self, other):\n        return self.val < other.val\n\n\n# Callable\nclass Multiplier:\n    \"\"\"Callable class.\"\"\"\n\n    def __init__(self, factor: int):\n        self.factor = factor\n\n    def __call__(self, x: int) -> int:\n        return x * self.factor\n\n\n# Property\nclass Circle:\n    \"\"\"Class with property.\"\"\"\n\n    def __init__(self, radius: float):\n        self._radius = radius\n\n    @property\n    def radius(self) -> float:\n        return self._radius\n\n    @radius.setter\n    def radius(self, value: float):\n        if value < 0:\n            raise ValueError(\"Radius must be positive\")\n        self._radius = value\n\n    @property\n    def area(self) -> float:\n        return 3.14159 * self._radius**2\n\n\n# Descriptor\nclass Positive:\n    \"\"\"Descriptor that enforces positive values.\"\"\"\n\n    def __init__(self, name):\n        self.name = name\n\n    def __get__(self, obj, objtype=None):\n        if obj is None:\n            return self\n        return obj.__dict__[self.name]\n\n    def __set__(self, obj, value):\n        if value < 0:\n            raise ValueError(\"Must be positive\")\n        obj.__dict__[self.name] = value\n\n\nclass DescriptorExample:\n    x = Positive(\"x\")\n\n    def __init__(self, x):\n        self.x = x\n\n\n# Context Manager\nclass ManagedResource:\n    \"\"\"Context manager class.\"\"\"\n\n    def __init__(self):\n        self.entered = False\n        self.exited = False\n\n    def __enter__(self):\n        self.entered = True\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.exited = True\n        return False\n\n\n# Singleton\nclass Singleton:\n    \"\"\"Singleton pattern.\"\"\"\n\n    _instance = None\n\n    def __new__(cls):\n        if cls._instance is None:\n            cls._instance = super().__new__(cls)\n        return cls._instance\n\n\n# Class Methods and Static Methods\nclass Date:\n    \"\"\"Class with classmethod and staticmethod.\"\"\"\n\n    def __init__(self, year: int, month: int, day: int):\n        self.year, self.month, self.day = year, month, day\n\n    @classmethod\n    def from_string(cls, date_string: str):\n        year, month, day = map(int, date_string.split(\"-\"))\n        return cls(year, month, day)\n\n    @staticmethod\n    def is_valid(date_string: str) -> bool:\n        try:\n            year, month, day = map(int, date_string.split(\"-\"))\n            return 1 <= month <= 12 and 1 <= day <= 31\n        except Exception:\n            return False\n\n\n# Abstract Base Class\nclass Shape(ABC):\n    \"\"\"Abstract base class.\"\"\"\n\n    @abstractmethod\n    def area(self) -> float:\n        pass\n\n\nclass Rectangle(Shape):\n    def __init__(self, width: float, height: float):\n        self.width, self.height = width, height\n\n    def area(self) -> float:\n        return self.width * self.height\n\n\n# MRO - Diamond Problem\nclass A:\n    def method(self):\n        return \"A\"\n\n\nclass B(A):\n    def method(self):\n        return \"B\"\n\n\nclass C(A):\n    def method(self):\n        return \"C\"\n\n\nclass D(B, C):\n    pass\n\n\n# Slots\nclass PointWithSlots:\n    \"\"\"Class with __slots__ for memory efficiency.\"\"\"\n\n    __slots__ = [\"x\", \"y\"]\n\n    def __init__(self, x, y):\n        self.x, self.y = x, y\n\n\n# Tests\nclass TestBasicClass:\n    def test_person(self):\n        p = Person(\"Alice\", 30)\n        assert p.name == \"Alice\"\n        assert p.greet() == \"Hello, I'm Alice\"\n\n\nclass TestClassAttributes:\n    def test_counter(self):\n        Counter.count = 0\n        a, b = Counter(), Counter()\n        assert a.id == 1\n        assert b.id == 2\n        assert Counter.count == 2\n\n\nclass TestInheritance:\n    def test_dog(self):\n        assert Dog(\"Buddy\").speak() == \"Buddy says Woof!\"\n\n    def test_cat(self):\n        assert Cat(\"Whiskers\").speak() == \"Whiskers says Meow!\"\n\n\nclass TestMagicMethods:\n    def test_vector_add(self):\n        v1 = Vector(1, 2)\n        v2 = Vector(3, 4)\n        assert v1 + v2 == Vector(4, 6)\n\n    def test_vector_mul(self):\n        v = Vector(1, 2)\n        assert v * 3 == Vector(3, 6)\n\n    def test_vector_repr(self):\n        assert repr(Vector(1, 2)) == \"Vector(1, 2)\"\n\n    def test_vector_str(self):\n        assert str(Vector(1, 2)) == \"(1, 2)\"\n\n\nclass TestComparison:\n    def test_total_ordering(self):\n        assert Number(1) < Number(2)\n        assert Number(2) > Number(1)\n        assert Number(2) >= Number(1)\n        assert Number(1) <= Number(2)\n        assert Number(1) == Number(1)\n\n\nclass TestCallable:\n    def test_multiplier(self):\n        double = Multiplier(2)\n        assert double(5) == 10\n        assert callable(double)\n\n\nclass TestProperty:\n    def test_circle_area(self):\n        c = Circle(5)\n        assert abs(c.area - 78.53975) < 0.001\n\n    def test_circle_setter(self):\n        c = Circle(5)\n        c.radius = 10\n        assert c.radius == 10\n\n    def test_circle_invalid(self):\n        c = Circle(5)\n        with pytest.raises(ValueError):\n            c.radius = -1\n\n\nclass TestDescriptor:\n    def test_positive(self):\n        ex = DescriptorExample(10)\n        assert ex.x == 10\n\n    def test_positive_invalid(self):\n        with pytest.raises(ValueError):\n            DescriptorExample(-1)\n\n\nclass TestContextManager:\n    def test_managed_resource(self):\n        with ManagedResource() as r:\n            assert r.entered\n        assert r.exited\n\n\nclass TestSingleton:\n    def test_singleton(self):\n        Singleton._instance = None  # reset\n        a = Singleton()\n        b = Singleton()\n        assert a is b\n\n\nclass TestClassMethods:\n    def test_from_string(self):\n        d = Date.from_string(\"2024-01-15\")\n        assert d.year == 2024\n        assert d.month == 1\n\n    def test_is_valid(self):\n        assert Date.is_valid(\"2024-01-15\")\n        assert not Date.is_valid(\"2024-13-01\")\n\n\nclass TestABC:\n    def test_rectangle(self):\n        r = Rectangle(3, 4)\n        assert r.area() == 12\n\n    def test_abstract_instantiation(self):\n        with pytest.raises(TypeError):\n            Shape()\n\n\nclass TestMRO:\n    def test_diamond(self):\n        assert D().method() == \"B\"\n\n    def test_mro(self):\n        assert D.mro() == [D, B, C, A, object]\n\n\nclass TestSlots:\n    def test_slots(self):\n        p = PointWithSlots(1, 2)\n        assert p.x == 1\n        assert p.y == 2\n\n    def test_slots_no_dict(self):\n        p = PointWithSlots(1, 2)\n        assert not hasattr(p, \"__dict__\")\n"
  },
  {
    "path": "src/basic/os_.py",
    "content": "\"\"\"\nTests for operating system operations.\n\nThese tests demonstrate Python's os module for file system operations,\nprocess management, environment variables, and path manipulation.\n\"\"\"\n\nimport os\nimport platform\nimport subprocess\nimport tempfile\nfrom pathlib import Path\n\nimport pytest\n\n\nclass TestSystemInfo:\n    \"\"\"Test system information functions.\"\"\"\n\n    def test_os_name(self):\n        \"\"\"Test os.name returns valid value.\"\"\"\n        assert os.name in (\"posix\", \"nt\")\n\n    def test_platform_system(self):\n        \"\"\"Test platform.system returns valid value.\"\"\"\n        assert platform.system() in (\"Linux\", \"Darwin\", \"Windows\")\n\n    def test_cpu_count(self):\n        \"\"\"Test cpu_count returns positive integer.\"\"\"\n        count = os.cpu_count()\n        assert count is not None\n        assert count > 0\n\n    def test_getpid(self):\n        \"\"\"Test getpid returns positive integer.\"\"\"\n        pid = os.getpid()\n        assert pid > 0\n\n    def test_getcwd(self):\n        \"\"\"Test getcwd returns valid path.\"\"\"\n        cwd = os.getcwd()\n        assert os.path.isdir(cwd)\n\n\nclass TestEnvironmentVariables:\n    \"\"\"Test environment variable operations.\"\"\"\n\n    def test_get_env(self):\n        \"\"\"Test getting environment variable.\"\"\"\n        # PATH should exist on all systems\n        path = os.environ.get(\"PATH\")\n        assert path is not None\n\n    def test_getenv_default(self):\n        \"\"\"Test getenv with default value.\"\"\"\n        value = os.getenv(\"NONEXISTENT_VAR_12345\", \"default\")\n        assert value == \"default\"\n\n    def test_set_env(self):\n        \"\"\"Test setting environment variable.\"\"\"\n        os.environ[\"TEST_VAR_PYSHEEET\"] = \"test_value\"\n        assert os.environ[\"TEST_VAR_PYSHEEET\"] == \"test_value\"\n        del os.environ[\"TEST_VAR_PYSHEEET\"]\n\n    def test_env_not_found(self):\n        \"\"\"Test KeyError for missing env var.\"\"\"\n        with pytest.raises(KeyError):\n            _ = os.environ[\"NONEXISTENT_VAR_12345\"]\n\n\nclass TestPathOperations:\n    \"\"\"Test path manipulation functions.\"\"\"\n\n    def test_join(self):\n        \"\"\"Test os.path.join.\"\"\"\n        path = os.path.join(\"dir1\", \"dir2\", \"file.txt\")\n        assert \"dir1\" in path\n        assert \"dir2\" in path\n        assert \"file.txt\" in path\n\n    def test_dirname_basename(self):\n        \"\"\"Test dirname and basename.\"\"\"\n        path = os.path.join(\"home\", \"user\", \"file.txt\")\n        assert os.path.basename(path) == \"file.txt\"\n        assert \"user\" in os.path.dirname(path)\n\n    def test_splitext(self):\n        \"\"\"Test splitext.\"\"\"\n        name, ext = os.path.splitext(\"file.txt\")\n        assert name == \"file\"\n        assert ext == \".txt\"\n\n    def test_abspath(self):\n        \"\"\"Test abspath returns absolute path.\"\"\"\n        abs_path = os.path.abspath(\".\")\n        assert os.path.isabs(abs_path)\n\n    def test_exists(self):\n        \"\"\"Test path existence checks.\"\"\"\n        assert os.path.exists(\".\")\n        assert not os.path.exists(\"/nonexistent/path/12345\")\n\n    def test_isfile_isdir(self):\n        \"\"\"Test isfile and isdir.\"\"\"\n        assert os.path.isdir(\".\")\n        # Create temp file to test isfile\n        with tempfile.NamedTemporaryFile() as f:\n            assert os.path.isfile(f.name)\n\n\nclass TestDirectoryOperations:\n    \"\"\"Test directory operations.\"\"\"\n\n    def test_mkdir_rmdir(self):\n        \"\"\"Test creating and removing directory.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            new_dir = os.path.join(tmpdir, \"test_dir\")\n            os.mkdir(new_dir)\n            assert os.path.isdir(new_dir)\n            os.rmdir(new_dir)\n            assert not os.path.exists(new_dir)\n\n    def test_makedirs(self):\n        \"\"\"Test creating nested directories.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            nested = os.path.join(tmpdir, \"a\", \"b\", \"c\")\n            os.makedirs(nested)\n            assert os.path.isdir(nested)\n\n    def test_makedirs_exist_ok(self):\n        \"\"\"Test makedirs with exist_ok.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            os.makedirs(tmpdir, exist_ok=True)  # Should not raise\n\n    def test_listdir(self):\n        \"\"\"Test listing directory contents.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            # Create some files\n            Path(tmpdir, \"file1.txt\").touch()\n            Path(tmpdir, \"file2.txt\").touch()\n            entries = os.listdir(tmpdir)\n            assert \"file1.txt\" in entries\n            assert \"file2.txt\" in entries\n\n    def test_walk(self):\n        \"\"\"Test walking directory tree.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            # Create nested structure\n            os.makedirs(os.path.join(tmpdir, \"subdir\"))\n            Path(tmpdir, \"file1.txt\").touch()\n            Path(tmpdir, \"subdir\", \"file2.txt\").touch()\n\n            files_found = []\n            for root, dirs, files in os.walk(tmpdir):\n                for f in files:\n                    files_found.append(f)\n\n            assert \"file1.txt\" in files_found\n            assert \"file2.txt\" in files_found\n\n\nclass TestFileOperations:\n    \"\"\"Test file operations.\"\"\"\n\n    def test_rename(self):\n        \"\"\"Test renaming file.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            old = os.path.join(tmpdir, \"old.txt\")\n            new = os.path.join(tmpdir, \"new.txt\")\n            Path(old).write_text(\"content\")\n            os.rename(old, new)\n            assert not os.path.exists(old)\n            assert os.path.exists(new)\n\n    def test_remove(self):\n        \"\"\"Test removing file.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            path = os.path.join(tmpdir, \"file.txt\")\n            Path(path).write_text(\"content\")\n            os.remove(path)\n            assert not os.path.exists(path)\n\n    def test_getsize(self):\n        \"\"\"Test getting file size.\"\"\"\n        with tempfile.NamedTemporaryFile(mode=\"w\", delete=False) as f:\n            f.write(\"hello\")\n            path = f.name\n        try:\n            size = os.path.getsize(path)\n            assert size == 5\n        finally:\n            os.unlink(path)\n\n\nclass TestSubprocess:\n    \"\"\"Test subprocess operations.\"\"\"\n\n    def test_run_simple(self):\n        \"\"\"Test simple subprocess.run.\"\"\"\n        result = subprocess.run(\n            [\"echo\", \"hello\"], capture_output=True, text=True\n        )\n        assert result.returncode == 0\n        assert \"hello\" in result.stdout\n\n    def test_check_output(self):\n        \"\"\"Test subprocess.check_output.\"\"\"\n        output = subprocess.check_output([\"echo\", \"test\"], text=True)\n        assert \"test\" in output\n\n    def test_run_with_input(self):\n        \"\"\"Test subprocess with input.\"\"\"\n        result = subprocess.run(\n            [\"cat\"], input=\"hello world\", capture_output=True, text=True\n        )\n        assert result.stdout == \"hello world\"\n\n\nclass TestTempFiles:\n    \"\"\"Test temporary file operations.\"\"\"\n\n    def test_named_temp_file(self):\n        \"\"\"Test NamedTemporaryFile.\"\"\"\n        with tempfile.NamedTemporaryFile(mode=\"w\", suffix=\".txt\") as f:\n            f.write(\"test data\")\n            f.flush()\n            assert os.path.exists(f.name)\n            assert f.name.endswith(\".txt\")\n\n    def test_temp_directory(self):\n        \"\"\"Test TemporaryDirectory.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            assert os.path.isdir(tmpdir)\n            # Create file inside\n            path = os.path.join(tmpdir, \"test.txt\")\n            Path(path).write_text(\"data\")\n            assert os.path.exists(path)\n        # Directory should be deleted\n        assert not os.path.exists(tmpdir)\n\n    def test_gettempdir(self):\n        \"\"\"Test getting temp directory path.\"\"\"\n        tmpdir = tempfile.gettempdir()\n        assert os.path.isdir(tmpdir)\n\n\nclass TestPathlib:\n    \"\"\"Test pathlib operations.\"\"\"\n\n    def test_path_creation(self):\n        \"\"\"Test creating Path objects.\"\"\"\n        p = Path(\"/home/user/file.txt\")\n        assert p.name == \"file.txt\"\n        assert p.stem == \"file\"\n        assert p.suffix == \".txt\"\n\n    def test_path_join(self):\n        \"\"\"Test joining paths with /.\"\"\"\n        p = Path(\"/home\") / \"user\" / \"file.txt\"\n        assert str(p) == \"/home/user/file.txt\"\n\n    def test_path_parent(self):\n        \"\"\"Test getting parent.\"\"\"\n        p = Path(\"/home/user/file.txt\")\n        assert p.parent == Path(\"/home/user\")\n\n    def test_path_exists(self):\n        \"\"\"Test path existence.\"\"\"\n        assert Path(\".\").exists()\n        assert Path(\".\").is_dir()\n\n    def test_read_write_text(self):\n        \"\"\"Test reading and writing text.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            p = Path(tmpdir) / \"test.txt\"\n            p.write_text(\"hello world\")\n            assert p.read_text() == \"hello world\"\n\n    def test_mkdir(self):\n        \"\"\"Test creating directory.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            p = Path(tmpdir) / \"a\" / \"b\" / \"c\"\n            p.mkdir(parents=True)\n            assert p.is_dir()\n\n    def test_glob(self):\n        \"\"\"Test glob pattern matching.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            (Path(tmpdir) / \"file1.py\").touch()\n            (Path(tmpdir) / \"file2.py\").touch()\n            (Path(tmpdir) / \"file3.txt\").touch()\n\n            py_files = list(Path(tmpdir).glob(\"*.py\"))\n            assert len(py_files) == 2\n\n    def test_iterdir(self):\n        \"\"\"Test iterating directory.\"\"\"\n        with tempfile.TemporaryDirectory() as tmpdir:\n            (Path(tmpdir) / \"file1.txt\").touch()\n            (Path(tmpdir) / \"file2.txt\").touch()\n\n            entries = list(Path(tmpdir).iterdir())\n            assert len(entries) == 2\n\n\nclass TestPsutil:\n    \"\"\"Test psutil operations.\"\"\"\n\n    @pytest.fixture\n    def psutil_available(self):\n        \"\"\"Check if psutil is installed.\"\"\"\n        try:\n            import psutil\n\n            return psutil\n        except ImportError:\n            pytest.skip(\"psutil not installed\")\n\n    def test_cpu_count(self, psutil_available):\n        \"\"\"Test CPU count.\"\"\"\n        psutil = psutil_available\n        logical = psutil.cpu_count()\n        physical = psutil.cpu_count(logical=False)\n        assert logical > 0\n        assert physical > 0\n        assert logical >= physical\n\n    def test_cpu_percent(self, psutil_available):\n        \"\"\"Test CPU percentage.\"\"\"\n        psutil = psutil_available\n        percent = psutil.cpu_percent(interval=0.1)\n        assert 0 <= percent <= 100\n\n    def test_virtual_memory(self, psutil_available):\n        \"\"\"Test virtual memory info.\"\"\"\n        psutil = psutil_available\n        mem = psutil.virtual_memory()\n        assert mem.total > 0\n        assert mem.available > 0\n        assert 0 <= mem.percent <= 100\n\n    def test_disk_usage(self, psutil_available):\n        \"\"\"Test disk usage.\"\"\"\n        psutil = psutil_available\n        usage = psutil.disk_usage(\"/\")\n        assert usage.total > 0\n        assert usage.used >= 0\n        assert 0 <= usage.percent <= 100\n\n    def test_process(self, psutil_available):\n        \"\"\"Test current process info.\"\"\"\n        psutil = psutil_available\n        p = psutil.Process()\n        assert p.pid == os.getpid()\n        assert p.name()\n        assert p.num_threads() > 0\n\n    def test_boot_time(self, psutil_available):\n        \"\"\"Test boot time.\"\"\"\n        psutil = psutil_available\n        boot = psutil.boot_time()\n        assert boot > 0\n"
  },
  {
    "path": "src/basic/rexp.py",
    "content": "\"\"\"Python Regular Expression Examples\n\nSource code for docs/notes/basic/python-rexp.rst\n\"\"\"\n\nimport re\nfrom collections import namedtuple\n\nimport pytest\n\n\n# Basic Matching\ndef search_pattern(pattern: str, text: str) -> str | None:\n    \"\"\"Find first match of pattern in text.\"\"\"\n    m = re.search(pattern, text)\n    return m.group() if m else None\n\n\ndef match_start(pattern: str, text: str) -> bool:\n    \"\"\"Check if text starts with pattern.\"\"\"\n    return re.match(pattern, text) is not None\n\n\ndef fullmatch(pattern: str, text: str) -> bool:\n    \"\"\"Check if entire text matches pattern.\"\"\"\n    return re.fullmatch(pattern, text) is not None\n\n\n# Find All\ndef find_all(pattern: str, text: str) -> list:\n    \"\"\"Find all matches of pattern.\"\"\"\n    return re.findall(pattern, text)\n\n\ndef find_all_groups(pattern: str, text: str) -> list:\n    \"\"\"Find all matches with groups.\"\"\"\n    return re.findall(pattern, text)\n\n\n# Split\ndef split_pattern(pattern: str, text: str) -> list:\n    \"\"\"Split text by pattern.\"\"\"\n    return re.split(pattern, text)\n\n\n# Groups\ndef parse_date(text: str) -> dict | None:\n    \"\"\"Parse date string into components.\"\"\"\n    m = re.search(r\"(\\d{4})-(\\d{2})-(\\d{2})\", text)\n    if m:\n        return {\"year\": m.group(1), \"month\": m.group(2), \"day\": m.group(3)}\n    return None\n\n\ndef parse_date_named(text: str) -> dict | None:\n    \"\"\"Parse date using named groups.\"\"\"\n    pattern = r\"(?P<year>\\d{4})-(?P<month>\\d{2})-(?P<day>\\d{2})\"\n    m = re.search(pattern, text)\n    return m.groupdict() if m else None\n\n\n# Non-capturing group\ndef parse_url(url: str) -> tuple | None:\n    \"\"\"Parse URL with non-capturing group for protocol.\"\"\"\n    m = re.search(r\"(?:https?|ftp)://([^/\\r\\n]+)(/[^\\r\\n]*)?\", url)\n    return m.groups() if m else None\n\n\n# Back Reference\ndef has_repeated_char(text: str) -> bool:\n    \"\"\"Check if text has repeated adjacent characters.\"\"\"\n    return re.search(r\"(\\w)\\1\", text) is not None\n\n\ndef match_html_tag(text: str) -> str | None:\n    \"\"\"Match HTML tag with matching close tag.\"\"\"\n    m = re.search(r\"<(\\w+)>[^<]*</\\1>\", text)\n    return m.group() if m else None\n\n\n# Lookahead/Lookbehind\ndef find_before_at(text: str) -> list:\n    \"\"\"Find words before @ symbol (positive lookahead).\"\"\"\n    return re.findall(r\"\\w+(?=@)\", text)\n\n\ndef find_after_dollar(text: str) -> list:\n    \"\"\"Find numbers after $ symbol (positive lookbehind).\"\"\"\n    return re.findall(r\"(?<=\\$)\\d+\", text)\n\n\ndef find_not_followed_by(text: str, suffix: str) -> list:\n    \"\"\"Find digits not followed by suffix (negative lookahead).\"\"\"\n    return re.findall(rf\"\\d+(?!{suffix})\", text)\n\n\n# Substitution\ndef replace_pattern(pattern: str, repl: str, text: str) -> str:\n    \"\"\"Replace pattern with replacement.\"\"\"\n    return re.sub(pattern, repl, text)\n\n\ndef double_numbers(text: str) -> str:\n    \"\"\"Double all numbers in text using function replacement.\"\"\"\n    return re.sub(r\"\\d+\", lambda m: str(int(m.group()) * 2), text)\n\n\ndef camel_to_snake(s: str) -> str:\n    \"\"\"Convert CamelCase to snake_case.\"\"\"\n    s = re.sub(r\"(.)([A-Z][a-z]+)\", r\"\\1_\\2\", s)\n    return re.sub(r\"([a-z])([A-Z])\", r\"\\1_\\2\", s).lower()\n\n\n# Compiled Patterns\nEMAIL_PATTERN = re.compile(r\"^[\\w.+-]+@[\\w-]+\\.[\\w.-]+$\")\nIP_PATTERN = re.compile(\n    r\"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}\"\n    r\"(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$\"\n)\nMAC_PATTERN = re.compile(r\"^([0-9a-f]{2}:){5}[0-9a-f]{2}$\", re.I)\nURL_PATTERN = re.compile(\n    r\"^(https?://)?([\\da-z.-]+)\\.([a-z.]{2,6})([/\\w.-]*)*/?$\", re.I\n)\nHEX_COLOR_PATTERN = re.compile(r\"^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$\")\nPHONE_PATTERN = re.compile(\n    r\"^(\\+1)?[-.\\s]?\\(?\\d{3}\\)?[-.\\s]?\\d{3}[-.\\s]?\\d{4}$\"\n)\nPASSWORD_PATTERN = re.compile(\n    r\"^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$\"\n)\n\n\ndef is_valid_email(text: str) -> bool:\n    \"\"\"Validate email address.\"\"\"\n    return EMAIL_PATTERN.match(text) is not None\n\n\ndef is_valid_ip(text: str) -> bool:\n    \"\"\"Validate IPv4 address.\"\"\"\n    return IP_PATTERN.match(text) is not None\n\n\ndef is_valid_mac(text: str) -> bool:\n    \"\"\"Validate MAC address.\"\"\"\n    return MAC_PATTERN.match(text) is not None\n\n\ndef is_valid_url(text: str) -> bool:\n    \"\"\"Validate URL.\"\"\"\n    return URL_PATTERN.match(text) is not None\n\n\ndef is_valid_hex_color(text: str) -> bool:\n    \"\"\"Validate hex color code.\"\"\"\n    return HEX_COLOR_PATTERN.match(text) is not None\n\n\ndef is_valid_phone(text: str) -> bool:\n    \"\"\"Validate US phone number.\"\"\"\n    return PHONE_PATTERN.match(text) is not None\n\n\ndef is_strong_password(text: str) -> bool:\n    \"\"\"Validate password strength.\"\"\"\n    return PASSWORD_PATTERN.match(text) is not None\n\n\n# HTML Tags\ndef find_open_tags(html: str) -> list:\n    \"\"\"Find all open tags.\"\"\"\n    return re.findall(r\"<[^/>][^>]*>\", html)\n\n\ndef find_close_tags(html: str) -> list:\n    \"\"\"Find all close tags.\"\"\"\n    return re.findall(r\"</[^>]+>\", html)\n\n\ndef strip_html_tags(html: str) -> str:\n    \"\"\"Remove all HTML tags.\"\"\"\n    return re.sub(r\"<[^>]+>\", \"\", html)\n\n\n# Lexer\nToken = namedtuple(\"Token\", [\"type\", \"value\"])\n\n\ndef tokenize(text: str) -> list:\n    \"\"\"Tokenize arithmetic expression.\"\"\"\n    tokens = [\n        r\"(?P<NUMBER>\\d+)\",\n        r\"(?P<PLUS>\\+)\",\n        r\"(?P<MINUS>-)\",\n        r\"(?P<TIMES>\\*)\",\n        r\"(?P<DIVIDE>/)\",\n        r\"(?P<WS>\\s+)\",\n    ]\n    lex = re.compile(\"|\".join(tokens))\n    scan = lex.scanner(text)\n    return [\n        Token(m.lastgroup, m.group())\n        for m in iter(scan.match, None)\n        if m.lastgroup != \"WS\"\n    ]\n\n\n# Utility functions\ndef find_hashtags(text: str) -> list:\n    \"\"\"Find all hashtags in text.\"\"\"\n    return re.findall(r\"#\\w+\", text)\n\n\ndef find_mentions(text: str) -> list:\n    \"\"\"Find all @mentions in text.\"\"\"\n    return re.findall(r\"@\\w+\", text)\n\n\ndef extract_domain(url: str) -> str | None:\n    \"\"\"Extract domain from URL.\"\"\"\n    m = re.search(r\"https?://([^/]+)\", url)\n    return m.group(1) if m else None\n\n\n# Tests\nclass TestBasicMatching:\n    def test_search(self):\n        assert search_pattern(r\"\\d+\", \"abc123def\") == \"123\"\n        assert search_pattern(r\"\\d+\", \"no digits\") is None\n\n    def test_match_start(self):\n        assert match_start(r\"\\d+\", \"123abc\")\n        assert not match_start(r\"\\d+\", \"abc123\")\n\n    def test_fullmatch(self):\n        assert fullmatch(r\"\\d+\", \"123\")\n        assert not fullmatch(r\"\\d+\", \"123abc\")\n\n\nclass TestFindAll:\n    def test_find_all(self):\n        assert find_all(r\"\\d+\", \"a1b22c333\") == [\"1\", \"22\", \"333\"]\n\n    def test_find_all_groups(self):\n        assert find_all_groups(r\"(\\w+)=(\\d+)\", \"a=1 b=2\") == [\n            (\"a\", \"1\"),\n            (\"b\", \"2\"),\n        ]\n\n\nclass TestSplit:\n    def test_split(self):\n        assert split_pattern(r\"\\s+\", \"a  b   c\") == [\"a\", \"b\", \"c\"]\n        assert split_pattern(r\"[,;]\", \"a,b;c\") == [\"a\", \"b\", \"c\"]\n\n\nclass TestGroups:\n    def test_parse_date(self):\n        result = parse_date(\"2024-01-15\")\n        assert result == {\"year\": \"2024\", \"month\": \"01\", \"day\": \"15\"}\n\n    def test_parse_date_named(self):\n        result = parse_date_named(\"2024-01-15\")\n        assert result == {\"year\": \"2024\", \"month\": \"01\", \"day\": \"15\"}\n\n    def test_parse_url(self):\n        assert parse_url(\"http://example.com/path\") == (\"example.com\", \"/path\")\n\n\nclass TestBackReference:\n    def test_repeated_char(self):\n        assert has_repeated_char(\"hello\")  # ll\n        assert not has_repeated_char(\"world\")\n\n    def test_html_tag(self):\n        assert match_html_tag(\"<b>bold</b>\") == \"<b>bold</b>\"\n        assert match_html_tag(\"<b>text</i>\") is None\n\n\nclass TestLookaround:\n    def test_lookahead(self):\n        assert find_before_at(\"user@example.com\") == [\"user\"]\n\n    def test_lookbehind(self):\n        assert find_after_dollar(\"$100 $200\") == [\"100\", \"200\"]\n\n    def test_negative_lookahead(self):\n        assert find_not_followed_by(\"12px 34em 56\", \"px\") == [\"1\", \"34\", \"56\"]\n\n\nclass TestSubstitution:\n    def test_replace(self):\n        assert replace_pattern(r\"\\d+\", \"X\", \"a1b2c3\") == \"aXbXcX\"\n\n    def test_double_numbers(self):\n        assert double_numbers(\"a1b2c3\") == \"a2b4c6\"\n\n    def test_camel_to_snake(self):\n        assert camel_to_snake(\"CamelCase\") == \"camel_case\"\n        assert camel_to_snake(\"SimpleHTTPServer\") == \"simple_http_server\"\n\n\nclass TestValidation:\n    def test_email(self):\n        assert is_valid_email(\"user@example.com\")\n        assert is_valid_email(\"user+tag@sub.domain.org\")\n        assert not is_valid_email(\"invalid@\")\n\n    def test_ip(self):\n        assert is_valid_ip(\"192.168.1.1\")\n        assert is_valid_ip(\"255.255.255.0\")\n        assert not is_valid_ip(\"256.0.0.0\")\n\n    def test_mac(self):\n        assert is_valid_mac(\"3c:38:51:05:03:1e\")\n        assert is_valid_mac(\"AA:BB:CC:DD:EE:FF\")\n\n    def test_url(self):\n        assert is_valid_url(\"https://www.example.com/path\")\n        assert is_valid_url(\"example.com\")\n\n    def test_hex_color(self):\n        assert is_valid_hex_color(\"#ffffff\")\n        assert is_valid_hex_color(\"fff\")\n        assert not is_valid_hex_color(\"#gggggg\")\n\n    def test_phone(self):\n        assert is_valid_phone(\"123-456-7890\")\n        assert is_valid_phone(\"(123) 456-7890\")\n\n    def test_password(self):\n        assert is_strong_password(\"Passw0rd!\")\n        assert not is_strong_password(\"weakpass\")\n\n\nclass TestHtmlTags:\n    def test_open_tags(self):\n        assert \"<table>\" in find_open_tags(\"<table><tr></tr></table>\")\n\n    def test_close_tags(self):\n        assert \"</table>\" in find_close_tags(\"<table></table>\")\n\n    def test_strip_tags(self):\n        assert strip_html_tags(\"<p>Hello</p>\") == \"Hello\"\n\n\nclass TestLexer:\n    def test_tokenize(self):\n        tokens = tokenize(\"9 + 5 * 2\")\n        assert tokens[0] == Token(\"NUMBER\", \"9\")\n        assert tokens[1] == Token(\"PLUS\", \"+\")\n        assert tokens[2] == Token(\"NUMBER\", \"5\")\n\n\nclass TestUtility:\n    def test_hashtags(self):\n        assert find_hashtags(\"Hello #world #python\") == [\"#world\", \"#python\"]\n\n    def test_mentions(self):\n        assert find_mentions(\"Hello @user @admin\") == [\"@user\", \"@admin\"]\n\n    def test_extract_domain(self):\n        assert (\n            extract_domain(\"https://www.example.com/path\") == \"www.example.com\"\n        )\n"
  },
  {
    "path": "src/basic/set.py",
    "content": "\"\"\"Python Set Examples\n\nSource code for docs/notes/basic/python-set.rst\n\"\"\"\n\nimport pytest\n\n\n# Create a Set\ndef create_set_literal():\n    \"\"\"Create set using literal syntax.\"\"\"\n    return {1, 2, 3}\n\n\ndef create_set_from_list(items: list) -> set:\n    \"\"\"Create set from list, removing duplicates.\"\"\"\n    return set(items)\n\n\ndef create_empty_set() -> set:\n    \"\"\"Create empty set.\"\"\"\n    return set()\n\n\n# Set Comprehension\ndef set_comprehension_basic(items: list) -> set:\n    \"\"\"Basic set comprehension.\"\"\"\n    return {x for x in items}\n\n\ndef set_comprehension_filter(items: list, threshold: int) -> set:\n    \"\"\"Set comprehension with filter.\"\"\"\n    return {x for x in items if x > threshold}\n\n\ndef set_comprehension_squares(n: int) -> set:\n    \"\"\"Set of squares.\"\"\"\n    return {x**2 for x in range(n)}\n\n\n# Uniquify\ndef uniquify_list(items: list) -> list:\n    \"\"\"Remove duplicates from list.\"\"\"\n    return list(set(items))\n\n\ndef uniquify_preserve_order(items: list) -> list:\n    \"\"\"Remove duplicates preserving order (Python 3.7+).\"\"\"\n    return list(dict.fromkeys(items))\n\n\n# Add Items\ndef add_single(s: set, item) -> set:\n    \"\"\"Add single item to set.\"\"\"\n    s.add(item)\n    return s\n\n\ndef add_multiple(s: set, items) -> set:\n    \"\"\"Add multiple items to set.\"\"\"\n    s.update(items)\n    return s\n\n\n# Remove Items\ndef remove_item(s: set, item) -> set:\n    \"\"\"Remove item from set (raises KeyError if missing).\"\"\"\n    s.remove(item)\n    return s\n\n\ndef discard_item(s: set, item) -> set:\n    \"\"\"Remove item from set (no error if missing).\"\"\"\n    s.discard(item)\n    return s\n\n\ndef pop_item(s: set):\n    \"\"\"Remove and return arbitrary item.\"\"\"\n    return s.pop()\n\n\n# Set Operations\ndef union(a: set, b: set) -> set:\n    \"\"\"Union of two sets.\"\"\"\n    return a | b\n\n\ndef intersection(a: set, b: set) -> set:\n    \"\"\"Intersection of two sets.\"\"\"\n    return a & b\n\n\ndef difference(a: set, b: set) -> set:\n    \"\"\"Difference of two sets (a - b).\"\"\"\n    return a - b\n\n\ndef symmetric_difference(a: set, b: set) -> set:\n    \"\"\"Symmetric difference of two sets.\"\"\"\n    return a ^ b\n\n\ndef is_subset(a: set, b: set) -> bool:\n    \"\"\"Check if a is subset of b.\"\"\"\n    return a <= b\n\n\ndef is_proper_subset(a: set, b: set) -> bool:\n    \"\"\"Check if a is proper subset of b.\"\"\"\n    return a < b\n\n\ndef is_superset(a: set, b: set) -> bool:\n    \"\"\"Check if a is superset of b.\"\"\"\n    return a >= b\n\n\ndef is_disjoint(a: set, b: set) -> bool:\n    \"\"\"Check if sets have no common elements.\"\"\"\n    return a.isdisjoint(b)\n\n\n# Membership\ndef membership_test(s: set, item) -> bool:\n    \"\"\"Test if item is in set.\"\"\"\n    return item in s\n\n\n# Frozenset\ndef create_frozenset(items: list) -> frozenset:\n    \"\"\"Create immutable frozenset.\"\"\"\n    return frozenset(items)\n\n\ndef frozenset_as_dict_key():\n    \"\"\"Use frozenset as dictionary key.\"\"\"\n    return {frozenset([1, 2]): \"a\", frozenset([3, 4]): \"b\"}\n\n\ndef frozenset_in_set():\n    \"\"\"Use frozenset as set element.\"\"\"\n    return {frozenset([1, 2]), frozenset([3, 4])}\n\n\n# Tests\nclass TestSetCreation:\n    def test_literal(self):\n        assert create_set_literal() == {1, 2, 3}\n\n    def test_from_list(self):\n        assert create_set_from_list([1, 2, 2, 3]) == {1, 2, 3}\n\n    def test_empty(self):\n        assert create_empty_set() == set()\n        assert len(create_empty_set()) == 0\n\n\nclass TestSetComprehension:\n    def test_basic(self):\n        assert set_comprehension_basic([1, 2, 2, 3]) == {1, 2, 3}\n\n    def test_filter(self):\n        assert set_comprehension_filter([1, 2, 3, 4, 5], 3) == {4, 5}\n\n    def test_squares(self):\n        assert set_comprehension_squares(5) == {0, 1, 4, 9, 16}\n\n\nclass TestUniquify:\n    def test_uniquify(self):\n        result = uniquify_list([1, 2, 2, 3, 3, 3])\n        assert set(result) == {1, 2, 3}\n\n    def test_preserve_order(self):\n        assert uniquify_preserve_order([3, 1, 2, 1, 3]) == [3, 1, 2]\n\n\nclass TestAddRemove:\n    def test_add_single(self):\n        s = {1, 2}\n        assert add_single(s, 3) == {1, 2, 3}\n\n    def test_add_multiple(self):\n        s = {1, 2}\n        assert add_multiple(s, [3, 4]) == {1, 2, 3, 4}\n\n    def test_remove(self):\n        s = {1, 2, 3}\n        assert remove_item(s, 2) == {1, 3}\n\n    def test_remove_missing(self):\n        s = {1, 2, 3}\n        with pytest.raises(KeyError):\n            remove_item(s, 10)\n\n    def test_discard(self):\n        s = {1, 2, 3}\n        assert discard_item(s, 2) == {1, 3}\n        assert discard_item(s, 10) == {1, 3}  # no error\n\n    def test_pop(self):\n        s = {1, 2, 3}\n        item = pop_item(s)\n        assert item in {1, 2, 3}\n        assert len(s) == 2\n\n\nclass TestSetOperations:\n    def test_union(self):\n        assert union({1, 2}, {2, 3}) == {1, 2, 3}\n\n    def test_intersection(self):\n        assert intersection({1, 2, 3}, {2, 3, 4}) == {2, 3}\n\n    def test_difference(self):\n        assert difference({1, 2, 3}, {2, 3, 4}) == {1}\n\n    def test_symmetric_difference(self):\n        assert symmetric_difference({1, 2, 3}, {2, 3, 4}) == {1, 4}\n\n    def test_subset(self):\n        assert is_subset({1, 2}, {1, 2, 3})\n        assert is_subset({1, 2}, {1, 2})  # equal is subset\n        assert not is_subset({1, 2, 3}, {1, 2})\n\n    def test_proper_subset(self):\n        assert is_proper_subset({1, 2}, {1, 2, 3})\n        assert not is_proper_subset({1, 2}, {1, 2})  # equal is not proper\n\n    def test_superset(self):\n        assert is_superset({1, 2, 3}, {1, 2})\n        assert not is_superset({1, 2}, {1, 2, 3})\n\n    def test_disjoint(self):\n        assert is_disjoint({1, 2}, {3, 4})\n        assert not is_disjoint({1, 2}, {2, 3})\n\n\nclass TestMembership:\n    def test_in(self):\n        assert membership_test({1, 2, 3}, 2)\n        assert not membership_test({1, 2, 3}, 10)\n\n\nclass TestFrozenset:\n    def test_create(self):\n        fs = create_frozenset([1, 2, 2, 3])\n        assert fs == frozenset({1, 2, 3})\n\n    def test_immutable(self):\n        fs = create_frozenset([1, 2, 3])\n        assert not hasattr(fs, \"add\")\n\n    def test_as_dict_key(self):\n        d = frozenset_as_dict_key()\n        assert d[frozenset([1, 2])] == \"a\"\n\n    def test_in_set(self):\n        s = frozenset_in_set()\n        assert frozenset([1, 2]) in s\n        assert frozenset([5, 6]) not in s\n"
  },
  {
    "path": "src/basic/socket_.py",
    "content": "\"\"\"Network/socket examples and tests for pysheeet documentation.\"\"\"\n\nimport socket\nimport threading\nimport time\nimport pytest\n\n\nclass TestHostname:\n    \"\"\"Test hostname and DNS resolution.\"\"\"\n\n    def test_gethostname(self):\n        hostname = socket.gethostname()\n        assert isinstance(hostname, str)\n        assert len(hostname) > 0\n\n    def test_gethostbyname_localhost(self):\n        ip = socket.gethostbyname(\"localhost\")\n        assert ip == \"127.0.0.1\"\n\n    def test_getaddrinfo(self):\n        results = socket.getaddrinfo(\n            \"localhost\", None, proto=socket.IPPROTO_TCP\n        )\n        assert len(results) > 0\n        family, socktype, proto, canonname, sockaddr = results[0]\n        assert family in (socket.AF_INET, socket.AF_INET6)\n\n\nclass TestByteOrder:\n    \"\"\"Test network byte order conversion.\"\"\"\n\n    def test_htons(self):\n        # Host to network short\n        result = socket.htons(1)\n        # On little-endian, 1 becomes 256\n        assert result in (1, 256)\n\n    def test_htonl(self):\n        # Host to network long\n        result = socket.htonl(1)\n        assert result in (1, 16777216)\n\n    def test_ntohs(self):\n        # Network to host short\n        val = socket.htons(1234)\n        assert socket.ntohs(val) == 1234\n\n    def test_ntohl(self):\n        # Network to host long\n        val = socket.htonl(12345678)\n        assert socket.ntohl(val) == 12345678\n\n\nclass TestIPConversion:\n    \"\"\"Test IP address string/binary conversion.\"\"\"\n\n    def test_inet_aton(self):\n        addr = socket.inet_aton(\"127.0.0.1\")\n        assert addr == b\"\\x7f\\x00\\x00\\x01\"\n\n    def test_inet_ntoa(self):\n        ip = socket.inet_ntoa(b\"\\x7f\\x00\\x00\\x01\")\n        assert ip == \"127.0.0.1\"\n\n    def test_inet_pton_ipv4(self):\n        addr = socket.inet_pton(socket.AF_INET, \"192.168.1.1\")\n        assert addr == b\"\\xc0\\xa8\\x01\\x01\"\n\n    def test_inet_ntop_ipv4(self):\n        ip = socket.inet_ntop(socket.AF_INET, b\"\\xc0\\xa8\\x01\\x01\")\n        assert ip == \"192.168.1.1\"\n\n    def test_inet_pton_ipv6(self):\n        addr = socket.inet_pton(socket.AF_INET6, \"::1\")\n        assert addr == b\"\\x00\" * 15 + b\"\\x01\"\n\n    def test_inet_ntop_ipv6(self):\n        ip = socket.inet_ntop(socket.AF_INET6, b\"\\x00\" * 15 + b\"\\x01\")\n        assert ip == \"::1\"\n\n\nclass TestSocketOptions:\n    \"\"\"Test socket options.\"\"\"\n\n    def test_reuseaddr(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        val = sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)\n        assert val != 0  # Non-zero means enabled\n        sock.close()\n\n    def test_timeout(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.settimeout(5.0)\n        assert sock.gettimeout() == 5.0\n        sock.close()\n\n    def test_blocking(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock.setblocking(False)\n        assert sock.getblocking() is False\n        sock.setblocking(True)\n        assert sock.getblocking() is True\n        sock.close()\n\n\nclass TestTCPEchoServer:\n    \"\"\"Test TCP echo server functionality.\"\"\"\n\n    def test_echo(self):\n        host, port = \"localhost\", 15566\n\n        # Start server in thread\n        server_ready = threading.Event()\n\n        def server():\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            sock.bind((host, port))\n            sock.listen(1)\n            server_ready.set()\n            conn, addr = sock.accept()\n            data = conn.recv(1024)\n            conn.send(data)\n            conn.close()\n            sock.close()\n\n        t = threading.Thread(target=server)\n        t.daemon = True\n        t.start()\n        server_ready.wait()\n\n        # Client\n        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        client.connect((host, port))\n        client.send(b\"Hello\")\n        response = client.recv(1024)\n        client.close()\n\n        assert response == b\"Hello\"\n        t.join(timeout=1)\n\n\nclass TestUDPEchoServer:\n    \"\"\"Test UDP echo server functionality.\"\"\"\n\n    def test_echo(self):\n        host, port = \"localhost\", 15567\n\n        server_ready = threading.Event()\n\n        def server():\n            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n            sock.bind((host, port))\n            server_ready.set()\n            data, addr = sock.recvfrom(1024)\n            sock.sendto(data, addr)\n            sock.close()\n\n        t = threading.Thread(target=server)\n        t.daemon = True\n        t.start()\n        server_ready.wait()\n\n        # Client\n        client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n        client.sendto(b\"Hello UDP\", (host, port))\n        response, _ = client.recvfrom(1024)\n        client.close()\n\n        assert response == b\"Hello UDP\"\n        t.join(timeout=1)\n\n\nclass TestSocketPair:\n    \"\"\"Test socketpair for IPC.\"\"\"\n\n    def test_socketpair(self):\n        parent, child = socket.socketpair()\n\n        # Send from parent to child\n        parent.send(b\"Hello\")\n        assert child.recv(1024) == b\"Hello\"\n\n        # Send from child to parent\n        child.send(b\"World\")\n        assert parent.recv(1024) == b\"World\"\n\n        parent.close()\n        child.close()\n\n\nclass TestPortCheck:\n    \"\"\"Test port availability checking.\"\"\"\n\n    def test_port_available(self):\n        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        try:\n            sock.bind((\"\", 0))  # Bind to any available port\n            port = sock.getsockname()[1]\n            assert port > 0\n        finally:\n            sock.close()\n\n    def test_port_in_use(self):\n        sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        sock1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        sock1.bind((\"\", 15568))\n        sock1.listen(1)\n\n        sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        try:\n            sock2.bind((\"\", 15568))\n            assert False, \"Should have raised OSError\"\n        except OSError:\n            pass\n        finally:\n            sock1.close()\n            sock2.close()\n\n\nclass TestMACConversion:\n    \"\"\"Test MAC address conversion.\"\"\"\n\n    def test_mac_to_bytes(self):\n        import binascii\n\n        mac = \"00:11:22:33:44:55\"\n        byte = binascii.unhexlify(mac.replace(\":\", \"\"))\n        assert byte == b'\\x00\\x11\"3DU'\n\n    def test_bytes_to_mac(self):\n        import binascii\n\n        byte = b'\\x00\\x11\"3DU'\n        mac = \":\".join(f\"{b:02x}\" for b in byte)\n        assert mac == \"00:11:22:33:44:55\"\n\n\nclass TestSelectorsEcho:\n    \"\"\"Test selectors-based async server.\"\"\"\n\n    def test_selectors_echo(self):\n        import selectors\n\n        host, port = \"localhost\", 15569\n        server_ready = threading.Event()\n        stop_server = threading.Event()\n\n        def server():\n            sel = selectors.DefaultSelector()\n            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n            sock.setblocking(False)\n            sock.bind((host, port))\n            sock.listen(1)\n\n            def accept(sock):\n                conn, addr = sock.accept()\n                conn.setblocking(False)\n                sel.register(conn, selectors.EVENT_READ, read)\n\n            def read(conn):\n                data = conn.recv(1024)\n                if data:\n                    conn.send(data)\n                sel.unregister(conn)\n                conn.close()\n                stop_server.set()\n\n            sel.register(sock, selectors.EVENT_READ, accept)\n            server_ready.set()\n\n            while not stop_server.is_set():\n                events = sel.select(timeout=0.1)\n                for key, mask in events:\n                    callback = key.data\n                    callback(key.fileobj)\n\n            sel.unregister(sock)\n            sock.close()\n            sel.close()\n\n        t = threading.Thread(target=server)\n        t.daemon = True\n        t.start()\n        server_ready.wait()\n\n        # Client\n        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n        client.connect((host, port))\n        client.send(b\"Async Hello\")\n        response = client.recv(1024)\n        client.close()\n\n        assert response == b\"Async Hello\"\n        t.join(timeout=2)\n"
  },
  {
    "path": "src/basic/sqlalchemy_core.py",
    "content": "\"\"\"SQLAlchemy examples and tests for pysheeet documentation.\"\"\"\n\nfrom datetime import datetime\nimport pytest\nfrom sqlalchemy import (\n    create_engine,\n    MetaData,\n    Table,\n    Column,\n    Integer,\n    String,\n    ForeignKey,\n    select,\n    insert,\n    update,\n    delete,\n    text,\n    inspect,\n    func,\n    and_,\n    or_,\n    desc,\n    case,\n    distinct,\n    union_all,\n    exists,\n    DateTime,\n)\nfrom sqlalchemy.orm import (\n    declarative_base,\n    sessionmaker,\n    relationship,\n    joinedload,\n    aliased,\n)\nfrom sqlalchemy.ext.hybrid import hybrid_property\n\n\n# ============================================================================\n# SQLAlchemy Core Tests\n# ============================================================================\n\n\nclass TestEngine:\n    \"\"\"Test engine creation and database URLs.\"\"\"\n\n    def test_create_sqlite_memory(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        assert engine is not None\n\n    def test_create_sqlite_file(self, tmp_path):\n        db_path = tmp_path / \"test.db\"\n        engine = create_engine(f\"sqlite:///{db_path}\")\n        assert engine is not None\n\n\nclass TestRawSQL:\n    \"\"\"Test raw SQL execution.\"\"\"\n\n    def test_execute_raw_sql(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        with engine.connect() as conn:\n            conn.execute(\n                text(\"CREATE TABLE test (id INTEGER PRIMARY KEY, name TEXT)\")\n            )\n            conn.execute(\n                text(\"INSERT INTO test (name) VALUES (:name)\"),\n                {\"name\": \"Alice\"},\n            )\n            conn.commit()\n            result = conn.execute(text(\"SELECT * FROM test\"))\n            rows = result.fetchall()\n        assert len(rows) == 1\n        assert rows[0][1] == \"Alice\"\n\n\nclass TestTransaction:\n    \"\"\"Test transaction management.\"\"\"\n\n    def test_begin_commit(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        with engine.begin() as conn:\n            conn.execute(\n                text(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)\")\n            )\n            conn.execute(text(\"INSERT INTO users (name) VALUES ('Bob')\"))\n        with engine.connect() as conn:\n            result = conn.execute(text(\"SELECT * FROM users\"))\n            assert len(result.fetchall()) == 1\n\n\nclass TestMetadata:\n    \"\"\"Test metadata and table definitions.\"\"\"\n\n    def test_define_table(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n            Column(\"email\", String(100)),\n        )\n        metadata.create_all(engine)\n        assert [c.name for c in users.columns] == [\"id\", \"name\", \"email\"]\n\n    def test_reflect_table(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        with engine.begin() as conn:\n            conn.execute(\n                text(\n                    \"CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT)\"\n                )\n            )\n        metadata = MetaData()\n        metadata.reflect(bind=engine)\n        assert \"products\" in metadata.tables\n\n\nclass TestInspect:\n    \"\"\"Test database inspection.\"\"\"\n\n    def test_get_table_names(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        Table(\"users\", metadata, Column(\"id\", Integer, primary_key=True))\n        metadata.create_all(engine)\n        inspector = inspect(engine)\n        assert \"users\" in inspector.get_table_names()\n\n\nclass TestCoreInsert:\n    \"\"\"Test Core insert operations.\"\"\"\n\n    def test_single_insert(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(users).values(name=\"Alice\"))\n        with engine.connect() as conn:\n            result = conn.execute(select(users))\n            rows = result.fetchall()\n        assert len(rows) == 1\n        assert rows[0][1] == \"Alice\"\n\n    def test_bulk_insert(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(users), [{\"name\": \"Bob\"}, {\"name\": \"Carol\"}])\n        with engine.connect() as conn:\n            result = conn.execute(select(users))\n            assert len(result.fetchall()) == 2\n\n\nclass TestCoreSelect:\n    \"\"\"Test Core select operations.\"\"\"\n\n    def test_select_all(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n            Column(\"age\", Integer),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(users),\n                [{\"name\": \"Alice\", \"age\": 30}, {\"name\": \"Bob\", \"age\": 25}],\n            )\n        with engine.connect() as conn:\n            result = conn.execute(select(users))\n            assert len(result.fetchall()) == 2\n\n    def test_select_where(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n            Column(\"age\", Integer),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(users),\n                [{\"name\": \"Alice\", \"age\": 30}, {\"name\": \"Bob\", \"age\": 25}],\n            )\n        with engine.connect() as conn:\n            result = conn.execute(select(users).where(users.c.age > 28))\n            rows = result.fetchall()\n        assert len(rows) == 1\n        assert rows[0][1] == \"Alice\"\n\n\nclass TestCoreUpdate:\n    \"\"\"Test Core update operations.\"\"\"\n\n    def test_update(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(users).values(name=\"Alice\"))\n            conn.execute(\n                update(users)\n                .where(users.c.name == \"Alice\")\n                .values(name=\"Alicia\")\n            )\n        with engine.connect() as conn:\n            result = conn.execute(select(users))\n            assert result.fetchone()[1] == \"Alicia\"\n\n\nclass TestCoreDelete:\n    \"\"\"Test Core delete operations.\"\"\"\n\n    def test_delete(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(users), [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}])\n            conn.execute(delete(users).where(users.c.name == \"Bob\"))\n        with engine.connect() as conn:\n            result = conn.execute(select(users))\n            rows = result.fetchall()\n        assert len(rows) == 1\n        assert rows[0][1] == \"Alice\"\n\n\nclass TestExpressionLanguage:\n    \"\"\"Test SQL expression language.\"\"\"\n\n    def test_and_condition(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n            Column(\"age\", Integer),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(users),\n                [\n                    {\"name\": \"Alice\", \"age\": 30},\n                    {\"name\": \"Bob\", \"age\": 25},\n                    {\"name\": \"Carol\", \"age\": 35},\n                ],\n            )\n        with engine.connect() as conn:\n            stmt = select(users).where(\n                and_(users.c.age > 25, users.c.age < 35)\n            )\n            result = conn.execute(stmt)\n            rows = result.fetchall()\n        assert len(rows) == 1\n        assert rows[0][1] == \"Alice\"\n\n    def test_or_condition(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(users),\n                [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}, {\"name\": \"Carol\"}],\n            )\n        with engine.connect() as conn:\n            stmt = select(users).where(\n                or_(users.c.name == \"Alice\", users.c.name == \"Bob\")\n            )\n            result = conn.execute(stmt)\n            assert len(result.fetchall()) == 2\n\n    def test_in_clause(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(users),\n                [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}, {\"name\": \"Carol\"}],\n            )\n        with engine.connect() as conn:\n            stmt = select(users).where(users.c.name.in_([\"Alice\", \"Carol\"]))\n            result = conn.execute(stmt)\n            assert len(result.fetchall()) == 2\n\n\nclass TestCoreJoin:\n    \"\"\"Test Core join operations.\"\"\"\n\n    def test_join(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"name\", String(50)),\n        )\n        orders = Table(\n            \"orders\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"user_id\", Integer, ForeignKey(\"users.id\")),\n            Column(\"product\", String(50)),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(users), [{\"name\": \"Alice\"}, {\"name\": \"Bob\"}])\n            conn.execute(\n                insert(orders),\n                [\n                    {\"user_id\": 1, \"product\": \"Book\"},\n                    {\"user_id\": 1, \"product\": \"Pen\"},\n                ],\n            )\n        with engine.connect() as conn:\n            stmt = select(users.c.name, orders.c.product).select_from(\n                users.join(orders)\n            )\n            result = conn.execute(stmt)\n            rows = result.fetchall()\n        assert len(rows) == 2\n        assert all(row[0] == \"Alice\" for row in rows)\n\n\nclass TestAggregate:\n    \"\"\"Test aggregate functions.\"\"\"\n\n    def test_count(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        sales = Table(\n            \"sales\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"amount\", Integer),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(insert(sales), [{\"amount\": 100}, {\"amount\": 200}])\n        with engine.connect() as conn:\n            result = conn.execute(select(func.count()).select_from(sales))\n            assert result.scalar() == 2\n\n    def test_sum_group_by(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        sales = Table(\n            \"sales\",\n            metadata,\n            Column(\"id\", Integer, primary_key=True),\n            Column(\"product\", String(50)),\n            Column(\"amount\", Integer),\n        )\n        metadata.create_all(engine)\n        with engine.begin() as conn:\n            conn.execute(\n                insert(sales),\n                [\n                    {\"product\": \"A\", \"amount\": 100},\n                    {\"product\": \"A\", \"amount\": 150},\n                    {\"product\": \"B\", \"amount\": 200},\n                ],\n            )\n        with engine.connect() as conn:\n            stmt = select(sales.c.product, func.sum(sales.c.amount)).group_by(\n                sales.c.product\n            )\n            result = conn.execute(stmt)\n            rows = dict(result.fetchall())\n        assert rows[\"A\"] == 250\n        assert rows[\"B\"] == 200\n\n\nclass TestDropTable:\n    \"\"\"Test dropping tables.\"\"\"\n\n    def test_drop_single(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        users = Table(\n            \"users\", metadata, Column(\"id\", Integer, primary_key=True)\n        )\n        metadata.create_all(engine)\n        assert \"users\" in inspect(engine).get_table_names()\n        users.drop(engine)\n        assert \"users\" not in inspect(engine).get_table_names()\n\n    def test_drop_all(self):\n        engine = create_engine(\"sqlite:///:memory:\")\n        metadata = MetaData()\n        Table(\"t1\", metadata, Column(\"id\", Integer, primary_key=True))\n        Table(\"t2\", metadata, Column(\"id\", Integer, primary_key=True))\n        metadata.create_all(engine)\n        assert len(inspect(engine).get_table_names()) == 2\n        metadata.drop_all(engine)\n        assert len(inspect(engine).get_table_names()) == 0\n"
  },
  {
    "path": "src/basic/sqlalchemy_orm.py",
    "content": "\"\"\"SQLAlchemy ORM examples and tests for pysheeet documentation.\"\"\"\n\nimport pytest\nfrom sqlalchemy import (\n    create_engine,\n    Column,\n    Integer,\n    String,\n    ForeignKey,\n    Table,\n    select,\n    and_,\n    or_,\n    func,\n    DateTime,\n    event,\n)\nfrom sqlalchemy.orm import (\n    declarative_base,\n    sessionmaker,\n    relationship,\n    joinedload,\n)\nfrom sqlalchemy.ext.hybrid import hybrid_property\nfrom datetime import datetime\n\n\n# ============================================================================\n# SQLAlchemy ORM Tests\n# ============================================================================\n\n\nclass TestDeclarativeBase:\n    \"\"\"Test declarative model definitions.\"\"\"\n\n    def test_define_model(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        assert User.__tablename__ == \"users\"\n\n\nclass TestSession:\n    \"\"\"Test session operations.\"\"\"\n\n    def test_add_commit(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            user = User(name=\"Alice\")\n            session.add(user)\n            session.commit()\n            assert user.id == 1\n        finally:\n            session.close()\n\n    def test_add_all(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            users = [User(name=\"Bob\"), User(name=\"Carol\")]\n            session.add_all(users)\n            session.commit()\n            assert all(u.id is not None for u in users)\n        finally:\n            session.close()\n\n\nclass TestORMQuery:\n    \"\"\"Test ORM query operations.\"\"\"\n\n    def test_select_all(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            users = session.execute(select(User)).scalars().all()\n            assert len(users) == 2\n        finally:\n            session.close()\n\n    def test_filter_where(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            age = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [User(name=\"Alice\", age=30), User(name=\"Bob\", age=25)]\n            )\n            session.commit()\n            user = (\n                session.execute(select(User).where(User.age > 28))\n                .scalars()\n                .first()\n            )\n            assert user.name == \"Alice\"\n        finally:\n            session.close()\n\n\nclass TestORMFilter:\n    \"\"\"Test ORM filter operations.\"\"\"\n\n    def test_and_filter(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            age = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    User(name=\"Alice\", age=30),\n                    User(name=\"Bob\", age=25),\n                    User(name=\"Amy\", age=35),\n                ]\n            )\n            session.commit()\n            stmt = select(User).where(\n                and_(User.age >= 30, User.name.like(\"A%\"))\n            )\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 2\n        finally:\n            session.close()\n\n    def test_or_filter(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [User(name=\"Alice\"), User(name=\"Bob\"), User(name=\"Carol\")]\n            )\n            session.commit()\n            stmt = select(User).where(\n                or_(User.name == \"Alice\", User.name == \"Bob\")\n            )\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 2\n        finally:\n            session.close()\n\n    def test_in_filter(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            age = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(age=25), User(age=30), User(age=35)])\n            session.commit()\n            stmt = select(User).where(User.age.in_([25, 35]))\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 2\n        finally:\n            session.close()\n\n\nclass TestORMUpdate:\n    \"\"\"Test ORM update operations.\"\"\"\n\n    def test_update_object(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add(User(name=\"Alice\"))\n            session.commit()\n            user = session.execute(select(User)).scalars().first()\n            user.name = \"Alicia\"\n            session.commit()\n            user = session.execute(select(User)).scalars().first()\n            assert user.name == \"Alicia\"\n        finally:\n            session.close()\n\n\nclass TestORMDelete:\n    \"\"\"Test ORM delete operations.\"\"\"\n\n    def test_delete_object(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            user = (\n                session.execute(select(User).where(User.name == \"Bob\"))\n                .scalars()\n                .first()\n            )\n            session.delete(user)\n            session.commit()\n            users = session.execute(select(User)).scalars().all()\n            assert len(users) == 1\n            assert users[0].name == \"Alice\"\n        finally:\n            session.close()\n\n\nclass TestOneToMany:\n    \"\"\"Test one-to-many relationships.\"\"\"\n\n    def test_relationship(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            posts = relationship(\"Post\", back_populates=\"author\")\n\n        class Post(Base):\n            __tablename__ = \"posts\"\n            id = Column(Integer, primary_key=True)\n            title = Column(String(100))\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n            author = relationship(\"User\", back_populates=\"posts\")\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            user = User(name=\"Alice\")\n            user.posts.append(Post(title=\"First\"))\n            user.posts.append(Post(title=\"Second\"))\n            session.add(user)\n            session.commit()\n            user = session.execute(select(User)).scalars().first()\n            assert len(user.posts) == 2\n        finally:\n            session.close()\n\n\nclass TestManyToMany:\n    \"\"\"Test many-to-many relationships.\"\"\"\n\n    def test_relationship(self):\n        Base = declarative_base()\n        student_course = Table(\n            \"student_course\",\n            Base.metadata,\n            Column(\n                \"student_id\",\n                Integer,\n                ForeignKey(\"students.id\"),\n                primary_key=True,\n            ),\n            Column(\n                \"course_id\",\n                Integer,\n                ForeignKey(\"courses.id\"),\n                primary_key=True,\n            ),\n        )\n\n        class Student(Base):\n            __tablename__ = \"students\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            courses = relationship(\n                \"Course\", secondary=student_course, back_populates=\"students\"\n            )\n\n        class Course(Base):\n            __tablename__ = \"courses\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            students = relationship(\n                \"Student\", secondary=student_course, back_populates=\"courses\"\n            )\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            math = Course(name=\"Math\")\n            physics = Course(name=\"Physics\")\n            alice = Student(name=\"Alice\", courses=[math, physics])\n            bob = Student(name=\"Bob\", courses=[math])\n            session.add_all([alice, bob])\n            session.commit()\n            math = (\n                session.execute(select(Course).where(Course.name == \"Math\"))\n                .scalars()\n                .first()\n            )\n            assert len(math.students) == 2\n        finally:\n            session.close()\n\n\nclass TestSelfReferential:\n    \"\"\"Test self-referential relationships.\"\"\"\n\n    def test_hierarchy(self):\n        Base = declarative_base()\n\n        class Employee(Base):\n            __tablename__ = \"employees\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            manager_id = Column(Integer, ForeignKey(\"employees.id\"))\n            manager = relationship(\n                \"Employee\", remote_side=[id], backref=\"subordinates\"\n            )\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            ceo = Employee(name=\"CEO\")\n            session.add(ceo)\n            session.flush()\n            manager = Employee(name=\"Manager\", manager_id=ceo.id)\n            session.add(manager)\n            session.flush()\n            worker = Employee(name=\"Worker\", manager_id=manager.id)\n            session.add(worker)\n            session.commit()\n            mgr = (\n                session.execute(\n                    select(Employee).where(Employee.name == \"Manager\")\n                )\n                .scalars()\n                .first()\n            )\n            assert mgr.manager.name == \"CEO\"\n            assert len(mgr.subordinates) == 1\n        finally:\n            session.close()\n\n\nclass TestCascade:\n    \"\"\"Test cascade delete.\"\"\"\n\n    def test_delete_orphan(self):\n        Base = declarative_base()\n\n        class Parent(Base):\n            __tablename__ = \"parents\"\n            id = Column(Integer, primary_key=True)\n            children = relationship(\"Child\", cascade=\"all, delete-orphan\")\n\n        class Child(Base):\n            __tablename__ = \"children\"\n            id = Column(Integer, primary_key=True)\n            parent_id = Column(Integer, ForeignKey(\"parents.id\"))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            parent = Parent()\n            parent.children = [Child(), Child()]\n            session.add(parent)\n            session.commit()\n            session.delete(parent)\n            session.commit()\n            children = session.execute(select(Child)).scalars().all()\n            assert len(children) == 0\n        finally:\n            session.close()\n\n\nclass TestEagerLoading:\n    \"\"\"Test eager loading.\"\"\"\n\n    def test_joinedload(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            posts = relationship(\"Post\", back_populates=\"author\")\n\n        class Post(Base):\n            __tablename__ = \"posts\"\n            id = Column(Integer, primary_key=True)\n            title = Column(String(100))\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n            author = relationship(\"User\", back_populates=\"posts\")\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            user = User(name=\"Alice\")\n            user.posts = [Post(title=\"Post1\"), Post(title=\"Post2\")]\n            session.add(user)\n            session.commit()\n            stmt = select(User).options(joinedload(User.posts))\n            user = session.execute(stmt).scalars().unique().first()\n            assert len(user.posts) == 2\n        finally:\n            session.close()\n\n\nclass TestHybridProperty:\n    \"\"\"Test hybrid properties.\"\"\"\n\n    def test_full_name(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            first_name = Column(String(50))\n            last_name = Column(String(50))\n\n            @hybrid_property\n            def full_name(self):\n                return f\"{self.first_name} {self.last_name}\"\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add(User(first_name=\"Alice\", last_name=\"Smith\"))\n            session.commit()\n            user = session.execute(select(User)).scalars().first()\n            assert user.full_name == \"Alice Smith\"\n        finally:\n            session.close()\n\n\nclass TestEventHooks:\n    \"\"\"Test event hooks.\"\"\"\n\n    def test_before_insert(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            created_at = Column(DateTime)\n\n        @event.listens_for(User, \"before_insert\")\n        def set_created_at(mapper, connection, target):\n            target.created_at = datetime.now()\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            user = User(name=\"Alice\")\n            session.add(user)\n            session.commit()\n            assert user.created_at is not None\n        finally:\n            session.close()\n"
  },
  {
    "path": "src/basic/sqlalchemy_query.py",
    "content": "\"\"\"SQLAlchemy query recipe examples and tests for pysheeet documentation.\"\"\"\n\nimport pytest\nfrom sqlalchemy import (\n    create_engine,\n    Column,\n    Integer,\n    String,\n    ForeignKey,\n    select,\n    insert,\n    func,\n    desc,\n    case,\n    distinct,\n    union_all,\n    exists,\n    text,\n)\nfrom sqlalchemy.orm import (\n    declarative_base,\n    sessionmaker,\n    relationship,\n    aliased,\n)\n\n\n# ============================================================================\n# SQLAlchemy Query Recipe Tests\n# ============================================================================\n\n\nclass TestOrderBy:\n    \"\"\"Test order by operations.\"\"\"\n\n    def test_ascending(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            age = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    User(name=\"Alice\", age=30),\n                    User(name=\"Bob\", age=25),\n                    User(name=\"Carol\", age=35),\n                ]\n            )\n            session.commit()\n            stmt = select(User).order_by(User.age)\n            users = session.execute(stmt).scalars().all()\n            assert [u.name for u in users] == [\"Bob\", \"Alice\", \"Carol\"]\n        finally:\n            session.close()\n\n    def test_descending(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            age = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(age=30), User(age=25), User(age=35)])\n            session.commit()\n            stmt = select(User).order_by(desc(User.age))\n            users = session.execute(stmt).scalars().all()\n            assert [u.age for u in users] == [35, 30, 25]\n        finally:\n            session.close()\n\n\nclass TestLimitOffset:\n    \"\"\"Test limit and offset operations.\"\"\"\n\n    def test_limit(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=f\"User{i}\") for i in range(10)])\n            session.commit()\n            stmt = select(User).order_by(User.id).limit(3)\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 3\n        finally:\n            session.close()\n\n    def test_offset(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=f\"User{i}\") for i in range(10)])\n            session.commit()\n            stmt = select(User).order_by(User.id).limit(3).offset(3)\n            users = session.execute(stmt).scalars().all()\n            assert [u.name for u in users] == [\"User3\", \"User4\", \"User5\"]\n        finally:\n            session.close()\n\n\nclass TestGroupBy:\n    \"\"\"Test group by and aggregate operations.\"\"\"\n\n    def test_sum_group_by(self):\n        Base = declarative_base()\n\n        class Sale(Base):\n            __tablename__ = \"sales\"\n            id = Column(Integer, primary_key=True)\n            product = Column(String(50))\n            amount = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    Sale(product=\"A\", amount=100),\n                    Sale(product=\"A\", amount=150),\n                    Sale(product=\"B\", amount=200),\n                ]\n            )\n            session.commit()\n            stmt = select(Sale.product, func.sum(Sale.amount)).group_by(\n                Sale.product\n            )\n            results = dict(session.execute(stmt).fetchall())\n            assert results[\"A\"] == 250\n            assert results[\"B\"] == 200\n        finally:\n            session.close()\n\n    def test_having(self):\n        Base = declarative_base()\n\n        class Sale(Base):\n            __tablename__ = \"sales\"\n            id = Column(Integer, primary_key=True)\n            product = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [Sale(product=\"A\"), Sale(product=\"A\"), Sale(product=\"B\")]\n            )\n            session.commit()\n            stmt = (\n                select(Sale.product, func.count())\n                .group_by(Sale.product)\n                .having(func.count() > 1)\n            )\n            results = session.execute(stmt).fetchall()\n            assert len(results) == 1\n            assert results[0][0] == \"A\"\n        finally:\n            session.close()\n\n\nclass TestJoin:\n    \"\"\"Test join operations.\"\"\"\n\n    def test_inner_join(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            product = Column(String(50))\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            alice = (\n                session.execute(select(User).where(User.name == \"Alice\"))\n                .scalars()\n                .first()\n            )\n            session.add_all([Order(product=\"Book\", user_id=alice.id)])\n            session.commit()\n            stmt = select(User.name, Order.product).join(Order)\n            results = session.execute(stmt).fetchall()\n            assert len(results) == 1\n            assert results[0] == (\"Alice\", \"Book\")\n        finally:\n            session.close()\n\n    def test_outer_join(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            alice = (\n                session.execute(select(User).where(User.name == \"Alice\"))\n                .scalars()\n                .first()\n            )\n            session.add(Order(user_id=alice.id))\n            session.commit()\n            stmt = select(User.name, Order.id).outerjoin(Order)\n            results = session.execute(stmt).fetchall()\n            assert len(results) == 2\n        finally:\n            session.close()\n\n\nclass TestSubquery:\n    \"\"\"Test subquery operations.\"\"\"\n\n    def test_scalar_subquery(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            score = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    User(name=\"Alice\", score=85),\n                    User(name=\"Bob\", score=90),\n                    User(name=\"Carol\", score=75),\n                ]\n            )\n            session.commit()\n            avg_score = select(func.avg(User.score)).scalar_subquery()\n            stmt = select(User).where(User.score > avg_score)\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 2\n        finally:\n            session.close()\n\n\nclass TestCTE:\n    \"\"\"Test common table expressions.\"\"\"\n\n    def test_cte(self):\n        Base = declarative_base()\n\n        class Sale(Base):\n            __tablename__ = \"sales\"\n            id = Column(Integer, primary_key=True)\n            region = Column(String(50))\n            amount = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    Sale(region=\"East\", amount=100),\n                    Sale(region=\"East\", amount=200),\n                    Sale(region=\"West\", amount=150),\n                ]\n            )\n            session.commit()\n            regional_totals = (\n                select(Sale.region, func.sum(Sale.amount).label(\"total\"))\n                .group_by(Sale.region)\n                .cte(\"regional_totals\")\n            )\n            stmt = select(regional_totals).where(regional_totals.c.total > 200)\n            results = session.execute(stmt).fetchall()\n            assert len(results) == 1\n            assert results[0][0] == \"East\"\n        finally:\n            session.close()\n\n\nclass TestExists:\n    \"\"\"Test exists operations.\"\"\"\n\n    def test_exists(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            alice = (\n                session.execute(select(User).where(User.name == \"Alice\"))\n                .scalars()\n                .first()\n            )\n            session.add(Order(user_id=alice.id))\n            session.commit()\n            has_orders = exists().where(Order.user_id == User.id)\n            stmt = select(User).where(has_orders)\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 1\n            assert users[0].name == \"Alice\"\n        finally:\n            session.close()\n\n    def test_not_exists(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            user_id = Column(Integer, ForeignKey(\"users.id\"))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            alice = (\n                session.execute(select(User).where(User.name == \"Alice\"))\n                .scalars()\n                .first()\n            )\n            session.add(Order(user_id=alice.id))\n            session.commit()\n            has_orders = exists().where(Order.user_id == User.id)\n            stmt = select(User).where(~has_orders)\n            users = session.execute(stmt).scalars().all()\n            assert len(users) == 1\n            assert users[0].name == \"Bob\"\n        finally:\n            session.close()\n\n\nclass TestUnion:\n    \"\"\"Test union operations.\"\"\"\n\n    def test_union_all(self):\n        Base = declarative_base()\n\n        class Customer(Base):\n            __tablename__ = \"customers\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        class Supplier(Base):\n            __tablename__ = \"suppliers\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([Customer(name=\"Alice\"), Customer(name=\"Bob\")])\n            session.add_all([Supplier(name=\"Acme\"), Supplier(name=\"Bob\")])\n            session.commit()\n            stmt = union_all(select(Customer.name), select(Supplier.name))\n            results = [row[0] for row in session.execute(stmt)]\n            assert len(results) == 4\n            assert results.count(\"Bob\") == 2\n        finally:\n            session.close()\n\n\nclass TestCase:\n    \"\"\"Test case expressions.\"\"\"\n\n    def test_case(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            score = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    User(name=\"Alice\", score=95),\n                    User(name=\"Bob\", score=75),\n                    User(name=\"Carol\", score=55),\n                ]\n            )\n            session.commit()\n            grade = case(\n                (User.score >= 90, \"A\"), (User.score >= 70, \"B\"), else_=\"C\"\n            )\n            stmt = select(User.name, grade.label(\"grade\"))\n            results = dict(session.execute(stmt).fetchall())\n            assert results[\"Alice\"] == \"A\"\n            assert results[\"Bob\"] == \"B\"\n            assert results[\"Carol\"] == \"C\"\n        finally:\n            session.close()\n\n\nclass TestDistinct:\n    \"\"\"Test distinct operations.\"\"\"\n\n    def test_distinct(self):\n        Base = declarative_base()\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            customer = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    Order(customer=\"Alice\"),\n                    Order(customer=\"Alice\"),\n                    Order(customer=\"Bob\"),\n                ]\n            )\n            session.commit()\n            stmt = select(Order.customer).distinct()\n            results = session.execute(stmt).fetchall()\n            assert len(results) == 2\n        finally:\n            session.close()\n\n    def test_count_distinct(self):\n        Base = declarative_base()\n\n        class Order(Base):\n            __tablename__ = \"orders\"\n            id = Column(Integer, primary_key=True)\n            product = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    Order(product=\"Book\"),\n                    Order(product=\"Book\"),\n                    Order(product=\"Pen\"),\n                ]\n            )\n            session.commit()\n            stmt = select(func.count(distinct(Order.product)))\n            result = session.execute(stmt).scalar()\n            assert result == 2\n        finally:\n            session.close()\n\n\nclass TestAliased:\n    \"\"\"Test aliased tables.\"\"\"\n\n    def test_aliased(self):\n        Base = declarative_base()\n\n        class Employee(Base):\n            __tablename__ = \"employees\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n            salary = Column(Integer)\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all(\n                [\n                    Employee(name=\"Alice\", salary=50000),\n                    Employee(name=\"Bob\", salary=60000),\n                    Employee(name=\"Carol\", salary=55000),\n                ]\n            )\n            session.commit()\n            alice_alias = aliased(Employee, name=\"alice\")\n            stmt = (\n                select(Employee.name)\n                .select_from(Employee)\n                .join(alice_alias, alice_alias.name == \"Alice\")\n                .where(Employee.salary > alice_alias.salary)\n            )\n            results = [row[0] for row in session.execute(stmt)]\n            assert \"Bob\" in results\n            assert \"Carol\" in results\n            assert \"Alice\" not in results\n        finally:\n            session.close()\n\n\nclass TestRawSQL:\n    \"\"\"Test raw SQL execution.\"\"\"\n\n    def test_text(self):\n        Base = declarative_base()\n\n        class User(Base):\n            __tablename__ = \"users\"\n            id = Column(Integer, primary_key=True)\n            name = Column(String(50))\n\n        engine = create_engine(\"sqlite:///:memory:\")\n        Base.metadata.create_all(engine)\n        Session = sessionmaker(bind=engine)\n        session = Session()\n        try:\n            session.add_all([User(name=\"Alice\"), User(name=\"Bob\")])\n            session.commit()\n            result = session.execute(\n                text(\"SELECT * FROM users WHERE name = :name\"),\n                {\"name\": \"Alice\"},\n            )\n            rows = result.fetchall()\n            assert len(rows) == 1\n            assert rows[0][1] == \"Alice\"\n        finally:\n            session.close()\n"
  },
  {
    "path": "src/basic/typing_.py",
    "content": "\"\"\"Python Typing Examples\n\nSource code for docs/notes/basic/python-typing.rst\n\"\"\"\n\nimport pytest\nfrom typing import (\n    Optional,\n    Union,\n    Callable,\n    TypeVar,\n    Generic,\n    Protocol,\n    TypedDict,\n    Literal,\n    Final,\n    ClassVar,\n)\n\n\n# Basic Types\ndef greet(name: str) -> str:\n    \"\"\"Function with type annotations.\"\"\"\n    return f\"Hello, {name}!\"\n\n\ndef add(a: int, b: int) -> int:\n    \"\"\"Add two integers.\"\"\"\n    return a + b\n\n\n# Collection Types\ndef sum_list(numbers: list[int]) -> int:\n    \"\"\"Sum a list of integers.\"\"\"\n    return sum(numbers)\n\n\ndef get_value(data: dict[str, int], key: str) -> int | None:\n    \"\"\"Get value from dict.\"\"\"\n    return data.get(key)\n\n\n# Optional and Union\ndef find_user(user_id: int) -> Optional[str]:\n    \"\"\"Return username or None.\"\"\"\n    users = {1: \"Alice\", 2: \"Bob\"}\n    return users.get(user_id)\n\n\ndef process(value: int | str) -> str:\n    \"\"\"Process int or str.\"\"\"\n    return str(value)\n\n\n# Callable\ndef apply(func: Callable[[int, int], int], a: int, b: int) -> int:\n    \"\"\"Apply function to arguments.\"\"\"\n    return func(a, b)\n\n\ndouble: Callable[[int], int] = lambda x: x * 2\n\n\n# TypeVar and Generics\nT = TypeVar(\"T\")\n\n\ndef first(items: list[T]) -> T:\n    \"\"\"Return first item.\"\"\"\n    return items[0]\n\n\nNumber = TypeVar(\"Number\", int, float)\n\n\ndef double_num(x: Number) -> Number:\n    \"\"\"Double a number.\"\"\"\n    return x * 2\n\n\n# Generic Class\nclass Stack(Generic[T]):\n    \"\"\"Generic stack class.\"\"\"\n\n    def __init__(self) -> None:\n        self._items: list[T] = []\n\n    def push(self, item: T) -> None:\n        self._items.append(item)\n\n    def pop(self) -> T:\n        return self._items.pop()\n\n    def is_empty(self) -> bool:\n        return len(self._items) == 0\n\n\n# Protocol\nclass Drawable(Protocol):\n    \"\"\"Protocol for drawable objects.\"\"\"\n\n    def draw(self) -> str: ...\n\n\nclass Circle:\n    \"\"\"Circle that implements Drawable.\"\"\"\n\n    def draw(self) -> str:\n        return \"Circle\"\n\n\nclass Square:\n    \"\"\"Square that implements Drawable.\"\"\"\n\n    def draw(self) -> str:\n        return \"Square\"\n\n\ndef render(shape: Drawable) -> str:\n    \"\"\"Render any drawable.\"\"\"\n    return shape.draw()\n\n\n# TypedDict\nclass UserDict(TypedDict):\n    \"\"\"Typed dictionary for user data.\"\"\"\n\n    name: str\n    age: int\n\n\n# Literal\ndef set_status(status: Literal[\"active\", \"inactive\"]) -> str:\n    \"\"\"Set status with literal type.\"\"\"\n    return f\"Status: {status}\"\n\n\n# Final\nMAX_SIZE: Final = 100\n\n\n# ClassVar\nclass Config:\n    \"\"\"Class with ClassVar.\"\"\"\n\n    debug: ClassVar[bool] = False\n    name: str\n\n    def __init__(self, name: str) -> None:\n        self.name = name\n\n\n# Tests\nclass TestBasicTypes:\n    def test_greet(self):\n        assert greet(\"World\") == \"Hello, World!\"\n\n    def test_add(self):\n        assert add(2, 3) == 5\n\n\nclass TestCollections:\n    def test_sum_list(self):\n        assert sum_list([1, 2, 3, 4, 5]) == 15\n\n    def test_get_value(self):\n        assert get_value({\"a\": 1, \"b\": 2}, \"a\") == 1\n        assert get_value({\"a\": 1}, \"x\") is None\n\n\nclass TestOptionalUnion:\n    def test_find_user(self):\n        assert find_user(1) == \"Alice\"\n        assert find_user(999) is None\n\n    def test_process(self):\n        assert process(42) == \"42\"\n        assert process(\"hello\") == \"hello\"\n\n\nclass TestCallable:\n    def test_apply(self):\n        assert apply(lambda a, b: a + b, 2, 3) == 5\n\n    def test_double(self):\n        assert double(5) == 10\n\n\nclass TestGenerics:\n    def test_first(self):\n        assert first([1, 2, 3]) == 1\n        assert first([\"a\", \"b\"]) == \"a\"\n\n    def test_double_num(self):\n        assert double_num(5) == 10\n        assert double_num(2.5) == 5.0\n\n\nclass TestGenericClass:\n    def test_stack(self):\n        s: Stack[int] = Stack()\n        s.push(1)\n        s.push(2)\n        assert s.pop() == 2\n        assert s.pop() == 1\n        assert s.is_empty()\n\n\nclass TestProtocol:\n    def test_render(self):\n        assert render(Circle()) == \"Circle\"\n        assert render(Square()) == \"Square\"\n\n\nclass TestTypedDict:\n    def test_user_dict(self):\n        user: UserDict = {\"name\": \"Alice\", \"age\": 30}\n        assert user[\"name\"] == \"Alice\"\n        assert user[\"age\"] == 30\n\n\nclass TestLiteral:\n    def test_set_status(self):\n        assert set_status(\"active\") == \"Status: active\"\n\n\nclass TestClassVar:\n    def test_config(self):\n        c = Config(\"test\")\n        assert c.name == \"test\"\n        assert Config.debug is False\n"
  },
  {
    "path": "src/basic/unicode_.py",
    "content": "\"\"\"Python Unicode Examples\n\nSource code for docs/notes/basic/python-unicode.rst\n\"\"\"\n\nimport pytest\nimport unicodedata\n\n\n# Encoding and Decoding\ndef encode_utf8(s: str) -> bytes:\n    \"\"\"Encode string to UTF-8 bytes.\"\"\"\n    return s.encode(\"utf-8\")\n\n\ndef decode_utf8(b: bytes) -> str:\n    \"\"\"Decode UTF-8 bytes to string.\"\"\"\n    return b.decode(\"utf-8\")\n\n\ndef encode_with_errors(s: str, encoding: str, errors: str) -> bytes:\n    \"\"\"Encode with error handling.\"\"\"\n    return s.encode(encoding, errors=errors)\n\n\n# Code Points\ndef get_code_point(char: str) -> int:\n    \"\"\"Get Unicode code point of character.\"\"\"\n    return ord(char)\n\n\ndef get_char(code_point: int) -> str:\n    \"\"\"Get character from code point.\"\"\"\n    return chr(code_point)\n\n\ndef format_code_points(s: str) -> list[str]:\n    \"\"\"Format string as list of code points.\"\"\"\n    return [f\"U+{ord(c):04X}\" for c in s]\n\n\n# Normalization\ndef normalize_nfc(s: str) -> str:\n    \"\"\"Normalize to NFC (composed) form.\"\"\"\n    return unicodedata.normalize(\"NFC\", s)\n\n\ndef normalize_nfd(s: str) -> str:\n    \"\"\"Normalize to NFD (decomposed) form.\"\"\"\n    return unicodedata.normalize(\"NFD\", s)\n\n\n# Character Info\ndef get_char_name(char: str) -> str:\n    \"\"\"Get Unicode name of character.\"\"\"\n    return unicodedata.name(char)\n\n\ndef get_char_category(char: str) -> str:\n    \"\"\"Get Unicode category of character.\"\"\"\n    return unicodedata.category(char)\n\n\ndef lookup_char(name: str) -> str:\n    \"\"\"Lookup character by Unicode name.\"\"\"\n    return unicodedata.lookup(name)\n\n\n# String Operations\ndef case_insensitive_equal(s1: str, s2: str) -> bool:\n    \"\"\"Case-insensitive comparison using casefold.\"\"\"\n    return s1.casefold() == s2.casefold()\n\n\n# Tests\nclass TestEncodingDecoding:\n    def test_encode_utf8(self):\n        assert encode_utf8(\"Café\") == b\"Caf\\xc3\\xa9\"\n\n    def test_decode_utf8(self):\n        assert decode_utf8(b\"Caf\\xc3\\xa9\") == \"Café\"\n\n    def test_roundtrip(self):\n        s = \"Hello, 世界!\"\n        assert decode_utf8(encode_utf8(s)) == s\n\n    def test_encode_errors_ignore(self):\n        assert encode_with_errors(\"Café\", \"ascii\", \"ignore\") == b\"Caf\"\n\n    def test_encode_errors_replace(self):\n        assert encode_with_errors(\"Café\", \"ascii\", \"replace\") == b\"Caf?\"\n\n\nclass TestCodePoints:\n    def test_get_code_point(self):\n        assert get_code_point(\"A\") == 65\n        assert get_code_point(\"é\") == 233\n        assert get_code_point(\"中\") == 20013\n\n    def test_get_char(self):\n        assert get_char(65) == \"A\"\n        assert get_char(233) == \"é\"\n        assert get_char(20013) == \"中\"\n\n    def test_format_code_points(self):\n        assert format_code_points(\"AB\") == [\"U+0041\", \"U+0042\"]\n\n\nclass TestNormalization:\n    def test_nfc(self):\n        # e + combining accent -> single é\n        composed = normalize_nfc(\"e\\u0301\")\n        assert composed == \"é\"\n        assert len(composed) == 1\n\n    def test_nfd(self):\n        # single é -> e + combining accent\n        decomposed = normalize_nfd(\"é\")\n        assert len(decomposed) == 2\n\n    def test_normalization_equality(self):\n        s1 = \"é\"\n        s2 = \"e\\u0301\"\n        assert s1 != s2\n        assert normalize_nfc(s1) == normalize_nfc(s2)\n\n\nclass TestCharInfo:\n    def test_get_char_name(self):\n        assert get_char_name(\"A\") == \"LATIN CAPITAL LETTER A\"\n        assert \"ACUTE\" in get_char_name(\"é\")\n\n    def test_get_char_category(self):\n        assert get_char_category(\"A\") == \"Lu\"  # Letter, uppercase\n        assert get_char_category(\"a\") == \"Ll\"  # Letter, lowercase\n        assert get_char_category(\"1\") == \"Nd\"  # Number, digit\n\n    def test_lookup_char(self):\n        assert lookup_char(\"LATIN CAPITAL LETTER A\") == \"A\"\n        assert lookup_char(\"GREEK SMALL LETTER ALPHA\") == \"α\"\n\n\nclass TestStringOperations:\n    def test_case_insensitive(self):\n        assert case_insensitive_equal(\"CAFÉ\", \"café\")\n        assert case_insensitive_equal(\"Straße\", \"strasse\")\n\n    def test_unicode_upper_lower(self):\n        assert \"café\".upper() == \"CAFÉ\"\n        assert \"CAFÉ\".lower() == \"café\"\n\n    def test_unicode_isalpha(self):\n        assert \"中文\".isalpha()\n        assert \"αβγ\".isalpha()\n"
  },
  {
    "path": "src/cext/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\nproject(pysheeet_cext)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\n\n# Find Python and pybind11\nfind_package(Python3 REQUIRED COMPONENTS Interpreter Development)\nfind_package(pybind11 CONFIG QUIET)\n\n# If pybind11 not found via CMake, try to find it via Python\nif(NOT pybind11_FOUND)\n    execute_process(\n        COMMAND ${Python3_EXECUTABLE} -c \"import pybind11; print(pybind11.get_cmake_dir())\"\n        OUTPUT_VARIABLE pybind11_DIR\n        OUTPUT_STRIP_TRAILING_WHITESPACE\n        RESULT_VARIABLE pybind11_RESULT\n    )\n    if(pybind11_RESULT EQUAL 0)\n        find_package(pybind11 CONFIG REQUIRED PATHS ${pybind11_DIR})\n    else()\n        message(FATAL_ERROR \"pybind11 not found. Install with: pip install pybind11\")\n    endif()\nendif()\n\n# Example 1: Basic functions (add, fib)\npybind11_add_module(example example.cpp)\n\n# Example 2: Vector2D class\npybind11_add_module(vector vector.cpp)\n\n# Example 3: NumPy integration\npybind11_add_module(numpy_example numpy_example.cpp)\n\n# Example 4: GIL release example\npybind11_add_module(gil_example gil_example.cpp)\n\n# Pure C library (for ctypes/cffi examples)\nadd_library(fib SHARED fib.c)\nset_target_properties(fib PROPERTIES PREFIX \"lib\")\n\n# Installation\ninstall(TARGETS example vector numpy_example gil_example fib\n        LIBRARY DESTINATION .)\n"
  },
  {
    "path": "src/cext/README.md",
    "content": "# pybind11 C++ Extension Examples\n\nThis directory contains C++ source files demonstrating pybind11 bindings.\n\n## Prerequisites\n\n```bash\npip install pybind11 numpy\n```\n\n## Building with CMake\n\n```bash\nmkdir build && cd build\ncmake ..\nmake\n```\n\nThe compiled modules will be in the `build/` directory.\n\n## Building with setup.py (Alternative)\n\n```bash\npip install .\n```\n\n## Examples\n\n### example.cpp\nBasic function bindings (add, fibonacci).\n\n```python\n>>> import example\n>>> example.add(1, 2)\n3\n>>> example.fib(10)\n55\n```\n\n### vector.cpp\nClass binding with operators and properties.\n\n```python\n>>> from vector import Vector2D\n>>> v = Vector2D(3, 4)\n>>> v.length()\n5.0\n>>> v2 = v + Vector2D(1, 1)\n>>> v2\nVector2D(4.0, 5.0)\n```\n\n### numpy_example.cpp\nNumPy array operations (zero-copy).\n\n```python\n>>> import numpy as np\n>>> from numpy_example import multiply_inplace\n>>> arr = np.array([1.0, 2.0, 3.0])\n>>> multiply_inplace(arr, 2.0)\n>>> arr\narray([2., 4., 6.])\n```\n\n### gil_example.cpp\nGIL release for parallel execution.\n\n```python\n>>> from gil_example import fib_nogil\n>>> import threading\n>>> # Runs in parallel because GIL is released\n>>> threads = [threading.Thread(target=fib_nogil, args=(30,)) for _ in range(4)]\n```\n\n### fib.c\nPure C library for ctypes/cffi examples.\n\n```bash\n# Compile\ngcc -shared -fPIC -o libfib.so fib.c      # Linux\nclang -shared -fPIC -o libfib.dylib fib.c # macOS\n```\n\n```python\n>>> import ctypes\n>>> lib = ctypes.CDLL(\"./libfib.so\")\n>>> lib.fib(10)\n55\n```\n"
  },
  {
    "path": "src/cext/capi/args.c",
    "content": "/* Demonstrate argument parsing in Python C API. */\n#include <Python.h>\n\n/* METH_NOARGS - no arguments */\nstatic PyObject* no_args(PyObject* self) { Py_RETURN_NONE; }\n\n/* METH_O - single object argument */\nstatic PyObject* single_arg(PyObject* self, PyObject* arg) { return Py_BuildValue(\"O\", arg); }\n\n/* METH_VARARGS - positional arguments */\nstatic PyObject* pos_args(PyObject* self, PyObject* args) {\n  PyObject *x = NULL, *y = NULL;\n  if (!PyArg_ParseTuple(args, \"OO\", &x, &y)) {\n    return NULL;\n  }\n  return Py_BuildValue(\"OO\", x, y);\n}\n\n/* METH_VARARGS | METH_KEYWORDS - keyword arguments */\nstatic PyObject* kw_args(PyObject* self, PyObject* args, PyObject* kwargs) {\n  static char* keywords[] = {\"x\", \"y\", \"z\", NULL};\n  PyObject *x = NULL, *y = NULL, *z = Py_None;\n\n  if (!PyArg_ParseTupleAndKeywords(args, kwargs, \"OO|O\", keywords, &x, &y, &z)) {\n    return NULL;\n  }\n  return Py_BuildValue(\"OOO\", x, y, z);\n}\n\n/* Parse specific types */\nstatic PyObject* typed_args(PyObject* self, PyObject* args) {\n  int i;\n  double d;\n  const char* s;\n\n  if (!PyArg_ParseTuple(args, \"ids\", &i, &d, &s)) {\n    return NULL;\n  }\n  return Py_BuildValue(\"{s:i,s:d,s:s}\", \"int\", i, \"double\", d, \"str\", s);\n}\n\nstatic PyMethodDef methods[] = {\n    {\"no_args\", (PyCFunction)no_args, METH_NOARGS, \"No arguments\"},\n    {\"single_arg\", (PyCFunction)single_arg, METH_O, \"Single argument\"},\n    {\"pos_args\", (PyCFunction)pos_args, METH_VARARGS, \"Positional arguments\"},\n    {\"kw_args\", (PyCFunction)kw_args, METH_VARARGS | METH_KEYWORDS, \"Keyword arguments\"},\n    {\"typed_args\", (PyCFunction)typed_args, METH_VARARGS, \"Typed arguments\"},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \"args\", \"Argument parsing examples\", -1, methods};\n\nPyMODINIT_FUNC PyInit_args(void) { return PyModule_Create(&module); }\n"
  },
  {
    "path": "src/cext/capi/errors.c",
    "content": "/* Demonstrate exception handling in Python C API. */\n#include <Python.h>\n\nstatic PyObject* FooError;\n\n/* Raise built-in exception */\nstatic PyObject* raise_value_error(PyObject* self) {\n  PyErr_SetString(PyExc_ValueError, \"This is a ValueError\");\n  return NULL;\n}\n\n/* Raise custom exception */\nstatic PyObject* raise_foo_error(PyObject* self) {\n  PyErr_SetString(FooError, \"This is a custom FooError\");\n  return NULL;\n}\n\n/* Raise with format string */\nstatic PyObject* raise_with_format(PyObject* self, PyObject* args) {\n  int code;\n  if (!PyArg_ParseTuple(args, \"i\", &code)) {\n    return NULL;\n  }\n  PyErr_Format(PyExc_RuntimeError, \"Error code: %d\", code);\n  return NULL;\n}\n\n/* Check and propagate exception */\nstatic PyObject* divide(PyObject* self, PyObject* args) {\n  double a, b;\n  if (!PyArg_ParseTuple(args, \"dd\", &a, &b)) {\n    return NULL;\n  }\n  if (b == 0.0) {\n    PyErr_SetString(PyExc_ZeroDivisionError, \"division by zero\");\n    return NULL;\n  }\n  return PyFloat_FromDouble(a / b);\n}\n\nstatic PyMethodDef methods[] = {\n    {\"raise_value_error\", (PyCFunction)raise_value_error, METH_NOARGS, NULL},\n    {\"raise_foo_error\", (PyCFunction)raise_foo_error, METH_NOARGS, NULL},\n    {\"raise_with_format\", (PyCFunction)raise_with_format, METH_VARARGS, NULL},\n    {\"divide\", (PyCFunction)divide, METH_VARARGS, \"Divide a by b\"},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \"errors\", \"Exception handling examples\", -1, methods};\n\nPyMODINIT_FUNC PyInit_errors(void) {\n  PyObject* m = PyModule_Create(&module);\n  if (!m) return NULL;\n\n  FooError = PyErr_NewException(\"errors.FooError\", NULL, NULL);\n  Py_INCREF(FooError);\n  PyModule_AddObject(m, \"FooError\", FooError);\n  return m;\n}\n"
  },
  {
    "path": "src/cext/capi/gil.c",
    "content": "/* Demonstrate GIL release and acquire in Python C API. */\n#include <Python.h>\n\n#ifdef _WIN32\n#include <windows.h>\n#define sleep(x) Sleep((x) * 1000)\n#else\n#include <unistd.h>\n#endif\n\n/* Sleep WITHOUT releasing GIL - blocks other threads */\nstatic PyObject* sleep_with_gil(PyObject* self, PyObject* args) {\n  int seconds;\n  if (!PyArg_ParseTuple(args, \"i\", &seconds)) {\n    return NULL;\n  }\n  sleep(seconds);\n  Py_RETURN_NONE;\n}\n\n/* Sleep WITH releasing GIL - allows other threads to run */\nstatic PyObject* sleep_no_gil(PyObject* self, PyObject* args) {\n  int seconds;\n  if (!PyArg_ParseTuple(args, \"i\", &seconds)) {\n    return NULL;\n  }\n\n  Py_BEGIN_ALLOW_THREADS sleep(seconds);\n  Py_END_ALLOW_THREADS\n\n      Py_RETURN_NONE;\n}\n\n/* CPU work without GIL */\nstatic unsigned long fib_impl(unsigned long n) {\n  if (n < 2) return n;\n  return fib_impl(n - 1) + fib_impl(n - 2);\n}\n\nstatic PyObject* fib_no_gil(PyObject* self, PyObject* args) {\n  unsigned long n, result;\n  if (!PyArg_ParseTuple(args, \"k\", &n)) {\n    return NULL;\n  }\n\n  Py_BEGIN_ALLOW_THREADS result = fib_impl(n);\n  Py_END_ALLOW_THREADS\n\n      return PyLong_FromUnsignedLong(result);\n}\n\nstatic PyMethodDef methods[] = {\n    {\"sleep_with_gil\", (PyCFunction)sleep_with_gil, METH_VARARGS, \"Sleep holding GIL (blocks threads)\"},\n    {\"sleep_no_gil\", (PyCFunction)sleep_no_gil, METH_VARARGS, \"Sleep releasing GIL (allows threads)\"},\n    {\"fib_no_gil\", (PyCFunction)fib_no_gil, METH_VARARGS, \"Fibonacci with GIL released\"},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \"gil\", \"GIL handling examples\", -1, methods};\n\nPyMODINIT_FUNC PyInit_gil(void) { return PyModule_Create(&module); }\n"
  },
  {
    "path": "src/cext/capi/setup.py",
    "content": "from setuptools import setup, Extension\n\nextensions = [\n    Extension(\"simple\", [\"simple.c\"]),\n    Extension(\"args\", [\"args.c\"]),\n    Extension(\"gil\", [\"gil.c\"]),\n    Extension(\"errors\", [\"errors.c\"]),\n    Extension(\"types_demo\", [\"types_demo.c\"]),\n]\n\nsetup(\n    name=\"capi_examples\",\n    version=\"1.0\",\n    ext_modules=extensions,\n)\n"
  },
  {
    "path": "src/cext/capi/simple.c",
    "content": "/* Simple C extension module demonstrating Python C API basics. */\n#include <Python.h>\n\nPyDoc_STRVAR(doc_mod, \"Simple example C extension module.\\n\");\nPyDoc_STRVAR(doc_hello, \"hello() -> str\\n\\nReturn a greeting string.\");\nPyDoc_STRVAR(doc_add, \"add(a, b) -> int\\n\\nAdd two integers.\");\nPyDoc_STRVAR(doc_fib, \"fib(n) -> int\\n\\nCompute Fibonacci number.\");\n\nstatic PyObject* hello(PyObject* self) { return PyUnicode_FromString(\"Hello from C!\"); }\n\nstatic PyObject* add(PyObject* self, PyObject* args) {\n  long a, b;\n  if (!PyArg_ParseTuple(args, \"ll\", &a, &b)) {\n    return NULL;\n  }\n  return PyLong_FromLong(a + b);\n}\n\nstatic unsigned long fib_impl(unsigned long n) {\n  if (n < 2) return n;\n  return fib_impl(n - 1) + fib_impl(n - 2);\n}\n\nstatic PyObject* fib(PyObject* self, PyObject* args) {\n  unsigned long n;\n  if (!PyArg_ParseTuple(args, \"k\", &n)) {\n    return NULL;\n  }\n  return PyLong_FromUnsignedLong(fib_impl(n));\n}\n\nstatic PyMethodDef methods[] = {\n    {\"hello\", (PyCFunction)hello, METH_NOARGS, doc_hello},\n    {\"add\", (PyCFunction)add, METH_VARARGS, doc_add},\n    {\"fib\", (PyCFunction)fib, METH_VARARGS, doc_fib},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \"simple\", doc_mod, -1, methods};\n\nPyMODINIT_FUNC PyInit_simple(void) { return PyModule_Create(&module); }\n"
  },
  {
    "path": "src/cext/capi/test_capi.py",
    "content": "\"\"\"Tests for Python C API extension examples.\"\"\"\n\nimport pytest\nimport sys\nimport os\n\n# Add build directory to path\nbuild_dir = os.path.join(os.path.dirname(__file__), \"build\")\nfor d in os.listdir(build_dir) if os.path.exists(build_dir) else []:\n    path = os.path.join(build_dir, d)\n    if os.path.isdir(path) and path not in sys.path:\n        sys.path.insert(0, path)\n\n\nclass TestSimple:\n    \"\"\"Test simple module.\"\"\"\n\n    def test_hello(self):\n        import simple\n\n        assert simple.hello() == \"Hello from C!\"\n\n    def test_add(self):\n        import simple\n\n        assert simple.add(1, 2) == 3\n        assert simple.add(-5, 10) == 5\n\n    def test_fib(self):\n        import simple\n\n        assert simple.fib(0) == 0\n        assert simple.fib(1) == 1\n        assert simple.fib(10) == 55\n        assert simple.fib(20) == 6765\n\n\nclass TestArgs:\n    \"\"\"Test argument parsing module.\"\"\"\n\n    def test_no_args(self):\n        import args\n\n        assert args.no_args() is None\n\n    def test_single_arg(self):\n        import args\n\n        assert args.single_arg(42) == 42\n        assert args.single_arg(\"hello\") == \"hello\"\n\n    def test_pos_args(self):\n        import args\n\n        assert args.pos_args(1, 2) == (1, 2)\n        assert args.pos_args(\"a\", \"b\") == (\"a\", \"b\")\n\n    def test_kw_args(self):\n        import args\n\n        assert args.kw_args(1, 2) == (1, 2, None)\n        assert args.kw_args(1, 2, 3) == (1, 2, 3)\n        assert args.kw_args(x=1, y=2, z=3) == (1, 2, 3)\n\n    def test_typed_args(self):\n        import args\n\n        result = args.typed_args(42, 3.14, \"hello\")\n        assert result == {\"int\": 42, \"double\": 3.14, \"str\": \"hello\"}\n\n\nclass TestGil:\n    \"\"\"Test GIL handling module.\"\"\"\n\n    def test_fib_no_gil(self):\n        import gil\n\n        assert gil.fib_no_gil(10) == 55\n        assert gil.fib_no_gil(20) == 6765\n\n\nclass TestErrors:\n    \"\"\"Test exception handling module.\"\"\"\n\n    def test_raise_value_error(self):\n        import errors\n\n        with pytest.raises(ValueError, match=\"This is a ValueError\"):\n            errors.raise_value_error()\n\n    def test_raise_foo_error(self):\n        import errors\n\n        with pytest.raises(errors.FooError, match=\"This is a custom FooError\"):\n            errors.raise_foo_error()\n\n    def test_raise_with_format(self):\n        import errors\n\n        with pytest.raises(RuntimeError, match=\"Error code: 42\"):\n            errors.raise_with_format(42)\n\n    def test_divide(self):\n        import errors\n\n        assert errors.divide(10.0, 2.0) == 5.0\n        with pytest.raises(ZeroDivisionError):\n            errors.divide(1.0, 0.0)\n\n\nclass TestTypesDemo:\n    \"\"\"Test Python types manipulation.\"\"\"\n\n    def test_list_demo(self):\n        import types_demo\n\n        assert types_demo.list_demo() == [1, 2, 3]\n\n    def test_list_sum(self):\n        import types_demo\n\n        assert types_demo.list_sum([1, 2, 3, 4]) == 10\n\n    def test_iter_list(self):\n        import types_demo\n\n        assert types_demo.iter_list([1, 2, 3]) == [2, 4, 6]\n\n    def test_dict_demo(self):\n        import types_demo\n\n        assert types_demo.dict_demo() == {\"name\": \"Python\", \"version\": 3}\n\n    def test_dict_get(self):\n        import types_demo\n\n        d = {\"a\": 1, \"b\": 2}\n        assert types_demo.dict_get(d, \"a\") == 1\n        assert types_demo.dict_get(d, \"c\") is None\n\n    def test_iter_dict(self):\n        import types_demo\n\n        d = {\"x\": 1, \"y\": 2}\n        result = types_demo.iter_dict(d)\n        assert set(result) == {(\"x\", 1), (\"y\", 2)}\n\n    def test_tuple_demo(self):\n        import types_demo\n\n        assert types_demo.tuple_demo() == (1, \"hello\", 3.14)\n\n    def test_tuple_unpack(self):\n        import types_demo\n\n        result = types_demo.tuple_unpack((42, \"test\", 2.5))\n        assert result == {\"int\": 42, \"str\": \"test\", \"float\": 2.5}\n\n    def test_set_demo(self):\n        import types_demo\n\n        assert types_demo.set_demo() == {1, 2, 3}\n\n    def test_set_contains(self):\n        import types_demo\n\n        s = {1, 2, 3}\n        assert types_demo.set_contains(s, 2) is True\n        assert types_demo.set_contains(s, 5) is False\n\n    def test_str_demo(self):\n        import types_demo\n\n        assert types_demo.str_demo() == \"Hello World\"\n\n    def test_str_format(self):\n        import types_demo\n\n        assert types_demo.str_format(\"Alice\", 30) == \"Alice is 30 years old\"\n\n    def test_bytes_demo(self):\n        import types_demo\n\n        assert types_demo.bytes_demo() == b\"hello bytes\"\n\n    def test_bytes_len(self):\n        import types_demo\n\n        assert types_demo.bytes_len(b\"hello\") == 5\n"
  },
  {
    "path": "src/cext/capi/types_demo.c",
    "content": "/* Demonstrate Python types manipulation in C API. */\n#include <Python.h>\n\n/* List operations */\nstatic PyObject* list_demo(PyObject* self) {\n  PyObject* list = PyList_New(0);\n  PyList_Append(list, PyLong_FromLong(1));\n  PyList_Append(list, PyLong_FromLong(2));\n  PyList_Append(list, PyLong_FromLong(3));\n  return list;\n}\n\nstatic PyObject* list_sum(PyObject* self, PyObject* args) {\n  PyObject* list;\n  if (!PyArg_ParseTuple(args, \"O!\", &PyList_Type, &list)) {\n    return NULL;\n  }\n  long sum = 0;\n  Py_ssize_t len = PyList_Size(list);\n  for (Py_ssize_t i = 0; i < len; i++) {\n    PyObject* item = PyList_GetItem(list, i); /* borrowed ref */\n    sum += PyLong_AsLong(item);\n  }\n  return PyLong_FromLong(sum);\n}\n\n/* Iterate list with iterator protocol */\nstatic PyObject* iter_list(PyObject* self, PyObject* args) {\n  PyObject *list, *iter, *item;\n  if (!PyArg_ParseTuple(args, \"O\", &list)) {\n    return NULL;\n  }\n  iter = PyObject_GetIter(list);\n  if (!iter) return NULL;\n\n  PyObject* result = PyList_New(0);\n  while ((item = PyIter_Next(iter)) != NULL) {\n    PyObject* doubled = PyLong_FromLong(PyLong_AsLong(item) * 2);\n    PyList_Append(result, doubled);\n    Py_DECREF(doubled);\n    Py_DECREF(item);\n  }\n  Py_DECREF(iter);\n  return result;\n}\n\n/* Dict operations */\nstatic PyObject* dict_demo(PyObject* self) {\n  PyObject* dict = PyDict_New();\n  PyDict_SetItemString(dict, \"name\", PyUnicode_FromString(\"Python\"));\n  PyDict_SetItemString(dict, \"version\", PyLong_FromLong(3));\n  return dict;\n}\n\nstatic PyObject* dict_get(PyObject* self, PyObject* args) {\n  PyObject* dict;\n  const char* key;\n  if (!PyArg_ParseTuple(args, \"O!s\", &PyDict_Type, &dict, &key)) {\n    return NULL;\n  }\n  PyObject* value = PyDict_GetItemString(dict, key); /* borrowed ref */\n  if (!value) {\n    Py_RETURN_NONE;\n  }\n  Py_INCREF(value);\n  return value;\n}\n\n/* Iterate dict */\nstatic PyObject* iter_dict(PyObject* self, PyObject* args) {\n  PyObject* dict;\n  if (!PyArg_ParseTuple(args, \"O!\", &PyDict_Type, &dict)) {\n    return NULL;\n  }\n  PyObject* result = PyList_New(0);\n  PyObject *key, *value;\n  Py_ssize_t pos = 0;\n  while (PyDict_Next(dict, &pos, &key, &value)) {\n    PyObject* pair = PyTuple_Pack(2, key, value);\n    PyList_Append(result, pair);\n    Py_DECREF(pair);\n  }\n  return result;\n}\n\n/* Tuple operations */\nstatic PyObject* tuple_demo(PyObject* self) { return Py_BuildValue(\"(isd)\", 1, \"hello\", 3.14); }\n\nstatic PyObject* tuple_unpack(PyObject* self, PyObject* args) {\n  int a;\n  const char* b;\n  double c;\n  if (!PyArg_ParseTuple(args, \"(isd)\", &a, &b, &c)) {\n    return NULL;\n  }\n  return Py_BuildValue(\"{s:i,s:s,s:d}\", \"int\", a, \"str\", b, \"float\", c);\n}\n\n/* Set operations */\nstatic PyObject* set_demo(PyObject* self) {\n  PyObject* set = PySet_New(NULL);\n  PySet_Add(set, PyLong_FromLong(1));\n  PySet_Add(set, PyLong_FromLong(2));\n  PySet_Add(set, PyLong_FromLong(2)); /* duplicate ignored */\n  PySet_Add(set, PyLong_FromLong(3));\n  return set;\n}\n\nstatic PyObject* set_contains(PyObject* self, PyObject* args) {\n  PyObject *set, *item;\n  if (!PyArg_ParseTuple(args, \"OO\", &set, &item)) {\n    return NULL;\n  }\n  int result = PySet_Contains(set, item);\n  if (result == -1) return NULL;\n  return PyBool_FromLong(result);\n}\n\n/* String operations */\nstatic PyObject* str_demo(PyObject* self) {\n  PyObject* s1 = PyUnicode_FromString(\"Hello\");\n  PyObject* s2 = PyUnicode_FromString(\" World\");\n  PyObject* result = PyUnicode_Concat(s1, s2);\n  Py_DECREF(s1);\n  Py_DECREF(s2);\n  return result;\n}\n\nstatic PyObject* str_format(PyObject* self, PyObject* args) {\n  const char* name;\n  int age;\n  if (!PyArg_ParseTuple(args, \"si\", &name, &age)) {\n    return NULL;\n  }\n  return PyUnicode_FromFormat(\"%s is %d years old\", name, age);\n}\n\n/* Bytes operations */\nstatic PyObject* bytes_demo(PyObject* self) { return PyBytes_FromString(\"hello bytes\"); }\n\nstatic PyObject* bytes_len(PyObject* self, PyObject* args) {\n  PyObject* bytes;\n  if (!PyArg_ParseTuple(args, \"S\", &bytes)) {\n    return NULL;\n  }\n  return PyLong_FromSsize_t(PyBytes_Size(bytes));\n}\n\nstatic PyMethodDef methods[] = {\n    {\"list_demo\", (PyCFunction)list_demo, METH_NOARGS, \"Create a list [1,2,3]\"},\n    {\"list_sum\", (PyCFunction)list_sum, METH_VARARGS, \"Sum list elements\"},\n    {\"iter_list\", (PyCFunction)iter_list, METH_VARARGS, \"Double each element\"},\n    {\"dict_demo\", (PyCFunction)dict_demo, METH_NOARGS, \"Create a dict\"},\n    {\"dict_get\", (PyCFunction)dict_get, METH_VARARGS, \"Get dict value by key\"},\n    {\"iter_dict\", (PyCFunction)iter_dict, METH_VARARGS, \"Get dict items as list\"},\n    {\"tuple_demo\", (PyCFunction)tuple_demo, METH_NOARGS, \"Create a tuple\"},\n    {\"tuple_unpack\", (PyCFunction)tuple_unpack, METH_VARARGS, \"Unpack tuple\"},\n    {\"set_demo\", (PyCFunction)set_demo, METH_NOARGS, \"Create a set\"},\n    {\"set_contains\", (PyCFunction)set_contains, METH_VARARGS, \"Check set membership\"},\n    {\"str_demo\", (PyCFunction)str_demo, METH_NOARGS, \"Concat strings\"},\n    {\"str_format\", (PyCFunction)str_format, METH_VARARGS, \"Format string\"},\n    {\"bytes_demo\", (PyCFunction)bytes_demo, METH_NOARGS, \"Create bytes\"},\n    {\"bytes_len\", (PyCFunction)bytes_len, METH_VARARGS, \"Get bytes length\"},\n    {NULL, NULL, 0, NULL}\n};\n\nstatic struct PyModuleDef module = {PyModuleDef_HEAD_INIT, \"types_demo\", \"Python types in C API\", -1, methods};\n\nPyMODINIT_FUNC PyInit_types_demo(void) { return PyModule_Create(&module); }\n"
  },
  {
    "path": "src/cext/conftest.py",
    "content": "\"\"\"\npytest configuration for C extension tests.\nAdds build directory to sys.path before tests run.\n\"\"\"\n\nimport sys\nfrom pathlib import Path\n\n\ndef pytest_configure(config):\n    \"\"\"Add build directory to path before collecting tests.\"\"\"\n    test_dir = Path(__file__).parent\n    build_dir = test_dir / \"build\"\n    if build_dir.exists():\n        sys.path.insert(0, str(build_dir))\n"
  },
  {
    "path": "src/cext/example.cpp",
    "content": "/**\n * example.cpp - Basic pybind11 example\n *\n * Build:\n *   mkdir build && cd build\n *   cmake .. && make\n *\n * Usage:\n *   >>> import example\n *   >>> example.add(1, 2)\n *   3\n *   >>> example.fib(10)\n *   55\n */\n#include <pybind11/pybind11.h>\n\nnamespace py = pybind11;\n\nint add(int a, int b) { return a + b; }\n\nunsigned long fib(unsigned long n) {\n  if (n < 2) return n;\n  return fib(n - 1) + fib(n - 2);\n}\n\n// Iterative version for large n\nunsigned long fib_iter(unsigned long n) {\n  if (n < 2) return n;\n  unsigned long a = 0, b = 1;\n  for (unsigned long i = 1; i < n; ++i) {\n    unsigned long tmp = a + b;\n    a = b;\n    b = tmp;\n  }\n  return b;\n}\n\nPYBIND11_MODULE(example, m) {\n  m.doc() = \"Example pybind11 module with basic functions\";\n\n  m.def(\"add\", &add, \"Add two integers\", py::arg(\"a\"), py::arg(\"b\"));\n\n  m.def(\"fib\", &fib, \"Compute Fibonacci number (recursive)\", py::arg(\"n\"));\n\n  m.def(\"fib_iter\", &fib_iter, \"Compute Fibonacci number (iterative)\", py::arg(\"n\"));\n}\n"
  },
  {
    "path": "src/cext/fib.c",
    "content": "/**\n * fib.c - Pure C library for ctypes/cffi examples\n *\n * Compile:\n *   gcc -shared -fPIC -o libfib.so fib.c      # Linux\n *   clang -shared -fPIC -o libfib.dylib fib.c # macOS\n *\n * Usage with ctypes:\n *   >>> import ctypes\n *   >>> lib = ctypes.CDLL(\"./libfib.so\")\n *   >>> lib.fib.argtypes = [ctypes.c_ulong]\n *   >>> lib.fib.restype = ctypes.c_ulong\n *   >>> lib.fib(10)\n *   55\n *\n * Usage with cffi:\n *   >>> from cffi import FFI\n *   >>> ffi = FFI()\n *   >>> ffi.cdef(\"unsigned long fib(unsigned long n);\")\n *   >>> lib = ffi.dlopen(\"./libfib.so\")\n *   >>> lib.fib(10)\n *   55\n */\n\nunsigned long fib(unsigned long n) {\n  if (n < 2) return n;\n  return fib(n - 1) + fib(n - 2);\n}\n\nunsigned long fib_iter(unsigned long n) {\n  if (n < 2) return n;\n  unsigned long a = 0, b = 1;\n  for (unsigned long i = 1; i < n; ++i) {\n    unsigned long tmp = a + b;\n    a = b;\n    b = tmp;\n  }\n  return b;\n}\n\nint add(int a, int b) { return a + b; }\n\ndouble multiply(double a, double b) { return a * b; }\n\n/* Structure example */\ntypedef struct {\n  double x;\n  double y;\n} Point;\n\ndouble point_distance(Point* p1, Point* p2) {\n  double dx = p2->x - p1->x;\n  double dy = p2->y - p1->y;\n  return dx * dx + dy * dy; /* Returns squared distance */\n}\n\nvoid point_scale(Point* p, double factor) {\n  p->x *= factor;\n  p->y *= factor;\n}\n"
  },
  {
    "path": "src/cext/gil_example.cpp",
    "content": "/**\n * gil_example.cpp - GIL release example\n *\n * Demonstrates:\n *   - Releasing GIL for CPU-intensive work\n *   - Allowing Python threads to run in parallel\n *   - Re-acquiring GIL when needed\n *\n * Usage:\n *   >>> from gil_example import slow_operation, fib_nogil\n *   >>> import threading\n *   >>> # These run in parallel because GIL is released\n *   >>> threads = [threading.Thread(target=slow_operation, args=(1,)) for _ in range(3)]\n */\n#include <pybind11/pybind11.h>\n\n#include <chrono>\n#include <functional>\n#include <thread>\n\nnamespace py = pybind11;\n\n// Slow operation that releases GIL\nvoid slow_operation(int seconds) {\n  // Release GIL while sleeping\n  py::gil_scoped_release release;\n  std::this_thread::sleep_for(std::chrono::seconds(seconds));\n}\n\n// CPU-intensive Fibonacci without GIL\nunsigned long fib_nogil(unsigned long n) {\n  // Release GIL for CPU work\n  py::gil_scoped_release release;\n\n  std::function<unsigned long(unsigned long)> fib_impl;\n  fib_impl = [&](unsigned long n) -> unsigned long {\n    if (n < 2) return n;\n    return fib_impl(n - 1) + fib_impl(n - 2);\n  };\n  return fib_impl(n);\n}\n\n// Example showing GIL re-acquisition\nvoid call_python_callback(py::function callback, const std::string& msg) {\n  // Release GIL for some work\n  {\n    py::gil_scoped_release release;\n    std::this_thread::sleep_for(std::chrono::milliseconds(100));\n  }\n  // GIL automatically re-acquired here\n  // Now safe to call Python\n  callback(msg);\n}\n\nPYBIND11_MODULE(gil_example, m) {\n  m.doc() = \"GIL release examples for parallel execution\";\n\n  m.def(\"slow_operation\", &slow_operation, \"Sleep for N seconds (releases GIL)\", py::arg(\"seconds\"));\n\n  m.def(\"fib_nogil\", &fib_nogil, \"Compute Fibonacci without holding GIL\", py::arg(\"n\"));\n\n  m.def(\"call_python_callback\", &call_python_callback, \"Call Python function after releasing GIL\", py::arg(\"callback\"), py::arg(\"msg\"));\n}\n"
  },
  {
    "path": "src/cext/numpy_example.cpp",
    "content": "/**\n * numpy_example.cpp - pybind11 NumPy integration\n *\n * Demonstrates:\n *   - Accepting NumPy arrays\n *   - Modifying arrays in-place (zero-copy)\n *   - Returning new arrays\n *   - 2D array operations\n *\n * Usage:\n *   >>> import numpy as np\n *   >>> from numpy_example import multiply_inplace, add_arrays, matrix_sum\n *   >>> arr = np.array([1.0, 2.0, 3.0])\n *   >>> multiply_inplace(arr, 2.0)\n *   >>> arr\n *   array([2., 4., 6.])\n */\n#include <pybind11/numpy.h>\n#include <pybind11/pybind11.h>\n\n#include <stdexcept>\n\nnamespace py = pybind11;\n\n// Modify array in-place (no copy)\nvoid multiply_inplace(py::array_t<double> arr, double factor) {\n  auto buf = arr.mutable_unchecked<1>();\n  for (py::ssize_t i = 0; i < buf.shape(0); i++) {\n    buf(i) *= factor;\n  }\n}\n\n// Return new array\npy::array_t<double> add_arrays(py::array_t<double> a, py::array_t<double> b) {\n  auto buf_a = a.unchecked<1>();\n  auto buf_b = b.unchecked<1>();\n\n  if (buf_a.shape(0) != buf_b.shape(0)) {\n    throw std::runtime_error(\"Arrays must have same length\");\n  }\n\n  auto result = py::array_t<double>(buf_a.shape(0));\n  auto buf_r = result.mutable_unchecked<1>();\n\n  for (py::ssize_t i = 0; i < buf_a.shape(0); i++) {\n    buf_r(i) = buf_a(i) + buf_b(i);\n  }\n  return result;\n}\n\n// Sum all elements in 2D array\ndouble matrix_sum(py::array_t<double> mat) {\n  auto buf = mat.unchecked<2>();\n  double sum = 0;\n  for (py::ssize_t i = 0; i < buf.shape(0); i++) {\n    for (py::ssize_t j = 0; j < buf.shape(1); j++) {\n      sum += buf(i, j);\n    }\n  }\n  return sum;\n}\n\n// Element-wise square\npy::array_t<double> square(py::array_t<double> arr) {\n  auto buf = arr.unchecked<1>();\n  auto result = py::array_t<double>(buf.shape(0));\n  auto buf_r = result.mutable_unchecked<1>();\n\n  for (py::ssize_t i = 0; i < buf.shape(0); i++) {\n    buf_r(i) = buf(i) * buf(i);\n  }\n  return result;\n}\n\nPYBIND11_MODULE(numpy_example, m) {\n  m.doc() = \"NumPy integration examples\";\n\n  m.def(\"multiply_inplace\", &multiply_inplace, \"Multiply array elements by factor in-place\", py::arg(\"arr\"), py::arg(\"factor\"));\n\n  m.def(\"add_arrays\", &add_arrays, \"Add two arrays element-wise\", py::arg(\"a\"), py::arg(\"b\"));\n\n  m.def(\"matrix_sum\", &matrix_sum, \"Sum all elements in 2D array\", py::arg(\"mat\"));\n\n  m.def(\"square\", &square, \"Square each element\", py::arg(\"arr\"));\n}\n"
  },
  {
    "path": "src/cext/setup.py",
    "content": "\"\"\"\nsetup.py for pybind11 examples\n\nBuild:\n    pip install .\n    # or\n    python setup.py build_ext --inplace\n\"\"\"\n\nfrom setuptools import setup, find_packages\n\ntry:\n    from pybind11.setup_helpers import Pybind11Extension, build_ext\n\n    ext_modules = [\n        Pybind11Extension(\n            \"example\",\n            [\"example.cpp\"],\n        ),\n        Pybind11Extension(\n            \"vector\",\n            [\"vector.cpp\"],\n        ),\n        Pybind11Extension(\n            \"numpy_example\",\n            [\"numpy_example.cpp\"],\n        ),\n        Pybind11Extension(\n            \"gil_example\",\n            [\"gil_example.cpp\"],\n        ),\n    ]\n\n    setup(\n        name=\"pysheeet_cext\",\n        version=\"1.0.0\",\n        description=\"pybind11 extension examples for pysheeet\",\n        ext_modules=ext_modules,\n        cmdclass={\"build_ext\": build_ext},\n        python_requires=\">=3.8\",\n    )\nexcept ImportError:\n    # pybind11 not installed, create minimal setup\n    setup(\n        name=\"pysheeet_cext\",\n        version=\"1.0.0\",\n        description=\"pybind11 extension examples for pysheeet\",\n    )\n"
  },
  {
    "path": "src/cext/test_cext.py",
    "content": "\"\"\"\nTests for pybind11 C++ extension modules.\n\nRun from src/cext directory:\n    python -m pytest test_cext.py -v\n\nBuild first:\n    mkdir build && cd build && cmake .. && make\n\"\"\"\n\nimport sys\nimport threading\nfrom datetime import datetime\n\nimport pytest\n\n# Try to import compiled modules (path set by conftest.py)\ntry:\n    import example\n\n    HAS_EXAMPLE = True\nexcept ImportError:\n    HAS_EXAMPLE = False\n\ntry:\n    import vector\n\n    HAS_VECTOR = True\nexcept ImportError:\n    HAS_VECTOR = False\n\ntry:\n    import numpy as np\n    import numpy_example\n\n    HAS_NUMPY = True\nexcept ImportError:\n    HAS_NUMPY = False\n\ntry:\n    import gil_example\n\n    HAS_GIL = True\nexcept ImportError:\n    HAS_GIL = False\n\n\n@pytest.mark.skipif(not HAS_EXAMPLE, reason=\"example module not built\")\nclass TestExample:\n    \"\"\"Test basic pybind11 functions.\"\"\"\n\n    def test_add(self):\n        assert example.add(1, 2) == 3\n        assert example.add(-5, 10) == 5\n        assert example.add(0, 0) == 0\n\n    def test_fib(self):\n        assert example.fib(0) == 0\n        assert example.fib(1) == 1\n        assert example.fib(10) == 55\n        assert example.fib(20) == 6765\n\n    def test_fib_iter(self):\n        assert example.fib_iter(0) == 0\n        assert example.fib_iter(1) == 1\n        assert example.fib_iter(10) == 55\n        assert example.fib_iter(50) == 12586269025\n\n\n@pytest.mark.skipif(not HAS_VECTOR, reason=\"vector module not built\")\nclass TestVector:\n    \"\"\"Test Vector2D class binding.\"\"\"\n\n    def test_constructor(self):\n        v = vector.Vector2D()\n        assert v.x == 0\n        assert v.y == 0\n\n        v = vector.Vector2D(3, 4)\n        assert v.x == 3\n        assert v.y == 4\n\n    def test_length(self):\n        v = vector.Vector2D(3, 4)\n        assert abs(v.length() - 5.0) < 1e-10\n\n        v = vector.Vector2D(0, 0)\n        assert v.length() == 0\n\n    def test_dot(self):\n        v1 = vector.Vector2D(1, 2)\n        v2 = vector.Vector2D(3, 4)\n        assert v1.dot(v2) == 11  # 1*3 + 2*4\n\n    def test_normalized(self):\n        v = vector.Vector2D(3, 4)\n        n = v.normalized()\n        assert abs(n.length() - 1.0) < 1e-10\n\n    def test_add(self):\n        v1 = vector.Vector2D(1, 2)\n        v2 = vector.Vector2D(3, 4)\n        v3 = v1 + v2\n        assert v3.x == 4\n        assert v3.y == 6\n\n    def test_sub(self):\n        v1 = vector.Vector2D(5, 7)\n        v2 = vector.Vector2D(2, 3)\n        v3 = v1 - v2\n        assert v3.x == 3\n        assert v3.y == 4\n\n    def test_mul(self):\n        v = vector.Vector2D(2, 3)\n        v2 = v * 2.0\n        assert v2.x == 4\n        assert v2.y == 6\n\n    def test_eq(self):\n        v1 = vector.Vector2D(1, 2)\n        v2 = vector.Vector2D(1, 2)\n        v3 = vector.Vector2D(1, 3)\n        assert v1 == v2\n        assert not (v1 == v3)\n\n    def test_repr(self):\n        v = vector.Vector2D(3, 4)\n        assert \"Vector2D\" in repr(v)\n        assert \"3\" in repr(v)\n        assert \"4\" in repr(v)\n\n\n@pytest.mark.skipif(not HAS_NUMPY, reason=\"numpy_example module not built\")\nclass TestNumPy:\n    \"\"\"Test NumPy integration.\"\"\"\n\n    def test_multiply_inplace(self):\n        arr = np.array([1.0, 2.0, 3.0])\n        numpy_example.multiply_inplace(arr, 2.0)\n        np.testing.assert_array_equal(arr, [2.0, 4.0, 6.0])\n\n    def test_add_arrays(self):\n        a = np.array([1.0, 2.0, 3.0])\n        b = np.array([4.0, 5.0, 6.0])\n        result = numpy_example.add_arrays(a, b)\n        np.testing.assert_array_equal(result, [5.0, 7.0, 9.0])\n\n    def test_add_arrays_length_mismatch(self):\n        a = np.array([1.0, 2.0])\n        b = np.array([1.0, 2.0, 3.0])\n        with pytest.raises(RuntimeError):\n            numpy_example.add_arrays(a, b)\n\n    def test_matrix_sum(self):\n        mat = np.array([[1.0, 2.0], [3.0, 4.0]])\n        assert numpy_example.matrix_sum(mat) == 10.0\n\n    def test_square(self):\n        arr = np.array([1.0, 2.0, 3.0])\n        result = numpy_example.square(arr)\n        np.testing.assert_array_equal(result, [1.0, 4.0, 9.0])\n\n\n@pytest.mark.skipif(not HAS_GIL, reason=\"gil_example module not built\")\nclass TestGIL:\n    \"\"\"Test GIL release functionality.\"\"\"\n\n    def test_fib_nogil(self):\n        result = gil_example.fib_nogil(20)\n        assert result == 6765\n\n    def test_slow_operation_parallel(self):\n        \"\"\"Test that slow_operation releases GIL allowing parallel execution.\"\"\"\n        results = []\n        start = datetime.now()\n\n        def worker(n):\n            results.append(n)\n            gil_example.slow_operation(1)\n\n        threads = [\n            threading.Thread(target=worker, args=(i,)) for i in range(3)\n        ]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n        elapsed = (datetime.now() - start).total_seconds()\n        # If GIL was released, all 3 should complete in ~1 second\n        # If GIL was held, it would take ~3 seconds\n        assert elapsed < 2.0, f\"Took {elapsed}s, GIL may not be released\"\n        assert len(results) == 3\n\n    def test_callback(self):\n        \"\"\"Test calling Python callback from C++.\"\"\"\n        results = []\n\n        def callback(msg):\n            results.append(msg)\n\n        gil_example.call_python_callback(callback, \"hello\")\n        assert results == [\"hello\"]\n"
  },
  {
    "path": "src/cext/vector.cpp",
    "content": "/**\n * vector.cpp - pybind11 class binding example\n *\n * Demonstrates:\n *   - Class binding with constructor\n *   - Read/write properties\n *   - Methods\n *   - Operator overloading\n *   - __repr__ for nice printing\n *\n * Usage:\n *   >>> from vector import Vector2D\n *   >>> v1 = Vector2D(3, 4)\n *   >>> v1.length()\n *   5.0\n *   >>> v2 = Vector2D(1, 2)\n *   >>> v3 = v1 + v2\n *   >>> v3\n *   Vector2D(4.0, 6.0)\n */\n#include <pybind11/pybind11.h>\n\n#include <cmath>\n#include <sstream>\n\nnamespace py = pybind11;\n\nclass Vector2D {\n public:\n  double x, y;\n\n  Vector2D(double x = 0, double y = 0) : x(x), y(y) {}\n\n  double length() const { return std::sqrt(x * x + y * y); }\n\n  double dot(const Vector2D& other) const { return x * other.x + y * other.y; }\n\n  Vector2D normalized() const {\n    double len = length();\n    if (len == 0) return Vector2D(0, 0);\n    return Vector2D(x / len, y / len);\n  }\n\n  Vector2D operator+(const Vector2D& other) const { return Vector2D(x + other.x, y + other.y); }\n\n  Vector2D operator-(const Vector2D& other) const { return Vector2D(x - other.x, y - other.y); }\n\n  Vector2D operator*(double scalar) const { return Vector2D(x * scalar, y * scalar); }\n\n  bool operator==(const Vector2D& other) const { return x == other.x && y == other.y; }\n\n  std::string repr() const {\n    std::ostringstream oss;\n    oss << \"Vector2D(\" << x << \", \" << y << \")\";\n    return oss.str();\n  }\n};\n\nPYBIND11_MODULE(vector, m) {\n  m.doc() = \"2D Vector class example\";\n\n  py::class_<Vector2D>(m, \"Vector2D\")\n      .def(py::init<double, double>(), py::arg(\"x\") = 0, py::arg(\"y\") = 0)\n      .def_readwrite(\"x\", &Vector2D::x)\n      .def_readwrite(\"y\", &Vector2D::y)\n      .def(\"length\", &Vector2D::length, \"Return vector length\")\n      .def(\"dot\", &Vector2D::dot, \"Dot product with another vector\")\n      .def(\"normalized\", &Vector2D::normalized, \"Return unit vector\")\n      .def(\"__add__\", &Vector2D::operator+)\n      .def(\"__sub__\", &Vector2D::operator-)\n      .def(\"__mul__\", &Vector2D::operator*)\n      .def(\"__eq__\", &Vector2D::operator==)\n      .def(\"__repr__\", &Vector2D::repr);\n}\n"
  },
  {
    "path": "src/cpp_from_python/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\nproject(cpp_from_python)\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n# Download and configure Google Test\ninclude(FetchContent)\nFetchContent_Declare(\n    googletest\n    GIT_REPOSITORY https://github.com/google/googletest.git\n    GIT_TAG release-1.12.1\n)\nset(gtest_force_shared_crt ON CACHE BOOL \"\" FORCE)\nFetchContent_MakeAvailable(googletest)\n\nenable_testing()\n\n# Single executable with tests\nadd_executable(cpp_from_py cpp_from_py.cpp)\ntarget_link_libraries(cpp_from_py gtest gtest_main)\n\n# Add test\nadd_test(NAME CppFromPythonTests COMMAND cpp_from_py)\n"
  },
  {
    "path": "src/cpp_from_python/cpp_from_py.cpp",
    "content": "/*\n * Learn C++ from Python - Modern C++ Examples with Tests\n *\n * Demonstrates modern C++ syntax with Python equivalents in Doxygen comments.\n * Build: mkdir build && cd build && cmake .. && make\n * Test: make test\n */\n\n#include <iostream>\n#include <vector>\n#include <map>\n#include <string>\n#include <algorithm>\n#include <numeric>\n#include <optional>\n#include <memory>\n#include <tuple>\n#include <functional>\n#include <gtest/gtest.h>\n\n/**\n * @brief Print hello world message\n *\n * Python equivalent:\n * @code{.py}\n * print(\"Hello, World!\")\n * @endcode\n */\nvoid hello_world() {\n    std::cout << \"Hello, World!\" << std::endl;\n}\n\n/**\n * @brief Demonstrate automatic type inference with auto keyword\n *\n * Python equivalent:\n * @code{.py}\n * x = 10\n * y = 3.14\n * name = \"Alice\"\n * is_valid = True\n * @endcode\n */\nvoid variables() {\n    auto x = 10;\n    auto y = 3.14;\n    auto name = \"Alice\";\n    auto is_valid = true;\n}\n\n/**\n * @brief Create and manipulate vectors (dynamic arrays)\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4, 5]\n * numbers.append(6)\n * print(numbers[0])\n * print(len(numbers))\n * @endcode\n */\nstd::vector<int> lists_and_vectors() {\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n    numbers.push_back(6);\n    return numbers;\n}\n\n/**\n * @brief Demonstrate array slicing and access patterns\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n * print(numbers[0])\n * print(numbers[-1])\n * print(numbers[2:5])\n * print(numbers[:3])\n * print(numbers[7:])\n * print(numbers[::2])\n * print(numbers[::-1])\n * @endcode\n */\nstd::vector<int> array_slicing() {\n    std::vector<int> numbers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n    // Slicing [2:5]\n    std::vector<int> slice(numbers.begin() + 2, numbers.begin() + 5);\n\n    // Every second element [::2]\n    std::vector<int> every_second;\n    for (size_t i = 0; i < numbers.size(); i += 2) {\n        every_second.push_back(numbers[i]);\n    }\n\n    // Reversed [::-1]\n    std::vector<int> reversed(numbers.rbegin(), numbers.rend());\n\n    return reversed;\n}\n\n/**\n * @brief Use maps for key-value storage\n *\n * Python equivalent:\n * @code{.py}\n * ages = {\"Alice\": 30, \"Bob\": 25}\n * ages[\"Charlie\"] = 35\n * print(ages[\"Alice\"])\n * @endcode\n */\nstd::map<std::string, int> dictionaries_and_maps() {\n    std::map<std::string, int> ages = {{\"Alice\", 30}, {\"Bob\", 25}};\n    ages[\"Charlie\"] = 35;\n    return ages;\n}\n\n/**\n * @brief Range-based for loops\n *\n * Python equivalent:\n * @code{.py}\n * for i in range(5):\n *     print(i)\n *\n * for item in [1, 2, 3]:\n *     print(item)\n * @endcode\n */\nvoid for_loop() {\n    for (int i = 0; i < 5; i++) {\n        // Traditional loop\n    }\n\n    for (auto item : {1, 2, 3}) {\n        // Range-based loop\n    }\n}\n\n/**\n * @brief Add two numbers\n *\n * Python equivalent:\n * @code{.py}\n * def add(a, b):\n *     return a + b\n *\n * result = add(3, 5)\n * @endcode\n */\nauto add(int a, int b) -> int {\n    return a + b;\n}\n\n/**\n * @brief Lambda function that squares a number\n *\n * Python equivalent:\n * @code{.py}\n * square = lambda x: x * x\n * print(square(5))\n *\n * numbers = [1, 2, 3, 4]\n * squared = list(map(lambda x: x * x, numbers))\n *\n * multiplier = 10\n * multiply = lambda x: x * multiplier\n * print(multiply(5))\n * @endcode\n */\nstd::function<int(int)> create_square_lambda() {\n    return [](int x) { return x * x; };\n}\n\n/**\n * @brief Lambda with variable capture\n *\n * Python equivalent:\n * @code{.py}\n * multiplier = 10\n * multiply = lambda x: x * multiplier\n * @endcode\n */\nstd::function<int(int)> create_multiply_lambda(int multiplier) {\n    return [multiplier](int x) { return x * multiplier; };\n}\n\n/**\n * @brief Transform vector using lambda\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4]\n * squared = list(map(lambda x: x * x, numbers))\n * @endcode\n */\nstd::vector<int> transform_with_lambda(const std::vector<int>& numbers) {\n    std::vector<int> squared;\n    std::transform(numbers.begin(), numbers.end(),\n                   std::back_inserter(squared),\n                   [](int x) { return x * x; });\n    return squared;\n}\n\n/**\n * @brief List comprehension equivalent\n *\n * Python equivalent:\n * @code{.py}\n * squares = [x * x for x in range(10)]\n * evens = [x for x in range(10) if x % 2 == 0]\n * @endcode\n */\nstd::vector<int> list_comprehension() {\n    std::vector<int> evens;\n    for (int x = 0; x < 10; x++) {\n        if (x % 2 == 0) {\n            evens.push_back(x);\n        }\n    }\n    return evens;\n}\n\n/**\n * @brief String concatenation and manipulation\n *\n * Python equivalent:\n * @code{.py}\n * s = \"Hello\"\n * s += \" World\"\n * print(len(s))\n * print(s[0])\n * @endcode\n */\nstd::string string_operations() {\n    std::string s = \"Hello\";\n    s += \" World\";\n    return s;\n}\n\n/**\n * @brief Person class with constructor and method\n *\n * Python equivalent:\n * @code{.py}\n * class Person:\n *     def __init__(self, name, age):\n *         self.name = name\n *         self.age = age\n *\n *     def greet(self):\n *         return f\"Hello, I'm {self.name}\"\n *\n * p = Person(\"Alice\", 30)\n * print(p.greet())\n * @endcode\n */\nclass Person {\npublic:\n    std::string name;\n    int age;\n\n    Person(std::string name, int age);\n    std::string greet() const;\n};\n\nPerson::Person(std::string n, int a) : name(n), age(a) {}\n\nstd::string Person::greet() const {\n    return \"Hello, I'm \" + name;\n}\n\n/**\n * @brief Optional value handling\n *\n * Python equivalent:\n * @code{.py}\n * def find_value(key):\n *     data = {\"a\": 1, \"b\": 2}\n *     return data.get(key)\n *\n * result = find_value(\"a\")\n * if result is not None:\n *     print(result)\n * @endcode\n */\nstd::optional<int> find_value(const std::string& key) {\n    std::map<std::string, int> data = {{\"a\", 1}, {\"b\", 2}};\n    auto it = data.find(key);\n    if (it != data.end()) {\n        return it->second;\n    }\n    return std::nullopt;\n}\n\n/**\n * @brief Tuple unpacking with structured bindings\n *\n * Python equivalent:\n * @code{.py}\n * point = (10, 20)\n * x, y = point\n * print(x, y)\n * @endcode\n */\nstd::tuple<int, int> create_tuple() {\n    return std::make_tuple(10, 20);\n}\n\n/**\n * @brief Filter even numbers from vector\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4, 5]\n * evens = list(filter(lambda x: x % 2 == 0, numbers))\n * @endcode\n */\nstd::vector<int> filter_evens(const std::vector<int>& numbers) {\n    std::vector<int> evens;\n    std::copy_if(numbers.begin(), numbers.end(),\n                 std::back_inserter(evens),\n                 [](int x) { return x % 2 == 0; });\n    return evens;\n}\n\n/**\n * @brief Check if any element satisfies condition\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4, 5]\n * has_even = any(x % 2 == 0 for x in numbers)\n * @endcode\n */\nbool has_even(const std::vector<int>& numbers) {\n    return std::any_of(numbers.begin(), numbers.end(),\n                       [](int x) { return x % 2 == 0; });\n}\n\n/**\n * @brief Check if all elements satisfy condition\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4, 5]\n * all_positive = all(x > 0 for x in numbers)\n * @endcode\n */\nbool all_positive(const std::vector<int>& numbers) {\n    return std::all_of(numbers.begin(), numbers.end(),\n                       [](int x) { return x > 0; });\n}\n\n/**\n * @brief Sort vector in place\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [3, 1, 4, 1, 5]\n * numbers.sort()\n * @endcode\n */\nstd::vector<int> sort_vector(std::vector<int> numbers) {\n    std::sort(numbers.begin(), numbers.end());\n    return numbers;\n}\n\n/**\n * @brief Find minimum element\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [3, 1, 4, 1, 5]\n * print(min(numbers))\n * @endcode\n */\nint find_min(const std::vector<int>& numbers) {\n    return *std::min_element(numbers.begin(), numbers.end());\n}\n\n/**\n * @brief Sum all elements\n *\n * Python equivalent:\n * @code{.py}\n * numbers = [1, 2, 3, 4, 5]\n * total = sum(numbers)\n * @endcode\n */\nint sum_vector(const std::vector<int>& numbers) {\n    return std::accumulate(numbers.begin(), numbers.end(), 0);\n}\n\n/**\n * @brief Function with default argument\n *\n * Python equivalent:\n * @code{.py}\n * def greet(name, greeting=\"Hello\"):\n *     return f\"{greeting}, {name}\"\n *\n * print(greet(\"Alice\"))\n * print(greet(\"Bob\", \"Hi\"))\n * @endcode\n */\nstd::string greet(const std::string& name, const std::string& greeting = \"Hello\") {\n    return greeting + \", \" + name;\n}\n\nTEST(BasicTest, AddFunction) {\n    EXPECT_EQ(add(3, 5), 8);\n    EXPECT_EQ(add(0, 0), 0);\n    EXPECT_EQ(add(-1, 1), 0);\n}\n\nTEST(VectorTest, ListsAndVectors) {\n    auto vec = lists_and_vectors();\n    EXPECT_EQ(vec.size(), 6);\n    EXPECT_EQ(vec[0], 1);\n    EXPECT_EQ(vec[5], 6);\n}\n\nTEST(VectorTest, ArraySlicing) {\n    auto reversed = array_slicing();\n    EXPECT_EQ(reversed.size(), 10);\n    EXPECT_EQ(reversed[0], 9);\n    EXPECT_EQ(reversed[9], 0);\n}\n\nTEST(MapTest, DictionariesAndMaps) {\n    auto ages = dictionaries_and_maps();\n    EXPECT_EQ(ages[\"Alice\"], 30);\n    EXPECT_EQ(ages[\"Bob\"], 25);\n    EXPECT_EQ(ages[\"Charlie\"], 35);\n}\n\nTEST(LambdaTest, SquareLambda) {\n    auto square = create_square_lambda();\n    EXPECT_EQ(square(5), 25);\n    EXPECT_EQ(square(0), 0);\n    EXPECT_EQ(square(-3), 9);\n}\n\nTEST(LambdaTest, MultiplyLambda) {\n    auto multiply = create_multiply_lambda(10);\n    EXPECT_EQ(multiply(5), 50);\n    EXPECT_EQ(multiply(0), 0);\n}\n\nTEST(LambdaTest, TransformWithLambda) {\n    std::vector<int> numbers = {1, 2, 3, 4};\n    auto squared = transform_with_lambda(numbers);\n    EXPECT_EQ(squared.size(), 4);\n    EXPECT_EQ(squared[0], 1);\n    EXPECT_EQ(squared[1], 4);\n    EXPECT_EQ(squared[2], 9);\n    EXPECT_EQ(squared[3], 16);\n}\n\nTEST(VectorTest, ListComprehension) {\n    auto evens = list_comprehension();\n    EXPECT_EQ(evens.size(), 5);\n    EXPECT_EQ(evens[0], 0);\n    EXPECT_EQ(evens[4], 8);\n}\n\nTEST(StringTest, StringOperations) {\n    auto str = string_operations();\n    EXPECT_EQ(str, \"Hello World\");\n    EXPECT_EQ(str.size(), 11);\n}\n\nTEST(ClassTest, PersonClass) {\n    Person p(\"Alice\", 30);\n    EXPECT_EQ(p.name, \"Alice\");\n    EXPECT_EQ(p.age, 30);\n    EXPECT_EQ(p.greet(), \"Hello, I'm Alice\");\n}\n\nTEST(OptionalTest, FindValue) {\n    auto result = find_value(\"a\");\n    ASSERT_TRUE(result.has_value());\n    EXPECT_EQ(result.value(), 1);\n\n    auto missing = find_value(\"z\");\n    EXPECT_FALSE(missing.has_value());\n}\n\nTEST(TupleTest, CreateTuple) {\n    auto [x, y] = create_tuple();\n    EXPECT_EQ(x, 10);\n    EXPECT_EQ(y, 20);\n}\n\nTEST(AlgorithmTest, FilterEvens) {\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n    auto evens = filter_evens(numbers);\n    EXPECT_EQ(evens.size(), 2);\n    EXPECT_EQ(evens[0], 2);\n    EXPECT_EQ(evens[1], 4);\n}\n\nTEST(AlgorithmTest, HasEven) {\n    std::vector<int> with_even = {1, 2, 3};\n    std::vector<int> without_even = {1, 3, 5};\n    EXPECT_TRUE(has_even(with_even));\n    EXPECT_FALSE(has_even(without_even));\n}\n\nTEST(AlgorithmTest, AllPositive) {\n    std::vector<int> all_pos = {1, 2, 3};\n    std::vector<int> has_neg = {1, -2, 3};\n    EXPECT_TRUE(all_positive(all_pos));\n    EXPECT_FALSE(all_positive(has_neg));\n}\n\nTEST(AlgorithmTest, SortVector) {\n    std::vector<int> unsorted = {3, 1, 4, 1, 5};\n    auto sorted = sort_vector(unsorted);\n    EXPECT_EQ(sorted[0], 1);\n    EXPECT_EQ(sorted[4], 5);\n}\n\nTEST(AlgorithmTest, FindMin) {\n    std::vector<int> numbers = {3, 1, 4, 1, 5};\n    EXPECT_EQ(find_min(numbers), 1);\n}\n\nTEST(AlgorithmTest, SumVector) {\n    std::vector<int> numbers = {1, 2, 3, 4, 5};\n    EXPECT_EQ(sum_vector(numbers), 15);\n}\n\nTEST(FunctionTest, DefaultArguments) {\n    EXPECT_EQ(greet(\"Alice\"), \"Hello, Alice\");\n    EXPECT_EQ(greet(\"Bob\", \"Hi\"), \"Hi, Bob\");\n}\n\nint main(int argc, char **argv) {\n    ::testing::InitGoogleTest(&argc, argv);\n    return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "src/gin/Dockerfile",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Modifications copyright (c) 2025 chang-ning\n# Modifications licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).\n# See LICENSE or https://creativecommons.org/licenses/by/4.0/\n\n# ref: https://github.com/aws-samples/awsome-distributed-training/blob/main/micro-benchmarks/nccl-tests\nARG CUDA_VERSION=12.8.1\nFROM nvcr.io/nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.47.0\nARG AWS_OFI_NCCL_VERSION=5f4202f11db1585d878196db4430aeda0e834a0c\nARG NCCL_VERSION=v2.29.3-1\nARG NCCL_TESTS_VERSION=v2.17.9\nARG NVSHMEM_VERSION=v3.5.19-1\nARG TORCH_VERSION=2.9.1\n\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils \\\n    libibverbs-dev \\\n    libibverbs1 \\\n    libmlx5-1 \\\n    libnccl2 \\\n    libnccl-dev\n\nRUN rm -rf /opt/hpcx \\\n    && rm -rf /usr/local/mpi \\\n    && rm -f /etc/ld.so.conf.d/hpcx.conf \\\n    && ldconfig\n\nENV OPAL_PREFIX=\n\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    apt-utils \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    check \\\n    cmake \\\n    ninja-build \\\n    curl \\\n    debhelper \\\n    devscripts \\\n    git \\\n    gcc \\\n    gdb \\\n    kmod \\\n    libsubunit-dev \\\n    libtool \\\n    openssh-client \\\n    openssh-server \\\n    pkg-config \\\n    vim \\\n    hwloc \\\n    libhwloc-dev \\\n    python3-dev \\\n    python3-venv \\\n    libomp-dev\n\nRUN apt-get purge -y cuda-compat-*\n\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \"    UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\n# Set paths for both aarch64 and x86_64\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:/opt/amazon/ofi-nccl/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin/:/opt/amazon/efa/bin:/usr/bin:/usr/local/bin:$PATH\n\nRUN apt-get install -y python3-pip \\\n    && pip3 install --break-system-packages --no-cache-dir awscli nvidia-ml-py Cython\n\n#################################################\n## Install NVIDIA GDRCopy\n##\n## NOTE: if `nccl-tests` or `/opt/gdrcopy/bin/sanity -v` crashes with incompatible version, ensure\n## that the cuda-compat-xx-x package is the latest.\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LD_LIBRARY_PATH=/opt/gdrcopy/lib:$LD_LIBRARY_PATH\nENV LIBRARY_PATH=/opt/gdrcopy/lib:$LIBRARY_PATH\nENV CPATH=/opt/gdrcopy/include:$CPATH\nENV PATH=/opt/gdrcopy/bin:$PATH\n\n#################################################\n## Install EFA installer\nRUN cd $HOME \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify --skip-plugin \\\n    && rm -rf $HOME/aws-efa-installer $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz\n\n###################################################\n## Install aws-ofi-nccl from source (pinned commit)\nRUN git clone https://github.com/aws/aws-ofi-nccl.git /tmp/aws-ofi-nccl \\\n    && cd /tmp/aws-ofi-nccl \\\n    && git checkout ${AWS_OFI_NCCL_VERSION} \\\n    && ./autogen.sh \\\n    && ./configure --prefix=/opt/amazon/ofi-nccl \\\n        --with-libfabric=/opt/amazon/efa \\\n        --with-cuda=/usr/local/cuda \\\n    && make -j$(nproc) \\\n    && make install \\\n    && rm -rf /tmp/aws-ofi-nccl\n\n###################################################\n## Install NCCL\nRUN git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git  /opt/nccl \\\n    && cd /opt/nccl \\\n    && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n    NVCC_GENCODE=\"-gencode=arch=compute_80,code=sm_80 -gencode=arch=compute_86,code=sm_86 -gencode=arch=compute_89,code=sm_89 -gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\n###################################################\n## Install NCCL-tests\nRUN git clone -b ${NCCL_TESTS_VERSION} https://github.com/NVIDIA/nccl-tests.git /opt/nccl-tests \\\n    && cd /opt/nccl-tests \\\n    && make -j $(nproc) \\\n    MPI=1 \\\n    MPI_HOME=/opt/amazon/openmpi/ \\\n    CUDA_HOME=/usr/local/cuda \\\n    NCCL_HOME=/opt/nccl/build \\\n    NVCC_GENCODE=\"-gencode=arch=compute_80,code=sm_80 -gencode=arch=compute_86,code=sm_86 -gencode=arch=compute_89,code=sm_89 -gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\n###################################################\n## Build NCCL Device API examples\nRUN cd /opt/nccl/examples/06_device_api \\\n    && make -j $(nproc) NCCL_HOME=/opt/nccl/build CUDA_HOME=/usr/local/cuda MPI=1 MPI_HOME=/opt/amazon/openmpi\n\n###################################################\n## Install NVSHMEM\nENV NVSHMEM_DIR=/opt/nvshmem\nENV NVSHMEM_HOME=/opt/nvshmem\nRUN git clone -b ${NVSHMEM_VERSION} https://github.com/NVIDIA/nvshmem.git \\\n  && cd nvshmem \\\n  && mkdir -p build \\\n  && cd build \\\n  && cmake -DNVSHMEM_PREFIX=/opt/nvshmem \\\n    -DCMAKE_CUDA_ARCHITECTURES=\"80;90\" \\\n    -DNVSHMEM_MPI_SUPPORT=1 \\\n    -DNVSHMEM_PMIX_SUPPORT=1 \\\n    -DNVSHMEM_LIBFABRIC_SUPPORT=1 \\\n    -DNVSHMEM_IBRC_SUPPORT=1 \\\n    -DNVSHMEM_IBGDA_SUPPORT=1 \\\n    -DNVSHMEM_USE_GDRCOPY=1 \\\n    -DNVSHMEM_BUILD_TESTS=1 \\\n    -DNVSHMEM_BUILD_EXAMPLES=1 \\\n    -DNVSHMEM_BUILD_HYDRA_LAUNCHER=1 \\\n    -DNVSHMEM_BUILD_TXZ_PACKAGE=0 \\\n    -DNVSHMEM_BUILD_PYTHON_LIB=0 \\\n    -DMPI_HOME=/opt/amazon/openmpi \\\n    -DPMIX_HOME=/opt/amazon/pmix \\\n    -DGDRCOPY_HOME=/opt/gdrcopy \\\n    -DLIBFABRIC_HOME=/opt/amazon/efa \\\n    -G Ninja .. \\\n  && ninja -j $(nproc) \\\n  && ninja install \\\n  && rm -rf /root/nvshmem\n\nRUN pip3 install --break-system-packages --no-cache-dir nvshmem4py-cu12\n\nENV LD_LIBRARY_PATH=/opt/amazon/pmix/lib:/opt/nvshmem/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/nvshmem/bin:$PATH\nENV NVSHMEM_REMOTE_TRANSPORT=libfabric\nENV NVSHMEM_LIBFABRIC_PROVIDER=efa\n\n###################################################\n## Install PyTorch (required for DeepEP)\nRUN pip3 install --break-system-packages --no-cache-dir torch==${TORCH_VERSION} --index-url https://download.pytorch.org/whl/cu128\n\n###################################################\n## Install DeepEP with NCCL GIN backend (PR #521)\nRUN unset NVSHMEM_DIR NVSHMEM_HOME \\\n    && export ENABLE_NCCL=1 \\\n    && export NCCL_DIR=/opt/nccl/build \\\n    && export LD_LIBRARY_PATH=/opt/nccl/build/lib:$LD_LIBRARY_PATH \\\n    && export LD_PRELOAD=/opt/nccl/build/lib/libnccl.so.2 \\\n    && git clone -b nccl https://github.com/aamirshafi/DeepEP.git /opt/DeepEP \\\n    && cd /opt/DeepEP \\\n    && git checkout 6d29f34 \\\n    && python3 setup.py build_ext --inplace \\\n    && pip install --break-system-packages --no-build-isolation .\n\nRUN rm -rf /var/lib/apt/lists/*\n\n## Set Open MPI variables to exclude network interface and conduit.\nENV OMPI_MCA_pml=^ucx            \\\n    OMPI_MCA_btl=tcp,self           \\\n    OMPI_MCA_btl_tcp_if_exclude=lo,docker0,veth_def_agent\\\n    OPAL_PREFIX=/opt/amazon/openmpi \\\n    NCCL_SOCKET_IFNAME=^docker,lo,veth\n\nENV FI_EFA_USE_DEVICE_RDMA=1\nENV FI_PROVIDER=efa\nENV FI_EFA_FORK_SAFE=1\nENV NCCL_BUFFSIZE=8388608\nENV NCCL_P2P_NET_CHUNKSIZE=524288\nENV NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\n\n\n## Turn off PMIx Error https://github.com/open-mpi/ompi/issues/7516\nENV PMIX_MCA_gds=hash\n\n## Set LD_PRELOAD for NCCL library\nENV LD_PRELOAD=/opt/nccl/build/lib/libnccl.so\n"
  },
  {
    "path": "src/gin/Makefile",
    "content": ".PHONY: help docker save clean\n\n.DEFAULT_GOAL := help\n\nIMAGE_NAME ?= nccl\nIMAGE_TAG ?= latest\n\nhelp:\n\t@echo \"NCCL GIN Test Makefile\"\n\t@echo \"\"\n\t@echo \"Targets:\"\n\t@echo \"  docker    Build Docker image\"\n\t@echo \"  save      Save Docker image to tar.gz and sqsh\"\n\t@echo \"  clean     Remove image, tarball, and sqsh\"\n\t@echo \"\"\n\t@echo \"Usage:\"\n\t@echo \"  make docker && make save\"\n\ndocker:\n\tdocker build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile .\n\nsave:\n\tenroot import -o $(IMAGE_NAME)+$(IMAGE_TAG).sqsh dockerd://$(IMAGE_NAME):$(IMAGE_TAG)\n\tdocker save $(IMAGE_NAME):$(IMAGE_TAG) | pigz > $(IMAGE_NAME)+$(IMAGE_TAG).tar.gz\n\nclean:\n\t-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true\n\t-rm -f $(IMAGE_NAME)+$(IMAGE_TAG).sqsh\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n"
  },
  {
    "path": "src/gin/run.enroot",
    "content": "#!/bin/bash\n# Launch command inside enroot container via srun + pyxis\n# Usage: salloc -N 2 ./run.enroot <command...>\n\nset -exo pipefail\n\nDIR=\"$( cd -- \"$( dirname -- \"${BASH_SOURCE[0]}\" )\" &> /dev/null && pwd )\"\nSQSH=\"${SQSH:-${DIR}/nccl+latest.sqsh}\"\nMOUNT=\"/fsx:/fsx\"\n\nmaster_addr=$(scontrol show hostnames $SLURM_JOB_NODELIST 2>/dev/null | head -n1)\nmaster_addr=$(getent hosts \"${master_addr}\" 2>/dev/null | awk '{print $1}' || echo \"${master_addr}\")\nmaster_addr=${master_addr:-127.0.0.1}\n\ncmd=\"$(cat <<EOF\nexport LD_LIBRARY_PATH=/opt/amazon/ofi-nccl/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:\\$LD_LIBRARY_PATH\nexport LD_PRELOAD=/opt/nccl/build/lib/libnccl.so.2\nexport FI_PROVIDER=efa\nexport FI_EFA_USE_DEVICE_RDMA=1\nexport FI_EFA_FORK_SAFE=1\nexport NCCL_NET_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-net-ofi.so\nexport NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\nexport NCCL_DEBUG=WARN\nexport NCCL_BUFFSIZE=8388608\nexport NCCL_P2P_NET_CHUNKSIZE=524288\nexport OMP_NUM_THREADS=1\nexport MASTER_ADDR=${master_addr}\nexport MASTER_PORT=29500\nexport DEEP_EP_BACKEND=nccl\nexport NCCL_GIN_TYPE=2\nexport TORCH_DISTRIBUTED_BACKEND=nccl\nexport WORLD_SIZE=${SLURM_NNODES:-2}\nexport RANK=\\${SLURM_NODEID:-0}\n$@\nEOF\n)\"\n\nsrun --container-image \"${SQSH}\" \\\n  --container-mounts \"${MOUNT}\" \\\n  --container-name nccl \\\n  --mpi=pmix \\\n  --ntasks-per-node=8 \\\n  bash -c \"${cmd}\"\n"
  },
  {
    "path": "src/gin/run.sbatch",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nGPUS=\"${GPUS:-all}\"\nDP_BACKEND=\"${DP_BACKEND:-rpc}\"\nDEEPEP_ENV=\"export DEEP_EP_BACKEND=nccl && export NCCL_GIN_TYPE=2 &&\"\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nCONTAINER_MOUNT=\"/fsx\"\nWORKSPACE=\"$PWD\"\nFORCE_PULL=false\nENABLE_NSYS=false\nVLLM_STARTED=false\nSERVE_ARGS=()\n\nwhile ((\"$#\")); do\n  case \"$1\" in\n  --image)\n    IMAGE=\"$2\"\n    shift 2\n    ;;\n  --container-mount)\n    CONTAINER_MOUNT=\"$2\"\n    shift 2\n    ;;\n  --workspace | -w)\n    WORKSPACE=\"$2\"\n    shift 2\n    ;;\n  --force | -f)\n    FORCE_PULL=true\n    shift\n    ;;\n  --nsys)\n    ENABLE_NSYS=true\n    shift\n    ;;\n  --profile)\n    SERVE_ARGS+=(--profiler-config \"{\\\"profiler\\\": \\\"torch\\\", \\\"torch_profiler_dir\\\": \\\"${PWD}/vllm_profile\\\"}\")\n    shift\n    ;;\n  --profiler-config)\n    SERVE_ARGS+=(--profiler-config \"$2\")\n    shift 2\n    ;;\n  *)\n    SERVE_ARGS+=(\"$1\")\n    shift\n    ;;\n  esac\ndone\n\n# Build nsys command prefix\nNSYS_CMD=\"\"\nif [[ \"${ENABLE_NSYS}\" == \"true\" ]]; then\n  NSYS_DIR=\"${WORKSPACE}/nsys-vllm\"\n  mkdir -p \"${NSYS_DIR}\"\n  NSYS_PATH=\"${NSYS_DIR}/profile-node${SLURM_NODEID:-0}.nsys-rep\"\n  NSYS_CMD=\"nsys profile\"\n  NSYS_CMD+=\" -t cuda,nvtx,osrt,cudnn,cublas\"\n  NSYS_CMD+=\" --trace-fork-before-exec=true\"\n  NSYS_CMD+=\" --cuda-graph-trace=node\"\n  NSYS_CMD+=\" --capture-range=cudaProfilerApi\"\n  NSYS_CMD+=\" --capture-range-end=repeat\"\n  NSYS_CMD+=\" --cuda-memory-usage=true\"\n  NSYS_CMD+=\" --cudabacktrace=true\"\n  NSYS_CMD+=\" -o ${NSYS_PATH}\"\n  NSYS_CMD+=\" --force-overwrite=true\"\nfi\nIMAGE=\"${IMAGE:-${WORKSPACE}/vllm-serve-latest.tar.gz}\"\nLOGDIR=\"${WORKSPACE}/logs\"\n\n# Build a shell-safe string from SERVE_ARGS for nested bash -c / docker exec\nSERVE_ARGS_STR=$(printf '%q ' \"${SERVE_ARGS[@]+\"${SERVE_ARGS[@]}\"}\")\n\n# Peek at SERVE_ARGS to extract values needed for topology computation\n_peek_arg() {\n  local short=\"$1\" long=\"$2\" default=\"$3\"\n  local i=0\n  while ((i < ${#SERVE_ARGS[@]})); do\n    if [[ \"${SERVE_ARGS[$i]}\" == \"$short\" || \"${SERVE_ARGS[$i]}\" == \"$long\" ]]; then\n      echo \"${SERVE_ARGS[$((i + 1))]}\"\n      return\n    fi\n    ((i++))\n  done\n  echo \"$default\"\n}\n_has_flag() {\n  for arg in \"${SERVE_ARGS[@]}\"; do [[ \"$arg\" == \"$1\" ]] && return 0; done\n  return 1\n}\n\nTP=$(_peek_arg \"-tp\" \"--tensor-parallel-size\" \"1\")\nPP=$(_peek_arg \"-pp\" \"--pipeline-parallel-size\" \"1\")\nENABLE_EP=$(_has_flag \"--enable-expert-parallel\" && echo \"true\" || echo \"false\")\n\nload_or_pull_image() {\n  if [[ \"${FORCE_PULL}\" == \"true\" ]]; then\n    info \"Force pull: cleaning up existing images...\"\n    srun --ntasks-per-node=1 bash -c '\n      docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n      docker images -aq | xargs -r docker rmi -f 2>/dev/null || true\n    '\n  fi\n\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    info \"Loading Docker image from tarball...\"\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    info \"Pulling Docker image from registry...\"\n    local registry=\"${IMAGE%%/*}\"\n    local region=$(echo \"${registry}\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n    region=\"${region:-us-west-2}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${IMAGE}' &>/dev/null; then\n        aws ecr get-login-password --region '${region}' | docker login --username AWS --password-stdin '${registry}'\n        docker pull '${IMAGE}'\n      fi\n    \"\n    CONTAINER_IMAGE=\"${IMAGE}\"\n  fi\n}\n\nlaunch_container() {\n  local name=\"${1}\" cmd=\"${2}\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  local net_if=\"${GLOO_SOCKET_IFNAME:-$(ip -o -4 route show to default | awk '{print $5}' | head -1)}\"\n\n  docker run --gpus \"${GPUS}\" \\\n    --privileged -d \\\n    --name \"${name}\" \\\n    --uts=host --ipc=host --net=host \\\n    --ulimit stack=67108864 --ulimit memlock=-1 \\\n    --security-opt seccomp=unconfined \\\n    \"${devices[@]}\" \\\n    -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\" \\\n    -e NCCL_SOCKET_IFNAME=\"${net_if}\" \\\n    -e GLOO_SOCKET_IFNAME=\"${net_if}\" \\\n    -e TP_SOCKET_IFNAME=\"${net_if}\" \\\n    --entrypoint bash \\\n    \"${CONTAINER_IMAGE:-${IMAGE}}\" \\\n    -c \"${cmd}\"\n}\n\nsetup_topology() {\n  NUM_NODES=${SLURM_JOB_NUM_NODES:-1}\n  GPUS_PER_NODE=8\n  TOTAL_GPUS=$((NUM_NODES * GPUS_PER_NODE))\n\n  if [[ \"$PP\" -gt 1 && \"$ENABLE_EP\" == \"true\" ]]; then\n    err \"Pipeline parallel (PP=$PP) and expert parallel cannot be enabled simultaneously\"\n    exit 1\n  fi\n\n  [[ \"$PP\" -gt 1 ]] && DP_BACKEND=\"mp\"\n\n  if [[ \"$ENABLE_EP\" == \"true\" ]]; then\n    DP=$((TOTAL_GPUS / TP))\n    if [[ $((DP * TP)) -ne $TOTAL_GPUS ]]; then\n      err \"DP($DP) * TP($TP) = $((DP * TP)) != TOTAL_GPUS($TOTAL_GPUS)\"\n      exit 1\n    fi\n  else\n    DP=$((TOTAL_GPUS / (TP * PP)))\n    if [[ $((DP * TP * PP)) -ne $TOTAL_GPUS ]]; then\n      err \"DP($DP) * TP($TP) * PP($PP) = $((DP * TP * PP)) != TOTAL_GPUS($TOTAL_GPUS)\"\n      exit 1\n    fi\n  fi\n  DP_LOCAL=$((GPUS_PER_NODE / TP))\n\n  readarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\n  HEAD_NODE=${NODES[0]}\n  HEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n\n  RAY_PORT=$((6379 + (SLURM_JOB_ID % 1000)))\n  RPC_PORT=$((13345 + (SLURM_JOB_ID % 1000)))\n\n  mkdir -p \"${LOGDIR}\"\n\n  info \"========================================\"\n  info \"vLLM Server\"\n  info \"========================================\"\n  info \"Image: ${IMAGE}\"\n  info \"Nodes: ${NUM_NODES}, Head: ${HEAD_NODE} (${HEAD_IP}), GPUs: ${TOTAL_GPUS}\"\n  info \"Parallelism: TP=${TP}, PP=${PP}, DP=${DP}, DP_LOCAL=${DP_LOCAL}, EP=${ENABLE_EP}\"\n  info \"Backend: ${DP_BACKEND}\"\n  info \"SERVE_ARGS: ${SERVE_ARGS[*]+\"${SERVE_ARGS[*]}\"}\"\n  info \"========================================\"\n}\n\nstop_nsys() {\n  [[ \"${VLLM_STARTED}\" != \"true\" ]] && return 0\n  info \"Sending SIGINT to nsys processes for graceful shutdown...\"\n  srun --ntasks-per-node=1 bash -c '\n    for cid in $(docker ps -q); do\n      docker exec \"$cid\" pkill -INT -f \"^nsys profile\" 2>/dev/null || true\n    done\n  ' 2>/dev/null || true\n}\n\nwait_for_nsys() {\n  [[ \"${VLLM_STARTED}\" != \"true\" ]] && return 0\n  info \"Waiting 60s for nsys to finalize profiles...\"\n  sleep 60\n}\n\ncleanup() {\n  info \"Cleaning up containers...\"\n  if [[ \"${ENABLE_NSYS}\" == \"true\" ]]; then\n    stop_nsys\n    wait_for_nsys\n  fi\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker stop -t 30 2>/dev/null || true\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n}\n\nstart_ray_head() {\n  info \"Starting Ray head on ${HEAD_NODE}...\"\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    $(declare -f launch_container)\n    CONTAINER_IMAGE='${CONTAINER_IMAGE:-}' IMAGE='${IMAGE}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container ray-head 'sleep infinity'\n  \"\n  sleep 5\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec ray-head ray start --head --port=${RAY_PORT} \\\n      --num-gpus=${GPUS_PER_NODE} --num-cpus=96 --disable-usage-stats\n  \"\n}\n\nstart_ray_workers() {\n  [[ \"$NUM_NODES\" -le 1 ]] && return\n  local worker_nodes=$(echo \"${NODES[@]:1}\" | tr ' ' ',')\n  info \"Starting Ray workers on ${worker_nodes}...\"\n  srun --nodes=$((NUM_NODES - 1)) --nodelist=\"${worker_nodes}\" --ntasks-per-node=1 bash -c \"\n    $(declare -f launch_container)\n    CONTAINER_IMAGE='${CONTAINER_IMAGE:-}' IMAGE='${IMAGE}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container ray-worker 'sleep infinity'\n  \"\n  sleep 5\n  srun --nodes=$((NUM_NODES - 1)) --nodelist=\"${worker_nodes}\" --ntasks-per-node=1 bash -c \"\n    docker exec ray-worker ray start --address=${HEAD_IP}:${RAY_PORT} \\\n      --num-gpus=${GPUS_PER_NODE} --num-cpus=96 --disable-usage-stats\n  \"\n}\n\nwait_for_gpus() {\n  info \"Waiting for ${TOTAL_GPUS} GPUs...\"\n  for _ in {1..120}; do\n    local gpu_count\n    gpu_count=$(srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n      docker exec ray-head python3 -c \\\n        'import ray; ray.init(address=\\\"auto\\\"); print(int(ray.cluster_resources().get(\\\"GPU\\\",0))); ray.shutdown()' \\\n        2>/dev/null\" || echo 0)\n    [[ \"$gpu_count\" -ge \"$TOTAL_GPUS\" ]] && return 0\n    sleep 5\n  done\n  err \"Timeout waiting for GPUs\"\n  return 1\n}\n\nstart_vllm_ray() {\n  info \"Launching vllm serve (Ray)...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n  local extra=\"--host 0.0.0.0 --port 8000 --data-parallel-backend ray --data-parallel-address ${HEAD_IP} --data-parallel-size ${DP}\"\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \\\n    \"docker exec -d ray-head bash -c '${DEEPEP_ENV} ${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} 2>&1 | tee ${logfile}'\"\n}\n\nstart_vllm_mp() {\n  info \"Starting vLLM with PP (multiprocessing)...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\n  for i in $(seq 0 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-node-${i} 'sleep infinity'\n    \" &\n  done\n  wait\n\n  local extra=\"--host 0.0.0.0 --port 8000 --nnodes ${NUM_NODES} --master-addr ${HEAD_IP} --master-port 29500\"\n\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \\\n      \"docker exec -d vllm-node-${i} bash -c '${DEEPEP_ENV} ${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} --node-rank ${i} --headless 2>&1 | tee ${logfile}.node${i}'\"\n  done\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \\\n    \"docker exec -d vllm-node-0 bash -c '${DEEPEP_ENV} ${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} --node-rank 0 2>&1 | tee ${logfile}'\"\n}\n\n# RPC backend\nstart_vllm_rpc() {\n  info \"Starting vLLM with RPC-based DP...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    $(declare -f launch_container)\n    IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-head 'sleep infinity'\n  \"\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-worker 'sleep infinity'\n    \"\n  done\n  sleep 3\n\n  local extra=\"--data-parallel-size ${DP} --data-parallel-size-local ${DP_LOCAL} --data-parallel-address ${HEAD_IP} --data-parallel-rpc-port ${RPC_PORT}\"\n\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    local start_rank=$((i * DP_LOCAL))\n    info \"Starting RPC worker on ${NODES[$i]} (rank ${start_rank})...\"\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      docker exec -d vllm-worker bash -c '${DEEPEP_ENV} ${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} \\\n        --data-parallel-start-rank ${start_rank} --headless \\\n        2>&1 | tee ${LOGDIR}/vllm_worker_${SLURM_JOB_ID}_${i}.log'\n    \"\n  done\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec -d vllm-head bash -c '${DEEPEP_ENV} ${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} \\\n      --host 0.0.0.0 --port 8000 \\\n      2>&1 | tee ${logfile}'\n  \"\n}\n\nwait_for_server() {\n  info \"Waiting for vLLM server at ${HEAD_IP}:8000...\"\n  for _ in {1..360}; do\n    if srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/health\" &>/dev/null &&\n      srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/v1/models | grep -q '\\\"id\\\"'\" &>/dev/null; then\n      info \"Server ready at ${HEAD_IP}:8000\"\n      return 0\n    fi\n    sleep 10\n  done\n  err \"Timeout waiting for server\"\n  return 1\n}\n\nsetup_topology\ntrap cleanup EXIT\ncleanup\n\nLOGFILE=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\nload_or_pull_image\n\ncase \"${DP_BACKEND}\" in\nray)\n  start_ray_head\n  start_ray_workers\n  wait_for_gpus\n  start_vllm_ray\n  ;;\nmp) start_vllm_mp ;;\nrpc) start_vllm_rpc ;;\n*)\n  err \"Unknown backend: ${DP_BACKEND}\"\n  exit 1\n  ;;\nesac\n\ntail -f \"${LOGFILE}\" 2>/dev/null &\n\nwait_for_server || exit 1\nVLLM_STARTED=true\n\ninfo \"vLLM serving on ${HEAD_IP}:8000 — Ctrl+C or scancel to stop\"\ninfo \"Logs: ${LOGFILE}\"\nsleep infinity\n"
  },
  {
    "path": "src/llm/sglang/Dockerfile",
    "content": "ARG SGLANG_VERSION=0.5.8\nARG CUDA_VERSION=12.8.1\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.46.0\nARG NCCL_VERSION=v2.29.2-1\n\nFROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION\nARG EFA_INSTALLER_VERSION\nARG NCCL_VERSION\nARG SGLANG_VERSION\n\n# Prevent interactive prompts\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=UTC\n\n# Update and remove conflicting packages\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils \\\n    libibverbs-dev \\\n    libibverbs1 \\\n    libmlx5-1 \\\n    libnccl2 \\\n    libnccl-dev\n\n# Clean up existing MPI installations\nRUN rm -rf /opt/hpcx \\\n    && rm -rf /usr/local/mpi \\\n    && rm -f /etc/ld.so.conf.d/hpcx.conf \\\n    && ldconfig\n\nENV OPAL_PREFIX=\n\n# Install build dependencies\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    apt-utils \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    check \\\n    cmake \\\n    curl \\\n    debhelper \\\n    devscripts \\\n    git \\\n    gcc \\\n    gdb \\\n    kmod \\\n    libnuma-dev \\\n    libsubunit-dev \\\n    libtool \\\n    openssh-client \\\n    openssh-server \\\n    pkg-config \\\n    python3 \\\n    python3-dev \\\n    python3-pip \\\n    vim \\\n    wget \\\n    ninja-build \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Remove cuda-compat if present\nRUN apt-get purge -y cuda-compat-* || true\n\n# Configure SSH\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \" UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\n# Set library paths\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:/opt/gdrcopy/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin:/opt/amazon/efa/bin:/opt/gdrcopy/bin:/usr/bin:/usr/local/bin:$PATH\n\n# Remove PEP 668 restriction and install packages\nRUN rm -f /usr/lib/python*/EXTERNALLY-MANAGED \\\n    && pip3 install --no-cache-dir awscli nvidia-ml-py Cython\n\n# Install GDRCopy\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LIBRARY_PATH=/opt/gdrcopy/lib:${LIBRARY_PATH:-}\nENV CPATH=/opt/gdrcopy/include\n\n# Install EFA dependencies\nRUN apt-get update -y && apt-get install -y --no-install-recommends \\\n    pciutils \\\n    environment-modules \\\n    tcl \\\n    libnl-3-200 \\\n    libnl-3-dev \\\n    libnl-route-3-200 \\\n    libnl-route-3-dev \\\n    udev \\\n    dmidecode \\\n    ethtool \\\n    iproute2 \\\n    libevent-core-2.1-7t64 \\\n    libevent-pthreads-2.1-7t64 \\\n    libhwloc15 \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Install EFA\nRUN cd /tmp \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify \\\n    && rm -rf /tmp/aws-efa-installer*\n\n# Install NCCL\nRUN git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git /tmp/nccl \\\n    && cd /tmp/nccl \\\n    && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n    NVCC_GENCODE=\"-gencode=arch=compute_90,code=sm_90\" \\\n    && mkdir -p /opt/nccl/build/lib \\\n    && cp -r build/lib/* /opt/nccl/build/lib/ \\\n    && cp -r build/include /opt/nccl/build/ \\\n    && rm -rf /tmp/nccl\n\n# OpenMPI settings\nENV OMPI_MCA_pml=^ucx\nENV OMPI_MCA_btl=tcp,self\nENV OMPI_MCA_btl_tcp_if_exclude=lo,docker0,veth_def_agent\nENV OPAL_PREFIX=/opt/amazon/openmpi\nENV PMIX_MCA_gds=hash\n\n# NCCL settings\nENV NCCL_DEBUG=INFO\nENV NCCL_SOCKET_IFNAME=^docker,lo,veth\nENV NCCL_P2P_NET_CHUNKSIZE=524288\nENV NCCL_BUFFSIZE=8388608\nENV NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\nENV LD_PRELOAD=/opt/nccl/build/lib/libnccl.so\n\n# EFA settings\nENV FI_PROVIDER=efa\nENV FI_EFA_USE_DEVICE_RDMA=1\nENV FI_EFA_FORK_SAFE=1\nENV RDMAV_FORK_SAFE=1\n\n# Install SGLang (with CUDA 12.8 wheels)\nRUN pip3 install --no-cache-dir \"sglang[all]==${SGLANG_VERSION}\" \\\n    --find-links https://docs.sglang.ai/whl/cu128/\n\n# Install Nsight Systems for profiling\nRUN apt-get update -y && apt-get install -y --no-install-recommends gnupg \\\n    && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/3bf863cc.pub \\\n    && echo \"deb https://developer.download.nvidia.com/devtools/repos/ubuntu2404/$(dpkg --print-architecture) /\" \\\n       > /etc/apt/sources.list.d/nvidia-devtools.list \\\n    && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F60F4B3D7FA2AF80 \\\n    && apt-get update -y \\\n    && apt-get install -y --no-install-recommends nsight-systems-cli \\\n    && rm -rf /var/lib/apt/lists/*\n\nWORKDIR /workspace\n"
  },
  {
    "path": "src/llm/sglang/Makefile",
    "content": ".PHONY: help docker sqush save load serve test bench clean\n\n.DEFAULT_GOAL := help\n\nhelp:\n\t@echo \"SGLang Serving Makefile\"\n\t@echo \"\"\n\t@echo \"Build targets:\"\n\t@echo \"  docker              Build Docker image\"\n\t@echo \"  sqush               Build Enroot sqsh file\"\n\t@echo \"  save                Save Docker image to tar.gz\"\n\t@echo \"  load                Load Docker image from tar.gz\"\n\t@echo \"\"\n\t@echo \"Run targets:\"\n\t@echo \"  serve               Launch SGLang server\"\n\t@echo \"  test                Test SGLang API endpoints\"\n\t@echo \"  bench               Run benchmarks (HOST=ip)\"\n\t@echo \"\"\n\t@echo \"Variables:\"\n\t@echo \"  MODEL=$(MODEL)\"\n\t@echo \"  PORT=$(PORT)\"\n\t@echo \"  TP=$(TP)\"\n\t@echo \"\"\n\t@echo \"Examples:\"\n\t@echo \"  make docker\"\n\t@echo \"  make serve MODEL=Qwen/Qwen2.5-14B-Instruct TP=8\"\n\t@echo \"  make test HOST=10.0.128.193\"\n\t@echo \"  make bench HOST=10.0.128.193\"\n\t@echo \"\"\n\t@echo \"Cleanup:\"\n\t@echo \"  clean               Remove containers and images\"\n\nIMAGE_NAME ?= sglang-serve\nIMAGE_TAG ?= latest\nCONTAINER_NAME ?= sglang-server\nMODEL ?= Qwen/Qwen2.5-7B-Instruct\nPORT ?= 30000\nTP ?= 1\nHOST ?= localhost\nBENCH_TYPE ?=\n\nDEVICES := --device=/dev/gdrdrv $(shell find /dev/infiniband -name \"uverbs*\" 2>/dev/null | sed 's/^/--device=/')\n\nDOCKER_RUN = docker run --gpus all \\\n\t--privileged \\\n\t--uts=host \\\n\t--ipc=host \\\n\t--net=host \\\n\t--ulimit stack=67108864 \\\n\t--ulimit memlock=-1 \\\n\t--security-opt seccomp=unconfined \\\n\t$(DEVICES) \\\n\t--rm \\\n\t--name $(CONTAINER_NAME) \\\n\t-v /fsx:/fsx \\\n\t--entrypoint bash \\\n\t$(IMAGE_NAME):$(IMAGE_TAG)\n\ndocker:\n\tdocker build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile .\n\nsqush: docker\n\tenroot import -o $(IMAGE_NAME)-$(IMAGE_TAG).sqsh dockerd://$(IMAGE_NAME):$(IMAGE_TAG)\n\nsave:\n\tdocker save $(IMAGE_NAME):$(IMAGE_TAG) | pigz > $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n\nload:\n\tpigz -dc $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz | docker load\n\nserve:\n\t$(DOCKER_RUN) -c 'python3 -m sglang.launch_server --model-path $(MODEL) --host 0.0.0.0 --port $(PORT) --tp $(TP)'\n\ntest:\n\t@./test.sh -H $(HOST) -p $(PORT)\n\nbench:\n\t@bash bench.sh -H $(HOST) -p $(PORT) -i $(IMAGE_NAME):$(IMAGE_TAG) $(if $(BENCH_TYPE),--type $(BENCH_TYPE))\n\nclean:\n\t-docker rm -f $(CONTAINER_NAME) 2>/dev/null || true\n\t-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).sqsh\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n"
  },
  {
    "path": "src/llm/sglang/README.rst",
    "content": "=============\nSGLang Serving\n=============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis cheat sheet provides quick-reference commands for launching an SGLang server in\nboth local (single-node) and SLURM (multi-node) environments. It covers building the\nDocker image, running with different parallelism strategies, and testing the server.\n\nFor more details, see the\n`SGLang documentation <https://docs.sglang.ai/>`_ and\n`GitHub repository <https://github.com/sgl-project/sglang>`_.\n\nFor parallelism strategies and benchmark methodology, see the\n`LLM Serving Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-serving.rst>`_ and\n`LLM Benchmark Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-bench.rst>`_.\n\nBuild Docker Image\n------------------\n\nThe Dockerfile bundles SGLang with EFA drivers, NCCL, and GDRCopy for high-performance\nmulti-node inference on GPU clusters.\n\n.. code-block:: bash\n\n    # Build the Docker image\n    make docker\n\n    # Save as a compressed tarball for SLURM nodes\n    # Output: sglang-serve-latest.tar.gz\n    make save\n\nLocal Serving (Single Node)\n---------------------------\n\nFor development or single-node deployments, SGLang can run directly on the host or\ninside a Docker container. The server exposes an OpenAI-compatible API on port 30000.\n\n**Bare metal** — run SGLang directly without Docker:\n\n.. code-block:: bash\n\n    # Single GPU\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 30000\n\n    # Tensor parallel across 8 GPUs\n    python -m sglang.launch_server --model-path Qwen/Qwen2.5-14B-Instruct --tp 8\n\n    # MoE model with expert parallelism (EP subdivides TP)\n    python -m sglang.launch_server --model-path Qwen/Qwen1.5-MoE-A2.7B --tp 8 --ep 2\n\n**Using Docker (via Makefile)**:\n\n.. code-block:: bash\n\n    # Single GPU with default model\n    make serve MODEL=Qwen/Qwen2.5-7B-Instruct\n\n    # Tensor parallel across 8 GPUs\n    make serve MODEL=Qwen/Qwen2.5-14B-Instruct TP=8\n\n**Using Docker directly**:\n\n.. code-block:: bash\n\n    # Single GPU\n    docker run --gpus all --rm --net=host -v /fsx:/fsx \\\n      sglang-serve:latest \\\n      python3 -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 30000\n\n    # Tensor parallel across 8 GPUs\n    docker run --gpus all --rm --net=host -v /fsx:/fsx \\\n      sglang-serve:latest \\\n      python3 -m sglang.launch_server --model-path Qwen/Qwen2.5-14B-Instruct --tp 8 --host 0.0.0.0 --port 30000\n\nSLURM Serving (Multi-Node)\n---------------------------\n\n``run.sbatch`` orchestrates multi-node SGLang serving on SLURM clusters. It handles\nDocker image distribution, container launch with EFA/GPU passthrough, and health\nchecking. The server runs until you stop it with ``Ctrl+C`` or ``scancel``.\n\n**Script flags** — consumed by the script, not passed to SGLang:\n\n.. list-table::\n   :widths: 30 70\n   :header-rows: 1\n\n   * - Flag\n     - Description\n   * - ``--image PATH``\n     - Docker image tarball or registry path (default: ``$WORKSPACE/sglang-serve-latest.tar.gz``)\n   * - ``--workspace, -w PATH``\n     - Base directory for default image and logs (default: ``$PWD``)\n   * - ``--container-mount PATH``\n     - Host path to bind-mount into containers (default: ``/fsx``)\n   * - ``--force, -f``\n     - Force remove existing containers and images before loading\n   * - ``--profile``\n     - Enable PyTorch profiler (writes to ``$PWD/sglang_profile``)\n\nAll other arguments are passed directly to ``python -m sglang.launch_server``.\n\n**Basic usage**:\n\n.. code-block:: bash\n\n    # Allocate 2 nodes with 8 GPUs each\n    salloc -N 2 --gpus-per-node=8 --exclusive\n\n    # MoE with expert parallelism (TP=8, EP=2 across 2 nodes)\n    bash run.sbatch \\\n      --model-path Qwen/Qwen1.5-MoE-A2.7B \\\n      --tp 8 --ep 2\n\n**Data parallelism** — requires ``--enable-dp-attention`` for multi-node:\n\n.. code-block:: bash\n\n    # TP=8, DP=2 (2 replicas across 16 GPUs)\n    bash run.sbatch \\\n      --model-path Qwen/Qwen2.5-14B-Instruct \\\n      --tp 8 --dp 2 --enable-dp-attention\n\n**Pipeline parallelism**:\n\n.. code-block:: bash\n\n    # TP=8, PP=2\n    bash run.sbatch \\\n      --model-path deepseek-ai/DeepSeek-V2-Lite \\\n      --tp 8 --pp 2\n\n**Custom image**:\n\n.. code-block:: bash\n\n    bash run.sbatch \\\n      --image /fsx/images/sglang-serve-latest.tar.gz \\\n      --model-path Qwen/Qwen2.5-72B-Instruct \\\n      --tp 8\n\n**Profiling**:\n\n.. code-block:: bash\n\n    # PyTorch profiler\n    bash run.sbatch --profile \\\n      --model-path Qwen/Qwen2.5-14B-Instruct \\\n      --tp 8\n\nTest the Server\n---------------\n\nSGLang serves on port 30000 by default:\n\n.. code-block:: bash\n\n    # Health check\n    curl http://<HEAD_IP>:30000/health\n\n    # List models\n    curl http://<HEAD_IP>:30000/v1/models\n\n    # Chat completion (OpenAI-compatible)\n    curl -X POST http://<HEAD_IP>:30000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"model\": \"Qwen/Qwen2.5-14B-Instruct\",\n        \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],\n        \"max_tokens\": 50\n      }'\n\n    # SGLang native generate endpoint\n    curl -X POST http://<HEAD_IP>:30000/generate \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\"text\": \"Hello\", \"sampling_params\": {\"max_new_tokens\": 50}}'\n\n    # Run the included test script\n    bash test.sh\n\n    # Test against a remote server\n    bash test.sh -H 10.0.128.193 -p 30000\n\nBenchmark\n---------\n\n``bench.sh`` measures serving performance (throughput, TTFT, ITL, latency) by sending\nrequests to a running SGLang server. It handles Docker image loading and container\nmanagement automatically.\n\n.. code-block:: bash\n\n    # Run all benchmarks\n    bash bench.sh -H 10.0.128.193 -i sglang-serve:latest\n\n    # Run specific benchmarks\n    bash bench.sh -H 10.0.128.193 -i sglang-serve:latest --type throughput,prefill\n\n    # Via Makefile\n    make bench HOST=10.0.128.193\n    make bench HOST=10.0.128.193 BENCH_TYPE=throughput,prefill\n\nAvailable benchmark types:\n\n- **throughput** — peak output tokens/sec at max request rate\n- **prefill** — TTFT scaling with input length (128→16K tokens)\n- **decode** — ITL as output length grows (128→1024 tokens)\n- **latency** — end-to-end latency under minimal load\n- **concurrency** — throughput vs latency at different concurrency levels\n- **sharegpt** — realistic conversational workload\n\nParallelism\n-----------\n\nSGLang's parallelism formula:\n\n.. code-block:: text\n\n    Total GPUs = TP × DP × PP\n\n**EP is a subdivision of TP**, not a separate multiplier. When using ``--ep N``,\nthe TP GPUs are divided into N expert-parallel groups.\n\n.. list-table::\n   :widths: 20 10 10 10 10 40\n   :header-rows: 1\n\n   * - Config\n     - TP\n     - EP\n     - DP\n     - PP\n     - Use case\n   * - Dense, max throughput\n     - 2\n     - 1\n     - 8\n     - 1\n     - 8 replicas of TP=2\n   * - Dense, large model\n     - 8\n     - 1\n     - 2\n     - 1\n     - 2 replicas of TP=8\n   * - Dense, very large\n     - 8\n     - 1\n     - 1\n     - 2\n     - Single replica, 2-stage pipeline\n   * - MoE model\n     - 8\n     - 2\n     - 1\n     - 1\n     - Experts split into 2 groups\n   * - MoE, more EP\n     - 8\n     - 4\n     - 1\n     - 1\n     - Experts split into 4 groups\n\n**Constraints:**\n\n- Multi-node DP requires ``--enable-dp-attention``\n- EP only works with MoE models and requires ``--enable-ep``\n- ``TP`` must be divisible by ``nnodes`` for multi-node\n"
  },
  {
    "path": "src/llm/sglang/bench.sh",
    "content": "#!/usr/bin/env bash\n# SGLang serving benchmark suite\n# Usage:\n#   salloc -N1 bash bench.sh -H 10.0.128.193 -i /fsx/sglang-serve-latest.tar.gz\n#   bash bench.sh -H 10.0.128.193 -i sglang-serve:latest\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\n\n_run() {\n    if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n        srun -N1 --ntasks-per-node=1 bash -c \"$*\"\n    else\n        bash -c \"$*\"\n    fi\n}\n\nload_or_pull_image() {\n    if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n        CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json \\\n            | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n        info \"Image tag: ${CONTAINER_IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n    else\n        CONTAINER_IMAGE=\"${IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n    fi\n}\n\nlaunch_container() {\n    local cmd=\"$1\"\n    _run \"\n        docker run --rm --net=host \\\n            -v '${PWD}:${PWD}' -w '${PWD}' \\\n            -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n            --entrypoint bash '${CONTAINER_IMAGE}' \\\n            -c '${cmd}'\n    \"\n}\n\n# If sglang is not available, load image and re-exec inside container\nif ! python3 -c \"import sglang\" &>/dev/null; then\n    IMAGE=\"\" _args=(\"$@\")\n    for ((i=0; i<${#_args[@]}; i++)); do\n        [[ \"${_args[$i]}\" == \"--image\" || \"${_args[$i]}\" == \"-i\" ]] \\\n            && { IMAGE=\"${_args[$((i+1))]}\"; break; }\n    done\n    IMAGE=\"${IMAGE:-${PWD}/sglang-serve-latest.tar.gz}\"\n\n    load_or_pull_image\n    _SCRIPT=\"$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")\"\n    launch_container \"bash ${_SCRIPT} $*\"\n    exit $?\nfi\n\nHOST=\"localhost\"\nPORT=\"30000\"\nMODEL=\"\"\nSEED=\"42\"\nRESULT_DIR=\"./results\"\nTYPES=\"throughput,prefill,decode,latency,concurrency,sharegpt\"\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS]\n\nOptions:\n  -H, --host HOST         Server host (default: $HOST)\n  -p, --port PORT         Server port (default: $PORT)\n  -m, --model MODEL       Model name (auto-detected if omitted)\n  -t, --type TYPES        Comma-separated tests (default: all)\n                          Available: throughput,prefill,decode,latency,concurrency,sharegpt\n  -o, --output DIR        Result directory (default: $RESULT_DIR)\n  -i, --image IMAGE       Docker image or tarball (default: ./sglang-serve-latest.tar.gz)\n  -h, --help              Show this help\n\nExamples:\n  $(basename \"$0\") -H 10.0.128.193\n  $(basename \"$0\") -H 10.0.128.193 --type throughput,prefill\n  $(basename \"$0\") -H 10.0.128.193 --type latency -m Qwen/Qwen2.5-14B-Instruct\nEOF\n}\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -H|--host)  HOST=\"$2\"; shift 2 ;;\n        -p|--port)  PORT=\"$2\"; shift 2 ;;\n        -m|--model) MODEL=\"$2\"; shift 2 ;;\n        -t|--type)  TYPES=\"$2\"; shift 2 ;;\n        -o|--output) RESULT_DIR=\"$2\"; shift 2 ;;\n        -i|--image)  shift 2 ;;\n        -h|--help)   usage; exit 0 ;;\n        *) echo \"Unknown option: $1\"; usage; exit 1 ;;\n    esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\nif [[ -z \"$MODEL\" ]]; then\n    MODEL=$(curl -s \"${BASE_URL}/v1/models\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['data'][0]['id'])\")\nfi\nmkdir -p \"$RESULT_DIR\"\n\nbench() {\n    local label=\"$1\"; shift\n    local outfile=\"${RESULT_DIR}/$(echo \"$label\" | tr ' /' '_').json\"\n    echo \"==> $label\"\n    python3 -m sglang.bench_serving \\\n        --backend sglang \\\n        --host \"$HOST\" \\\n        --port \"$PORT\" \\\n        --model \"$MODEL\" \\\n        --seed \"$SEED\" \\\n        --output-file \"$outfile\" \\\n        \"$@\"\n    echo \"\"\n}\n\nbench_throughput() {\n    bench \"Throughput (random 512in/256out, max rate)\" \\\n        --dataset-name random \\\n        --random-input 512 --random-output 256 \\\n        --num-prompts 100 --request-rate inf\n}\n\nbench_prefill() {\n    for len in 128 512 2048 4096 16384; do\n        bench \"Prefill TTFT (input=${len})\" \\\n            --dataset-name random \\\n            --random-input \"$len\" --random-output 1 \\\n            --num-prompts 100 --request-rate 4\n    done\n}\n\nbench_decode() {\n    for len in 128 256 512 1024; do\n        bench \"Decode ITL (output=${len})\" \\\n            --dataset-name random \\\n            --random-input 128 --random-output \"$len\" \\\n            --num-prompts 100 --request-rate 4\n    done\n}\n\nbench_latency() {\n    bench \"Latency (short 128/128, rate=1)\" \\\n        --dataset-name random \\\n        --random-input 128 --random-output 128 \\\n        --num-prompts 100 --request-rate 1\n    bench \"Latency (medium 512/256, rate=1)\" \\\n        --dataset-name random \\\n        --random-input 512 --random-output 256 \\\n        --num-prompts 100 --request-rate 1\n    bench \"Latency (long 4096/512, rate=1)\" \\\n        --dataset-name random \\\n        --random-input 4096 --random-output 512 \\\n        --num-prompts 100 --request-rate 1\n}\n\nbench_concurrency() {\n    for c in 1 4 16 64 256; do\n        bench \"Concurrency=${c} (512in/256out)\" \\\n            --dataset-name random \\\n            --random-input 512 --random-output 256 \\\n            --num-prompts 100 --request-rate inf --max-concurrency \"$c\"\n    done\n}\n\nSHAREGPT_PATH=\"${SHAREGPT_PATH:-ShareGPT_V3_unfiltered_cleaned_split.json}\"\nbench_sharegpt() {\n    if [[ ! -f \"$SHAREGPT_PATH\" ]]; then\n        echo \"Downloading ShareGPT dataset...\"\n        wget -q -O \"$SHAREGPT_PATH\" \\\n            https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\n    fi\n    bench \"ShareGPT (100 prompts, max rate)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --request-rate inf\n    bench \"ShareGPT (100 prompts, rate=4)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --request-rate 4\n}\n\nIFS=',' read -ra TESTS <<< \"$TYPES\"\nfor t in \"${TESTS[@]}\"; do\n    t=$(echo \"$t\" | xargs)\n    echo \"========================================\"\n    echo \"Running: $t\"\n    echo \"========================================\"\n    case \"$t\" in\n        throughput)  bench_throughput ;;\n        prefill)     bench_prefill ;;\n        decode)      bench_decode ;;\n        latency)     bench_latency ;;\n        concurrency) bench_concurrency ;;\n        sharegpt)    bench_sharegpt ;;\n        *) echo \"Unknown test: $t\"; exit 1 ;;\n    esac\ndone\n"
  },
  {
    "path": "src/llm/sglang/run.sbatch",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nGPUS=\"${GPUS:-all}\"\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr()  { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nCONTAINER_MOUNT=\"/fsx\"\nWORKSPACE=\"$PWD\"\nFORCE_PULL=false\nPROFILE_DIR=\"\"\nSERVE_ARGS=()\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    --image)            IMAGE=\"$2\"; shift 2 ;;\n    --container-mount)  CONTAINER_MOUNT=\"$2\"; shift 2 ;;\n    --workspace|-w)     WORKSPACE=\"$2\"; shift 2 ;;\n    --force|-f)         FORCE_PULL=true; shift ;;\n    --profile)          PROFILE_DIR=\"${PWD}/sglang_profile\"; shift ;;\n    *)                  SERVE_ARGS+=(\"$1\"); shift ;;\n  esac\ndone\nIMAGE=\"${IMAGE:-${WORKSPACE}/sglang-serve-latest.tar.gz}\"\nLOGDIR=\"${WORKSPACE}/logs\"\n[[ -n \"${PROFILE_DIR}\" ]] && mkdir -p \"${PROFILE_DIR}\"\n\nSERVE_ARGS_STR=$(printf '%q ' \"${SERVE_ARGS[@]+\"${SERVE_ARGS[@]}\"}\")\n\n_peek_arg() {\n  local short=\"$1\" long=\"$2\" default=\"$3\"\n  local i=0\n  while (( i < ${#SERVE_ARGS[@]} )); do\n    if [[ \"${SERVE_ARGS[$i]}\" == \"$short\" || \"${SERVE_ARGS[$i]}\" == \"$long\" ]]; then\n      echo \"${SERVE_ARGS[$((i+1))]}\"; return\n    fi\n    ((i++))\n  done\n  echo \"$default\"\n}\n\nload_or_pull_image() {\n  if [[ \"${FORCE_PULL}\" == \"true\" ]]; then\n    info \"Force pull: cleaning up existing images...\"\n    srun --ntasks-per-node=1 bash -c '\n      docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n      docker images -aq | xargs -r docker rmi -f 2>/dev/null || true\n    '\n  fi\n\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    info \"Loading Docker image from tarball...\"\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    info \"Pulling Docker image from registry...\"\n    local registry=\"${IMAGE%%/*}\"\n    local region=$(echo \"${registry}\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n    region=\"${region:-us-west-2}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${IMAGE}' &>/dev/null; then\n        aws ecr get-login-password --region '${region}' | docker login --username AWS --password-stdin '${registry}'\n        docker pull '${IMAGE}'\n      fi\n    \"\n    CONTAINER_IMAGE=\"${IMAGE}\"\n  fi\n}\n\nlaunch_container() {\n  local name=\"${1}\" cmd=\"${2}\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  local net_if=\"${GLOO_SOCKET_IFNAME:-$(ip -o -4 route show to default | awk '{print $5}' | head -1)}\"\n\n  docker run --gpus \"${GPUS}\" \\\n    --privileged -d \\\n    --name \"${name}\" \\\n    --uts=host --ipc=host --net=host \\\n    --ulimit stack=67108864 --ulimit memlock=-1 \\\n    --security-opt seccomp=unconfined \\\n    \"${devices[@]}\" \\\n    -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\" \\\n    -e NCCL_SOCKET_IFNAME=\"${net_if}\" \\\n    -e GLOO_SOCKET_IFNAME=\"${net_if}\" \\\n    -e TP_SOCKET_IFNAME=\"${net_if}\" \\\n    ${PROFILE_DIR:+-e SGLANG_TORCH_PROFILER_DIR=\"${PROFILE_DIR}\"} \\\n    --entrypoint bash \\\n    \"${CONTAINER_IMAGE:-${IMAGE}}\" \\\n    -c \"${cmd}\"\n}\n\nsetup_topology() {\n  NUM_NODES=${SLURM_JOB_NUM_NODES:-1}\n  GPUS_PER_NODE=8\n  TOTAL_GPUS=$((NUM_NODES * GPUS_PER_NODE))\n\n  readarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\n  HEAD_NODE=${NODES[0]}\n  HEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n  DIST_PORT=$((25000 + (SLURM_JOB_ID % 1000)))\n\n  mkdir -p \"${LOGDIR}\"\n\n  info \"========================================\"\n  info \"SGLang Server\"\n  info \"========================================\"\n  info \"Image: ${IMAGE}\"\n  info \"Nodes: ${NUM_NODES}, Head: ${HEAD_NODE} (${HEAD_IP}), GPUs: ${TOTAL_GPUS}\"\n  info \"SERVE_ARGS: ${SERVE_ARGS[*]+\"${SERVE_ARGS[*]}\"}\"\n  info \"========================================\"\n}\n\ncleanup() {\n  info \"Cleaning up containers...\"\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}/sglang_server_${SLURM_JOB_ID}.log\"\n}\n\nstart_sglang() {\n  local logfile=\"${LOGDIR}/sglang_server_${SLURM_JOB_ID}.log\"\n\n  # Launch containers on all nodes\n  for i in $(seq 0 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      CONTAINER_IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' PROFILE_DIR='${PROFILE_DIR}' launch_container sglang-node-${i} 'sleep infinity'\n    \" &\n  done\n  wait\n  sleep 3\n\n  # Only inject host/port and multi-node flags; SGLang owns parallelism (TP/PP/DP/EP)\n  local extra=\"--host 0.0.0.0 --port 30000\"\n\n  if [[ \"$NUM_NODES\" -gt 1 ]]; then\n    extra+=\" --nnodes ${NUM_NODES} --dist-init-addr ${HEAD_IP}:${DIST_PORT}\"\n\n    # Start worker nodes first (rank 1..N-1)\n    for i in $(seq 1 $((NUM_NODES - 1))); do\n      srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n        docker exec -d sglang-node-${i} bash -c 'python3 -m sglang.launch_server ${SERVE_ARGS_STR} ${extra} --node-rank ${i} 2>&1 | tee ${logfile}.node${i}'\n      \"\n    done\n  fi\n\n  # Start head node (rank 0) — this one serves the API\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec -d sglang-node-0 bash -c 'python3 -m sglang.launch_server ${SERVE_ARGS_STR} ${extra} --node-rank 0 2>&1 | tee ${logfile}'\n  \"\n}\n\nwait_for_server() {\n  info \"Waiting for SGLang server at ${HEAD_IP}:30000...\"\n  for _ in {1..360}; do\n    if srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:30000/health\" &>/dev/null &&\n       srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:30000/v1/models | grep -q '\\\"id\\\"'\" &>/dev/null; then\n      info \"Server ready at ${HEAD_IP}:30000\"\n      return 0\n    fi\n    sleep 10\n  done\n  err \"Timeout waiting for server\"; return 1\n}\n\nsetup_topology\ntrap cleanup EXIT\ncleanup\n\nLOGFILE=\"${LOGDIR}/sglang_server_${SLURM_JOB_ID}.log\"\n\nload_or_pull_image\nstart_sglang\n\ntail -f \"${LOGFILE}\" 2>/dev/null &\n\nwait_for_server || exit 1\n\ninfo \"SGLang serving on ${HEAD_IP}:30000 — Ctrl+C or scancel to stop\"\ninfo \"Logs: ${LOGFILE}\"\nsleep infinity\n"
  },
  {
    "path": "src/llm/sglang/test.sh",
    "content": "#!/usr/bin/env bash\n# SGLang API test script\n\nset -uo pipefail\n\nHOST=\"localhost\"\nPORT=\"30000\"\nMODEL=\"\"\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    -h|--help)  echo \"Usage: $0 [-H host] [-p port] [-m model]\"; exit 0 ;;\n    -H|--host)  HOST=\"$2\"; shift 2 ;;\n    -p|--port)  PORT=\"$2\"; shift 2 ;;\n    -m|--model) MODEL=\"$2\"; shift 2 ;;\n    *) echo \"Unknown option: $1\"; exit 1 ;;\n  esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\n\n# Auto-detect model from server if not specified\nif [[ -z \"$MODEL\" ]]; then\n  MODEL=$(curl -sf \"${BASE_URL}/v1/models\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['data'][0]['id'])\" 2>/dev/null)\n  if [[ -z \"$MODEL\" ]]; then\n    echo \"ERROR: Cannot detect model. Is the server running at ${BASE_URL}?\"\n    exit 1\n  fi\nfi\n\nPASS=0\nFAIL=0\n\necho \"Testing SGLang server at ${BASE_URL}\"\necho \"Model: ${MODEL}\"\necho \"========================================\"\n\ntest_endpoint() {\n  local name=\"$1\" cmd=\"$2\"\n  echo -ne \"\\n${name}... \"\n  if eval \"$cmd\" 2>&1; then\n    ((PASS++))\n  else\n    ((FAIL++))\n  fi\n}\n\ntest_endpoint \"[1/10] List models\" \\\n  \"curl -sf '${BASE_URL}/v1/models' | jq -e '.data'\"\n\ntest_endpoint \"[2/10] Basic completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[3/10] Batch completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": [\\\"Once\\\", \\\"In\\\"], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[4/10] Chat completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[5/10] Sampling parameters\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10, \\\"temperature\\\": 0.9, \\\"top_p\\\": 0.95}' | jq -e '.choices'\"\n\ntest_endpoint \"[6/10] Streaming\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"stream\\\": true, \\\"max_tokens\\\": 10}' | grep -q 'data:'\"\n\ntest_endpoint \"[7/10] Logprobs\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 5, \\\"logprobs\\\": 5}' | jq -e '.choices[0].logprobs'\"\n\ntest_endpoint \"[8/10] Stop sequences\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"1.\\\", \\\"max_tokens\\\": 20, \\\"stop\\\": [\\\"3.\\\"]}' | jq -e '.choices'\"\n\ntest_endpoint \"[9/10] Native generate\" \\\n  \"curl -sf -X POST '${BASE_URL}/generate' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"text\\\": \\\"Hello\\\", \\\"sampling_params\\\": {\\\"max_new_tokens\\\": 10}}' | jq -e '.text'\"\n\ntest_endpoint \"[10/10] Health check\" \\\n  \"curl -sf '${BASE_URL}/health' | jq -e '.'\"\n\necho -e \"\\n========================================\"\necho \"Results: ${PASS} passed, ${FAIL} failed\"\n[[ $FAIL -gt 0 ]] && exit 1\n"
  },
  {
    "path": "src/llm/tensorrt-llm/Dockerfile",
    "content": "ARG TRTLLM_VERSION=1.1.0\nARG CUDA_VERSION=13.0.0\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.46.0\nARG NCCL_VERSION=v2.29.2-1\n\nFROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION\nARG EFA_INSTALLER_VERSION\nARG NCCL_VERSION\nARG TRTLLM_VERSION\n\n# Prevent interactive prompts\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=UTC\n\n# Update and remove conflicting packages\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils \\\n    libibverbs-dev \\\n    libibverbs1 \\\n    libmlx5-1 \\\n    libnccl2 \\\n    libnccl-dev\n\n# Clean up existing MPI installations\nRUN rm -rf /opt/hpcx \\\n    && rm -rf /usr/local/mpi \\\n    && rm -f /etc/ld.so.conf.d/hpcx.conf \\\n    && ldconfig\n\nENV OPAL_PREFIX=\n\n# Install build dependencies\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    apt-utils \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    check \\\n    cmake \\\n    curl \\\n    debhelper \\\n    devscripts \\\n    git \\\n    gcc \\\n    gdb \\\n    kmod \\\n    libnuma-dev \\\n    libsubunit-dev \\\n    libtool \\\n    openssh-client \\\n    openssh-server \\\n    pkg-config \\\n    python3 \\\n    python3-dev \\\n    python3-pip \\\n    vim \\\n    wget \\\n    ninja-build \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Remove cuda-compat if present\nRUN apt-get purge -y cuda-compat-* || true\n\n# Configure SSH\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \" UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\n# Set library paths\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:/opt/gdrcopy/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin:/opt/amazon/efa/bin:/opt/gdrcopy/bin:/usr/bin:/usr/local/bin:$PATH\n\n# Remove PEP 668 restriction and install packages\nRUN rm -f /usr/lib/python*/EXTERNALLY-MANAGED \\\n    && pip3 install --no-cache-dir awscli nvidia-ml-py Cython\n\n# Install GDRCopy\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LIBRARY_PATH=/opt/gdrcopy/lib:${LIBRARY_PATH:-}\nENV CPATH=/opt/gdrcopy/include\n\n# Install EFA dependencies\nRUN apt-get update -y && apt-get install -y --no-install-recommends \\\n    pciutils \\\n    environment-modules \\\n    tcl \\\n    libnl-3-200 \\\n    libnl-3-dev \\\n    libnl-route-3-200 \\\n    libnl-route-3-dev \\\n    udev \\\n    dmidecode \\\n    ethtool \\\n    iproute2 \\\n    libevent-core-2.1-7t64 \\\n    libevent-pthreads-2.1-7t64 \\\n    libhwloc15 \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Install EFA\nRUN cd /tmp \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify \\\n    && rm -rf /tmp/aws-efa-installer*\n\n# Install NCCL\nRUN git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git /tmp/nccl \\\n    && cd /tmp/nccl \\\n    && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n    NVCC_GENCODE=\"-gencode=arch=compute_90,code=sm_90\" \\\n    && mkdir -p /opt/nccl/build/lib \\\n    && cp -r build/lib/* /opt/nccl/build/lib/ \\\n    && cp -r build/include /opt/nccl/build/ \\\n    && rm -rf /tmp/nccl\n\n# OpenMPI settings\nENV OMPI_MCA_pml=^ucx\nENV OMPI_MCA_btl=tcp,self\nENV OMPI_MCA_btl_tcp_if_exclude=lo,docker0,veth_def_agent\nENV OPAL_PREFIX=/opt/amazon/openmpi\nENV PMIX_MCA_gds=hash\n\n# NCCL settings\nENV NCCL_DEBUG=INFO\nENV NCCL_SOCKET_IFNAME=^docker,lo,veth\nENV NCCL_P2P_NET_CHUNKSIZE=524288\nENV NCCL_BUFFSIZE=8388608\nENV NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\nENV LD_PRELOAD=/opt/nccl/build/lib/libnccl.so\n\n# EFA settings\nENV FI_PROVIDER=efa\nENV FI_EFA_USE_DEVICE_RDMA=1\nENV FI_EFA_FORK_SAFE=1\nENV RDMAV_FORK_SAFE=1\n\n# Install TensorRT-LLM\n# urllib3 is installed by debian so pip cannot uninstall it; ignore it\nRUN pip3 install --no-cache-dir --ignore-installed urllib3 tensorrt-llm==${TRTLLM_VERSION} \\\n    && pip3 install --no-cache-dir --force-reinstall nvidia-ml-py \\\n    && pip3 uninstall -y pynvml 2>/dev/null || true\n\n# Install Nsight Systems for profiling\nRUN apt-get update -y && apt-get install -y --no-install-recommends gnupg \\\n    && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/3bf863cc.pub \\\n    && echo \"deb https://developer.download.nvidia.com/devtools/repos/ubuntu2404/$(dpkg --print-architecture) /\" \\\n       > /etc/apt/sources.list.d/nvidia-devtools.list \\\n    && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F60F4B3D7FA2AF80 \\\n    && apt-get update -y \\\n    && apt-get install -y --no-install-recommends nsight-systems-cli \\\n    && rm -rf /var/lib/apt/lists/*\n\nWORKDIR /workspace\n"
  },
  {
    "path": "src/llm/tensorrt-llm/Makefile",
    "content": ".PHONY: help docker sqush save load serve test bench clean\n\n.DEFAULT_GOAL := help\n\nhelp:\n\t@echo \"TensorRT-LLM Serving Makefile\"\n\t@echo \"\"\n\t@echo \"Build targets:\"\n\t@echo \"  docker              Build Docker image\"\n\t@echo \"  sqush               Build Enroot sqsh file\"\n\t@echo \"  save                Save Docker image to tar.gz\"\n\t@echo \"  load                Load Docker image from tar.gz\"\n\t@echo \"\"\n\t@echo \"Run targets:\"\n\t@echo \"  serve               Launch TensorRT-LLM server\"\n\t@echo \"  test                Test API endpoints\"\n\t@echo \"  bench               Run benchmarks (HOST=ip)\"\n\t@echo \"\"\n\t@echo \"Variables:\"\n\t@echo \"  MODEL=$(MODEL)\"\n\t@echo \"  PORT=$(PORT)\"\n\t@echo \"  TP=$(TP)\"\n\t@echo \"\"\n\t@echo \"Examples:\"\n\t@echo \"  make docker\"\n\t@echo \"  make serve MODEL=Qwen/Qwen2.5-14B-Instruct TP=8\"\n\t@echo \"  make test HOST=10.0.128.193\"\n\t@echo \"  make bench HOST=10.0.128.193\"\n\t@echo \"\"\n\t@echo \"Cleanup:\"\n\t@echo \"  clean               Remove containers and images\"\n\nIMAGE_NAME ?= tensorrt-llm-serve\nIMAGE_TAG ?= latest\nCONTAINER_NAME ?= trtllm-server\nMODEL ?= Qwen/Qwen2.5-7B-Instruct\nPORT ?= 8000\nTP ?= 1\nHOST ?= localhost\nBENCH_TYPE ?=\n\nDEVICES := --device=/dev/gdrdrv $(shell find /dev/infiniband -name \"uverbs*\" 2>/dev/null | sed 's/^/--device=/')\n\nDOCKER_RUN = docker run --gpus all \\\n\t--privileged \\\n\t--uts=host \\\n\t--ipc=host \\\n\t--net=host \\\n\t--ulimit stack=67108864 \\\n\t--ulimit memlock=-1 \\\n\t--security-opt seccomp=unconfined \\\n\t$(DEVICES) \\\n\t--rm \\\n\t--name $(CONTAINER_NAME) \\\n\t-v /fsx:/fsx \\\n\t--entrypoint bash \\\n\t$(IMAGE_NAME):$(IMAGE_TAG)\n\ndocker:\n\tdocker build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile .\n\nsqush: docker\n\tenroot import -o $(IMAGE_NAME)-$(IMAGE_TAG).sqsh dockerd://$(IMAGE_NAME):$(IMAGE_TAG)\n\nsave:\n\tdocker save $(IMAGE_NAME):$(IMAGE_TAG) | pigz > $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n\nload:\n\tpigz -dc $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz | docker load\n\nserve:\n\t$(DOCKER_RUN) -c 'trtllm-serve $(MODEL) --host 0.0.0.0 --port $(PORT) --tp_size $(TP)'\n\ntest:\n\t@./test.sh -H $(HOST) -p $(PORT)\n\nbench:\n\t@bash bench.sh -H $(HOST) -p $(PORT) -i $(IMAGE_NAME):$(IMAGE_TAG) $(if $(BENCH_TYPE),--type $(BENCH_TYPE))\n\nclean:\n\t-docker rm -f $(CONTAINER_NAME) 2>/dev/null || true\n\t-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).sqsh\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n"
  },
  {
    "path": "src/llm/tensorrt-llm/README.rst",
    "content": "====================\nTensorRT-LLM Serving\n====================\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis cheat sheet provides quick-reference commands for launching a TensorRT-LLM server\nin both local (single-node) and SLURM environments. It covers building the Docker image,\nrunning with different parallelism strategies, and testing the server. TensorRT-LLM v1.1.0\nuses ``trtllm-serve`` for OpenAI-compatible online serving and ``trtllm-bench`` for\nbenchmarking.\n\nFor more details, see the\n`TensorRT-LLM documentation <https://nvidia.github.io/TensorRT-LLM/>`_ and\n`GitHub repository <https://github.com/NVIDIA/TensorRT-LLM>`_.\n\nFor parallelism strategies and benchmark methodology, see the\n`LLM Serving Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-serving.rst>`_ and\n`LLM Benchmark Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-bench.rst>`_.\n\nBuild Docker Image\n------------------\n\nThe Dockerfile bundles TensorRT-LLM with EFA drivers, NCCL, and GDRCopy for\nhigh-performance inference on GPU clusters.\n\n.. code-block:: bash\n\n    # Build the Docker image\n    make docker\n\n    # Save as a compressed tarball for SLURM nodes\n    # Output: tensorrt-llm-serve-latest.tar.gz\n    make save\n\nLocal Serving (Single Node)\n---------------------------\n\nTensorRT-LLM exposes an OpenAI-compatible API on port 8000 by default via\n``trtllm-serve``.\n\n**Bare metal** — run directly (requires TensorRT-LLM installed):\n\n.. code-block:: bash\n\n    # Single GPU\n    trtllm-serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    # Tensor parallel across 8 GPUs\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --tp_size 8\n\n    # FP8 quantized model\n    trtllm-serve nvidia/Qwen3-8B-FP8\n\n**Using Docker (via Makefile)**:\n\n.. code-block:: bash\n\n    # Single GPU with default model\n    make serve MODEL=Qwen/Qwen2.5-7B-Instruct\n\n    # Tensor parallel across 8 GPUs\n    make serve MODEL=Qwen/Qwen2.5-14B-Instruct TP=8\n\n**Using the NGC container directly**:\n\n.. code-block:: bash\n\n    docker run --gpus all --rm --ipc host --net=host \\\n      --ulimit memlock=-1 --ulimit stack=67108864 \\\n      -v /fsx:/fsx \\\n      nvcr.io/nvidia/tensorrt-llm/release:1.1.0 \\\n      trtllm-serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\nSLURM Serving\n-------------\n\n``run.sbatch`` orchestrates TensorRT-LLM serving on SLURM clusters. It handles Docker\nimage distribution, container launch with EFA/GPU passthrough, and health checking.\nThe server runs until you stop it with ``Ctrl+C`` or ``scancel``.\n\n**Script flags** — consumed by the script, not passed to trtllm-serve:\n\n.. list-table::\n   :widths: 30 70\n   :header-rows: 1\n\n   * - Flag\n     - Description\n   * - ``--image PATH``\n     - Docker image tarball or registry path (default: ``$WORKSPACE/tensorrt-llm-serve-latest.tar.gz``)\n   * - ``--workspace, -w PATH``\n     - Base directory for default image and logs (default: ``$PWD``)\n   * - ``--container-mount PATH``\n     - Host path to bind-mount into containers (default: ``/fsx``)\n   * - ``--force, -f``\n     - Force remove existing containers and images before loading\n\nAll other arguments are passed directly to ``trtllm-serve``.\n\n**Basic usage**:\n\n.. code-block:: bash\n\n    # Allocate 1 node with 8 GPUs\n    salloc -N 1 --gpus-per-node=8 --exclusive\n\n    # Serve with TP=8\n    bash run.sbatch \\\n      Qwen/Qwen2.5-14B-Instruct \\\n      --tp_size 8\n\n**Custom image**:\n\n.. code-block:: bash\n\n    bash run.sbatch \\\n      --image /fsx/images/tensorrt-llm-serve-latest.tar.gz \\\n      Qwen/Qwen2.5-72B-Instruct \\\n      --tp_size 8\n\n**FP8 quantized model**:\n\n.. code-block:: bash\n\n    bash run.sbatch nvidia/Qwen3-8B-FP8\n\nTest the Server\n---------------\n\nTensorRT-LLM serves on port 8000 by default:\n\n.. code-block:: bash\n\n    # Health check\n    curl http://<HEAD_IP>:8000/health\n\n    # List models\n    curl http://<HEAD_IP>:8000/v1/models\n\n    # Chat completion (OpenAI-compatible)\n    curl -X POST http://<HEAD_IP>:8000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"model\": \"Qwen/Qwen2.5-14B-Instruct\",\n        \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],\n        \"max_tokens\": 50\n      }'\n\n    # Completions endpoint\n    curl -X POST http://<HEAD_IP>:8000/v1/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"model\": \"Qwen/Qwen2.5-14B-Instruct\",\n        \"prompt\": \"The capital of France is\",\n        \"max_tokens\": 20\n      }'\n\n    # Run the included test script\n    bash test.sh\n\n    # Test against a remote server\n    bash test.sh -H 10.0.128.193 -p 8000\n\nBenchmark\n---------\n\n``bench.sh`` measures serving performance (throughput, TTFT, ITL, latency) by sending\nrequests to a running TensorRT-LLM server. It handles Docker image loading and container\nmanagement automatically.\n\n.. code-block:: bash\n\n    # Run all benchmarks\n    bash bench.sh -H 10.0.128.193 -i tensorrt-llm-serve:latest\n\n    # Run specific benchmarks\n    bash bench.sh -H 10.0.128.193 -i tensorrt-llm-serve:latest --type throughput,prefill\n\n    # Via Makefile\n    make bench HOST=10.0.128.193\n    make bench HOST=10.0.128.193 BENCH_TYPE=throughput,prefill\n\nAvailable benchmark types:\n\n- **throughput** — peak output tokens/sec at max request rate\n- **prefill** — TTFT scaling with input length (128→4096 tokens)\n- **decode** — ITL as output length grows (128→1024 tokens)\n- **latency** — end-to-end latency under minimal load\n- **concurrency** — throughput vs latency at different concurrency levels\n- **sharegpt** — realistic conversational workload\n\nYou can also use the built-in ``trtllm-bench`` CLI for more detailed benchmarking:\n\n.. code-block:: bash\n\n    # Static benchmark (no server needed)\n    trtllm-bench --model Qwen/Qwen2.5-7B-Instruct \\\n      --dataset-type synthetic --num-requests 100\n\nParallelism\n-----------\n\nTensorRT-LLM supports multiple parallelism strategies, configured via a YAML file\n(``parallel_config.yaml``) passed with ``--config``:\n\n- **Tensor Parallel (TP)** — shards model weights across GPUs\n- **Pipeline Parallel (PP)** — distributes layers across GPUs\n- **Data Parallel (DP)** — replicates model across GPUs for different requests\n- **Expert Parallel (EP)** — distributes MoE experts across GPUs\n- **Context Parallel (CP)** — distributes long-context processing across GPUs\n- **Wide-EP** — advanced EP with load balancing for large-scale MoE (DeepSeek-V3/R1, LLaMA4, Qwen3)\n\n**Attention module** supports TP (small batches) or DP (large batches) via\n``enable_attention_dp``. **MoE FFN** supports TP, EP, or hybrid ETP where\n``moe_tensor_parallel_size × moe_expert_parallel_size = tensor_parallel_size``.\n\n.. list-table::\n   :widths: 25 55 20\n   :header-rows: 1\n\n   * - Strategy\n     - Use case\n     - Key config\n   * - TP only\n     - Dense models, small batch, memory-constrained\n     - ``tensor_parallel_size: 8``\n   * - PP\n     - Very large models that don't fit in single-node GPU memory\n     - ``pipeline_parallel_size: 2``\n   * - DP (attention)\n     - Large batch, high throughput\n     - ``enable_attention_dp: true``\n   * - EP only (MoE)\n     - MoE models with high expert count\n     - ``moe_expert_parallel_size: 8``\n   * - Hybrid ETP (MoE)\n     - Balance workload and kernel efficiency\n     - ``moe_tensor_parallel_size: 4, moe_expert_parallel_size: 2``\n   * - Wide-EP (MoE)\n     - Large-scale MoE with load balancing (hot expert replication)\n     - See ``examples/wide_ep/``\n\n**Configuration via YAML** (recommended):\n\n.. code-block:: yaml\n\n    # parallel_config.yaml\n\n    # Dense model: TP=8\n    tensor_parallel_size: 8\n\n    # Dense model: TP=8 with attention DP\n    # tensor_parallel_size: 8\n    # enable_attention_dp: true\n\n    # MoE: EP only\n    # tensor_parallel_size: 8\n    # moe_expert_parallel_size: 8\n\n    # MoE: Hybrid TP-4 × EP-2\n    # tensor_parallel_size: 8\n    # moe_tensor_parallel_size: 4\n    # moe_expert_parallel_size: 2\n\n.. code-block:: bash\n\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --config parallel_config.yaml\n\n**Quick examples via CLI flags**:\n\n.. code-block:: bash\n\n    # TP=4\n    trtllm-serve Qwen/Qwen2.5-14B-Instruct --tp_size 4\n\n    # TP=8, PP=2\n    trtllm-serve Qwen/Qwen2.5-72B-Instruct --tp_size 8 --pp_size 2\n\n    # MoE with EP\n    trtllm-serve Qwen/Qwen1.5-MoE-A2.7B --tp_size 8 --ep_size 4\n\nKey Differences from SGLang / vLLM\n-----------------------------------\n\n.. list-table::\n   :widths: 25 25 25 25\n   :header-rows: 1\n\n   * - Feature\n     - TensorRT-LLM\n     - SGLang\n     - vLLM\n   * - Serve command\n     - ``trtllm-serve <model>``\n     - ``python -m sglang.launch_server``\n     - ``vllm serve <model>``\n   * - Default port\n     - 8000\n     - 30000\n     - 8000\n   * - TP flag\n     - ``--tp_size N``\n     - ``--tp N``\n     - ``--tensor-parallel-size N``\n   * - Bench tool\n     - ``trtllm-bench``\n     - ``sglang.bench_serving``\n     - ``vllm bench``\n   * - Container\n     - ``nvcr.io/nvidia/tensorrt-llm/release``\n     - Custom build\n     - Custom build\n   * - Quantization\n     - FP8, FP4, INT4 AWQ, INT8 SQ\n     - FP8, AWQ, GPTQ\n     - FP8, AWQ, GPTQ, BitsAndBytes\n"
  },
  {
    "path": "src/llm/tensorrt-llm/bench.sh",
    "content": "#!/usr/bin/env bash\n# TensorRT-LLM serving benchmark suite\n# Uses the official benchmark_serving.py for proper TTFT/TPOT/ITL/E2EL metrics\n# Usage:\n#   salloc -N1 bash bench.sh -H 10.0.128.193 -i /fsx/tensorrt-llm-serve-latest.tar.gz\n#   bash bench.sh -H 10.0.128.193 -i tensorrt-llm-serve:latest\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\n\n_run() {\n    if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n        srun -N1 --ntasks-per-node=1 bash -c \"$*\"\n    else\n        bash -c \"$*\"\n    fi\n}\n\nload_or_pull_image() {\n    if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n        CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json \\\n            | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n        info \"Image tag: ${CONTAINER_IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n    else\n        CONTAINER_IMAGE=\"${IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n    fi\n}\n\nlaunch_container() {\n    local cmd=\"$1\"\n    _run \"\n        docker run --rm --net=host --gpus all \\\n            -v '${PWD}:${PWD}' -w '${PWD}' \\\n            -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n            --entrypoint bash '${CONTAINER_IMAGE}' \\\n            -c '${cmd}'\n    \"\n}\n\n# If tensorrt_llm package is not installed, load image and re-exec inside container\nif ! python3 -c \"import importlib.metadata; importlib.metadata.version('tensorrt-llm')\" &>/dev/null 2>&1; then\n    info \"tensorrt_llm not found, bootstrapping into container...\"\n    info \"SLURM_JOB_ID=${SLURM_JOB_ID:-<unset>} SLURM_NODELIST=${SLURM_NODELIST:-<unset>}\"\n    IMAGE=\"\" _args=(\"$@\")\n    for ((i=0; i<${#_args[@]}; i++)); do\n        [[ \"${_args[$i]}\" == \"--image\" || \"${_args[$i]}\" == \"-i\" ]] \\\n            && { IMAGE=\"${_args[$((i+1))]}\"; break; }\n    done\n    IMAGE=\"${IMAGE:-${PWD}/tensorrt-llm-serve-latest.tar.gz}\"\n    info \"IMAGE=${IMAGE}\"\n\n    load_or_pull_image\n    info \"CONTAINER_IMAGE=${CONTAINER_IMAGE}\"\n    _SCRIPT=\"$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")\"\n    info \"Launching container on: $(hostname)\"\n    launch_container \"bash ${_SCRIPT} $*\"\n    exit $?\nfi\n\nHOST=\"localhost\"\nPORT=\"8000\"\nMODEL=\"\"\nSEED=\"42\"\nRESULT_DIR=\"./results\"\nTYPES=\"throughput,prefill,decode,latency,concurrency,sharegpt\"\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS]\n\nOptions:\n  -H, --host HOST         Server host (default: $HOST)\n  -p, --port PORT         Server port (default: $PORT)\n  -m, --model MODEL       Model name (auto-detected if omitted)\n  -t, --type TYPES        Comma-separated tests (default: all)\n                          Available: throughput,prefill,decode,latency,concurrency,sharegpt\n  -o, --output DIR        Result directory (default: $RESULT_DIR)\n  -i, --image IMAGE       Docker image or tarball (default: ./tensorrt-llm-serve-latest.tar.gz)\n  -h, --help              Show this help\n\nExamples:\n  $(basename \"$0\") -H 10.0.128.193\n  $(basename \"$0\") -H 10.0.128.193 --type throughput,prefill\n  $(basename \"$0\") -H 10.0.128.193 --type latency -m Qwen/Qwen2.5-14B-Instruct\nEOF\n}\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -H|--host)  HOST=\"$2\"; shift 2 ;;\n        -p|--port)  PORT=\"$2\"; shift 2 ;;\n        -m|--model) MODEL=\"$2\"; shift 2 ;;\n        -t|--type)  TYPES=\"$2\"; shift 2 ;;\n        -o|--output) RESULT_DIR=\"$2\"; shift 2 ;;\n        -i|--image)  shift 2 ;;\n        -h|--help)   usage; exit 0 ;;\n        *) echo \"Unknown option: $1\"; usage; exit 1 ;;\n    esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\nif [[ -z \"$MODEL\" ]]; then\n    echo \"Error: --model (-m) is required for TensorRT-LLM benchmark (tokenizer loading).\"\n    echo \"Example: $(basename \"$0\") -H 10.0.128.193 -m Qwen/Qwen1.5-MoE-A2.7B\"\n    exit 1\nfi\nmkdir -p \"$RESULT_DIR\"\n\n# Official TensorRT-LLM benchmark client with TTFT/TPOT/ITL/E2EL metrics\nbench() {\n    local label=\"$1\"; shift\n    local outfile=\"${RESULT_DIR}/$(echo \"$label\" | tr ' /' '_').json\"\n    echo \"==> $label\"\n    python3 -m tensorrt_llm.serve.scripts.benchmark_serving \\\n        --backend openai \\\n        --base-url \"${BASE_URL}\" \\\n        --model \"$MODEL\" \\\n        --seed \"$SEED\" \\\n        --save-result \\\n        --result-dir \"$RESULT_DIR\" \\\n        --result-filename \"$(basename \"$outfile\")\" \\\n        \"$@\"\n    echo \"\"\n}\n\nbench_throughput() {\n    bench \"Throughput (random 512in/256out, max rate)\" \\\n        --dataset-name random --random-ids --random-prefix-len 0 \\\n        --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --ignore-eos\n}\n\nbench_prefill() {\n    for len in 128 512 1024 2048; do\n        bench \"Prefill TTFT (input=${len})\" \\\n            --dataset-name random --random-ids --random-prefix-len 0 \\\n            --random-input-len \"$len\" --random-output-len 1 \\\n            --num-prompts 100 --max-concurrency 4 --ignore-eos\n    done\n}\n\nbench_decode() {\n    for len in 128 256 512 1024; do\n        bench \"Decode ITL (output=${len})\" \\\n            --dataset-name random --random-ids --random-prefix-len 0 \\\n            --random-input-len 128 --random-output-len \"$len\" \\\n            --num-prompts 100 --max-concurrency 4 --ignore-eos\n    done\n}\n\nbench_latency() {\n    bench \"Latency (short 128/128, concurrency=1)\" \\\n        --dataset-name random --random-ids --random-prefix-len 0 \\\n        --random-input-len 128 --random-output-len 128 \\\n        --num-prompts 100 --max-concurrency 1 --ignore-eos\n    bench \"Latency (medium 512/256, concurrency=1)\" \\\n        --dataset-name random --random-ids --random-prefix-len 0 \\\n        --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --max-concurrency 1 --ignore-eos\n}\n\nbench_concurrency() {\n    for c in 1 4 16 64 256; do\n        bench \"Concurrency=${c} (512in/256out)\" \\\n            --dataset-name random --random-ids --random-prefix-len 0 \\\n            --random-input-len 512 --random-output-len 256 \\\n            --num-prompts $((c * 5)) --max-concurrency \"$c\" --ignore-eos\n    done\n}\n\nSHAREGPT_PATH=\"${SHAREGPT_PATH:-ShareGPT_V3_unfiltered_cleaned_split.json}\"\nbench_sharegpt() {\n    if [[ ! -f \"$SHAREGPT_PATH\" ]]; then\n        echo \"Downloading ShareGPT dataset...\"\n        wget -q -O \"$SHAREGPT_PATH\" \\\n            https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\n    fi\n    bench \"ShareGPT (100 prompts, max rate)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --ignore-eos\n    bench \"ShareGPT (100 prompts, concurrency=4)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --max-concurrency 4 --ignore-eos\n}\n\nIFS=',' read -ra TESTS <<< \"$TYPES\"\nfor t in \"${TESTS[@]}\"; do\n    t=$(echo \"$t\" | xargs)\n    echo \"========================================\"\n    echo \"Running: $t\"\n    echo \"========================================\"\n    case \"$t\" in\n        throughput)  bench_throughput ;;\n        prefill)     bench_prefill ;;\n        decode)      bench_decode ;;\n        latency)     bench_latency ;;\n        concurrency) bench_concurrency ;;\n        sharegpt)    bench_sharegpt ;;\n        *) echo \"Unknown test: $t\"; exit 1 ;;\n    esac\ndone\n"
  },
  {
    "path": "src/llm/tensorrt-llm/run.sbatch",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nGPUS=\"${GPUS:-all}\"\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr()  { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nCONTAINER_MOUNT=\"/fsx\"\nWORKSPACE=\"$PWD\"\nFORCE_PULL=false\nSERVE_ARGS=()\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    --image)            IMAGE=\"$2\"; shift 2 ;;\n    --container-mount)  CONTAINER_MOUNT=\"$2\"; shift 2 ;;\n    --workspace|-w)     WORKSPACE=\"$2\"; shift 2 ;;\n    --force|-f)         FORCE_PULL=true; shift ;;\n    *)                  SERVE_ARGS+=(\"$1\"); shift ;;\n  esac\ndone\nIMAGE=\"${IMAGE:-${WORKSPACE}/tensorrt-llm-serve-latest.tar.gz}\"\nLOGDIR=\"${WORKSPACE}/logs\"\n\nSERVE_ARGS_STR=$(printf '%q ' \"${SERVE_ARGS[@]+\"${SERVE_ARGS[@]}\"}\")\n\n_peek_arg() {\n  local short=\"$1\" long=\"$2\" default=\"$3\"\n  local i=0\n  while (( i < ${#SERVE_ARGS[@]} )); do\n    if [[ \"${SERVE_ARGS[$i]}\" == \"$short\" || \"${SERVE_ARGS[$i]}\" == \"$long\" ]]; then\n      echo \"${SERVE_ARGS[$((i+1))]}\"; return\n    fi\n    ((i++))\n  done\n  echo \"$default\"\n}\n\nload_or_pull_image() {\n  if [[ \"${FORCE_PULL}\" == \"true\" ]]; then\n    info \"Force pull: cleaning up existing images...\"\n    srun --ntasks-per-node=1 bash -c '\n      docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n      docker images -aq | xargs -r docker rmi -f 2>/dev/null || true\n    '\n  fi\n\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    info \"Loading Docker image from tarball...\"\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    info \"Pulling Docker image from registry...\"\n    local registry=\"${IMAGE%%/*}\"\n    local region=$(echo \"${registry}\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n    region=\"${region:-us-west-2}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${IMAGE}' &>/dev/null; then\n        aws ecr get-login-password --region '${region}' | docker login --username AWS --password-stdin '${registry}'\n        docker pull '${IMAGE}'\n      fi\n    \"\n    CONTAINER_IMAGE=\"${IMAGE}\"\n  fi\n}\n\nlaunch_container() {\n  local name=\"${1}\" cmd=\"${2}\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  local net_if=\"${GLOO_SOCKET_IFNAME:-$(ip -o -4 route show to default | awk '{print $5}' | head -1)}\"\n\n  docker run --gpus \"${GPUS}\" \\\n    --privileged -d \\\n    --name \"${name}\" \\\n    --uts=host --ipc=host --net=host \\\n    --ulimit stack=67108864 --ulimit memlock=-1 \\\n    --security-opt seccomp=unconfined \\\n    \"${devices[@]}\" \\\n    -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\" \\\n    -e NCCL_SOCKET_IFNAME=\"${net_if}\" \\\n    -e GLOO_SOCKET_IFNAME=\"${net_if}\" \\\n    -e TP_SOCKET_IFNAME=\"${net_if}\" \\\n    --entrypoint bash \\\n    \"${CONTAINER_IMAGE:-${IMAGE}}\" \\\n    -c \"${cmd}\"\n}\n\nsetup_topology() {\n  NUM_NODES=${SLURM_JOB_NUM_NODES:-1}\n  GPUS_PER_NODE=8\n  TOTAL_GPUS=$((NUM_NODES * GPUS_PER_NODE))\n\n  readarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\n  HEAD_NODE=${NODES[0]}\n  HEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n\n  mkdir -p \"${LOGDIR}\"\n\n  info \"========================================\"\n  info \"TensorRT-LLM Server\"\n  info \"========================================\"\n  info \"Image: ${IMAGE}\"\n  info \"Nodes: ${NUM_NODES}, Head: ${HEAD_NODE} (${HEAD_IP}), GPUs: ${TOTAL_GPUS}\"\n  info \"SERVE_ARGS: ${SERVE_ARGS[*]+\"${SERVE_ARGS[*]}\"}\"\n  info \"========================================\"\n}\n\ncleanup() {\n  info \"Cleaning up containers...\"\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}/trtllm_server_${SLURM_JOB_ID}.log\"\n}\n\nstart_trtllm() {\n  local logfile=\"${LOGDIR}/trtllm_server_${SLURM_JOB_ID}.log\"\n\n  # Launch container on head node\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    $(declare -f launch_container)\n    CONTAINER_IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container trtllm-node-0 'sleep infinity'\n  \"\n  sleep 3\n\n  # trtllm-serve syntax: trtllm-serve <MODEL> [OPTIONS]\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec -d trtllm-node-0 bash -c 'trtllm-serve ${SERVE_ARGS_STR} --host 0.0.0.0 --port 8000 2>&1 | tee ${logfile}'\n  \"\n}\n\nwait_for_server() {\n  info \"Waiting for TensorRT-LLM server at ${HEAD_IP}:8000...\"\n  for _ in {1..360}; do\n    if srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/health\" &>/dev/null &&\n       srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/v1/models | grep -q '\\\"id\\\"'\" &>/dev/null; then\n      info \"Server ready at ${HEAD_IP}:8000\"\n      return 0\n    fi\n    sleep 10\n  done\n  err \"Timeout waiting for server\"; return 1\n}\n\nsetup_topology\ntrap cleanup EXIT\ncleanup\n\nLOGFILE=\"${LOGDIR}/trtllm_server_${SLURM_JOB_ID}.log\"\n\nload_or_pull_image\nstart_trtllm\n\ntail -f \"${LOGFILE}\" 2>/dev/null &\n\nwait_for_server || exit 1\n\ninfo \"TensorRT-LLM serving on ${HEAD_IP}:8000 — Ctrl+C or scancel to stop\"\ninfo \"Logs: ${LOGFILE}\"\nsleep infinity\n"
  },
  {
    "path": "src/llm/tensorrt-llm/test.sh",
    "content": "#!/usr/bin/env bash\n# TensorRT-LLM API test script\n\nset -uo pipefail\n\nHOST=\"localhost\"\nPORT=\"8000\"\nMODEL=\"\"\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    -h|--help)  echo \"Usage: $0 [-H host] [-p port] [-m model]\"; exit 0 ;;\n    -H|--host)  HOST=\"$2\"; shift 2 ;;\n    -p|--port)  PORT=\"$2\"; shift 2 ;;\n    -m|--model) MODEL=\"$2\"; shift 2 ;;\n    *) echo \"Unknown option: $1\"; exit 1 ;;\n  esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\n\n# Auto-detect model from server if not specified\nif [[ -z \"$MODEL\" ]]; then\n  MODEL=$(curl -sf \"${BASE_URL}/v1/models\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['data'][0]['id'])\" 2>/dev/null)\n  if [[ -z \"$MODEL\" ]]; then\n    echo \"ERROR: Cannot detect model. Is the server running at ${BASE_URL}?\"\n    exit 1\n  fi\nfi\n\nPASS=0\nFAIL=0\n\necho \"Testing TensorRT-LLM server at ${BASE_URL}\"\necho \"Model: ${MODEL}\"\necho \"========================================\"\n\ntest_endpoint() {\n  local name=\"$1\" cmd=\"$2\"\n  echo -ne \"\\n${name}... \"\n  if eval \"$cmd\" 2>&1; then\n    ((PASS++))\n  else\n    ((FAIL++))\n  fi\n}\n\ntest_endpoint \"[1/8] Health check\" \\\n  \"curl -sf '${BASE_URL}/health' | jq -e '.'\"\n\ntest_endpoint \"[2/8] List models\" \\\n  \"curl -sf '${BASE_URL}/v1/models' | jq -e '.data'\"\n\ntest_endpoint \"[3/8] Basic completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[4/8] Chat completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[5/8] Sampling parameters\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10, \\\"temperature\\\": 0.9, \\\"top_p\\\": 0.95}' | jq -e '.choices'\"\n\ntest_endpoint \"[6/8] Streaming\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"stream\\\": true, \\\"max_tokens\\\": 10}' | grep -q 'data:'\"\n\ntest_endpoint \"[7/8] Stop sequences\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"1.\\\", \\\"max_tokens\\\": 20, \\\"stop\\\": [\\\"3.\\\"]}' | jq -e '.choices'\"\n\ntest_endpoint \"[8/8] Batch completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": [\\\"Once\\\", \\\"In\\\"], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\necho -e \"\\n========================================\"\necho \"Results: ${PASS} passed, ${FAIL} failed\"\n[[ $FAIL -gt 0 ]] && exit 1\n"
  },
  {
    "path": "src/llm/vllm/Dockerfile",
    "content": "ARG VLLM_VERSION=0.15.1\nARG CUDA_VERSION=12.8.1\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.46.0\nARG NCCL_VERSION=v2.29.2-1\nARG NVSHMEM_VERSION=v3.5.19-1\n\nFROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION\nARG EFA_INSTALLER_VERSION\nARG NCCL_VERSION\nARG NVSHMEM_VERSION\nARG VLLM_VERSION\n\n# Prevent interactive prompts\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=UTC\n\n# Update and remove conflicting packages\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils \\\n    libibverbs-dev \\\n    libibverbs1 \\\n    libmlx5-1 \\\n    libnccl2 \\\n    libnccl-dev\n\n# Clean up existing MPI installations\nRUN rm -rf /opt/hpcx \\\n    && rm -rf /usr/local/mpi \\\n    && rm -f /etc/ld.so.conf.d/hpcx.conf \\\n    && ldconfig\n\nENV OPAL_PREFIX=\n\n# Install build dependencies\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    apt-utils \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    check \\\n    cmake \\\n    curl \\\n    debhelper \\\n    devscripts \\\n    git \\\n    gcc \\\n    gdb \\\n    kmod \\\n    libsubunit-dev \\\n    libtool \\\n    openssh-client \\\n    openssh-server \\\n    pkg-config \\\n    python3 \\\n    python3-dev \\\n    python3-pip \\\n    vim \\\n    wget \\\n    ninja-build \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Remove cuda-compat if present\nRUN apt-get purge -y cuda-compat-* || true\n\n# Configure SSH\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \" UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\n# Set library paths\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:/opt/gdrcopy/lib:/opt/nvshmem/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin:/opt/amazon/efa/bin:/opt/gdrcopy/bin:/usr/bin:/usr/local/bin:$PATH\n\n# Remove PEP 668 restriction and install packages\nRUN rm -f /usr/lib/python*/EXTERNALLY-MANAGED \\\n    && pip3 install --no-cache-dir awscli nvidia-ml-py Cython\n\n# Install GDRCopy\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LIBRARY_PATH=/opt/gdrcopy/lib:${LIBRARY_PATH:-}\nENV CPATH=/opt/gdrcopy/include\n\n# Install EFA dependencies\nRUN apt-get update -y && apt-get install -y --no-install-recommends \\\n    pciutils \\\n    environment-modules \\\n    tcl \\\n    libnl-3-200 \\\n    libnl-3-dev \\\n    libnl-route-3-200 \\\n    libnl-route-3-dev \\\n    udev \\\n    dmidecode \\\n    ethtool \\\n    iproute2 \\\n    libevent-core-2.1-7t64 \\\n    libevent-pthreads-2.1-7t64 \\\n    libhwloc15 \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Install EFA\nRUN cd /tmp \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify \\\n    && rm -rf /tmp/aws-efa-installer*\n\n# Install NCCL\nRUN git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git /tmp/nccl \\\n    && cd /tmp/nccl \\\n    && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n    NVCC_GENCODE=\"-gencode=arch=compute_90,code=sm_90\" \\\n    && mkdir -p /opt/nccl/build/lib \\\n    && cp -r build/lib/* /opt/nccl/build/lib/ \\\n    && cp -r build/include /opt/nccl/build/ \\\n    && rm -rf /tmp/nccl\n\n# Install NVSHMEM\nENV NVSHMEM_DIR=/opt/nvshmem\nENV NVSHMEM_HOME=/opt/nvshmem\nRUN git clone https://github.com/NVIDIA/nvshmem.git /tmp/nvshmem \\\n    && cd /tmp/nvshmem \\\n    && git checkout ${NVSHMEM_VERSION} \\\n    && mkdir -p build \\\n    && cd build \\\n    && cmake -DNVSHMEM_PREFIX=/opt/nvshmem \\\n        -DCMAKE_CUDA_ARCHITECTURES=\"90\" \\\n        -DNVSHMEM_MPI_SUPPORT=1 \\\n        -DNVSHMEM_PMIX_SUPPORT=1 \\\n        -DNVSHMEM_LIBFABRIC_SUPPORT=1 \\\n        -DNVSHMEM_IBRC_SUPPORT=1 \\\n        -DNVSHMEM_IBGDA_SUPPORT=1 \\\n        -DNVSHMEM_USE_GDRCOPY=1 \\\n        -DNVSHMEM_BUILD_TESTS=0 \\\n        -DNVSHMEM_BUILD_EXAMPLES=0 \\\n        -DNVSHMEM_BUILD_HYDRA_LAUNCHER=0 \\\n        -DNVSHMEM_BUILD_TXZ_PACKAGE=0 \\\n        -DNVSHMEM_BUILD_PYTHON_LIB=0 \\\n        -DMPI_HOME=/opt/amazon/openmpi \\\n        -DPMIX_HOME=/opt/amazon/pmix \\\n        -DGDRCOPY_HOME=/opt/gdrcopy \\\n        -DLIBFABRIC_HOME=/opt/amazon/efa \\\n        -G Ninja .. \\\n    && ninja -j $(nproc) \\\n    && ninja install \\\n    && rm -rf /tmp/nvshmem\n\n# OpenMPI settings\nENV OMPI_MCA_pml=^ucx\nENV OMPI_MCA_btl=tcp,self\nENV OMPI_MCA_btl_tcp_if_exclude=lo,docker0,veth_def_agent\nENV OPAL_PREFIX=/opt/amazon/openmpi\nENV PMIX_MCA_gds=hash\n\n# NCCL settings\nENV NCCL_DEBUG=INFO\nENV NCCL_SOCKET_IFNAME=^docker,lo,veth\nENV NCCL_P2P_NET_CHUNKSIZE=524288\nENV NCCL_BUFFSIZE=8388608\nENV NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\nENV LD_PRELOAD=/opt/nccl/build/lib/libnccl.so\n\n# EFA settings\nENV FI_PROVIDER=efa\nENV FI_EFA_USE_DEVICE_RDMA=1\nENV FI_EFA_FORK_SAFE=1\nENV RDMAV_FORK_SAFE=1\n\n# vLLM settings\nENV VLLM_RPC_TIMEOUT=3600000\nENV VLLM_ENGINE_READY_TIMEOUT_S=3600\nENV VLLM_USE_DEEP_GEMM=1\nENV DG_JIT_CACHE_DIR=/tmp\n\n# NVSHMEM settings\nENV NVSHMEM_REMOTE_TRANSPORT=libfabric\nENV NVSHMEM_LIBFABRIC_PROVIDER=efa\nENV NVSHMEM_DISABLE_CUDA_VMM=1\n\n# Install vLLM\nRUN pip3 install --no-cache-dir vllm==${VLLM_VERSION}\n\n# Install Nsight Systems for profiling\nRUN apt-get update -y && apt-get install -y --no-install-recommends gnupg \\\n    && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/3bf863cc.pub \\\n    && echo \"deb https://developer.download.nvidia.com/devtools/repos/ubuntu2404/$(dpkg --print-architecture) /\" \\\n       > /etc/apt/sources.list.d/nvidia-devtools.list \\\n    && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F60F4B3D7FA2AF80 \\\n    && apt-get update -y \\\n    && apt-get install -y --no-install-recommends nsight-systems-cli \\\n    && rm -rf /var/lib/apt/lists/*\n\n# Install DeepGEMM (requires torch from vLLM)\nRUN git clone --recursive -b v2.1.1.post3 https://github.com/deepseek-ai/DeepGEMM.git /tmp/deepgemm \\\n    && cd /tmp/deepgemm \\\n    && python3 setup.py bdist_wheel \\\n    && pip3 install dist/*.whl \\\n    && rm -rf /tmp/deepgemm\n\n# Copy run.sh script\nCOPY run.sh /workspace/run.sh\nRUN chmod +x /workspace/run.sh\n\nWORKDIR /workspace\n"
  },
  {
    "path": "src/llm/vllm/Makefile",
    "content": ".PHONY: help docker save clean format\n\n.DEFAULT_GOAL := help\n\nIMAGE_NAME ?= vllm-serve\nIMAGE_TAG ?= latest\n\nhelp:\n\t@echo \"vLLM Makefile\"\n\t@echo \"\"\n\t@echo \"Targets:\"\n\t@echo \"  docker    Build Docker image\"\n\t@echo \"  save      Save Docker image to tar.gz\"\n\t@echo \"  format    Format shell scripts with shfmt (indent=2)\"\n\t@echo \"  clean     Remove image and tarball\"\n\t@echo \"\"\n\t@echo \"Usage:\"\n\t@echo \"  make docker && make save\"\n\t@echo \"  make format\"\n\ndocker:\n\tdocker build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile .\n\nsave:\n\tdocker save $(IMAGE_NAME):$(IMAGE_TAG) | pigz > $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n\nformat:\n\t@command -v shfmt >/dev/null 2>&1 || { echo \"shfmt not found. Install: brew install shfmt\"; exit 1; }\n\tshfmt -i 2 -w *.sh *.sbatch\n\nclean:\n\t-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n"
  },
  {
    "path": "src/llm/vllm/README.rst",
    "content": "============\nvLLM Serving\n============\n\n.. contents:: Table of Contents\n    :backlinks: none\n\nThis cheat sheet provides quick-reference commands for launching a vLLM server in both\nlocal (single-node) and SLURM (multi-node) environments. It covers building the Docker\nimage, running with different parallelism strategies, and testing the server endpoints.\n\nFor more details, see the\n`vLLM documentation <https://docs.vllm.ai/>`_ and\n`GitHub repository <https://github.com/vllm-project/vllm>`_.\n\nFor detailed explanations of tensor parallelism, pipeline parallelism, data parallelism,\nand expert parallelism, see the\nFor parallelism strategies and benchmark methodology, see the\n`LLM Serving Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-serving.rst>`_ and\n`LLM Benchmark Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-bench.rst>`_.\n\nBuild Docker Image\n------------------\n\nThe Dockerfile bundles vLLM with EFA drivers, NCCL, NVSHMEM, and GDRCopy for\nhigh-performance multi-node inference on GPU clusters. Build the image and save it\nas a compressed tarball for distribution to SLURM nodes via a shared filesystem.\n\n.. code-block:: bash\n\n    # Build the Docker image with all dependencies\n    make docker\n\n    # Save as a compressed tarball for SLURM nodes\n    # Output: vllm-serve-latest.tar.gz\n    make save\n\nLocal Serving (Single Node)\n---------------------------\n\nFor development or single-node deployments, vLLM can run directly on the host or\ninside a Docker container. The server exposes an OpenAI-compatible API on port 8000.\n\n**Bare metal** — run vLLM directly without Docker:\n\n.. code-block:: bash\n\n    # Single GPU — simplest way to serve a model\n    vllm serve Qwen/Qwen2.5-7B-Instruct --host 0.0.0.0 --port 8000\n\n    # Tensor parallel across 8 GPUs — for models too large for a single GPU\n    vllm serve Qwen/Qwen2.5-14B-Instruct --tensor-parallel-size 8\n\n**Using Docker (via Makefile)** — convenient targets for common configurations:\n\n.. code-block:: bash\n\n    # Single GPU with default model\n    make single\n\n    # Tensor parallel across 8 GPUs\n    make mp TP=8\n\n    # Pipeline parallel — split model into 2 stages, each with TP=4\n    make mp TP=4 PP=2\n\n    # Ray backend with data parallelism — 2 replicas, each using 4 GPUs\n    make ray TP=4 DP=2\n\n**Using Docker (via run.sh)** — the entrypoint script supports multiple serving modes\n(single, ray, mp, rpc) with explicit control over parallelism settings:\n\n.. code-block:: bash\n\n    # Single GPU mode\n    docker run --gpus all --rm --net=host -v /fsx:/fsx \\\n      vllm-serve:latest ./run.sh single --model Qwen/Qwen2.5-7B-Instruct\n\n    # Multiprocessing mode with tensor parallelism\n    docker run --gpus all --rm --net=host -v /fsx:/fsx \\\n      vllm-serve:latest ./run.sh mp --model Qwen/Qwen2.5-14B-Instruct --tp 8\n\nSLURM Serving (Multi-Node)\n---------------------------\n\n``run.sbatch`` orchestrates multi-node vLLM serving on SLURM clusters. It handles\nDocker image distribution, container launch with EFA/GPU passthrough, parallelism\ncomputation, and health checking. The server runs until you stop it with ``Ctrl+C``\nor ``scancel``.\n\n**Script flags** — these are consumed by the script and not passed to vLLM:\n\n.. list-table::\n   :widths: 30 70\n   :header-rows: 1\n\n   * - Flag\n     - Description\n   * - ``--image PATH``\n     - Docker image tarball or registry path (default: ``$WORKSPACE/vllm-serve-latest.tar.gz``)\n   * - ``--workspace, -w PATH``\n     - Base directory for default image and logs (default: ``$PWD``)\n   * - ``--container-mount PATH``\n     - Host path to bind-mount into containers (default: ``/fsx``)\n   * - ``--force, -f``\n     - Force remove existing containers and images before loading\n   * - ``--nsys``\n     - Enable Nsight Systems profiling (writes to ``$WORKSPACE/nsys-vllm/``)\n\nAll other arguments are passed directly to ``vllm serve`` as-is.\n\n**Basic usage** — allocate nodes with ``salloc``, then run the script. The script\nauto-detects the SLURM allocation and computes DP based on total GPUs and TP/PP:\n\n.. code-block:: bash\n\n    # Allocate 2 nodes with 8 GPUs each\n    salloc -N 2 --gpus-per-node=8 --exclusive\n\n    # Expert parallel for MoE models (TP=8, DP=2, EP=16 auto-computed)\n    bash run.sbatch \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel\n\n**Custom image** — specify a different Docker image tarball or registry path:\n\n.. code-block:: bash\n\n    bash run.sbatch \\\n      --image /fsx/images/vllm-serve-latest.tar.gz \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel\n\n**Pipeline parallel** — for large dense models that need to be split across nodes.\nThe script auto-selects the multiprocessing backend when PP > 1:\n\n.. code-block:: bash\n\n    bash run.sbatch \\\n      deepseek-ai/DeepSeek-V2-Lite \\\n      --tensor-parallel-size 8 \\\n      --pipeline-parallel-size 2\n\n**Force reload** — remove cached containers and images before loading:\n\n.. code-block:: bash\n\n    bash run.sbatch -f \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel\n\n**Backend selection** — the script supports three distributed backends. RPC is the\ndefault and works best for most cases. Ray is useful when you need its scheduling\nfeatures. Multiprocessing is auto-selected for pipeline parallelism:\n\n.. code-block:: bash\n\n    # RPC backend (default) — lightweight, best for DP + EP\n    bash run.sbatch Qwen/Qwen3-30B-A3B-FP8 --tensor-parallel-size 8\n\n    # Ray backend — uses Ray cluster for worker management\n    DP_BACKEND=ray bash run.sbatch \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel\n\n    # Multiprocessing backend — auto-selected when PP > 1\n    bash run.sbatch \\\n      deepseek-ai/DeepSeek-V2-Lite \\\n      --tensor-parallel-size 8 \\\n      --pipeline-parallel-size 2\n\nTest the Server\n---------------\n\nOnce the server is ready, it prints the head node IP address. Use standard HTTP\nrequests to interact with the OpenAI-compatible API:\n\n.. code-block:: bash\n\n    # Health check — returns 200 when server is ready\n    curl http://<HEAD_IP>:8000/health\n\n    # List available models\n    curl http://<HEAD_IP>:8000/v1/models\n\n    # Chat completion request\n    curl -X POST http://<HEAD_IP>:8000/v1/chat/completions \\\n      -H \"Content-Type: application/json\" \\\n      -d '{\n        \"model\": \"Qwen/Qwen3-30B-A3B-FP8\",\n        \"messages\": [{\"role\": \"user\", \"content\": \"Hello\"}],\n        \"max_tokens\": 50\n      }'\n\n    # Or run the included test script\n    bash test.sh\n\n    # Test against a remote server\n    bash test.sh -H 10.0.128.193\n\n    # Test with specific port and model\n    bash test.sh -H 10.0.128.193 -p 8000 -m Qwen/Qwen3-30B-A3B-FP8\n\nBenchmark\n---------\n\n``bench.sh`` measures serving performance (throughput, TTFT, ITL, latency) by sending\nrequests to a running vLLM server. It handles Docker image loading and container\nmanagement automatically.\n\n.. code-block:: bash\n\n    # Run all benchmarks\n    bash bench.sh -H 10.0.128.193 -i vllm-serve:latest\n\n    # Run specific benchmarks\n    bash bench.sh -H 10.0.128.193 -i vllm-serve:latest --type throughput,prefill\n\n    # Via Makefile\n    make bench HOST=10.0.128.193\n    make bench HOST=10.0.128.193 BENCH_TYPE=throughput,prefill\n\nSweep\n-----\n\n``sweep.sh`` runs predefined sweep suites by calling ``sweep.sbatch`` for each\nconfiguration. Each suite launches its own ``vllm serve``, sweeps a parameter, and\ncollects results. Requires GPU access.\n\n.. code-block:: bash\n\n    # All suites (rate, concurrency, input, output)\n    bash sweep.sh -m Qwen/Qwen3-0.6B\n\n    # Select specific suites\n    bash sweep.sh -m Qwen/Qwen3-30B-A3B-FP8 \\\n        -i vllm-serve:latest \\\n        --serve-cmd \"vllm serve Qwen/Qwen3-30B-A3B-FP8 -tp 8 --enable-expert-parallel\" \\\n        --type rate,input\n\n    # Via Makefile\n    make sweep\n    make sweep SWEEP_MODEL=Qwen/Qwen3-30B-A3B-FP8 SWEEP_TYPE=rate,concurrency\n\n    # Show vllm serve stdout (model loading, request logs) — very useful for debugging\n    bash sweep.sh -m Qwen/Qwen3-0.6B --show-stdout\n\n    # Custom serve command — TP=2 with expert parallel, rate sweep only\n    bash sweep.sh -m Qwen/Qwen1.5-MoE-A2.7B \\\n        --serve-cmd \"vllm serve Qwen/Qwen1.5-MoE-A2.7B -tp 2 --enable-expert-parallel\" \\\n        --type rate --show-stdout\n\nAvailable suites:\n\n- **rate** — Sweeps request rate (1, 2, 4, 8, 16, 32, inf) to find saturation point\n- **concurrency** — Sweeps concurrent requests (1–128) to find optimal batch size\n- **input** — Sweeps input length (128–16K) to measure TTFT scaling with context\n- **output** — Sweeps output length (64–2048) to measure ITL as KV cache grows\n\nFor direct control over sweep parameters, use ``sweep.sbatch`` which passes all args\nthrough to ``vllm bench sweep serve``:\n\n.. code-block:: bash\n\n    # Custom serve + bench commands\n    bash sweep.sbatch -m Qwen/Qwen3-30B-A3B-FP8 \\\n        --serve-cmd \"vllm serve Qwen/Qwen3-30B-A3B-FP8 -tp 8 --enable-expert-parallel\" \\\n        --bench-cmd \"vllm bench serve --model Qwen/Qwen3-30B-A3B-FP8 --dataset-name sharegpt\" \\\n        --bench-params results/bench_params.json --show-stdout\n\nSee the `LLM Benchmark Guide <https://github.com/crazyguitar/pysheeet/blob/master/docs/notes/llm/llm-bench.rst>`_\nfor detailed explanations of each benchmark type and metric.\n\nNotes and Limitations\n---------------------\n\n**Profiling:**\n\nvLLM supports PyTorch profiler tracing via ``--profiler-config``. The server must be\nstarted with profiling enabled, then the benchmark client triggers ``/start_profile``\nand ``/stop_profile`` endpoints via ``--profile``.\n\n.. code-block:: bash\n\n    # Server — start with profiling enabled (default writes to $PWD/vllm_profile)\n    bash run.sbatch --profile \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8\n\n    # Server — custom profiler config\n    bash run.sbatch \\\n      --profiler-config '{\"profiler\": \"torch\", \"torch_profiler_dir\": \"/fsx/traces\"}' \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8\n\n    # Client — run benchmark with profiling\n    bash bench.sh -H 10.0.128.193 --type throughput --profile\n\nView traces at https://ui.perfetto.dev/ (supports ``.gz`` files directly).\n\nSee the `vLLM Profiling Guide <https://docs.vllm.ai/en/latest/contributing/profiling/>`_\nfor more details.\n\n**Nsight Systems profiling:**\n\n``--nsys`` wraps the ``vllm serve`` command with ``nsys profile`` for GPU-level tracing\n(CUDA kernels, NVTX ranges, memory usage). Profiles are saved per-node to\n``$WORKSPACE/nsys-vllm/``. The script sends ``SIGINT`` to nsys on cleanup for graceful\nfinalization.\n\n.. code-block:: bash\n\n    # Server — enable nsys + vLLM's CUDA profiler (terminal 0)\n    bash run.sbatch --nsys \\\n      Qwen/Qwen3-30B-A3B-FP8 \\\n      --tensor-parallel-size 8 \\\n      --enable-expert-parallel \\\n      --profiler-config '{\"profiler\": \"cuda\"}'\n\n    # Client — run benchmark with profiling (terminal 1)\n    bash bench.sh -H <server-host> --type throughput --profile\n\n    # Stop server with Ctrl+C (terminal 0)\n    # Nsys will finalize profiles (~30s)\n    # Profile files: nsys-vllm/profile-node*.nsys-rep\n\nOpen ``.nsys-rep`` files with `Nsight Systems <https://developer.nvidia.com/nsight-systems>`_\nor export to JSON for custom analysis.\n\n**Parallelism constraints:**\n\n- vLLM does not support combining PP, TP, and DP simultaneously. When using PP mode,\n  DP is not available (``TOTAL_GPUS = TP × PP``).\n- EP and PP are mutually exclusive. Use EP for MoE models and PP for large dense models.\n- EP is computed automatically: ``EP = TP × DP = world_size``. As TP increases, DP\n  decreases proportionally to maintain the same total parallelism.\n\n**Formulas:**\n\n- EP mode: ``TOTAL_GPUS = DP × TP``, EP auto-computed by vLLM\n- PP mode: ``TOTAL_GPUS = TP × PP`` (no DP)\n- DP mode: ``TOTAL_GPUS = DP × TP``\n\n\nOffline Benchmarking\n---------------------\n\n``offline_bench.sh`` measures raw inference performance without API server overhead.\nUses ``torchrun`` for multi-node coordination and supports profiling with Nsight Systems.\n\n**Single GPU:**\n\n.. code-block:: bash\n\n    bash offline_bench.sh \\\n      --model meta-llama/Llama-3.1-8B \\\n      --input-len 512 --output-len 128 \\\n      --num-prompts 100\n\n**Multi-GPU with tensor parallelism:**\n\n.. code-block:: bash\n\n    salloc -N 1 bash offline_bench.sh \\\n      --model Qwen/Qwen2-57B-A14B \\\n      --tensor-parallel-size 4 --enable-expert-parallel \\\n      --input-len 1024 --output-len 256 \\\n      --num-prompts 100\n\n    # Multi-node with custom docker image\n    salloc -N 4 bash offline_bench.sh \\\n      --image \"$PWD/vllm-serve-latest.tar.gz\" \\\n      --model Qwen/Qwen2-57B-A14B \\\n      --all2all-backend allgather_reducescatter \\\n      --tensor-parallel-size 4 --enable-expert-parallel \\\n      --gpu-memory-utilization 0.8 \\\n      --input-len 2048 --output-len 512 \\\n      --num-prompts 50\n\n    # ShareGPT dataset\n    wget -O ShareGPT_V3_unfiltered_cleaned_split.json \\\n      https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\n\n    bash offline_bench.sh \\\n      --model meta-llama/Llama-3.1-8B \\\n      --dataset-path ShareGPT_V3_unfiltered_cleaned_split.json \\\n      --num-prompts 100\n\n**Nsight Systems profiling:**\n\nSimilar to the server workflow above, ``--nsys`` wraps the ``torchrun`` command (instead\nof ``vllm serve``) with ``nsys profile``. Profiles are saved per-node to\n``$WORKSPACE/nsys-offline/``.\n\n.. code-block:: bash\n\n    salloc -N 4 bash offline_bench.sh --nsys \\\n      --model Qwen/Qwen2-57B-A14B \\\n      --tensor-parallel-size 4 --enable-expert-parallel \\\n      --all2all-backend allgather_reducescatter \\\n      --input-len 2048 --output-len 512 \\\n      --num-prompts 50\n    # Profile files: nsys-offline/profile-node*.nsys-rep\n\n**VizTracer profiling (Python-level tracing):**\n\nVizTracer is a lightweight Python profiler that traces function calls without the memory\noverhead of PyTorch profiler. It works reliably with multi-node/high data-parallelism\nsetups where PyTorch profiler may cause OOM. Use VizTracer to understand Python-level\nexecution flow and identify bottlenecks in application logic.\n\n.. code-block:: bash\n\n    salloc -N 2 bash offline_bench.sh \\\n      --model Qwen/Qwen2-57B-A14B \\\n      --tensor-parallel-size 4 --enable-expert-parallel \\\n      --viztracer ./vllm-trace.json \\\n      --num-prompts 50\n    # View trace at https://ui.perfetto.dev/\n\n**Profiling comparison:**\n\n- **nsys**: GPU kernel-level profiling (CUDA operations, memory transfers, NCCL)\n- **VizTracer**: Python function-level profiling (application logic, scheduling)\n- Use nsys for GPU performance analysis, VizTracer for Python code analysis\n"
  },
  {
    "path": "src/llm/vllm/bench.sh",
    "content": "#!/usr/bin/env bash\n# vLLM serving benchmark suite\n# Usage:\n#   salloc -N1 bash bench.sh -H 10.0.128.193 -i /fsx/vllm-serve-latest.tar.gz\n#   bash bench.sh -H 10.0.128.193 -i vllm-serve:latest\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\n# Docker image helpers (mirrors run.sbatch)\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\n\n# Wrap a command with srun if inside a SLURM allocation, otherwise run directly\n_run() {\n    if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n        srun -N1 --ntasks-per-node=1 bash -c \"$*\"\n    else\n        bash -c \"$*\"\n    fi\n}\n\nload_or_pull_image() {\n    if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n        CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json \\\n            | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n        info \"Image tag: ${CONTAINER_IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n    else\n        CONTAINER_IMAGE=\"${IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n    fi\n}\n\nlaunch_container() {\n    local cmd=\"$1\"\n    _run \"\n        docker run --rm --net=host \\\n            -v '${PWD}:${PWD}' -w '${PWD}' \\\n            -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n            --entrypoint bash '${CONTAINER_IMAGE}' \\\n            -c '${cmd}'\n    \"\n}\n\n# If vllm CLI is not available, load image and re-exec inside container\nif ! command -v vllm &>/dev/null; then\n    # Pre-parse --image/-i before full arg parsing\n    IMAGE=\"\" _args=(\"$@\")\n    for ((i=0; i<${#_args[@]}; i++)); do\n        [[ \"${_args[$i]}\" == \"--image\" || \"${_args[$i]}\" == \"-i\" ]] \\\n            && { IMAGE=\"${_args[$((i+1))]}\"; break; }\n    done\n    IMAGE=\"${IMAGE:-${PWD}/vllm-serve-latest.tar.gz}\"\n\n    load_or_pull_image\n    _SCRIPT=\"$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")\"\n    launch_container \"bash ${_SCRIPT} $*\"\n    exit $?\nfi\n\nHOST=\"localhost\"\nPORT=\"8000\"\nMODEL=\"\"\nSEED=\"42\"\nRESULT_DIR=\"./results\"\nPROFILE=\"\"\nTYPES=\"throughput,prefill,decode,latency,concurrency,longctx,sharegpt,sonnet\"\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS]\n\nOptions:\n  -H, --host HOST         Server host (default: $HOST)\n  -p, --port PORT         Server port (default: $PORT)\n  -m, --model MODEL       Model name (auto-detected if omitted)\n  -t, --type TYPES        Comma-separated tests (default: all)\n                          Available: throughput,prefill,decode,latency,concurrency,longctx,sharegpt,sonnet\n  -o, --output DIR        Result directory (default: $RESULT_DIR)\n  -i, --image IMAGE       Docker image or tarball (default: ./vllm-serve-latest.tar.gz)\n  --profile               Enable PyTorch profiler (server must have --profiler-config set)\n  -h, --help              Show this help\n\nExamples:\n  $(basename \"$0\") -H 10.0.128.193\n  $(basename \"$0\") -H 10.0.128.193 --type throughput,prefill\n  $(basename \"$0\") -H 10.0.128.193 --type latency -m Qwen/Qwen3-30B-A3B-FP8\n  $(basename \"$0\") -H 10.0.128.193 --type throughput --profile\nEOF\n}\n\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        -H|--host)  HOST=\"$2\"; shift 2 ;;\n        -p|--port)  PORT=\"$2\"; shift 2 ;;\n        -m|--model) MODEL=\"$2\"; shift 2 ;;\n        -t|--type)  TYPES=\"$2\"; shift 2 ;;\n        -o|--output) RESULT_DIR=\"$2\"; shift 2 ;;\n        -i|--image)  shift 2 ;;  # consumed by preamble above\n        --profile)   PROFILE=\"--profile\"; shift ;;\n        -h|--help)   usage; exit 0 ;;\n        *) echo \"Unknown option: $1\"; usage; exit 1 ;;\n    esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\nif [[ -z \"$MODEL\" ]]; then\n    MODEL=$(curl -s \"${BASE_URL}/v1/models\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['data'][0]['id'])\")\nfi\nmkdir -p \"$RESULT_DIR\"\n\nif [[ -n \"$PROFILE\" ]]; then\n    info \"Profiling enabled (server must have --profiler-config set)\"\n    info \"View traces at https://ui.perfetto.dev/\"\nfi\n\nbench() {\n    local label=\"$1\"; shift\n    echo \"==> $label\"\n    vllm bench serve \\\n        --model \"$MODEL\" \\\n        --base-url \"$BASE_URL\" \\\n        --backend openai-chat \\\n        --endpoint /v1/chat/completions \\\n        --seed \"$SEED\" \\\n        --save-result \\\n        --result-dir \"$RESULT_DIR\" \\\n        $PROFILE \\\n        \"$@\"\n    echo \"\"\n}\n\n\n# Throughput: measures peak output tokens/sec and request throughput.\n# Uses request-rate=inf to saturate the server — all 1000 prompts are sent as fast as\n# possible, forcing the scheduler to batch aggressively. 512in/256out is a moderate\n# workload that exercises both prefill and decode phases. 1000 prompts follows the\n# GPUStack methodology for statistically stable throughput numbers.\nbench_throughput() {\n    bench \"Throughput (random 512in/256out, max rate)\" \\\n        --dataset-name random \\\n        --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --request-rate inf\n}\n\n# Prefill (TTFT): measures Time to First Token, which reflects prompt processing speed.\n# output-len=1 isolates prefill from decode — we only care how fast the model processes\n# the input. Sweeps 128→16K tokens to show how TTFT scales with context length (should\n# grow roughly linearly due to attention's O(n) compute per layer during prefill).\n# rate=4 keeps the server lightly loaded so TTFT reflects compute, not queueing.\nbench_prefill() {\n    for len in 128 512 2048 4096 16384; do\n        bench \"Prefill TTFT (input=${len})\" \\\n            --dataset-name random \\\n            --random-input-len \"$len\" --random-output-len 1 \\\n            --num-prompts 100 --request-rate 4\n    done\n}\n\n# Decode (ITL): measures Inter-Token Latency and Time Per Output Token during generation.\n# input=128 keeps prefill minimal so the benchmark focuses on autoregressive decode.\n# Sweeps 128→1024 output tokens to reveal how ITL changes as KV cache grows — longer\n# sequences increase memory pressure and may trigger preemption/swapping.\n# rate=4 avoids batching interference so ITL reflects per-request decode speed.\nbench_decode() {\n    for len in 128 256 512 1024; do\n        bench \"Decode ITL (output=${len})\" \\\n            --dataset-name random \\\n            --random-input-len 128 --random-output-len \"$len\" \\\n            --num-prompts 100 --request-rate 4\n    done\n}\n\n# Latency (E2E): measures end-to-end request latency under minimal load.\n# rate=1 ensures requests are mostly processed alone (no batching), giving a baseline\n# for best-case latency. Tests short/medium/long to show how total latency scales.\n# These numbers represent the \"single user\" experience (similar to ChatGPT-style usage\n# where one user waits for a complete response).\nbench_latency() {\n    bench \"Latency (short 128/128, rate=1)\" \\\n        --dataset-name random \\\n        --random-input-len 128 --random-output-len 128 \\\n        --num-prompts 100 --request-rate 1\n    bench \"Latency (medium 512/256, rate=1)\" \\\n        --dataset-name random \\\n        --random-input-len 512 --random-output-len 256 \\\n        --num-prompts 100 --request-rate 1\n    bench \"Latency (long 4096/512, rate=1)\" \\\n        --dataset-name random \\\n        --random-input-len 4096 --random-output-len 512 \\\n        --num-prompts 100 --request-rate 1\n}\n\n# Concurrency: finds the server's saturation point by sweeping concurrent requests.\n# request-rate=inf with max-concurrency=N caps how many requests run in parallel.\n# At low concurrency (1-4), latency is good but throughput is low (GPU underutilized).\n# At high concurrency (64-256), throughput plateaus and latency degrades (queueing).\n# The \"knee\" where throughput stops improving is the optimal operating point.\n# 500 prompts per level gives enough samples for stable percentile metrics.\nbench_concurrency() {\n    for c in 1 4 16 64 256; do\n        bench \"Concurrency=${c} (512in/256out)\" \\\n            --dataset-name random \\\n            --random-input-len 512 --random-output-len 256 \\\n            --num-prompts 100 --request-rate inf --max-concurrency \"$c\"\n    done\n}\n\n# Long context: tests behavior with very long inputs (4K→32K tokens).\n# Inspired by GPUStack's \"very long prompt\" config (32000in/100out). Long inputs stress\n# KV cache memory, attention compute, and may trigger chunked prefill. output=100 keeps\n# decode short so we focus on prefill scaling. rate=1 and fewer prompts (50) because\n# each request is expensive and we want to avoid OOM under memory pressure.\nbench_longctx() {\n    for len in 4096 16384 32000; do\n        bench \"Long context (input=${len})\" \\\n            --dataset-name random \\\n            --random-input-len \"$len\" --random-output-len 100 \\\n            --num-prompts 50 --request-rate 1\n    done\n}\n\n# ShareGPT: realistic conversational workload from real user conversations. Variable\n# input/output lengths reflecting actual usage patterns. This is the standard dataset\n# used by vLLM CI, GPUStack perf lab, and most published benchmarks. Unlike random\n# datasets, ShareGPT captures the natural distribution of short/long prompts and\n# responses, making it the best proxy for production chat traffic.\n# Ref: github.com/vllm-project/vllm/blob/main/benchmarks/README.md\n# Ref: GPUStack perf lab uses ShareGPT with 1000 prompts as primary benchmark.\n#\n# Requires: wget https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\nSHAREGPT_PATH=\"${SHAREGPT_PATH:-ShareGPT_V3_unfiltered_cleaned_split.json}\"\nbench_sharegpt() {\n    if [[ ! -f \"$SHAREGPT_PATH\" ]]; then\n        echo \"Downloading ShareGPT dataset...\"\n        wget -q -O \"$SHAREGPT_PATH\" \\\n            https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered/resolve/main/ShareGPT_V3_unfiltered_cleaned_split.json\n    fi\n    bench \"ShareGPT (1000 prompts, max rate)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --request-rate inf\n    bench \"ShareGPT (1000 prompts, rate=4)\" \\\n        --dataset-name sharegpt \\\n        --dataset-path \"$SHAREGPT_PATH\" \\\n        --num-prompts 100 --request-rate 4\n}\n\n# Sonnet: uses Shakespeare's sonnets with a shared prefix to test prefix caching.\n# All prompts share a common prefix (--sonnet-prefix-len=200 tokens of sonnet text),\n# then each request gets a unique suffix. This exercises vLLM's automatic prefix\n# caching — if enabled, the shared prefix KV cache is computed once and reused across\n# requests, dramatically reducing TTFT. Comparing sonnet results with prefix caching\n# on vs off shows the caching speedup.\n# Ref: vllm/benchmarks/datasets.py SonnetDataset\n# Ref: Default params: input=550, output=150, prefix=200\nbench_sonnet() {\n    SONNET_PATH=\"${SONNET_PATH:-sonnet.txt}\"\n    if [[ ! -f \"$SONNET_PATH\" ]]; then\n        echo \"Downloading Shakespeare sonnets...\"\n        wget -q -O \"$SONNET_PATH\" \\\n            https://raw.githubusercontent.com/vllm-project/vllm/main/benchmarks/sonnet.txt\n    fi\n    bench \"Sonnet (prefix caching, max rate)\" \\\n        --dataset-name sonnet --dataset-path \"$SONNET_PATH\" \\\n        --sonnet-input-len 550 --sonnet-output-len 150 --sonnet-prefix-len 200 \\\n        --num-prompts 100 --request-rate inf\n    bench \"Sonnet (prefix caching, rate=4)\" \\\n        --dataset-name sonnet --dataset-path \"$SONNET_PATH\" \\\n        --sonnet-input-len 550 --sonnet-output-len 150 --sonnet-prefix-len 200 \\\n        --num-prompts 100 --request-rate 4\n}\n\nIFS=',' read -ra TESTS <<< \"$TYPES\"\nfor t in \"${TESTS[@]}\"; do\n    t=$(echo \"$t\" | xargs)  # trim whitespace\n    echo \"========================================\"\n    echo \"Running: $t\"\n    echo \"========================================\"\n    case \"$t\" in\n        throughput)  bench_throughput ;;\n        prefill)     bench_prefill ;;\n        decode)      bench_decode ;;\n        latency)     bench_latency ;;\n        concurrency) bench_concurrency ;;\n        longctx)     bench_longctx ;;\n        sharegpt)    bench_sharegpt ;;\n        sonnet)      bench_sonnet ;;\n        *) echo \"Unknown test: $t\"; exit 1 ;;\n    esac\ndone\n"
  },
  {
    "path": "src/llm/vllm/offline_bench.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nOffline vLLM benchmark without API server overhead.\nBased on vllm torchrun_dp_example.py for distributed inference.\n\nUsage:\n    # Single GPU\n    python offline_bench.py --model meta-llama/Llama-3.1-8B --num-prompts 50\n\n    # Multi-GPU with tensor parallelism\n    torchrun --nproc-per-node=4 offline_bench.py \\\n        --model Qwen/Qwen2-57B-A14B \\\n        --tp-size 4 --enable-ep \\\n        --num-prompts 100\n\n    # Data parallelism\n    torchrun --nproc-per-node=8 offline_bench.py \\\n        --model meta-llama/Llama-3.1-8B \\\n        --tp-size 2 --dp-size 4 \\\n        --num-prompts 200\n\"\"\"\nimport argparse\nimport json\nimport os\nimport time\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom typing import List\nimport numpy as np\n\nfrom vllm import LLM, SamplingParams\n\n\n@contextmanager\ndef viztracer_profiler(output_file, world_rank):\n    \"\"\"VizTracer profiler context manager.\"\"\"\n    viztracer = None\n    if output_file:\n        try:\n            from viztracer import VizTracer\n\n            viztracer = VizTracer(output_file=output_file, verbose=0, log_torch=True)\n            if world_rank == 0:\n                print(f\"VizTracer profiling enabled. Output: {output_file}\")\n        except ImportError:\n            if world_rank == 0:\n                print(\"Warning: viztracer not installed (pip install viztracer)\")\n\n    try:\n        if viztracer:\n            viztracer.start()\n            if world_rank == 0:\n                print(\"VizTracer started\")\n        yield\n    finally:\n        if viztracer:\n            viztracer.stop()\n            viztracer.save()\n            if world_rank == 0:\n                print(f\"VizTracer trace saved to {output_file}\")\n\n\n@contextmanager\ndef cuda_profiler(enabled, world_rank):\n    \"\"\"CUDA profiler context manager for nsys.\"\"\"\n    profiler = None\n\n    if enabled:\n        import torch.cuda.profiler as profiler\n\n    try:\n        if enabled:\n            profiler.start()\n            if world_rank == 0:\n                print(\"CUDA profiler started for nsys\")\n        yield\n    finally:\n        if enabled and profiler:\n            profiler.stop()\n            if world_rank == 0:\n                print(\"CUDA profiler stopped\")\n\n\ndef load_sharegpt_prompts(dataset_path: str, num_prompts: int) -> List[str]:\n    \"\"\"Load prompts from ShareGPT dataset.\"\"\"\n    with open(dataset_path) as f:\n        data = json.load(f)\n\n    prompts = []\n    for item in data[:num_prompts]:\n        if \"conversations\" in item and len(item[\"conversations\"]) > 0:\n            prompts.append(item[\"conversations\"][0][\"value\"])\n\n    return prompts[:num_prompts]\n\n\ndef generate_random_prompts(num_prompts: int, input_len: int, tokenizer) -> List[str]:\n    \"\"\"Generate random prompts with specified input length.\"\"\"\n    # Use a fixed vocabulary for reproducibility\n    vocab = list(range(1000, 10000))  # Use token IDs from vocab\n    prompts = []\n\n    for _ in range(num_prompts):\n        # Generate random token IDs\n        token_ids = [vocab[i % len(vocab)] for i in range(input_len)]\n        # Decode to text\n        prompt = tokenizer.decode(token_ids)\n        prompts.append(prompt)\n\n    return prompts\n\n\ndef generate_dummy_prompts(num_prompts: int) -> List[str]:\n    \"\"\"Generate dummy prompts for testing.\"\"\"\n    base_prompts = [\n        \"Hello, my name is\",\n        \"The president of the United States is\",\n        \"The capital of France is\",\n        \"The future of AI is\",\n        \"Explain quantum computing in simple terms:\",\n        \"Write a short story about a robot:\",\n    ]\n    return [base_prompts[i % len(base_prompts)] for i in range(num_prompts)]\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description=\"Offline vLLM benchmark\")\n\n    # Model args\n    parser.add_argument(\"--model\", type=str, required=True, help=\"Model name or path\")\n    parser.add_argument(\n        \"--max-model-len\", type=int, default=None, help=\"Max model length\"\n    )\n    parser.add_argument(\n        \"--gpu-memory-utilization\",\n        type=float,\n        default=0.9,\n        help=\"GPU memory utilization\",\n    )\n\n    # Parallelism args\n    parser.add_argument(\n        \"--tensor-parallel-size\",\n        \"--tp-size\",\n        type=int,\n        default=1,\n        dest=\"tp_size\",\n        help=\"Tensor parallel size\",\n    )\n    parser.add_argument(\n        \"--pipeline-parallel-size\",\n        \"--pp-size\",\n        type=int,\n        default=1,\n        dest=\"pp_size\",\n        help=\"Pipeline parallel size\",\n    )\n    parser.add_argument(\n        \"--data-parallel-size\",\n        \"--dp-size\",\n        type=int,\n        default=1,\n        dest=\"dp_size\",\n        help=\"Data parallel size\",\n    )\n    parser.add_argument(\n        \"--enable-expert-parallel\",\n        \"--enable-ep\",\n        action=\"store_true\",\n        dest=\"enable_ep\",\n        help=\"Enable expert parallel\",\n    )\n\n    # Benchmark args\n    parser.add_argument(\"--num-prompts\", type=int, default=50, help=\"Number of prompts\")\n    parser.add_argument(\n        \"--dataset-path\", type=str, default=None, help=\"Path to ShareGPT dataset\"\n    )\n    parser.add_argument(\n        \"--input-len\",\n        type=int,\n        default=None,\n        help=\"Input length for random prompts (default: use dataset or dummy prompts)\",\n    )\n    parser.add_argument(\n        \"--output-len\",\n        type=int,\n        default=None,\n        help=\"Output length (maps to --max-tokens)\",\n    )\n    parser.add_argument(\"--max-tokens\", type=int, default=128, help=\"Max output tokens\")\n    parser.add_argument(\n        \"--temperature\", type=float, default=0.0, help=\"Sampling temperature\"\n    )\n    parser.add_argument(\"--seed\", type=int, default=0, help=\"Random seed\")\n\n    # Profiling args\n    parser.add_argument(\n        \"--nsys\",\n        type=str,\n        default=None,\n        help=\"Enable nsys profiling and save to file (e.g., ./profile.nsys-rep)\",\n    )\n    parser.add_argument(\n        \"--viztracer\",\n        type=str,\n        default=None,\n        help=\"Enable VizTracer profiling and save to file (e.g., ./vllm-trace.json)\",\n    )\n    # vLLM args\n    parser.add_argument(\n        \"--enforce-eager\", action=\"store_true\", help=\"Disable CUDA graph\"\n    )\n    parser.add_argument(\n        \"--all2all-backend\",\n        type=str,\n        default=None,\n        help=\"All-to-all backend (allgather_reducescatter, nccl)\",\n    )\n\n    return parser.parse_args()\n\n\ndef main():\n    args = parse_args()\n\n    # Map --output-len to --max-tokens\n    if args.output_len:\n        args.max_tokens = args.output_len\n\n    # Sampling params\n    sampling_params = SamplingParams(\n        temperature=args.temperature,\n        max_tokens=args.max_tokens,\n        seed=args.seed,\n        ignore_eos=args.input_len is not None,  # Ignore EOS for random prompts\n    )\n\n    # Initialize LLM first to get tokenizer\n    llm_kwargs = {\n        \"model\": args.model,\n        \"tensor_parallel_size\": args.tp_size,\n        \"pipeline_parallel_size\": args.pp_size,\n        \"data_parallel_size\": args.dp_size,\n        \"enable_expert_parallel\": args.enable_ep,\n        \"gpu_memory_utilization\": args.gpu_memory_utilization,\n        \"seed\": args.seed,\n        \"enforce_eager\": args.enforce_eager,\n        \"disable_log_stats\": False,\n    }\n\n    if args.max_model_len:\n        llm_kwargs[\"max_model_len\"] = args.max_model_len\n\n    if args.all2all_backend:\n        llm_kwargs[\"all2all_backend\"] = args.all2all_backend\n\n    # Use external launcher for multi-process\n    if args.dp_size > 1:\n        llm_kwargs[\"distributed_executor_backend\"] = \"external_launcher\"\n\n    print(f\"Initializing LLM with config: {json.dumps(llm_kwargs, indent=2)}\")\n\n    llm = LLM(**llm_kwargs)\n\n    # Load prompts\n    if args.input_len:\n        prompts = generate_random_prompts(\n            args.num_prompts, args.input_len, llm.get_tokenizer()\n        )\n    elif args.dataset_path:\n        prompts = load_sharegpt_prompts(args.dataset_path, args.num_prompts)\n    else:\n        prompts = generate_dummy_prompts(args.num_prompts)\n\n    # Get data parallel rank/size\n    dp_rank = llm.llm_engine.vllm_config.parallel_config.data_parallel_rank\n    dp_size = llm.llm_engine.vllm_config.parallel_config.data_parallel_size\n\n    # Get world rank for printing\n    import torch.distributed as dist\n\n    world_rank = dist.get_rank() if dist.is_initialized() else 0\n\n    # Distribute prompts across DP ranks\n    local_prompts = [p for i, p in enumerate(prompts) if i % dp_size == dp_rank]\n\n    # Warmup\n    if world_rank == 0:\n        print(\"Warming up...\")\n    _ = llm.generate(local_prompts[:1], sampling_params)\n\n    # Benchmark with profiling\n    if world_rank == 0:\n        print(f\"Running benchmark with {len(prompts)} total prompts...\")\n\n    with cuda_profiler(args.nsys, world_rank), viztracer_profiler(\n        args.viztracer, world_rank\n    ):\n        start = time.perf_counter()\n        outputs = llm.generate(local_prompts, sampling_params)\n        elapsed = time.perf_counter() - start\n\n    # Collect per-request metrics\n    metrics = []\n    for output in outputs:\n        num_output_tokens = len(output.outputs[0].token_ids)\n        input_tokens = len(output.prompt_token_ids)\n\n        # Check if detailed metrics are available\n        req_metrics = output.metrics\n        if req_metrics and hasattr(req_metrics, \"first_token_ts\"):\n            ttft = (req_metrics.first_token_ts - req_metrics.scheduled_ts) * 1000\n            if num_output_tokens > 1:\n                total_gen_time = (\n                    req_metrics.last_token_ts - req_metrics.first_token_ts\n                ) * 1000\n                tpot = total_gen_time / (num_output_tokens - 1)\n            else:\n                tpot = 0\n        else:\n            ttft = 0\n            tpot = 0\n\n        metrics.append(\n            {\n                \"ttft\": ttft,\n                \"tpot\": tpot,\n                \"input_tokens\": input_tokens,\n                \"output_tokens\": num_output_tokens,\n            }\n        )\n\n    # Aggregate stats\n    total_input = sum(m[\"input_tokens\"] for m in metrics)\n    total_output = sum(m[\"output_tokens\"] for m in metrics)\n    ttfts = [m[\"ttft\"] for m in metrics]\n    tpots = [m[\"tpot\"] for m in metrics if m[\"tpot\"] > 0]\n\n    # Aggregate across DP ranks\n    if dp_size > 1:\n        import torch.distributed as dist\n\n        # Collect stats from all ranks\n        local_stats = [\n            len(metrics),\n            elapsed,\n            total_input,\n            total_output,\n            np.mean(ttfts) if ttfts else 0,\n            np.median(ttfts) if ttfts else 0,\n            np.percentile(ttfts, 99) if ttfts else 0,\n            np.mean(tpots) if tpots else 0,\n            np.median(tpots) if tpots else 0,\n            np.percentile(tpots, 99) if tpots else 0,\n        ]\n\n        # Gather from all world ranks\n        world_size = dist.get_world_size()\n        world_rank = dist.get_rank()\n        all_stats = [None] * world_size\n        dist.all_gather_object(all_stats, local_stats)\n\n        # Only print from world rank 0\n        if world_rank == 0:\n            # Filter to only DP ranks (every TP_SIZE-th rank)\n            tp_size = llm.llm_engine.vllm_config.parallel_config.tensor_parallel_size\n            dp_stats = [all_stats[i] for i in range(0, world_size, tp_size)]\n\n            # Aggregate\n            total_reqs = sum(s[0] for s in dp_stats)\n            max_time = max(s[1] for s in dp_stats)\n            total_in = sum(s[2] for s in dp_stats)\n            total_out = sum(s[3] for s in dp_stats)\n\n            # Average latency metrics\n            mean_ttft = np.mean([s[4] for s in dp_stats])\n            median_ttft = np.mean([s[5] for s in dp_stats])\n            p99_ttft = max(s[6] for s in dp_stats)\n            mean_tpot = np.mean([s[7] for s in dp_stats])\n            median_tpot = np.mean([s[8] for s in dp_stats])\n            p99_tpot = max(s[9] for s in dp_stats)\n\n            print_results(\n                total_reqs,\n                0,\n                max_time,\n                total_in,\n                total_out,\n                mean_ttft,\n                median_ttft,\n                p99_ttft,\n                mean_tpot,\n                median_tpot,\n                p99_tpot,\n            )\n\n        # Barrier to ensure all ranks finish before cleanup\n        dist.barrier()\n    else:\n        # Single process\n        print_results(\n            len(metrics),\n            0,\n            elapsed,\n            total_input,\n            total_output,\n            np.mean(ttfts) if ttfts else 0,\n            np.median(ttfts) if ttfts else 0,\n            np.percentile(ttfts, 99) if ttfts else 0,\n            np.mean(tpots) if tpots else 0,\n            np.median(tpots) if tpots else 0,\n            np.percentile(tpots, 99) if tpots else 0,\n        )\n\n\ndef print_results(\n    successful,\n    failed,\n    duration,\n    total_input,\n    total_output,\n    mean_ttft,\n    median_ttft,\n    p99_ttft,\n    mean_tpot,\n    median_tpot,\n    p99_tpot,\n):\n    \"\"\"Print results in vLLM bench serve format.\"\"\"\n    print(\"\\n============ Serving Benchmark Result ============\")\n    print(f\"Successful requests:                     {successful:<10}\")\n    print(f\"Failed requests:                         {failed:<10}\")\n    print(f\"Benchmark duration (s):                  {duration:<10.2f}\")\n    print(f\"Total input tokens:                      {total_input:<10}\")\n    print(f\"Total generated tokens:                  {total_output:<10}\")\n    print(f\"Request throughput (req/s):              {successful/duration:<10.2f}\")\n    print(f\"Output token throughput (tok/s):         {total_output/duration:<10.2f}\")\n    print(\n        f\"Total token throughput (tok/s):          {(total_input+total_output)/duration:<10.2f}\"\n    )\n    print(\"---------------Time to First Token----------------\")\n    print(f\"Mean TTFT (ms):                          {mean_ttft:<10.2f}\")\n    print(f\"Median TTFT (ms):                        {median_ttft:<10.2f}\")\n    print(f\"P99 TTFT (ms):                           {p99_ttft:<10.2f}\")\n    print(\"-----Time per Output Token (excl. 1st token)------\")\n    print(f\"Mean TPOT (ms):                          {mean_tpot:<10.2f}\")\n    print(f\"Median TPOT (ms):                        {median_tpot:<10.2f}\")\n    print(f\"P99 TPOT (ms):                           {p99_tpot:<10.2f}\")\n    print(\"---------------Inter-token Latency----------------\")\n    print(f\"Mean ITL (ms):                           {mean_tpot:<10.2f}\")\n    print(f\"Median ITL (ms):                         {median_tpot:<10.2f}\")\n    print(f\"P99 ITL (ms):                            {p99_tpot:<10.2f}\")\n    print(\"==================================================\\n\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/llm/vllm/offline_bench.sh",
    "content": "#!/usr/bin/env bash\n# Offline vLLM benchmark wrapper (no API server)\n# Usage:\n#   salloc -N1 bash offline_bench.sh --model meta-llama/Llama-3.1-8B --num-prompts 50\n#   salloc -N2 bash offline_bench.sh --model Qwen/Qwen2-57B-A14B --tp-size 4 --enable-ep\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\nIMAGE=\"${IMAGE:-${PWD}/vllm-serve-latest.tar.gz}\"\nNPROC=\"${NPROC:-}\"\n\n# Setup multi-node coordination early (before container launch)\nif [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n  NUM_NODES=${SLURM_JOB_NUM_NODES:-1}\n  readarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\n  HEAD_NODE=${NODES[0]}\n  HEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n  MASTER_PORT=$((29500 + (SLURM_JOB_ID % 1000)))\nelse\n  NUM_NODES=1\n  HEAD_IP=\"127.0.0.1\"\n  MASTER_PORT=29500\nfi\n\n_run() {\n  if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n    srun bash -c \"$*\"\n  else\n    bash -c \"$*\"\n  fi\n}\n\nload_or_pull_image() {\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json |\n      python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n  else\n    CONTAINER_IMAGE=\"${IMAGE}\"\n    _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n  fi\n}\n\nlaunch_container() {\n  local cmd=\"$1\"\n  _run \"\n        docker run --rm --gpus all --privileged --ipc=host --net=host \\\n            -v '${PWD}:${PWD}' -w '${PWD}' \\\n            -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n            --entrypoint bash '${CONTAINER_IMAGE}' \\\n            -c '${cmd}'\n    \"\n}\n\nusage() {\n  cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS] MODEL_ARGS...\n\nScript Options:\n  --nproc N         Number of processes for torchrun (auto-detect if not set)\n  --image IMAGE     Docker image or tarball (default: ./vllm-serve-latest.tar.gz)\n  --nsys            Enable nsys profiling\n  -h, --help        Show this help\n\nModel/Benchmark Args (passed to offline_bench.py):\n  --model MODEL                             Model name or path (required)\n  --num-prompts N                           Number of prompts (default: 50)\n  --tensor-parallel-size, --tp-size N       Tensor parallel size (default: 1)\n  --pipeline-parallel-size, --pp-size N     Pipeline parallel size (default: 1)\n  --data-parallel-size, --dp-size N         Data parallel size (default: 1)\n  --enable-expert-parallel, --enable-ep     Enable expert parallel\n  --all2all-backend TYPE                    All-to-all backend (allgather_reducescatter, nccl)\n  --enforce-eager                           Disable CUDA graph\n  --max-tokens N                            Max output tokens (default: 128)\n  --dataset-path PATH                       Path to ShareGPT dataset\n  --viztracer FILE                          Enable VizTracer and save to file (e.g., ./vllm-trace.json)\n\nExamples:\n  # Single GPU\n  $(basename \"$0\") --model meta-llama/Llama-3.1-8B --num-prompts 50\n\n  # Multi-GPU with TP\n  $(basename \"$0\") --model Qwen/Qwen2-57B-A14B --tp-size 4 --enable-ep --num-prompts 100\n\n  # Multi-GPU with DP\n  $(basename \"$0\") --model meta-llama/Llama-3.1-8B --tp-size 2 --dp-size 4 --num-prompts 200\n\n  # With nsys profiling\n  $(basename \"$0\") --nsys --model Qwen/Qwen2-57B-A14B --tp-size 4 --enable-ep --num-prompts 50\nEOF\n}\n\nBENCH_ARGS=()\nTP_SIZE=1\nDP_SIZE=1\nENABLE_NSYS=false\n\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n  --nproc)\n    NPROC=\"$2\"\n    shift 2\n    ;;\n  --image)\n    IMAGE=\"$2\"\n    shift 2\n    ;;\n  --nsys)\n    ENABLE_NSYS=true\n    shift\n    ;;\n  -h | --help)\n    usage\n    exit 0\n    ;;\n  *)\n    BENCH_ARGS+=(\"$1\")\n    shift\n    ;;\n  esac\ndone\n\n# Parse BENCH_ARGS to extract TP/DP sizes for nproc calculation\nfor ((i = 0; i < ${#BENCH_ARGS[@]}; i++)); do\n  case \"${BENCH_ARGS[$i]}\" in\n  --tp-size | --tensor-parallel-size)\n    TP_SIZE=\"${BENCH_ARGS[$((i + 1))]}\"\n    ;;\n  --dp-size | --data-parallel-size)\n    DP_SIZE=\"${BENCH_ARGS[$((i + 1))]}\"\n    ;;\n  esac\ndone\n\n# Auto-detect nproc if not set\nif [[ -z \"$NPROC\" ]]; then\n  # Calculate based on total GPUs across all nodes\n  GPUS_PER_NODE=8\n  TOTAL_GPUS=$((NUM_NODES * GPUS_PER_NODE))\n\n  # If DP not specified, auto-calculate\n  if [[ \"$DP_SIZE\" -eq 1 ]]; then\n    DP_SIZE=$((TOTAL_GPUS / TP_SIZE))\n    [[ $DP_SIZE -lt 1 ]] && DP_SIZE=1\n    info \"Auto-detected: DP_SIZE=${DP_SIZE} (${TOTAL_GPUS} GPUs / TP_SIZE=${TP_SIZE})\"\n    # Inject --data-parallel-size into BENCH_ARGS\n    BENCH_ARGS+=(\"--data-parallel-size\" \"$DP_SIZE\")\n  fi\n\n  # nproc-per-node = GPUs per node\n  NPROC=$GPUS_PER_NODE\nfi\n\n# If python is not available or not in container, load image and run in container\nif ! command -v python3 &>/dev/null || ! python3 -c \"import vllm\" &>/dev/null 2>&1; then\n  load_or_pull_image\n  SCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\n  # Build nsys command prefix\n  NSYS_CMD=\"\"\n  NSYS_ARG=\"\"\n  if [[ \"${ENABLE_NSYS}\" == \"true\" ]]; then\n    NSYS_DIR=\"${PWD}/nsys-offline\"\n    mkdir -p \"${NSYS_DIR}\"\n    NSYS_PATH=\"${NSYS_DIR}/profile-node${SLURM_NODEID:-0}.nsys-rep\"\n    NSYS_CMD=\"nsys profile\"\n    NSYS_CMD+=\" -t cuda,nvtx,osrt,cudnn,cublas\"\n    NSYS_CMD+=\" --trace-fork-before-exec=true\"\n    NSYS_CMD+=\" --cuda-graph-trace=node\"\n    NSYS_CMD+=\" --capture-range=cudaProfilerApi\"\n    NSYS_CMD+=\" --capture-range-end=repeat\"\n    NSYS_CMD+=\" --cuda-memory-usage=true\"\n    NSYS_CMD+=\" --cudabacktrace=true\"\n    NSYS_CMD+=\" -o ${NSYS_PATH}\"\n    NSYS_CMD+=\" --force-overwrite=true\"\n    NSYS_ARG=\"--nsys ${NSYS_PATH}\"\n  fi\n\n  TORCHRUN_CMD=\"${NSYS_CMD:+${NSYS_CMD} }torchrun \\\n        --nnodes=${NUM_NODES} \\\n        --nproc-per-node=${NPROC} \\\n        --rdzv-backend=c10d \\\n        --rdzv-endpoint=${HEAD_IP}:${MASTER_PORT} \\\n        --rdzv-id=${SLURM_JOB_ID:-12345} \\\n        ${SCRIPT_DIR}/offline_bench.py ${BENCH_ARGS[*]}\"\n\n  info \"========================================\"\n  info \"Offline vLLM Benchmark\"\n  info \"========================================\"\n  info \"Nodes: ${NUM_NODES}, Processes per node: ${NPROC}\"\n  info \"Rendezvous: ${HEAD_IP}:${MASTER_PORT}\"\n  info \"Command: ${TORCHRUN_CMD}\"\n  info \"========================================\"\n\n  launch_container \"${TORCHRUN_CMD}\"\n  exit $?\nfi\n\n# This code only runs if already inside container with vllm available\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\ninfo \"========================================\"\ninfo \"Offline vLLM Benchmark (inside container)\"\ninfo \"========================================\"\ninfo \"Nodes: ${NUM_NODES}, Processes per node: ${NPROC}\"\ninfo \"Rendezvous: ${HEAD_IP}:${MASTER_PORT}\"\ninfo \"========================================\"\n\n${NSYS_CMD} torchrun \\\n  --nnodes=\"${NUM_NODES}\" \\\n  --nproc-per-node=\"${NPROC}\" \\\n  --rdzv-backend=c10d \\\n  --rdzv-endpoint=\"${HEAD_IP}:${MASTER_PORT}\" \\\n  --rdzv-id=\"${SLURM_JOB_ID:-12345}\" \\\n  \"${SCRIPT_DIR}/offline_bench.py\" \\\n  ${NSYS_ARG} \\\n  \"${BENCH_ARGS[@]}\"\n"
  },
  {
    "path": "src/llm/vllm/run.sbatch",
    "content": "#!/bin/bash\n\nset -euo pipefail\n\nGPUS=\"${GPUS:-all}\"\nDP_BACKEND=\"${DP_BACKEND:-rpc}\"\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr()  { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nCONTAINER_MOUNT=\"/fsx\"\nWORKSPACE=\"$PWD\"\nFORCE_PULL=false\nENABLE_NSYS=false\nVLLM_STARTED=false\nSERVE_ARGS=()\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    --image)            IMAGE=\"$2\"; shift 2 ;;\n    --container-mount)  CONTAINER_MOUNT=\"$2\"; shift 2 ;;\n    --workspace|-w)     WORKSPACE=\"$2\"; shift 2 ;;\n    --force|-f)         FORCE_PULL=true; shift ;;\n    --nsys)             ENABLE_NSYS=true; shift ;;\n    --profile)          SERVE_ARGS+=(--profiler-config \"{\\\"profiler\\\": \\\"torch\\\", \\\"torch_profiler_dir\\\": \\\"${PWD}/vllm_profile\\\"}\"); shift ;;\n    --profiler-config)  SERVE_ARGS+=(--profiler-config \"$2\"); shift 2 ;;\n    *)                  SERVE_ARGS+=(\"$1\"); shift ;;\n  esac\ndone\n\n# Build nsys command prefix\nNSYS_CMD=\"\"\nif [[ \"${ENABLE_NSYS}\" == \"true\" ]]; then\n  NSYS_DIR=\"${WORKSPACE}/nsys-vllm\"\n  mkdir -p \"${NSYS_DIR}\"\n  NSYS_PATH=\"${NSYS_DIR}/profile-node${SLURM_NODEID:-0}.nsys-rep\"\n  NSYS_CMD=\"nsys profile\"\n  NSYS_CMD+=\" -t cuda,nvtx,osrt,cudnn,cublas\"\n  NSYS_CMD+=\" --trace-fork-before-exec=true\"\n  NSYS_CMD+=\" --cuda-graph-trace=node\"\n  NSYS_CMD+=\" --capture-range=cudaProfilerApi\"\n  NSYS_CMD+=\" --capture-range-end=repeat\"\n  NSYS_CMD+=\" --cuda-memory-usage=true\"\n  NSYS_CMD+=\" --cudabacktrace=true\"\n  NSYS_CMD+=\" -o ${NSYS_PATH}\"\n  NSYS_CMD+=\" --force-overwrite=true\"\nfi\nIMAGE=\"${IMAGE:-${WORKSPACE}/vllm-serve-latest.tar.gz}\"\nLOGDIR=\"${WORKSPACE}/logs\"\n\n# Build a shell-safe string from SERVE_ARGS for nested bash -c / docker exec\nSERVE_ARGS_STR=$(printf '%q ' \"${SERVE_ARGS[@]+\"${SERVE_ARGS[@]}\"}\")\n\n# Peek at SERVE_ARGS to extract values needed for topology computation\n_peek_arg() {\n  local short=\"$1\" long=\"$2\" default=\"$3\"\n  local i=0\n  while (( i < ${#SERVE_ARGS[@]} )); do\n    if [[ \"${SERVE_ARGS[$i]}\" == \"$short\" || \"${SERVE_ARGS[$i]}\" == \"$long\" ]]; then\n      echo \"${SERVE_ARGS[$((i+1))]}\"; return\n    fi\n    ((i++))\n  done\n  echo \"$default\"\n}\n_has_flag() {\n  for arg in \"${SERVE_ARGS[@]}\"; do [[ \"$arg\" == \"$1\" ]] && return 0; done\n  return 1\n}\n\nTP=$(_peek_arg \"-tp\" \"--tensor-parallel-size\" \"1\")\nPP=$(_peek_arg \"-pp\" \"--pipeline-parallel-size\" \"1\")\nENABLE_EP=$(_has_flag \"--enable-expert-parallel\" && echo \"true\" || echo \"false\")\n\nload_or_pull_image() {\n  if [[ \"${FORCE_PULL}\" == \"true\" ]]; then\n    info \"Force pull: cleaning up existing images...\"\n    srun --ntasks-per-node=1 bash -c '\n      docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n      docker images -aq | xargs -r docker rmi -f 2>/dev/null || true\n    '\n  fi\n\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    info \"Loading Docker image from tarball...\"\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    info \"Pulling Docker image from registry...\"\n    local registry=\"${IMAGE%%/*}\"\n    local region=$(echo \"${registry}\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n    region=\"${region:-us-west-2}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${IMAGE}' &>/dev/null; then\n        aws ecr get-login-password --region '${region}' | docker login --username AWS --password-stdin '${registry}'\n        docker pull '${IMAGE}'\n      fi\n    \"\n    CONTAINER_IMAGE=\"${IMAGE}\"\n  fi\n}\n\nlaunch_container() {\n  local name=\"${1}\" cmd=\"${2}\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  local net_if=\"${GLOO_SOCKET_IFNAME:-$(ip -o -4 route show to default | awk '{print $5}' | head -1)}\"\n\n  docker run --gpus \"${GPUS}\" \\\n    --privileged -d \\\n    --name \"${name}\" \\\n    --uts=host --ipc=host --net=host \\\n    --ulimit stack=67108864 --ulimit memlock=-1 \\\n    --security-opt seccomp=unconfined \\\n    \"${devices[@]}\" \\\n    -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\" \\\n    -e NCCL_SOCKET_IFNAME=\"${net_if}\" \\\n    -e GLOO_SOCKET_IFNAME=\"${net_if}\" \\\n    -e TP_SOCKET_IFNAME=\"${net_if}\" \\\n    --entrypoint bash \\\n    \"${CONTAINER_IMAGE:-${IMAGE}}\" \\\n    -c \"${cmd}\"\n}\n\nsetup_topology() {\n  NUM_NODES=${SLURM_JOB_NUM_NODES:-1}\n  GPUS_PER_NODE=8\n  TOTAL_GPUS=$((NUM_NODES * GPUS_PER_NODE))\n\n  if [[ \"$PP\" -gt 1 && \"$ENABLE_EP\" == \"true\" ]]; then\n    err \"Pipeline parallel (PP=$PP) and expert parallel cannot be enabled simultaneously\"\n    exit 1\n  fi\n\n  [[ \"$PP\" -gt 1 ]] && DP_BACKEND=\"mp\"\n\n  if [[ \"$ENABLE_EP\" == \"true\" ]]; then\n    DP=$((TOTAL_GPUS / TP))\n    if [[ $((DP * TP)) -ne $TOTAL_GPUS ]]; then\n      err \"DP($DP) * TP($TP) = $((DP * TP)) != TOTAL_GPUS($TOTAL_GPUS)\"; exit 1\n    fi\n  else\n    DP=$((TOTAL_GPUS / (TP * PP)))\n    if [[ $((DP * TP * PP)) -ne $TOTAL_GPUS ]]; then\n      err \"DP($DP) * TP($TP) * PP($PP) = $((DP * TP * PP)) != TOTAL_GPUS($TOTAL_GPUS)\"; exit 1\n    fi\n  fi\n  DP_LOCAL=$((GPUS_PER_NODE / TP))\n\n  readarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\n  HEAD_NODE=${NODES[0]}\n  HEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n\n  RAY_PORT=$((6379 + (SLURM_JOB_ID % 1000)))\n  RPC_PORT=$((13345 + (SLURM_JOB_ID % 1000)))\n\n  mkdir -p \"${LOGDIR}\"\n\n  info \"========================================\"\n  info \"vLLM Server\"\n  info \"========================================\"\n  info \"Image: ${IMAGE}\"\n  info \"Nodes: ${NUM_NODES}, Head: ${HEAD_NODE} (${HEAD_IP}), GPUs: ${TOTAL_GPUS}\"\n  info \"Parallelism: TP=${TP}, PP=${PP}, DP=${DP}, DP_LOCAL=${DP_LOCAL}, EP=${ENABLE_EP}\"\n  info \"Backend: ${DP_BACKEND}\"\n  info \"SERVE_ARGS: ${SERVE_ARGS[*]+\"${SERVE_ARGS[*]}\"}\"\n  info \"========================================\"\n}\n\nstop_nsys() {\n  [[ \"${VLLM_STARTED}\" != \"true\" ]] && return 0\n  info \"Sending SIGINT to nsys processes for graceful shutdown...\"\n  srun --ntasks-per-node=1 bash -c '\n    for cid in $(docker ps -q); do\n      docker exec \"$cid\" pkill -INT -f \"^nsys profile\" 2>/dev/null || true\n    done\n  ' 2>/dev/null || true\n}\n\nwait_for_nsys() {\n  [[ \"${VLLM_STARTED}\" != \"true\" ]] && return 0\n  info \"Waiting 30s for nsys to finalize profiles...\"\n  sleep 30\n}\n\ncleanup() {\n  info \"Cleaning up containers...\"\n  if [[ \"${ENABLE_NSYS}\" == \"true\" ]]; then\n    stop_nsys\n    wait_for_nsys\n  fi\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker stop -t 30 2>/dev/null || true\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n}\n\nstart_ray_head() {\n  info \"Starting Ray head on ${HEAD_NODE}...\"\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    $(declare -f launch_container)\n    CONTAINER_IMAGE='${CONTAINER_IMAGE:-}' IMAGE='${IMAGE}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container ray-head 'sleep infinity'\n  \"\n  sleep 5\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec ray-head ray start --head --port=${RAY_PORT} \\\n      --num-gpus=${GPUS_PER_NODE} --num-cpus=96 --disable-usage-stats\n  \"\n}\n\nstart_ray_workers() {\n  [[ \"$NUM_NODES\" -le 1 ]] && return\n  local worker_nodes=$(echo \"${NODES[@]:1}\" | tr ' ' ',')\n  info \"Starting Ray workers on ${worker_nodes}...\"\n  srun --nodes=$((NUM_NODES - 1)) --nodelist=\"${worker_nodes}\" --ntasks-per-node=1 bash -c \"\n    $(declare -f launch_container)\n    CONTAINER_IMAGE='${CONTAINER_IMAGE:-}' IMAGE='${IMAGE}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container ray-worker 'sleep infinity'\n  \"\n  sleep 5\n  srun --nodes=$((NUM_NODES - 1)) --nodelist=\"${worker_nodes}\" --ntasks-per-node=1 bash -c \"\n    docker exec ray-worker ray start --address=${HEAD_IP}:${RAY_PORT} \\\n      --num-gpus=${GPUS_PER_NODE} --num-cpus=96 --disable-usage-stats\n  \"\n}\n\nwait_for_gpus() {\n  info \"Waiting for ${TOTAL_GPUS} GPUs...\"\n  for _ in {1..120}; do\n    local gpu_count\n    gpu_count=$(srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n      docker exec ray-head python3 -c \\\n        'import ray; ray.init(address=\\\"auto\\\"); print(int(ray.cluster_resources().get(\\\"GPU\\\",0))); ray.shutdown()' \\\n        2>/dev/null\" || echo 0)\n    [[ \"$gpu_count\" -ge \"$TOTAL_GPUS\" ]] && return 0\n    sleep 5\n  done\n  err \"Timeout waiting for GPUs\"; return 1\n}\n\nstart_vllm_ray() {\n  info \"Launching vllm serve (Ray)...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n  local extra=\"--host 0.0.0.0 --port 8000 --data-parallel-backend ray --data-parallel-address ${HEAD_IP} --data-parallel-size ${DP}\"\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \\\n    \"docker exec -d ray-head bash -c '${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} 2>&1 | tee ${logfile}'\"\n}\n\nstart_vllm_mp() {\n  info \"Starting vLLM with PP (multiprocessing)...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\n  for i in $(seq 0 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-node-${i} 'sleep infinity'\n    \" &\n  done\n  wait\n\n  local extra=\"--host 0.0.0.0 --port 8000 --nnodes ${NUM_NODES} --master-addr ${HEAD_IP} --master-port 29500\"\n\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \\\n      \"docker exec -d vllm-node-${i} bash -c '${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} --node-rank ${i} --headless 2>&1 | tee ${logfile}.node${i}'\"\n  done\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \\\n    \"docker exec -d vllm-node-0 bash -c '${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} --node-rank 0 2>&1 | tee ${logfile}'\"\n}\n\n# RPC backend\nstart_vllm_rpc() {\n  info \"Starting vLLM with RPC-based DP...\"\n  local logfile=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    $(declare -f launch_container)\n    IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-head 'sleep infinity'\n  \"\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      IMAGE='${CONTAINER_IMAGE:-${IMAGE}}' GPUS='${GPUS}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' launch_container vllm-worker 'sleep infinity'\n    \"\n  done\n  sleep 3\n\n  local extra=\"--data-parallel-size ${DP} --data-parallel-size-local ${DP_LOCAL} --data-parallel-address ${HEAD_IP} --data-parallel-rpc-port ${RPC_PORT}\"\n\n  for i in $(seq 1 $((NUM_NODES - 1))); do\n    local start_rank=$((i * DP_LOCAL))\n    info \"Starting RPC worker on ${NODES[$i]} (rank ${start_rank})...\"\n    srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n      docker exec -d vllm-worker bash -c '${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} \\\n        --data-parallel-start-rank ${start_rank} --headless \\\n        2>&1 | tee ${LOGDIR}/vllm_worker_${SLURM_JOB_ID}_${i}.log'\n    \"\n  done\n\n  srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n    docker exec -d vllm-head bash -c '${NSYS_CMD} vllm serve ${SERVE_ARGS_STR} ${extra} \\\n      --host 0.0.0.0 --port 8000 \\\n      2>&1 | tee ${logfile}'\n  \"\n}\n\nwait_for_server() {\n  info \"Waiting for vLLM server at ${HEAD_IP}:8000...\"\n  for _ in {1..360}; do\n    if srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/health\" &>/dev/null &&\n       srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"curl -sf localhost:8000/v1/models | grep -q '\\\"id\\\"'\" &>/dev/null; then\n      info \"Server ready at ${HEAD_IP}:8000\"\n      return 0\n    fi\n    sleep 10\n  done\n  err \"Timeout waiting for server\"; return 1\n}\n\nsetup_topology\ntrap cleanup EXIT\ncleanup\n\nLOGFILE=\"${LOGDIR}/vllm_server_${SLURM_JOB_ID}.log\"\n\nload_or_pull_image\n\ncase \"${DP_BACKEND}\" in\n  ray)\n    start_ray_head\n    start_ray_workers\n    wait_for_gpus\n    start_vllm_ray\n    ;;\n  mp)  start_vllm_mp ;;\n  rpc) start_vllm_rpc ;;\n  *)   err \"Unknown backend: ${DP_BACKEND}\"; exit 1 ;;\nesac\n\ntail -f \"${LOGFILE}\" 2>/dev/null &\n\nwait_for_server || exit 1\nVLLM_STARTED=true\n\ninfo \"vLLM serving on ${HEAD_IP}:8000 — Ctrl+C or scancel to stop\"\ninfo \"Logs: ${LOGFILE}\"\nsleep infinity\n"
  },
  {
    "path": "src/llm/vllm/run.sh",
    "content": "#!/bin/bash\n# vLLM server launcher with multiple modes\n\nset -uo pipefail\n\nPROGRAM=\"$0\"\nMODE=\"single\"\nMODEL=\"Qwen/Qwen2.5-7B-Instruct\"\nPORT=\"8000\"\nTP=\"1\"\nPP=\"1\"\nDP=\"1\"\nHEAD_IP=\"127.0.0.1\"\nNODE_RANK=\"0\"\n\n# Log info message with timestamp\ninfo() {\n  echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"\n}\n\n# Log error message with timestamp to stderr\nerr() {\n  echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2\n}\n\n# Display usage information\nusage() {\n  cat <<EOF\nUsage:  $PROGRAM [OPTIONS] MODE\n\nModes:\n  single                    Simple single-GPU server (default)\n  ray                        Ray cluster backend\n  mp                         Multiprocessing (TP/PP)\n  rpc                        RPC backend (multi-node)\n\nOptions:\n  -h,--help                  show this help\n  -m,--model MODEL           model path or HuggingFace ID (default: Qwen/Qwen2.5-7B-Instruct)\n  -p,--port PORT             API port (default: 8000)\n  --tp SIZE                  tensor parallel size (default: 1)\n  --pp SIZE                  pipeline parallel size (default: 1)\n  --dp SIZE                  data parallel size (default: 1)\n  --head-ip IP               head node IP for RPC mode (default: 127.0.0.1)\n  --node-rank RANK           node rank for RPC mode (default: 0)\n\nExamples:\n  $PROGRAM                                    # single mode\n  $PROGRAM ray                                # Ray backend\n  $PROGRAM mp --tp 8                          # TP=8\n  $PROGRAM mp --tp 4 --pp 2                   # TP=4, PP=2\n  $PROGRAM rpc --head-ip 10.0.0.1 --node-rank 0 --tp 8 --dp 2  # head\n  $PROGRAM rpc --head-ip 10.0.0.1 --node-rank 1 --tp 8 --dp 2  # worker\n\nEOF\n}\n\n# Launch single mode\nlaunch_single() {\n  info \"Starting single mode...\"\n  vllm serve \"${MODEL}\" \\\n    --host 0.0.0.0 \\\n    --port \"${PORT}\" \\\n    --gpu-memory-utilization 0.9\n}\n\n# Launch Ray backend\nlaunch_ray() {\n  info \"Starting Ray backend (TP=${TP}, PP=${PP}, DP=${DP})...\"\n  local gpus\n  gpus=$(nvidia-smi -L | wc -l)\n  ray start --head --port=6379 --num-gpus=\"${gpus}\" --disable-usage-stats\n  trap \"ray stop\" EXIT\n\n  vllm serve \"${MODEL}\" \\\n    --host 0.0.0.0 \\\n    --port \"${PORT}\" \\\n    --tensor-parallel-size \"${TP}\" \\\n    --pipeline-parallel-size \"${PP}\" \\\n    --data-parallel-size \"${DP}\" \\\n    --data-parallel-backend ray \\\n    --gpu-memory-utilization 0.9\n}\n\n# Launch multiprocessing mode\nlaunch_mp() {\n  info \"Starting multiprocessing (TP=${TP}, PP=${PP}, DP=${DP})...\"\n  local args=(\n    --host 0.0.0.0\n    --port \"${PORT}\"\n    --gpu-memory-utilization 0.9\n  )\n  [ \"${TP}\" -gt 1 ] && args+=(--tensor-parallel-size \"${TP}\")\n  [ \"${PP}\" -gt 1 ] && args+=(--pipeline-parallel-size \"${PP}\")\n  [ \"${DP}\" -gt 1 ] && args+=(--data-parallel-size \"${DP}\")\n\n  vllm serve \"${MODEL}\" \"${args[@]}\"\n}\n\n# Launch RPC backend\nlaunch_rpc() {\n  if [ \"${NODE_RANK}\" -eq 0 ]; then\n    info \"Starting RPC head (TP=${TP}, PP=${PP}, DP=${DP})...\"\n    local args=(\n      --host 0.0.0.0\n      --port \"${PORT}\"\n      --data-parallel-address \"${HEAD_IP}\"\n      --data-parallel-rpc-port 13345\n      --gpu-memory-utilization 0.9\n    )\n    [ \"${TP}\" -gt 1 ] && args+=(--tensor-parallel-size \"${TP}\")\n    [ \"${PP}\" -gt 1 ] && args+=(--pipeline-parallel-size \"${PP}\")\n    [ \"${DP}\" -gt 1 ] && args+=(--data-parallel-size \"${DP}\")\n\n    vllm serve \"${MODEL}\" \"${args[@]}\"\n  else\n    info \"Starting RPC worker rank ${NODE_RANK} (TP=${TP}, PP=${PP}, DP=${DP})...\"\n    local args=(\n      --data-parallel-address \"${HEAD_IP}\"\n      --data-parallel-rpc-port 13345\n      --headless\n      --gpu-memory-utilization 0.9\n    )\n    [ \"${TP}\" -gt 1 ] && args+=(--tensor-parallel-size \"${TP}\")\n    [ \"${PP}\" -gt 1 ] && args+=(--pipeline-parallel-size \"${PP}\")\n    [ \"${DP}\" -gt 1 ] && args+=(--data-parallel-size \"${DP}\")\n\n    vllm serve \"${MODEL}\" \"${args[@]}\"\n  fi\n}\n\n# Launch vLLM server based on mode\nlaunch() {\n  local mode=\"$1\"\n\n  case \"$mode\" in\n    single) launch_single ;;\n    ray) launch_ray ;;\n    mp) launch_mp ;;\n    rpc) launch_rpc ;;\n    *)\n      err \"Unknown mode: $mode\"\n      usage\n      return 1\n      ;;\n  esac\n}\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    -h|-\\?|--help) usage; exit 0 ;;\n    -m|--model) MODEL=\"$2\"; shift 2 ;;\n    -p|--port) PORT=\"$2\"; shift 2 ;;\n    --tp) TP=\"$2\"; shift 2 ;;\n    --pp) PP=\"$2\"; shift 2 ;;\n    --dp) DP=\"$2\"; shift 2 ;;\n    --head-ip) HEAD_IP=\"$2\"; shift 2 ;;\n    --node-rank) NODE_RANK=\"$2\"; shift 2 ;;\n    --*=|-*) err \"unsupported option $1\"; exit 1 ;;\n    *) MODE=\"$1\"; shift ;;\n  esac\ndone\n\nif ! launch \"${MODE}\"; then\n  exit 1\nfi\n"
  },
  {
    "path": "src/llm/vllm/sweep.sbatch",
    "content": "#!/usr/bin/env bash\n# sweep.sbatch — Self-contained sweep benchmark with server lifecycle management.\n#\n# Wraps `vllm bench sweep serve` inside a GPU-enabled Docker container.\n# Follows the same CLI as the upstream sweep command — --serve-cmd and\n# --bench-cmd are passed through directly.\n#\n# Usage:\n#   bash sweep.sbatch -m Qwen/Qwen3-0.6B\n#   bash sweep.sbatch -m Qwen/Qwen3-30B-A3B-FP8 \\\n#       --serve-cmd \"vllm serve Qwen/Qwen3-30B-A3B-FP8 -tp 8 --enable-expert-parallel\" \\\n#   salloc -N2 bash sweep.sbatch -m Qwen/Qwen1.5-MoE-A2.7B \\\n#       --serve-cmd \"vllm serve Qwen/Qwen1.5-MoE-A2.7B -tp 8\"\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\nMODEL=\"Qwen/Qwen3-0.6B\"\nIMAGE=\"${PWD}/vllm-serve-latest.tar.gz\"\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\nGPUS=\"${GPUS:-all}\"\nSERVE_CMD=\"\"            # --serve-cmd (auto-generated from MODEL if empty)\nBENCH_CMD=\"\"            # --bench-cmd (auto-generated from MODEL if empty)\nSWEEP_ARGS=()           # everything else → vllm bench sweep serve\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [options] [vllm-bench-sweep-args...]\n\nOptions (consumed by sweep.sh):\n  -m, --model MODEL       Model name/path (default: $MODEL)\n  -i, --image IMAGE       Docker image tarball or registry URI\n  -h, --help              Show this help\n\nAll other args are passed through to vllm bench sweep serve:\n  --serve-cmd, --bench-cmd, --serve-params, --bench-params,\n  --resume, --show-stdout, --num-runs, --dry-run, -o, etc.\nEOF\n    exit 0\n}\n\nwhile (( \"$#\" )); do\n    case \"$1\" in\n        -m|--model)  MODEL=\"$2\"; shift 2 ;;\n        -i|--image)  IMAGE=\"$2\"; shift 2 ;;\n        -h|--help)   usage ;;\n        *)           SWEEP_ARGS+=(\"$1\"); shift ;;\n    esac\ndone\n\n_run() {\n    if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n        srun -N1 --ntasks-per-node=1 bash -c \"$*\"\n    else\n        bash -c \"$*\"\n    fi\n}\n\nload_or_pull_image() {\n    if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n        CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json \\\n            | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n        info \"Image tag: ${CONTAINER_IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n    else\n        CONTAINER_IMAGE=\"${IMAGE}\"\n        _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n    fi\n}\n\n# Passes args as an array directly to docker so --serve-cmd/--bench-cmd quoting\n# is preserved (no bash -c needed).\nlaunch_container() {\n    local devices=()\n    [[ -e /dev/gdrdrv ]] && devices+=(\"--device=/dev/gdrdrv\")\n    while IFS= read -r -d '' d; do\n        devices+=(\"--device=${d}\")\n    done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n    local docker_cmd=(\n        docker run --rm --gpus \"${GPUS}\"\n        --ipc=host --net=host --uts=host\n        --ulimit stack=67108864 --ulimit memlock=-1\n        \"${devices[@]}\"\n        -v \"${PWD}:${PWD}\" -w \"${PWD}\"\n        -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\"\n        -e VLLM_LOGGING_LEVEL=\"${VLLM_LOGGING_LEVEL:-INFO}\"\n        \"${CONTAINER_IMAGE}\"\n        \"$@\"\n    )\n\n    if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n        srun -N1 --ntasks-per-node=1 \"${docker_cmd[@]}\"\n    else\n        \"${docker_cmd[@]}\"\n    fi\n}\n\n# Auto-generate --serve-cmd and --bench-cmd if not in SWEEP_ARGS\nhas_arg() { for a in \"${SWEEP_ARGS[@]+\"${SWEEP_ARGS[@]}\"}\"; do [[ \"$a\" == \"$1\" ]] && return 0; done; return 1; }\n\ndefaults=()\nif ! has_arg \"--serve-cmd\"; then\n    defaults+=(--serve-cmd \"vllm serve ${MODEL}\")\nfi\nif ! has_arg \"--bench-cmd\"; then\n    defaults+=(--bench-cmd \"vllm bench serve --model ${MODEL}\")\nfi\n\ninfo \"Model:  ${MODEL}\"\n\nload_or_pull_image\nlaunch_container \\\n    vllm bench sweep serve \\\n    \"${defaults[@]+\"${defaults[@]}\"}\" \\\n    \"${SWEEP_ARGS[@]+\"${SWEEP_ARGS[@]}\"}\"\n\ninfo \"Sweep complete\"\n"
  },
  {
    "path": "src/llm/vllm/sweep.sh",
    "content": "#!/usr/bin/env bash\n# vLLM sweep benchmark suite\n#\n# Runs predefined sweep configurations via sweep.sbatch. Each suite writes\n# a different bench_params.json and invokes sweep.sbatch with the appropriate\n# --bench-cmd for that workload.\n#\n# Usage:\n#   bash sweep.sh -m Qwen/Qwen3-0.6B\n#   bash sweep.sh -m Qwen/Qwen3-30B-A3B-FP8 -i $PWD/vllm-serve-latest.tar.gz \\\n#       --serve-cmd \"vllm serve Qwen/Qwen3-30B-A3B-FP8 -tp 8 --enable-expert-parallel\" \\\n#       --type rate,concurrency\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\nMODEL=\"Qwen/Qwen3-0.6B\"\nIMAGE=\"\"\nRESULT_DIR=\"results\"\nNUM_PROMPTS=100\nSEED=42\nTYPES=\"rate,concurrency,input,output\"\nSBATCH_ARGS=()          # passthrough to sweep.sbatch\n\nusage() {\n    cat <<EOF\nUsage: $(basename \"$0\") [options] [sweep.sbatch args...]\n\nOptions (consumed by sweep.sh):\n  -m, --model MODEL       Model name/path (default: $MODEL)\n  -i, --image IMAGE       Docker image tarball or registry URI\n  -o, --output DIR        Results directory (default: $RESULT_DIR)\n  -t, --type TYPES        Comma-separated suites (default: all)\n                          Available: rate,concurrency,input,output\n  -h, --help              Show this help\n\nAll other args are passed through to sweep.sbatch (and then to vllm bench sweep serve):\n  --serve-cmd, --show-stdout, --num-runs, --dry-run, etc.\nEOF\n    exit 0\n}\n\nwhile (( \"$#\" )); do\n    case \"$1\" in\n        -m|--model)  MODEL=\"$2\"; shift 2 ;;\n        -i|--image)  IMAGE=\"$2\"; shift 2 ;;\n        -o|--output) RESULT_DIR=\"$2\"; shift 2 ;;\n        -t|--type)   TYPES=\"$2\"; shift 2 ;;\n        -h|--help)   usage ;;\n        *)           SBATCH_ARGS+=(\"$1\"); shift ;;\n    esac\ndone\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"$0\")\" && pwd)\"\n\nrun_sweep() {\n    local name=\"$1\" bench_extra=\"$2\" params_json=\"$3\"\n    local out=\"${RESULT_DIR}/${name}\"\n    mkdir -p \"$out\"\n\n    echo \"$params_json\" > \"${out}/bench_params.json\"\n\n    info \"=== Sweep: ${name} ===\"\n    bash \"${SCRIPT_DIR}/sweep.sbatch\" \\\n        -m \"$MODEL\" \\\n        ${IMAGE:+-i \"$IMAGE\"} \\\n        --bench-cmd \"vllm bench serve --model ${MODEL} ${bench_extra} --seed ${SEED}\" \\\n        --bench-params \"${out}/bench_params.json\" \\\n        -o \"$out\" \\\n        \"${SBATCH_ARGS[@]+\"${SBATCH_ARGS[@]}\"}\"\n}\n\n\n# Request rate: find saturation point with fixed workload\nsweep_rate() {\n    run_sweep \"rate\" \\\n        \"--dataset-name random --random-input-len 512 --random-output-len 256 --num-prompts ${NUM_PROMPTS}\" \\\n        '[{\"request-rate\":1},{\"request-rate\":2},{\"request-rate\":4},{\"request-rate\":8},{\"request-rate\":16},{\"request-rate\":32},{\"request-rate\":\"inf\"}]'\n}\n\n# Concurrency: find optimal batch size\nsweep_concurrency() {\n    run_sweep \"concurrency\" \\\n        \"--dataset-name random --random-input-len 512 --random-output-len 256 --num-prompts ${NUM_PROMPTS}\" \\\n        '[{\"max-concurrency\":1},{\"max-concurrency\":2},{\"max-concurrency\":4},{\"max-concurrency\":8},{\"max-concurrency\":16},{\"max-concurrency\":32},{\"max-concurrency\":64},{\"max-concurrency\":128}]'\n}\n\n# Input length: measure TTFT scaling with context size\nsweep_input() {\n    run_sweep \"input\" \\\n        \"--dataset-name random --random-output-len 128 --num-prompts ${NUM_PROMPTS} --request-rate inf\" \\\n        '[{\"random-input-len\":128},{\"random-input-len\":256},{\"random-input-len\":512},{\"random-input-len\":1024},{\"random-input-len\":2048},{\"random-input-len\":4096},{\"random-input-len\":8192},{\"random-input-len\":16384}]'\n}\n\n# Output length: measure ITL as KV cache grows\nsweep_output() {\n    run_sweep \"output\" \\\n        \"--dataset-name random --random-input-len 512 --num-prompts ${NUM_PROMPTS} --request-rate inf\" \\\n        '[{\"random-output-len\":64},{\"random-output-len\":128},{\"random-output-len\":256},{\"random-output-len\":512},{\"random-output-len\":1024},{\"random-output-len\":2048}]'\n}\n\n\nIFS=',' read -ra TESTS <<< \"$TYPES\"\nfor t in \"${TESTS[@]}\"; do\n    t=$(echo \"$t\" | xargs)\n    case \"$t\" in\n        rate)        sweep_rate ;;\n        concurrency) sweep_concurrency ;;\n        input)       sweep_input ;;\n        output)      sweep_output ;;\n        *) echo \"Unknown sweep: $t\"; exit 1 ;;\n    esac\ndone\n\ninfo \"All sweeps complete — results in ${RESULT_DIR}/\"\n"
  },
  {
    "path": "src/llm/vllm/test.sh",
    "content": "#!/usr/bin/env bash\n# vLLM API test script\n\nset -uo pipefail\n\nHOST=\"localhost\"\nPORT=\"8000\"\nMODEL=\"\"\n\nwhile (( \"$#\" )); do\n  case \"$1\" in\n    -h|--help)  echo \"Usage: $0 [-H host] [-p port] [-m model]\"; exit 0 ;;\n    -H|--host)  HOST=\"$2\"; shift 2 ;;\n    -p|--port)  PORT=\"$2\"; shift 2 ;;\n    -m|--model) MODEL=\"$2\"; shift 2 ;;\n    *) echo \"Unknown option: $1\"; exit 1 ;;\n  esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\n\n# Auto-detect model from server if not specified\nif [[ -z \"$MODEL\" ]]; then\n  MODEL=$(curl -sf \"${BASE_URL}/v1/models\" | python3 -c \"import sys,json; print(json.load(sys.stdin)['data'][0]['id'])\" 2>/dev/null)\n  if [[ -z \"$MODEL\" ]]; then\n    echo \"ERROR: Cannot detect model. Is the server running at ${BASE_URL}?\"\n    exit 1\n  fi\nfi\n\nPASS=0\nFAIL=0\n\necho \"Testing vLLM server at ${BASE_URL}\"\necho \"Model: ${MODEL}\"\necho \"========================================\"\n\ntest_endpoint() {\n  local name=\"$1\" cmd=\"$2\"\n  echo -ne \"\\n${name}... \"\n  if eval \"$cmd\" 2>&1; then\n    ((PASS++))\n  else\n    ((FAIL++))\n  fi\n}\n\ntest_endpoint \"[1/10] List models\" \\\n  \"curl -sf '${BASE_URL}/v1/models' | jq -e '.data'\"\n\ntest_endpoint \"[2/10] Basic completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[3/10] Batch completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": [\\\"Once\\\", \\\"In\\\"], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[4/10] Chat completions\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10}' | jq -e '.choices'\"\n\ntest_endpoint \"[5/10] Sampling parameters\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"max_tokens\\\": 10, \\\"temperature\\\": 0.9, \\\"top_p\\\": 0.95}' | jq -e '.choices'\"\n\ntest_endpoint \"[6/10] Streaming\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/chat/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"messages\\\": [{\\\"role\\\": \\\"user\\\", \\\"content\\\": \\\"Hi\\\"}], \\\"stream\\\": true, \\\"max_tokens\\\": 10}' | grep -q 'data:'\"\n\ntest_endpoint \"[7/10] Logprobs\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 5, \\\"logprobs\\\": 5}' | jq -e '.choices[0].logprobs'\"\n\ntest_endpoint \"[8/10] Stop sequences\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"1.\\\", \\\"max_tokens\\\": 20, \\\"stop\\\": [\\\"3.\\\"]}' | jq -e '.choices'\"\n\ntest_endpoint \"[9/10] Echo\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hello\\\", \\\"max_tokens\\\": 5, \\\"echo\\\": true}' | jq -e '.choices'\"\n\ntest_endpoint \"[10/10] Multiple completions (n=2)\" \\\n  \"curl -sf -X POST '${BASE_URL}/v1/completions' \\\n    -H 'Content-Type: application/json' \\\n    -d '{\\\"model\\\": \\\"${MODEL}\\\", \\\"prompt\\\": \\\"Hi\\\", \\\"max_tokens\\\": 10, \\\"n\\\": 2}' | jq -e '.choices | length == 2'\"\n\necho -e \"\\n========================================\"\necho \"Results: ${PASS} passed, ${FAIL} failed\"\n[[ $FAIL -gt 0 ]] && exit 1\n"
  },
  {
    "path": "src/megatron/Dockerfile",
    "content": "# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Modifications copyright (c) 2025 chang-ning\n# Modifications licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).\n# See LICENSE or https://creativecommons.org/licenses/by/4.0/\n\nARG CUDA_VERSION=12.8.1\nFROM nvcr.io/nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.47.0\nARG AWS_OFI_NCCL_VERSION=5f4202f11db1585d878196db4430aeda0e834a0c\nARG NCCL_VERSION=v2.29.3-1\nARG NCCL_TESTS_VERSION=v2.17.9\nARG NVSHMEM_VERSION=v3.5.19-1\nARG TORCH_VERSION=2.9.1\nARG MEGATRON_BRIDGE_VERSION=v0.2.2\n\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils \\\n    libibverbs-dev \\\n    libibverbs1 \\\n    libmlx5-1 \\\n    libnccl2 \\\n    libnccl-dev\n\nRUN rm -rf /opt/hpcx \\\n    && rm -rf /usr/local/mpi \\\n    && rm -f /etc/ld.so.conf.d/hpcx.conf \\\n    && ldconfig\n\nENV OPAL_PREFIX=\n\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    apt-utils \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    check \\\n    cmake \\\n    ninja-build \\\n    curl \\\n    debhelper \\\n    devscripts \\\n    git \\\n    gcc \\\n    gdb \\\n    kmod \\\n    libsubunit-dev \\\n    libtool \\\n    openssh-client \\\n    openssh-server \\\n    pkg-config \\\n    vim \\\n    hwloc \\\n    libhwloc-dev \\\n    python3-dev \\\n    python3-venv \\\n    libomp-dev\n\nRUN apt-get purge -y cuda-compat-*\n\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \"    UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\n# Set paths for both aarch64 and x86_64\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/nccl/build/lib:/opt/amazon/efa/lib:/opt/amazon/ofi-nccl/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin/:/opt/amazon/efa/bin:/usr/bin:/usr/local/bin:$PATH\n\nRUN apt-get install -y python3-pip \\\n    && pip3 install --break-system-packages --no-cache-dir awscli nvidia-ml-py Cython\n\n#################################################\n## Install NVIDIA GDRCopy\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LD_LIBRARY_PATH=/opt/gdrcopy/lib:$LD_LIBRARY_PATH\nENV LIBRARY_PATH=/opt/gdrcopy/lib:$LIBRARY_PATH\nENV CPATH=/opt/gdrcopy/include:$CPATH\nENV PATH=/opt/gdrcopy/bin:$PATH\n\n#################################################\n## Install EFA installer\nRUN cd $HOME \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify --skip-plugin \\\n    && rm -rf $HOME/aws-efa-installer $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz\n\n###################################################\n## Install aws-ofi-nccl from source (pinned commit)\nRUN git clone https://github.com/aws/aws-ofi-nccl.git /tmp/aws-ofi-nccl \\\n    && cd /tmp/aws-ofi-nccl \\\n    && git checkout ${AWS_OFI_NCCL_VERSION} \\\n    && ./autogen.sh \\\n    && ./configure --prefix=/opt/amazon/ofi-nccl \\\n        --with-libfabric=/opt/amazon/efa \\\n        --with-cuda=/usr/local/cuda \\\n        --with-nvtx=/usr/local/cuda \\\n    && make -j$(nproc) \\\n    && make install \\\n    && rm -rf /tmp/aws-ofi-nccl\n\n###################################################\n## Install NCCL\nRUN git clone -b ${NCCL_VERSION} https://github.com/NVIDIA/nccl.git  /opt/nccl \\\n    && cd /opt/nccl \\\n    && make -j $(nproc) src.build CUDA_HOME=/usr/local/cuda \\\n    NVCC_GENCODE=\"-gencode=arch=compute_80,code=sm_80 -gencode=arch=compute_86,code=sm_86 -gencode=arch=compute_89,code=sm_89 -gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\n###################################################\n## Install NCCL-tests\nRUN git clone -b ${NCCL_TESTS_VERSION} https://github.com/NVIDIA/nccl-tests.git /opt/nccl-tests \\\n    && cd /opt/nccl-tests \\\n    && make -j $(nproc) \\\n    MPI=1 \\\n    MPI_HOME=/opt/amazon/openmpi/ \\\n    CUDA_HOME=/usr/local/cuda \\\n    NCCL_HOME=/opt/nccl/build \\\n    NVCC_GENCODE=\"-gencode=arch=compute_80,code=sm_80 -gencode=arch=compute_86,code=sm_86 -gencode=arch=compute_89,code=sm_89 -gencode=arch=compute_90,code=sm_90 -gencode=arch=compute_100,code=sm_100\"\n\n###################################################\n## Build NCCL Device API examples\nRUN cd /opt/nccl/examples/06_device_api \\\n    && make -j $(nproc) NCCL_HOME=/opt/nccl/build CUDA_HOME=/usr/local/cuda MPI=1 MPI_HOME=/opt/amazon/openmpi\n\n###################################################\n## Install NVSHMEM\nENV NVSHMEM_DIR=/opt/nvshmem\nENV NVSHMEM_HOME=/opt/nvshmem\nRUN git clone -b ${NVSHMEM_VERSION} https://github.com/NVIDIA/nvshmem.git \\\n  && cd nvshmem \\\n  && mkdir -p build \\\n  && cd build \\\n  && cmake -DNVSHMEM_PREFIX=/opt/nvshmem \\\n    -DCMAKE_CUDA_ARCHITECTURES=\"80;90\" \\\n    -DNVSHMEM_MPI_SUPPORT=1 \\\n    -DNVSHMEM_PMIX_SUPPORT=1 \\\n    -DNVSHMEM_LIBFABRIC_SUPPORT=1 \\\n    -DNVSHMEM_IBRC_SUPPORT=1 \\\n    -DNVSHMEM_IBGDA_SUPPORT=1 \\\n    -DNVSHMEM_USE_GDRCOPY=1 \\\n    -DNVSHMEM_BUILD_TESTS=1 \\\n    -DNVSHMEM_BUILD_EXAMPLES=1 \\\n    -DNVSHMEM_BUILD_HYDRA_LAUNCHER=1 \\\n    -DNVSHMEM_BUILD_TXZ_PACKAGE=0 \\\n    -DNVSHMEM_BUILD_PYTHON_LIB=0 \\\n    -DMPI_HOME=/opt/amazon/openmpi \\\n    -DPMIX_HOME=/opt/amazon/pmix \\\n    -DGDRCOPY_HOME=/opt/gdrcopy \\\n    -DLIBFABRIC_HOME=/opt/amazon/efa \\\n    -G Ninja .. \\\n  && ninja -j $(nproc) \\\n  && ninja install \\\n  && rm -rf /root/nvshmem\n\nRUN pip3 install --break-system-packages --no-cache-dir nvshmem4py-cu12\n\nENV LD_LIBRARY_PATH=/opt/amazon/pmix/lib:/opt/nvshmem/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/nvshmem/bin:$PATH\nENV NVSHMEM_REMOTE_TRANSPORT=libfabric\nENV NVSHMEM_LIBFABRIC_PROVIDER=efa\n\n###################################################\n## Install PyTorch\nRUN pip3 install --break-system-packages --no-cache-dir torch==${TORCH_VERSION} --index-url https://download.pytorch.org/whl/cu128\n\n###################################################\n## Install DeepEP with NCCL GIN backend\nRUN unset NVSHMEM_DIR NVSHMEM_HOME \\\n    && export ENABLE_NCCL=1 \\\n    && export NCCL_DIR=/opt/nccl/build \\\n    && export LD_LIBRARY_PATH=/opt/nccl/build/lib:$LD_LIBRARY_PATH \\\n    && export LD_PRELOAD=/opt/nccl/build/lib/libnccl.so.2 \\\n    && git clone -b nccl https://github.com/aamirshafi/DeepEP.git /opt/DeepEP \\\n    && cd /opt/DeepEP \\\n    && git checkout 6d29f34 \\\n    && python3 setup.py build_ext --inplace \\\n    && pip install --break-system-packages --no-build-isolation .\n\nRUN rm -rf /var/lib/apt/lists/*\n\n## Set Open MPI variables\nENV OMPI_MCA_pml=^ucx            \\\n    OMPI_MCA_btl=tcp,self           \\\n    OMPI_MCA_btl_tcp_if_exclude=lo,docker0,veth_def_agent\\\n    OPAL_PREFIX=/opt/amazon/openmpi \\\n    NCCL_SOCKET_IFNAME=^docker,lo,veth\n\nENV FI_EFA_USE_DEVICE_RDMA=1\nENV FI_PROVIDER=efa\nENV FI_EFA_FORK_SAFE=1\nENV NCCL_BUFFSIZE=8388608\nENV NCCL_P2P_NET_CHUNKSIZE=524288\nENV NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\n\n## Turn off PMIx Error\nENV PMIX_MCA_gds=hash\n\n## Set LD_PRELOAD for NCCL library\nENV LD_PRELOAD=/opt/nccl/build/lib/libnccl.so\n\n# NVSHMEM additional settings\nENV NVSHMEM_DISABLE_CUDA_VMM=1\n\n# Install Nsight Systems for profiling\nRUN apt-get update -y && apt-get install -y --no-install-recommends gnupg \\\n    && apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/3bf863cc.pub \\\n    && echo \"deb https://developer.download.nvidia.com/devtools/repos/ubuntu2404/$(dpkg --print-architecture) /\" \\\n       > /etc/apt/sources.list.d/nvidia-devtools.list \\\n    && apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F60F4B3D7FA2AF80 \\\n    && apt-get update -y \\\n    && apt-get install -y --no-install-recommends nsight-systems-cli \\\n    && rm -rf /var/lib/apt/lists/*\n\n###################################################\n## Install cuDNN (headers + libs needed for TransformerEngine build)\nRUN apt-get update -y && apt-get install -y --no-install-recommends libcudnn9-dev-cuda-12 \\\n    && rm -rf /var/lib/apt/lists/*\n\n###################################################\n## Build tools and pinned numpy (before TE build)\nENV TORCH_CUDA_ARCH_LIST=\"9.0;10.0\"\nENV NVTE_FRAMEWORK=pytorch\nENV NVTE_CUDA_ARCHS=\"90;100\"\nENV NCCL_DIR=/opt/nccl/build\nENV NCCL_INCLUDE_DIR=/opt/nccl/build/include\nENV NCCL_LIB_DIR=/opt/nccl/build/lib\nENV CPATH=/opt/nccl/build/include:${CPATH}\nRUN pip3 install --break-system-packages --no-cache-dir \\\n    numpy ninja pybind11 packaging \"setuptools<80\" wheel\n\n###################################################\n## Pre-install CUDA extensions against current torch\nRUN pip3 install --break-system-packages --no-cache-dir --no-build-isolation causal-conv1d==1.6.0 mamba-ssm==2.3.0\n\n###################################################\n## Install Megatron-Bridge\nRUN apt-get update && apt-get remove -y python3-blinker && rm -rf /var/lib/apt/lists/*\nRUN git clone -b ${MEGATRON_BRIDGE_VERSION} --depth 1 https://github.com/NVIDIA-NeMo/Megatron-Bridge.git /tmp/Megatron-Bridge \\\n    && cd /tmp/Megatron-Bridge \\\n    && pip install --break-system-packages --no-cache-dir --no-build-isolation \"torch==${TORCH_VERSION}\" . \\\n    && rm -rf /tmp/Megatron-Bridge\n\nRUN pip install --break-system-packages --no-cache-dir viztracer\n\n# Sanity check\nRUN python3 -c \"import torch; print(f'torch={torch.__version__}, CUDA={torch.version.cuda}')\" \\\n    && python3 -c \"import transformer_engine; print(f'TE={transformer_engine.__version__}')\" \\\n    && python3 -c \"import megatron.core; print('megatron-core OK')\" \\\n    && python3 -c \"from megatron.bridge import AutoBridge; print('megatron-bridge OK')\"\n\nWORKDIR /workspace\n"
  },
  {
    "path": "src/megatron/Makefile",
    "content": "IMAGE_NAME ?= megatron-lm\n\n.PHONY: build sqsh clean fmt\n\nbuild:\n\t./enroot.sh -n \"$(IMAGE_NAME)\" -f Dockerfile\n\nsqsh: build\n\nclean:\n\tdocker rmi -f \"$(IMAGE_NAME)\" 2>/dev/null || true\n\trm -f *.sqsh\n\nfmt:\n\tshfmt -i 2 -w *.sh\n\tblack recipes/\n"
  },
  {
    "path": "src/megatron/README.md",
    "content": "# Megatron\n\n```bash\nmake build\n\n# Launch a 2-node DeepSeek V2 Lite pretrain job:\nsalloc -N 2\n./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n    hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite\n\n# Launch DeepSeek V2 pretrain (4 nodes):\nsalloc -N 4\n./srun.sh recipes/deepseek_v2_pretrain.py \\\n    hf_path=/fsx/models/deepseek-ai/DeepSeek-V2\n\n# Override config with Hydra-style args:\n./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n    train.train_iters=1000\n\n# Launch DeepSeek-V2-Lite using DeepEP with NCCL GIN\n./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n    hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n    moe_token_dispatcher_type=deepep \\\n    model.tensor_model_parallel_size=1 \\\n    model.expert_model_parallel_size=64 \\\n    model.sequence_parallel=false\n\n# Nsys profile\n./srun.sh --nsys recipes/deepseek_v2_lite_pretrain.py \\\n  hf_path=/fsx/hf_pretrained_models/deepseek-ai/DeepSeek-V2-Lite \\\n  moe_token_dispatcher_type=deepep \\\n  model.tensor_model_parallel_size=1 \\\n  model.expert_model_parallel_size=64 \\\n  model.sequence_parallel=false \\\n  profiling.use_nsys_profiler=true \\\n  profiling.profile_step_start=10 \\\n  profiling.profile_step_end=15 \\\n  profiling.profile_ranks=[0]\n\n# Viztracer profile\n./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n  hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite \\\n  profiling.use_viztracer=true \\\n  profiling.profile_step_start=10 \\\n  profiling.profile_step_end=15 \\\n  profiling.profile_ranks=[0]\n```\n\n## Environment Variables\n\n| Variable | Default | Description |\n|---|---|---|\n| `SQSH` | `./megatron-lm+latest.sqsh` | Path to enroot image |\n| `MOUNT` | `.:/workspace/megatron,/fsx:/fsx` | Container mounts |\n| `GPUS_PER_NODE` | `8` | GPUs per node |\n"
  },
  {
    "path": "src/megatron/enroot.sh",
    "content": "#!/bin/bash\n\nFILE=\"${PWD}/Dockerfile\"\nIMAGE=\"\"\nINPUT=\"${PWD}\"\nPROGRAM=\"$0\"\nOUTPUT=\"${PWD}\"\n\nerr() {\n  echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2\n}\n\nusage() {\n  set +x\n  cat <<EOF\nusage:  $PROGRAM [OPTIONS] params\n\noptions:\n\n  -h,--help             show this help\n  -f,--file [path]      input docker file. [default: ${PWD}/Dockerfile]\n  -i,--input [path]     input directory for docker build. [default: ${INPUT}]\n  -o,--output [path]    output directory. [default: ${OUTPUT}]\n  -n,--image [image]    image name.\n\nexample:\n\n  ./enroot.sh -n \"nccl-tests\" -f \"Dockerfile\"\nEOF\n}\n\nrun() {\n  local image=\"${1}\"\n  local output=\"${2}\"\n  local arr=()\n  local url\n  local tag\n\n  if ! command -v enroot &>/dev/null; then\n    err \"please install 'enroot' cli\"\n    return 1\n  fi\n\n  # split string\n  IFS=$'\\n' read -d \"\" -ra arr <<<\"${image//\\//$'\\n'}\"\n\n  url=\"${arr[0]}\"\n  tag=\"${arr[1]//\\:/-}\"\n\n  if [ -z \"${url}\" ]; then\n    err \"parse ${image} fail\"\n    return 1\n  fi\n\n  if [ -z \"${tag}\" ]; then\n    tag=\"latest\"\n  fi\n\n  if ! enroot import -o \"${output}/${url}+${tag}.sqsh\" \"dockerd://${image}\"; then\n    err \"enroot import ${image} fail\"\n    return 1\n  fi\n}\n\nbuild() {\n  local file=\"${1}\"\n  local image=\"${2}\"\n  local input=\"${3}\"\n  local arr=()\n  local url\n\n  if ! command -v docker &>/dev/null; then\n    err \"please install 'docker' cli\"\n    return 1\n  fi\n\n  if [ -z \"${image}\" ]; then\n    err \"please specify a image name\"\n    return 1\n  fi\n\n  IFS=$'\\n' read -d \"\" -ra arr <<<\"${image//\\//$'\\n'}\"\n\n  url=\"${arr[0]}\"\n\n  if [ -z \"${url}\" ]; then\n    err \"parse ${image} url fail\"\n    return 1\n  fi\n\n  docker images \"${url}\" -q | xargs -I{} docker rmi -f {}\n\n  if ! docker build -f \"${file}\" -t \"${image}\" \"${input}\"; then\n    err \"docker bukld -f $file -t $image $input fail\"\n    return 1\n  fi\n}\n\nwhile ((\"$#\")); do\n  case \"$1\" in\n  -h | -\\? | --help)\n    usage\n    exit 0\n    ;;\n  -f | --file)\n    FILE=\"$2\"\n    shift 2\n    ;;\n  -n | --image)\n    IMAGE=\"${2}\"\n    shift 2\n    ;;\n  -i | --input)\n    INPUT=\"${2}\"\n    shift 2\n    ;;\n  -o | --output)\n    OUTPUT=\"${2}\"\n    shift 2\n    ;;\n  *) break ;;\n  esac\ndone\n\nset -exo pipefail\n\nif ! build \"${FILE}\" \"${IMAGE}\" \"${INPUT}\"; then\n  err \"build image fail\"\n  exit 1\nfi\n\nif ! run \"${IMAGE}\" \"${OUTPUT}\"; then\n  err \"create enroot image fail\"\n  exit 1\nfi\n"
  },
  {
    "path": "src/megatron/entrypoint.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Generic entrypoint for Megatron Bridge recipes.\n\nUsage:\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py hf_path=/fsx/models/deepseek-ai/DeepSeek-V2-Lite\n    ./srun.sh recipes/qwen3_30b_a3b_pretrain.py hf_path=/fsx/models/Qwen/Qwen3-30B-A3B-FP8\n\"\"\"\n\nimport importlib.util\nimport sys\n\nimport megatron.core.jit as _jit\nif not hasattr(_jit, \"disable_jit_fuser\"):\n    _jit.disable_jit_fuser = lambda: None\n\nimport viztracer_plugin\nviztracer_plugin.install()\n\nfrom omegaconf import OmegaConf\n\nfrom megatron.bridge.training.gpt_step import forward_step\nfrom megatron.bridge.training.pretrain import pretrain\nfrom megatron.bridge.training.utils.omegaconf_utils import (\n    apply_overrides,\n    create_omegaconf_dict_config,\n    parse_hydra_overrides,\n)\n\n\ndef load_recipe(path, **kwargs):\n    \"\"\"Load a recipe module and call its `configure()` function.\"\"\"\n    spec = importlib.util.spec_from_file_location(\"recipe\", path)\n    mod = importlib.util.module_from_spec(spec)\n    spec.loader.exec_module(mod)\n    return mod.configure(**kwargs)\n\n\ndef parse_cli_overrides(cfg, args):\n    \"\"\"Apply Hydra-style CLI overrides (key=value) to a ConfigContainer.\"\"\"\n    if args:\n        omega_conf, excluded = create_omegaconf_dict_config(cfg)\n        omega_conf = parse_hydra_overrides(omega_conf, args)\n        apply_overrides(cfg, OmegaConf.to_container(omega_conf, resolve=True), excluded)\n\n\ndef main() -> None:\n    recipe_path = sys.argv[1]\n    overrides = sys.argv[2:]\n\n    recipe_kwargs = {}\n    remaining = []\n    for o in overrides:\n        if o.startswith(\"hf_path=\"):\n            recipe_kwargs[\"hf_path\"] = o.split(\"=\", 1)[1]\n        elif o.startswith(\"moe_token_dispatcher_type=\"):\n            recipe_kwargs[\"moe_token_dispatcher_type\"] = o.split(\"=\", 1)[1]\n        else:\n            remaining.append(o)\n\n    cfg = load_recipe(recipe_path, **recipe_kwargs)\n    parse_cli_overrides(cfg, remaining)\n    pretrain(config=cfg, forward_step_func=forward_step)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "src/megatron/recipes/deepseek_v2_lite_pretrain.py",
    "content": "from megatron.bridge.recipes.deepseek.deepseek_v2 import (\n    deepseek_v2_lite_pretrain_config,\n)\n\n\ndef configure(hf_path=None, moe_token_dispatcher_type=None):\n    cfg = deepseek_v2_lite_pretrain_config(\n        **({\"hf_path\": hf_path} if hf_path else {}),\n        tensor_model_parallel_size=8,\n        pipeline_model_parallel_size=1,\n        expert_model_parallel_size=2,\n        sequence_parallel=True,\n        seq_length=4096,\n        train_iters=500,\n        global_batch_size=64,\n        micro_batch_size=1,\n        eval_interval=100,\n        lr_warmup_iters=50,\n        save_interval=0,\n    )\n    cfg.model.moe_permute_fusion = False\n    if moe_token_dispatcher_type == \"deepep\":\n        cfg.model.moe_token_dispatcher_type = \"flex\"\n        cfg.model.moe_flex_dispatcher_backend = \"deepep\"\n        cfg.model.moe_enable_deepep = True\n        cfg.model.moe_shared_expert_overlap = False\n    return cfg\n"
  },
  {
    "path": "src/megatron/srun.sh",
    "content": "#!/bin/bash\n# Launch Megatron Bridge recipe inside enroot container via srun + pyxis\n# Usage: salloc -N 2 ./srun.sh recipes/deepseek_v2_lite_pretrain.py [overrides...]\n# Example: salloc -N 2 ./srun.sh recipes/deepseek_v2_lite_pretrain.py model.tensor_model_parallel_size=4\n\nset -exo pipefail\n\nDIR=\"$(cd -- \"$(dirname -- \"${BASH_SOURCE[0]}\")\" &>/dev/null && pwd)\"\nSQSH=\"${SQSH:-${DIR}/megatron-lm+latest.sqsh}\"\nMOUNT=\"${MOUNT:-${DIR}:/workspace/megatron,/fsx:/fsx}\"\nGPUS_PER_NODE=\"${GPUS_PER_NODE:-8}\"\nENABLE_NSYS=false\n\n# Parse flags before recipe arg\nwhile [[ \"${1:-}\" == --* ]]; do\n  case \"$1\" in\n  --nsys)\n    ENABLE_NSYS=true\n    shift\n    ;;\n  *) break ;;\n  esac\ndone\n\nRECIPE=\"${1:?Usage: srun.sh [--nsys] <recipe.py> [overrides...]}\"\nRECIPE_PATH=\"/workspace/megatron/${RECIPE}\"\nENTRYPOINT=\"/workspace/megatron/entrypoint.py\"\nshift\nOVERRIDES=\"$*\"\n\nmaster_host=$(scontrol show hostnames \"$SLURM_JOB_NODELIST\" 2>/dev/null | head -n1)\nmaster_addr=$(getent hosts \"${master_host}\" 2>/dev/null | awk '{print $1}' || echo \"${master_host}\")\nmaster_addr=${master_addr:-127.0.0.1}\n\ncmd=\"$(\n  cat <<EOF\nexport LD_LIBRARY_PATH=/opt/amazon/ofi-nccl/lib:/opt/amazon/efa/lib:/opt/aws-ofi-nccl/install/lib:\\$LD_LIBRARY_PATH\nexport FI_PROVIDER=efa\nexport FI_EFA_USE_DEVICE_RDMA=1\nexport FI_EFA_FORK_SAFE=1\nexport NCCL_NET_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-net-ofi.so\nexport NCCL_TUNER_PLUGIN=/opt/amazon/ofi-nccl/lib/libnccl-tuner-ofi.so\nexport NCCL_DEBUG=WARN\nexport NCCL_BUFFSIZE=8388608\nexport NCCL_P2P_NET_CHUNKSIZE=524288\nexport OMP_NUM_THREADS=1\nexport TRITON_CACHE_DIR=/tmp/triton_cache_\\${SLURM_PROCID:-0}\nexport TORCH_EXTENSIONS_DIR=/tmp/torch_ext_\\${SLURM_PROCID:-0}\nexport MASTER_ADDR=${master_addr}\nexport MASTER_PORT=29500\nexport DEEP_EP_BACKEND=nccl\nexport NCCL_GIN_TYPE=2\nexport CUDA_DEVICE_MAX_CONNECTIONS=1\nexport TORCH_DISTRIBUTED_BACKEND=nccl\nexport RANK=\\${SLURM_PROCID}\nexport LOCAL_RANK=\\${SLURM_LOCALID}\nexport WORLD_SIZE=\\${SLURM_NTASKS}\nNSYS_CMD=\"\"\nHOST=\\$(hostname)\nif [ \"${ENABLE_NSYS}\" = \"true\" ] && [ \"\\${RANK}\" = \"0\" ]; then\n  NSYS_DIR=/workspace/megatron/nsys-megatron\n  mkdir -p \\${NSYS_DIR}\n  NSYS_CMD=\"nsys profile\"\n  NSYS_CMD+=\" -t cuda,nvtx\"\n  NSYS_CMD+=\" -s none\"\n  NSYS_CMD+=\" --cpuctxsw=none\"\n  NSYS_CMD+=\" --capture-range=cudaProfilerApi\"\n  NSYS_CMD+=\" --capture-range-end=stop\"\n  NSYS_CMD+=\" --enable efa_metrics\"\n  NSYS_CMD+=\" -o \\${NSYS_DIR}/profile-\\$(hostname)-rank\\${RANK}.nsys-rep\"\n  NSYS_CMD+=\" --force-overwrite=true\"\nfi\nif [ -n \"\\${NSYS_CMD}\" ]; then echo \"NSYS_CMD=\\${NSYS_CMD}\"; fi\n\\${NSYS_CMD} python3 ${ENTRYPOINT} ${RECIPE_PATH} ${OVERRIDES}\nEOF\n)\"\n\nsrun --container-image \"${SQSH}\" \\\n  --container-mounts \"${MOUNT}\" \\\n  --container-name megatron \\\n  --mpi=pmix \\\n  --ntasks-per-node=${GPUS_PER_NODE} \\\n  bash -c \"${cmd}\"\n"
  },
  {
    "path": "src/megatron/viztracer_plugin.py",
    "content": "\"\"\"VizTracer profiling plugin for Megatron Bridge.\n\nMonkey-patches megatron.bridge.training.profiling to add viztracer support.\nActivated when `profiling.use_viztracer=true` is passed as a Hydra override.\n\nUsage:\n    ./srun.sh recipes/deepseek_v2_lite_pretrain.py \\\n        profiling.use_viztracer=true \\\n        profiling.profile_step_start=10 \\\n        profiling.profile_step_end=15 \\\n        profiling.profile_ranks=[0]\n\"\"\"\n\nimport os\nfrom dataclasses import dataclass, field\nfrom typing import Optional\n\nimport torch\n\nfrom megatron.bridge.training import profiling as _profiling\nfrom megatron.bridge.training.config import ProfilingConfig\n\n# Store original functions\n_orig_handle_step = _profiling.handle_profiling_step\n_orig_handle_stop = _profiling.handle_profiling_stop\n\n# Global viztracer instance per rank\n_viztracer_instance: Optional[object] = None\n\n\ndef _patched_handle_step(config, iteration, rank, pytorch_prof):\n    global _viztracer_instance\n\n    # Delegate to original for nsys/pytorch\n    result = _orig_handle_step(config, iteration, rank, pytorch_prof)\n    if result is not None:\n        return result\n\n    if not getattr(config, \"use_viztracer\", False):\n        return None\n    if not _profiling.should_profile_rank(config, rank):\n        return None\n    if iteration != config.profile_step_start:\n        return None\n\n    from viztracer import VizTracer\n\n    out_dir = os.environ.get(\"VIZTRACER_OUTPUT_DIR\", os.path.join(os.path.dirname(__file__), \"viztracer_output\"))\n    os.makedirs(out_dir, exist_ok=True)\n    out_path = os.path.join(out_dir, f\"trace_rank{rank}.json\")\n\n    _viztracer_instance = VizTracer(\n        output_file=out_path,\n        log_torch=True,\n    )\n    _viztracer_instance.start()\n    return None\n\n\ndef _patched_handle_stop(config, iteration, rank, pytorch_prof, nsys_nvtx_context=None):\n    global _viztracer_instance\n\n    _orig_handle_stop(config, iteration, rank, pytorch_prof, nsys_nvtx_context)\n\n    if not getattr(config, \"use_viztracer\", False):\n        return\n    if not _profiling.should_profile_rank(config, rank):\n        return\n    if iteration != config.profile_step_end:\n        return\n    if _viztracer_instance is not None:\n        _viztracer_instance.stop()\n        _viztracer_instance.save()\n        _viztracer_instance = None\n\n\ndef install():\n    \"\"\"Monkey-patch profiling hooks and extend ProfilingConfig.\"\"\"\n    import dataclasses\n\n    # Register use_viztracer as a real dataclass field so OmegaConf recognizes it\n    f = dataclasses.field(default=False)\n    f.name = \"use_viztracer\"\n    f._field_type = dataclasses._FIELD\n    ProfilingConfig.__dataclass_fields__[\"use_viztracer\"] = f\n    ProfilingConfig.use_viztracer = False\n\n    # Patch the original finalize to accept viztracer\n    _orig_finalize = ProfilingConfig.finalize\n\n    def _patched_finalize(self):\n        if getattr(self, \"use_viztracer\", False):\n            assert not self.use_nsys_profiler and not self.use_pytorch_profiler, (\n                \"use_viztracer is mutually exclusive with nsys and pytorch profilers\"\n            )\n        else:\n            _orig_finalize(self)\n\n    ProfilingConfig.finalize = _patched_finalize\n\n    # Patch profiling hooks\n    _profiling.handle_profiling_step = _patched_handle_step\n    _profiling.handle_profiling_stop = _patched_handle_stop\n"
  },
  {
    "path": "src/new_py3/py3.py",
    "content": "\"\"\"New features in Python 3 (3.12 → 3.0)\n\nSource code examples for docs/notes/python-new-py3.rst\n\"\"\"\n\nimport sys\nimport asyncio\nimport pytest\nfrom dataclasses import dataclass, FrozenInstanceError\n\nPY_VERSION = sys.version_info[:2]\n\n\n# Python 3.9 - Dictionary Merge (PEP 584)\ndef dict_merge(a: dict, b: dict) -> dict:\n    \"\"\"Merge dicts with | operator.\"\"\"\n    return a | b\n\n\ndef dict_update(a: dict, b: dict) -> dict:\n    \"\"\"Update dict in place with |= operator.\"\"\"\n    a |= b\n    return a\n\n\n# Python 3.8 - Positional-only parameters (PEP 570)\ndef positional_only(a, b, /, c, d):\n    \"\"\"a and b must be positional, c and d can be keyword.\"\"\"\n    return a + b + c + d\n\n\n# Python 3.8 - Walrus operator (PEP 572)\ndef walrus_example(data: list):\n    \"\"\"Assignment expression in condition.\"\"\"\n    if (n := len(data)) > 3:\n        return n\n    return None\n\n\ndef walrus_fib(count: int) -> list:\n    \"\"\"Fibonacci using walrus operator.\"\"\"\n    f = (0, 1)\n    return [(f := (f[1], sum(f)))[0] for _ in range(count)]\n\n\n# Python 3.7 - Data Classes (PEP 557)\n@dataclass\nclass Point:\n    \"\"\"Mutable dataclass.\"\"\"\n\n    x: int\n    y: int\n\n\n@dataclass(frozen=True)\nclass FrozenPoint:\n    \"\"\"Immutable dataclass.\"\"\"\n\n    x: int\n    y: int\n\n\n# Python 3.6 - f-string (PEP 498)\ndef fstring_basic(name: str) -> str:\n    \"\"\"Basic f-string interpolation.\"\"\"\n    return f\"Hello, {name}!\"\n\n\ndef fstring_format(value: float) -> str:\n    \"\"\"F-string with format spec.\"\"\"\n    return f\"{value:1.3}\"\n\n\n# Python 3.5 - Async/Await (PEP 492)\nasync def async_greet() -> str:\n    \"\"\"Native coroutine syntax.\"\"\"\n    await asyncio.sleep(0.01)\n    return \"Hello\"\n\n\n# Python 3.5 - General unpacking (PEP 448)\ndef general_unpacking() -> list:\n    \"\"\"Multiple unpacking in literals.\"\"\"\n    return [*range(3), 3, *range(4, 6)]\n\n\n# Python 3.3 - yield from (PEP 380)\ndef fib_gen(n: int):\n    \"\"\"Generator for fibonacci.\"\"\"\n    a, b = 0, 1\n    for _ in range(n):\n        yield a\n        b, a = a + b, b\n\n\ndef delegate_fib(n: int):\n    \"\"\"Delegate to subgenerator.\"\"\"\n    yield from fib_gen(n)\n\n\n# Python 3.0 - Extended unpacking (PEP 3132)\ndef extended_unpacking() -> tuple:\n    \"\"\"Star operator captures remaining items.\"\"\"\n    a, *b, c = range(5)\n    return a, b, c\n\n\n# Python 3.0 - Keyword-only arguments (PEP 3102)\ndef keyword_only(a, b, *, kw):\n    \"\"\"kw must be passed as keyword argument.\"\"\"\n    return a, b, kw\n\n\n# Python 3.0 - nonlocal keyword (PEP 3104)\ndef nonlocal_example() -> str:\n    \"\"\"Modify variable in enclosing scope.\"\"\"\n    outer = \"original\"\n\n    def inner():\n        nonlocal outer\n        outer = \"modified\"\n\n    inner()\n    return outer\n\n\n# Tests\n@pytest.mark.skipif(PY_VERSION < (3, 12), reason=\"Requires Python 3.12+\")\nclass TestPython312:\n    def test_box_int(self):\n        exec(\n            \"class Box[T]:\\n    def __init__(self, item: T): self.item = item\\nassert Box(42).item == 42\"\n        )\n\n    def test_first(self):\n        exec(\n            \"def first[T](items: list[T]) -> T: return items[0]\\nassert first([1, 2, 3]) == 1\"\n        )\n\n    def test_fstring_nested(self):\n        songs = [\"Take me back to Eden\", \"&\", \"Satellite\"]\n        result = eval('f\"Playlist: {\", \".join(songs)}\"')\n        assert \"Playlist:\" in result\n\n\n@pytest.mark.skipif(PY_VERSION < (3, 11), reason=\"Requires Python 3.11+\")\nclass TestPython311:\n    def test_exception_group(self):\n        code = \"\"\"\ncaught_value = caught_type = False\ntry:\n    raise ExceptionGroup(\"errors\", [ValueError(\"invalid\"), TypeError(\"wrong\")])\nexcept* ValueError:\n    caught_value = True\nexcept* TypeError:\n    caught_type = True\nassert caught_value and caught_type\n\"\"\"\n        exec(code)\n\n\n@pytest.mark.skipif(PY_VERSION < (3, 10), reason=\"Requires Python 3.10+\")\nclass TestPython310:\n    def test_http_status(self):\n        code = \"\"\"\ndef http_status(status: int) -> str:\n    match status:\n        case 200: return \"OK\"\n        case 404: return \"Not Found\"\n        case 500: return \"Internal Server Error\"\n        case _: return \"Unknown\"\nassert http_status(200) == \"OK\"\nassert http_status(404) == \"Not Found\"\nassert http_status(999) == \"Unknown\"\n\"\"\"\n        exec(code)\n\n    def test_describe_point(self):\n        code = \"\"\"\ndef describe_point(point: tuple) -> str:\n    match point:\n        case (0, 0): return \"Origin\"\n        case (x, 0): return f\"On x-axis at {x}\"\n        case (0, y): return f\"On y-axis at {y}\"\n        case (x, y): return f\"Point at ({x}, {y})\"\nassert describe_point((0, 0)) == \"Origin\"\nassert describe_point((5, 0)) == \"On x-axis at 5\"\n\"\"\"\n        exec(code)\n\n\nclass TestPython39:\n    def test_dict_merge(self):\n        assert dict_merge({\"a\": 1}, {\"b\": 2}) == {\"a\": 1, \"b\": 2}\n\n    def test_dict_update(self):\n        assert dict_update({\"a\": 1}, {\"b\": 2}) == {\"a\": 1, \"b\": 2}\n\n\nclass TestPython38:\n    def test_positional_only(self):\n        assert positional_only(1, 2, 3, 4) == 10\n        assert positional_only(1, 2, c=3, d=4) == 10\n\n    def test_walrus(self):\n        assert walrus_example([1, 2, 3, 4, 5]) == 5\n        assert walrus_example([1, 2]) is None\n\n    def test_walrus_fib(self):\n        assert walrus_fib(10) == [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]\n\n\nclass TestPython37:\n    def test_dataclass(self):\n        assert Point(1, 2) == Point(1, 2)\n\n    def test_frozen_dataclass(self):\n        with pytest.raises(FrozenInstanceError):\n            FrozenPoint(1, 2).x = 3\n\n\nclass TestPython36:\n    def test_fstring_basic(self):\n        assert fstring_basic(\"World\") == \"Hello, World!\"\n\n    def test_fstring_format(self):\n        assert fstring_format(123.567) == \"1.24e+02\"\n\n\nclass TestPython35:\n    def test_async_greet(self):\n        assert asyncio.run(async_greet()) == \"Hello\"\n\n    def test_general_unpacking(self):\n        assert general_unpacking() == [0, 1, 2, 3, 4, 5]\n\n\nclass TestPython33:\n    def test_delegate_fib(self):\n        assert list(delegate_fib(10)) == [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n\n\nclass TestPython30:\n    def test_extended_unpacking(self):\n        assert extended_unpacking() == (0, [1, 2, 3], 4)\n\n    def test_keyword_only(self):\n        assert keyword_only(1, 2, kw=3) == (1, 2, 3)\n\n    def test_nonlocal(self):\n        assert nonlocal_example() == \"modified\"\n"
  },
  {
    "path": "src/nixl/Dockerfile",
    "content": "ARG CUDA_VERSION=12.8.1\nARG GDRCOPY_VERSION=v2.5.1\nARG EFA_INSTALLER_VERSION=1.47.0\nARG UCX_VERSION=v1.20.0\nARG NIXL_VERSION=0.10.1\nARG CUDA_ARCH=90\n\nFROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu24.04\n\nARG GDRCOPY_VERSION\nARG EFA_INSTALLER_VERSION\nARG UCX_VERSION\nARG NIXL_VERSION\nARG CUDA_ARCH\n\nENV DEBIAN_FRONTEND=noninteractive\nENV TZ=UTC\n\nRUN apt-get update -y && apt-get upgrade -y\nRUN apt-get remove -y --allow-change-held-packages \\\n    ibverbs-utils libibverbs-dev libibverbs1 libmlx5-1\n\nRUN rm -rf /opt/hpcx /usr/local/mpi /etc/ld.so.conf.d/hpcx.conf && ldconfig\n\nENV OPAL_PREFIX=\n\nRUN DEBIAN_FRONTEND=noninteractive apt-get install -y --allow-unauthenticated \\\n    autoconf \\\n    automake \\\n    build-essential \\\n    ca-certificates \\\n    cmake \\\n    curl \\\n    dmidecode \\\n    environment-modules \\\n    etcd-server \\\n    ethtool \\\n    git \\\n    ibverbs-providers \\\n    ibverbs-utils \\\n    iproute2 \\\n    jq \\\n    libcpprest-dev \\\n    libcurl4-openssl-dev \\\n    libelf-dev \\\n    libevent-core-2.1-7t64 \\\n    libevent-pthreads-2.1-7t64 \\\n    libgflags-dev \\\n    libgrpc++-dev \\\n    libgrpc-dev \\\n    libhwloc-dev \\\n    libhwloc15 \\\n    libibumad-dev \\\n    libibverbs-dev \\\n    libnl-3-200 \\\n    libnl-3-dev \\\n    libnl-route-3-200 \\\n    libnl-route-3-dev \\\n    libnuma-dev \\\n    libprotobuf-dev \\\n    librdmacm-dev \\\n    libssl-dev \\\n    libtool \\\n    meson \\\n    ninja-build \\\n    openssh-client \\\n    openssh-server \\\n    pciutils \\\n    pkg-config \\\n    protobuf-compiler-grpc \\\n    pybind11-dev \\\n    python3 \\\n    python3-dev \\\n    python3-pip \\\n    rdma-core \\\n    tcl \\\n    udev \\\n    uuid-dev \\\n    vim \\\n    wget \\\n    zlib1g-dev \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN apt-get purge -y cuda-compat-* || true\n\nRUN mkdir -p /var/run/sshd\nRUN sed -i 's/[ #]\\(.*StrictHostKeyChecking \\).*/ \\1no/g' /etc/ssh/ssh_config && \\\n    echo \"    UserKnownHostsFile /dev/null\" >> /etc/ssh/ssh_config && \\\n    sed -i 's/#\\(StrictModes \\).*/\\1no/g' /etc/ssh/sshd_config\n\nENV LD_LIBRARY_PATH=/usr/local/cuda/extras/CUPTI/lib64:/opt/amazon/openmpi/lib:/opt/amazon/efa/lib:/opt/gdrcopy/lib:/usr/local/lib:$LD_LIBRARY_PATH\nENV PATH=/opt/amazon/openmpi/bin:/opt/amazon/efa/bin:/usr/bin:/usr/local/bin:$PATH\n\nRUN rm -f /usr/lib/python*/EXTERNALLY-MANAGED \\\n    && pip3 install --no-cache-dir awscli nvidia-ml-py\n\n# --- GDRCopy ---\nRUN git clone -b ${GDRCOPY_VERSION} https://github.com/NVIDIA/gdrcopy.git /tmp/gdrcopy \\\n    && cd /tmp/gdrcopy \\\n    && make prefix=/opt/gdrcopy install \\\n    && rm -rf /tmp/gdrcopy\n\nENV LD_LIBRARY_PATH=/opt/gdrcopy/lib:$LD_LIBRARY_PATH\nENV LIBRARY_PATH=/opt/gdrcopy/lib:$LIBRARY_PATH\nENV CPATH=/opt/gdrcopy/include\nENV PATH=/opt/gdrcopy/bin:$PATH\n\n# --- EFA ---\nRUN cd $HOME \\\n    && curl -O https://efa-installer.amazonaws.com/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && tar -xf $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz \\\n    && cd aws-efa-installer \\\n    && ./efa_installer.sh -y -g -d --skip-kmod --skip-limit-conf --no-verify \\\n    && rm -rf $HOME/aws-efa-installer $HOME/aws-efa-installer-${EFA_INSTALLER_VERSION}.tar.gz\n\n# --- UCX (verbs + rdmacm + dm + efa) ---\nENV UCX_PREFIX=/usr/local/ucx\n\nRUN git clone --depth 1 --branch ${UCX_VERSION} https://github.com/openucx/ucx.git /tmp/ucx \\\n    && cd /tmp/ucx && ./autogen.sh \\\n    && ./contrib/configure-release-mt \\\n       --prefix=${UCX_PREFIX} \\\n       --enable-shared --disable-static \\\n       --enable-optimizations --enable-cma --enable-mt \\\n       --enable-devel-headers \\\n       --with-cuda=/usr/local/cuda \\\n       --with-gdrcopy=/opt/gdrcopy \\\n       --with-verbs --with-rdmacm --with-dm --with-efa \\\n    && make -j$(nproc) && make install \\\n    && echo \"${UCX_PREFIX}/lib\" > /etc/ld.so.conf.d/ucx.conf \\\n    && echo \"${UCX_PREFIX}/lib/ucx\" >> /etc/ld.so.conf.d/ucx.conf \\\n    && ldconfig && rm -rf /tmp/ucx\n\nENV PATH=\"${UCX_PREFIX}/bin:${PATH}\"\nENV LD_LIBRARY_PATH=\"${UCX_PREFIX}/lib:${UCX_PREFIX}/lib/ucx:${LD_LIBRARY_PATH}\"\n\n# --- PyTorch (install before NIXL, needed for Python bindings) ---\nRUN pip3 install --no-cache-dir torch==2.9.1 --index-url https://download.pytorch.org/whl/cu128\n\n# --- NIXL ---\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\nRUN git clone --depth 1 --branch ${NIXL_VERSION} \\\n      https://github.com/ai-dynamo/nixl.git /opt/nixl \\\n    && cd /opt/nixl \\\n    && pip3 install --no-cache-dir tomlkit \\\n    && export PKG_CONFIG_PATH=\"/opt/amazon/efa/lib/pkgconfig:$PKG_CONFIG_PATH\" \\\n    && export CPATH=\"/opt/amazon/efa/include:$CPATH\" \\\n    && export LIBRARY_PATH=\"/opt/amazon/efa/lib:/usr/local/cuda/lib64/stubs\" \\\n    && meson setup build \\\n       --prefix=/usr/local \\\n       --buildtype=release \\\n       -Ducx_path=${UCX_PREFIX} \\\n       -Dlibfabric_path=/opt/amazon/efa \\\n    && ninja -C build -j$(nproc) \\\n    && ninja -C build install \\\n    && pip3 install --no-cache-dir build/src/bindings/python/nixl-meta/nixl-${NIXL_VERSION}-py3-none-any.whl \\\n    && ldconfig\n\n# --- nixlbench ---\n\n# etcd-cpp-api (nixlbench dep)\nRUN git clone --depth 1 https://github.com/etcd-cpp-apiv3/etcd-cpp-apiv3.git /tmp/etcd-cpp \\\n    && cd /tmp/etcd-cpp && mkdir build && cd build \\\n    && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Release \\\n    && make -j$(nproc) && make install && ldconfig \\\n    && rm -rf /tmp/etcd-cpp\n\nRUN cd /opt/nixl/benchmark/nixlbench \\\n    && meson setup build --prefix=/usr/local --buildtype=release -Dnixl_path=/usr/local \\\n    && ninja -C build -j$(nproc) && ninja -C build install\n\n# --- kvbench (Python-based, lives in /opt/nixl/benchmark/kvbench) ---\nRUN pip3 install --no-cache-dir pyYAML click tabulate\n\n# --- Environment ---\nENV OPAL_PREFIX=/opt/amazon/openmpi \\\n    OMPI_MCA_pml=ob1 \\\n    OMPI_MCA_btl=\"tcp,self\" \\\n    FI_PROVIDER=efa \\\n    FI_EFA_FORK_SAFE=1 \\\n    FI_EFA_USE_DEVICE_RDMA=1 \\\n    FI_LOG_LEVEL=warn \\\n    UCX_TLS=\"^cuda_ipc\" \\\n    UCX_NET_DEVICES=all \\\n    PYTHONPATH=\"/opt/nixl/benchmark\"\n\n# --- Validation ---\nRUN echo \"=== NIXL Image Validation ===\" \\\n    && ucx_info -v | head -3 \\\n    && fi_info --version \\\n    && ${OPAL_PREFIX}/bin/mpirun --version | head -1 \\\n    && which ucx_perftest \\\n    && which nixlbench \\\n    && python3 -c \"import nixl; print('nixl OK')\"\n\n# --- flash-attn + DeepGEMM ---\nRUN apt-get update && apt-get remove -y python3-blinker && rm -rf /var/lib/apt/lists/*\nRUN pip3 install --no-cache-dir packaging numpy ninja pybind11 \"setuptools<80\" wheel\nRUN pip3 install --no-cache-dir flash-attn==2.8.1 --no-build-isolation\nRUN git clone --recursive -b v2.1.1.post3 https://github.com/deepseek-ai/DeepGEMM.git /tmp/deepgemm \\\n    && cd /tmp/deepgemm \\\n    && python3 setup.py bdist_wheel \\\n    && pip3 install dist/*.whl \\\n    && rm -rf /tmp/deepgemm\n\n# --- vLLM + SGLang + Ray ---\nENV VLLM_USE_DEEP_GEMM=1\nENV DG_JIT_CACHE_DIR=/tmp\nRUN pip3 install --no-cache-dir vllm==0.15.1\nRUN pip3 install --no-cache-dir \"sglang[all]==0.5.9\"\nRUN pip3 install --no-cache-dir ray\n\n# --- vllm-router (PD disaggregation proxy) ---\nRUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y\nENV PATH=\"/root/.cargo/bin:${PATH}\"\nRUN pip3 install --no-cache-dir vllm-router\n\nWORKDIR /workspace\nCMD [\"/bin/bash\"]\n"
  },
  {
    "path": "src/nixl/Makefile",
    "content": ".PHONY: help docker save clean\n\n.DEFAULT_GOAL := help\n\nIMAGE_NAME ?= nixl\nIMAGE_TAG ?= latest\n\nhelp:\n\t@echo \"NIXL Makefile\"\n\t@echo \"\"\n\t@echo \"Targets:\"\n\t@echo \"  docker    Build Docker image\"\n\t@echo \"  save      Save Docker image to tar.gz\"\n\t@echo \"  clean     Remove image and tarball\"\n\t@echo \"\"\n\t@echo \"Usage:\"\n\t@echo \"  make docker && make save\"\n\ndocker:\n\tdocker build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile .\n\nsave:\n\tdocker save $(IMAGE_NAME):$(IMAGE_TAG) | pigz > $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n\nclean:\n\t-docker rmi $(IMAGE_NAME):$(IMAGE_TAG) 2>/dev/null || true\n\t-rm -f $(IMAGE_NAME)-$(IMAGE_TAG).tar.gz\n"
  },
  {
    "path": "src/nixl/bench.sh",
    "content": "#!/usr/bin/env bash\n# vLLM serving benchmark suite\n# Usage:\n#   salloc -N1 bash bench.sh -H 10.0.128.193 -i /fsx/nixl-latest.tar.gz\n#   bash bench.sh -H 10.0.128.193 -i vllm-serve:latest\nset -euo pipefail\n\ninfo() { echo \"[$(date +'%H:%M:%S')] $*\"; }\n\n# Docker image helpers (mirrors run.sbatch)\nCONTAINER_MOUNT=\"${CONTAINER_MOUNT:-/fsx}\"\n\n# Wrap a command with srun if inside a SLURM allocation, otherwise run directly\n_run() {\n  if [[ -n \"${SLURM_JOB_ID:-}\" ]]; then\n    srun -N1 --ntasks-per-node=1 bash -c \"$*\"\n  else\n    bash -c \"$*\"\n  fi\n}\n\nload_or_pull_image() {\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json |\n      python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Loading Docker image from tarball...'\n                pigz -dc '${IMAGE}' | docker load\n            fi\n        \"\n  else\n    CONTAINER_IMAGE=\"${IMAGE}\"\n    _run \"\n            if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n                echo 'Pulling ${CONTAINER_IMAGE}...'\n                registry=\\\"\\${CONTAINER_IMAGE%%/*}\\\"\n                region=\\$(echo \\\"\\${registry}\\\" | sed -n 's/.*\\.ecr\\.\\([^.]*\\)\\.amazonaws\\.com/\\1/p')\n                region=\\\"\\${region:-us-west-2}\\\"\n                aws ecr get-login-password --region \\\"\\${region}\\\" \\\n                    | docker login --username AWS --password-stdin \\\"\\${registry}\\\"\n                docker pull '${CONTAINER_IMAGE}'\n            fi\n        \"\n  fi\n}\n\nlaunch_container() {\n  local cmd=\"$1\"\n  _run \"\n        docker run --rm --net=host \\\n            -v '${PWD}:${PWD}' -w '${PWD}' \\\n            -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n            --entrypoint bash '${CONTAINER_IMAGE}' \\\n            -c '${cmd}'\n    \"\n}\n\n# If vllm CLI is not available, load image and re-exec inside container\nif ! command -v vllm &>/dev/null; then\n  # Pre-parse --image/-i before full arg parsing\n  IMAGE=\"\" _args=(\"$@\")\n  for ((i = 0; i < ${#_args[@]}; i++)); do\n    [[ \"${_args[$i]}\" == \"--image\" || \"${_args[$i]}\" == \"-i\" ]] &&\n      {\n        IMAGE=\"${_args[$((i + 1))]}\"\n        break\n      }\n  done\n  IMAGE=\"${IMAGE:-${PWD}/nixl-latest.tar.gz}\"\n\n  load_or_pull_image\n  _SCRIPT=\"$(cd \"$(dirname \"$0\")\" && pwd)/$(basename \"$0\")\"\n  launch_container \"bash ${_SCRIPT} $*\"\n  exit $?\nfi\n\nHOST=\"localhost\"\nPORT=\"8000\"\nBENCH_ARGS=()\n\nusage() {\n  cat <<EOF\nUsage: $(basename \"$0\") [OPTIONS] [-- VLLM_BENCH_ARGS...]\n\nOptions:\n  -H, --host HOST         Server host (default: $HOST)\n  -p, --port PORT         Server port (default: $PORT)\n  -i, --image IMAGE       Docker image or tarball (default: ./nixl-latest.tar.gz)\n  -h, --help              Show this help\n\nExamples:\n  $(basename \"$0\") -H 10.0.128.193 -- --model meta-llama/Llama-3.1-8B --dataset-name sharegpt --num-prompts 100\n  $(basename \"$0\") -H 10.0.128.193 -- --model meta-llama/Llama-3.1-8B --profile\nEOF\n}\n\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n  -H | --host)\n    HOST=\"$2\"\n    shift 2\n    ;;\n  -p | --port)\n    PORT=\"$2\"\n    shift 2\n    ;;\n  -i | --image) shift 2 ;; # consumed by preamble above\n  -h | --help)\n    usage\n    exit 0\n    ;;\n  --)\n    shift\n    BENCH_ARGS+=(\"$@\")\n    break\n    ;;\n  *)\n    echo \"Unknown option: $1\"\n    usage\n    exit 1\n    ;;\n  esac\ndone\n\nBASE_URL=\"http://${HOST}:${PORT}\"\n\ninfo \"========================================\"\ninfo \"vLLM Benchmark Client\"\ninfo \"========================================\"\ninfo \"Server: ${BASE_URL}\"\ninfo \"Command: vllm bench serve --base-url ${BASE_URL} ${BENCH_ARGS[*]}\"\ninfo \"========================================\"\n\nvllm bench serve \\\n  --base-url \"$BASE_URL\" \\\n  \"${BENCH_ARGS[@]}\"\n"
  },
  {
    "path": "src/nixl/nixl.sbatch",
    "content": "#!/bin/bash\n# Usage:\n#   salloc -N 2 bash nixl.sbatch [--image <path>] [nixlbench args...]\n#\n# Examples:\n#   salloc -N 2 bash nixl.sbatch --backend UCX --initiator_seg_type VRAM --target_seg_type VRAM\n#   salloc -N 2 bash nixl.sbatch --image ./nixl-test-latest.tar.gz --backend UCX\n\nset -euo pipefail\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nWORKSPACE=\"$PWD\"\nCONTAINER_MOUNT=\"/fsx\"\nETCD_PORT=$((2379 + (SLURM_JOB_ID % 1000)))\nBENCH_ARGS=()\nDOCKER_ENVS=()\n\nwhile ((\"$#\")); do\n  case \"$1\" in\n  --image) IMAGE=\"$2\"; shift 2 ;;\n  --container-mount) CONTAINER_MOUNT=\"$2\"; shift 2 ;;\n  --env) DOCKER_ENVS+=(\"-e\" \"$2\"); shift 2 ;;\n  *) BENCH_ARGS+=(\"$1\"); shift ;;\n  esac\ndone\n\nIMAGE=\"${IMAGE:-${WORKSPACE}/nixl-latest.tar.gz}\"\nNUM_NODES=${SLURM_JOB_NUM_NODES:-1}\nreadarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\nHEAD_NODE=${NODES[0]}\nHEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\n\ninfo \"========================================\"\ninfo \"nixlbench\"\ninfo \"========================================\"\ninfo \"Image: ${IMAGE}\"\ninfo \"Nodes: ${NUM_NODES}, Head: ${HEAD_NODE} (${HEAD_IP})\"\ninfo \"Args: ${BENCH_ARGS[*]+\"${BENCH_ARGS[*]}\"}\"\ninfo \"========================================\"\n\nload_image() {\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    CONTAINER_IMAGE=\"${IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      docker image inspect '${CONTAINER_IMAGE}' &>/dev/null || docker pull '${CONTAINER_IMAGE}'\n    \"\n  fi\n}\n\nlaunch_container() {\n  local name=\"$1\" node=\"$2\" cmd=\"$3\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  srun --nodes=1 --nodelist=\"${node}\" bash -c \"\n    docker run --gpus all \\\n      --privileged -d \\\n      --name '${name}' \\\n      --uts=host --ipc=host --net=host \\\n      --ulimit stack=67108864 --ulimit memlock=-1 \\\n      --security-opt seccomp=unconfined \\\n      ${devices[*]+\"${devices[*]}\"} \\\n      ${DOCKER_ENVS[*]+\"${DOCKER_ENVS[*]}\"} \\\n      -v '${CONTAINER_MOUNT}:${CONTAINER_MOUNT}' \\\n      --entrypoint bash \\\n      '${CONTAINER_IMAGE}' \\\n      -c '${cmd}'\n  \"\n}\n\nLOGDIR=\"${WORKSPACE}/logs\"\n\ncleanup() {\n  info \"Cleaning up...\"\n  kill $(jobs -p) 2>/dev/null || true\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker stop -t 10 2>/dev/null || true\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}\"/nixlbench-*.log\n}\n\ntrap cleanup EXIT\ncleanup\nload_image\n\n# Start etcd on head node\ninfo \"Starting etcd on ${HEAD_NODE}:${ETCD_PORT}...\"\nlaunch_container \"etcd\" \"${HEAD_NODE}\" \\\n  \"etcd --data-dir=/tmp/etcd-data \\\n        --listen-client-urls http://0.0.0.0:${ETCD_PORT} \\\n        --advertise-client-urls http://${HEAD_IP}:${ETCD_PORT} \\\n        --listen-peer-urls http://0.0.0.0:$((ETCD_PORT+1)) \\\n        --initial-advertise-peer-urls http://${HEAD_IP}:$((ETCD_PORT+1)) \\\n        --initial-cluster default=http://${HEAD_IP}:$((ETCD_PORT+1))\"\nETCD_URL=\"http://${HEAD_IP}:${ETCD_PORT}\"\n\n# Wait for etcd to be ready\ninfo \"Waiting for etcd at ${ETCD_URL}...\"\nfor i in $(seq 1 60); do\n  if curl -sf \"${ETCD_URL}/health\" >/dev/null 2>&1; then\n    info \"etcd is ready\"\n    break\n  fi\n  if [ \"$i\" -eq 60 ]; then\n    err \"etcd failed to start within 60s\"\n    exit 1\n  fi\n  sleep 2\ndone\n\n# Launch nixlbench containers\nfor i in $(seq 0 $((NUM_NODES - 1))); do\n  launch_container \"nixl-${i}\" \"${NODES[$i]}\" \"sleep infinity\"\ndone\nsleep 3\n\nBENCH_ARGS_STR=\"${BENCH_ARGS[*]+\"${BENCH_ARGS[*]}\"}\"\nmkdir -p \"${LOGDIR}\"\n\n# Start nixlbench on worker nodes\nfor i in $(seq 1 $((NUM_NODES - 1))); do\n  info \"Starting nixlbench on ${NODES[$i]}...\"\n  srun --nodes=1 --nodelist=\"${NODES[$i]}\" bash -c \"\n    docker exec -d nixl-${i} bash -c 'nixlbench --etcd_endpoints ${ETCD_URL} ${BENCH_ARGS_STR} 2>&1 | tee ${LOGDIR}/nixlbench-${NODES[$i]}.log'\n  \"\n  tail -f \"${LOGDIR}/nixlbench-${NODES[$i]}.log\" 2>/dev/null | sed \"s/^/[${NODES[$i]}] /\" &\n  sleep 1\ndone\n\n# Start nixlbench on head node (foreground)\ninfo \"Starting nixlbench on ${HEAD_NODE}...\"\nsrun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n  docker exec nixl-0 bash -c 'nixlbench --etcd_endpoints ${ETCD_URL} ${BENCH_ARGS_STR} 2>&1 | tee ${LOGDIR}/nixlbench-${HEAD_NODE}.log'\n\"\n"
  },
  {
    "path": "src/nixl/vllm.sbatch",
    "content": "#!/bin/bash\n# Usage:\n#   salloc -N <nodes> bash vllm.sbatch [--route R] [--prefill P] [--image <path>] [vllm serve args...]\n#\n# Modes:\n#   --prefill 0 (default): pure DP, no disaggregation\n#   --prefill P:           P prefill + (nodes_per_group - P) decode per group\n#   --route R:             split nodes into R identical groups, vllm-router round-robins\n#\n# Examples:\n#   # Exp 1: Pure DP — 4 nodes, TP=8\n#   salloc -N 4 bash vllm.sbatch --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite\n#\n#   # Exp 2: 2 groups of 2 nodes, pure DP each, router round-robins\n#   salloc -N 4 bash vllm.sbatch --route 2 --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite\n#\n#   # Exp 3: 2 groups of 2 nodes, each 1P+1D, router round-robins\n#   salloc -N 4 bash vllm.sbatch --route 2 --prefill 1 --model /fsx/models/deepseek-ai/DeepSeek-V2-Lite\n\nset -euo pipefail\n\ninfo() { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][info] $*\"; }\nerr()  { echo -e \"[$(date +'%Y-%m-%dT%H:%M:%S%z')][error] $*\" >&2; }\n\nIMAGE=\"\"\nWORKSPACE=\"$PWD\"\nCONTAINER_MOUNT=\"/fsx\"\nNUM_PREFILL=0\nROUTE_SIZE=1\nSERVE_ARGS=()\n\nwhile ((\"$#\")); do\n  case \"$1\" in\n  --image)           IMAGE=\"$2\"; shift 2 ;;\n  --container-mount) CONTAINER_MOUNT=\"$2\"; shift 2 ;;\n  --prefill)         NUM_PREFILL=\"$2\"; shift 2 ;;\n  --route)           ROUTE_SIZE=\"$2\"; shift 2 ;;\n  *)                 SERVE_ARGS+=(\"$1\"); shift ;;\n  esac\ndone\n\nIMAGE=\"${IMAGE:-${WORKSPACE}/nixl-latest.tar.gz}\"\nNUM_NODES=${SLURM_JOB_NUM_NODES:-1}\nreadarray -t NODES < <(scontrol show hostnames \"$SLURM_JOB_NODELIST\")\nHEAD_NODE=${NODES[0]}\nHEAD_IP=$(getent ahostsv4 \"$HEAD_NODE\" | head -1 | awk '{print $1}')\nLOGDIR=\"${WORKSPACE}/logs\"\n\n_peek_arg() {\n  local short=\"$1\" long=\"$2\" default=\"$3\" i=0\n  while ((i < ${#SERVE_ARGS[@]})); do\n    if [[ \"${SERVE_ARGS[$i]}\" == \"$short\" || \"${SERVE_ARGS[$i]}\" == \"$long\" ]]; then\n      echo \"${SERVE_ARGS[$((i + 1))]}\"\n      return\n    fi\n    ((i++))\n  done\n  echo \"$default\"\n}\n\nGPUS_PER_NODE=8\nTP=$(_peek_arg \"-tp\" \"--tensor-parallel-size\" \"${GPUS_PER_NODE}\")\nMODEL=$(_peek_arg \"\" \"--model\" \"deepseek-ai/DeepSeek-V2-Lite\")\nSERVE_ARGS_STR=$(printf '%q ' \"${SERVE_ARGS[@]+\"${SERVE_ARGS[@]}\"}\")\nDP_LOCAL=$((GPUS_PER_NODE / TP))\n\n# Validate\nif (( NUM_NODES % ROUTE_SIZE != 0 )); then\n  err \"NUM_NODES(${NUM_NODES}) must be divisible by --route ${ROUTE_SIZE}\"\n  exit 1\nfi\nNODES_PER_GROUP=$((NUM_NODES / ROUTE_SIZE))\nNUM_DECODE=$((NODES_PER_GROUP - NUM_PREFILL))\nif (( NUM_PREFILL > 0 && NUM_DECODE < 1 )); then\n  err \"Each group needs at least 1 decode node. Got ${NODES_PER_GROUP} nodes/group, --prefill ${NUM_PREFILL}\"\n  exit 1\nfi\n\nROUTER_PORT=$((8000 + ROUTE_SIZE))\n\nmkdir -p \"${LOGDIR}\"\n\nif (( NUM_PREFILL == 0 )); then\n  MODE=\"pure-dp\"\nelse\n  MODE=\"${NUM_PREFILL}P+${NUM_DECODE}D\"\nfi\n\ninfo \"========================================\"\ninfo \"vLLM Server\"\ninfo \"========================================\"\ninfo \"Image:    ${IMAGE}\"\ninfo \"Model:    ${MODEL}\"\ninfo \"Nodes:    ${NUM_NODES}, TP=${TP}, DP_LOCAL=${DP_LOCAL}\"\ninfo \"Route:    ${ROUTE_SIZE} group(s), ${NODES_PER_GROUP} node(s)/group, mode=${MODE}\"\ninfo \"Args:     ${SERVE_ARGS[*]+\"${SERVE_ARGS[*]}\"}\"\ninfo \"========================================\"\n\nload_image() {\n  if [[ \"${IMAGE}\" == *.tar.gz ]]; then\n    CONTAINER_IMAGE=$(pigz -dc \"${IMAGE}\" | tar -xf - -O manifest.json | python3 -c \"import sys,json; print(json.load(sys.stdin)[0]['RepoTags'][0])\")\n    info \"Image tag: ${CONTAINER_IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      if ! docker image inspect '${CONTAINER_IMAGE}' &>/dev/null; then\n        pigz -dc '${IMAGE}' | docker load\n      fi\n    \"\n  else\n    CONTAINER_IMAGE=\"${IMAGE}\"\n    srun --ntasks-per-node=1 bash -c \"\n      docker image inspect '${CONTAINER_IMAGE}' &>/dev/null || docker pull '${CONTAINER_IMAGE}'\n    \"\n  fi\n}\n\nlaunch_container() {\n  local name=\"$1\" cmd=\"$2\"\n  local devices=(\"--device=/dev/gdrdrv\")\n  while IFS= read -r -d '' d; do\n    devices+=(\"--device=${d}\")\n  done < <(find \"/dev/infiniband\" -name \"uverbs*\" -print0 2>/dev/null)\n\n  local net_if=\"${GLOO_SOCKET_IFNAME:-$(ip -o -4 route show to default | awk '{print $5}' | head -1)}\"\n  local host_ip; host_ip=$(ip -o -4 addr show \"${net_if}\" | awk '{print $4}' | cut -d/ -f1 | head -1)\n\n  docker run --gpus all \\\n    --privileged -d \\\n    --name \"${name}\" \\\n    --uts=host --ipc=host --net=host \\\n    --ulimit stack=67108864 --ulimit memlock=-1 \\\n    --security-opt seccomp=unconfined \\\n    \"${devices[@]}\" \\\n    -e NCCL_SOCKET_IFNAME=\"${net_if}\" \\\n    -e GLOO_SOCKET_IFNAME=\"${net_if}\" \\\n    -e VLLM_NIXL_SIDE_CHANNEL_HOST=\"${host_ip}\" \\\n    -e VLLM_NIXL_SIDE_CHANNEL_PORT=\"${SIDE_CHANNEL_PORT:-5600}\" \\\n    -e VLLM_RPC_TIMEOUT=\"${VLLM_RPC_TIMEOUT:-3600000}\" \\\n    -e VLLM_ENGINE_READY_TIMEOUT_S=\"${VLLM_ENGINE_READY_TIMEOUT_S:-3600}\" \\\n    -v \"${CONTAINER_MOUNT}:${CONTAINER_MOUNT}\" \\\n    --entrypoint bash \\\n    \"${CONTAINER_IMAGE}\" \\\n    -c \"${cmd}\"\n}\n\ncleanup() {\n  info \"Cleaning up...\"\n  srun --ntasks-per-node=1 bash -c '\n    docker ps -aq | xargs -r docker rm -f 2>/dev/null || true\n  ' 2>/dev/null || true\n  rm -f \"${LOGDIR}\"/vllm-*.log\n}\n\nwait_for_server() {\n  local ip=\"$1\" port=\"$2\" label=\"$3\"\n  info \"Waiting for ${label} at ${ip}:${port} ...\"\n  for _ in $(seq 1 360); do\n    if curl -sf \"http://${ip}:${port}/health\" >/dev/null 2>&1; then\n      info \"${label} is ready\"\n      return 0\n    fi\n    sleep 5\n  done\n  err \"${label} failed to start within 1800s\"\n  return 1\n}\n\n# --- Launch a pure-DP group on a set of nodes ---\n# Args: group_id, node_list (space-separated), port\nstart_dp_group() {\n  local gid=\"$1\" port=\"$2\"\n  shift 2\n  local gnodes=(\"$@\")\n  local ghead=\"${gnodes[0]}\"\n  local ghead_ip; ghead_ip=$(getent ahostsv4 \"$ghead\" | head -1 | awk '{print $1}')\n  local gsize=${#gnodes[@]}\n  local dp=$((gsize * DP_LOCAL))\n  local rpc_port=$((13345 + (SLURM_JOB_ID % 1000) + gid))\n  local logfile=\"${LOGDIR}/vllm-g${gid}.log\"\n\n  info \"Group ${gid}: ${gsize} nodes, DP=${dp}, head=${ghead}:${port}\"\n\n  # Launch containers\n  for i in $(seq 0 $((gsize - 1))); do\n    srun --nodes=1 --nodelist=\"${gnodes[$i]}\" bash -c \"\n      $(declare -f launch_container)\n      CONTAINER_IMAGE='${CONTAINER_IMAGE}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' \\\n      launch_container 'vllm-g${gid}-n${i}' 'sleep infinity'\n    \"\n  done\n  sleep 3\n\n  local extra=\"--data-parallel-size ${dp} --data-parallel-size-local ${DP_LOCAL} --data-parallel-address ${ghead_ip} --data-parallel-rpc-port ${rpc_port}\"\n\n  # Start workers\n  for i in $(seq 1 $((gsize - 1))); do\n    local start_rank=$((i * DP_LOCAL))\n    srun --nodes=1 --nodelist=\"${gnodes[$i]}\" bash -c \"\n      docker exec -d vllm-g${gid}-n${i} bash -c 'vllm serve ${SERVE_ARGS_STR} ${extra} \\\n        --data-parallel-start-rank ${start_rank} --headless \\\n        2>&1 | tee ${logfile}.node${i}'\n    \"\n  done\n\n  # Start head\n  srun --nodes=1 --nodelist=\"${ghead}\" bash -c \"\n    docker exec -d vllm-g${gid}-n0 bash -c 'vllm serve ${SERVE_ARGS_STR} ${extra} \\\n      --host 0.0.0.0 --port ${port} \\\n      2>&1 | tee ${logfile}'\n  \"\n  tail -f \"${logfile}\" 2>/dev/null | sed \"s/^/[g${gid}] /\" &\n}\n\n# --- Launch a PD-disaggregated group on a set of nodes ---\n# Args: group_id, base_port, node_list (space-separated)\nstart_pd_group() {\n  local gid=\"$1\" base_port=\"$2\"\n  shift 2\n  local gnodes=(\"$@\")\n  local gsize=${#gnodes[@]}\n\n  info \"Group ${gid}: ${NUM_PREFILL}P+${NUM_DECODE}D, nodes=${gnodes[*]}\"\n\n  # Launch prefill nodes\n  for i in $(seq 0 $((NUM_PREFILL - 1))); do\n    local node=\"${gnodes[$i]}\"\n    local port=$((base_port + i))\n    srun --nodes=1 --nodelist=\"${node}\" bash -c \"\n      $(declare -f launch_container)\n      CONTAINER_IMAGE='${CONTAINER_IMAGE}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' \\\n      launch_container 'vllm-g${gid}-p${i}' \\\n        'vllm serve ${SERVE_ARGS_STR} \\\n           --host 0.0.0.0 --port ${port} \\\n           --data-parallel-size ${DP_LOCAL} \\\n           --kv-transfer-config.kv_connector NixlConnector \\\n           --kv-transfer-config.kv_role kv_producer \\\n           --kv-transfer-config.kv_load_failure_policy fail \\\n           --kv-transfer-config.kv_connector_extra_config.backends+ LIBFABRIC \\\n           2>&1 | tee ${LOGDIR}/vllm-g${gid}-p${i}.log'\n    \"\n    tail -f \"${LOGDIR}/vllm-g${gid}-p${i}.log\" 2>/dev/null | sed \"s/^/[g${gid}-p${i}] /\" &\n  done\n\n  # Launch decode nodes\n  for i in $(seq 0 $((NUM_DECODE - 1))); do\n    local node=\"${gnodes[$((NUM_PREFILL + i))]}\"\n    local port=$((base_port + NUM_PREFILL + i))\n    srun --nodes=1 --nodelist=\"${node}\" bash -c \"\n      $(declare -f launch_container)\n      CONTAINER_IMAGE='${CONTAINER_IMAGE}' CONTAINER_MOUNT='${CONTAINER_MOUNT}' \\\n      launch_container 'vllm-g${gid}-d${i}' \\\n        'vllm serve ${SERVE_ARGS_STR} \\\n           --host 0.0.0.0 --port ${port} \\\n           --data-parallel-size ${DP_LOCAL} \\\n           --kv-transfer-config.kv_connector NixlConnector \\\n           --kv-transfer-config.kv_role kv_consumer \\\n           --kv-transfer-config.kv_load_failure_policy fail \\\n           --kv-transfer-config.kv_connector_extra_config.backends+ LIBFABRIC \\\n           2>&1 | tee ${LOGDIR}/vllm-g${gid}-d${i}.log'\n    \"\n    tail -f \"${LOGDIR}/vllm-g${gid}-d${i}.log\" 2>/dev/null | sed \"s/^/[g${gid}-d${i}] /\" &\n  done\n}\n\nstart_router() {\n  local router_args=\"\"\n\n  if (( NUM_PREFILL == 0 )); then\n    # Pure DP: round-robin across group endpoints\n    router_args=\"--worker-urls\"\n    for g in $(seq 0 $((ROUTE_SIZE - 1))); do\n      router_args=\"${router_args} http://${GROUP_IPS[$g]}:${GROUP_PORTS[$g]}\"\n    done\n    info \"Starting vllm-router (DP) on ${HEAD_NODE}:${ROUTER_PORT} ...\"\n    srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n      docker exec -d vllm-g0-n0 bash -c \\\n        'vllm-router \\\n           --policy round_robin \\\n           ${router_args} \\\n           --host 0.0.0.0 \\\n           --port ${ROUTER_PORT} \\\n           --intra-node-data-parallel-size ${DP_LOCAL} \\\n           2>&1 | tee ${LOGDIR}/vllm-router.log'\n    \"\n  else\n    # PD disaggregation: collect all prefill/decode endpoints across groups\n    local prefill_args=\"\" decode_args=\"\"\n    for g in $(seq 0 $((ROUTE_SIZE - 1))); do\n      local goffset=$((g * NODES_PER_GROUP))\n      local base_port=$((8000 + g * NODES_PER_GROUP))\n      for i in $(seq 0 $((NUM_PREFILL - 1))); do\n        local ip; ip=$(getent ahostsv4 \"${NODES[$((goffset + i))]}\" | head -1 | awk '{print $1}')\n        prefill_args=\"${prefill_args} --prefill http://${ip}:$((base_port + i))\"\n      done\n      for i in $(seq 0 $((NUM_DECODE - 1))); do\n        local ip; ip=$(getent ahostsv4 \"${NODES[$((goffset + NUM_PREFILL + i))]}\" | head -1 | awk '{print $1}')\n        decode_args=\"${decode_args} --decode http://${ip}:$((base_port + NUM_PREFILL + i))\"\n      done\n    done\n    info \"Starting vllm-router (PD) on ${HEAD_NODE}:${ROUTER_PORT} ...\"\n    srun --nodes=1 --nodelist=\"${HEAD_NODE}\" bash -c \"\n      docker exec -d vllm-g0-p0 bash -c \\\n        'vllm-router \\\n           --policy consistent_hash \\\n           --vllm-pd-disaggregation \\\n           ${prefill_args} \\\n           ${decode_args} \\\n           --host 0.0.0.0 \\\n           --port ${ROUTER_PORT} \\\n           --intra-node-data-parallel-size ${DP_LOCAL} \\\n           2>&1 | tee ${LOGDIR}/vllm-router.log'\n    \"\n  fi\n  tail -f \"${LOGDIR}/vllm-router.log\" 2>/dev/null | sed \"s/^/[router] /\" &\n}\n\nstart_groups() {\n  GROUP_IPS=()\n  GROUP_PORTS=()\n\n  for g in $(seq 0 $((ROUTE_SIZE - 1))); do\n    local goffset=$((g * NODES_PER_GROUP))\n    local gnodes=(\"${NODES[@]:$goffset:$NODES_PER_GROUP}\")\n    local ghead_ip; ghead_ip=$(getent ahostsv4 \"${gnodes[0]}\" | head -1 | awk '{print $1}')\n\n    if (( NUM_PREFILL == 0 )); then\n      local port=$((8000 + g))\n      start_dp_group \"$g\" \"$port\" \"${gnodes[@]}\"\n      GROUP_IPS+=(\"${ghead_ip}\")\n      GROUP_PORTS+=(\"${port}\")\n    else\n      local base_port=$((8000 + g * NODES_PER_GROUP))\n      start_pd_group \"$g\" \"$base_port\" \"${gnodes[@]}\"\n    fi\n  done\n}\n\nwait_all_groups() {\n  if (( NUM_PREFILL == 0 )); then\n    for g in $(seq 0 $((ROUTE_SIZE - 1))); do\n      wait_for_server \"${GROUP_IPS[$g]}\" \"${GROUP_PORTS[$g]}\" \"group-${g}\" || exit 1\n    done\n  else\n    for g in $(seq 0 $((ROUTE_SIZE - 1))); do\n      local goffset=$((g * NODES_PER_GROUP))\n      local base_port=$((8000 + g * NODES_PER_GROUP))\n      for i in $(seq 0 $((NUM_PREFILL - 1))); do\n        local ip; ip=$(getent ahostsv4 \"${NODES[$((goffset + i))]}\" | head -1 | awk '{print $1}')\n        wait_for_server \"${ip}\" \"$((base_port + i))\" \"g${g}-prefill-${i}\" || exit 1\n      done\n      for i in $(seq 0 $((NUM_DECODE - 1))); do\n        local ip; ip=$(getent ahostsv4 \"${NODES[$((goffset + NUM_PREFILL + i))]}\" | head -1 | awk '{print $1}')\n        wait_for_server \"${ip}\" \"$((base_port + NUM_PREFILL + i))\" \"g${g}-decode-${i}\" || exit 1\n      done\n    done\n  fi\n}\n\nprint_summary() {\n  info \"========================================\"\n  info \"All instances ready — mode=${MODE}\"\n  if (( ROUTE_SIZE > 1 || NUM_PREFILL > 0 )); then\n    info \"  Router: http://${HEAD_IP}:${ROUTER_PORT}\"\n    info \"\"\n    info \"  Test (via router):\"\n    info \"    curl http://${HEAD_IP}:${ROUTER_PORT}/v1/completions -H 'Content-Type: application/json' \\\\\"\n    info \"      -d '{\\\"model\\\":\\\"${MODEL}\\\",\\\"prompt\\\":\\\"Hello\\\",\\\"max_tokens\\\":64}'\"\n  else\n    info \"  Endpoint: http://${HEAD_IP}:8000\"\n    info \"\"\n    info \"  Test:\"\n    info \"    curl http://${HEAD_IP}:8000/v1/completions -H 'Content-Type: application/json' \\\\\"\n    info \"      -d '{\\\"model\\\":\\\"${MODEL}\\\",\\\"prompt\\\":\\\"Hello\\\",\\\"max_tokens\\\":64}'\"\n  fi\n  info \"========================================\"\n}\n\nmain() {\n  trap cleanup EXIT\n  cleanup\n  load_image\n\n  start_groups\n  wait_all_groups\n\n  if (( ROUTE_SIZE > 1 || NUM_PREFILL > 0 )); then\n    start_router\n  fi\n\n  print_summary\n  sleep infinity\n}\n\nmain\n"
  },
  {
    "path": "src/security/vulnerability_.py",
    "content": "\"\"\"Tests demonstrating security vulnerabilities and secure alternatives.\"\"\"\n\nimport pytest\nimport hmac\nimport secrets\nimport os\n\n\nclass TestTimingAttack:\n    \"\"\"Demonstrate timing attack vulnerability in string comparison.\"\"\"\n\n    def test_insecure_comparison(self):\n        \"\"\"Insecure comparison - vulnerable to timing attack.\"\"\"\n        secret = b\"correct_secret_token\"\n\n        def insecure_compare(a, b):\n            \"\"\"INSECURE: Returns early on mismatch.\"\"\"\n            if len(a) != len(b):\n                return False\n            for x, y in zip(a, b):\n                if x != y:\n                    return False\n            return True\n\n        # These comparisons take different amounts of time\n        assert insecure_compare(secret, secret) == True\n        assert insecure_compare(secret, b\"wrong_secret_token!\") == False\n        assert insecure_compare(secret, b\"c\") == False\n\n    def test_secure_comparison(self):\n        \"\"\"Secure comparison using hmac.compare_digest.\"\"\"\n        secret = b\"correct_secret_token\"\n\n        # Constant-time comparison - safe from timing attacks\n        assert hmac.compare_digest(secret, secret) == True\n        assert hmac.compare_digest(secret, b\"wrong_secret_token!\") == False\n\n\nclass TestWeakRandom:\n    \"\"\"Demonstrate weak vs strong random number generation.\"\"\"\n\n    def test_weak_random(self):\n        \"\"\"INSECURE: random module is predictable.\"\"\"\n        import random\n\n        # Mersenne Twister can be predicted after observing outputs\n        random.seed(12345)\n        values = [random.randint(0, 100) for _ in range(10)]\n\n        # Same seed produces same sequence - predictable!\n        random.seed(12345)\n        values2 = [random.randint(0, 100) for _ in range(10)]\n        assert values == values2\n\n    def test_secure_random(self):\n        \"\"\"SECURE: secrets module uses OS entropy.\"\"\"\n        # Cryptographically secure random\n        token1 = secrets.token_hex(16)\n        token2 = secrets.token_hex(16)\n\n        # Each call produces unique, unpredictable value\n        assert token1 != token2\n        assert len(token1) == 32\n\n\nclass TestSQLInjection:\n    \"\"\"Demonstrate SQL injection vulnerability.\"\"\"\n\n    def test_vulnerable_query_building(self):\n        \"\"\"INSECURE: String formatting in SQL.\"\"\"\n\n        def build_query_insecure(username):\n            # VULNERABLE: User input directly in query\n            return f\"SELECT * FROM users WHERE username = '{username}'\"\n\n        # Normal input\n        query = build_query_insecure(\"alice\")\n        assert query == \"SELECT * FROM users WHERE username = 'alice'\"\n\n        # Malicious input - SQL injection!\n        malicious = \"admin' OR '1'='1\"\n        query = build_query_insecure(malicious)\n        # This would return ALL users!\n        assert \"OR '1'='1'\" in query\n\n    def test_parameterized_query(self):\n        \"\"\"SECURE: Parameterized queries prevent injection.\"\"\"\n\n        def build_query_secure(username):\n            # Parameters are escaped by the database driver\n            return (\"SELECT * FROM users WHERE username = ?\", (username,))\n\n        query, params = build_query_secure(\"admin' OR '1'='1\")\n        # The malicious input is treated as a literal string\n        assert params == (\"admin' OR '1'='1\",)\n\n\nclass TestCommandInjection:\n    \"\"\"Demonstrate command injection vulnerability.\"\"\"\n\n    def test_vulnerable_shell_command(self):\n        \"\"\"INSECURE: User input in shell command.\"\"\"\n\n        def build_command_insecure(filename):\n            # VULNERABLE: Shell injection possible\n            return f\"cat {filename}\"\n\n        # Malicious input\n        malicious = \"file.txt; rm -rf /\"\n        cmd = build_command_insecure(malicious)\n        # Would execute: cat file.txt; rm -rf /\n        assert \"; rm -rf /\" in cmd\n\n    def test_secure_command(self):\n        \"\"\"SECURE: Use argument list, not shell string.\"\"\"\n        import shlex\n\n        def build_command_secure(filename):\n            # Validate and use list of arguments\n            if not filename.replace(\".\", \"\").replace(\"_\", \"\").isalnum():\n                raise ValueError(\"Invalid filename\")\n            return [\"cat\", filename]\n\n        # Malicious input is rejected\n        with pytest.raises(ValueError):\n            build_command_secure(\"file.txt; rm -rf /\")\n\n        # Valid input works\n        cmd = build_command_secure(\"file.txt\")\n        assert cmd == [\"cat\", \"file.txt\"]\n\n\nclass TestPickleVulnerability:\n    \"\"\"Demonstrate pickle deserialization vulnerability.\"\"\"\n\n    def test_pickle_code_execution(self):\n        \"\"\"INSECURE: Pickle can execute arbitrary code.\"\"\"\n        import pickle\n\n        class MaliciousPayload:\n            def __reduce__(self):\n                # This would execute when unpickled!\n                # return (os.system, (\"echo HACKED\",))\n                # For safety, we just return a harmless function\n                return (str, (\"PAYLOAD_EXECUTED\",))\n\n        # Attacker creates malicious pickle\n        malicious_data = pickle.dumps(MaliciousPayload())\n\n        # Victim unpickles - code executes!\n        result = pickle.loads(malicious_data)\n        assert result == \"PAYLOAD_EXECUTED\"\n\n    def test_safe_json(self):\n        \"\"\"SECURE: JSON cannot execute code.\"\"\"\n        import json\n\n        data = {\"user\": \"alice\", \"role\": \"admin\"}\n        serialized = json.dumps(data)\n        deserialized = json.loads(serialized)\n\n        assert deserialized == data\n        # JSON only supports basic types - no code execution\n\n\nclass TestHardcodedSecrets:\n    \"\"\"Demonstrate hardcoded secrets vulnerability.\"\"\"\n\n    def test_environment_variables(self):\n        \"\"\"SECURE: Use environment variables for secrets.\"\"\"\n        # Set a test secret\n        os.environ[\"TEST_API_KEY\"] = \"secret_key_123\"\n\n        # Read from environment\n        api_key = os.environ.get(\"TEST_API_KEY\")\n        assert api_key == \"secret_key_123\"\n\n        # Clean up\n        del os.environ[\"TEST_API_KEY\"]\n\n        # Missing secret should be handled\n        missing = os.environ.get(\"NONEXISTENT_KEY\")\n        assert missing is None\n\n\nclass TestAESModes:\n    \"\"\"Demonstrate AES mode vulnerabilities.\"\"\"\n\n    def test_ecb_mode_pattern_leak(self):\n        \"\"\"INSECURE: ECB mode leaks patterns in plaintext.\"\"\"\n        # ECB encrypts identical blocks to identical ciphertext\n        # This reveals patterns in the data\n\n        # Simulated ECB behavior (without actual crypto)\n        def fake_ecb_encrypt(blocks):\n            # Same input block -> same output (pattern leak!)\n            block_map = {}\n            result = []\n            for i, block in enumerate(blocks):\n                if block not in block_map:\n                    block_map[block] = f\"cipher_{i}\"\n                result.append(block_map[block])\n            return result\n\n        # Repeated plaintext blocks\n        blocks = [\"AAAA\", \"BBBB\", \"AAAA\", \"CCCC\", \"AAAA\"]\n        encrypted = fake_ecb_encrypt(blocks)\n\n        # Pattern is visible! Same plaintext -> same ciphertext\n        assert encrypted[0] == encrypted[2] == encrypted[4]\n\n    def test_cbc_needs_authentication(self):\n        \"\"\"AES-CBC without authentication is vulnerable.\"\"\"\n        # CBC provides confidentiality but not integrity\n        # Attacker can modify ciphertext without detection\n        # This enables padding oracle attacks\n\n        # AES-GCM provides both confidentiality AND integrity\n        # Tampering is detected and rejected\n        pass\n\n\nclass TestPasswordStorage:\n    \"\"\"Demonstrate password storage vulnerabilities.\"\"\"\n\n    def test_weak_hash(self):\n        \"\"\"INSECURE: MD5/SHA1 for passwords.\"\"\"\n        import hashlib\n\n        password = \"password123\"\n\n        # INSECURE: Fast hashes are easily brute-forced\n        md5_hash = hashlib.md5(password.encode()).hexdigest()\n        sha1_hash = hashlib.sha1(password.encode()).hexdigest()\n\n        # These can be cracked in seconds with rainbow tables\n        assert len(md5_hash) == 32\n        assert len(sha1_hash) == 40\n\n    def test_secure_password_hash(self):\n        \"\"\"SECURE: Use slow, salted password hashing.\"\"\"\n        # In production, use argon2-cffi or bcrypt\n        # This is a simplified demonstration\n\n        import hashlib\n\n        password = \"password123\"\n        salt = secrets.token_bytes(16)\n\n        # PBKDF2 with high iteration count (still not ideal, use Argon2)\n        hash_value = hashlib.pbkdf2_hmac(\n            \"sha256\", password.encode(), salt, iterations=100000\n        )\n\n        assert len(hash_value) == 32\n        assert len(salt) == 16\n"
  }
]