Skip to content

Commit f534f12

Browse files
Built site for gh-pages
1 parent 435186b commit f534f12

File tree

6 files changed

+66
-33
lines changed

6 files changed

+66
-33
lines changed

.nojekyll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5420a02e
1+
ee3921f1

docs/customization.html

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ <h3 class="anchored" data-anchor-id="type-checking-with-mypy">Type checking with
256256
</section>
257257
<section id="developing-with-llms" class="level3">
258258
<h3 class="anchored" data-anchor-id="developing-with-llms">Developing with LLMs</h3>
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="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="static/documentation.txt">single text file</a> for easy downloading and embedding.</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 text file: <a href="static/llms.txt">llms.txt</a>.</p>
260+
<p>One use case for this file, if using the Cursor IDE, is to rename it to <code>.cursorrules</code> and place it in your project directory (see the <a href="https://docs.cursor.com/context/rules-for-ai">Cursor docs</a> on this for more information). Alternatively, you could use it as a custom system prompt in the web interface for ChatGPT, Claude, or the LLM of your choice.</p>
261+
<p>We have also exposed the full Markdown-formatted project documentation as a <a href="static/documentation.txt">single text file</a> for easy downloading and embedding for RAG workflows.</p>
261262
</section>
262263
</section>
263264
<section id="project-structure" class="level2">
@@ -303,7 +304,7 @@ <h4 class="anchored" data-anchor-id="routing-patterns-in-this-template">Routing
303304
<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>
304305
<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>
305306
<p>We divide our GET routes into authenticated and unauthenticated routes, using commented section headers in our code that look like this:</p>
306-
<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>
307+
<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>
307308
<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>
308309
<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>
309310
</section>
@@ -416,11 +417,16 @@ <h4 class="anchored" data-anchor-id="models-and-relationships">Models and relati
416417
<p>Our database models are defined in <code>utils/models.py</code>. Each model is a Python class that inherits from <code>SQLModel</code> and represents a database table. The key models are:</p>
417418
<ul>
418419
<li><code>Organization</code>: Represents a company or team</li>
419-
<li><code>User</code>: Represents a user account</li>
420-
<li><code>Role</code>: Represents a discrete set of user permissions within an organization</li>
421-
<li><code>Permission</code>: Represents specific actions a user can perform</li>
422-
<li><code>RolePermissionLink</code>: Maps roles to their allowed permissions</li>
423-
<li><code>PasswordResetToken</code>: Manages password reset functionality</li>
420+
<li><code>User</code>: Represents a user account with name, email, and avatar</li>
421+
<li><code>Role</code>: Represents a set of permissions within an organization</li>
422+
<li><code>Permission</code>: Represents specific actions a user can perform (defined by ValidPermissions enum)</li>
423+
<li><code>PasswordResetToken</code>: Manages password reset functionality with expiration</li>
424+
<li><code>UserPassword</code>: Stores hashed user passwords separately from user data</li>
425+
</ul>
426+
<p>Two additional models are used by SQLModel to manage many-to-many relationships; you generally will not need to interact with them directly:</p>
427+
<ul>
428+
<li><code>UserRoleLink</code>: Maps users to their roles (many-to-many relationship)</li>
429+
<li><code>RolePermissionLink</code>: Maps roles to their permissions (many-to-many relationship)</li>
424430
</ul>
425431
<p>Here’s an entity-relationship diagram (ERD) of the current database schema, automatically generated from our SQLModel definitions:</p>
426432
<div class="quarto-figure quarto-figure-center">
@@ -444,19 +450,25 @@ <h4 class="anchored" data-anchor-id="database-helpers">Database helpers</h4>
444450
<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>
445451
<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>
446452
<p>The session automatically handles transaction management, ensuring that database operations are atomic and consistent.</p>
453+
<p>There is also a helper method on the <code>User</code> model that checks if a user has a specific permission for a given organization. Its first argument must be a <code>ValidPermissions</code> enum value (from <code>utils/models.py</code>), and its second argument must be an <code>Organization</code> object or an <code>int</code> representing an organization ID:</p>
454+
<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>permission <span class="op">=</span> ValidPermissions.CREATE_ROLE</span>
455+
<span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a>organization <span class="op">=</span> session.<span class="bu">exec</span>(select(Organization).where(Organization.name <span class="op">==</span> <span class="st">"Acme Inc."</span>)).first()</span>
456+
<span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a></span>
457+
<span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>user.has_permission(permission, organization)</span></code><button title="Copy to Clipboard" class="code-copy-button"><i class="bi"></i></button></pre></div>
458+
<p>You should create custom <code>ValidPermissions</code> enum values for your application and validate that users have the necessary permissions before allowing them to modify organization data resources.</p>
447459
</section>
448460
<section id="cascade-deletes" class="level4">
449461
<h4 class="anchored" data-anchor-id="cascade-deletes">Cascade deletes</h4>
450462
<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>
463+
<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>sa_relationship_kwargs<span class="op">=</span>{</span>
464+
<span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> <span class="st">"cascade"</span>: <span class="st">"all, delete-orphan"</span></span>
465+
<span id="cb10-3"><a href="#cb10-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>
454466
<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>
455467
<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>
468+
<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>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>
457469
<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>
470+
<div class="sourceCode" id="cb12"><pre class="sourceCode python code-with-copy"><code class="sourceCode python"><span id="cb12-1"><a href="#cb12-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>
471+
<span id="cb12-2"><a href="#cb12-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>
460472
<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>
461473

462474

docs/static/documentation.txt

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,11 @@ with open(output_path, 'w', encoding='utf-8') as f:
192192
f.write(final_content)
193193
```
194194

195-
In line with the [llms.txt standard](https://llmstxt.org/), we have provided a Markdown-formatted prompt—designed to help LLM agents understand how to work with this template—as a [text file](docs/static/llms.txt). One use case for this file is to rename it to `.cursorrules` and place it in your project directory is using the Cursor IDE (see the [Cursor docs](https://docs.cursor.com/context/rules-for-ai) on this for more information).
195+
In line with the [llms.txt standard](https://llmstxt.org/), we have provided a Markdown-formatted prompt—designed to help LLM agents understand how to work with this template—as a text file: [llms.txt](docs/static/llms.txt).
196196

197-
We have also exposed the full Markdown-formatted project documentation as a [single text file](docs/static/documentation.txt) for easy downloading and embedding.
197+
One use case for this file, if using the Cursor IDE, is to rename it to `.cursorrules` and place it in your project directory (see the [Cursor docs](https://docs.cursor.com/context/rules-for-ai) on this for more information). Alternatively, you could use it as a custom system prompt in the web interface for ChatGPT, Claude, or the LLM of your choice.
198+
199+
We have also exposed the full Markdown-formatted project documentation as a [single text file](docs/static/documentation.txt) for easy downloading and embedding for RAG workflows.
198200

199201
## Contributing
200202

@@ -692,9 +694,11 @@ We find that mypy is an enormous time-saver, catching many errors early and grea
692694

693695
### Developing with LLMs
694696

695-
In line with the [llms.txt standard](https://llmstxt.org/), we have provided a Markdown-formatted prompt—designed to help LLM agents understand how to work with this template—as a [text file](static/llms.txt). One use case for this file is to rename it to `.cursorrules` and place it in your project directory is using the Cursor IDE (see the [Cursor docs](https://docs.cursor.com/context/rules-for-ai) on this for more information).
697+
In line with the [llms.txt standard](https://llmstxt.org/), we have provided a Markdown-formatted prompt—designed to help LLM agents understand how to work with this template—as a text file: [llms.txt](static/llms.txt).
698+
699+
One use case for this file, if using the Cursor IDE, is to rename it to `.cursorrules` and place it in your project directory (see the [Cursor docs](https://docs.cursor.com/context/rules-for-ai) on this for more information). Alternatively, you could use it as a custom system prompt in the web interface for ChatGPT, Claude, or the LLM of your choice.
696700

697-
We have also exposed the full Markdown-formatted project documentation as a [single text file](static/documentation.txt) for easy downloading and embedding.
701+
We have also exposed the full Markdown-formatted project documentation as a [single text file](static/documentation.txt) for easy downloading and embedding for RAG workflows.
698702

699703
## Project structure
700704

@@ -738,7 +742,7 @@ We name our GET routes using the convention `read_<name>`, where `<name>` is the
738742
We divide our GET routes into authenticated and unauthenticated routes, using commented section headers in our code that look like this:
739743

740744
```python
741-
# -- Authenticated Routes --
745+
# --- Authenticated Routes ---
742746
```
743747

744748
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.
@@ -885,11 +889,16 @@ SQLModel is an Object-Relational Mapping (ORM) library that allows us to interac
885889
Our database models are defined in `utils/models.py`. Each model is a Python class that inherits from `SQLModel` and represents a database table. The key models are:
886890

887891
- `Organization`: Represents a company or team
888-
- `User`: Represents a user account
889-
- `Role`: Represents a discrete set of user permissions within an organization
890-
- `Permission`: Represents specific actions a user can perform
891-
- `RolePermissionLink`: Maps roles to their allowed permissions
892-
- `PasswordResetToken`: Manages password reset functionality
892+
- `User`: Represents a user account with name, email, and avatar
893+
- `Role`: Represents a set of permissions within an organization
894+
- `Permission`: Represents specific actions a user can perform (defined by ValidPermissions enum)
895+
- `PasswordResetToken`: Manages password reset functionality with expiration
896+
- `UserPassword`: Stores hashed user passwords separately from user data
897+
898+
Two additional models are used by SQLModel to manage many-to-many relationships; you generally will not need to interact with them directly:
899+
900+
- `UserRoleLink`: Maps users to their roles (many-to-many relationship)
901+
- `RolePermissionLink`: Maps roles to their permissions (many-to-many relationship)
893902

894903
Here's an entity-relationship diagram (ERD) of the current database schema, automatically generated from our SQLModel definitions:
895904

@@ -939,6 +948,17 @@ async def get_users(session: Session = Depends(get_session)):
939948

940949
The session automatically handles transaction management, ensuring that database operations are atomic and consistent.
941950

951+
There is also a helper method on the `User` model that checks if a user has a specific permission for a given organization. Its first argument must be a `ValidPermissions` enum value (from `utils/models.py`), and its second argument must be an `Organization` object or an `int` representing an organization ID:
952+
953+
```python
954+
permission = ValidPermissions.CREATE_ROLE
955+
organization = session.exec(select(Organization).where(Organization.name == "Acme Inc.")).first()
956+
957+
user.has_permission(permission, organization)
958+
```
959+
960+
You should create custom `ValidPermissions` enum values for your application and validate that users have the necessary permissions before allowing them to modify organization data resources.
961+
942962
#### Cascade deletes
943963

944964
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 `Relationship`, we set:

index.html

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,8 +327,9 @@ <h3 class="anchored" data-anchor-id="lint-types-with-mypy">Lint types with mypy<
327327
</section>
328328
<section id="developing-with-llms" class="level2">
329329
<h2 class="anchored" data-anchor-id="developing-with-llms">Developing with LLMs</h2>
330-
<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="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>
331-
<p>We have also exposed the full Markdown-formatted project documentation as a <a href="docs/static/documentation.txt">single text file</a> for easy downloading and embedding.</p>
330+
<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 text file: <a href="docs/static/llms.txt">llms.txt</a>.</p>
331+
<p>One use case for this file, if using the Cursor IDE, is to rename it to <code>.cursorrules</code> and place it in your project directory (see the <a href="https://docs.cursor.com/context/rules-for-ai">Cursor docs</a> on this for more information). Alternatively, you could use it as a custom system prompt in the web interface for ChatGPT, Claude, or the LLM of your choice.</p>
332+
<p>We have also exposed the full Markdown-formatted project documentation as a <a href="docs/static/documentation.txt">single text file</a> for easy downloading and embedding for RAG workflows.</p>
332333
</section>
333334
<section id="contributing" class="level2">
334335
<h2 class="anchored" data-anchor-id="contributing">Contributing</h2>

0 commit comments

Comments
 (0)