Skip to content

Commit 61b385a

Browse files
author
David Montague
committed
Add docs for GUID type
1 parent 397eae7 commit 61b385a

File tree

10 files changed

+133
-15
lines changed

10 files changed

+133
-15
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ docs-build:
9797
.PHONY: docs-format ## Format the python code that is part of the docs
9898
docs-format:
9999
isort -rc docs/src
100-
autoflake -r --remove-all-unused-imports --ignore-init-module-imports docs/src
100+
autoflake -r --remove-all-unused-imports --ignore-init-module-imports docs/src -i
101101
black -l 82 docs/src
102102

103103

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ This package includes a number of utilities to help reduce boilerplate and reuse
2525
* **Response-Model Inferring Router**: Let FastAPI infer the `response_model` to use based on your return type annotation.
2626
* **Repeated Tasks**: Easily trigger periodic tasks on server startup
2727
* **Timing Middleware**: Log basic timing information for every request
28-
* **SQLAlchemy Sessions**: The `FastAPISessionMaker` class provides an easily-customized SQLAlchemy Session dependency
29-
* **GUID Type**: The provided GUID type makes it easy to use UUIDs as the primary keys for your database tables
28+
* **SQLAlchemy Sessions**: The `FastAPISessionMaker` class provides an easily-customized SQLAlchemy Session dependency
29+
* **OpenAPI Spec Simplification**: Simplify your OpenAPI Operation IDs for cleaner output from OpenAPI Generator
3030

3131
---
3232

@@ -36,7 +36,7 @@ It also adds a variety of more basic utilities that are useful across a wide var
3636
* **APISettings**: A subclass of `pydantic.BaseSettings` that makes it easy to configure FastAPI through environment variables
3737
* **String-Valued Enums**: The `StrEnum` and `CamelStrEnum` classes make string-valued enums easier to maintain
3838
* **CamelCase Conversions**: Convenience functions for converting strings from `snake_case` to `camelCase` or `PascalCase` and back
39-
* **OpenAPI Spec Simplification**: Simplify your OpenAPI Operation IDs for cleaner output from OpenAPI Generator
39+
* **GUID Type**: The provided GUID type makes it easy to use UUIDs as the primary keys for your database tables
4040

