[{"data":1,"prerenderedAt":2243},["ShallowReactive",2],{"page-\u002Fpython\u002F25-packaging-and-virtual-environments":3},{"id":4,"title":5,"body":6,"description":27,"extension":2237,"meta":2238,"navigation":164,"path":2239,"seo":2240,"stem":2241,"__hash__":2242},"content\u002Fpython\u002F25-packaging-and-virtual-environments.md","25 — Packaging & Virtual Environments",{"type":7,"value":8,"toc":2215},"minimark",[9,13,18,82,108,115,211,252,292,302,354,386,393,414,466,488,520,571,592,598,835,890,924,948,954,1099,1154,1160,1272,1428,1461,1465,1547,1595,1635,1641,1645,1681,1736,1776,1780,1854,1858,1957,1961,1967,2021,2152,2156,2211],[10,11,5],"h1",{"id":12},"_25-packaging-virtual-environments",[14,15,17],"h2",{"id":16},"why-isolation-matters","Why Isolation Matters",[19,20,22],"code-wrapper",{"language":21},"bash",[23,24,28],"pre",{"className":25,"code":26,"language":21,"meta":27,"style":27},"language-bash shiki shiki-themes github-light github-dark","pip install django==4.2\n# later, a different project on the same machine:\npip install django==5.0\n# without isolation, the SECOND install silently overwrites the first —\n# both projects now share one global django version, and one of them breaks\n","",[29,30,31,51,58,70,76],"code",{"__ignoreMap":27},[32,33,36,40,44,47],"span",{"class":34,"line":35},"line",1,[32,37,39],{"class":38},"sIsaT","pip",[32,41,43],{"class":42},"sJ6F3"," install",[32,45,46],{"class":42}," django==",[32,48,50],{"class":49},"snvgF","4.2\n",[32,52,54],{"class":34,"line":53},2,[32,55,57],{"class":56},"sdCPZ","# later, a different project on the same machine:\n",[32,59,61,63,65,67],{"class":34,"line":60},3,[32,62,39],{"class":38},[32,64,43],{"class":42},[32,66,46],{"class":42},[32,68,69],{"class":49},"5.0\n",[32,71,73],{"class":34,"line":72},4,[32,74,75],{"class":56},"# without isolation, the SECOND install silently overwrites the first —\n",[32,77,79],{"class":34,"line":78},5,[32,80,81],{"class":56},"# both projects now share one global django version, and one of them breaks\n",[83,84,85,86,89,90,92,93,96,97,101,102,104,105,107],"p",{},"Every Python installation has exactly one ",[29,87,88],{},"site-packages"," directory per interpreter — installing a package with ",[29,91,39],{}," (with no virtual environment active) writes into that shared, global location. Two projects on the same machine requiring different versions of the same dependency cannot coexist under a single global install; the second ",[29,94,95],{},"pip install"," simply replaces the first. ",[98,99,100],"strong",{},"Best practice",": never run ",[29,103,95],{}," against the system or global Python interpreter for project work — always activate a virtual environment first, so each project gets its own isolated ",[29,106,88],{},".",[14,109,111,114],{"id":110},"venv-the-standard-librarys-built-in-tool",[29,112,113],{},"venv",": The Standard Library's Built-In Tool",[19,116,117],{"language":21},[23,118,120],{"className":25,"code":119,"language":21,"meta":27,"style":27},"python3 -m venv .venv          # creates a .venv\u002F directory with its own interpreter and site-packages\nsource .venv\u002Fbin\u002Factivate      # macOS\u002FLinux\n# .venv\\Scripts\\activate       # Windows (cmd.exe)\n# .venv\\Scripts\\Activate.ps1   # Windows (PowerShell)\n\nwhich python                    # now points INSIDE .venv\u002F, not the system interpreter\npython -m pip install requests  # installs into .venv\u002Flib\u002F...\u002Fsite-packages, not globally\n\ndeactivate                       # restores the shell's original PATH\n",[29,121,122,139,150,155,160,166,178,197,202],{"__ignoreMap":27},[32,123,124,127,130,133,136],{"class":34,"line":35},[32,125,126],{"class":38},"python3",[32,128,129],{"class":49}," -m",[32,131,132],{"class":42}," venv",[32,134,135],{"class":42}," .venv",[32,137,138],{"class":56},"          # creates a .venv\u002F directory with its own interpreter and site-packages\n",[32,140,141,144,147],{"class":34,"line":53},[32,142,143],{"class":49},"source",[32,145,146],{"class":42}," .venv\u002Fbin\u002Factivate",[32,148,149],{"class":56},"      # macOS\u002FLinux\n",[32,151,152],{"class":34,"line":60},[32,153,154],{"class":56},"# .venv\\Scripts\\activate       # Windows (cmd.exe)\n",[32,156,157],{"class":34,"line":72},[32,158,159],{"class":56},"# .venv\\Scripts\\Activate.ps1   # Windows (PowerShell)\n",[32,161,162],{"class":34,"line":78},[32,163,165],{"emptyLinePlaceholder":164},true,"\n",[32,167,169,172,175],{"class":34,"line":168},6,[32,170,171],{"class":49},"which",[32,173,174],{"class":42}," python",[32,176,177],{"class":56},"                    # now points INSIDE .venv\u002F, not the system interpreter\n",[32,179,181,184,186,189,191,194],{"class":34,"line":180},7,[32,182,183],{"class":38},"python",[32,185,129],{"class":49},[32,187,188],{"class":42}," pip",[32,190,43],{"class":42},[32,192,193],{"class":42}," requests",[32,195,196],{"class":56},"  # installs into .venv\u002Flib\u002F...\u002Fsite-packages, not globally\n",[32,198,200],{"class":34,"line":199},8,[32,201,165],{"emptyLinePlaceholder":164},[32,203,205,208],{"class":34,"line":204},9,[32,206,207],{"class":38},"deactivate",[32,209,210],{"class":56},"                       # restores the shell's original PATH\n",[83,212,213,215,216,219,220,223,224,227,228,230,231,233,234,236,237,240,241,243,244,247,248,251],{},[29,214,113],{}," works by prepending the environment's ",[29,217,218],{},"bin\u002F"," (or ",[29,221,222],{},"Scripts\u002F"," on Windows) directory to ",[29,225,226],{},"PATH"," and pointing ",[29,229,183],{},"\u002F",[29,232,39],{}," at copies (or symlinks) of the interpreter scoped to that directory — every ",[29,235,95],{}," afterward lands in ",[29,238,239],{},".venv\u002Flib\u002FpythonX.Y\u002Fsite-packages"," instead of the system location. ",[98,242,100],{},": name the directory ",[29,245,246],{},".venv"," (the convention most tools, editors, and ",[29,249,250],{},".gitignore"," templates already expect) and never commit it to version control — it's large, platform-specific, and trivially reproducible from a lockfile or requirements file.",[19,253,255],{"language":254},"ini",[23,256,259],{"className":257,"code":258,"language":254,"meta":27,"style":27},"language-ini shiki shiki-themes github-light github-dark","# .gitignore\n.venv\u002F\n__pycache__\u002F\n*.pyc\n.pytest_cache\u002F\n.mypy_cache\u002F\n",[29,260,261,266,272,277,282,287],{"__ignoreMap":27},[32,262,263],{"class":34,"line":35},[32,264,265],{"class":56},"# .gitignore\n",[32,267,268],{"class":34,"line":53},[32,269,271],{"class":270},"ssxIu",".venv\u002F\n",[32,273,274],{"class":34,"line":60},[32,275,276],{"class":270},"__pycache__\u002F\n",[32,278,279],{"class":34,"line":72},[32,280,281],{"class":270},"*.pyc\n",[32,283,284],{"class":34,"line":78},[32,285,286],{"class":270},".pytest_cache\u002F\n",[32,288,289],{"class":34,"line":168},[32,290,291],{"class":270},".mypy_cache\u002F\n",[293,294,296,299,300],"h3",{"id":295},"python-m-pip-vs-bare-pip",[29,297,298],{},"python -m pip"," vs bare ",[29,301,39],{},[19,303,304],{"language":21},[23,305,307],{"className":25,"code":306,"language":21,"meta":27,"style":27},"pip install requests\n# WRONG in some setups — if a stray global `pip` executable is earlier on PATH\n# than the venv's, this can install into the WRONG environment silently\n\npython -m pip install requests\n# CORRECT and unambiguous — always installs into whichever interpreter `python`\n# currently resolves to, guaranteed to match the active venv\n",[29,308,309,318,323,328,332,344,349],{"__ignoreMap":27},[32,310,311,313,315],{"class":34,"line":35},[32,312,39],{"class":38},[32,314,43],{"class":42},[32,316,317],{"class":42}," requests\n",[32,319,320],{"class":34,"line":53},[32,321,322],{"class":56},"# WRONG in some setups — if a stray global `pip` executable is earlier on PATH\n",[32,324,325],{"class":34,"line":60},[32,326,327],{"class":56},"# than the venv's, this can install into the WRONG environment silently\n",[32,329,330],{"class":34,"line":72},[32,331,165],{"emptyLinePlaceholder":164},[32,333,334,336,338,340,342],{"class":34,"line":78},[32,335,183],{"class":38},[32,337,129],{"class":49},[32,339,188],{"class":42},[32,341,43],{"class":42},[32,343,317],{"class":42},[32,345,346],{"class":34,"line":168},[32,347,348],{"class":56},"# CORRECT and unambiguous — always installs into whichever interpreter `python`\n",[32,350,351],{"class":34,"line":180},[32,352,353],{"class":56},"# currently resolves to, guaranteed to match the active venv\n",[83,355,356,358,359,361,362,364,365,367,368,370,371,378,379,381,382,385],{},[29,357,39],{}," as a bare command is resolved via ",[29,360,226],{}," lookup, which can point to an unexpected ",[29,363,39],{}," executable if a virtual environment wasn't activated cleanly (a common issue in CI scripts, subshells, or IDE-launched terminals). ",[29,366,298],{}," instead runs ",[29,369,39],{}," as a module ",[372,373,374,375,377],"em",{},"of the currently resolved ",[29,376,183],{}," interpreter",", removing that entire class of \"installed into the wrong place\" bugs. ",[98,380,100],{},": use ",[29,383,384],{},"python -m pip install ..."," in scripts and CI, especially anywhere the active environment can't be visually confirmed.",[14,387,389,392],{"id":388},"requirementstxt-simple-but-limited",[29,390,391],{},"requirements.txt",": Simple, but Limited",[19,394,395],{"language":21},[23,396,398],{"className":25,"code":397,"language":21,"meta":27,"style":27},"pip freeze > requirements.txt\n",[29,399,400],{"__ignoreMap":27},[32,401,402,404,407,411],{"class":34,"line":35},[32,403,39],{"class":38},[32,405,406],{"class":42}," freeze",[32,408,410],{"class":409},"svdQ7"," >",[32,412,413],{"class":42}," requirements.txt\n",[19,415,416],{"language":254},[23,417,419],{"className":257,"code":418,"language":254,"meta":27,"style":27},"# requirements.txt — generated by pip freeze; PINS EVERY installed package, including transitive deps\ncertifi==2024.7.4\ncharset-normalizer==3.3.2\nidna==3.7\nrequests==2.32.3\nurllib3==2.2.2\n",[29,420,421,426,434,442,450,458],{"__ignoreMap":27},[32,422,423],{"class":34,"line":35},[32,424,425],{"class":56},"# requirements.txt — generated by pip freeze; PINS EVERY installed package, including transitive deps\n",[32,427,428,431],{"class":34,"line":53},[32,429,430],{"class":409},"certifi",[32,432,433],{"class":270},"==2024.7.4\n",[32,435,436,439],{"class":34,"line":60},[32,437,438],{"class":409},"charset-normalizer",[32,440,441],{"class":270},"==3.3.2\n",[32,443,444,447],{"class":34,"line":72},[32,445,446],{"class":409},"idna",[32,448,449],{"class":270},"==3.7\n",[32,451,452,455],{"class":34,"line":78},[32,453,454],{"class":409},"requests",[32,456,457],{"class":270},"==2.32.3\n",[32,459,460,463],{"class":34,"line":168},[32,461,462],{"class":409},"urllib3",[32,464,465],{"class":270},"==2.2.2\n",[19,467,468],{"language":21},[23,469,471],{"className":25,"code":470,"language":21,"meta":27,"style":27},"python -m pip install -r requirements.txt\n",[29,472,473],{"__ignoreMap":27},[32,474,475,477,479,481,483,486],{"class":34,"line":35},[32,476,183],{"class":38},[32,478,129],{"class":49},[32,480,188],{"class":42},[32,482,43],{"class":42},[32,484,485],{"class":49}," -r",[32,487,413],{"class":42},[83,489,490,493,494,497,498,500,501,503,504,503,506,508,509,512,513,515,516,519],{},[29,491,492],{},"pip freeze"," dumps the ",[372,495,496],{},"entire"," environment — direct dependencies (",[29,499,454],{},") mixed indiscriminately with everything they pulled in transitively (",[29,502,462],{},", ",[29,505,446],{},[29,507,430],{},") — with no distinction between \"I chose this\" and \"this got installed as a side effect.\" ",[98,510,511],{},"The core problem",": ",[29,514,391],{}," has no concept of dependency ",[372,517,518],{},"groups"," (dev vs production vs test), no way to express version ranges vs exact pins in the same file cleanly, and no metadata about the project itself (name, version, entry points) — it's a flat list of pins, nothing more.",[19,521,522],{"language":254},[23,523,525],{"className":257,"code":524,"language":254,"meta":27,"style":27},"# requirements.txt — hand-written, direct dependencies only, loose version ranges\nrequests>=2.31,\u003C3.0\nclick>=8.1\n\n# requirements-dev.txt — a common convention to separate concerns\n-r requirements.txt\npytest>=8.0\nruff>=0.5\nmypy>=1.10\n",[29,526,527,532,537,542,546,551,556,561,566],{"__ignoreMap":27},[32,528,529],{"class":34,"line":35},[32,530,531],{"class":56},"# requirements.txt — hand-written, direct dependencies only, loose version ranges\n",[32,533,534],{"class":34,"line":53},[32,535,536],{"class":270},"requests>=2.31,\u003C3.0\n",[32,538,539],{"class":34,"line":60},[32,540,541],{"class":270},"click>=8.1\n",[32,543,544],{"class":34,"line":72},[32,545,165],{"emptyLinePlaceholder":164},[32,547,548],{"class":34,"line":78},[32,549,550],{"class":56},"# requirements-dev.txt — a common convention to separate concerns\n",[32,552,553],{"class":34,"line":168},[32,554,555],{"class":270},"-r requirements.txt\n",[32,557,558],{"class":34,"line":180},[32,559,560],{"class":270},"pytest>=8.0\n",[32,562,563],{"class":34,"line":199},[32,564,565],{"class":270},"ruff>=0.5\n",[32,567,568],{"class":34,"line":204},[32,569,570],{"class":270},"mypy>=1.10\n",[83,572,573,574,577,578,580,581,584,585,588,589,591],{},"A common pre-",[29,575,576],{},"pyproject.toml"," convention splits ",[29,579,391],{}," (production dependencies, loose ranges) from ",[29,582,583],{},"requirements-dev.txt"," (adds testing\u002Flinting tools, ",[29,586,587],{},"-r requirements.txt"," pulls in the base file) — workable, but entirely a hand-maintained convention with no tooling enforcing it, unlike the standardized structure ",[29,590,576],{}," provides.",[14,593,595,597],{"id":594},"pyprojecttoml-the-modern-standard",[29,596,576],{},": The Modern Standard",[19,599,601],{"language":600},"toml",[23,602,605],{"className":603,"code":604,"language":600,"meta":27,"style":27},"language-toml shiki shiki-themes github-light github-dark","[project]\nname = \"orders-service\"\nversion = \"1.4.0\"\ndescription = \"Order processing microservice\"\nreadme = \"README.md\"\nrequires-python = \">=3.11\"\nlicense = { text = \"MIT\" }\nauthors = [{ name = \"Ada Lovelace\", email = \"ada@example.com\" }]\ndependencies = [\n    \"requests>=2.31,\u003C3.0\",\n    \"pydantic>=2.7\",\n    \"click>=8.1\",\n]\n\n[project.optional-dependencies]\ndev = [\"pytest>=8.0\", \"ruff>=0.5\", \"mypy>=1.10\"]\ndocs = [\"mkdocs>=1.6\"]\n\n[project.scripts]\norders-cli = \"orders_service.cli:main\"\n\n[build-system]\nrequires = [\"hatchling\"]\nbuild-backend = \"hatchling.build\"\n",[29,606,607,618,626,634,642,650,658,669,686,691,700,708,716,721,726,740,761,772,777,791,800,805,815,826],{"__ignoreMap":27},[32,608,609,612,615],{"class":34,"line":35},[32,610,611],{"class":270},"[",[32,613,614],{"class":38},"project",[32,616,617],{"class":270},"]\n",[32,619,620,623],{"class":34,"line":53},[32,621,622],{"class":270},"name = ",[32,624,625],{"class":42},"\"orders-service\"\n",[32,627,628,631],{"class":34,"line":60},[32,629,630],{"class":270},"version = ",[32,632,633],{"class":42},"\"1.4.0\"\n",[32,635,636,639],{"class":34,"line":72},[32,637,638],{"class":270},"description = ",[32,640,641],{"class":42},"\"Order processing microservice\"\n",[32,643,644,647],{"class":34,"line":78},[32,645,646],{"class":270},"readme = ",[32,648,649],{"class":42},"\"README.md\"\n",[32,651,652,655],{"class":34,"line":168},[32,653,654],{"class":270},"requires-python = ",[32,656,657],{"class":42},"\">=3.11\"\n",[32,659,660,663,666],{"class":34,"line":180},[32,661,662],{"class":270},"license = { text = ",[32,664,665],{"class":42},"\"MIT\"",[32,667,668],{"class":270}," }\n",[32,670,671,674,677,680,683],{"class":34,"line":199},[32,672,673],{"class":270},"authors = [{ name = ",[32,675,676],{"class":42},"\"Ada Lovelace\"",[32,678,679],{"class":270},", email = ",[32,681,682],{"class":42},"\"ada@example.com\"",[32,684,685],{"class":270}," }]\n",[32,687,688],{"class":34,"line":204},[32,689,690],{"class":270},"dependencies = [\n",[32,692,694,697],{"class":34,"line":693},10,[32,695,696],{"class":42},"    \"requests>=2.31,\u003C3.0\"",[32,698,699],{"class":270},",\n",[32,701,703,706],{"class":34,"line":702},11,[32,704,705],{"class":42},"    \"pydantic>=2.7\"",[32,707,699],{"class":270},[32,709,711,714],{"class":34,"line":710},12,[32,712,713],{"class":42},"    \"click>=8.1\"",[32,715,699],{"class":270},[32,717,719],{"class":34,"line":718},13,[32,720,617],{"class":270},[32,722,724],{"class":34,"line":723},14,[32,725,165],{"emptyLinePlaceholder":164},[32,727,729,731,733,735,738],{"class":34,"line":728},15,[32,730,611],{"class":270},[32,732,614],{"class":38},[32,734,107],{"class":270},[32,736,737],{"class":38},"optional-dependencies",[32,739,617],{"class":270},[32,741,743,746,749,751,754,756,759],{"class":34,"line":742},16,[32,744,745],{"class":270},"dev = [",[32,747,748],{"class":42},"\"pytest>=8.0\"",[32,750,503],{"class":270},[32,752,753],{"class":42},"\"ruff>=0.5\"",[32,755,503],{"class":270},[32,757,758],{"class":42},"\"mypy>=1.10\"",[32,760,617],{"class":270},[32,762,764,767,770],{"class":34,"line":763},17,[32,765,766],{"class":270},"docs = [",[32,768,769],{"class":42},"\"mkdocs>=1.6\"",[32,771,617],{"class":270},[32,773,775],{"class":34,"line":774},18,[32,776,165],{"emptyLinePlaceholder":164},[32,778,780,782,784,786,789],{"class":34,"line":779},19,[32,781,611],{"class":270},[32,783,614],{"class":38},[32,785,107],{"class":270},[32,787,788],{"class":38},"scripts",[32,790,617],{"class":270},[32,792,794,797],{"class":34,"line":793},20,[32,795,796],{"class":270},"orders-cli = ",[32,798,799],{"class":42},"\"orders_service.cli:main\"\n",[32,801,803],{"class":34,"line":802},21,[32,804,165],{"emptyLinePlaceholder":164},[32,806,808,810,813],{"class":34,"line":807},22,[32,809,611],{"class":270},[32,811,812],{"class":38},"build-system",[32,814,617],{"class":270},[32,816,818,821,824],{"class":34,"line":817},23,[32,819,820],{"class":270},"requires = [",[32,822,823],{"class":42},"\"hatchling\"",[32,825,617],{"class":270},[32,827,829,832],{"class":34,"line":828},24,[32,830,831],{"class":270},"build-backend = ",[32,833,834],{"class":42},"\"hatchling.build\"\n",[83,836,837,839,840,230,847,852,853,503,856,859,860,862,863,866,867,230,870,873,874,877,878,503,880,503,883,503,886,889],{},[29,838,576],{}," (standardized by ",[841,842,846],"a",{"href":843,"rel":844},"https:\u002F\u002Fpeps.python.org\u002Fpep-0517\u002F",[845],"nofollow","PEP 517",[841,848,851],{"href":849,"rel":850},"https:\u002F\u002Fpeps.python.org\u002Fpep-0621\u002F",[845],"PEP 621",") unifies what used to be scattered across ",[29,854,855],{},"setup.py",[29,857,858],{},"setup.cfg",", and ",[29,861,391],{}," into one declarative file: project metadata, dependencies (with named ",[372,864,865],{},"optional groups"," like ",[29,868,869],{},"dev",[29,871,872],{},"docs",", installed via ",[29,875,876],{},"pip install \".[dev]\"","), console-script entry points, and the build backend, all in a single tool-agnostic format that ",[29,879,39],{},[29,881,882],{},"build",[29,884,885],{},"poetry",[29,887,888],{},"uv",", and every modern packaging tool understand.",[19,891,892],{"language":21},[23,893,895],{"className":25,"code":894,"language":21,"meta":27,"style":27},"pip install -e \".[dev]\"\n# -e : \"editable install\" — changes to source files take effect immediately,\n#      no reinstall needed; standard for local development\n# \".[dev]\" : install the current directory's project, plus its \"dev\" optional group\n",[29,896,897,909,914,919],{"__ignoreMap":27},[32,898,899,901,903,906],{"class":34,"line":35},[32,900,39],{"class":38},[32,902,43],{"class":42},[32,904,905],{"class":49}," -e",[32,907,908],{"class":42}," \".[dev]\"\n",[32,910,911],{"class":34,"line":53},[32,912,913],{"class":56},"# -e : \"editable install\" — changes to source files take effect immediately,\n",[32,915,916],{"class":34,"line":60},[32,917,918],{"class":56},"#      no reinstall needed; standard for local development\n",[32,920,921],{"class":34,"line":72},[32,922,923],{"class":56},"# \".[dev]\" : install the current directory's project, plus its \"dev\" optional group\n",[83,925,926,928,929,931,932,934,935,937,938,941,942,944,945,947],{},[98,927,100],{},": for any new project, start with ",[29,930,576],{},", not ",[29,933,391],{}," — it's the direction the entire ecosystem has moved (",[29,936,855],{},"-based packaging is legacy as of recent ",[29,939,940],{},"setuptools","\u002FPyPA guidance), and tools like ",[29,943,888],{}," and ",[29,946,885],{}," are built around it natively.",[14,949,951,953],{"id":950},"uv-the-fast-modern-toolchain",[29,952,888],{},": The Fast Modern Toolchain",[19,955,956],{"language":21},[23,957,959],{"className":25,"code":958,"language":21,"meta":27,"style":27},"curl -LsSf https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.sh | sh\n\nuv init orders-service               # scaffolds pyproject.toml + a starter layout\ncd orders-service\n\nuv add requests pydantic             # adds to pyproject.toml AND installs, resolving the whole tree\nuv add --dev pytest ruff mypy        # adds to the \"dev\" dependency group specifically\n\nuv run pytest                        # runs INSIDE the project's venv automatically — no activate needed\nuv sync                               # installs exactly what uv.lock pins, reproducibly\n\nuv python install 3.12                # uv can even manage Python interpreter versions itself\nuv python pin 3.12\n",[29,960,961,978,982,995,1003,1007,1022,1043,1047,1059,1069,1073,1087],{"__ignoreMap":27},[32,962,963,966,969,972,975],{"class":34,"line":35},[32,964,965],{"class":38},"curl",[32,967,968],{"class":49}," -LsSf",[32,970,971],{"class":42}," https:\u002F\u002Fastral.sh\u002Fuv\u002Finstall.sh",[32,973,974],{"class":409}," |",[32,976,977],{"class":38}," sh\n",[32,979,980],{"class":34,"line":53},[32,981,165],{"emptyLinePlaceholder":164},[32,983,984,986,989,992],{"class":34,"line":60},[32,985,888],{"class":38},[32,987,988],{"class":42}," init",[32,990,991],{"class":42}," orders-service",[32,993,994],{"class":56},"               # scaffolds pyproject.toml + a starter layout\n",[32,996,997,1000],{"class":34,"line":72},[32,998,999],{"class":49},"cd",[32,1001,1002],{"class":42}," orders-service\n",[32,1004,1005],{"class":34,"line":78},[32,1006,165],{"emptyLinePlaceholder":164},[32,1008,1009,1011,1014,1016,1019],{"class":34,"line":168},[32,1010,888],{"class":38},[32,1012,1013],{"class":42}," add",[32,1015,193],{"class":42},[32,1017,1018],{"class":42}," pydantic",[32,1020,1021],{"class":56},"             # adds to pyproject.toml AND installs, resolving the whole tree\n",[32,1023,1024,1026,1028,1031,1034,1037,1040],{"class":34,"line":180},[32,1025,888],{"class":38},[32,1027,1013],{"class":42},[32,1029,1030],{"class":49}," --dev",[32,1032,1033],{"class":42}," pytest",[32,1035,1036],{"class":42}," ruff",[32,1038,1039],{"class":42}," mypy",[32,1041,1042],{"class":56},"        # adds to the \"dev\" dependency group specifically\n",[32,1044,1045],{"class":34,"line":199},[32,1046,165],{"emptyLinePlaceholder":164},[32,1048,1049,1051,1054,1056],{"class":34,"line":204},[32,1050,888],{"class":38},[32,1052,1053],{"class":42}," run",[32,1055,1033],{"class":42},[32,1057,1058],{"class":56},"                        # runs INSIDE the project's venv automatically — no activate needed\n",[32,1060,1061,1063,1066],{"class":34,"line":693},[32,1062,888],{"class":38},[32,1064,1065],{"class":42}," sync",[32,1067,1068],{"class":56},"                               # installs exactly what uv.lock pins, reproducibly\n",[32,1070,1071],{"class":34,"line":702},[32,1072,165],{"emptyLinePlaceholder":164},[32,1074,1075,1077,1079,1081,1084],{"class":34,"line":710},[32,1076,888],{"class":38},[32,1078,174],{"class":42},[32,1080,43],{"class":42},[32,1082,1083],{"class":49}," 3.12",[32,1085,1086],{"class":56},"                # uv can even manage Python interpreter versions itself\n",[32,1088,1089,1091,1093,1096],{"class":34,"line":718},[32,1090,888],{"class":38},[32,1092,174],{"class":42},[32,1094,1095],{"class":42}," pin",[32,1097,1098],{"class":49}," 3.12\n",[83,1100,1101,1103,1104,1107,1108,230,1110,230,1112,230,1115,1118,1119,1121,1122,1124,1125,1128,1129,1132,1133,1136,1137,1139,1140,512,1142,1144,1145,1147,1148,1150,1151,1153],{},[29,1102,888],{}," (from Astral, the makers of ",[29,1105,1106],{},"ruff",") reimplements the entire ",[29,1109,39],{},[29,1111,113],{},[29,1113,1114],{},"pip-tools",[29,1116,1117],{},"pyenv"," toolchain in Rust, and is dramatically faster (often 10-100x) at dependency resolution and installation than ",[29,1120,39],{},", largely due to a global cache and a from-scratch resolver rather than ",[29,1123,39],{},"'s historically backtracking one. ",[29,1126,1127],{},"uv.lock"," is a fully resolved, hash-pinned lockfile (every transitive dependency, exact version, exact hash) — ",[29,1130,1131],{},"uv sync"," reproduces the ",[372,1134,1135],{},"exact"," same environment on any machine, unlike a loose ",[29,1138,391],{}," range. ",[98,1141,100],{},[29,1143,888],{}," has rapidly become the recommended default for new Python projects as of 2025 — reach for it before ",[29,1146,39],{},"+",[29,1149,113],{}," manually, or ",[29,1152,885],{},", unless a project or team already has a specific reason to use something else.",[14,1155,1157,1159],{"id":1156},"poetry-the-established-alternative",[29,1158,885],{},": The Established Alternative",[19,1161,1162],{"language":21},[23,1163,1165],{"className":25,"code":1164,"language":21,"meta":27,"style":27},"curl -sSL https:\u002F\u002Finstall.python-poetry.org | python3 -\n\npoetry new orders-service\ncd orders-service\n\npoetry add requests pydantic\npoetry add --group dev pytest ruff mypy\n\npoetry install                 # creates\u002Fupdates the venv AND installs from poetry.lock\npoetry run pytest              # runs a command inside poetry's managed venv\npoetry shell                   # drops into an activated shell inside the venv\n",[29,1166,1167,1185,1189,1198,1204,1208,1219,1238,1242,1251,1262],{"__ignoreMap":27},[32,1168,1169,1171,1174,1177,1179,1182],{"class":34,"line":35},[32,1170,965],{"class":38},[32,1172,1173],{"class":49}," -sSL",[32,1175,1176],{"class":42}," https:\u002F\u002Finstall.python-poetry.org",[32,1178,974],{"class":409},[32,1180,1181],{"class":38}," python3",[32,1183,1184],{"class":42}," -\n",[32,1186,1187],{"class":34,"line":53},[32,1188,165],{"emptyLinePlaceholder":164},[32,1190,1191,1193,1196],{"class":34,"line":60},[32,1192,885],{"class":38},[32,1194,1195],{"class":42}," new",[32,1197,1002],{"class":42},[32,1199,1200,1202],{"class":34,"line":72},[32,1201,999],{"class":49},[32,1203,1002],{"class":42},[32,1205,1206],{"class":34,"line":78},[32,1207,165],{"emptyLinePlaceholder":164},[32,1209,1210,1212,1214,1216],{"class":34,"line":168},[32,1211,885],{"class":38},[32,1213,1013],{"class":42},[32,1215,193],{"class":42},[32,1217,1218],{"class":42}," pydantic\n",[32,1220,1221,1223,1225,1228,1231,1233,1235],{"class":34,"line":180},[32,1222,885],{"class":38},[32,1224,1013],{"class":42},[32,1226,1227],{"class":49}," --group",[32,1229,1230],{"class":42}," dev",[32,1232,1033],{"class":42},[32,1234,1036],{"class":42},[32,1236,1237],{"class":42}," mypy\n",[32,1239,1240],{"class":34,"line":199},[32,1241,165],{"emptyLinePlaceholder":164},[32,1243,1244,1246,1248],{"class":34,"line":204},[32,1245,885],{"class":38},[32,1247,43],{"class":42},[32,1249,1250],{"class":56},"                 # creates\u002Fupdates the venv AND installs from poetry.lock\n",[32,1252,1253,1255,1257,1259],{"class":34,"line":693},[32,1254,885],{"class":38},[32,1256,1053],{"class":42},[32,1258,1033],{"class":42},[32,1260,1261],{"class":56},"              # runs a command inside poetry's managed venv\n",[32,1263,1264,1266,1269],{"class":34,"line":702},[32,1265,885],{"class":38},[32,1267,1268],{"class":42}," shell",[32,1270,1271],{"class":56},"                   # drops into an activated shell inside the venv\n",[19,1273,1274],{"language":600},[23,1275,1277],{"className":603,"code":1276,"language":600,"meta":27,"style":27},"[tool.poetry]\nname = \"orders-service\"\nversion = \"1.4.0\"\ndescription = \"Order processing microservice\"\n\n[tool.poetry.dependencies]\npython = \"^3.11\"\nrequests = \"^2.31\"\npydantic = \"^2.7\"\n\n[tool.poetry.group.dev.dependencies]\npytest = \"^8.0\"\nruff = \"^0.5\"\n\n[build-system]\nrequires = [\"poetry-core\"]\nbuild-backend = \"poetry.core.masonry.api\"\n",[29,1278,1279,1292,1298,1304,1310,1314,1331,1339,1347,1355,1359,1384,1392,1400,1404,1412,1421],{"__ignoreMap":27},[32,1280,1281,1283,1286,1288,1290],{"class":34,"line":35},[32,1282,611],{"class":270},[32,1284,1285],{"class":38},"tool",[32,1287,107],{"class":270},[32,1289,885],{"class":38},[32,1291,617],{"class":270},[32,1293,1294,1296],{"class":34,"line":53},[32,1295,622],{"class":270},[32,1297,625],{"class":42},[32,1299,1300,1302],{"class":34,"line":60},[32,1301,630],{"class":270},[32,1303,633],{"class":42},[32,1305,1306,1308],{"class":34,"line":72},[32,1307,638],{"class":270},[32,1309,641],{"class":42},[32,1311,1312],{"class":34,"line":78},[32,1313,165],{"emptyLinePlaceholder":164},[32,1315,1316,1318,1320,1322,1324,1326,1329],{"class":34,"line":168},[32,1317,611],{"class":270},[32,1319,1285],{"class":38},[32,1321,107],{"class":270},[32,1323,885],{"class":38},[32,1325,107],{"class":270},[32,1327,1328],{"class":38},"dependencies",[32,1330,617],{"class":270},[32,1332,1333,1336],{"class":34,"line":180},[32,1334,1335],{"class":270},"python = ",[32,1337,1338],{"class":42},"\"^3.11\"\n",[32,1340,1341,1344],{"class":34,"line":199},[32,1342,1343],{"class":270},"requests = ",[32,1345,1346],{"class":42},"\"^2.31\"\n",[32,1348,1349,1352],{"class":34,"line":204},[32,1350,1351],{"class":270},"pydantic = ",[32,1353,1354],{"class":42},"\"^2.7\"\n",[32,1356,1357],{"class":34,"line":693},[32,1358,165],{"emptyLinePlaceholder":164},[32,1360,1361,1363,1365,1367,1369,1371,1374,1376,1378,1380,1382],{"class":34,"line":702},[32,1362,611],{"class":270},[32,1364,1285],{"class":38},[32,1366,107],{"class":270},[32,1368,885],{"class":38},[32,1370,107],{"class":270},[32,1372,1373],{"class":38},"group",[32,1375,107],{"class":270},[32,1377,869],{"class":38},[32,1379,107],{"class":270},[32,1381,1328],{"class":38},[32,1383,617],{"class":270},[32,1385,1386,1389],{"class":34,"line":710},[32,1387,1388],{"class":270},"pytest = ",[32,1390,1391],{"class":42},"\"^8.0\"\n",[32,1393,1394,1397],{"class":34,"line":718},[32,1395,1396],{"class":270},"ruff = ",[32,1398,1399],{"class":42},"\"^0.5\"\n",[32,1401,1402],{"class":34,"line":723},[32,1403,165],{"emptyLinePlaceholder":164},[32,1405,1406,1408,1410],{"class":34,"line":728},[32,1407,611],{"class":270},[32,1409,812],{"class":38},[32,1411,617],{"class":270},[32,1413,1414,1416,1419],{"class":34,"line":742},[32,1415,820],{"class":270},[32,1417,1418],{"class":42},"\"poetry-core\"",[32,1420,617],{"class":270},[32,1422,1423,1425],{"class":34,"line":763},[32,1424,831],{"class":270},[32,1426,1427],{"class":42},"\"poetry.core.masonry.api\"\n",[83,1429,1430,1432,1433,1435,1436,1438,1439,1442,1443,1445,1446,512,1449,1451,1452,1454,1455,1457,1458,1460],{},[29,1431,885],{}," predates ",[29,1434,888],{}," by several years and popularized the \"lockfile + dependency groups + one CLI for everything\" workflow that ",[29,1437,888],{}," later reimplemented for speed — ",[29,1440,1441],{},"poetry.lock"," serves the same reproducibility role as ",[29,1444,1127],{},". ",[98,1447,1448],{},"The trade-off today",[29,1450,885],{}," has a larger, more mature plugin ecosystem and longer production track record; ",[29,1453,888],{}," is substantially faster and increasingly the default recommendation for new projects, but is newer and its ecosystem conventions are still stabilizing. Either is a legitimate choice; picking neither (bare ",[29,1456,39],{}," + hand-written ",[29,1459,391],{},") is the option to avoid for anything beyond a throwaway script.",[14,1462,1464],{"id":1463},"publishing-a-package-to-pypi","Publishing a Package to PyPI",[19,1466,1467],{"language":21},[23,1468,1470],{"className":25,"code":1469,"language":21,"meta":27,"style":27},"pip install build twine\n\npython -m build\n# creates dist\u002Forders_service-1.4.0-py3-none-any.whl (wheel — prebuilt, fast to install)\n# and dist\u002Forders_service-1.4.0.tar.gz (sdist — source distribution, buildable from scratch)\n\ntwine upload --repository testpypi dist\u002F*     # ALWAYS rehearse on TestPyPI first\ntwine upload dist\u002F*                            # the real, irreversible upload to PyPI\n",[29,1471,1472,1484,1488,1497,1502,1507,1511,1534],{"__ignoreMap":27},[32,1473,1474,1476,1478,1481],{"class":34,"line":35},[32,1475,39],{"class":38},[32,1477,43],{"class":42},[32,1479,1480],{"class":42}," build",[32,1482,1483],{"class":42}," twine\n",[32,1485,1486],{"class":34,"line":53},[32,1487,165],{"emptyLinePlaceholder":164},[32,1489,1490,1492,1494],{"class":34,"line":60},[32,1491,183],{"class":38},[32,1493,129],{"class":49},[32,1495,1496],{"class":42}," build\n",[32,1498,1499],{"class":34,"line":72},[32,1500,1501],{"class":56},"# creates dist\u002Forders_service-1.4.0-py3-none-any.whl (wheel — prebuilt, fast to install)\n",[32,1503,1504],{"class":34,"line":78},[32,1505,1506],{"class":56},"# and dist\u002Forders_service-1.4.0.tar.gz (sdist — source distribution, buildable from scratch)\n",[32,1508,1509],{"class":34,"line":168},[32,1510,165],{"emptyLinePlaceholder":164},[32,1512,1513,1516,1519,1522,1525,1528,1531],{"class":34,"line":180},[32,1514,1515],{"class":38},"twine",[32,1517,1518],{"class":42}," upload",[32,1520,1521],{"class":49}," --repository",[32,1523,1524],{"class":42}," testpypi",[32,1526,1527],{"class":42}," dist\u002F",[32,1529,1530],{"class":49},"*",[32,1532,1533],{"class":56},"     # ALWAYS rehearse on TestPyPI first\n",[32,1535,1536,1538,1540,1542,1544],{"class":34,"line":199},[32,1537,1515],{"class":38},[32,1539,1518],{"class":42},[32,1541,1527],{"class":42},[32,1543,1530],{"class":49},[32,1545,1546],{"class":56},"                            # the real, irreversible upload to PyPI\n",[83,1548,1549,1550,1553,1554,1557,1558,1553,1561,1564,1565,1567,1568,1571,1572,1575,1576,1578,1579,1445,1582,1584,1585,1590,1591,1594],{},"A ",[98,1551,1552],{},"wheel"," (",[29,1555,1556],{},".whl",") is a prebuilt, ready-to-install artifact (no compilation step needed at install time, even for packages with C extensions, since the wheel is built per-platform); an ",[98,1559,1560],{},"sdist",[29,1562,1563],{},".tar.gz",") is the raw source, which ",[29,1566,39],{}," falls back to building from scratch if no matching wheel exists for the installer's platform. ",[98,1569,1570],{},"Critical, unforgiving gotcha",": PyPI does not allow re-uploading a file under a version number that was ever published before, even if it was deleted — a botched ",[29,1573,1574],{},"1.4.0"," upload cannot be fixed by re-uploading ",[29,1577,1574],{}," again; the only path forward is bumping to ",[29,1580,1581],{},"1.4.1",[98,1583,100],{},": always upload to ",[841,1586,1589],{"href":1587,"rel":1588},"https:\u002F\u002Ftest.pypi.org\u002F",[845],"TestPyPI"," first and verify ",[29,1592,1593],{},"pip install --index-url https:\u002F\u002Ftest.pypi.org\u002Fsimple\u002F orders-service"," works end-to-end before touching the real index.",[19,1596,1597],{"language":21},[23,1598,1600],{"className":25,"code":1599,"language":21,"meta":27,"style":27},"pip install twine\ntwine upload --repository testpypi dist\u002F*\n# Enter your API token (starts with pypi-) when prompted — NEVER your account password;\n# PyPI deprecated password-based uploads in favor of scoped API tokens\n",[29,1601,1602,1610,1625,1630],{"__ignoreMap":27},[32,1603,1604,1606,1608],{"class":34,"line":35},[32,1605,39],{"class":38},[32,1607,43],{"class":42},[32,1609,1483],{"class":42},[32,1611,1612,1614,1616,1618,1620,1622],{"class":34,"line":53},[32,1613,1515],{"class":38},[32,1615,1518],{"class":42},[32,1617,1521],{"class":49},[32,1619,1524],{"class":42},[32,1621,1527],{"class":42},[32,1623,1624],{"class":49},"*\n",[32,1626,1627],{"class":34,"line":60},[32,1628,1629],{"class":56},"# Enter your API token (starts with pypi-) when prompted — NEVER your account password;\n",[32,1631,1632],{"class":34,"line":72},[32,1633,1634],{"class":56},"# PyPI deprecated password-based uploads in favor of scoped API tokens\n",[83,1636,1637,1640],{},[98,1638,1639],{},"Security best practice",": authenticate with a scoped API token (generated per-project on PyPI's account settings page), not a username\u002Fpassword — a leaked project-scoped token can only publish that one package, while a leaked password compromises the entire account.",[14,1642,1644],{"id":1643},"semantic-versioning","Semantic Versioning",[19,1646,1647],{"language":254},[23,1648,1650],{"className":257,"code":1649,"language":254,"meta":27,"style":27},"# MAJOR.MINOR.PATCH\n1.4.0 -> 1.4.1   # PATCH: backward-compatible bug fix\n1.4.0 -> 1.5.0   # MINOR: backward-compatible new feature\n1.4.0 -> 2.0.0   # MAJOR: breaking change — existing callers may need to update their code\n",[29,1651,1652,1657,1665,1673],{"__ignoreMap":27},[32,1653,1654],{"class":34,"line":35},[32,1655,1656],{"class":56},"# MAJOR.MINOR.PATCH\n",[32,1658,1659,1662],{"class":34,"line":53},[32,1660,1661],{"class":270},"1.4.0 -> 1.4.1   ",[32,1663,1664],{"class":56},"# PATCH: backward-compatible bug fix\n",[32,1666,1667,1670],{"class":34,"line":60},[32,1668,1669],{"class":270},"1.4.0 -> 1.5.0   ",[32,1671,1672],{"class":56},"# MINOR: backward-compatible new feature\n",[32,1674,1675,1678],{"class":34,"line":72},[32,1676,1677],{"class":270},"1.4.0 -> 2.0.0   ",[32,1679,1680],{"class":56},"# MAJOR: breaking change — existing callers may need to update their code\n",[19,1682,1683],{"language":600},[23,1684,1686],{"className":603,"code":1685,"language":600,"meta":27,"style":27},"[project]\ndependencies = [\n    \"requests>=2.31,\u003C3.0\",   # accepts any 2.x >= 2.31, blocks the (potentially breaking) 3.0\n    \"pydantic~=2.7.0\",       # ~= is \"compatible release\": accepts 2.7.x, blocks 2.8+\n    \"click==8.1.7\",          # exact pin — no automatic updates at all, most reproducible, least flexible\n]\n",[29,1687,1688,1696,1700,1710,1721,1732],{"__ignoreMap":27},[32,1689,1690,1692,1694],{"class":34,"line":35},[32,1691,611],{"class":270},[32,1693,614],{"class":38},[32,1695,617],{"class":270},[32,1697,1698],{"class":34,"line":53},[32,1699,690],{"class":270},[32,1701,1702,1704,1707],{"class":34,"line":60},[32,1703,696],{"class":42},[32,1705,1706],{"class":270},",   ",[32,1708,1709],{"class":56},"# accepts any 2.x >= 2.31, blocks the (potentially breaking) 3.0\n",[32,1711,1712,1715,1718],{"class":34,"line":72},[32,1713,1714],{"class":42},"    \"pydantic~=2.7.0\"",[32,1716,1717],{"class":270},",       ",[32,1719,1720],{"class":56},"# ~= is \"compatible release\": accepts 2.7.x, blocks 2.8+\n",[32,1722,1723,1726,1729],{"class":34,"line":78},[32,1724,1725],{"class":42},"    \"click==8.1.7\"",[32,1727,1728],{"class":270},",          ",[32,1730,1731],{"class":56},"# exact pin — no automatic updates at all, most reproducible, least flexible\n",[32,1733,1734],{"class":34,"line":168},[32,1735,617],{"class":270},[83,1737,1738,1739,1742,1743,1745,1746,1749,1750,1753,1754,1757,1758,1760,1761,1763,1764,1766,1767,230,1769,1771,1772,1775],{},"Version ",[372,1740,1741],{},"specifiers"," in ",[29,1744,576],{}," are a policy decision, not a formality: ",[29,1747,1748],{},">=2.31,\u003C3.0"," trusts the maintainer's semver promise that no ",[29,1751,1752],{},"2.x"," release breaks the API; ",[29,1755,1756],{},"==8.1.7"," trusts nothing and pins exactly, trading flexibility for maximum reproducibility. ",[98,1759,100],{},": application ",[29,1762,576],{}," files (things you deploy, not libraries others depend on) should generally combine loose ranges in ",[29,1765,576],{}," with an exact-pinned lockfile (",[29,1768,1127],{},[29,1770,1441],{},") for reproducible installs — libraries published to PyPI should keep ranges as loose as genuinely compatible, since an overly strict pin in a ",[372,1773,1774],{},"library"," needlessly constrains every downstream project that depends on it.",[14,1777,1779],{"id":1778},"tips-tricks","💡 Tips & Tricks",[1781,1782,1783,1803,1816,1827,1839],"ul",{},[1784,1785,1786,1789,1790,1793,1794,1796,1797,1799,1800,1802],"li",{},[98,1787,1788],{},"Idiom",": run ",[29,1791,1792],{},"python -m pip install --upgrade pip"," right after creating a fresh ",[29,1795,113],{}," — the bundled ",[29,1798,39],{}," version can lag behind the latest release for months, and newer ",[29,1801,39],{}," versions resolve dependency conflicts more reliably.",[1784,1804,1805,512,1808,1811,1812,1815],{},[98,1806,1807],{},"Debug",[29,1809,1810],{},"pip show \u003Cpackage>"," prints exactly where a package is installed from (",[29,1813,1814],{},"Location:",") — the fastest way to confirm whether an import is resolving to the virtual environment or an unexpected global install.",[1784,1817,1818,512,1820,219,1823,1826],{},[98,1819,1788],{},[29,1821,1822],{},"pip list --outdated",[29,1824,1825],{},"uv pip list --outdated",") shows every installed package with a newer version available — run it periodically rather than discovering a security fix was available three versions ago.",[1784,1828,1829,512,1832,1834,1835,1838],{},[98,1830,1831],{},"Performance",[29,1833,888],{},"'s global package cache means installing the same package version across ",[372,1836,1837],{},"different"," projects' virtual environments is nearly instant after the first download — a meaningful speedup for anyone juggling many small projects or CI jobs.",[1784,1840,1841,1844,1845,503,1847,1849,1850,1853],{},[98,1842,1843],{},"Safety",": commit the lockfile (",[29,1846,1127],{},[29,1848,1441],{},") to version control, but never the ",[29,1851,1852],{},".venv\u002F"," directory itself — the lockfile is what makes an install reproducible across machines; the venv is a disposable, regeneratable artifact.",[14,1855,1857],{"id":1856},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[1781,1859,1860,1882,1895,1905,1937],{},[1784,1861,1862,1870,1871,1873,1874,1877,1878,1881],{},[98,1863,1864,1865,1867,1868],{},"A bare ",[29,1866,95],{}," with no active virtual environment silently writes into the global\u002Fsystem ",[29,1869,88],{}," — on some Linux distributions this can even affect OS-level tools written in Python, which is precisely why modern ",[29,1872,39],{}," versions refuse this by default (",[29,1875,1876],{},"error: externally-managed-environment",") unless a venv is active or ",[29,1879,1880],{},"--break-system-packages"," is explicitly passed.",[1784,1883,1884,1890,1891,1894],{},[98,1885,1886,1889],{},[29,1887,1888],{},"pip freeze > requirements.txt"," captures the environment as it happens to be right now, including packages installed for unrelated experimentation"," — running it in a venv that ever had a stray ",[29,1892,1893],{},"pip install some-debug-tool"," bakes that tool into the committed requirements file for everyone else on the team.",[1784,1896,1897,1900,1901,1904],{},[98,1898,1899],{},"PyPI permanently reserves every version number ever uploaded, even if deleted"," — there is no way to \"fix\" a bad ",[29,1902,1903],{},"1.0.0"," upload by re-uploading; the only forward path is a new version number, making a pre-upload TestPyPI rehearsal the only real safety net.",[1784,1906,1907,512,1917,1920,1921,1924,1925,1928,1929,1932,1933,1936],{},[98,1908,1909,1912,1913,1916],{},[29,1910,1911],{},"~="," (compatible release) and ",[29,1914,1915],{},">=,\u003C"," ranges parse differently than most developers expect at the boundary",[29,1918,1919],{},"~=2.7.0"," means ",[29,1922,1923],{},">=2.7.0, ==2.7.*"," (locks the ",[372,1926,1927],{},"minor"," version, only patch updates allowed), while ",[29,1930,1931],{},"~=2.7"," (no patch component) means ",[29,1934,1935],{},">=2.7, ==2.*"," (allows minor updates too) — the number of version segments specified changes which segment is allowed to float.",[1784,1938,1939,1950,1951,1953,1954,1956],{},[98,1940,1941,1942,1945,1946,1949],{},"Editable installs (",[29,1943,1944],{},"pip install -e .",") historically wrote a ",[29,1947,1948],{},".egg-link"," file and could behave inconsistently with namespace packages or certain build backends"," — modern ",[29,1952,576],{},"-based editable installs (PEP 660) are far more reliable, but an old-style ",[29,1955,855],{},"-only editable install occasionally leaves the package importable from a stale path even after the source directory has moved.",[14,1958,1960],{"id":1959},"spot-the-bug","🧠 Spot the Bug",[83,1962,1963,1964,1966],{},"A team commits ",[29,1965,391],{}," to fix \"works on my machine\" issues, but a teammate's fresh clone still installs a different, incompatible version of a transitive dependency than the one used in production. Find the bug.",[19,1968,1969],{"language":21},[23,1970,1972],{"className":25,"code":1971,"language":21,"meta":27,"style":27},"# requirements.txt, committed to the repo:\nrequests\n\n# setup steps a new teammate follows:\npython3 -m venv .venv\nsource .venv\u002Fbin\u002Factivate\npip install -r requirements.txt\n",[29,1973,1974,1979,1984,1988,1993,2004,2011],{"__ignoreMap":27},[32,1975,1976],{"class":34,"line":35},[32,1977,1978],{"class":56},"# requirements.txt, committed to the repo:\n",[32,1980,1981],{"class":34,"line":53},[32,1982,1983],{"class":38},"requests\n",[32,1985,1986],{"class":34,"line":60},[32,1987,165],{"emptyLinePlaceholder":164},[32,1989,1990],{"class":34,"line":72},[32,1991,1992],{"class":56},"# setup steps a new teammate follows:\n",[32,1994,1995,1997,1999,2001],{"class":34,"line":78},[32,1996,126],{"class":38},[32,1998,129],{"class":49},[32,2000,132],{"class":42},[32,2002,2003],{"class":42}," .venv\n",[32,2005,2006,2008],{"class":34,"line":168},[32,2007,143],{"class":49},[32,2009,2010],{"class":42}," .venv\u002Fbin\u002Factivate\n",[32,2012,2013,2015,2017,2019],{"class":34,"line":180},[32,2014,39],{"class":38},[32,2016,43],{"class":42},[32,2018,485],{"class":49},[32,2020,413],{"class":42},[2022,2023,2024,2028,2056,2059,2107,2110,2133],"details",{},[2025,2026,2027],"summary",{},"Answer",[83,2029,2030,2032,2033,2035,2036,2039,2040,2043,2044,2047,2048,2050,2051,944,2053,2055],{},[29,2031,391],{}," contains only ",[29,2034,454],{}," with ",[98,2037,2038],{},"no version specifier at all"," — every ",[29,2041,2042],{},"pip install -r requirements.txt"," resolves to whatever the ",[372,2045,2046],{},"latest"," ",[29,2049,454],{}," (and its latest-compatible transitive dependencies, like ",[29,2052,462],{},[29,2054,430],{},") happens to be on the day it runs, not the version actually used and tested in production. Two installs performed weeks apart, or on different machines, can silently resolve to entirely different dependency trees, since nothing in the file pins anything.",[83,2057,2058],{},"The fix is either an exact pin generated from the actual working environment, or (better) a proper lockfile-based workflow:",[19,2060,2061],{"language":183},[23,2062,2065],{"className":2063,"code":2064,"language":183,"meta":27,"style":27},"language-python shiki shiki-themes github-light github-dark","# requirements.txt regenerated from the REAL, working environment:\npip freeze > requirements.txt\n# certifi==2024.7.4\n# charset-normalizer==3.3.2\n# idna==3.7\n# requests==2.32.3\n# urllib3==2.2.2\n",[29,2066,2067,2072,2082,2087,2092,2097,2102],{"__ignoreMap":27},[32,2068,2069],{"class":34,"line":35},[32,2070,2071],{"class":56},"# requirements.txt regenerated from the REAL, working environment:\n",[32,2073,2074,2077,2080],{"class":34,"line":53},[32,2075,2076],{"class":270},"pip freeze ",[32,2078,2079],{"class":409},">",[32,2081,413],{"class":270},[32,2083,2084],{"class":34,"line":60},[32,2085,2086],{"class":56},"# certifi==2024.7.4\n",[32,2088,2089],{"class":34,"line":72},[32,2090,2091],{"class":56},"# charset-normalizer==3.3.2\n",[32,2093,2094],{"class":34,"line":78},[32,2095,2096],{"class":56},"# idna==3.7\n",[32,2098,2099],{"class":34,"line":168},[32,2100,2101],{"class":56},"# requests==2.32.3\n",[32,2103,2104],{"class":34,"line":180},[32,2105,2106],{"class":56},"# urllib3==2.2.2\n",[83,2108,2109],{},"or, migrating to a tool with a real resolver and lockfile:",[19,2111,2112],{"language":183},[23,2113,2115],{"className":2063,"code":2114,"language":183,"meta":27,"style":27},"uv add requests   # writes an exact, hash-pinned resolution into uv.lock\nuv sync           # every teammate and CI run gets the IDENTICAL resolved tree\n",[29,2116,2117,2125],{"__ignoreMap":27},[32,2118,2119,2122],{"class":34,"line":35},[32,2120,2121],{"class":270},"uv add requests   ",[32,2123,2124],{"class":56},"# writes an exact, hash-pinned resolution into uv.lock\n",[32,2126,2127,2130],{"class":34,"line":53},[32,2128,2129],{"class":270},"uv sync           ",[32,2131,2132],{"class":56},"# every teammate and CI run gets the IDENTICAL resolved tree\n",[83,2134,2135,2138,2139,2141,2142,2144,2145,2148,2149,2151],{},[98,2136,2137],{},"The lesson",": an unpinned ",[29,2140,391],{}," (or one missing transitive-dependency pins) provides no actual reproducibility guarantee — it just narrows ",[372,2143,171],{}," package to install, not ",[372,2146,2147],{},"which version","; true reproducibility requires either a full ",[29,2150,492],{}," snapshot or a dedicated lockfile tool that hash-pins the entire resolved dependency tree.",[14,2153,2155],{"id":2154},"key-takeaways","Key Takeaways",[1781,2157,2158,2169,2177,2185,2202,2205],{},[1784,2159,2160,2161,2163,2164,230,2166,2168],{},"Always work inside a virtual environment (",[29,2162,113],{},", or one managed by ",[29,2165,888],{},[29,2167,885],{},") — installing directly into the global interpreter causes version conflicts between unrelated projects and, on some systems, is blocked outright.",[1784,2170,2171,2173,2174,2176],{},[29,2172,391],{}," is a flat, unstructured list of pins with no concept of dependency groups or project metadata; ",[29,2175,576],{}," (PEP 621) is the modern, standardized replacement that unifies metadata, dependencies, optional groups, and build configuration.",[1784,2178,2179,2181,2182,2184],{},[29,2180,888],{}," reimplements the whole toolchain in Rust for dramatic speed gains and is the increasingly-recommended default for new projects in 2025; ",[29,2183,885],{}," is the mature, established alternative with a longer track record.",[1784,2186,2187,2188,503,2190,2192,2193,2195,2196,2198,2199,2201],{},"A lockfile (",[29,2189,1127],{},[29,2191,1441],{},", or a ",[29,2194,492],{}," snapshot) pins the ",[372,2197,496],{}," resolved dependency tree for true reproducibility — loose ranges in ",[29,2200,576],{}," alone do not guarantee two installs resolve identically.",[1784,2203,2204],{},"PyPI never allows reusing a version number once published, even after deletion — always rehearse a release on TestPyPI first, and authenticate with a scoped API token, never a password.",[1784,2206,2207,2208,2210],{},"Version specifiers are a policy choice: loose ranges (",[29,2209,1748],{},") suit published libraries that shouldn't over-constrain downstream users; exact pins suit deployed applications that need maximum reproducibility.",[2212,2213,2214],"style",{},"html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}",{"title":27,"searchDepth":53,"depth":53,"links":2216},[2217,2218,2223,2225,2227,2229,2231,2232,2233,2234,2235,2236],{"id":16,"depth":53,"text":17},{"id":110,"depth":53,"text":2219,"children":2220},"venv: The Standard Library's Built-In Tool",[2221],{"id":295,"depth":60,"text":2222},"python -m pip vs bare pip",{"id":388,"depth":53,"text":2224},"requirements.txt: Simple, but Limited",{"id":594,"depth":53,"text":2226},"pyproject.toml: The Modern Standard",{"id":950,"depth":53,"text":2228},"uv: The Fast Modern Toolchain",{"id":1156,"depth":53,"text":2230},"poetry: The Established Alternative",{"id":1463,"depth":53,"text":1464},{"id":1643,"depth":53,"text":1644},{"id":1778,"depth":53,"text":1779},{"id":1856,"depth":53,"text":1857},{"id":1959,"depth":53,"text":1960},{"id":2154,"depth":53,"text":2155},"md",{},"\u002Fpython\u002F25-packaging-and-virtual-environments",{"title":5,"description":27},"python\u002F25-packaging-and-virtual-environments","93CLynTHXJNIYBYd7oHFNMuvuuBs0iQod5YgA40y60c",1789924651821]