Commits: 5

Setup devenv with Python, FastAPI and LSP

Implement a simple server. To run it in development mode:

fastapi dev main.py
new file mode 100644
index 0000000..8c1629e
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,12 @@
+if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
+  source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-yMJ2OVMzrFaDPn7q8nCBZFRYpL/f0RcHzhmw/i6btJM="
+fi
+
+export DEVENV_IN_DIRENV_SHELL=true
+
+watch_file flake.nix
+watch_file flake.lock
+if ! use flake . --no-pure-eval
+then
+  echo "devenv could not be built. The devenv environment was not loaded. Make the necessary changes to devenv.nix and hit enter to try again." >&2
+fi
new file mode 100644
index 0000000..18b86e4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.devenv
+.direnv
+__pycache__
new file mode 100644
index 0000000..5eb345c
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,223 @@
+{
+  "nodes": {
+    "cachix": {
+      "inputs": {
+        "devenv": [
+          "devenv"
+        ],
+        "flake-compat": [
+          "devenv"
+        ],
+        "git-hooks": [
+          "devenv",
+          "git-hooks"
+        ],
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1748883665,
+        "narHash": "sha256-R0W7uAg+BLoHjMRMQ8+oiSbTq8nkGz5RDpQ+ZfxxP3A=",
+        "owner": "cachix",
+        "repo": "cachix",
+        "rev": "f707778d902af4d62d8dd92c269f8e70de09acbe",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "latest",
+        "repo": "cachix",
+        "type": "github"
+      }
+    },
+    "devenv": {
+      "inputs": {
+        "cachix": "cachix",
+        "flake-compat": "flake-compat",
+        "git-hooks": "git-hooks",
+        "nix": "nix",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1757003908,
+        "narHash": "sha256-Op3cnPTav+ObcL4R4BGuWHEFxW6YS2A0aE3Av6sZN2g=",
+        "owner": "cachix",
+        "repo": "devenv",
+        "rev": "ac8ebf17828c0e7d9be0270d359123fffcc6f066",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "devenv",
+        "type": "github"
+      }
+    },
+    "flake-compat": {
+      "flake": false,
+      "locked": {
+        "lastModified": 1747046372,
+        "narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
+        "type": "github"
+      },
+      "original": {
+        "owner": "edolstra",
+        "repo": "flake-compat",
+        "type": "github"
+      }
+    },
+    "flake-parts": {
+      "inputs": {
+        "nixpkgs-lib": [
+          "devenv",
+          "nix",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1733312601,
+        "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=",
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "flake-parts",
+        "type": "github"
+      }
+    },
+    "git-hooks": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "flake-compat"
+        ],
+        "gitignore": "gitignore",
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1750779888,
+        "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=",
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "repo": "git-hooks.nix",
+        "type": "github"
+      }
+    },
+    "gitignore": {
+      "inputs": {
+        "nixpkgs": [
+          "devenv",
+          "git-hooks",
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1709087332,
+        "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
+        "type": "github"
+      },
+      "original": {
+        "owner": "hercules-ci",
+        "repo": "gitignore.nix",
+        "type": "github"
+      }
+    },
+    "nix": {
+      "inputs": {
+        "flake-compat": [
+          "devenv",
+          "flake-compat"
+        ],
+        "flake-parts": "flake-parts",
+        "git-hooks-nix": [
+          "devenv",
+          "git-hooks"
+        ],
+        "nixpkgs": [
+          "devenv",
+          "nixpkgs"
+        ],
+        "nixpkgs-23-11": [
+          "devenv"
+        ],
+        "nixpkgs-regression": [
+          "devenv"
+        ]
+      },
+      "locked": {
+        "lastModified": 1755029779,
+        "narHash": "sha256-3+GHIYGg4U9XKUN4rg473frIVNn8YD06bjwxKS1IPrU=",
+        "owner": "cachix",
+        "repo": "nix",
+        "rev": "b0972b0eee6726081d10b1199f54de6d2917f861",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "devenv-2.30",
+        "repo": "nix",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1755783167,
+        "narHash": "sha256-gj7qvMNz7YvhjYxNq4I370cAYIZEw2PbVs5BSwaLrD4=",
+        "owner": "cachix",
+        "repo": "devenv-nixpkgs",
+        "rev": "4a880fb247d24fbca57269af672e8f78935b0328",
+        "type": "github"
+      },
+      "original": {
+        "owner": "cachix",
+        "ref": "rolling",
+        "repo": "devenv-nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "devenv": "devenv",
+        "nixpkgs": "nixpkgs",
+        "systems": "systems"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
new file mode 100644
index 0000000..2640694
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,45 @@
+{
+  inputs = {
+    nixpkgs.url = "github:cachix/devenv-nixpkgs/rolling";
+    systems.url = "github:nix-systems/default";
+    devenv.url = "github:cachix/devenv";
+    devenv.inputs.nixpkgs.follows = "nixpkgs";
+  };
+
+  nixConfig = {
+    extra-trusted-public-keys = "devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw=";
+    extra-substituters = "https://devenv.cachix.org";
+  };
+
+  outputs = { self, nixpkgs, devenv, systems, ... } @ inputs:
+    let
+      forEachSystem = nixpkgs.lib.genAttrs (import systems);
+    in
+    {
+      devShells = forEachSystem
+        (system:
+          let
+            pkgs = nixpkgs.legacyPackages.${system};
+          in
+          {
+            default = devenv.lib.mkShell {
+              inherit inputs pkgs;
+              modules = [
+                {
+                  # https://devenv.sh/reference/options/
+                  packages = [];
+
+                  languages.python = {
+                    enable = true;
+                    venv.enable = true;
+                    venv.requirements = ''
+                      fastapi[standard]
+                      python-lsp-server[all]
+                    '';
+                  };
+                }
+              ];
+            };
+          });
+    };
+}
new file mode 100644
index 0000000..11e82cc
--- /dev/null
+++ b/main.py
@@ -0,0 +1,7 @@
+from fastapi import FastAPI
+
+app = FastAPI()
+
+@app.get("/")
+def get_root():
+    return "A quick hello"

Register a develop process in devenv

To start the development server run

devenv up
index 2640694..75792ae 100644
--- a/flake.nix
+++ b/flake.nix
@@ -37,6 +37,10 @@
=                      python-lsp-server[all]
=                    '';
=                  };
+
+                  processes = {
+                    develop.exec = "fastapi dev main.py";
+                  };
=                }
=              ];
=            };

