<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: pip</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/pip.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-03-24T21:11:38+00:00</updated><author><name>Simon Willison</name></author><entry><title>Package Managers Need to Cool Down</title><link href="https://simonwillison.net/2026/Mar/24/package-managers-need-to-cool-down/#atom-tag" rel="alternate"/><published>2026-03-24T21:11:38+00:00</published><updated>2026-03-24T21:11:38+00:00</updated><id>https://simonwillison.net/2026/Mar/24/package-managers-need-to-cool-down/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://nesbitt.io/2026/03/04/package-managers-need-to-cool-down.html"&gt;Package Managers Need to Cool Down&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Today's &lt;a href="https://simonwillison.net/2026/Mar/24/malicious-litellm/"&gt;LiteLLM supply chain attack&lt;/a&gt; inspired me to revisit the idea of &lt;a href="https://simonwillison.net/2025/Nov/21/dependency-cooldowns/"&gt;dependency cooldowns&lt;/a&gt;, the practice of only installing updated dependencies once they've been out in the wild for a few days to give the community a chance to spot if they've been subverted in some way.&lt;/p&gt;
&lt;p&gt;This recent piece (March 4th) piece by Andrew Nesbitt reviews the current state of dependency cooldown mechanisms across different packaging tools. It's surprisingly well supported! There's been a flurry of activity across major packaging tools, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pnpm.io/blog/releases/10.16#new-setting-for-delayed-dependency-updates"&gt;pnpm 10.16&lt;/a&gt; (September 2025) — &lt;code&gt;minimumReleaseAge&lt;/code&gt; with &lt;code&gt;minimumReleaseAgeExclude&lt;/code&gt; for trusted packages&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/yarnpkg/berry/releases/tag/%40yarnpkg%2Fcli%2F4.10.0"&gt;Yarn 4.10.0&lt;/a&gt; (September 2025) — &lt;code&gt;npmMinimalAgeGate&lt;/code&gt; (in minutes) with &lt;code&gt;npmPreapprovedPackages&lt;/code&gt; for exemptions&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bun.com/blog/bun-v1.3#minimum-release-age"&gt;Bun 1.3&lt;/a&gt; (October 2025) — &lt;code&gt;minimumReleaseAge&lt;/code&gt; via &lt;code&gt;bunfig.toml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deno.com/blog/v2.6#controlling-dependency-stability"&gt;Deno 2.6&lt;/a&gt; (December 2025) — &lt;code&gt;--minimum-dependency-age&lt;/code&gt; for &lt;code&gt;deno update&lt;/code&gt; and &lt;code&gt;deno outdated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astral-sh/uv/releases/tag/0.9.17"&gt;uv 0.9.17&lt;/a&gt; (December 2025) — added relative duration support to existing &lt;code&gt;--exclude-newer&lt;/code&gt;, plus per-package overrides via &lt;code&gt;exclude-newer-package&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ichard26.github.io/blog/2026/01/whats-new-in-pip-26.0/"&gt;pip 26.0&lt;/a&gt; (January 2026) — &lt;code&gt;--uploaded-prior-to&lt;/code&gt; (absolute timestamps only; &lt;a href="https://github.com/pypa/pip/issues/13674"&gt;relative duration support requested&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://socket.dev/blog/npm-introduces-minimumreleaseage-and-bulk-oidc-configuration"&gt;npm 11.10.0&lt;/a&gt; (February 2026) — &lt;code&gt;min-release-age&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;pip&lt;/code&gt; currently only supports absolute rather than relative dates but Seth Larson &lt;a href="https://sethmlarson.dev/pip-relative-dependency-cooling-with-crontab"&gt;has a workaround for that&lt;/a&gt; using a scheduled cron to update the absolute date in the &lt;code&gt;pip.conf&lt;/code&gt; config file.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/supply-chain"&gt;supply-chain&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="packaging"/><category term="pip"/><category term="pypi"/><category term="python"/><category term="security"/><category term="npm"/><category term="deno"/><category term="supply-chain"/><category term="uv"/></entry><entry><title>2025 Python Packaging Ecosystem Survey</title><link href="https://simonwillison.net/2025/May/18/2025-python-packaging-ecosystem-survey/#atom-tag" rel="alternate"/><published>2025-05-18T11:50:06+00:00</published><updated>2025-05-18T11:50:06+00:00</updated><id>https://simonwillison.net/2025/May/18/2025-python-packaging-ecosystem-survey/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://anaconda.surveymonkey.com/r/py-package-2025"&gt;2025 Python Packaging Ecosystem Survey&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
If you make use of Python packaging tools (pip, Anaconda, uv, dozens of others) and have opinions please spend a few minutes with this year's packaging survey. This one was "Co-authored by 30+ of your favorite Python Ecosystem projects, organizations and companies."


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/surveys"&gt;surveys&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/psf"&gt;psf&lt;/a&gt;&lt;/p&gt;



</summary><category term="packaging"/><category term="pip"/><category term="python"/><category term="surveys"/><category term="psf"/></entry><entry><title>Using pip to install a Large Language Model that's under 100MB</title><link href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#atom-tag" rel="alternate"/><published>2025-02-07T06:34:59+00:00</published><updated>2025-02-07T06:34:59+00:00</updated><id>https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#atom-tag</id><summary type="html">
    &lt;p&gt;I just released &lt;a href="https://github.com/simonw/llm-smollm2"&gt;llm-smollm2&lt;/a&gt;, a new plugin for &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; that bundles a quantized copy of the &lt;a href="https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct"&gt;SmolLM2-135M-Instruct&lt;/a&gt; LLM inside of the Python package.&lt;/p&gt;
&lt;p&gt;This means you can now &lt;code&gt;pip install&lt;/code&gt; a full LLM!&lt;/p&gt;

