From c9a4bfec4c8ff14801e4a745c5652c6b4c340ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Tue, 13 Aug 2024 22:53:50 -0500 Subject: [PATCH 01/34] =?UTF-8?q?=F0=9F=91=B7=20Update=20labeler=20GitHub?= =?UTF-8?q?=20Actions=20permissions=20and=20dependencies=20(#1071)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7cb88936b..7c5b4616a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -19,6 +19,10 @@ jobs: - uses: actions/labeler@v5 # Run this after labeler applied labels check-labels: + needs: + - labeler + permissions: + pull-requests: read name: Check labels runs-on: ubuntu-latest steps: From 714d795e9075fcc68796d33c262d0506969aef65 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Aug 2024 03:56:02 +0000 Subject: [PATCH 02/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 8cb9be525..1cd370091 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1069](https://github.com/fastapi/sqlmodel/pull/1069) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action labeler. PR [#1068](https://github.com/fastapi/sqlmodel/pull/1068) by [@tiangolo](https://github.com/tiangolo). * 👷 Update GitHub Action add-to-project. PR [#1067](https://github.com/fastapi/sqlmodel/pull/1067) by [@tiangolo](https://github.com/tiangolo). From 383938155fd07ae399c830c9456b2ff2ca3dee88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Wed, 14 Aug 2024 14:02:07 -0500 Subject: [PATCH 03/34] =?UTF-8?q?=F0=9F=91=B7=20Update=20configs=20for=20G?= =?UTF-8?q?itHub=20Action=20labeler,=20to=20add=20only=20one=20label=20(#1?= =?UTF-8?q?072)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/labeler.yml | 33 +++++++++++++++++++++------------ .github/workflows/labeler.yml | 5 +++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/labeler.yml b/.github/labeler.yml index f4ee6649d..1af3d5e40 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,15 +1,24 @@ docs: - - changed-files: - - any-glob-to-any-file: - - docs/**/* - - docs_src/**/* + - all: + - changed-files: + - any-glob-to-any-file: + - docs/** + - docs_src/** + - all-globs-to-all-files: + - '!sqlmodel/**' + - '!pyproject.toml' internal: - - changed-files: - - any-glob-to-any-file: - - .github/**/* - - scripts/**/* - - .gitignore - - .pre-commit-config.yaml - - pdm_build.py - - requirements*.txt + - all: + - changed-files: + - any-glob-to-any-file: + - .github/** + - scripts/** + - .gitignore + - .pre-commit-config.yaml + - pdm_build.py + - requirements*.txt + - all-globs-to-all-files: + - '!docs/**' + - '!sqlmodel/**' + - '!pyproject.toml' diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 7c5b4616a..d62f1668d 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,4 +1,4 @@ -name: Pull Request Labeler and Checker +name: Labels on: pull_request_target: types: @@ -17,13 +17,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 + with: + sync-labels: true # Run this after labeler applied labels check-labels: needs: - labeler permissions: pull-requests: read - name: Check labels runs-on: ubuntu-latest steps: - uses: docker://agilepathway/pull-request-label-checker:latest From 865d4c8417caf5e91f820c8deafe556bcdf86078 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 14 Aug 2024 19:02:27 +0000 Subject: [PATCH 04/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1cd370091..2ee51f967 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo). * 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1069](https://github.com/fastapi/sqlmodel/pull/1069) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action labeler. PR [#1068](https://github.com/fastapi/sqlmodel/pull/1068) by [@tiangolo](https://github.com/tiangolo). From 756e70a24e41b1a7e65486fc0dd3e5a7ea496540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 15 Aug 2024 13:46:10 -0500 Subject: [PATCH 05/34] =?UTF-8?q?=F0=9F=91=B7=20Do=20not=20sync=20labels?= =?UTF-8?q?=20as=20it=20overrides=20manually=20added=20labels=20(#1073)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/labeler.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index d62f1668d..c3bb83f9a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -17,8 +17,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/labeler@v5 - with: - sync-labels: true # Run this after labeler applied labels check-labels: needs: From 65453903099957eb8a1d792dac240a5303a70242 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 15 Aug 2024 18:46:35 +0000 Subject: [PATCH 06/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 2ee51f967..218140a21 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo). * 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo). * 👷 Add GitHub Action label-checker. PR [#1069](https://github.com/fastapi/sqlmodel/pull/1069) by [@tiangolo](https://github.com/tiangolo). From be94cd77fce86b1f0ddf1e3165a55a1b5fbe8efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 15 Aug 2024 15:52:00 -0500 Subject: [PATCH 07/34] =?UTF-8?q?=F0=9F=94=A7=20Add=20URLs=20to=20`pyproje?= =?UTF-8?q?ct.toml`,=20show=20up=20in=20PyPI=20(#1074)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 11e83daa1..a27309b0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,6 +43,8 @@ dependencies = [ Homepage = "https://github.com/fastapi/sqlmodel" Documentation = "https://sqlmodel.tiangolo.com" Repository = "https://github.com/fastapi/sqlmodel" +Issues = "https://github.com/fastapi/sqlmodel/issues" +Changelog = "https://sqlmodel.tiangolo.com/release-notes/" [tool.pdm] version = { source = "file", path = "sqlmodel/__init__.py" } From 805718d78dcc000f5b0bc04b444fcdee127a9170 Mon Sep 17 00:00:00 2001 From: github-actions Date: Thu, 15 Aug 2024 20:53:41 +0000 Subject: [PATCH 08/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 218140a21..1df2bcdee 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo). * 👷 Update labeler GitHub Actions permissions and dependencies. PR [#1071](https://github.com/fastapi/sqlmodel/pull/1071) by [@tiangolo](https://github.com/tiangolo). From e448c339dba0f24d75fe8bf6abc4c02b89acca2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 18 Aug 2024 16:15:32 -0500 Subject: [PATCH 09/34] =?UTF-8?q?=F0=9F=94=A7=20Update=20coverage=20config?= =?UTF-8?q?=20files=20(#1077)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- pyproject.toml | 7 +++++++ scripts/test.sh | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8fbbc324b..86e91cb6c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -100,7 +100,7 @@ jobs: - run: ls -la coverage - run: coverage combine coverage - run: coverage report - - run: coverage html --show-contexts --title "Coverage for ${{ github.sha }}" + - run: coverage html --title "Coverage for ${{ github.sha }}" - name: Store coverage HTML uses: actions/upload-artifact@v4 diff --git a/pyproject.toml b/pyproject.toml index a27309b0a..e3b70b5ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,14 +73,18 @@ optional-dependencies = {} [tool.coverage.run] parallel = true +data_file = "coverage/.coverage" source = [ "docs_src", "tests", "sqlmodel" ] context = '${CONTEXT}' +dynamic_context = "test_function" [tool.coverage.report] +show_missing = true +sort = "-Cover" exclude_lines = [ "pragma: no cover", "@overload", @@ -88,6 +92,9 @@ exclude_lines = [ "if TYPE_CHECKING:", ] +[tool.coverage.html] +show_contexts = true + [tool.mypy] strict = true diff --git a/scripts/test.sh b/scripts/test.sh index 9b758bdbd..ff4b114b1 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -5,5 +5,5 @@ set -x coverage run -m pytest tests coverage combine -coverage report --show-missing +coverage report coverage html From 45f49d57c1d006565c2fabdb0fa8865cd02c89c7 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 18 Aug 2024 21:16:03 +0000 Subject: [PATCH 10/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1df2bcdee..110a3677e 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -9,6 +9,7 @@ ### Internal +* 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo). * 👷 Update configs for GitHub Action labeler, to add only one label. PR [#1072](https://github.com/fastapi/sqlmodel/pull/1072) by [@tiangolo](https://github.com/tiangolo). From c96d6b791cbe60b3fcdcc864e91e4e78443ec341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 14:03:39 -0500 Subject: [PATCH 11/34] =?UTF-8?q?=F0=9F=93=9D=20Add=20docs=20for=20virtual?= =?UTF-8?q?=20environments=20and=20environment=20variables,=20update=20con?= =?UTF-8?q?tributing=20(#1082)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- README.md | 2 + docs/contributing.md | 134 ++-- docs/databases.md | 2 +- docs/environment-variables.md | 300 ++++++++ docs/index.md | 2 + docs/install.md | 39 ++ docs/tutorial/fastapi/simple-hero-api.md | 6 +- docs/tutorial/fastapi/tests.md | 4 +- docs/tutorial/index.md | 197 ------ docs/virtual-environments.md | 844 +++++++++++++++++++++++ mkdocs.yml | 3 + 12 files changed, 1284 insertions(+), 251 deletions(-) create mode 100644 docs/environment-variables.md create mode 100644 docs/install.md create mode 100644 docs/virtual-environments.md diff --git a/.gitignore b/.gitignore index 65f9c629b..9e195bfa7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ *.pyc -env* .mypy_cache .vscode .idea @@ -12,3 +11,4 @@ coverage.xml site *.db .cache +.venv* diff --git a/README.md b/README.md index d62606ed7..fc38789b7 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,8 @@ As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. T ## Installation +Make sure you create a virtual environment, activate it, and then install SQLModel, for example with: +
```console diff --git a/docs/contributing.md b/docs/contributing.md index 217ed61c5..e3693c5a8 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,49 +4,43 @@ First, you might want to see the basic ways to [help SQLModel and get help](help ## Developing -If you already cloned the repository and you know that you need to deep dive in the code, here are some guidelines to set up your environment. +If you already cloned the sqlmodel repository and you want to deep dive in the code, here are some guidelines to set up your environment. -### Poetry +### Virtual Environment -**SQLModel** uses Poetry to build, package, and publish the project. +Follow the instructions to create and activate a [virtual environment](virtual-environments.md){.internal-link target=_blank} for the internal code of `sqlmodel`. -You can learn how to install it in the Poetry docs. +### Install Requirements Using `pip` -After having Poetry available, you can install the development dependencies: +After activating the environment, install the required packages:
```console -$ poetry install +$ pip install -r requirements.txt ---> 100% ```
-It will also create a virtual environment automatically and will install all the dependencies and your local SQLModel in it. +It will install all the dependencies and your local SQLModel in your local environment. -### Poetry Shell +### Using your Local SQLModel -To use your current environment, and to have access to all the tools in it (for example `pytest` for the tests) enter into a Poetry Shell: +If you create a Python file that imports and uses SQLModel, and run it with the Python from your local environment, it will use your cloned local SQLModel source code. -
- -```console -$ poetry shell -``` +And if you update that local SQLModel source code when you run that Python file again, it will use the fresh version of SQLModel you just edited. -
- -That will set up the environment variables needed and start a new shell with them. +That way, you don't have to "install" your local version to be able to test every change. -#### Using your local SQLModel +/// note | "Technical Details" -If you create a Python file that imports and uses SQLModel, and run it with the Python from your local Poetry environment, it will use your local SQLModel source code. +This only happens when you install using this included `requirements.txt` instead of running `pip install sqlmodel` directly. -And if you update that local SQLModel source code, when you run that Python file again, it will use the fresh version of SQLModel you just edited. +That is because inside the `requirements.txt` file, the local version of SQLModel is marked to be installed in "editable" mode, with the `-e` option. -Poetry takes care of making that work. But of course, it will only work in the current Poetry environment, if you install standard SQLModel in another environment (not from the source in the GitHub repo), that will use the standard SQLModel, not your custom version. +/// ### Format @@ -62,41 +56,36 @@ $ bash scripts/format.sh It will also auto-sort all your imports. -## Docs - -The documentation uses MkDocs with Material for MkDocs. - -All the documentation is in Markdown format in the directory `./docs`. +## Tests -Many of the tutorials have blocks of code. +There is a script that you can run locally to test all the code and generate coverage reports in HTML: -In most of the cases, these blocks of code are actual complete applications that can be run as is. +
-In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory. +```console +$ bash scripts/test-cov-html.sh +``` -And those Python files are included/injected in the documentation when generating the site. +
-### Docs for tests +This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. -Most of the tests actually run against the example source files in the documentation. +## Docs -This helps making sure that: +First, make sure you set up your environment as described above, that will install all the requirements. -* The documentation is up to date. -* The documentation examples can be run as is. -* Most of the features are covered by the documentation, ensured by test coverage. +### Docs Live During local development, there is a script that builds the site and checks for any changes, live-reloading:
```console -$ bash scripts/docs-live.sh +$ python ./scripts/docs.py live -[INFO] - Building documentation... -[INFO] - Cleaning site directory -[INFO] - Documentation built in 2.74 seconds -[INFO] - Serving on http://127.0.0.1:8008 +[INFO] Serving on http://127.0.0.1:8008 +[INFO] Start watching changes +[INFO] Start detecting changes ```
@@ -105,20 +94,71 @@ It will serve the documentation on `http://127.0.0.1:8008`. That way, you can edit the documentation/source files and see the changes live. -## Tests +/// tip -There is a script that you can run locally to test all the code and generate coverage reports in HTML: +Alternatively, you can perform the same steps that scripts does manually. + +Go into the docs director at `docs/`: + +```console +$ cd docs/ +``` + +Then run `mkdocs` in that directory: + +```console +$ mkdocs serve --dev-addr 8008 +``` + +/// + +#### Typer CLI (Optional) + +The instructions here show you how to use the script at `./scripts/docs.py` with the `python` program directly. + +But you can also use Typer CLI, and you will get autocompletion in your terminal for the commands after installing completion. + +If you install Typer CLI, you can install completion with:
```console -$ bash scripts/test.sh +$ typer --install-completion + +zsh completion installed in /home/user/.bashrc. +Completion will take effect once you restart the terminal. ```
-This command generates a directory `./htmlcov/`, if you open the file `./htmlcov/index.html` in your browser, you can explore interactively the regions of code that are covered by the tests, and notice if there is any region missing. +### Docs Structure + +The documentation uses MkDocs. -## Thanks +And there are extra tools/scripts in place in `./scripts/docs.py`. -Thanks for contributing! ☕ +/// tip + +You don't need to see the code in `./scripts/docs.py`, you just use it in the command line. + +/// + +All the documentation is in Markdown format in the directory `./docs`. + +Many of the tutorials have blocks of code. + +In most of the cases, these blocks of code are actual complete applications that can be run as is. + +In fact, those blocks of code are not written inside the Markdown, they are Python files in the `./docs_src/` directory. + +And those Python files are included/injected in the documentation when generating the site. + +### Docs for Tests + +Most of the tests actually run against the example source files in the documentation. + +This helps to make sure that: + +* The documentation is up-to-date. +* The documentation examples can be run as is. +* Most of the features are covered by the documentation, ensured by test coverage. diff --git a/docs/databases.md b/docs/databases.md index ff5ed9f77..e87476726 100644 --- a/docs/databases.md +++ b/docs/databases.md @@ -4,7 +4,7 @@ Are you a seasoned developer and already know everything about databases? 🤓 -Then you can skip to the [Tutorial - User Guide: First Steps](tutorial/index.md){.internal-link target=_blank} right away. +Then you can skip to the next sections right away. /// diff --git a/docs/environment-variables.md b/docs/environment-variables.md new file mode 100644 index 000000000..bd8b2a06d --- /dev/null +++ b/docs/environment-variables.md @@ -0,0 +1,300 @@ +# Environment Variables + +Before we jump into code, let's cover a bit some of the **basics** that we'll need to understand how to work with Python (and programming) in general. Let's check a bit about **environment variables**. + +/// tip + +If you already know what "environment variables" are and how to use them, feel free to skip this. + +/// + +An environment variable (also known as "**env var**") is a variable that lives **outside** of the Python code, in the **operating system**, and could be read by your Python code (or by other programs as well). + +Environment variables could be useful for handling application **settings**, as part of the **installation** of Python, etc. + +## Create and Use Env Vars + +You can **create** and use environment variables in the **shell (terminal)**, without needing Python: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// You could create an env var MY_NAME with +$ export MY_NAME="Wade Wilson" + +// Then you could use it with other programs, like +$ echo "Hello $MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Create an env var MY_NAME +$ $Env:MY_NAME = "Wade Wilson" + +// Use it with other programs, like +$ echo "Hello $Env:MY_NAME" + +Hello Wade Wilson +``` + +
+ +//// + +## Read env vars in Python + +You could also create environment variables **outside** of Python, in the terminal (or with any other method), and then **read them in Python**. + +For example you could have a file `main.py` with: + +```Python hl_lines="3" +import os + +name = os.getenv("MY_NAME", "World") +print(f"Hello {name} from Python") +``` + +/// tip + +The second argument to `os.getenv()` is the default value to return. + +If not provided, it's `None` by default, here we provide `"World"` as the default value to use. + +/// + +Then you could call that Python program: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ export MY_NAME="Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +// Here we don't set the env var yet +$ python main.py + +// As we didn't set the env var, we get the default value + +Hello World from Python + +// But if we create an environment variable first +$ $Env:MY_NAME = "Wade Wilson" + +// And then call the program again +$ python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python +``` + +
+ +//// + +As environment variables can be set outside of the code, but can be read by the code, and don't have to be stored (committed to `git`) with the rest of the files, it's common to use them for configurations or **settings**. + +You can also create an environment variable only for a **specific program invocation**, that is only available to that program, and only for its duration. + +To do that, create it right before the program itself, on the same line: + +
+ +```console +// Create an env var MY_NAME in line for this program call +$ MY_NAME="Wade Wilson" python main.py + +// Now it can read the environment variable + +Hello Wade Wilson from Python + +// The env var no longer exists afterwards +$ python main.py + +Hello World from Python +``` + +
+ +/// tip + +You can read more about it at The Twelve-Factor App: Config. + +/// + +## Types and Validation + +These environment variables can only handle **text strings**, as they are external to Python and have to be compatible with other programs and the rest of the system (and even with different operating systems, as Linux, Windows, macOS). + +That means that **any value** read in Python from an environment variable **will be a `str`**, and any conversion to a different type or any validation has to be done in code. + +## `PATH` Environment Variable + +There is a **special** environment variable called **`PATH`** that is used by the operating systems (Linux, macOS, Windows) to find programs to run. + +The value of the variable `PATH` is a long string that is made of directories separated by a colon `:` on Linux and macOS, and by a semicolon `;` on Windows. + +For example, the `PATH` environment variable could look like this: + +//// tab | Linux, macOS + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +This means that the system should look for programs in the directories: + +* `/usr/local/bin` +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32 +``` + +This means that the system should look for programs in the directories: + +* `C:\Program Files\Python312\Scripts` +* `C:\Program Files\Python312` +* `C:\Windows\System32` + +//// + +When you type a **command** in the terminal, the operating system **looks for** the program in **each of those directories** listed in the `PATH` environment variable. + +For example, when you type `python` in the terminal, the operating system looks for a program called `python` in the **first directory** in that list. + +If it finds it, then it will **use it**. Otherwise it keeps looking in the **other directories**. + +### Installing Python and Updating the `PATH` + +When you install Python, you might be asked if you want to update the `PATH` environment variable. + +//// tab | Linux, macOS + +Let's say you install Python and it ends up in a directory `/opt/custompython/bin`. + +If you say yes to update the `PATH` environment variable, then the installer will add `/opt/custompython/bin` to the `PATH` environment variable. + +It could look like this: + +```plaintext +/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/opt/custompython/bin +``` + +This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. + +//// + +//// tab | Windows + +Let's say you install Python and it ends up in a directory `C:\opt\custompython\bin`. + +If you say yes to update the `PATH` environment variable, then the installer will add `C:\opt\custompython\bin` to the `PATH` environment variable. + +```plaintext +C:\Program Files\Python312\Scripts;C:\Program Files\Python312;C:\Windows\System32;C:\opt\custompython\bin +``` + +This way, when you type `python` in the terminal, the system will find the Python program in `C:\opt\custompython\bin` (the last directory) and use that one. + +//// + +This way, when you type `python` in the terminal, the system will find the Python program in `/opt/custompython/bin` (the last directory) and use that one. + +So, if you type: + +
+ +```console +$ python +``` + +
+ +//// tab | Linux, macOS + +The system will **find** the `python` program in `/opt/custompython/bin` and run it. + +It would be roughly equivalent to typing: + +
+ +```console +$ /opt/custompython/bin/python +``` + +
+ +//// + +//// tab | Windows + +The system will **find** the `python` program in `C:\opt\custompython\bin\python` and run it. + +It would be roughly equivalent to typing: + +
+ +```console +$ C:\opt\custompython\bin\python +``` + +
+ +//// + +This information will be useful when learning about [Virtual Environments](virtual-environments.md){.internal-link target=_blank}. + +## Conclusion + +With this you should have a basic understanding of what **environment variables** are and how to use them in Python. + +You can also read more about them in the Wikipedia for Environment Variable. + +In many cases it's not very obvious how environment variables would be useful and applicable right away. But they keep showing up in many different scenarios when you are developing, so it's good to know about them. + +For example, you will need this information in the next section, about [Virtual Environments](virtual-environments.md). diff --git a/docs/index.md b/docs/index.md index f5a34f837..96062f036 100644 --- a/docs/index.md +++ b/docs/index.md @@ -78,6 +78,8 @@ As **SQLModel** is based on **Pydantic** and **SQLAlchemy**, it requires them. T ## Installation +Make sure you create a virtual environment, activate it, and then install SQLModel, for example with: +
```console diff --git a/docs/install.md b/docs/install.md new file mode 100644 index 000000000..8ac2e04c2 --- /dev/null +++ b/docs/install.md @@ -0,0 +1,39 @@ +# Install **SQLModel** + +Create a project directory, create a [virtual environment](virtual-environments.md){.internal-link target=_blank}, activate it, and then install **SQLModel**, for example with: + +
+ +```console +$ pip install sqlmodel +---> 100% +Successfully installed sqlmodel pydantic sqlalchemy +``` + +
+ +As **SQLModel** is built on top of SQLAlchemy and Pydantic, when you install `sqlmodel` they will also be automatically installed. + +## Install DB Browser for SQLite + +Remember that [SQLite is a simple database in a single file](../databases.md#a-single-file-database){.internal-link target=_blank}? + +For most of the tutorial I'll use SQLite for the examples. + +Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](../databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning. + +In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like PostgreSQL (which is also free). But for now we'll stick to SQLite. + +Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data. + +To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use DB Browser for SQLite. + +It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface. + + + +Go ahead and Install DB Browser for SQLite, it's free. + +## Next Steps + +Okay, let's get going! On the next section we'll start the [Tutorial - User Guide](tutorial/index.md). 🚀 diff --git a/docs/tutorial/fastapi/simple-hero-api.md b/docs/tutorial/fastapi/simple-hero-api.md index 0a9611895..fc85f2a00 100644 --- a/docs/tutorial/fastapi/simple-hero-api.md +++ b/docs/tutorial/fastapi/simple-hero-api.md @@ -10,14 +10,14 @@ FastAPI is the framework to create the **web API**. But we also need another type of program to run it, it is called a "**server**". We will use **Uvicorn** for that. And we will install Uvicorn with its *standard* dependencies. -Make sure you [have a virtual environment activated](../index.md#create-a-python-virtual-environment){.internal-link target=_blank}. +Then install FastAPI. -Then install FastAPI and Uvicorn: +Make sure you create a [virtual environment](../../virtual-environments.md){.internal-link target=_blank}, activate it, and then install them, for example with:
```console -$ python -m pip install fastapi "uvicorn[standard]" +$ pip install fastapi "uvicorn[standard]" ---> 100% ``` diff --git a/docs/tutorial/fastapi/tests.md b/docs/tutorial/fastapi/tests.md index 33b17f87d..922103bdb 100644 --- a/docs/tutorial/fastapi/tests.md +++ b/docs/tutorial/fastapi/tests.md @@ -42,12 +42,12 @@ If you haven't done testing in FastAPI applications, first check the ```console -$ python -m pip install requests pytest +$ pip install requests pytest ---> 100% ``` diff --git a/docs/tutorial/index.md b/docs/tutorial/index.md index 0c517bbe1..dd61e8597 100644 --- a/docs/tutorial/index.md +++ b/docs/tutorial/index.md @@ -2,8 +2,6 @@ In this tutorial you will learn how to use **SQLModel**. -But before we start playing with SQLModel, let's prepare everything else we need. A bit of type annotations, setting up the environment to install everything, and installing DB Browser for SQLite. 🤓 - ## Type hints If you need a refresher about how to use Python type hints (type annotations), check FastAPI's Python types intro. @@ -33,198 +31,3 @@ Using it in your editor is what really shows you the benefits of **SQLModel**, s Running the examples is what will really help you understand what is going on. You can learn a lot more by running some examples and playing around with them than by reading all the docs here. - -## Create a Project - -Please go ahead and create a directory for the project we will work on on this tutorial. - -What I normally do is that I create a directory named `code` inside my home/user directory. - -And inside of that I create one directory per project. - -So, for example: - -
- -```console -// Go to the home directory -$ cd -// Create a directory for all your code projects -$ mkdir code -// Enter into that code directory -$ cd code -// Create a directory for this project -$ mkdir sqlmodel-tutorial -// Enter into that directory -$ cd sqlmodel-tutorial -``` - -
- -/// tip - -Make sure you don't name it also `sqlmodel`, so that you don't end up overriding the name of the package. - -/// - -### Make sure you have Python - -Make sure you have an officially supported version of Python. - -You can check which version you have with: - -
- -```console -$ python3 --version -Python 3.11 -``` - -
- -There's a chance that you have multiple Python versions installed. - -You might want to try with the specific versions, for example with: - -* `python3.12` -* `python3.11` -* `python3.10` -* `python3.9` - -The code would look like this: - -
- -```console -// Let's check with just python3 -$ python3 --version -// This is too old! 😱 -Python 3.5.6 -// Let's see if python3.10 is available -$ python3.10 --version -// Oh, no, this one is not available 😔 -command not found: python3.10 -$ python3.9 --version -// Nice! This works 🎉 -Python 3.9.0 -// In this case, we would continue using python3.9 instead of python3 -``` - -
- -If you have different versions and `python3` is not the latest, make sure you use the latest version you have available. For example `python3.9`. - -If you don't have a valid Python version installed, go and install that first. - -### Create a Python virtual environment - -When writing Python code, you should **always** use virtual environments in one way or another. - -If you don't know what that is, you can read the official tutorial for virtual environments, it's quite simple. - -In very short, a virtual environment is a small directory that contains a copy of Python and all the libraries you need to run your code. - -And when you "activate" it, any package that you install, for example with `pip`, will be installed in that virtual environment. - -/// tip - -There are other tools to manage virtual environments, like Poetry. - -And there are alternatives that are particularly useful for deployment like Docker and other types of containers. In this case, the "virtual environment" is not just the Python standard files and the installed packages, but the whole system. - -/// - -Go ahead and create a Python virtual environment for this project. And make sure to also upgrade `pip`. - -Here are the commands you could use: - -/// tab | Linux, macOS, Linux in Windows - -
- -```console -// Remember that you might need to use python3.9 or similar 💡 -// Create the virtual environment using the module "venv" -$ python3 -m venv env -// ...here it creates the virtual environment in the directory "env" -// Activate the virtual environment -$ source ./env/bin/activate -// Verify that the virtual environment is active -# (env) $$ which python -// The important part is that it is inside the project directory, at "code/sqlmodel-tutorial/env/bin/python" -/home/leela/code/sqlmodel-tutorial/env/bin/python -// Use the module "pip" to install and upgrade the package "pip" 🤯 -# (env) $$ python -m pip install --upgrade pip ----> 100% -Successfully installed pip -``` - -
- -/// - -/// tab | Windows PowerShell - -
- -```console -// Create the virtual environment using the module "venv" -# >$ python3 -m venv env -// ...here it creates the virtual environment in the directory "env" -// Activate the virtual environment -# >$ .\env\Scripts\Activate.ps1 -// Verify that the virtual environment is active -# (env) >$ Get-Command python -// The important part is that it is inside the project directory, at "code\sqlmodel-tutorial\env\python.exe" -CommandType Name Version Source ------------ ---- ------- ------ -Application python 0.0.0.0 C:\Users\leela\code\sqlmodel-tutorial\env\python.exe -// Use the module "pip" to install and upgrade the package "pip" 🤯 -# (env) >$ python3 -m pip install --upgrade pip ----> 100% -Successfully installed pip -``` - -
- -/// - -## Install **SQLModel** - -Now, after making sure we are inside of a virtual environment in some way, we can install **SQLModel**: - -
- -```console -# (env) $$ pip install sqlmodel ----> 100% -Successfully installed sqlmodel pydantic sqlalchemy -``` - -
- -As **SQLModel** is built on top of SQLAlchemy and Pydantic, when you install `sqlmodel` they will also be automatically installed. - -## Install DB Browser for SQLite - -Remember that [SQLite is a simple database in a single file](../databases.md#a-single-file-database){.internal-link target=_blank}? - -For most of the tutorial I'll use SQLite for the examples. - -Python has integrated support for SQLite, it is a single file read and processed from Python. And it doesn't need an [External Database Server](../databases.md#a-server-database){.internal-link target=_blank}, so it will be perfect for learning. - -In fact, SQLite is perfectly capable of handling quite big applications. At some point you might want to migrate to a server-based database like PostgreSQL (which is also free). But for now we'll stick to SQLite. - -Through the tutorial I will show you SQL fragments, and Python examples. And I hope (and expect 🧐) you to actually run them, and verify that the database is working as expected and showing you the same data. - -To be able to explore the SQLite file yourself, independent of Python code (and probably at the same time), I recommend you use DB Browser for SQLite. - -It's a great and simple program to interact with SQLite databases (SQLite files) in a nice user interface. - - - -Go ahead and Install DB Browser for SQLite, it's free. - -## Next Steps - -Okay, let's get going! On the [next section](create-db-and-table-with-db-browser.md) we'll start creating a database. 🚀 diff --git a/docs/virtual-environments.md b/docs/virtual-environments.md new file mode 100644 index 000000000..541ae5272 --- /dev/null +++ b/docs/virtual-environments.md @@ -0,0 +1,844 @@ +# Virtual Environments + +When you work in Python projects you probably should use a **virtual environment** (or a similar mechanism) to isolate the packages you install for each project. + +/// info + +If you already know about virtual environments, how to create them and use them, you might want to skip this section. 🤓 + +/// + +/// tip + +A **virtual environment** is different than an **environment variable**. + +An **environment variable** is a variable in the system that can be used by programs. + +A **virtual environment** is a directory with some files in it. + +/// + +/// info + +This page will teach you how to use **virtual environments** and how they work. + +If you are ready to adopt a **tool that manages everything** for you (including installing Python), try uv. + +/// + +## Create a Project + +First, create a directory for your project. + +What I normally do is that I create a directory named `code` inside my home/user directory. + +And inside of that I create one directory per project. + +
+ +```console +// Go to the home directory +$ cd +// Create a directory for all your code projects +$ mkdir code +// Enter into that code directory +$ cd code +// Create a directory for this project +$ mkdir awesome-project +// Enter into that project directory +$ cd awesome-project +``` + +
+ +## Create a Virtual Environment + +When you start working on a Python project **for the first time**, create a virtual environment **inside your project**. + +/// tip + +You only need to do this **once per project**, not every time you work. + +/// + +//// tab | `venv` + +To create a virtual environment, you can use the `venv` module that comes with Python. + +
+ +```console +$ python -m venv .venv +``` + +
+ +/// details | What that command means + +* `python`: use the program called `python` +* `-m`: call a module as a script, we'll tell it which module next +* `venv`: use the module called `venv` that normally comes installed with Python +* `.venv`: create the virtual environment in the new directory `.venv` + +/// + +//// + +//// tab | `uv` + +If you have `uv` installed, you can use it to create a virtual environment. + +
+ +```console +$ uv venv +``` + +
+ +/// tip + +By default, `uv` will create a virtual environment in a directory called `.venv`. + +But you could customize it passing an additional argument with the directory name. + +/// + +//// + +That command creates a new virtual environment in a directory called `.venv`. + +/// details | `.venv` or other name + +You could create the virtual environment in a different directory, but there's a convention of calling it `.venv`. + +/// + +## Activate the Virtual Environment + +Activate the new virtual environment so that any Python command you run or package you install uses it. + +/// tip + +Do this **every time** you start a **new terminal session** to work on the project. + +/// + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Or if you use Bash for Windows (e.g. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +/// tip + +Every time you install a **new package** in that environment, **activate** the environment again. + +This makes sure that if you use a **terminal (CLI) program** installed by that package, you use the one from your virtual environment and not any other that could be installed globally, probably with a different version than what you need. + +/// + +## Check the Virtual Environment is Active + +Check that the virtual environment is active (the previous command worked). + +/// tip + +This is **optional**, but it's a good way to **check** that everything is working as expected and you are using the virtual environment you intended. + +/// + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +If it shows the `python` binary at `.venv/bin/python`, inside of your project (in this case `awesome-project`), then it worked. 🎉 + +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +If it shows the `python` binary at `.venv\Scripts\python`, inside of your project (in this case `awesome-project`), then it worked. 🎉 + +//// + +## Upgrade `pip` + +/// tip + +If you use `uv` you would use it to install things instead of `pip`, so you don't need to upgrade `pip`. 😎 + +/// + +If you are using `pip` to install packages (it comes by default with Python), you should **upgrade** it to the latest version. + +Many exotic errors while installing a package are solved by just upgrading `pip` first. + +/// tip + +You would normally do this **once**, right after you create the virtual environment. + +/// + +Make sure the virtual environment is active (with the command above) and then run: + +
+ +```console +$ python -m pip install --upgrade pip + +---> 100% +``` + +
+ +## Add `.gitignore` + +If you are using **Git** (you should), add a `.gitignore` file to exclude everything in your `.venv` from Git. + +/// tip + +If you used `uv` to create the virtual environment, it already did this for you, you can skip this step. 😎 + +/// + +/// tip + +Do this **once**, right after you create the virtual environment. + +/// + +
+ +```console +$ echo "*" > .venv/.gitignore +``` + +
+ +/// details | What that command means + +* `echo "*"`: will "print" the text `*` in the terminal (the next part changes that a bit) +* `>`: anything printed to the terminal by the command to the left of `>` should not be printed but instead written to the file that goes to the right of `>` +* `.gitignore`: the name of the file where the text should be written + +And `*` for Git means "everything". So, it will ignore everything in the `.venv` directory. + +That command will create a file `.gitignore` with the content: + +```gitignore +* +``` + +/// + +## Install Packages + +After activating the environment, you can install packages in it. + +/// tip + +Do this **once** when installing or upgrading the packages your project needs. + +If you need to upgrade a version or add a new package you would **do this again**. + +/// + +### Install Packages Directly + +If you're in a hurry and don't want to use a file to declare your project's package requirements, you can install them directly. + +/// tip + +It's a (very) good idea to put the packages and versions your program needs in a file (for example `requirements.txt` or `pyproject.toml`). + +/// + +//// tab | `pip` + +
+ +```console +$ pip install sqlmodel + +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +If you have `uv`: + +
+ +```console +$ uv pip install sqlmodel +---> 100% +``` + +
+ +//// + +### Install from `requirements.txt` + +If you have a `requirements.txt`, you can now use it to install its packages. + +//// tab | `pip` + +
+ +```console +$ pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +//// tab | `uv` + +If you have `uv`: + +
+ +```console +$ uv pip install -r requirements.txt +---> 100% +``` + +
+ +//// + +/// details | `requirements.txt` + +A `requirements.txt` with some packages could look like: + +```requirements.txt +sqlmodel==0.13.0 +rich==13.7.1 +``` + +/// + +## Run Your Program + +After you activated the virtual environment, you can run your program, and it will use the Python inside of your virtual environment with the packages you installed there. + +
+ +```console +$ python main.py + +Hello World +``` + +
+ +## Configure Your Editor + +You would probably use an editor, make sure you configure it to use the same virtual environment you created (it will probably autodetect it) so that you can get autocompletion and inline errors. + +For example: + +* VS Code +* PyCharm + +/// tip + +You normally have to do this only **once**, when you create the virtual environment. + +/// + +## Deactivate the Virtual Environment + +Once you are done working on your project you can **deactivate** the virtual environment. + +
+ +```console +$ deactivate +``` + +
+ +This way, when you run `python` it won't try to run it from that virtual environment with the packages installed there. + +## Ready to Work + +Now you're ready to start working on your project. + + + +/// tip + +Do you want to understand what's all that above? + +Continue reading. 👇🤓 + +/// + +## Why Virtual Environments + +To work with SQLModel you need to install Python. + +After that, you would need to **install** SQLModel and any other **packages** you want to use. + +To install packages you would normally use the `pip` command that comes with Python (or similar alternatives). + +Nevertheless, if you just use `pip` directly, the packages would be installed in your **global Python environment** (the global installation of Python). + +### The Problem + +So, what's the problem with installing packages in the global Python environment? + +At some point, you will probably end up writing many different programs that depend on **different packages**. And some of these projects you work on will depend on **different versions** of the same package. 😱 + +For example, you could create a project called `philosophers-stone`, this program depends on another package called **`harry`, using the version `1`**. So, you need to install `harry`. + +```mermaid +flowchart LR + stone(philosophers-stone) -->|requires| harry-1[harry v1] +``` + +Then, at some point later, you create another project called `prisoner-of-azkaban`, and this project also depends on `harry`, but this project needs **`harry` version `3`**. + +```mermaid +flowchart LR + azkaban(prisoner-of-azkaban) --> |requires| harry-3[harry v3] +``` + +But now the problem is, if you install the packages globally (in the global environment) instead of in a local **virtual environment**, you will have to choose which version of `harry` to install. + +If you want to run `philosophers-stone` you will need to first install `harry` version `1`, for example with: + +
+ +```console +$ pip install "harry==1" +``` + +
+ +And then you would end up with `harry` version `1` installed in your global Python environment. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -->|requires| harry-1 + end +``` + +But then if you want to run `prisoner-of-azkaban`, you will need to uninstall `harry` version `1` and install `harry` version `3` (or just installing version `3` would automatically uninstall version `1`). + +
+ +```console +$ pip install "harry==3" +``` + +
+ +And then you would end up with `harry` version `3` installed in your global Python environment. + +And if you try to run `philosophers-stone` again, there's a chance it would **not work** because it needs `harry` version `1`. + +```mermaid +flowchart LR + subgraph global[global env] + harry-1[harry v1] + style harry-1 fill:#ccc,stroke-dasharray: 5 5 + harry-3[harry v3] + end + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) -.-x|⛔️| harry-1 + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --> |requires| harry-3 + end +``` + +/// tip + +It's very common in Python packages to try the best to **avoid breaking changes** in **new versions**, but it's better to be safe, and install newer versions intentionally and when you can run the tests to check everything is working correctly. + +/// + +Now, imagine that with **many** other **packages** that all your **projects depend on**. That's very difficult to manage. And you would probably end up running some projects with some **incompatible versions** of the packages, and not knowing why something isn't working. + +Also, depending on your operating system (e.g. Linux, Windows, macOS), it could have come with Python already installed. And in that case it probably had some packages pre-installed with some specific versions **needed by your system**. If you install packages in the global Python environment, you could end up **breaking** some of the programs that came with your operating system. + +## Where are Packages Installed + +When you install Python, it creates some directories with some files in your computer. + +Some of these directories are the ones in charge of having all the packages you install. + +When you run: + +
+ +```console +// Don't run this now, it's just an example 🤓 +$ pip install sqlmodel +---> 100% +``` + +
+ +That will download a compressed file with the SQLModel code, normally from PyPI. + +It will also **download** files for other packages that SQLModel depends on. + +Then it will **extract** all those files and put them in a directory in your computer. + +By default, it will put those files downloaded and extracted in the directory that comes with your Python installation, that's the **global environment**. + +## What are Virtual Environments + +The solution to the problems of having all the packages in the global environment is to use a **virtual environment for each project** you work on. + +A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project. + +This way, each project will have it's own virtual environment (`.venv` directory) with its own packages. + +```mermaid +flowchart TB + subgraph stone-project[philosophers-stone project] + stone(philosophers-stone) --->|requires| harry-1 + subgraph venv1[.venv] + harry-1[harry v1] + end + end + subgraph azkaban-project[prisoner-of-azkaban project] + azkaban(prisoner-of-azkaban) --->|requires| harry-3 + subgraph venv2[.venv] + harry-3[harry v3] + end + end + stone-project ~~~ azkaban-project +``` + +## What Does Activating a Virtual Environment Mean + +When you activate a virtual environment, for example with: + +//// tab | Linux, macOS + +
+ +```console +$ source .venv/bin/activate +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ .venv\Scripts\Activate.ps1 +``` + +
+ +//// + +//// tab | Windows Bash + +Or if you use Bash for Windows (e.g. Git Bash): + +
+ +```console +$ source .venv/Scripts/activate +``` + +
+ +//// + +That command will create or modify some [environment variables](environment-variables.md){.internal-link target=_blank} that will be available for the next commands. + +One of those variables is the `PATH` variable. + +/// tip + +You can learn more about the `PATH` environment variable in the [Environment Variables](environment-variables.md#path-environment-variable){.internal-link target=_blank} section. + +/// + +Activating a virtual environment adds its path `.venv/bin` (on Linux and macOS) or `.venv\Scripts` (on Windows) to the `PATH` environment variable. + +Let's say that before activating the environment, the `PATH` variable looked like this: + +//// tab | Linux, macOS + +```plaintext +/usr/bin:/bin:/usr/sbin:/sbin +``` + +That means that the system would look for programs in: + +* `/usr/bin` +* `/bin` +* `/usr/sbin` +* `/sbin` + +//// + +//// tab | Windows + +```plaintext +C:\Windows\System32 +``` + +That means that the system would look for programs in: + +* `C:\Windows\System32` + +//// + +After activating the virtual environment, the `PATH` variable would look something like this: + +//// tab | Linux, macOS + +```plaintext +/home/user/code/awesome-project/.venv/bin:/usr/bin:/bin:/usr/sbin:/sbin +``` + +That means that the system will now start looking first look for programs in: + +```plaintext +/home/user/code/awesome-project/.venv/bin +``` + +before looking in the other directories. + +So, when you type `python` in the terminal, the system will find the Python program in + +```plaintext +/home/user/code/awesome-project/.venv/bin/python +``` + +and use that one. + +//// + +//// tab | Windows + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts;C:\Windows\System32 +``` + +That means that the system will now start looking first look for programs in: + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts +``` + +before looking in the other directories. + +So, when you type `python` in the terminal, the system will find the Python program in + +```plaintext +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +and use that one. + +//// + +An important detail is that it will put the virtual environment path at the **beginning** of the `PATH` variable. The system will find it **before** finding any other Python available. This way, when you run `python`, it will use the Python **from the virtual environment** instead of any other `python` (for example, a `python` from a global environment). + +Activating a virtual environment also changes a couple of other things, but this is one of the most important things it does. + +## Checking a Virtual Environment + +When you check if a virtual environment is active, for example with: + +//// tab | Linux, macOS, Windows Bash + +
+ +```console +$ which python + +/home/user/code/awesome-project/.venv/bin/python +``` + +
+ +//// + +//// tab | Windows PowerShell + +
+ +```console +$ Get-Command python + +C:\Users\user\code\awesome-project\.venv\Scripts\python +``` + +
+ +//// + +That means that the `python` program that will be used is the one **in the virtual environment**. + +you use `which` in Linux and macOS and `Get-Command` in Windows PowerShell. + +The way that command works is that it will go and check in the `PATH` environment variable, going through **each path in order**, looking for the program called `python`. Once it finds it, it will **show you the path** to that program. + +The most important part is that when you call `python`, that is the exact "`python`" that will be executed. + +So, you can confirm if you are in the correct virtual environment. + +/// tip + +It's easy to activate one virtual environment, get one Python, and then **go to another project**. + +And the second project **wouldn't work** because you are using the **incorrect Python**, from a virtual environment for another project. + +It's useful being able to check what `python` is being used. 🤓 + +/// + +## Why Deactivate a Virtual Environment + +For example, you could be working on a project `philosophers-stone`, **activate that virtual environment**, install packages and work with that environment. + +And then you want to work on **another project** `prisoner-of-azkaban`. + +You go to that project: + +
+ +```console +$ cd ~/code/prisoner-of-azkaban +``` + +
+ +If you don't deactivate the virtual environment for `philosophers-stone`, when you run `python` in the terminal, it will try to use the Python from `philosophers-stone`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +$ python main.py + +// Error importing sirius, it's not installed 😱 +Traceback (most recent call last): + File "main.py", line 1, in + import sirius +``` + +
+ +But if you deactivate the virtual environment and activate the new one for `prisoner-of-askaban` then when you run `python` it will use the Python from the virtual environment in `prisoner-of-azkaban`. + +
+ +```console +$ cd ~/code/prisoner-of-azkaban + +// You don't need to be in the old directory to deactivate, you can do it wherever you are, even after going to the other project 😎 +$ deactivate + +// Activate the virtual environment in prisoner-of-azkaban/.venv 🚀 +$ source .venv/bin/activate + +// Now when you run python, it will find the package sirius installed in this virtual environment ✨ +$ python main.py + +I solemnly swear 🐺 +``` + +
+ +## Alternatives + +This is a simple guide to get you started and teach you how everything works **underneath**. + +There are many **alternatives** to managing virtual environments, package dependencies (requirements), projects. + +Once you are ready and want to use a tool to **manage the entire project**, packages dependencies, virtual environments, etc. I would suggest you try uv. + +`uv` can do a lot of things, it can: + +* **Install Python** for you, including different versions +* Manage the **virtual environment** for your projects +* Install **packages** +* Manage package **dependencies and versions** for your project +* Make sure you have an **exact** set of packages and versions to install, including their dependencies, so that you can be sure that you can run your project in production exactly the same as in your computer while developing, this is called **locking** +* And many other things + +## Conclusion + +If you read and understood all this, now **you know much more** about virtual environments than many developers out there. 🤓 + +Knowing these details will most probably be useful in a future time when you are debugging something that seems complex, but you will know **how it all works underneath**. 😎 diff --git a/mkdocs.yml b/mkdocs.yml index d4e3963ec..f0f4aa17a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -71,6 +71,9 @@ nav: - learn/index.md - databases.md - db-to-code.md + - environment-variables.md + - virtual-environments.md + - install.md - Tutorial - User Guide: - tutorial/index.md - tutorial/create-db-and-table-with-db-browser.md From 6c1b011636977890ef04535d9fb80d8afc51501f Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Aug 2024 19:03:56 +0000 Subject: [PATCH 12/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 110a3677e..187b0b50b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Docs +* 📝 Add docs for virtual environments and environment variables, update contributing. PR [#1082](https://github.com/fastapi/sqlmodel/pull/1082) by [@tiangolo](https://github.com/tiangolo). * 📝 Add docs about repo management and team. PR [#1059](https://github.com/tiangolo/sqlmodel/pull/1059) by [@tiangolo](https://github.com/tiangolo). * ✏️ Fix typo in `cascade_delete` docs. PR [#1030](https://github.com/tiangolo/sqlmodel/pull/1030) by [@tiangolo](https://github.com/tiangolo). From 2978d1083481206b8d8045664cf7261fead98765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 14:35:37 -0500 Subject: [PATCH 13/34] =?UTF-8?q?=F0=9F=91=B7=20Update=20Python=20version?= =?UTF-8?q?=20for=20coverage=20(#1083)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 86e91cb6c..fa691cb27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -86,7 +86,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: '3.8' + python-version: '3.12' - name: Get coverage files uses: actions/download-artifact@v4 From 2d986c1dd844cf52146fe69ff1f2077ec213dbd3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 23 Aug 2024 19:36:13 +0000 Subject: [PATCH 14/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 187b0b50b..62e7230b7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ### Internal +* 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo). * 👷 Do not sync labels as it overrides manually added labels. PR [#1073](https://github.com/fastapi/sqlmodel/pull/1073) by [@tiangolo](https://github.com/tiangolo). From e946b0d923a484bec4b86cbf25cad2e0537fadcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 23:10:51 -0500 Subject: [PATCH 15/34] =?UTF-8?q?=F0=9F=94=A7=20Update=20lint=20script=20(?= =?UTF-8?q?#1084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lint.sh b/scripts/lint.sh index 4b279b5fc..7fab52df5 100755 --- a/scripts/lint.sh +++ b/scripts/lint.sh @@ -5,4 +5,4 @@ set -x mypy sqlmodel ruff check sqlmodel tests docs_src scripts -ruff format sqlmodel tests docs_src --check +ruff format sqlmodel tests docs_src scripts --check From 28f57431d6af5305102822d7432f9aefbeed74cb Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 04:11:30 +0000 Subject: [PATCH 16/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 62e7230b7..a6ebdd031 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,6 +10,7 @@ ### Internal +* 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo). * 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo). * 🔧 Add URLs to `pyproject.toml`, show up in PyPI. PR [#1074](https://github.com/fastapi/sqlmodel/pull/1074) by [@tiangolo](https://github.com/tiangolo). From 160eb908a340e76989fadab08708e72a7213bc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Fri, 23 Aug 2024 23:19:42 -0500 Subject: [PATCH 17/34] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Fix=20a=20typo=20in?= =?UTF-8?q?=20`docs/virtual-environments.md`=20(#1085)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/virtual-environments.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/virtual-environments.md b/docs/virtual-environments.md index 541ae5272..ca48de91d 100644 --- a/docs/virtual-environments.md +++ b/docs/virtual-environments.md @@ -558,7 +558,7 @@ The solution to the problems of having all the packages in the global environmen A virtual environment is a **directory**, very similar to the global one, where you can install the packages for a project. -This way, each project will have it's own virtual environment (`.venv` directory) with its own packages. +This way, each project will have its own virtual environment (`.venv` directory) with its own packages. ```mermaid flowchart TB From 5afdaeb68f8329aeebcd264bc821039459f2229f Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 04:20:02 +0000 Subject: [PATCH 18/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index a6ebdd031..1e3f312f2 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Docs +* ✏️ Fix a typo in `docs/virtual-environments.md`. PR [#1085](https://github.com/fastapi/sqlmodel/pull/1085) by [@tiangolo](https://github.com/tiangolo). * 📝 Add docs for virtual environments and environment variables, update contributing. PR [#1082](https://github.com/fastapi/sqlmodel/pull/1082) by [@tiangolo](https://github.com/tiangolo). * 📝 Add docs about repo management and team. PR [#1059](https://github.com/tiangolo/sqlmodel/pull/1059) by [@tiangolo](https://github.com/tiangolo). * ✏️ Fix typo in `cascade_delete` docs. PR [#1030](https://github.com/tiangolo/sqlmodel/pull/1030) by [@tiangolo](https://github.com/tiangolo). From 57824a27231c02ccc04c096225a4b383a868fa5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:42:31 -0500 Subject: [PATCH 19/34] =?UTF-8?q?=E2=AC=86=20Bump=20ruff=20from=200.4.7=20?= =?UTF-8?q?to=200.6.2=20(#1081)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.7 to 0.6.2. - [Release notes](https://github.com/astral-sh/ruff/releases) - [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ruff/compare/v0.4.7...0.6.2) --- updated-dependencies: - dependency-name: ruff dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements-tests.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-tests.txt b/requirements-tests.txt index 089e1a832..847ce90c4 100644 --- a/requirements-tests.txt +++ b/requirements-tests.txt @@ -3,7 +3,7 @@ pytest >=7.0.1,<8.0.0 coverage[toml] >=6.2,<8.0 mypy ==1.4.1 -ruff ==0.4.7 +ruff ==0.6.2 # For FastAPI tests fastapi >=0.103.2 httpx ==0.24.1 From 6e4ec1f0e91176efc864e85e0b29b7f0229a1c7b Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 23:42:56 +0000 Subject: [PATCH 20/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 1e3f312f2..a37733694 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ ### Internal +* ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo). * 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update coverage config files. PR [#1077](https://github.com/fastapi/sqlmodel/pull/1077) by [@tiangolo](https://github.com/tiangolo). From 20859a4660fc084c26063bf3021917e1aefb377e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:43:08 -0500 Subject: [PATCH 21/34] =?UTF-8?q?=E2=AC=86=20[pre-commit.ci]=20pre-commit?= =?UTF-8?q?=20autoupdate=20(#1028)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.2 → v0.6.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.2...v0.6.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d8828f442..7532f21b5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.5.2 + rev: v0.6.1 hooks: - id: ruff args: From 711693e55e54eee6f577521ea861859029613584 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 24 Aug 2024 23:44:13 +0000 Subject: [PATCH 22/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index a37733694..bb17557cb 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ ### Internal +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo). * 👷 Update Python version for coverage. PR [#1083](https://github.com/fastapi/sqlmodel/pull/1083) by [@tiangolo](https://github.com/tiangolo). From ed6cf3576587a70d4515b8b2ad5fa05bd984d93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sun, 25 Aug 2024 21:17:30 -0500 Subject: [PATCH 23/34] =?UTF-8?q?=F0=9F=91=B7=20Update=20`latest-changes`?= =?UTF-8?q?=20GitHub=20Action=20(#1087)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/latest-changes.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/latest-changes.yml b/.github/workflows/latest-changes.yml index f77dffc07..77a3b3eec 100644 --- a/.github/workflows/latest-changes.yml +++ b/.github/workflows/latest-changes.yml @@ -30,8 +30,7 @@ jobs: if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled == 'true' }} with: limit-access-to-actor: true - - uses: docker://tiangolo/latest-changes:0.2.0 - # - uses: tiangolo/latest-changes@main + - uses: tiangolo/latest-changes@0.3.1 with: token: ${{ secrets.GITHUB_TOKEN }} latest_changes_file: docs/release-notes.md From 9acd934c7feb9596fc1fabf356907a2a42dc8588 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 26 Aug 2024 02:22:45 +0000 Subject: [PATCH 24/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index bb17557cb..f6f09ad12 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ ### Internal +* 👷 Update `latest-changes` GitHub Action. PR [#1087](https://github.com/fastapi/sqlmodel/pull/1087) by [@tiangolo](https://github.com/tiangolo). * ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot). * 🔧 Update lint script. PR [#1084](https://github.com/fastapi/sqlmodel/pull/1084) by [@tiangolo](https://github.com/tiangolo). From e4f3ec7a8012759fdc271ea27a20db48d206a531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 31 Aug 2024 11:33:20 +0200 Subject: [PATCH 25/34] =?UTF-8?q?=E2=9C=85=20Refactor=20test=5Fenums=20to?= =?UTF-8?q?=20make=20them=20independent=20of=20previous=20imports=20(#1095?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_enums.py | 49 ++++++++++++-------------------------- tests/test_enums_models.py | 28 ++++++++++++++++++++++ 2 files changed, 43 insertions(+), 34 deletions(-) create mode 100644 tests/test_enums_models.py diff --git a/tests/test_enums.py b/tests/test_enums.py index f0543e90f..2808f3f9a 100644 --- a/tests/test_enums.py +++ b/tests/test_enums.py @@ -1,10 +1,11 @@ -import enum -import uuid +import importlib +import pytest from sqlalchemy import create_mock_engine from sqlalchemy.sql.type_api import TypeEngine -from sqlmodel import Field, SQLModel +from sqlmodel import SQLModel +from . import test_enums_models from .conftest import needs_pydanticv1, needs_pydanticv2 """ @@ -16,30 +17,6 @@ """ -class MyEnum1(str, enum.Enum): - A = "A" - B = "B" - - -class MyEnum2(str, enum.Enum): - C = "C" - D = "D" - - -class BaseModel(SQLModel): - id: uuid.UUID = Field(primary_key=True) - enum_field: MyEnum2 - - -class FlatModel(SQLModel, table=True): - id: uuid.UUID = Field(primary_key=True) - enum_field: MyEnum1 - - -class InheritModel(BaseModel, table=True): - pass - - def pg_dump(sql: TypeEngine, *args, **kwargs): dialect = sql.compile(dialect=postgres_engine.dialect) sql_str = str(dialect).rstrip() @@ -58,7 +35,9 @@ def sqlite_dump(sql: TypeEngine, *args, **kwargs): sqlite_engine = create_mock_engine("sqlite://", sqlite_dump) -def test_postgres_ddl_sql(capsys): +def test_postgres_ddl_sql(clear_sqlmodel, capsys: pytest.CaptureFixture[str]): + assert test_enums_models, "Ensure the models are imported and registered" + importlib.reload(test_enums_models) SQLModel.metadata.create_all(bind=postgres_engine, checkfirst=False) captured = capsys.readouterr() @@ -66,17 +45,19 @@ def test_postgres_ddl_sql(capsys): assert "CREATE TYPE myenum2 AS ENUM ('C', 'D');" in captured.out -def test_sqlite_ddl_sql(capsys): +def test_sqlite_ddl_sql(clear_sqlmodel, capsys: pytest.CaptureFixture[str]): + assert test_enums_models, "Ensure the models are imported and registered" + importlib.reload(test_enums_models) SQLModel.metadata.create_all(bind=sqlite_engine, checkfirst=False) captured = capsys.readouterr() - assert "enum_field VARCHAR(1) NOT NULL" in captured.out + assert "enum_field VARCHAR(1) NOT NULL" in captured.out, captured assert "CREATE TYPE" not in captured.out @needs_pydanticv1 def test_json_schema_flat_model_pydantic_v1(): - assert FlatModel.schema() == { + assert test_enums_models.FlatModel.schema() == { "title": "FlatModel", "type": "object", "properties": { @@ -97,7 +78,7 @@ def test_json_schema_flat_model_pydantic_v1(): @needs_pydanticv1 def test_json_schema_inherit_model_pydantic_v1(): - assert InheritModel.schema() == { + assert test_enums_models.InheritModel.schema() == { "title": "InheritModel", "type": "object", "properties": { @@ -118,7 +99,7 @@ def test_json_schema_inherit_model_pydantic_v1(): @needs_pydanticv2 def test_json_schema_flat_model_pydantic_v2(): - assert FlatModel.model_json_schema() == { + assert test_enums_models.FlatModel.model_json_schema() == { "title": "FlatModel", "type": "object", "properties": { @@ -134,7 +115,7 @@ def test_json_schema_flat_model_pydantic_v2(): @needs_pydanticv2 def test_json_schema_inherit_model_pydantic_v2(): - assert InheritModel.model_json_schema() == { + assert test_enums_models.InheritModel.model_json_schema() == { "title": "InheritModel", "type": "object", "properties": { diff --git a/tests/test_enums_models.py b/tests/test_enums_models.py new file mode 100644 index 000000000..b46ccb7d2 --- /dev/null +++ b/tests/test_enums_models.py @@ -0,0 +1,28 @@ +import enum +import uuid + +from sqlmodel import Field, SQLModel + + +class MyEnum1(str, enum.Enum): + A = "A" + B = "B" + + +class MyEnum2(str, enum.Enum): + C = "C" + D = "D" + + +class BaseModel(SQLModel): + id: uuid.UUID = Field(primary_key=True) + enum_field: MyEnum2 + + +class FlatModel(SQLModel, table=True): + id: uuid.UUID = Field(primary_key=True) + enum_field: MyEnum1 + + +class InheritModel(BaseModel, table=True): + pass From 4eaf8b9efb561396351feec444eaa2b6a41ea0a5 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 31 Aug 2024 09:33:38 +0000 Subject: [PATCH 26/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index f6f09ad12..ebf22b860 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -11,6 +11,7 @@ ### Internal +* ✅ Refactor test_enums to make them independent of previous imports. PR [#1095](https://github.com/fastapi/sqlmodel/pull/1095) by [@tiangolo](https://github.com/tiangolo). * 👷 Update `latest-changes` GitHub Action. PR [#1087](https://github.com/fastapi/sqlmodel/pull/1087) by [@tiangolo](https://github.com/tiangolo). * ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1028](https://github.com/fastapi/sqlmodel/pull/1028) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). * ⬆ Bump ruff from 0.4.7 to 0.6.2. PR [#1081](https://github.com/fastapi/sqlmodel/pull/1081) by [@dependabot[bot]](https://github.com/apps/dependabot). From a14ab0bd3ca8cd5e6701537654ccf4e60cbc4bea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 31 Aug 2024 11:38:19 +0200 Subject: [PATCH 27/34] =?UTF-8?q?=F0=9F=90=9B=20Fix=20support=20for=20type?= =?UTF-8?q?s=20with=20`Optional[Annoated[x,=20f()]]`,=20e.g.=20`id:=20Opti?= =?UTF-8?q?onal[pydantic.UUID4]`=20(#1093)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlmodel/_compat.py | 24 +++++++++++++++--------- sqlmodel/main.py | 4 ++-- tests/test_annotated_uuid.py | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) create mode 100644 tests/test_annotated_uuid.py diff --git a/sqlmodel/_compat.py b/sqlmodel/_compat.py index 4018d1bb3..4e80cdc37 100644 --- a/sqlmodel/_compat.py +++ b/sqlmodel/_compat.py @@ -21,7 +21,7 @@ from pydantic import VERSION as P_VERSION from pydantic import BaseModel from pydantic.fields import FieldInfo -from typing_extensions import get_args, get_origin +from typing_extensions import Annotated, get_args, get_origin # Reassign variable to make it reexported for mypy PYDANTIC_VERSION = P_VERSION @@ -177,16 +177,17 @@ def is_field_noneable(field: "FieldInfo") -> bool: return False return False - def get_type_from_field(field: Any) -> Any: - type_: Any = field.annotation + def get_sa_type_from_type_annotation(annotation: Any) -> Any: # Resolve Optional fields - if type_ is None: + if annotation is None: raise ValueError("Missing field type") - origin = get_origin(type_) + origin = get_origin(annotation) if origin is None: - return type_ + return annotation + elif origin is Annotated: + return get_sa_type_from_type_annotation(get_args(annotation)[0]) if _is_union_type(origin): - bases = get_args(type_) + bases = get_args(annotation) if len(bases) > 2: raise ValueError( "Cannot have a (non-optional) union as a SQLAlchemy field" @@ -197,9 +198,14 @@ def get_type_from_field(field: Any) -> Any: "Cannot have a (non-optional) union as a SQLAlchemy field" ) # Optional unions are allowed - return bases[0] if bases[0] is not NoneType else bases[1] + use_type = bases[0] if bases[0] is not NoneType else bases[1] + return get_sa_type_from_type_annotation(use_type) return origin + def get_sa_type_from_field(field: Any) -> Any: + type_: Any = field.annotation + return get_sa_type_from_type_annotation(type_) + def get_field_metadata(field: Any) -> Any: for meta in field.metadata: if isinstance(meta, (PydanticMetadata, MaxLen)): @@ -444,7 +450,7 @@ def is_field_noneable(field: "FieldInfo") -> bool: ) return field.allow_none # type: ignore[no-any-return, attr-defined] - def get_type_from_field(field: Any) -> Any: + def get_sa_type_from_field(field: Any) -> Any: if isinstance(field.type_, type) and field.shape == SHAPE_SINGLETON: return field.type_ raise ValueError(f"The field {field.name} has no matching SQLAlchemy type") diff --git a/sqlmodel/main.py b/sqlmodel/main.py index d8fced51f..1597e4e04 100644 --- a/sqlmodel/main.py +++ b/sqlmodel/main.py @@ -71,7 +71,7 @@ get_field_metadata, get_model_fields, get_relationship_to, - get_type_from_field, + get_sa_type_from_field, init_pydantic_private_attrs, is_field_noneable, is_table_model_class, @@ -649,7 +649,7 @@ def get_sqlalchemy_type(field: Any) -> Any: if sa_type is not Undefined: return sa_type - type_ = get_type_from_field(field) + type_ = get_sa_type_from_field(field) metadata = get_field_metadata(field) # Check enums first as an enum can also be a str, needed by Pydantic/FastAPI diff --git a/tests/test_annotated_uuid.py b/tests/test_annotated_uuid.py new file mode 100644 index 000000000..b0e25ab09 --- /dev/null +++ b/tests/test_annotated_uuid.py @@ -0,0 +1,26 @@ +import uuid +from typing import Optional + +from sqlmodel import Field, Session, SQLModel, create_engine, select + +from tests.conftest import needs_pydanticv2 + + +@needs_pydanticv2 +def test_annotated_optional_types(clear_sqlmodel) -> None: + from pydantic import UUID4 + + class Hero(SQLModel, table=True): + # Pydantic UUID4 is: Annotated[UUID, UuidVersion(4)] + id: Optional[UUID4] = Field(default_factory=uuid.uuid4, primary_key=True) + + engine = create_engine("sqlite:///:memory:") + SQLModel.metadata.create_all(engine) + with Session(engine) as db: + hero = Hero() + db.add(hero) + db.commit() + statement = select(Hero) + result = db.exec(statement).all() + assert len(result) == 1 + assert isinstance(hero.id, uuid.UUID) From feb5ff174764eb32c9ae9d76acb5dfcdbf037ed8 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sat, 31 Aug 2024 09:38:41 +0000 Subject: [PATCH 28/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index ebf22b860..a58974acd 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Fixes + +* 🐛 Fix support for types with `Optional[Annoated[x, f()]]`, e.g. `id: Optional[pydantic.UUID4]`. PR [#1093](https://github.com/fastapi/sqlmodel/pull/1093) by [@tiangolo](https://github.com/tiangolo). + ### Docs * ✏️ Fix a typo in `docs/virtual-environments.md`. PR [#1085](https://github.com/fastapi/sqlmodel/pull/1085) by [@tiangolo](https://github.com/tiangolo). From 5930fb055101f0b88b218bb4e559321491371ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 31 Aug 2024 11:39:52 +0200 Subject: [PATCH 29/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index a58974acd..f53583484 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -36,7 +36,7 @@ * 💄 Add dark-mode logo. PR [#1061](https://github.com/tiangolo/sqlmodel/pull/1061) by [@tiangolo](https://github.com/tiangolo). * 🔨 Update docs.py script to enable dirty reload conditionally. PR [#1060](https://github.com/tiangolo/sqlmodel/pull/1060) by [@tiangolo](https://github.com/tiangolo). * 🔧 Update MkDocs previews. PR [#1058](https://github.com/tiangolo/sqlmodel/pull/1058) by [@tiangolo](https://github.com/tiangolo). -* 💄 Update Termynal line-height. PR [#1057](https://github.com/tiangolo/sqlmodel/pull/1057) by [@tiangolo](https://github.com/tiangolo). +* 💄 Update Termynal line-height. PR [#1057](https://github.com/tiangolo/sqlmodel/pull/1057) by [@tiangolo](https://github.com/tiangolo). * 👷 Upgrade build docs configs. PR [#1047](https://github.com/tiangolo/sqlmodel/pull/1047) by [@tiangolo](https://github.com/tiangolo). * 👷 Add alls-green for test-redistribute. PR [#1055](https://github.com/tiangolo/sqlmodel/pull/1055) by [@tiangolo](https://github.com/tiangolo). * 👷 Update docs-previews to handle no docs changes. PR [#1056](https://github.com/tiangolo/sqlmodel/pull/1056) by [@tiangolo](https://github.com/tiangolo). From 016b2baaadc623c97b80cba744198bfecd5b024d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Sat, 31 Aug 2024 11:40:12 +0200 Subject: [PATCH 30/34] =?UTF-8?q?=F0=9F=94=96=20Release=20version=200.0.22?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 2 ++ sqlmodel/__init__.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/release-notes.md b/docs/release-notes.md index f53583484..631eb3a58 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,8 @@ ## Latest Changes +## 0.0.22 + ### Fixes * 🐛 Fix support for types with `Optional[Annoated[x, f()]]`, e.g. `id: Optional[pydantic.UUID4]`. PR [#1093](https://github.com/fastapi/sqlmodel/pull/1093) by [@tiangolo](https://github.com/tiangolo). diff --git a/sqlmodel/__init__.py b/sqlmodel/__init__.py index b02ddc9aa..f62988f4a 100644 --- a/sqlmodel/__init__.py +++ b/sqlmodel/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.0.21" +__version__ = "0.0.22" # Re-export from SQLAlchemy from sqlalchemy.engine import create_engine as create_engine From 76b9b5d1093ee87f24823d519c6c5a6824dc99fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 08:49:40 +0200 Subject: [PATCH 31/34] =?UTF-8?q?=E2=AC=86=20[pre-commit.ci]=20pre-commit?= =?UTF-8?q?=20autoupdate=20(#1088)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.1 → v0.6.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.1...v0.6.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7532f21b5..317514062 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.1 + rev: v0.6.2 hooks: - id: ruff args: From e2d3153dcca6237384a9a6622d605bd337185279 Mon Sep 17 00:00:00 2001 From: github-actions Date: Sun, 1 Sep 2024 06:50:00 +0000 Subject: [PATCH 32/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 631eb3a58..945f62a43 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -2,6 +2,10 @@ ## Latest Changes +### Internal + +* ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1088](https://github.com/fastapi/sqlmodel/pull/1088) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). + ## 0.0.22 ### Fixes From 5c6688e945bbe6947b7046d5c32afde45a644b8d Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Mon, 2 Sep 2024 22:13:38 +0200 Subject: [PATCH 33/34] =?UTF-8?q?=F0=9F=92=9A=20Set=20`include-hidden-file?= =?UTF-8?q?s`=20to=20`True`=20when=20using=20the=20`upload-artifact`=20GH?= =?UTF-8?q?=20action=20(#1098)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit set include-hidden-files to true for actions/upload-artifact@v4 --- .github/workflows/build-docs.yml | 1 + .github/workflows/test.yml | 3 +++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml index a23930528..00adbfbc5 100644 --- a/.github/workflows/build-docs.yml +++ b/.github/workflows/build-docs.yml @@ -77,6 +77,7 @@ jobs: with: name: docs-site path: ./site/** + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why docs-all-green: # This job does nothing and is only used for the branch protection diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa691cb27..fe2b2c025 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,6 +76,8 @@ jobs: with: name: coverage-${{ matrix.python-version }}-${{ matrix.pydantic-version }} path: coverage + include-hidden-files: true + coverage-combine: needs: - test @@ -107,6 +109,7 @@ jobs: with: name: coverage-html path: htmlcov + include-hidden-files: true # https://github.com/marketplace/actions/alls-green#why alls-green: # This job does nothing and is only used for the branch protection From 6f6f50cfb1d49b6084c8e570c7b960a001802f1d Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 2 Sep 2024 20:13:56 +0000 Subject: [PATCH 34/34] =?UTF-8?q?=F0=9F=93=9D=20Update=20release=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/release-notes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes.md b/docs/release-notes.md index 945f62a43..9a2f6cea7 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -4,6 +4,7 @@ ### Internal +* 💚 Set `include-hidden-files` to `True` when using the `upload-artifact` GH action. PR [#1098](https://github.com/fastapi/sqlmodel/pull/1098) by [@svlandeg](https://github.com/svlandeg). * ⬆ [pre-commit.ci] pre-commit autoupdate. PR [#1088](https://github.com/fastapi/sqlmodel/pull/1088) by [@pre-commit-ci[bot]](https://github.com/apps/pre-commit-ci). ## 0.0.22