Add docstrings and a hardcoded list of one joke

It's corny.

index 11e82cc..14c9b97 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,25 @@
+"""
+A sample REST API service.
+
+You can use it to teach concepts of REST.
+"""
+
=from fastapi import FastAPI
=
=app = FastAPI()
+jokes = [
+    "How do you find a velociraptor?\n\nBy taking the integral of the acceleraptor!"
+]
+
=
=@app.get("/")
-def get_root():
+def get_random_joke():
+    """Get a single random joke."""
=    return "A quick hello"
+
+
+@app.get("/jokes")
+def list_jokes():
+    """List all jokes."""
+    return jokes
+

Add type hints, define the Joke class

index 14c9b97..367e33d 100644
--- a/main.py
+++ b/main.py
@@ -5,21 +5,31 @@ You can use it to teach concepts of REST.
="""
=
=from fastapi import FastAPI
+from pydantic import BaseModel
=
=app = FastAPI()
+
+
+class Joke(BaseModel):
+    """A joke."""
+
+    text: str
+
+
=jokes = [
-    "How do you find a velociraptor?\n\nBy taking the integral of the acceleraptor!"
+    Joke(text="How do you find a velociraptor?\n\n" +
+         "By taking the integral of the acceleraptor!")
=]
=
=
=@app.get("/")
-def get_random_joke():
+def get_random_joke() -> Joke:
=    """Get a single random joke."""
-    return "A quick hello"
+    return jokes[0]
=
=
=@app.get("/jokes")
-def list_jokes():
+def list_jokes() -> list[Joke]:
=    """List all jokes."""
=    return jokes
=

Implement the POST /jokes/ endpoint

index 367e33d..1afcfec 100644
--- a/main.py
+++ b/main.py
@@ -33,3 +33,9 @@ def list_jokes() -> list[Joke]:
=    """List all jokes."""
=    return jokes
=
+
+@app.post("/jokes")
+def new_joke(joke: Joke) -> Joke:
+    """Write a new joke."""
+    jokes.append(joke)
+    return joke