&lt;p&gt;If you're already using &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; you can install it like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm install llm-smollm2&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run prompts like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;llm -m SmolLM2 &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Are dogs real?&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;(New favourite test prompt for tiny models, courtesy of &lt;a href="https://bsky.app/profile/timfduffy.com/post/3lhknvwijps2o"&gt;Tim Duffy&lt;/a&gt;. &lt;a href="https://gist.github.com/simonw/a71d83cd8c6caa49cec4a9f296c267df"&gt;Here's the result&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;If you don't have LLM yet first &lt;a href="https://llm.datasette.io/en/stable/setup.html"&gt;follow these installation instructions&lt;/a&gt;, or &lt;code&gt;brew install llm&lt;/code&gt; or &lt;code&gt;pipx install llm&lt;/code&gt; or &lt;code&gt;uv tool install llm&lt;/code&gt; depending on your preferred way of getting your Python tools.&lt;/p&gt;
&lt;p&gt;If you have &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt; setup you don't need to install anything at all! The following command will spin up an ephemeral environment, install the necessary packages and start a chat session with the model all in one go:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx --with llm-smollm2 llm chat -m SmolLM2&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/smol.gif" alt="$ uvx --with llm-smollm2 llm chat -m SmolLM2
Chatting with SmolLM2
Type 'exit' or 'quit' to exit
Type '!multi' to enter multiple lines, then '!end' to finish
&gt; tell me a long story about a dog
The story of a dog is a tale of love, loss, and the unspoken rules of the universe. It begins with a small, timid, and timid dog named Max. Max was born into a loving family, but his life was forever changed by a cruel and cruel owner. He was left to fend for himself, and he was left to fend for himself.

Max's owner, a kind and gentle soul named Elena, was a cruel and cruel one. She had no patience, no love, and she would do anything to keep Max away from her. Max was left to fend for himself, and he was left to fend for himself.

