Skip to content

Commit 9680bdd

Browse files
Built site for gh-pages
1 parent a5b9dd1 commit 9680bdd

File tree

7 files changed

+59
-31
lines changed

7 files changed

+59
-31
lines changed

.nojekyll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
70c5cb36
1+
695efc74

docs/customization.html

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,8 @@ <h3 class="anchored" data-anchor-id="testing">Testing</h3>
236236
<li><code>set_up_database</code>: Sets up the test database before running the test suite by dropping all tables and recreating them to ensure a clean state.</li>
237237
<li><code>session</code>: Provides a session for database operations in tests.</li>
238238
<li><code>clean_db</code>: Cleans up the database tables before each test by deleting all entries in the <code>PasswordResetToken</code> and <code>User</code> tables.</li>
239-
<li><code>client</code>: Provides a <code>TestClient</code> instance with the session fixture, overriding the <code>get_session</code> dependency to use the test session.</li>
239+
<li><code>auth_client</code>: Provides a <code>TestClient</code> instance with access and refresh token cookies set, overriding the <code>get_session</code> dependency to use the <code>session</code> fixture.</li>
240+
<li><code>unauth_client</code>: Provides a <code>TestClient</code> instance without authentication cookies set, overriding the <code>get_session</code> dependency to use the <code>session</code> fixture.</li>
240241
<li><code>test_user</code>: Creates a test user in the database with a predefined name, email, and hashed password.</li>
241242
</ul>
242243
<p>To run the tests, use these commands:</p>
@@ -250,12 +251,13 @@ <h3 class="anchored" data-anchor-id="testing">Testing</h3>
250251
<section id="type-checking-with-mypy" class="level3">
251252
<h3 class="anchored" data-anchor-id="type-checking-with-mypy">Type checking with mypy</h3>
252253
<p>The project uses type annotations and mypy for static type checking. To run mypy, use this command from the root directory:</p>
253-
<div class="sourceCode" id="cb1"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">mypy</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
254-
<p>We find that mypy is an enormous time-saver, catching many errors early and greatly reducing time spent debugging unit tests. However, note that mypy requires you type annotate every variable, function, and method in your code base, so taking advantage of it is a lifestyle change!</p>
254+
<div class="sourceCode" id="cb1"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">mypy</span> .</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
255+
<p>We find that mypy is an enormous time-saver, catching many errors early and greatly reducing time spent debugging unit tests. However, note that mypy requires you type annotate every variable, function, and method in your code base, so taking advantage of it requires a lifestyle change!</p>
255256
</section>
256257
<section id="developing-with-llms" class="level3">
257258
<h3 class="anchored" data-anchor-id="developing-with-llms">Developing with LLMs</h3>
258-
<p>In line with the <a href="https://llmstxt.org/">llms.txt standard</a>, we have exposed the full Markdown-formatted project documentation as a <a href="https://promptlytechnologies.com/docs/static/llms.txt">single text file</a> to make it more usable by LLM agents.</p>
259+
<p>In line with the <a href="https://llmstxt.org/">llms.txt standard</a>, we have provided a Markdown-formatted prompt—designed to help LLM agents understand how to work with this template—as a <a href="https://promptlytechnologies.com/docs/static/llms.txt">text file</a>. One use case for this file is to rename it to <code>.cursorrules</code> and place it in your project directory is using the Cursor IDE (see the <a href="https://docs.cursor.com/context/rules-for-ai">Cursor docs</a> on this for more information).</p>
260+
<p>We have also exposed the full Markdown-formatted project documentation as a <a href="https://promptlytechnologies.com/docs/static/documentation.txt">single text file</a> for easy downloading and embedding.</p>
259261
</section>
260262
</section>
261263
<section id="project-structure" class="level2">
@@ -298,10 +300,12 @@ <h3 class="anchored" data-anchor-id="defining-a-web-backend-with-fastapi">Defini
298300
<p>We also create POST endpoints, which accept form submissions so the user can create, update, and delete data in the database. This template follows the Post-Redirect-Get (PRG) pattern to handle POST requests. When a form is submitted, the server processes the data and then returns a “redirect” response, which sends the user to a GET endpoint to re-render the page with the updated data. (See <a href="https://promptlytechnologies.com/fastapi-jinja2-postgres-webapp/docs/architecture.html">Architecture</a> for more details.)</p>
299301
<section id="routing-patterns-in-this-template" class="level4">
300302
<h4 class="anchored" data-anchor-id="routing-patterns-in-this-template">Routing patterns in this template</h4>
301-
<p>In this template, GET routes are defined in the main entry point for the application, <code>main.py</code>. POST routes are organized into separate modules within the <code>routers/</code> directory. We name our GET routes using the convention <code>read_&lt;name&gt;</code>, where <code>&lt;name&gt;</code> is the name of the page, to indicate that they are read-only endpoints that do not modify the database.</p>
303+
<p>In this template, GET routes are defined in the main entry point for the application, <code>main.py</code>. POST routes are organized into separate modules within the <code>routers/</code> directory.</p>
304+
<p>We name our GET routes using the convention <code>read_&lt;name&gt;</code>, where <code>&lt;name&gt;</code> is the name of the page, to indicate that they are read-only endpoints that do not modify the database.</p>
302305
<p>We divide our GET routes into authenticated and unauthenticated routes, using commented section headers in our code that look like this:</p>
303306
<div class="sourceCode" id="cb2"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># -- Authenticated Routes --</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
304-
<p>Some of our routes take request parameters, which we pass as keyword arguments to the route handler. These parameters should be type annotated for validation purposes. Some parameters are shared across all authenticated or unauthenticated routes, so we define them in the <code>common_authenticated_parameters</code> and <code>common_unauthenticated_parameters</code> dependencies defined in <code>main.py</code>.</p>
307+
<p>Some of our routes take request parameters, which we pass as keyword arguments to the route handler. These parameters should be type annotated for validation purposes.</p>
308+
<p>Some parameters are shared across all authenticated or unauthenticated routes, so we define them in the <code>common_authenticated_parameters</code> and <code>common_unauthenticated_parameters</code> dependencies defined in <code>main.py</code>.</p>
305309
</section>
306310
</section>
307311
<section id="html-templating-with-jinja2" class="level3">
@@ -317,7 +321,7 @@ <h4 class="anchored" data-anchor-id="context-variables">Context variables</h4>
317321
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="st">"welcome.html"</span>,</span>
318322
<span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> {<span class="st">"username"</span>: <span class="st">"Alice"</span>}</span>
319323
<span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> )</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
320-
<p>In this example, the <code>welcome.html</code> template will receive two pieces of context: the user’s <code>request</code>, which is always passed automatically by FastAPI, and a <code>username</code> variable, which we specify as “Alice”. We can then use the <code>{ username }</code> syntax in the <code>welcome.html</code> template (or any of its parent or child templates) to insert the value into the HTML.</p>
324+
<p>In this example, the <code>welcome.html</code> template will receive two pieces of context: the user’s <code>request</code>, which is always passed automatically by FastAPI, and a <code>username</code> variable, which we specify as “Alice”. We can then use the <code>{{ username }}</code> syntax in the <code>welcome.html</code> template (or any of its parent or child templates) to insert the value into the HTML.</p>
321325
</section>
322326
<section id="form-validation-strategy" class="level4">
323327
<h4 class="anchored" data-anchor-id="form-validation-strategy">Form validation strategy</h4>
@@ -426,9 +430,9 @@ <h4 class="anchored" data-anchor-id="models-and-relationships">Models and relati
426430
</figure>
427431
</div>
428432
</section>
429-
<section id="database-operations" class="level4">
430-
<h4 class="anchored" data-anchor-id="database-operations">Database operations</h4>
431-
<p>Database operations are handled by helper functions in <code>utils/db.py</code>. Key functions include:</p>
433+
<section id="database-helpers" class="level4">
434+
<h4 class="anchored" data-anchor-id="database-helpers">Database helpers</h4>
435+
<p>Database operations are facilitated by helper functions in <code>utils/db.py</code>. Key functions include:</p>
432436
<ul>
433437
<li><code>set_up_db()</code>: Initializes the database schema and default data (which we do on every application start in <code>main.py</code>)</li>
434438
<li><code>get_connection_url()</code>: Creates a database connection URL from environment variables in <code>.env</code></li>
@@ -440,6 +444,20 @@ <h4 class="anchored" data-anchor-id="database-operations">Database operations</h
440444
<span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> users <span class="op">=</span> session.<span class="bu">exec</span>(select(User)).<span class="bu">all</span>()</span>
441445
<span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> users</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
442446
<p>The session automatically handles transaction management, ensuring that database operations are atomic and consistent.</p>
447+
</section>
448+
<section id="cascade-deletes" class="level4">
449+
<h4 class="anchored" data-anchor-id="cascade-deletes">Cascade deletes</h4>
450+
<p>Cascade deletes (in which deleting a record from one table deletes related records from another table) can be handled at either the ORM level or the database level. This template handles cascade deletes at the ORM level, via SQLModel relationships. Inside a SQLModel <code>Relationship</code>, we set:</p>
451+
<div class="sourceCode" id="cb9"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>sa_relationship_kwargs<span class="op">=</span>{</span>
452+
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> <span class="st">"cascade"</span>: <span class="st">"all, delete-orphan"</span></span>
453+
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>}</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
454+
<p>This tells SQLAlchemy to cascade all operations (e.g., <code>SELECT</code>, <code>INSERT</code>, <code>UPDATE</code>, <code>DELETE</code>) to the related table. Since this happens through the ORM, we need to be careful to do all our database operations through the ORM using supported syntax. That generally means loading database records into Python objects and then deleting those objects rather than deleting records in the database directly.</p>
455+
<p>For example,</p>
456+
<div class="sourceCode" id="cb10"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>session.<span class="bu">exec</span>(delete(Role))</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
457+
<p>will not trigger the cascade delete. Instead, we need to select the role objects and then delete them:</p>
458+
<div class="sourceCode" id="cb11"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> role <span class="kw">in</span> session.<span class="bu">exec</span>(select(Role)).<span class="bu">all</span>():</span>
459+
<span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> session.delete(role)</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
460+
<p>This is slower than deleting the records directly, but it makes <a href="https://sqlmodel.tiangolo.com/tutorial/many-to-many/create-models-with-link/#create-the-tables">many-to-many relationships</a> much easier to manage.</p>
443461

