-
Notifications
You must be signed in to change notification settings - Fork 317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: add docs via mkdocs ✍️ #280
Closed
Closed
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
bdcee7c
initial docs infra
sydney-runkle 2719d60
fix build docs link
sydney-runkle 0dba098
try to add permissions before running
sydney-runkle 752140c
cleaner docs build
sydney-runkle 4745cf6
try upgrading pip
sydney-runkle 54029e1
fix syntax
sydney-runkle 39bffc2
remove dep that was being annoying
sydney-runkle 7918c3d
remove index.md placeholder
sydney-runkle 0d4ec5e
3.8 fix
sydney-runkle 69680c2
slightly modified index.md
sydney-runkle 21709c0
version updates, working on linting
sydney-runkle bee0c7a
linting first pass
sydney-runkle 18b1b1e
this is fastui, not dirty-equals, whoops
sydney-runkle 73ecd45
Merge branch 'main' into add-docs
sydney-runkle b58b98c
initial docstrings for basic components
sydney-runkle File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
::: fastui.components | ||
options: | ||
docstring_options: | ||
ignore_init_summary: false | ||
members: - Text - Paragraph - PageTitle - Div - Page - Heading - Markdown - Code - Json - Button - Link - LinkList - Navbar - Modal - ServerLoad - Image - Iframe - FireEvent - Error - Spinner - Toast - Custom - Table - Pagination - Display - Details - Form - FormField - ModelForm - Footer - AnyComponent - FormFieldBoolean - FormFieldFile - FormFieldInput - FormFieldSelect - FormFieldSelectSearch | ||
|
||
<!-- TODO: don't render attributes in TOC --> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/* Revert hue value to that of pre mkdocs-material v9.4.0 */ | ||
[data-md-color-scheme='slate'] { | ||
--md-hue: 230; | ||
--md-default-bg-color: hsla(230, 15%, 21%, 1); | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
# FastUI | ||
|
||
## The Principle | ||
|
||
FastUI is a new way to build web application user interfaces defined by declarative Python code. | ||
|
||
This means: | ||
|
||
- **If you're a Python developer** — you can build responsive web applications using React without writing a single line of JavaScript, or touching `npm`. | ||
- **If you're a frontend developer** — you can concentrate on building magical components that are truly reusable, no copy-pasting components for each view. | ||
- **For everyone** — a true separation of concerns, the backend defines the entire application; while the frontend is free to implement just the user interface | ||
|
||
At its heart, FastUI is a set of matching [Pydantic](https://docs.pydantic.dev) models and TypeScript interfaces that allow you to define a user interface. This interface is validated at build time by TypeScript and pyright/mypy and at runtime by Pydantic. | ||
|
||
You can see a simple demo of an application built with FastUI [here](https://fastui-demo.onrender.com). | ||
|
||
## The Practice - Installation | ||
|
||
FastUI is made up of 4 things: | ||
|
||
- [`fastui` PyPI package](https://pypi.python.org/pypi/fastui) — Pydantic models for UI components, and some utilities. While it works well with [FastAPI](https://fastapi.tiangolo.com) it doesn't depend on FastAPI, and most of it could be used with any python web framework. | ||
- [`@pydantic/fastui` npm package](https://www.npmjs.com/package/@pydantic/fastui) — a React TypeScript package that lets you reuse the machinery and types of FastUI while implementing your own components | ||
- [`@pydantic/fastui-bootstrap` npm package](https://www.npmjs.com/package/@pydantic/fastui-bootstrap) — implementation/customisation of all FastUI components using [Bootstrap](https://getbootstrap.com) | ||
- [`@pydantic/fastui-prebuilt` npm package](https://www.jsdelivr.com/package/npm/@pydantic/fastui-prebuilt) (available on [jsdelivr.com CDN](https://www.jsdelivr.com/package/npm/@pydantic/fastui-prebuilt)) providing a pre-built version of the FastUI React app so you can use it without installing any npm packages or building anything yourself. The Python package provides a simple HTML page to serve this app. | ||
|
||
## Usage | ||
|
||
Here's a simple but complete FastAPI application that uses FastUI to show some user profiles: | ||
|
||
```python | ||
from datetime import date | ||
|
||
from fastapi import FastAPI, HTTPException | ||
from fastapi.responses import HTMLResponse | ||
from fastui import FastUI, AnyComponent, prebuilt_html, components as c | ||
from fastui.components.display import DisplayMode, DisplayLookup | ||
from fastui.events import GoToEvent, BackEvent | ||
from pydantic import BaseModel, Field | ||
|
||
app = FastAPI() | ||
|
||
|
||
class User(BaseModel): | ||
id: int | ||
name: str | ||
dob: date = Field(title='Date of Birth') | ||
|
||
|
||
# define some users | ||
users = [ | ||
User(id=1, name='John', dob=date(1990, 1, 1)), | ||
User(id=2, name='Jack', dob=date(1991, 1, 1)), | ||
User(id=3, name='Jill', dob=date(1992, 1, 1)), | ||
User(id=4, name='Jane', dob=date(1993, 1, 1)), | ||
] | ||
|
||
|
||
@app.get("/api/", response_model=FastUI, response_model_exclude_none=True) | ||
def users_table() -> list[AnyComponent]: | ||
""" | ||
Show a table of four users, `/api` is the endpoint the frontend will connect to | ||
when a user visits `/` to fetch components to render. | ||
""" | ||
return [ | ||
c.Page( # Page provides a basic container for components | ||
components=[ | ||
c.Heading(text='Users', level=2), # renders `<h2>Users</h2>` | ||
c.Table( | ||
data=users, | ||
# define two columns for the table | ||
columns=[ | ||
# the first is the users, name rendered as a link to their profile | ||
DisplayLookup(field='name', on_click=GoToEvent(url='/user/{id}/')), | ||
# the second is the date of birth, rendered as a date | ||
DisplayLookup(field='dob', mode=DisplayMode.date), | ||
], | ||
), | ||
] | ||
), | ||
] | ||
|
||
|
||
@app.get("/api/user/{user_id}/", response_model=FastUI, response_model_exclude_none=True) | ||
def user_profile(user_id: int) -> list[AnyComponent]: | ||
""" | ||
User profile page, the frontend will fetch this when the user visits `/user/{id}/`. | ||
""" | ||
try: | ||
user = next(u for u in users if u.id == user_id) | ||
except StopIteration: | ||
raise HTTPException(status_code=404, detail="User not found") | ||
return [ | ||
c.Page( | ||
components=[ | ||
c.Heading(text=user.name, level=2), | ||
c.Link(components=[c.Text(text='Back')], on_click=BackEvent()), | ||
c.Details(data=user), | ||
] | ||
), | ||
] | ||
|
||
|
||
@app.get('/{path:path}') | ||
async def html_landing() -> HTMLResponse: | ||
"""Simple HTML page which serves the React app, comes last as it matches all paths.""" | ||
return HTMLResponse(prebuilt_html(title='FastUI Demo')) | ||
``` | ||
|
||
Which renders like this: | ||
|
||
![screenshot](https://raw.githubusercontent.com/pydantic/FastUI/main/screenshot.png) | ||
|
||
Of course, that's a very simple application, the [full demo](https://fastui-demo.onrender.com) is more complete. | ||
|
||
### The Principle (long version) | ||
|
||
FastUI is an implementation of the RESTful principle; but not as it's usually understood, instead I mean the principle defined in the original [PhD dissertation](https://ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm) by Roy Fielding, and excellently summarised in [this essay on htmx.org](https://htmx.org/essays/how-did-rest-come-to-mean-the-opposite-of-rest/) (HTMX people, I'm sorry to use your article to promote React which I know you despise 🙏). | ||
|
||
The RESTful principle as described in the HTMX article is that the frontend doesn't need to (and shouldn't) know anything about the application you're building. Instead, it should just provide all the components you need to construct the interface, the backend can then tell the frontend what to do. | ||
|
||
Think of your frontend as a puppet, and the backend as the hand within it — the puppet doesn't need to know what to say, that's kind of the point. | ||
|
||
Building an application this way has a number of significant advantages: | ||
|
||
- You only need to write code in one place to build a new feature — add a new view, change the behavior of an existing view or alter the URL structure | ||
- Deploying the front and backend can be completely decoupled, provided the frontend knows how to render all the components the backend is going to ask it to use, you're good to go | ||
- You should be able to reuse a rich set of opensource components, they should end up being better tested and more reliable than anything you could build yourself, this is possible because the components need no context about how they're going to be used (note: since FastUI is brand new, this isn't true yet, hopefully we get there) | ||
- We can use Pydantic, TypeScript and JSON Schema to provide guarantees that the two sides are communicating with an agreed schema | ||
|
||
In the abstract, FastUI is like the opposite of GraphQL but with the same goal — GraphQL lets frontend developers extend an application without any new backend development; FastUI lets backend developers extend an application without any new frontend development. | ||
|
||
#### Beyond Python and React | ||
|
||
Of course, this principle shouldn't be limited to Python and React applications — provided we use the same set of agreed schemas and encoding to communicate, we should be able to use any frontend and backend that implements the schema. Interchangeably. | ||
|
||
This could mean: | ||
|
||
- Implementing a web frontend using another JS framework like Vue — lots of work, limited value | ||
- Implementing a web frontend using an edge server, so the browser just sees HTML — lots of work but very valuable | ||
- Implementing frontends for other platforms like mobile or IOT — lots of work, no idea if it's actually a good idea? | ||
- Implementing the component models in another language like Rust or Go — since there's actually not that much code in the backend, so this would be a relatively small and mechanical task |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import logging | ||
import os | ||
import re | ||
|
||
from typing import Match | ||
|
||
from mkdocs.config import Config | ||
from mkdocs.structure.files import Files | ||
from mkdocs.structure.pages import Page | ||
|
||
try: | ||
import pytest | ||
except ImportError: | ||
pytest = None | ||
|
||
logger = logging.getLogger('mkdocs.test_examples') | ||
|
||
|
||
def on_pre_build(config: Config): | ||
pass | ||
|
||
|
||
def on_files(files: Files, config: Config) -> Files: | ||
return remove_files(files) | ||
|
||
|
||
def remove_files(files: Files) -> Files: | ||
to_remove = [] | ||
for file in files: | ||
if file.src_path in {'plugins.py'}: | ||
to_remove.append(file) | ||
elif file.src_path.startswith('__pycache__/'): | ||
to_remove.append(file) | ||
|
||
logger.debug('removing files: %s', [f.src_path for f in to_remove]) | ||
for f in to_remove: | ||
files.remove(f) | ||
|
||
return files | ||
|
||
|
||
def on_page_markdown(markdown: str, page: Page, config: Config, files: Files) -> str: | ||
markdown = remove_code_fence_attributes(markdown) | ||
return add_version(markdown, page) | ||
|
||
|
||
def add_version(markdown: str, page: Page) -> str: | ||
if page.file.src_uri == 'index.md': | ||
version_ref = os.getenv('GITHUB_REF') | ||
if version_ref and version_ref.startswith('refs/tags/'): | ||
version = re.sub('^refs/tags/', '', version_ref.lower()) | ||
url = f'https://github.com/samuelcolvin/dirty-equals/releases/tag/{version}' | ||
version_str = f'Documentation for version: [{version}]({url})' | ||
elif sha := os.getenv('GITHUB_SHA'): | ||
sha = sha[:7] | ||
url = f'https://github.com/samuelcolvin/dirty-equals/commit/{sha}' | ||
version_str = f'Documentation for development version: [{sha}]({url})' | ||
else: | ||
version_str = 'Documentation for development version' | ||
markdown = re.sub(r'{{ *version *}}', version_str, markdown) | ||
return markdown | ||
|
||
|
||
def remove_code_fence_attributes(markdown: str) -> str: | ||
""" | ||
There's no way to add attributes to code fences that works with both pycharm and mkdocs, hence we use | ||
`py key="value"` to provide attributes to pytest-examples, then remove those attributes here. | ||
|
||
https://youtrack.jetbrains.com/issue/IDEA-297873 & https://python-markdown.github.io/extensions/fenced_code_blocks/ | ||
""" | ||
|
||
def remove_attrs(match: Match[str]) -> str: | ||
suffix = re.sub( | ||
r' (?:test|lint|upgrade|group|requires|output|rewrite_assert)=".+?"', '', match.group(2), flags=re.M | ||
) | ||
return f'{match.group(1)}{suffix}' | ||
|
||
return re.sub(r'^( *``` *py)(.*)', remove_attrs, markdown, flags=re.M) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
site_name: FastUI | ||
site_description: Build web application user interfaces defined by declarative Python code. | ||
site_url: https://docs.pydantic.dev/fastui/ | ||
|
||
theme: | ||
name: 'material' | ||
palette: | ||
- media: "(prefers-color-scheme: light)" | ||
scheme: default | ||
primary: pink | ||
accent: pink | ||
toggle: | ||
icon: material/lightbulb-outline | ||
name: "Switch to dark mode" | ||
- media: "(prefers-color-scheme: dark)" | ||
scheme: slate | ||
primary: pink | ||
accent: pink | ||
toggle: | ||
icon: material/lightbulb | ||
name: "Switch to light mode" | ||
features: | ||
- content.code.annotate | ||
- content.tabs.link | ||
- content.code.copy | ||
- announce.dismiss | ||
- navigation.tabs | ||
- search.suggest | ||
- search.highlight | ||
logo: assets/logo-white.svg | ||
favicon: assets/favicon.png | ||
|
||
repo_name: pydantic/FastUI | ||
repo_url: https://github.com/pydantic/FastUI | ||
edit_uri: '' | ||
|
||
# https://www.mkdocs.org/user-guide/configuration/#validation | ||
validation: | ||
omitted_files: warn | ||
absolute_links: warn | ||
unrecognized_links: warn | ||
|
||
extra_css: | ||
- 'extra/tweaks.css' | ||
|
||
# TODO: add flarelytics support | ||
# extra_javascript: | ||
# - '/flarelytics/client.js' | ||
|
||
markdown_extensions: | ||
- toc: | ||
permalink: true | ||
- admonition | ||
- pymdownx.details | ||
- pymdownx.extra | ||
- pymdownx.superfences | ||
- pymdownx.highlight: | ||
anchor_linenums: true | ||
- pymdownx.inlinehilite | ||
- pymdownx.snippets | ||
- attr_list | ||
- md_in_html | ||
- pymdownx.emoji: | ||
emoji_index: !!python/name:material.extensions.emoji.twemoji | ||
emoji_generator: !!python/name:material.extensions.emoji.to_svg | ||
extra: | ||
version: | ||
provider: mike | ||
watch: | ||
- src | ||
plugins: | ||
- mike: | ||
alias_type: symlink | ||
canonical_version: latest | ||
- search | ||
- mkdocstrings: | ||
handlers: | ||
python: | ||
paths: | ||
- src/python-fastui | ||
options: | ||
members_order: source | ||
separate_signature: true | ||
docstring_options: | ||
ignore_init_summary: true | ||
merge_init_into_class: true | ||
show_signature_annotations: true | ||
signature_crossrefs: true | ||
- mkdocs-simple-hooks: | ||
hooks: | ||
on_pre_build: 'docs.plugins:on_pre_build' | ||
on_files: 'docs.plugins:on_files' | ||
on_page_markdown: 'docs.plugins:on_page_markdown' | ||
nav: | ||
- Introduction: index.md | ||
- Guide: guide.md | ||
- Components: components.md |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let's remember to enable this.