Max's owner, Elena, was determined to make Max happy. She took Max to a beautiful and beautiful place, where Max was allowed to roam freely. Max was allowed to roam freely, and Max was allowed to roam freely. [Then repeats that sentence many times]" style="max-width: 100%;" /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#finding-a-tiny-model"&gt;Finding a tiny model&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#building-the-plugin"&gt;Building the plugin&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#packaging-the-plugin"&gt;Packaging the plugin&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#publishing-to-pypi"&gt;Publishing to PyPI&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2025/Feb/7/pip-install-llm-smollm2/#is-the-model-any-good-"&gt;Is the model any good?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="finding-a-tiny-model"&gt;Finding a tiny model&lt;/h4&gt;
&lt;p&gt;The fact that the model is almost exactly 100MB is no coincidence: that's the &lt;a href="https://pypi.org/help/#file-size-limit"&gt;default size limit&lt;/a&gt; for a Python package that can be uploaded to the Python Package Index (PyPI).&lt;/p&gt;
&lt;p&gt;I &lt;a href="https://bsky.app/profile/simonwillison.net/post/3lhklqd62jc2x"&gt;asked on Bluesky&lt;/a&gt; if anyone had seen a just-about-usable GGUF model that was under 100MB, and Artisan Loaf &lt;a href="https://bsky.app/profile/artisanloaf.bsky.social/post/3lhklumfhvs2r"&gt;pointed me&lt;/a&gt; to &lt;a href="https://huggingface.co/HuggingFaceTB/SmolLM2-135M-Instruct"&gt;SmolLM2-135M-Instruct&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I ended up using &lt;a href="https://huggingface.co/QuantFactory/SmolLM2-135M-Instruct-GGUF/tree/main"&gt;this quantization&lt;/a&gt; by &lt;a href="https://huggingface.co/QuantFactory"&gt;QuantFactory&lt;/a&gt; just because it was the first sub-100MB model I tried that worked.&lt;/p&gt;
&lt;p&gt;Trick for finding quantized models: Hugging Face has a neat "model tree" feature in the side panel of their model pages, which includes links to relevant quantized models. I find most of my GGUFs using that feature.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/hugging-face-model-tree.jpg" alt="Model tree for HuggingFaceTB/SmolLM2-135M-Instruct. 60 Quantizations, 6 adapters, 80 finetunes, 1 merge." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="building-the-plugin"&gt;Building the plugin&lt;/h4&gt;
&lt;p&gt;I first tried the model out using Python and the &lt;a href="https://github.com/abetlen/llama-cpp-python"&gt;llama-cpp-python&lt;/a&gt; library like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with llama-cpp-python python&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;llama_cpp&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Llama&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;pprint&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;pprint&lt;/span&gt;
&lt;span class="pl-s1"&gt;llm&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;Llama&lt;/span&gt;(&lt;span class="pl-s1"&gt;model_path&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"SmolLM2-135M-Instruct.Q4_1.gguf"&lt;/span&gt;)
&lt;span class="pl-s1"&gt;output&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;llm&lt;/span&gt;.&lt;span class="pl-c1"&gt;create_chat_completion&lt;/span&gt;(&lt;span class="pl-s1"&gt;messages&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[
    {&lt;span class="pl-s"&gt;"role"&lt;/span&gt;: &lt;span class="pl-s"&gt;"user"&lt;/span&gt;, &lt;span class="pl-s"&gt;"content"&lt;/span&gt;: &lt;span class="pl-s"&gt;"Hi"&lt;/span&gt;}
])
&lt;span class="pl-en"&gt;pprint&lt;/span&gt;(&lt;span class="pl-s1"&gt;output&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;This gave me the output I was expecting:&lt;/p&gt;
&lt;pre&gt;{&lt;span class="pl-s"&gt;'choices'&lt;/span&gt;: [{&lt;span class="pl-s"&gt;'finish_reason'&lt;/span&gt;: &lt;span class="pl-s"&gt;'stop'&lt;/span&gt;,
              &lt;span class="pl-s"&gt;'index'&lt;/span&gt;: &lt;span class="pl-c1"&gt;0&lt;/span&gt;,
              &lt;span class="pl-s"&gt;'logprobs'&lt;/span&gt;: &lt;span class="pl-c1"&gt;None&lt;/span&gt;,
              &lt;span class="pl-s"&gt;'message'&lt;/span&gt;: {&lt;span class="pl-s"&gt;'content'&lt;/span&gt;: &lt;span class="pl-s"&gt;'Hello! How can I assist you today?'&lt;/span&gt;,
                          &lt;span class="pl-s"&gt;'role'&lt;/span&gt;: &lt;span class="pl-s"&gt;'assistant'&lt;/span&gt;}}],
 &lt;span class="pl-s"&gt;'created'&lt;/span&gt;: &lt;span class="pl-c1"&gt;1738903256&lt;/span&gt;,
 &lt;span class="pl-s"&gt;'id'&lt;/span&gt;: &lt;span class="pl-s"&gt;'chatcmpl-76ea1733-cc2f-46d4-9939-90efa2a05e7c'&lt;/span&gt;,
 &lt;span class="pl-s"&gt;'model'&lt;/span&gt;: &lt;span class="pl-s"&gt;'SmolLM2-135M-Instruct.Q4_1.gguf'&lt;/span&gt;,
 &lt;span class="pl-s"&gt;'object'&lt;/span&gt;: &lt;span class="pl-s"&gt;'chat.completion'&lt;/span&gt;,
 &lt;span class="pl-s"&gt;'usage'&lt;/span&gt;: {&lt;span class="pl-s"&gt;'completion_tokens'&lt;/span&gt;: &lt;span class="pl-c1"&gt;9&lt;/span&gt;, &lt;span class="pl-s"&gt;'prompt_tokens'&lt;/span&gt;: &lt;span class="pl-c1"&gt;31&lt;/span&gt;, &lt;span class="pl-s"&gt;'total_tokens'&lt;/span&gt;: &lt;span class="pl-c1"&gt;40&lt;/span&gt;}}&lt;/pre&gt;
&lt;p&gt;But it also &lt;em&gt;spammed&lt;/em&gt; my terminal with a huge volume of debugging output - which started like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;llama_model_load_from_file_impl: using device Metal (Apple M2 Max) - 49151 MiB free
llama_model_loader: loaded meta data with 33 key-value pairs and 272 tensors from SmolLM2-135M-Instruct.Q4_1.gguf (version GGUF V3 (latest))
llama_model_loader: Dumping metadata keys/values. Note: KV overrides do not apply in this output.
llama_model_loader: - kv   0:                       general.architecture str              = llama
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then continued for more than &lt;a href="https://gist.github.com/simonw/9ef7acd836b1cc40c14686eae4dca340"&gt;500 lines&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;I've had this problem with &lt;code&gt;llama-cpp-python&lt;/code&gt; and &lt;code&gt;llama.cpp&lt;/code&gt; in the past, and was sad to find that the documentation still doesn't have a great answer for how to avoid this.&lt;/p&gt;
&lt;p&gt;So I turned to the just released &lt;a href="https://simonwillison.net/2025/Feb/5/gemini-2/"&gt;Gemini 2.0 Pro (Experimental)&lt;/a&gt;, because I know it's a strong model with a long input limit.&lt;/p&gt;
&lt;p&gt;I ran the entire &lt;code&gt;llama-cpp-python&lt;/code&gt; codebase through it like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; /tmp
git clone https://github.com/abetlen/llama-cpp-python
&lt;span class="pl-c1"&gt;cd&lt;/span&gt; llama-cpp-python
files-to-prompt -e py &lt;span class="pl-c1"&gt;.&lt;/span&gt; -c &lt;span class="pl-k"&gt;|&lt;/span&gt; llm -m gemini-2.0-pro-exp-02-05 \
  &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;How can I prevent this library from logging any information at all while it is running - no stderr or anything like that&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/20476c2c6f7604df2994212cebfafef4#response"&gt;the answer I got back&lt;/a&gt;. It recommended setting the logger to &lt;code&gt;logging.CRITICAL&lt;/code&gt;, passing &lt;code&gt;verbose=False&lt;/code&gt; to the constructor and, most importantly, using the following context manager to suppress all output:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;contextlib&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;contextmanager&lt;/span&gt;, &lt;span class="pl-s1"&gt;redirect_stderr&lt;/span&gt;, &lt;span class="pl-s1"&gt;redirect_stdout&lt;/span&gt;

&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;contextmanager&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;suppress_output&lt;/span&gt;():
    &lt;span class="pl-s"&gt;"""&lt;/span&gt;
&lt;span class="pl-s"&gt;    Suppresses all stdout and stderr output within the context.&lt;/span&gt;
&lt;span class="pl-s"&gt;    """&lt;/span&gt;
    &lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;open&lt;/span&gt;(&lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;devnull&lt;/span&gt;, &lt;span class="pl-s"&gt;"w"&lt;/span&gt;) &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-s1"&gt;devnull&lt;/span&gt;:
        &lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;redirect_stdout&lt;/span&gt;(&lt;span class="pl-s1"&gt;devnull&lt;/span&gt;), &lt;span class="pl-en"&gt;redirect_stderr&lt;/span&gt;(&lt;span class="pl-s1"&gt;devnull&lt;/span&gt;):
            &lt;span class="pl-k"&gt;yield&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;This worked! It turned out most of the output came from initializing the &lt;code&gt;LLM&lt;/code&gt; class, so I wrapped that like so:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;suppress_output&lt;/span&gt;():
    &lt;span class="pl-s1"&gt;model&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;Llama&lt;/span&gt;(&lt;span class="pl-s1"&gt;model_path&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;self&lt;/span&gt;.&lt;span class="pl-c1"&gt;model_path&lt;/span&gt;, &lt;span class="pl-s1"&gt;verbose&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;False&lt;/span&gt;)&lt;/pre&gt;
&lt;p&gt;Proof of concept in hand I set about writing the plugin. I started with my &lt;a href="https://github.com/simonw/llm-plugin"&gt;simonw/llm-plugin&lt;/a&gt; cookiecutter template:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx cookiecutter gh:simonw/llm-plugin&lt;/pre&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;  [1/6] plugin_name (): smollm2
  [2/6] description (): SmolLM2-135M-Instruct.Q4_1 for LLM
  [3/6] hyphenated (smollm2): 
  [4/6] underscored (smollm2): 
  [5/6] github_username (): simonw
  [6/6] author_name (): Simon Willison
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://github.com/simonw/llm-smollm2/blob/0.1.1/llm_smollm2/__init__.py"&gt;rest of the plugin&lt;/a&gt; was mostly borrowed from my existing &lt;a href="https://github.com/simonw/llm-gguf/blob/0.2/llm_gguf.py"&gt;llm-gguf&lt;/a&gt; plugin, updated based on the latest README for the &lt;code&gt;llama-cpp-python&lt;/code&gt; project.&lt;/p&gt;
&lt;p&gt;There's more information on building plugins in &lt;a href="https://llm.datasette.io/en/stable/plugins/tutorial-model-plugin.html"&gt;the tutorial on writing a plugin&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="packaging-the-plugin"&gt;Packaging the plugin&lt;/h4&gt;
&lt;p&gt;Once I had that working the last step was to figure out how to package it for PyPI. I'm never quite sure of the best way to bundle a binary file in a Python package, especially one that uses a &lt;code&gt;pyproject.toml&lt;/code&gt; file... so I dumped a copy of my existing &lt;code&gt;pyproject.toml&lt;/code&gt; file into o3-mini-high and prompted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Modify this to bundle a SmolLM2-135M-Instruct.Q4_1.gguf file inside the package. I don't want to use hatch or a manifest or anything, I just want to use setuptools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://chatgpt.com/share/67a59122-67c8-8006-9be4-29f8419343ad"&gt;the shared transcript&lt;/a&gt; - it gave me exactly what I wanted. I bundled it by adding this to the end of the &lt;code&gt;toml&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight highlight-source-toml"&gt;&lt;pre&gt;[&lt;span class="pl-en"&gt;tool&lt;/span&gt;.&lt;span class="pl-en"&gt;setuptools&lt;/span&gt;.&lt;span class="pl-en"&gt;package-data&lt;/span&gt;]
&lt;span class="pl-smi"&gt;llm_smollm2&lt;/span&gt; = [&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;SmolLM2-135M-Instruct.Q4_1.gguf&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;]&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then dropping that &lt;code&gt;.gguf&lt;/code&gt; file into the &lt;code&gt;llm_smollm2/&lt;/code&gt; directory and putting my plugin code in &lt;code&gt;llm_smollm2/__init__.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I tested it locally by running this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;python -m pip install build
python -m build&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I fired up a fresh virtual environment and ran &lt;code&gt;pip install ../path/to/llm-smollm2/dist/llm_smollm2-0.1-py3-none-any.whl&lt;/code&gt; to confirm that the package worked as expected.&lt;/p&gt;
&lt;h4 id="publishing-to-pypi"&gt;Publishing to PyPI&lt;/h4&gt;
&lt;p&gt;My cookiecutter template comes with &lt;a href="https://github.com/simonw/llm-smollm2/blob/main/.github/workflows/publish.yml"&gt;a GitHub Actions workflow&lt;/a&gt; that publishes the package to PyPI when a new release is created using the GitHub web interface. Here's the relevant YAML:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;  &lt;span class="pl-ent"&gt;deploy&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;runs-on&lt;/span&gt;: &lt;span class="pl-s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="pl-ent"&gt;needs&lt;/span&gt;: &lt;span class="pl-s"&gt;[test]&lt;/span&gt;
    &lt;span class="pl-ent"&gt;environment&lt;/span&gt;: &lt;span class="pl-s"&gt;release&lt;/span&gt;
    &lt;span class="pl-ent"&gt;permissions&lt;/span&gt;:
      &lt;span class="pl-ent"&gt;id-token&lt;/span&gt;: &lt;span class="pl-s"&gt;write&lt;/span&gt;
    &lt;span class="pl-ent"&gt;steps&lt;/span&gt;:
    - &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;actions/checkout@v4&lt;/span&gt;
    - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Set up Python&lt;/span&gt;
      &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;actions/setup-python@v5&lt;/span&gt;
      &lt;span class="pl-ent"&gt;with&lt;/span&gt;:
        &lt;span class="pl-ent"&gt;python-version&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;3.13&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
        &lt;span class="pl-ent"&gt;cache&lt;/span&gt;: &lt;span class="pl-s"&gt;pip&lt;/span&gt;
        &lt;span class="pl-ent"&gt;cache-dependency-path&lt;/span&gt;: &lt;span class="pl-s"&gt;pyproject.toml&lt;/span&gt;
    - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Install dependencies&lt;/span&gt;
      &lt;span class="pl-ent"&gt;run&lt;/span&gt;: &lt;span class="pl-s"&gt;|&lt;/span&gt;
&lt;span class="pl-s"&gt;        pip install setuptools wheel build&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;    - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Build&lt;/span&gt;
      &lt;span class="pl-ent"&gt;run&lt;/span&gt;: &lt;span class="pl-s"&gt;|&lt;/span&gt;
&lt;span class="pl-s"&gt;        python -m build&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;    - &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;Publish&lt;/span&gt;
      &lt;span class="pl-ent"&gt;uses&lt;/span&gt;: &lt;span class="pl-s"&gt;pypa/gh-action-pypi-publish@release/v1&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This runs after the &lt;code&gt;test&lt;/code&gt; job has passed. It uses the &lt;a href="https://github.com/pypa/gh-action-pypi-publish"&gt;pypa/gh-action-pypi-publish&lt;/a&gt; Action to publish to PyPI - I wrote more about how that works &lt;a href="https://til.simonwillison.net/pypi/pypi-releases-from-github"&gt;in this TIL&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="is-the-model-any-good-"&gt;Is the model any good?&lt;/h4&gt;
&lt;p&gt;This one really isn't! It's not really surprising but it turns out 94MB really isn't enough space for a model that can do anything useful.&lt;/p&gt;
&lt;p&gt;It's &lt;em&gt;super&lt;/em&gt; fun to play with, and I continue to maintain that small, weak models are a great way to help build a mental model of how this technology actually works.&lt;/p&gt;
&lt;p&gt;That's not to say SmolLM2 isn't a fantastic model family. I'm running the smallest, most restricted version here. &lt;a href="https://huggingface.co/blog/smollm"&gt;SmolLM - blazingly fast and remarkably powerful&lt;/a&gt; describes the full model family - which comes in 135M, 360M, and 1.7B sizes. The larger versions are a whole lot more capable.&lt;/p&gt;
&lt;p&gt;If anyone can figure out something genuinely useful to do with the 94MB version I'd love to hear about it.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-actions"&gt;github-actions&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/local-llms"&gt;local-llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gemini"&gt;gemini&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/smollm"&gt;smollm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/o3"&gt;o3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llama-cpp"&gt;llama-cpp&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="pip"/><category term="plugins"/><category term="projects"/><category term="pypi"/><category term="python"/><category term="ai"/><category term="github-actions"/><category term="generative-ai"/><category term="local-llms"/><category term="llms"/><category term="ai-assisted-programming"/><category term="llm"/><category term="gemini"/><category term="uv"/><category term="smollm"/><category term="o3"/><category term="llama-cpp"/></entry><entry><title>Using uv with PyTorch</title><link href="https://simonwillison.net/2024/Nov/19/using-uv-with-pytorch/#atom-tag" rel="alternate"/><published>2024-11-19T23:20:18+00:00</published><updated>2024-11-19T23:20:18+00:00</updated><id>https://simonwillison.net/2024/Nov/19/using-uv-with-pytorch/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.astral.sh/uv/guides/integration/pytorch/"&gt;Using uv with PyTorch&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
PyTorch is a notoriously tricky piece of Python software to install, due to the need to provide separate wheels for different combinations of Python version and GPU accelerator (e.g. different CUDA versions).&lt;/p&gt;
&lt;p&gt;uv now has dedicated documentation for PyTorch which I'm finding really useful - it clearly explains the challenge and then shows exactly how to configure a &lt;code&gt;pyproject.toml&lt;/code&gt; such that &lt;code&gt;uv&lt;/code&gt; knows which version of each package it should install from where.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/charliermarsh/status/1858966355518878163"&gt;@charliermarsh&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pytorch"&gt;pytorch&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;



</summary><category term="packaging"/><category term="pip"/><category term="python"/><category term="pytorch"/><category term="uv"/></entry><entry><title>TIL: Using uv to develop Python command-line applications</title><link href="https://simonwillison.net/2024/Oct/24/uv-cli/#atom-tag" rel="alternate"/><published>2024-10-24T05:56:21+00:00</published><updated>2024-10-24T05:56:21+00:00</updated><id>https://simonwillison.net/2024/Oct/24/uv-cli/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://til.simonwillison.net/python/uv-cli-apps"&gt;TIL: Using uv to develop Python command-line applications&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I've been increasingly using &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt; to try out new software (via &lt;code&gt;uvx&lt;/code&gt;) and experiment with new ideas, but I hadn't quite figured out the right way to use it for developing my own projects.&lt;/p&gt;
&lt;p&gt;It turns out I was missing a few things - in particular the fact that there's no need to use &lt;code&gt;uv pip&lt;/code&gt; at all when working with a local development environment, you can get by entirely on &lt;code&gt;uv run&lt;/code&gt; (and maybe &lt;code&gt;uv sync --extra test&lt;/code&gt; to install test dependencies) with no direct invocations of &lt;code&gt;uv pip&lt;/code&gt; at all.&lt;/p&gt;
&lt;p&gt;I bounced &lt;a href="https://gist.github.com/simonw/975dfa41e9b03bca2513a986d9aa3dcf"&gt;a few questions&lt;/a&gt; off Charlie Marsh and filled in the missing gaps - this TIL shows my new uv-powered process for hacking on Python CLI apps built using Click and my &lt;a href="https://github.com/simonw/click-app"&gt;simonw/click-app&lt;/a&gt; cookecutter template.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/til"&gt;til&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cookiecutter"&gt;cookiecutter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/astral"&gt;astral&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/charlie-marsh"&gt;charlie-marsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="packaging"/><category term="pip"/><category term="python"/><category term="til"/><category term="cookiecutter"/><category term="uv"/><category term="astral"/><category term="charlie-marsh"/></entry><entry><title>light-the-torch</title><link href="https://simonwillison.net/2024/Aug/22/light-the-torch/#atom-tag" rel="alternate"/><published>2024-08-22T04:11:32+00:00</published><updated>2024-08-22T04:11:32+00:00</updated><id>https://simonwillison.net/2024/Aug/22/light-the-torch/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://pypi.org/project/light-the-torch/"&gt;light-the-torch&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;light-the-torch&lt;/code&gt; is a small utility that wraps &lt;code&gt;pip&lt;/code&gt; to ease the installation process for PyTorch distributions like &lt;code&gt;torch&lt;/code&gt;, &lt;code&gt;torchvision&lt;/code&gt;, &lt;code&gt;torchaudio&lt;/code&gt;, and so on as well as third-party packages that depend on them. It auto-detects compatible CUDA versions from the local setup and installs the correct PyTorch binaries without user interference.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use it like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;pip install light-the-torch
ltt install torch&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It works by wrapping and &lt;a href="https://github.com/pmeier/light-the-torch/blob/main/light_the_torch/_patch.py"&gt;patching pip&lt;/a&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/thezachmueller/status/1826384400684384476"&gt;@ thezachmueller&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pytorch"&gt;pytorch&lt;/a&gt;&lt;/p&gt;



</summary><category term="packaging"/><category term="pip"/><category term="python"/><category term="pytorch"/></entry><entry><title>uv pip install --exclude-newer example</title><link href="https://simonwillison.net/2024/May/10/uv-pip-install-exclude-newer/#atom-tag" rel="alternate"/><published>2024-05-10T16:35:40+00:00</published><updated>2024-05-10T16:35:40+00:00</updated><id>https://simonwillison.net/2024/May/10/uv-pip-install-exclude-newer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hauntsaninja/typing_extensions/blob/f694a4e2effdd2179f76e886498ffd3446e96b0b/.github/workflows/third_party.yml#L111"&gt;uv pip install --exclude-newer example&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A neat new feature of the &lt;code&gt;uv pip install&lt;/code&gt; command is the &lt;code&gt;--exclude-newer&lt;/code&gt; option, which can be used to avoid installing any package versions released after the specified date.&lt;/p&gt;
&lt;p&gt;Here's a clever example of that in use from the &lt;code&gt;typing_extensions&lt;/code&gt; packages CI tests that run against some downstream packages:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv pip install --system -r test-requirements.txt --exclude-newer $(git show -s --date=format:'%Y-%m-%dT%H:%M:%SZ' --format=%cd HEAD)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;They use &lt;code&gt;git show&lt;/code&gt; to get the date of the most recent commit (&lt;code&gt;%cd&lt;/code&gt; means commit date) formatted as an ISO timestamp, then pass that to &lt;code&gt;--exclude-newer&lt;/code&gt;.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/hauntsaninja/status/1788848732437713171"&gt;@hauntsaninja&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/git"&gt;git&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/astral"&gt;astral&lt;/a&gt;&lt;/p&gt;



</summary><category term="git"/><category term="pip"/><category term="python"/><category term="uv"/><category term="astral"/></entry><entry><title>uv: Python packaging in Rust</title><link href="https://simonwillison.net/2024/Feb/15/uv-python-packaging-in-rust/#atom-tag" rel="alternate"/><published>2024-02-15T19:57:13+00:00</published><updated>2024-02-15T19:57:13+00:00</updated><id>https://simonwillison.net/2024/Feb/15/uv-python-packaging-in-rust/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://astral.sh/blog/uv"&gt;uv: Python packaging in Rust&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
"uv is an extremely fast Python package installer and resolver, written in Rust, and designed as a drop-in replacement for pip and pip-tools workflows."&lt;/p&gt;
&lt;p&gt;From Charlie Marsh and Astral, the team behind &lt;a href="https://github.com/astral-sh/ruff"&gt;Ruff&lt;/a&gt;, who describe it as a milestone in their pursuit of a "Cargo for Python".&lt;/p&gt;
&lt;p&gt;Also in this announcement: Astral are taking over stewardship of Armin Ronacher's Rye packaging tool, another Rust project.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; is reported to be 8-10x faster than regular &lt;code&gt;pip&lt;/code&gt;, increasing to 80-115x faster with a warm global module cache thanks to copy-on-write and hard links on supported filesystems - which saves on disk space too.&lt;/p&gt;
&lt;p&gt;It also has a &lt;code&gt;--resolution=lowest&lt;/code&gt; option for installing the lowest available version of dependencies - extremely useful for testing, I've been wanting this for my own projects for a while.&lt;/p&gt;
&lt;p&gt;Also included: &lt;code&gt;uv venv&lt;/code&gt; - a fast tool for creating new virtual environments with no dependency on Python itself.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/charliermarsh/status/1758216803275149389"&gt;@charliermarsh&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rye"&gt;rye&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ruff"&gt;ruff&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/astral"&gt;astral&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/charlie-marsh"&gt;charlie-marsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="armin-ronacher"/><category term="pip"/><category term="python"/><category term="rust"/><category term="rye"/><category term="ruff"/><category term="uv"/><category term="astral"/><category term="charlie-marsh"/></entry><entry><title>Making SQLite extensions pip install-able</title><link href="https://simonwillison.net/2023/Feb/6/making-sqlite-extensions-pip-install-able/#atom-tag" rel="alternate"/><published>2023-02-06T19:44:10+00:00</published><updated>2023-02-06T19:44:10+00:00</updated><id>https://simonwillison.net/2023/Feb/6/making-sqlite-extensions-pip-install-able/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://observablehq.com/@asg017/making-sqlite-extensions-pip-install-able"&gt;Making SQLite extensions pip install-able&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Alex Garcia figured out how to bundle a compiled SQLite extension in a Python wheel (building different wheels for different platforms) and publish them to PyPI. This is a huge leap forward in terms of the usability of SQLite extensions, which have previously been pretty difficult to actually install and run. Alex also created Datasette plugins that depend on his packages, so you can now “datasette install datasette-sqlite-regex” (or datasette-sqlite-ulid, datasette-sqlite-fastrand, datasette-sqlite-jsonschema) to gain access to his custom SQLite extensions in your Datasette instance. It even works with “datasette publish --install” to deploy to Vercel, Fly.io and Cloud Run.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://discordapp.com/channels/823971286308356157/823971286941302908/1072220706366029864"&gt;Datasette Discord&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/alex-garcia"&gt;alex-garcia&lt;/a&gt;&lt;/p&gt;



</summary><category term="pip"/><category term="plugins"/><category term="python"/><category term="sqlite"/><category term="datasette"/><category term="alex-garcia"/></entry><entry><title>Useful tricks with pip install URL and GitHub</title><link href="https://simonwillison.net/2022/Apr/24/pip-install-github/#atom-tag" rel="alternate"/><published>2022-04-24T15:07:46+00:00</published><updated>2022-04-24T15:07:46+00:00</updated><id>https://simonwillison.net/2022/Apr/24/pip-install-github/#atom-tag</id><summary type="html">
    &lt;p&gt;The &lt;code&gt;pip install&lt;/code&gt; command can accept a URL to a zip file or tarball. GitHub provides URLs that can create a zip file of any branch, tag or commit in any repository. Combining these is a really useful trick for maintaining Python packages.&lt;/p&gt;
&lt;h4&gt;pip install URL&lt;/h4&gt;
&lt;p&gt;The most common way of using &lt;code&gt;pip&lt;/code&gt; is with package names from &lt;a href="https://pypi.org/"&gt;PyPi&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install datasette
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the &lt;a href="https://pip.pypa.io/en/stable/cli/pip_install/"&gt;pip install&lt;/a&gt; command has a bunch of other abilities - it can install files, pull from various version control systems and most importantly it can install packages from a URL.&lt;/p&gt;
&lt;p&gt;I sometimes use this to distribute ad-hoc packages that I don’t want to upload to PyPI. Here’s a quick and simple Datasette plugin I built a while ago that I install using this option:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install 'https://static.simonwillison.net/static/2021/datasette_expose_some_environment_variables-0.1-py3-none-any.whl'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;a href="https://gist.github.com/simonw/b6dbb230d755c33490087581821d7082#file-datasette_expose_some_environment_variables-py"&gt;Source code here&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;You can also list URLs like this directly in your &lt;code&gt;requirements.txt&lt;/code&gt; file, one per line.&lt;/p&gt;
&lt;h4&gt;datasette install&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt; has a &lt;code&gt;datasette install&lt;/code&gt; command which wraps &lt;code&gt;pip install&lt;/code&gt;. It exists purely so that people can install Datasette plugins easily without first having to figure out the location of Datasette's Python virtual environment.&lt;/p&gt;
&lt;p&gt;This works with URLs too, so you can install that plugin like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette install https://static.simonwillison.net/static/2021/datasette_expose_some_environment_variables-0.1-py3-none-any.whl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href="https://docs.datasette.io/en/stable/publish.html"&gt;datasette publish&lt;/a&gt; commands have an &lt;code&gt;--install&lt;/code&gt; option for installing plugin which works with URLs too:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette publish cloudrun mydatabase.db \
  --service=plugins-demo \
  --install datasette-vega \
  --install https://static.simonwillison.net/static/2021/datasette_expose_some_environment_variables-0.1-py3-none-any.whl \
  --install datasette-graphql
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Installing branches, tags and commits&lt;/h4&gt;
&lt;p&gt;Any reference in a GitHub repository can be downloaded as a zip file or tarball - that means branches, tags and commits are all available.&lt;/p&gt;
&lt;p&gt;If your repository contains a Python package with a &lt;code&gt;setup.py&lt;/code&gt; file, those URLs will be compatible with &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This means you can use URLs to install tags, branches and even exact commits!&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install https://github.com/simonw/datasette/archive/refs/heads/main.zip&lt;/code&gt; installs the latest &lt;code&gt;main&lt;/code&gt; branch from the &lt;a href="https://github.com/simonw/datasette"&gt;simonw/datasette&lt;/a&gt; repository.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip install https://github.com/simonw/datasette/archive/refs/tags/0.61.1.zip&lt;/code&gt; - installs version 0.61.1 of Datasette, via &lt;a href="https://github.com/simonw/datasette/tree/0.61.1"&gt;this tag&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip install https://github.com/simonw/datasette/archive/refs/heads/0.60.x.zip&lt;/code&gt; - installs the latest head from my &lt;a href="https://github.com/simonw/datasette/tree/0.60.x"&gt;0.60.x branch&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip install https://github.com/simonw/datasette/archive/e64d14e4.zip&lt;/code&gt; - installs the package from the snapshot at commit &lt;a href="https://github.com/simonw/datasette/commit/e64d14e413a955a10df88e106a8b5f1572ec8613"&gt;e64d14e413a955a10df88e106a8b5f1572ec8613&lt;/a&gt; - note that you can use just the first few characters in the URL rather than the full commit hash.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last option, installing for a specific commit hash, is particularly useful in &lt;code&gt;requirements.txt&lt;/code&gt; files since unlike branches or tags you can be certain that the content will not change in the future.&lt;/p&gt;
&lt;p&gt;As you can see, the URLs are all predictable - GitHub has really good URL design. But if you don't want to remember or look them up you can instead find them using the Code -&amp;gt; Download ZIP menu item for any view onto the repository:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2022/github-download-zip.png" alt="Screenshot of the GitHub web interface - click on the green Code button, then right click on Download ZIP and selecet Copy Link" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;h4&gt;Installing from a fork&lt;/h4&gt;
&lt;p&gt;I sometimes use this trick when I find a bug in an open source Python library and need to apply my fix before it has been accepted by upstream.&lt;/p&gt;
&lt;p&gt;I create a fork on GitHub, apply my fix and send a pull request to the project.&lt;/p&gt;
&lt;p&gt;Then in my &lt;code&gt;requirements.txt&lt;/code&gt; file I drop in a URL to the fix in my own repository - with a comment reminding me to switch back to the official package as soon as they've applied the bug fix.&lt;/p&gt;
&lt;h4&gt;Installing pull requests&lt;/h4&gt;
&lt;p&gt;This is a new trick I discovered this morning: there's a hard-to-find URL that lets you do the same thing for code in pull requests.&lt;/p&gt;
&lt;p&gt;Consider &lt;a href="https://github.com/simonw/datasette/pull/1717"&gt;PR #1717&lt;/a&gt; against Datasette, by  Tim Sherratt, adding a &lt;code&gt;--timeout&lt;/code&gt; option the &lt;code&gt;datasette publish cloudrun&lt;/code&gt; command.&lt;/p&gt;
&lt;p&gt;I can install that in a fresh environment on my machine using:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install https://api.github.com/repos/simonw/datasette/zipball/pull/1717/head
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't as useful as checking out the code directly, since it's harder to review the code in a text editor - but it's useful knowing it's possible.&lt;/p&gt;
&lt;h4&gt;Installing gists&lt;/h4&gt;
&lt;p&gt;GitHub &lt;a href="https://gist.github.com/"&gt;Gists&lt;/a&gt; also get URLs to zip files. This means it's possible to create and host a full Python package just using a Gist, by packaging together a &lt;code&gt;setup.py&lt;/code&gt; file and one or more Python  modules.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/b6dbb230d755c33490087581821d7082"&gt;an example Gist&lt;/a&gt; containing my &lt;code&gt; datasette-expose-some-environment-variables&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;You can right click and copy link on the "Download ZIP" button to get this URL:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://gist.github.com/simonw/b6dbb230d755c33490087581821d7082/archive/872818f6b928d9393737eee541c3c76d6aa4b1ba.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then pass that to &lt;code&gt;pip install&lt;/code&gt; or &lt;code&gt;datasette install&lt;/code&gt; to install it.&lt;/p&gt;
&lt;p&gt;That Gist has two files - a &lt;code&gt;setup.py&lt;/code&gt; file containing the following:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;setuptools&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;setup&lt;/span&gt;

&lt;span class="pl-v"&gt;VERSION&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"0.1"&lt;/span&gt;

&lt;span class="pl-en"&gt;setup&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;name&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"datasette-expose-some-environment-variables"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;description&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Expose environment variables in Datasette at /-/env"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;author&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Simon Willison"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;license&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"Apache License, Version 2.0"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;version&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-v"&gt;VERSION&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;py_modules&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s"&gt;"datasette_expose_some_environment_variables"&lt;/span&gt;],
    &lt;span class="pl-s1"&gt;entry_points&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;{
        &lt;span class="pl-s"&gt;"datasette"&lt;/span&gt;: [
            &lt;span class="pl-s"&gt;"expose_some_environment_variables = datasette_expose_some_environment_variables"&lt;/span&gt;
        ]
    },
    &lt;span class="pl-s1"&gt;install_requires&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[&lt;span class="pl-s"&gt;"datasette"&lt;/span&gt;],
)&lt;/pre&gt;
&lt;p&gt;And a &lt;code&gt;datasette_expose_some_environment_variables.py&lt;/code&gt; file containing the actual plugin:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;datasette&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;datasette&lt;/span&gt;.&lt;span class="pl-s1"&gt;utils&lt;/span&gt;.&lt;span class="pl-s1"&gt;asgi&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Response&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;