444462

445463
</section>

docs/installation.html

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ <h2 class="anchored" data-anchor-id="install-all-dependencies-in-a-vscode-dev-co
227227
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="fu">}</span></span>
228228
<span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a><span class="fu">}</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
229229
<p>Simply create a <code>.devcontainer</code> folder in the root of the project and add a <code>devcontainer.json</code> file in the folder with the above content. VSCode may prompt you to install the Dev Container extension if you haven’t already, and/or to open the project in a container. If not, you can manually select “Dev Containers: Reopen in Container” from View &gt; Command Palette.</p>
230+
<p><em>IMPORTANT: If using this dev container configuration, you will need to set the <code>DB_HOST</code> environment variable to “host.docker.internal” in the <code>.env</code> file.</em></p>
230231
</section>
231232
<section id="install-development-dependencies-manually" class="level2">
232233
<h2 class="anchored" data-anchor-id="install-development-dependencies-manually">Install development dependencies manually</h2>
@@ -290,20 +291,27 @@ <h2 class="anchored" data-anchor-id="set-environment-variables">Set environment
290291
<p>Generate a 256 bit secret key with <code>openssl rand -base64 32</code> and paste it into the .env file.</p>
291292
<p>Set your desired database name, username, and password in the .env file.</p>
292293
<p>To use password recovery, register a <a href="https://resend.com/">Resend</a> account, verify a domain, get an API key, and paste the API key into the .env file.</p>
294+
<p>If using the dev container configuration, you will need to set the <code>DB_HOST</code> environment variable to “host.docker.internal” in the .env file. Otherwise, set <code>DB_HOST</code> to “localhost” for local development. (In production, <code>DB_HOST</code> will be set to the hostname of the database server.)</p>
293295
</section>
294296
<section id="start-development-database" class="level2">
295297
<h2 class="anchored" data-anchor-id="start-development-database">Start development database</h2>
298+
<p>To start the development database, run the following command in your terminal from the root directory:</p>
296299
<div class="sourceCode" id="cb9"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> compose up <span class="at">-d</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
300+
<p>If at any point you change the environment variables in the .env file, you will need to stop the database service <em>and tear down the volume</em>:</p>
301+
<div class="sourceCode" id="cb10"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="co"># Don't forget the -v flag to tear down the volume!</span></span>
302+
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> compose down <span class="at">-v</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
303+
<p>You may also need to restart the terminal session to pick up the new environment variables. You can also add the <code>--force-recreate</code> and <code>--build</code> flags to the startup command to ensure the container is rebuilt:</p>
304+
<div class="sourceCode" id="cb11"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">docker</span> compose up <span class="at">-d</span> <span class="at">--force-recreate</span> <span class="at">--build</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
297305
</section>
298306
<section id="run-the-development-server" class="level2">
299307
<h2 class="anchored" data-anchor-id="run-the-development-server">Run the development server</h2>
300-
<p>Make sure the development database is running and tables and default permissions/roles are created first.</p>
301-
<div class="sourceCode" id="cb10"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">uvicorn</span> main:app <span class="at">--host</span> 0.0.0.0 <span class="at">--port</span> 8000 <span class="at">--reload</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
308+
<p>Before running the development server, make sure the development database is running and tables and default permissions/roles are created first. Then run the following command in your terminal from the root directory:</p>
309+
<div class="sourceCode" id="cb12"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">uvicorn</span> main:app <span class="at">--host</span> 0.0.0.0 <span class="at">--port</span> 8000 <span class="at">--reload</span></span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
302310
<p>Navigate to http://localhost:8000/</p>
303311
</section>
304312
<section id="lint-types-with-mypy" class="level2">
305313
<h2 class="anchored" data-anchor-id="lint-types-with-mypy">Lint types with mypy</h2>
306-
<div class="sourceCode" id="cb11"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="ex">mypy</span> .</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
314+
<div class="sourceCode" id="cb13"><pre class="sourceCode bash code-with-copy"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">mypy</span> .</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
307315

308316

309317
</section>

docs/static/schema.png

-16.3 KB
Loading

0 commit comments

Comments
 (0)