Skip to content

Commit f4de16b

Browse files
authored
Merge pull request #71 from Promptly-Technologies-LLC/main
Merging
2 parents ec938e5 + 7e315a6 commit f4de16b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6019
-3396
lines changed

.github/workflows/test.yml

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
python-version: ["3.12"]
15-
poetry-version: [latest]
1615
os: [ubuntu-latest]
1716

1817
runs-on: ${{ matrix.os }}
@@ -35,15 +34,16 @@ jobs:
3534
steps:
3635
- uses: actions/checkout@v4
3736

38-
- uses: actions/setup-python@v5
37+
- name: Install uv
38+
uses: astral-sh/setup-uv@v4
39+
40+
- name: Set up Python
41+
uses: actions/setup-python@v5
3942
with:
4043
python-version: ${{ matrix.python-version }}
4144

42-
- name: Install and configure Poetry
43-
uses: snok/install-poetry@v1
44-
4545
- name: Install project
46-
run: poetry install
46+
run: uv sync --all-extras --dev
4747

4848
- name: Set env variables for pytest
4949
run: |
@@ -54,6 +54,7 @@ jobs:
5454
echo "DB_NAME=test_db" >> $GITHUB_ENV
5555
echo "SECRET_KEY=$(openssl rand -base64 32)" >> $GITHUB_ENV
5656
echo "BASE_URL=http://localhost:8000" >> $GITHUB_ENV
57+
echo "RESEND_API_KEY=resend_api_key" >> $GITHUB_ENV
5758
5859
- name: Verify environment variables
5960
run: |
@@ -63,10 +64,11 @@ jobs:
6364
[ -n "$DB_HOST" ] && \
6465
[ -n "$DB_PORT" ] && \
6566
[ -n "$DB_NAME" ] && \
66-
[ -n "$SECRET_KEY" ]
67+
[ -n "$SECRET_KEY" ] && \
68+
[ -n "$RESEND_API_KEY" ]
6769
6870
- name: Run type checking with mypy
69-
run: poetry run mypy .
71+
run: uv run mypy .
7072

7173
- name: Run tests with pytest
72-
run: poetry run pytest -s tests/
74+
run: uv run pytest tests/

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ __pycache__
55
/.quarto/
66
_docs/
77
.pytest_cache/
8-
.mypy_cache/
8+
.mypy_cache/
9+
.cursorrules

README.md

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ to deploy to any major cloud hosting platform.
5555

5656
**Additional technologies:**
5757