&lt;span class="pl-v"&gt;REDACT&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; {&lt;span class="pl-s"&gt;"GPG_KEY"&lt;/span&gt;}


&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;env&lt;/span&gt;(&lt;span class="pl-s1"&gt;request&lt;/span&gt;):
    &lt;span class="pl-s1"&gt;output&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; []
    &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt;, &lt;span class="pl-s1"&gt;value&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-s1"&gt;environ&lt;/span&gt;.&lt;span class="pl-en"&gt;items&lt;/span&gt;():
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;key&lt;/span&gt; &lt;span class="pl-c1"&gt;not&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-v"&gt;REDACT&lt;/span&gt;:
            &lt;span class="pl-s1"&gt;output&lt;/span&gt;.&lt;span class="pl-en"&gt;append&lt;/span&gt;(&lt;span class="pl-s"&gt;"{}={}"&lt;/span&gt;.&lt;span class="pl-en"&gt;format&lt;/span&gt;(&lt;span class="pl-s1"&gt;key&lt;/span&gt;, &lt;span class="pl-s1"&gt;value&lt;/span&gt;))
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-v"&gt;Response&lt;/span&gt;.&lt;span class="pl-en"&gt;text&lt;/span&gt;(&lt;span class="pl-s"&gt;"&lt;span class="pl-cce"&gt;\n&lt;/span&gt;"&lt;/span&gt;.&lt;span class="pl-en"&gt;join&lt;/span&gt;(&lt;span class="pl-s1"&gt;output&lt;/span&gt;))