4141
See the [docs](https://fastapi-utils.davidmontague.xyz/) for more details and examples.
4242

docs/index.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ This package includes a number of utilities to help reduce boilerplate and reuse
2525
* **Response-Model Inferring Router**: Let FastAPI infer the `response_model` to use based on your return type annotation.
2626
* **Repeated Tasks**: Easily trigger periodic tasks on server startup
2727
* **Timing Middleware**: Log basic timing information for every request
28-
* **SQLAlchemy Sessions**: The `FastAPISessionMaker` class provides an easily-customized SQLAlchemy Session dependency
29-
* **GUID Type**: The provided GUID type makes it easy to use UUIDs as the primary keys for your database tables
28+
* **SQLAlchemy Sessions**: The `FastAPISessionMaker` class provides an easily-customized SQLAlchemy Session dependency
29+
* **OpenAPI Spec Simplification**: Simplify your OpenAPI Operation IDs for cleaner output from OpenAPI Generator
3030

3131
---
3232

@@ -36,7 +36,7 @@ It also adds a variety of more basic utilities that are useful across a wide var
3636
* **APISettings**: A subclass of `pydantic.BaseSettings` that makes it easy to configure FastAPI through environment variables
3737
* **String-Valued Enums**: The `StrEnum` and `CamelStrEnum` classes make string-valued enums easier to maintain
3838
* **CamelCase Conversions**: Convenience functions for converting strings from `snake_case` to `camelCase` or `PascalCase` and back
39-
* **OpenAPI Spec Simplification**: Simplify your OpenAPI Operation IDs for cleaner output from OpenAPI Generator
39+
* **GUID Type**: The provided GUID type makes it easy to use UUIDs as the primary keys for your database tables
4040

4141
See the [docs](https://fastapi-utils.davidmontague.xyz/) for more details and examples.
4242

docs/src/class_based_views2.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@
1010
from fastapi_utils.api_model import APIMessage, APIModel
1111
from fastapi_utils.cbv import cbv
1212
from fastapi_utils.guid_type import GUID
13-
14-
# Begin Setup
1513
from fastapi_utils.inferring_router import InferringRouter
1614

15+
# Begin Setup
1716
UserID = NewType("UserID", UUID)
1817
ItemID = NewType("ItemID", UUID)
1918

docs/src/guid1.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import sqlalchemy as sa
2+
from sqlalchemy.ext.declarative import declarative_base
3+
4+
from fastapi_utils.guid_type import GUID
5+
6+
Base = declarative_base()
7+
8+
9+
class User(Base):
10+
__tablename__ = "user"
11+
id = sa.Column(GUID, primary_key=True)
12+
name = sa.Column(sa.String, nullable=False)
13+
related_id = sa.Column(GUID) # a nullable, related field

docs/src/guid2.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import sqlalchemy as sa
2+
3+
from fastapi_utils.guid_type import setup_guids_postgresql
4+
5+
database_uri = "postgresql://user:password@db:5432/app"
6+
engine = sa.create_engine(database_uri)
7+
setup_guids_postgresql(engine)
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
The two most common types used for primary keys in database tables are integers and UUIDs
2+
(sometimes referred to as GUIDs).
3+
4+
There are a number of tradeoffs to make when deciding whether to use integers vs. UUIDs,
5+
including:
6+
7+
* UUIDs don't reveal anything about the number of records in a table
8+
* UUIDs are practically impossible for an adversary to guess (though you shouldn't rely solely on that for security!)
9+
* UUIDs are harder to communicate/remember
10+
* UUIDs may result in worse performance for certain access patterns due to the random ordering
11+
12+
You'll have to decide based on your application which is right for you, but if you want to
13+
use UUIDs/GUIDs for your primary keys, there are some difficulties to navigate.
14+
15+
## Challenges using UUID-valued primary keys with sqlalchemy
16+
17+
Python has support for UUIDs in the standard library, and most relational databases
18+
have good support for them as well.
19+
20+
However, if you want a database-agnostic or database-driver-agnostic type, you may run into
21+
challenges.
22+
23+
In particular, the postgres-compatible UUID type provided by sqlalchemy (`sqlalchemy.dialects.postgresql.UUID`)
24+
will not work with other databases, and it also doesn't come with a way to set a server-default, meaning that
25+
you'll always need to take responsibility for generating an ID in your application code.
26+
27+
Even worse, if you try to use the postgres-compatible UUID type simultaneously with both `sqlalchemy` and the
28+
`encode/databases` package, you may run into issues where queries using one require you to set `UUID(as_uuid=True)`,
29+
when declaring the column, and the other requires you to declare the table using `UUID(as_uuid=False)`.
30+
31+
Fortunately, sqlalchemy provides a
32+
[backend-agnostic implementation of GUID type](https://docs.sqlalchemy.org/en/13/core/custom_types.html#backend-agnostic-guid-type)
33+
that uses the postgres-specific UUID type when possible, and more carefully parses the result to ensure
34+
`uuid.UUID` isn't called on something that is already a `uuid.UUID` (which raises an error).
35+
36+
For convenience, this package includes this `GUID` type, along with conveniences for setting up server defaults
37+
for primary keys of this type.
38+
39+
## Using GUID
40+
41+
You can create a sqlalchemy table with a GUID as a primary key using the declarative API like this:
42+
43+
```python hl_lines=""
44+
{!./src/guid1.py!}
45+
```
46+
47+
## Server Default
48+
If you want to add a server default, it will no longer be backend-agnostic, but
49+
you can use `fastapi_utils.guid_type.GUID_SERVER_DEFAULT_POSTGRESQL`:
50+
51+
```python
52+
import sqlalchemy as sa
53+
from sqlalchemy.ext.declarative import declarative_base
54+
55+
from fastapi_utils.guid_type import GUID, GUID_SERVER_DEFAULT_POSTGRESQL
56+
57+
Base = declarative_base()
58+
59+
60+
class User(Base):
61+
__tablename__ = "user"
62+
id = sa.Column(
63+
GUID,
64+
primary_key=True,
65+
server_default=GUID_SERVER_DEFAULT_POSTGRESQL
66+
)
67+
name = sa.Column(sa.String, nullable=False)
68+
related_id = sa.Column(GUID)
69+
```
70+
(Behind the scenes, this is essentially just setting the server-side default to `"gen_random_uuid()"`.)
71+
72+
Note this will only work if you have installed the `pgcrypto` extension
73+
in your postgres instance. If the user you connect with has the right privileges, this can be done
74+
by calling the `fastapi_utils.guid_type.setup_guids_postgresql` function:
75+
76+
```python
77+
{!./src/guid2.py!}
78+
```
79+
80+
## Non-Server Default
81+
82+
If you are comfortable having no server default for your primary key column, you can still
83+
make use of an application-side default (so that `sqlalchemy` will generate a default value when you
84+
create new records):
85+
86+
```python
87+
import sqlalchemy as sa
88+
from sqlalchemy.ext.declarative import declarative_base
89+
90+
from fastapi_utils.guid_type import GUID, GUID_DEFAULT_SQLITE
91+
92+
Base = declarative_base()
93+
94+
95+
class User(Base):
96+
__tablename__ = "user"
97+
id = sa.Column(GUID, primary_key=True, default=GUID_DEFAULT_SQLITE)
98+
name = sa.Column(sa.String, nullable=False)
99+
related_id = sa.Column(GUID)
100+
```
101+
102+
`GUID_DEFAULT_SQLITE` is just an alias for the standard library `uuid.uuid4`,
103+
which could be used in its place.

docs/user-guide/class-based-views.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ To use the `@cbv` decorator, you need to:
3535

3636
Let's follow these steps to simplify the example above, while preserving all of the original logic:
3737

38-
```python hl_lines="59 62 64 65 66 70 71 72"
38+
```python hl_lines="11 58 61 63 64 65 69 70 71"
3939
{!./src/class_based_views2.py!}
4040
```
4141

docs/user-guide/guid-type.md

Lines changed: 0 additions & 1 deletion
This file was deleted.

mkdocs.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,13 @@ nav:
2525
- Repeated Tasks: 'user-guide/repeated-tasks.md'
2626
- Timing Middleware: 'user-guide/timing-middleware.md'
2727
- SQLAlchemy Sessions: 'user-guide/sessions.md'
28-
- GUID Type: 'user-guide/guid-type.md'
2928
- OpenAPI Spec Simplification: 'user-guide/openapi.md'
3029
- Other Utilities:
3130
- APIModel: 'user-guide/basics/api-model.md'
3231
- APISettings: 'user-guide/basics/api-settings.md'
3332
- String-Valued Enums: 'user-guide/basics/enums.md'
3433
- CamelCase Conversion: 'user-guide/basics/camelcase.md'
35-
36-
37-
34+
- GUID Type: 'user-guide/basics/guid-type.md'
3835
- Get Help: 'help-fastapi-utils.md'
3936
- Development - Contributing: 'contributing.md'
4037
- Release Notes: 'release-notes.md'

0 commit comments

Comments
 (0)