
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/productivity","feed_url":"https:\/\/mathspp.com\/blog\/tags\/productivity.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"TIL #140 \u2013 Install Jupyter with uv","date_published":"2026-03-03T16:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","url":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","content_html":"<p>Today I learned how to install jupyter properly while using uv to manage tools.<\/p>\n\n<h2 id=\"running-a-jupyter-notebook-server-or-jupyter-lab\">Running a Jupyter notebook server or Jupyter lab<a href=\"#running-a-jupyter-notebook-server-or-jupyter-lab\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To run a Jupyter notebook server with uv, you can run the command<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter notebook<\/code><\/pre>\n<p>Similarly, if you want to run Jupyter lab, you can run<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter lab<\/code><\/pre>\n<p>Both work, but uv will kindly present a message explaining how it's actually doing you a favour, because it <em>guessed<\/em> what you wanted.\nThat's because <code>uvx something<\/code> usually looks for a package named \u201csomething\u201d with a command called \u201csomething\u201d.<\/p>\n<p>As it turns out, the command <code>jupyter<\/code> comes from the package <code>jupyter-core<\/code>, not from the package <code>jupyter<\/code>.<\/p>\n<h2 id=\"installing-jupyter\">Installing Jupyter<a href=\"#installing-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you're running Jupyter notebooks often, you can install the notebook server and Jupyter lab with<\/p>\n<pre><code class=\"language-bash\">$ uv tool install --with jupyter jupyter-core<\/code><\/pre>\n<h3 id=\"why-uv-tool-install-jupyter-fails\">Why <code>uv tool install jupyter<\/code> fails<a href=\"#why-uv-tool-install-jupyter-fails\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>Running <code>uv tool install jupyter<\/code> fails because the package <code>jupyter<\/code> doesn't provide any commands by itself.<\/p>\n<h3 id=\"why-uv-tool-install-jupyter-core-doesn-t-work\">Why <code>uv tool install jupyter-core<\/code> doesn't work<a href=\"#why-uv-tool-install-jupyter-core-doesn-t-work\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The command <code>uv tool install jupyter-core<\/code> looks like it works because it installs the command <code>jupyter<\/code> correctly.\nHowever, if you use <code>--help<\/code> you can see that you don't have access to the subcommands you need:<\/p>\n<pre><code class=\"language-bash\">$ uv tool install jupyter-core\n...\nInstalled 3 executables: jupyter, jupyter-migrate, jupyter-troubleshoot\n$ jupyter --help\n...\nAvailable subcommands: book migrate troubleshoot<\/code><\/pre>\n<p>That's because the subcommands <code>notebook<\/code> and <code>lab<\/code> are from the package <code>jupyter<\/code>.\nThe solution?\nInstall <code>jupyter-core<\/code> <em>with<\/em> the additional dependency <code>jupyter<\/code>, which is what the command <code>uv tool install --with jupyter jupyter-core<\/code> does.<\/p>\n<h2 id=\"other-usages-of-jupyter\">Other usages of Jupyter<a href=\"#other-usages-of-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The uv documentation has a <a href=\"https:\/\/docs.astral.sh\/uv\/guides\/integration\/jupyter\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">page dedicated exclusively to the usage of uv with Jupyter<\/a>, so check it out for other use cases of the uv and Jupyter combo!<\/p>","summary":"Today I learned how to install jupyter properly while using uv to manage tools.","date_modified":"2026-03-03T18:05:58+01:00","tags":["python","programming","uv","productivity"],"image":"\/user\/pages\/02.blog\/04.til\/140.install-jupyter-with-uv\/thumbnail.webp"},{"title":"TIL #138 \u2013 Custom directives in Jupyter Book","date_published":"2026-01-02T22:22:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/custom-directives-in-jupyter-book","url":"https:\/\/mathspp.com\/blog\/til\/custom-directives-in-jupyter-book","content_html":"<p>Today I learned how to create and register a simple Sphinx extension to use as a custom directive in a Jupyter Book project.<\/p>\n\n<p>I wanted to create a custom directive that I could use in a <a href=\"https:\/\/jupyterbook.org\/v1\/start\/overview.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Jupyter Book<\/a> project that would look like this:<\/p>\n<pre><code>Some prose goes here.\n\n```{mypy} snippet.py\n```<\/code><\/pre>\n<p>Then, the directive <code>{mypy}<\/code> would run mypy against the file <code>snippet.py<\/code> and include the mypy output in the book.<\/p>\n<p>With the help of ChatGPT I was able to quickly whip up a Sphinx extension that defines this directive, including the ability to infer the location of my Python snippets based on the concrete structure I have for this project I'm working on: a file <code>snippet.py<\/code> mentioned in a chapter called <code>xx.my-chapter.md<\/code> can be found in <code>snippets\/my-chapter\/snippet.py<\/code>, where <code>snippets<\/code> is at the root of the project.<\/p>\n<p>After a bit of back and forth and some manual tweaks, this is the directive I ended up with:<\/p>\n<details><summary><code>_ext\/mypy_directive.py<\/code><\/summary><pre><code>\"\"\"\nCreates a Sphinx directive {mypy} that runs mypy on the given file and includes the output.\nWhen used as ```{mypy} script.py in a file called `xx.some-chapter.md`, this directive\nwill try to find the file in `snippets\/some-chapter\/script.py`.\nIf the file argument contains slashes, it is interpreted as an absolute path.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport subprocess\nfrom pathlib import Path\nfrom typing import Any, List\n\nfrom docutils import nodes\nfrom sphinx.util.docutils import SphinxDirective\n\nclass MypyDirective(SphinxDirective):\n    \"\"\"\n    Usage (MyST):\n        ```{mypy} path\/to\/file.py\n        :flags: --strict --show-error-codes\n        ```\n    \"\"\"\n\n    required_arguments = 1  # the file path\n    optional_arguments = 0\n    has_content = False\n\n    option_spec = {\n        \"flags\": lambda s: s,  # pass extra mypy CLI flags as a single string\n    }\n\n    def run(self) -&amp;gt; List[nodes.Node]:\n        script_arg = self.arguments[0]\n        env = self.env\n\n        # Get current document filename (e.g. \"given-chapter\")\n        # env.docname is like \"chapters\/given-chapter\" (no extension)\n        _, _, chapter_name = Path(env.docname).name.partition(\".\")\n\n        # If the user passed just \"script.py\", infer snippets\/&lt;chapter&gt;\/&lt;script.py&gt;.\n        # If they passed a path with a slash, treat it as explicit.\n        if \"\/\" not in script_arg and \"\\\\\" not in script_arg:\n            inferred_rel = str(Path(\"snippets\") \/ chapter_name \/ script_arg)\n        else:\n            inferred_rel = script_arg\n\n        # Sphinx helper: resolve filenames relative to doc, and track dependencies\n        _, abs_path_str = env.relfn2path(inferred_rel)\n        abs_path = Path(abs_path_str)\n\n        # Ensure rebuilds happen when the file changes\n        env.note_dependency(str(abs_path))\n\n        if not abs_path.exists():\n            msg = f\"[mypy] File not found: {abs_path}\"\n            return [nodes.literal_block(text=msg)]\n\n        flags = self.options.get(\"flags\", \"\").strip()\n        cmd: list[str] = [\"mypy\", str(abs_path)]\n        if flags:\n            cmd.extend(flags.split())\n\n        proc = subprocess.run(\n            cmd,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            text=True,\n            cwd=abs_path.parent,\n        )\n\n        output = proc.stdout.rstrip()\n        if not output:\n            output = \"[mypy] (no output)\"\n\n        output = f\"$ mypy {abs_path.name}\\n\" + output\n\n        # Render as a literal block (monospace). &ldquo;language&rdquo; here is just for CSS\/classes.\n        block = nodes.literal_block(output, output)\n        block[\"language\"] = \"text\"\n        return [block]\n\ndef setup(app: Any) -&amp;gt; dict[str, Any]:\n    app.add_directive(\"mypy\", MypyDirective)\n    return {\"version\": \"0.1\", \"parallel_read_safe\": True, \"parallel_write_safe\": True}<\/code><\/pre>\n<p>&lt;\/script.py&gt;<\/p>\n<\/details><p>To be able to use it, I had to tweak the book configuration to tell it where to find my extension:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  ...\n  local_extensions:\n    mypy_directive: _ext\n  extra_extensions:\n    - mypy_directive<\/code><\/pre>\n<p>This assumes the code <code>mypy_directive.py<\/code> lives inside <code>_ext<\/code> in...<\/p>","summary":"Today I learned how to create and register a simple Sphinx extension to use as a custom directive in a Jupyter Book project.","date_modified":"2026-01-02T23:39:07+01:00","tags":["productivity","llm"],"image":"\/user\/pages\/02.blog\/04.til\/138.custom-directives-in-jupyter-book\/thumbnail.webp"},{"title":"TIL #137 \u2013 Inline SVGs in Jupyter notebooks","date_published":"2025-11-23T23:34:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/inline-svgs-in-jupyter-notebooks","url":"https:\/\/mathspp.com\/blog\/til\/inline-svgs-in-jupyter-notebooks","content_html":"<p>Today I learned how to inline SVGs in Jupyter notebooks in two simple steps.<\/p>\n\n<p>Today I learned how to inline SVGs in Jupyter notebooks in two simple steps:<\/p>\n<ol>\n<li>URL-encode the SVG markup. If you have an SVG file, open it and copy the contents of the file starting from the <code>&lt;svg&gt;<\/code> tag all the way up to the closing <code>&lt;\/svg&gt;<\/code> and encode it so it's safe to use in a URL. You can use <a href=\"https:\/\/tools.mathspp.com\/url-encode\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">this URL encoder tool I created<\/a>.<\/li>\n<li>Add an image using Markdown syntax with <code>![ALT text](data:image\/svg+xml,&lt;URL-encoded string here&gt;)<\/code>.<\/li>\n<\/ol>\n<p>For any non-trivial SVG the URL-encoded string will look huge and nasty, as the image below shows:<\/p>\n<figure class=\"image-caption\"><img title=\"The URL-encoded SVG.\" alt=\"Screenshot of a Jupyter notebok with a Markdown cell being edited. The markup in the cell starts with \u201c![](data:image\/svg+xml,\u201d and is followed by a very long string of weird-looking characters with lots of percent signs.\" src=\"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/_markup.webp\"><figcaption class=\"\">The URL-encoded SVG.<\/figcaption><\/figure>\n<p>But when I \u201cexecute\u201d the cell to render the Markdown, the SVG displays neatly:<\/p>\n<figure class=\"image-caption\"><img title=\"The rendered SVG.\" alt=\"Screenshot of an SVG showing 8 diagrams with white and black squares arranged in the same shape but with different colouring schemes.\" src=\"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/_svg.webp\"><figcaption class=\"\">The rendered SVG.<\/figcaption><\/figure>\n<p>This was an interesting endeavour because I thought I could just paste the SVG markup in the notebook cell and it would be rendered; I was under the impression that you could write arbitrary HTML in those cells.\nI was either wrong or I did it in the wrong way!<\/p>","summary":"Today I learned how to inline SVGs in Jupyter notebooks in two simple steps.","date_modified":"2025-11-24T01:18:24+01:00","tags":["productivity","programming"],"image":"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/thumbnail.webp"},{"title":"TIL #136 \u2013 Publish an EPUB book with Jupyter Book","date_published":"2025-11-17T11:01:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/publish-an-epub-book-with-jupyter-book","url":"https:\/\/mathspp.com\/blog\/til\/publish-an-epub-book-with-jupyter-book","content_html":"<p>Today I learned how to set the configurations of my Jupyter Book to build my book in the EPUB format.<\/p>\n\n<h1 id=\"publish-an-epub-book-with-jupyter-book\">Publish an EPUB book with Jupyter Book<a href=\"#publish-an-epub-book-with-jupyter-book\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h1>\n<p>I've been redoing my book <a href=\"\/books\/pydonts\">Pydon'ts \u2013 Write elegant Python code<\/a> in <a href=\"https:\/\/jupyterbook.org\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Jupyter Book<\/a> (v1) because the PDFs have great typesetting defaults and look much better than what I currently have with pandoc plus a couple of custom filters.<\/p>\n<p>I was trying to also get Jupyter Book to build my book in EPUB format but was struggling a bit with it because I was getting a couple of weird warnings when I ran the build command:<\/p>\n<pre><code class=\"language-bash\">bash % jb build --all --builder=custom --custom-builder=epub .<\/code><\/pre>\n<p>This created an EPUB with the name <code>Projectnamenotset.epub<\/code> and the book title was <code>Projectnamenotset<\/code>, so I knew something was off.<\/p>\n<p>The configuration file had the correct metadata:<\/p>\n<pre><code class=\"language-yaml\"># _config.yml\ntitle: Pydon'ts \u2013 Write elegant Python code\nauthor: Rodrigo Gir\u00e3o Serr\u00e3o\n# ...<\/code><\/pre>\n<div class=\"notices yellow\">\n<p>Strictly speaking, I was only getting warnings so the build process was working fine.\nBut I wanted to run the flag <code>-W<\/code>, which turns warnings into errors, so I was hitting a couple of roadblocks on top of the weird project name.<\/p>\n<\/div>\n<h2 id=\"epub3-requires-a-version\">EPUB3 requires a version<a href=\"#epub3-requires-a-version\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I was getting a warning saying that the format EPUB3 required a non-empty version, which just meant I had to specify a version in the Sphinx config:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    version: \"2025.11.17\"<\/code><\/pre>\n<h2 id=\"setting-the-epub-file-name\">Setting the EPUB file name<a href=\"#setting-the-epub-file-name\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To set the EPUB file name to something other than <code>Projectnamenotset.epub<\/code> I had to set the option <code>epub_basename<\/code> in the Sphinx config:<\/p>\n<pre><code class=\"language-yaml\"># _config.yml\nsphinx:\n  config:\n    # ...\n    epub_basename: \"pydonts\"<\/code><\/pre>\n<h2 id=\"setting-the-epub-title\">Setting the EPUB title<a href=\"#setting-the-epub-title\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Although I had the <code>title<\/code> metadata set, I had to set it again in the Sphinx config so it would show as the EPUB title:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    # ...\n    epub_title: \"Pydon'ts \u2013 Write elegant Python code\"<\/code><\/pre>\n<h2 id=\"unknown-mimetype-for-index-html\">Unknown mimetype for <code>index.html<\/code><a href=\"#unknown-mimetype-for-index-html\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Since my root file is not called <code>index<\/code>, I was also getting a warning saying <code>sphinx.errors.SphinxWarning: unknown mimetype for index.html<\/code>.<\/p>\n<p>Long story short, an extension wants me to have the file <code>index.html<\/code> so that I can always navigate to the root URL and see something, and then the file <code>index.html<\/code> just redirects to my custom root, which is <code>foreword<\/code> in this case:<\/p>\n<pre><code class=\"language-yaml\"># _toc.yml\nformat: jb-book\nroot: foreword  # &lt;--\nparts:\n  - caption: Introduction\n    chapters:\n    - file: pydonts\/pydont-disrespect-the-zen-of-python.md\n    # ...<\/code><\/pre>\n<p>So, I had to tell the EPUB builder to ignore the file <code>index.html<\/code>, that was being built but shouldn't be used when building the final EPUB:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    epub_exclude_files:\n      - \"index.html\"<\/code><\/pre>\n<h2 id=\"final-configuration\">Final configuration<a href=\"#final-configuration\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Here's the final set of configurations I use to build the EPUB:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    epub_exclude_files:\n      - \"index.html\"\n    epub_basename: \"pydonts\"\n    epub_title: \"Pydon'ts \u2013 Write elegant Python code\"\n    version: \"2025.11.17\"\n    language: en<\/code><\/pre>\n<p>I build with this command:<\/p>\n<pre><code class=\"language-bash\">bash % jb build --all --builder=custom --custom-builder=epub -W .<\/code><\/pre>","summary":"Today I learned how to set the configurations of my Jupyter Book to build my book in the EPUB format.","date_modified":"2025-11-17T12:20:19+01:00","tags":["productivity"],"image":"\/user\/pages\/02.blog\/04.til\/136.publish-an-epub-book-with-jupyter-book\/thumbnail.webp"},{"title":"TIL #131 \u2013 Change casing in search &amp; replace","date_published":"2025-09-03T00:51:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/change-casing-in-search-and-replace","url":"https:\/\/mathspp.com\/blog\/til\/change-casing-in-search-and-replace","content_html":"<p>Today I learned you can change the casing of matched groups when doing a search &amp; replace in VS Code with regex.<\/p>\n\n<p>VS Code has a search &amp; replace feature that lets you use regex to look for patterns and then reference groups in the replacement...\nBut it lets you do something else that's really cool.<\/p>\n<h2 id=\"changing-casing-with-special-sequences\">Changing casing with special sequences<a href=\"#changing-casing-with-special-sequences\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>When you are replacing groups, you can use special sequences to change the casing of the group you're inserting, according to the following table:<\/p>\n<table>\n<thead>\n<tr>\n<th>Sequence<\/th>\n<th>Effect<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>\\u<\/code><\/td>\n<td>Uppercase the first letter<\/td>\n<\/tr>\n<tr>\n<td><code>\\U<\/code><\/td>\n<td>Uppercase the whole group<\/td>\n<\/tr>\n<tr>\n<td><code>\\l<\/code><\/td>\n<td>Lowercase the first letter<\/td>\n<\/tr>\n<tr>\n<td><code>\\L<\/code><\/td>\n<td>Lowercase the whole group<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The picture below shows an example of a search &amp; replace operation where I looked for the text \u201call in one go\u201d.\nI enclosed that in a regex group and I'm replacing it with the pattern <code>\\U$1<\/code>, which means the replacement would be the all-uppercase string \u201cALL IN ONE GO\u201d:<\/p>\n<figure class=\"image-caption\"><img title=\"Search and replace with \\U.\" alt=\"Screenshot of VS Code previewing a search and replace operation where the special sequence \\U was used to transform the text being replaced.\" src=\"\/user\/pages\/02.blog\/04.til\/131.change-casing-in-search-and-replace\/_uppercase.webp\"><figcaption class=\"\">Search and replace with \\U.<\/figcaption><\/figure>\n<p>Pretty nifty, right?<\/p>\n<h2 id=\"the-same-thing-in-python\">The same thing in Python<a href=\"#the-same-thing-in-python\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The module <code>re<\/code> in Python supports searching and replacing with the function <code>re.sub<\/code> but it doesn't let you do the same case-changing operations with special sequences.\nInstead, you have to use the fact that <a href=\"https:\/\/mathspp.com\/blog\/dynamic-string-replacements-with-regex\"><code>re.sub<\/code> supports dynamic string replacements<\/a> and then implement the logic yourself.<\/p>\n<p>First, you can't use the string methods <code>upper<\/code> and <code>lower<\/code> directly for <code>\\U<\/code> and <code>\\L<\/code>; you have to grab the text from the object <code>Match<\/code>.\nYou also have to pick the string apart to implement the <code>\\u<\/code> and <code>\\l<\/code>:<\/p>\n<pre><code class=\"language-py\">def all_upper(match):  # \\U\n    return match.group(0).upper()\n\ndef first_upper(match):  # \\u\n    s = match.group(0)\n    return s[0].upper() + s[1:]\n\ndef all_lower(match):  # \\L\n    return match.group(0).lower()\n\ndef first_lower(match):  # \\l\n    s = match.group(0)\n    return s[0].lower() + s[1:]<\/code><\/pre>\n<p>Here's an example:<\/p>\n<pre><code class=\"language-py\"># E.g., same behaviour as \\U$0 in VS Code:\nre.sub(\n    \"all in one go\",  # pattern to search for\n    all_upper,  # dynamic replacement\n    \"... all in one go ...\",  # source text\n)  # -&gt; '... ALL IN ONE GO ...'<\/code><\/pre>\n<table>\n<thead>\n<tr>\n<th>VS Code sequence<\/th>\n<th>Python function<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>\\u<\/code><\/td>\n<td><code>first_upper<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>\\U<\/code><\/td>\n<td><code>all_upper<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>\\l<\/code><\/td>\n<td><code>first_lower<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>\\L<\/code><\/td>\n<td><code>all_lower<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>","summary":"Today I learned you can change the casing of matched groups when doing a search &amp; replace in VS Code with regex.","date_modified":"2025-10-20T22:34:56+02:00","tags":["regex","productivity","vscode","python"],"image":"\/user\/pages\/02.blog\/04.til\/131.change-casing-in-search-and-replace\/thumbnail.webp"},{"title":"TIL #130 \u2013 Format Python code directly with uv","date_published":"2025-08-22T18:34:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/format-python-code-directly-with-uv","url":"https:\/\/mathspp.com\/blog\/til\/format-python-code-directly-with-uv","content_html":"<p>Today I learned you can format your Python code directly with uv.<\/p>\n\n<p>In uv version 0.8.13, released one or two days ago, uv added the command <code>format<\/code> that allows you to format your Python code directly through the uv CLI.<\/p>\n<h2 id=\"update-your-uv\">Update your uv<a href=\"#update-your-uv\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>First and foremost, make sure you're rocking uv 0.8.13 or greater by running <code>uv self update<\/code>.<\/p>\n<h2 id=\"format-your-code-with-uv\">Format your code with uv<a href=\"#format-your-code-with-uv\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To format your code with uv you can simply run <code>uv format<\/code>, which will use Ruff to format the code in your current directory:<\/p>\n<pre><code class=\"language-sh\">$ uv format<\/code><\/pre>\n<p>The idea is not to have uv replace Ruff; it's just so that you don't have to think about a separate tool if you don't want to.<\/p>\n<h2 id=\"uv-format-arguments\"><code>uv format<\/code> arguments<a href=\"#uv-format-arguments\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><code>uv format<\/code> accepts the same arguments and options that <code>ruff format<\/code> accepts, so you'll want to <a href=\"https:\/\/docs.astral.sh\/ruff\/formatter\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">check the Ruff docs<\/a> to learn more.\nMy favourite option is <code>--diff<\/code>, to take a look at the formatting diff without doing any formatting changes.<\/p>\n<p>As of now, the feature is marked as being experimental, which means it might change in the future!<\/p>","summary":"Today I learned you can format your Python code directly with uv.","date_modified":"2025-10-20T22:34:56+02:00","tags":["python","programming","productivity","uv"],"image":"\/user\/pages\/02.blog\/04.til\/130.format-python-code-directly-with-uv\/thumbnail.webp"},{"title":"TIL #125 \u2013 Images in docstrings","date_published":"2025-07-06T17:44:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/images-in-docstrings","url":"https:\/\/mathspp.com\/blog\/til\/images-in-docstrings","content_html":"<p>Today I learned you can embed images in your module docstrings and they'll be rendered in the tooltips inside your IDE.<\/p>\n\n<h2 id=\"images-in-docstrings\">Images in docstrings<a href=\"#images-in-docstrings\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><a href=\"https:\/\/bsky.app\/profile\/physicssux.bsky.social\/post\/3lt4rudnbyc2v\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">As it turns out<\/a>, VS Code (and likely other editors, too) render docstrings as Markdown.\nThat means you can add images to your docstrings using Markdown syntax and they will be rendered.<\/p>\n<p>I used <code>uv init --package myproj<\/code> to initialise a project and this is what I put at the top of the file <code>src\/myproj\/__init__.py<\/code> as the module docstring:<\/p>\n<pre><code class=\"language-py\">\"\"\"\nHey there!\n\n![](https:\/\/mathspp.com\/user\/themes\/myquark\/images\/rodrigo_256.png)\n\"\"\"<\/code><\/pre>\n<p>Then, I created another file and typed <code>import myproj<\/code> at the top of the file.\nWhen I hovered over the import line, the tooltip showed my face:<\/p>\n<figure class=\"image-caption\"><img title=\"A VS Code tooltip showing an image.\" alt=\"Screenshot of VS Code showing a tooltip with the documentation for the module myproj that shows a static picture of my face.\" src=\"\/user\/pages\/02.blog\/04.til\/125.images-in-docstrings\/_face.webp\"><figcaption class=\"\">A VS Code tooltip showing an image.<\/figcaption><\/figure>\n<p>This works with GIFs and plain images, as long as you're pointing to a URL on the Internet.\n(It looks like it doesn't work with local files).\nIt also works on other objects that can be documented with docstrings...\nFor example, it also works with functions.<\/p>\n<p>Here's a simple function I defined and documented inside <code>src\/myproj\/__init__.py<\/code>:<\/p>\n<pre><code class=\"language-py\">def two():\n    \"\"\"![](https:\/\/mathspp.com\/blog\/til\/images-in-docstrings\/_two.gif)\"\"\"\n    return 2<\/code><\/pre>\n<p>When I used it in my other file and hovered over its name, I saw the GIF animation inside VS Code:<\/p>\n<figure class=\"image-caption\"><img title=\"A VS Code tooltip showing a GIF.\" alt=\"Screenshot of VS Code showing a tooltip with the documentation for the function myproj.two that includes a GIF of two celebreties, with the one on the right saying the word \u201ctwo\u201d while lifting their pointer and middle fingers in the typical gesture to represent the number two.\" src=\"\/user\/pages\/02.blog\/04.til\/125.images-in-docstrings\/_two.webp\"><figcaption class=\"\">A VS Code tooltip showing a GIF.<\/figcaption><\/figure>\n<p>This is pretty hilarious but a reasonable use case might be to include diagrams for complex functions...<\/p>\n<h3 id=\"try-it-yourself\">Try it yourself!<a href=\"#try-it-yourself\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>Give it a go yourself!\nOpen an arbitrary Python file and paste this code in:<\/p>\n<pre><code class=\"language-py\">def two():\n    \"\"\"![](https:\/\/mathspp.com\/blog\/til\/images-in-docstrings\/_two.gif)\"\"\"\n    return 2\n\nprint(two())<\/code><\/pre>\n<p>Hover over the <code>two()<\/code> in the last line and you should see the \u201cTWO!\u201d GIF playing.<\/p>\n<p><a href=\"https:\/\/bsky.app\/profile\/physicssux.bsky.social\/post\/3lt4rudnbyc2v\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Hats off to \u201cMACE!!!\u201d who indirectly showed me this on BlueSky<\/a>.<\/p>","summary":"Today I learned you can embed images in your module docstrings and they&#039;ll be rendered in the tooltips inside your IDE.","date_modified":"2025-10-20T22:34:56+02:00","tags":["productivity","vscode"],"image":"\/user\/pages\/02.blog\/04.til\/125.images-in-docstrings\/thumbnail.webp"},{"title":"Automatically pushing code changes during live coding with uv","date_published":"2025-05-14T10:25:00+02:00","id":"https:\/\/mathspp.com\/blog\/automatically-pushing-code-changes-during-live-coding-with-uv","url":"https:\/\/mathspp.com\/blog\/automatically-pushing-code-changes-during-live-coding-with-uv","content_html":"<p>This article shows the small script I use to automatically push code changes while live coding in talks or classes, improved with uv.<\/p>\n\n<p>One year ago I <a href=\"\/blog\/til\/automatically-push-code-changes-during-live-coding\">wrote a short TIL article where I showed how I automatically push code changes while I'm live-coding<\/a>.\nThe code I shared looked like this:<\/p>\n<pre><code class=\"language-py\">from pathlib import Path\nfrom time import sleep\n\nfrom git import Repo\n\nrepo = Repo(Path(__file__).parent)\n\nwhile True:\n    repo.index.add(\"*\")\n    repo.index.commit(\"Auto sync commit\")\n    repo.remote().push()\n    sleep(60)<\/code><\/pre>\n<p>This code uses <code>GitPython<\/code> to add all files in the folder that contains this script, adds a generic commit message, and pushes.\nThe script does this every minute, so when I'm teaching or doing any sort of live-coding, the repo I'm working off of gets updated every minute and participants can keep tabs on what I'm writing.<\/p>\n<p>Having used this for a while, there are two disadvantages to it:<\/p>\n<ol>\n<li>I need to install <code>GitPython<\/code> in a virtual environment in each repo I want to use this on; and<\/li>\n<li>the script itself gets pushed to the repo, polluting it a bit.<\/li>\n<\/ol>\n<p>(As I write this, I realised, I could fix 2. by adding the script to a file <code>.gitignore<\/code>!)<\/p>\n<p>To fix 1. and 2., and to improve my user experience a bit, I <a href=\"\/blog\/til\/standalone-executable-python-scripts-with-uv\">started using uv to inline the dependency on <code>GitPython<\/code> and to turn it into a standalone executable<\/a>.<\/p>\n<p>Assuming the script was called <code>gitsync.py<\/code>, by doing <code>uv add GitPython --script gitsync.py<\/code> and by <a href=\"\/link-blog\/simonwillison-net-2024-aug-21-usrbinenv-uv-run\">adding the uv shebang<\/a>, the top of the script now looks like this:<\/p>\n<pre><code class=\"language-py\">#!\/usr\/bin\/env -S uv run\n\n# \/\/\/ script\n# requires-python = \"&gt;=3.13\"\n# dependencies = [\n#     \"gitpython\",\n# ]\n# \/\/\/<\/code><\/pre>\n<p>I also tweaked the script structure a bit and now I use <code>os.getcwd()<\/code> to figure out the current working directory when I run <code>gitsync.py<\/code>:<\/p>\n<pre><code class=\"language-py\">#!\/usr\/bin\/env -S uv run\n\n# \/\/\/ script\n# requires-python = \"&gt;=3.13\"\n# dependencies = [\n#     \"gitpython\",\n# ]\n# \/\/\/\n\nimport os\nfrom time import sleep\n\nfrom git import Repo\n\ndef main() -&gt; None:\n    repo_folder = os.getcwd()\n    print(f\"gitsync.py starting at {repo_folder}\")\n    repo = Repo(repo_folder)\n\n    while True:\n        repo.index.add(\"*\")\n        repo.index.commit(\"Auto sync commit\")\n        repo.remote().push()\n        sleep(60)\n\nif __name__ == \"__main__\":\n    main()<\/code><\/pre>\n<p>This makes it so that I can put <code>gitsync.py<\/code> in a directory that's in my PATH, and then use it from anywhere.\nNow, when I'm teaching, I just run <code>gitsync.py &amp;<\/code> and that starts syncing my code in the background.\nPretty cool!<\/p>","summary":"This article shows the small script I use to automatically push code changes while live coding in talks or classes, improved with uv.","date_modified":"2025-07-23T16:49:02+02:00","tags":["git","productivity","programming","python","uv"],"image":"\/user\/pages\/02.blog\/automatically-pushing-code-changes-during-live-coding-with-uv\/thumbnail.webp"},{"title":"TIL #122 \u2013 CLI tools with extra dependencies","date_published":"2025-05-04T09:34:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/cli-tools-with-extra-dependencies","url":"https:\/\/mathspp.com\/blog\/til\/cli-tools-with-extra-dependencies","content_html":"<p>Today I learned you can use uv to install CLI tools with extra dependencies.<\/p>\n\n<h2 id=\"cli-tools-with-extra-dependencies\">CLI tools with extra dependencies<a href=\"#cli-tools-with-extra-dependencies\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>You can use <code>uv<\/code> to install CLI tools in isolated virtual environments.\nE.g., <code>uv tool install black<\/code> installs <code>black<\/code> in an isolated venv.\nAfter installing, <code>black<\/code> will be added to your path and you can use it directly.<\/p>\n<p>When installing tools, you can use the option <code>--with<\/code> to specify extra dependencies that will be available when you use the CLI tool you are installing.\nHere are two examples:<\/p>\n<ul>\n<li><code>uv tool install --with pandas --with polars marimo<\/code> \u2013 this installs Marimo and makes sure Pandas and Polars will be available inside Marimo notebooks.<\/li>\n<li><code>uv tool install --with \"ruamel.yaml\" cogapp<\/code> \u2013 this installs cog, a file generation tool, but makes sure there is a YAML parser available to be used by cog (I use this a lot to build my books).<\/li>\n<\/ul>","summary":"Today I learned you can use uv to install CLI tools with extra dependencies.","date_modified":"2025-10-20T22:34:56+02:00","tags":["productivity","python","uv"],"image":"\/user\/pages\/02.blog\/04.til\/122.cli-tools-with-extra-dependencies\/thumbnail.webp"},{"title":"TIL #121 \u2013 uv escape hatch","date_published":"2025-03-12T15:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/use-pip-directly-from-a-uv-virtual-environment","url":"https:\/\/mathspp.com\/blog\/til\/use-pip-directly-from-a-uv-virtual-environment","content_html":"<p>Today I learned how to use uv to escape uv and go back to using venv and pip for a given project.<\/p>\n\n<h2 id=\"use-pip-directly-from-a-uv-virtual-environment\">Use pip directly from a uv virtual environment<a href=\"#use-pip-directly-from-a-uv-virtual-environment\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>uv comes with the subcommands <code>uv venv<\/code> and <code>uv pip<\/code> that let you use your <code>venv<\/code> + <code>pip<\/code> workflows, with the commands you already know, while benefiting from the speed of uv.\nHowever, I have a specific project in which I need to be able to use <code>pip<\/code> directly from the virtual environment that uv created.<\/p>\n<p>After I ran <code>uv venv<\/code> and activated my virtual environment, I tried using <code>pip<\/code> to install a package with <code>python -m pip install my_package<\/code> but got an error message saying \u201c.venv\/bin\/python: No module named pip\u201d.<\/p>\n<p>To fix this, I used the option <code>--seed<\/code> that seeds my virtual environment with <code>pip<\/code>.\nSo, I recreated my virtual environment with <code>uv venv --seed<\/code>, activated it, and then I was able to use <code>pip<\/code> directly from within the virtual environment.<\/p>\n<p>This kind of goes against the point of uv, but the fact that there is an escape hatch for random situations like mine just goes to show that uv is well thought out...<\/p>","summary":"Today I learned how to use uv to escape uv and go back to venv and pip for a given project.","date_modified":"2025-10-20T22:34:56+02:00","tags":["productivity","python","uv"],"image":"\/user\/pages\/02.blog\/04.til\/121.use-pip-directly-from-a-uv-virtual-environment\/thumbnail.webp"}]}