&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;hookimpl&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;register_routes&lt;/span&gt;():
    &lt;span class="pl-k"&gt;return&lt;/span&gt; [
        (&lt;span class="pl-s"&gt;r"^/-/env$"&lt;/span&gt;, &lt;span class="pl-s1"&gt;env&lt;/span&gt;)
    ]&lt;/pre&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="github"/><category term="pip"/><category term="plugins"/><category term="python"/><category term="datasette"/></entry><entry><title>Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies</title><link href="https://simonwillison.net/2021/Feb/10/dependency-confusion/#atom-tag" rel="alternate"/><published>2021-02-10T20:42:06+00:00</published><updated>2021-02-10T20:42:06+00:00</updated><id>https://simonwillison.net/2021/Feb/10/dependency-confusion/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610"&gt;Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Alex Birsan describes a new category of security vulnerability he discovered in the npm, pip and gem packaging ecosystems: if a company uses a private repository with internal package names, uploading a package with the same name to the public repository can often result in an attacker being able to execute their own code inside the networks of their target. Alex scored over $130,000 in bug bounties from this one, from a number of name-brand companies. Of particular note for Python developers: the --extra-index-url argument to pip will consult both public and private registries and install the package with the highest version number!

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/jacobian/status/1359589716790824962"&gt;@jacobian&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;&lt;/p&gt;