58-
- [Poetry](https://python-poetry.org/): Python dependency manager
58+
- [uv](https://docs.astral.sh/uv/): Python dependency manager
5959
- [Pytest](https://docs.pytest.org/en/7.4.x/): testing framework
6060
- [Docker](https://www.docker.com/): development containerization
6161
- [Github Actions](https://docs.github.com/en/actions): CI/CD pipeline
@@ -72,56 +72,73 @@ to deploy to any major cloud hosting platform.
7272
For comprehensive installation instructions, see the [installation
7373
page](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/installation.html).
7474

75-
### Python and Docker
75+
### uv
7676

77-
- [Python 3.12 or higher](https://www.python.org/downloads/)
78-
- [Docker and Docker Compose](https://docs.docker.com/get-docker/)
77+
MacOS and Linux:
7978

80-
### PostgreSQL headers
79+
``` bash
80+
wget -qO- https://astral.sh/uv/install.sh | sh
81+
```
8182

82-
For Ubuntu/Debian:
83+
Windows:
8384

8485
``` bash
85-
sudo apt update && sudo apt install -y python3-dev libpq-dev
86+
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
8687
```
8788

88-
For macOS:
89+
See the [uv installation
90+
docs](https://docs.astral.sh/uv/getting-started/installation/) for more
91+
information.
92+
93+
### Python
94+
95+
Install Python 3.12 or higher from either the official [downloads
96+
page](https://www.python.org/downloads/) or using uv:
8997

9098
``` bash
91-
brew install postgresql
99+
# Installs the latest version
100+
uv python install
92101
```
93102

94-
For Windows:
103+
### Docker and Docker Compose
95104

96-
- No installation required
105+
Install Docker Desktop and Coker Compose for your operating system by
106+
following the [instructions in the
107+
documentation](https://docs.docker.com/compose/install/).
97108

98-
### Python dependencies
109+
### PostgreSQL headers
99110

100-
1. Install Poetry
111+
For Ubuntu/Debian:
101112

102113
``` bash
103-
pipx install poetry
114+
sudo apt update && sudo apt install -y python3-dev libpq-dev
104115
```
105116

106-
2. Install project dependencies
117+
For macOS:
107118

108119
``` bash
109-
poetry install
120+
brew install postgresql
110121
```
111122

112-
3. Activate shell
123+
For Windows:
124+
125+
- No installation required
126+
127+
### Python dependencies
128+
129+
From the root directory, run:
113130

114131
``` bash
115-
poetry shell
132+
uv venv
133+
uv sync
116134
```
117135

118-
(Note: You will need to activate the shell every time you open a new
119-
terminal session. Alternatively, you can use the `poetry run` prefix
120-
before other commands to run them without activating the shell.)
136+
This will create an in-project virtual environment and install all
137+
dependencies.
121138

122139
### Set environment variables
123140

124-
Copy .env.example to .env with `cp .env.example .env`.
141+
Copy `.env.example` to `.env` with `cp .env.example .env`.
125142

126143
Generate a 256 bit secret key with `openssl rand -base64 32` and paste
127144
it into the .env file.
@@ -134,6 +151,9 @@ account, verify a domain, get an API key, and paste the API key into the
134151

135152
### Start development database
136153

154+
To start the development database, run the following command in your
155+
terminal from the root directory:
156+
137157
``` bash
138158
docker compose up -d
139159
```
@@ -155,14 +175,31 @@ Navigate to http://localhost:8000/
155175
mypy .
156176
```
157177

158-
### Contributing
178+
## Developing with LLMs
179+
180+
In line with the [llms.txt standard](https://llmstxt.org/), we have
181+
provided a Markdown-formatted prompt—designed to help LLM agents
182+
understand how to work with this template—as a text file:
183+
[llms.txt](docs/static/llms.txt).
184+
185+
One use case for this file, if using the Cursor IDE, is to rename it to
186+
`.cursorrules` and place it in your project directory (see the [Cursor
187+
docs](https://docs.cursor.com/context/rules-for-ai) on this for more
188+
information). Alternatively, you could use it as a custom system prompt
189+
in the web interface for ChatGPT, Claude, or the LLM of your choice.
190+
191+
We have also exposed the full Markdown-formatted project documentation
192+
as a [single text file](docs/static/documentation.txt) for easy
193+
downloading and embedding for RAG workflows.
194+
195+
## Contributing
159196

160197
Your contributions are welcome! See the [issues
161198
page](https://github.com/promptly-technologies-llc/fastapi-jinja2-postgres-webapp/issues)
162199
for ideas. Fork the repository, create a new branch, make your changes,
163200
and submit a pull request.
164201

165-
### License
202+
## License
166203

167204
This project is created and maintained by [Promptly Technologies,
168205
LLC](https://promptlytechnologies.com/) and licensed under the MIT

docs/architecture.qmd

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,103 @@ dot.render('static/data_flow', format='png', cleanup=True)
5959

6060
![Data flow diagram](static/data_flow.png)
6161

62-
The advantage of the PRG pattern is that it is very straightforward to implement and keeps most of the rendering logic on the server side. The disadvantage is that it requires an extra round trip to the database to fetch the updated data, and re-rendering the entire page template may be less efficient than a partial page update on the client side.
62+
The advantage of the PRG pattern is that it is very straightforward to implement and keeps most of the rendering logic on the server side. The disadvantage is that it requires an extra round trip to the database to fetch the updated data, and re-rendering the entire page template may be less efficient than a partial page update on the client side.
63+
64+
## Form validation flow
65+
66+
We've experimented with several approaches to validating form inputs in the FastAPI endpoints.
67+
68+
### Objectives
69+
70+
Ideally, on an invalid input, we would redirect the user back to the form, preserving their inputs and displaying an error message about which input was invalid.
71+
72+
This would keep the error handling consistent with the PRG pattern described in the [Architecture](https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture) section of this documentation.
73+
74+
To keep the code DRY, we'd also like to handle such validation with Pydantic dependencies, Python exceptions, and exception-handling middleware as much as possible.
75+
76+
### Obstacles
77+
78+
One challenge is that if we redirect back to the page with the form, the page is re-rendered with empty form fields.
79+
80+
This can be overcome by passing the inputs from the request as context variables to the template.
81+
82+
But that's a bit clunky, because then we have to support form-specific context variables in every form page and corresponding GET endpoint.
83+
84+
Also, we have to:
85+
86+
1. access the request object (which is not by default available to our middleware), and
87+
2. extract the form inputs (at least one of which is invalid in this error case), and
88+
3. pass the form inputs to the template (which is a bit challenging to do in a DRY way since there are different sets of form inputs for different forms).
89+
90+
Solving these challenges is possible, but gets high-complexity pretty quickly.
91+
92+
### Approaches
93+
94+
The best solution, I think, is to use really robust client-side form validation to prevent invalid inputs from being sent to the server in the first place. That makes it less important what we do on the server side, although we still need to handle the server-side error case as a backup in the event that something slips past our validation on the client side.
95+
96+
Here are some patterns we've considered for server-side error handling:
97+
98+
<style>
99+
.styled-table, .styled-table th, .styled-table td {
100+
border: 1px solid black;
101+
padding: 8px;
102+
border-collapse: collapse;
103+
}
104+
105+
.styled-table th:nth-child(1) { width: 50%; }
106+
.styled-table th:nth-child(2),
107+
.styled-table th:nth-child(3),
108+
.styled-table th:nth-child(4) { width: 15%; }
109+
.styled-table th:nth-child(5) { width: 10%; }
110+
</style>
111+
112+
<table class="styled-table">
113+
<thead>
114+
<tr>
115+
<th>Approach</th>
116+
<th>Returns to same page</th>
117+
<th>Preserves form inputs</th>
118+
<th>Follows PRG pattern</th>
119+
<th>Complexity</th>
120+
</tr>
121+
</thead>
122+
<tbody>
123+
<tr>
124+
<td>Validate with Pydantic dependency, catch and redirect from middleware (with exception message as context) to an error page with "go back" button</td>
125+
<td>No</td>
126+
<td>Yes</td>
127+
<td>Yes</td>
128+
<td>Low</td>
129+
</tr>
130+
<tr>
131+
<td>Validate in FastAPI endpoint function body, redirect to origin page with error message query param</td>
132+
<td>Yes</td>
133+
<td>No</td>
134+
<td>Yes</td>
135+
<td>Medium</td>
136+
</tr>
137+
<tr>
138+
<td>Validate in FastAPI endpoint function body, redirect to origin page with error message query param and form inputs as context so we can re-render page with original form inputs</td>
139+
<td>Yes</td>
140+
<td>Yes</td>
141+
<td>Yes</td>
142+
<td>High</td>
143+
</tr>
144+
<tr>
145+
<td>Validate with Pydantic dependency, use session context to get form inputs from request, redirect to origin page from middleware with exception message and form inputs as context so we can re-render page with original form inputs</td>
146+
<td>Yes</td>
147+
<td>Yes</td>
148+
<td>Yes</td>
149+
<td>High</td>
150+
</tr>
151+
<tr>
152+
<td>Validate in either Pydantic dependency or function endpoint body and directly return error message or error toast HTML partial in JSON, then mount error toast with HTMX or some simple layout-level Javascript</td>
153+
<td>Yes</td>
154+
<td>Yes</td>
155+
<td>No</td>
156+
<td>Low</td>
157+
</tr>
158+
</tbody>
159+
</table>
160+
161+
Presently this template primarily uses option 1 but also supports option 2. Ultimately, I think option 5 will be preferable; support for that [is planned](https://github.com/Promptly-Technologies-LLC/fastapi-jinja2-postgres-webapp/issues/5) for a future update or fork of this template.

0 commit comments

Comments
 (0)