</summary><category term="pip"/><category term="python"/><category term="security"/><category term="npm"/></entry><entry><title>How to install and upgrade Datasette using pipx</title><link href="https://simonwillison.net/2020/May/4/datasette-using-pipx/#atom-tag" rel="alternate"/><published>2020-05-04T19:23:24+00:00</published><updated>2020-05-04T19:23:24+00:00</updated><id>https://simonwillison.net/2020/May/4/datasette-using-pipx/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.readthedocs.io/en/latest/installation.html#install-using-pipx"&gt;How to install and upgrade Datasette using pipx&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’ve been using pipx to run Datasette for a while now—it’s a neat Python packaging tool which installs a Python CLI command with all of its dependencies in its own isolated virtual environment. Today, thanks to Twitter, I figured out how to install and upgrade plugins in the same environment—so I added a section to the Datasette installation documentation about it.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1257388367358156803"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;



</summary><category term="pip"/><category term="python"/><category term="datasette"/></entry><entry><title>What to do when PyPI goes down</title><link href="https://simonwillison.net/2010/Jul/21/pypi/#atom-tag" rel="alternate"/><published>2010-07-21T10:19:00+00:00</published><updated>2010-07-21T10:19:00+00:00</updated><id>https://simonwillison.net/2010/Jul/21/pypi/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.jacobian.org/writing/when-pypi-goes-down/#c5203"&gt;What to do when PyPI goes down&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
My deployment scripts tend to rely on PyPI these days (they install dependencies in to a virtualenv) which makes me distinctly uncomfortable. Jacob explains how to use the PyPI mirrors that are starting to come online, but that won’t help if the PyPI listing links to an externally hosted file which starts to 404, as happened with the python-openid package quite recently (now fixed). The comments on the post discuss workarounds, including hosting your own PyPI mirror or bundling tar.gz files of your dependencies with your project.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/deployment"&gt;deployment&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jacob-kaplan-moss"&gt;jacob-kaplan-moss&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/setuptools"&gt;setuptools&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/recovered"&gt;recovered&lt;/a&gt;&lt;/p&gt;



</summary><category term="deployment"/><category term="jacob-kaplan-moss"/><category term="packaging"/><category term="pip"/><category term="pypi"/><category term="python"/><category term="setuptools"/><category term="recovered"/></entry><entry><title>Fabric, Django, Git, Apache, mod_wsgi, virtualenv and pip deployment</title><link href="https://simonwillison.net/2009/Jul/28/fabric/#atom-tag" rel="alternate"/><published>2009-07-28T11:56:09+00:00</published><updated>2009-07-28T11:56:09+00:00</updated><id>https://simonwillison.net/2009/Jul/28/fabric/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://morethanseven.net/2009/07/27/fabric-django-git-apache-mod_wsgi-virtualenv-and-p/"&gt;Fabric, Django, Git, Apache, mod_wsgi, virtualenv and pip deployment&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’m slowly working my way through this stack at the moment—next stop, fabric.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/apache"&gt;apache&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deployment"&gt;deployment&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/fabric"&gt;fabric&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gareth-rushgrove"&gt;gareth-rushgrove&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/git"&gt;git&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/modwsgi"&gt;modwsgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/virtualenv"&gt;virtualenv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/wsgi"&gt;wsgi&lt;/a&gt;&lt;/p&gt;



</summary><category term="apache"/><category term="deployment"/><category term="django"/><category term="fabric"/><category term="gareth-rushgrove"/><category term="git"/><category term="modwsgi"/><category term="pip"/><category term="python"/><category term="virtualenv"/><category term="wsgi"/></entry><entry><title>Tools of the Modern Python Hacker: Virtualenv, Fabric and Pip</title><link href="https://simonwillison.net/2009/Jul/9/tools/#atom-tag" rel="alternate"/><published>2009-07-09T11:40:58+00:00</published><updated>2009-07-09T11:40:58+00:00</updated><id>https://simonwillison.net/2009/Jul/9/tools/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://clemesha.org/blog/2009/jul/05/modern-python-hacker-tools-virtualenv-fabric-pip/"&gt;Tools of the Modern Python Hacker: Virtualenv, Fabric and Pip&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Ashamed to say I’m not using any of these yet—for Django projects, my manage.py inserts an “ext” directory at the beginning of the Python path which contains my dependencies for that project.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/deployment"&gt;deployment&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/fabric"&gt;fabric&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pythonpath"&gt;pythonpath&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tools"&gt;tools&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/virtualenv"&gt;virtualenv&lt;/a&gt;&lt;/p&gt;



</summary><category term="deployment"/><category term="django"/><category term="fabric"/><category term="pip"/><category term="python"/><category term="pythonpath"/><category term="tools"/><category term="virtualenv"/></entry><entry><title>On packaging</title><link href="https://simonwillison.net/2008/Dec/14/packaging/#atom-tag" rel="alternate"/><published>2008-12-14T16:57:42+00:00</published><updated>2008-12-14T16:57:42+00:00</updated><id>https://simonwillison.net/2008/Dec/14/packaging/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.b-list.org/weblog/2008/dec/14/packaging/"&gt;On packaging&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
James Bennett discusses the problems with setuptools (and ruby gems), and recommends Ian Bicking’s pip as a setuptools replacement.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/gems"&gt;gems&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ian-bicking"&gt;ian-bicking&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/james-bennett"&gt;james-bennett&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pip"&gt;pip&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ruby"&gt;ruby&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/setuptools"&gt;setuptools&lt;/a&gt;&lt;/p&gt;



</summary><category term="gems"/><category term="ian-bicking"/><category term="james-bennett"/><category term="pip"/><category term="python"/><category term="ruby"/><category term="setuptools"/></entry></feed>