Browse Source

🎨 Update terminal examples and Typer note (#1139)

* 🎨 Update terminal examples with Termynal

* 🍱 Add Termynal scripts and styles from Typer for terminal examples
pull/1146/head
Sebastián Ramírez 5 years ago
committed by GitHub
parent
commit
faf88cea0b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 42
      README.md
  2. 11
      docs/advanced/extending-openapi.md
  3. 10
      docs/advanced/sql-databases-peewee.md
  4. 10
      docs/advanced/sub-applications-proxy.md
  5. 20
      docs/advanced/templates.md
  6. 10
      docs/advanced/websockets.md
  7. 3
      docs/contributing.md
  8. 108
      docs/css/termynal.css
  9. 58
      docs/deployment.md
  10. 42
      docs/index.md
  11. 110
      docs/js/custom.js
  12. 264
      docs/js/termynal.js
  13. 10
      docs/tutorial/bigger-applications.md
  14. 16
      docs/tutorial/debugging.md
  15. 43
      docs/tutorial/first-steps.md
  16. 24
      docs/tutorial/index.md
  17. 10
      docs/tutorial/security/first-steps.md
  18. 30
      docs/tutorial/security/oauth2-jwt.md
  19. 15
      docs/tutorial/sql-databases.md
  20. 10
      docs/tutorial/static-files.md
  21. 30
      docs/tutorial/testing.md
  22. 2
      mkdocs.yml

42
README.md

@ -77,6 +77,14 @@ The key features are:
--- ---
## **Typer**, the FastAPI of CLIs
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be used in the terminal instead of a web API, check out <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
## Requirements ## Requirements
Python 3.6+ Python 3.6+
@ -88,16 +96,28 @@ FastAPI stands on the shoulders of giants:
## Installation ## Installation
```bash <div class="termy">
pip install fastapi
```console
$ pip install fastapi
---> 100%
``` ```
</div>
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>. You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
```bash <div class="termy">
pip install uvicorn
```console
$ pip install uvicorn
---> 100%
``` ```
</div>
## Example ## Example
### Create it ### Create it
@ -151,10 +171,20 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
Run the server with: Run the server with:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
``` ```
</div>
<details markdown="1"> <details markdown="1">
<summary>About the command <code>uvicorn main:app --reload</code>...</summary> <summary>About the command <code>uvicorn main:app --reload</code>...</summary>

11
docs/advanced/extending-openapi.md

@ -155,10 +155,17 @@ After that, your file structure could look like:
Now you need to install `aiofiles`: Now you need to install `aiofiles`:
```bash
pip install aiofiles <div class="termy">
```console
$ pip install aiofiles
---> 100%
``` ```
</div>
### Serve the static files ### Serve the static files
* Import `StaticFiles`. * Import `StaticFiles`.

10
docs/advanced/sql-databases-peewee.md

@ -369,10 +369,16 @@ async def reset_db_state():
Then run your app with Uvicorn: Then run your app with Uvicorn:
```bash <div class="termy">
uvicorn sql_app.main:app --reload
```console
$ uvicorn sql_app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
Open your browser at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> and create a couple of users. Open your browser at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a> and create a couple of users.
Then open 10 tabs at <a href="http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get" class="external-link" target="_blank">http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get</a> at the same time. Then open 10 tabs at <a href="http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get" class="external-link" target="_blank">http://127.0.0.1:8000/docs#/default/read_slow_users_slowusers__get</a> at the same time.

10
docs/advanced/sub-applications-proxy.md

@ -73,10 +73,16 @@ Here you need to make sure you use the same path that you used for the `openapi_
Now, run `uvicorn`, if your file is at `main.py`, it would be: Now, run `uvicorn`, if your file is at `main.py`, it would be:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
And open the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. And open the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic API docs for the main app, including only its own paths: You will see the automatic API docs for the main app, including only its own paths:

20
docs/advanced/templates.md

@ -8,16 +8,28 @@ There are utilities to configure it easily that you can use directly in your **F
Install `jinja2`: Install `jinja2`:
```bash <div class="termy">
pip install jinja2
```console
$ pip install jinja2
---> 100%
``` ```
</div>
If you need to also serve static files (as in this example), install `aiofiles`: If you need to also serve static files (as in this example), install `aiofiles`:
```bash <div class="termy">
pip install aiofiles
```console
$ pip install aiofiles
---> 100%
``` ```
</div>
## Using `Jinja2Templates` ## Using `Jinja2Templates`
* Import `Jinja2Templates`. * Import `Jinja2Templates`.

10
docs/advanced/websockets.md

@ -85,10 +85,16 @@ To learn more about the options, check Starlette's documentation for:
If your file is named `main.py`, run your application with: If your file is named `main.py`, run your application with:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
Open your browser at <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>. Open your browser at <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000</a>.
You will see a simple page like: You will see a simple page like:

3
docs/contributing.md

@ -51,6 +51,7 @@ $ Get-Command pip
some/directory/fastapi/env/bin/pip some/directory/fastapi/env/bin/pip
``` ```
!!! tip !!! tip
Every time you install a new package with `pip` under that environment, activate the environment again. Every time you install a new package with `pip` under that environment, activate the environment again.
@ -60,7 +61,7 @@ some/directory/fastapi/env/bin/pip
**FastAPI** uses <a href="https://flit.readthedocs.io/en/latest/index.html" class="external-link" target="_blank">Flit</a> to build, package and publish the project. **FastAPI** uses <a href="https://flit.readthedocs.io/en/latest/index.html" class="external-link" target="_blank">Flit</a> to build, package and publish the project.
After activating the environment as described above, install `flit`: After activating the environment as described above, install `flit`:
```console ```console
$ pip install flit $ pip install flit

108
docs/css/termynal.css

@ -0,0 +1,108 @@
/**
* termynal.js
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/
:root {
--color-bg: #252a33;
--color-text: #eee;
--color-text-subtle: #a2a2a2;
}
[data-termynal] {
width: 750px;
max-width: 100%;
background: var(--color-bg);
color: var(--color-text);
font-size: 18px;
/* font-family: 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace; */
font-family: 'Roboto Mono', 'Fira Mono', Consolas, Menlo, Monaco, 'Courier New', Courier, monospace;
border-radius: 4px;
padding: 75px 45px 35px;
position: relative;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
[data-termynal]:before {
content: '';
position: absolute;
top: 15px;
left: 15px;
display: inline-block;
width: 15px;
height: 15px;
border-radius: 50%;
/* A little hack to display the window buttons in one pseudo element. */
background: #d9515d;
-webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930;
}
[data-termynal]:after {
content: 'bash';
position: absolute;
color: var(--color-text-subtle);
top: 5px;
left: 0;
width: 100%;
text-align: center;
}
a[data-terminal-control] {
text-align: right;
display: block;
color: #aebbff;
}
[data-ty] {
display: block;
line-height: 2;
}
[data-ty]:before {
/* Set up defaults and ensure empty lines are displayed. */
content: '';
display: inline-block;
vertical-align: middle;
}
[data-ty="input"]:before,
[data-ty-prompt]:before {
margin-right: 0.75em;
color: var(--color-text-subtle);
}
[data-ty="input"]:before {
content: '$';
}
[data-ty][data-ty-prompt]:before {
content: attr(data-ty-prompt);
}
[data-ty-cursor]:after {
content: attr(data-ty-cursor);
font-family: monospace;
margin-left: 0.5em;
-webkit-animation: blink 1s infinite;
animation: blink 1s infinite;
}
/* Cursor animation */
@-webkit-keyframes blink {
50% {
opacity: 0;
}
}
@keyframes blink {
50% {
opacity: 0;
}
}

58
docs/deployment.md

@ -188,18 +188,28 @@ def read_item(item_id: int, q: str = None):
* Go to the project directory (in where your `Dockerfile` is, containing your `app` directory). * Go to the project directory (in where your `Dockerfile` is, containing your `app` directory).
* Build your FastAPI image: * Build your FastAPI image:
```bash <div class="termy">
docker build -t myimage .
```console
$ docker build -t myimage .
---> 100%
``` ```
</div>
### Start the Docker container ### Start the Docker container
* Run a container based on your image: * Run a container based on your image:
```bash <div class="termy">
docker run -d --name mycontainer -p 80:80 myimage
```console
$ docker run -d --name mycontainer -p 80:80 myimage
``` ```
</div>
Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores). Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores).
### Check it ### Check it
@ -319,30 +329,54 @@ You just need to install an ASGI compatible server like:
* <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>, a lightning-fast ASGI server, built on uvloop and httptools. * <a href="https://www.uvicorn.org/" class="external-link" target="_blank">Uvicorn</a>, a lightning-fast ASGI server, built on uvloop and httptools.
```bash <div class="termy">
pip install uvicorn
```console
$ pip install uvicorn
---> 100%
``` ```
</div>
* <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>, an ASGI server also compatible with HTTP/2. * <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>, an ASGI server also compatible with HTTP/2.
```bash <div class="termy">
pip install hypercorn
```console
$ pip install hypercorn
---> 100%
``` ```
</div>
...or any other ASGI server. ...or any other ASGI server.
And run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.: And run your application the same way you have done in the tutorials, but without the `--reload` option, e.g.:
```bash <div class="termy">
uvicorn main:app --host 0.0.0.0 --port 80
```console
$ uvicorn main:app --host 0.0.0.0 --port 80
<span style="color: green;">INFO</span>: Uvicorn running on http://0.0.0.0:80 (Press CTRL+C to quit)
``` ```
</div>
or with Hypercorn: or with Hypercorn:
```bash <div class="termy">
hypercorn main:app --bind 0.0.0.0:80
```console
$ hypercorn main:app --bind 0.0.0.0:80
Running on 0.0.0.0:8080 over http (CTRL + C to quit)
``` ```
</div>
You might want to set up some tooling to make sure it is restarted automatically if it stops. You might want to set up some tooling to make sure it is restarted automatically if it stops.
You might also want to install <a href="https://gunicorn.org/" class="external-link" target="_blank">Gunicorn</a> and <a href="https://www.uvicorn.org/#running-with-gunicorn" class="external-link" target="_blank">use it as a manager for Uvicorn</a>, or use Hypercorn with multiple workers. You might also want to install <a href="https://gunicorn.org/" class="external-link" target="_blank">Gunicorn</a> and <a href="https://www.uvicorn.org/#running-with-gunicorn" class="external-link" target="_blank">use it as a manager for Uvicorn</a>, or use Hypercorn with multiple workers.

42
docs/index.md

@ -77,6 +77,14 @@ The key features are:
--- ---
## **Typer**, the FastAPI of CLIs
<a href="https://typer.tiangolo.com" target="_blank"><img src="https://typer.tiangolo.com/img/logo-margin/logo-margin-vector.svg" style="width: 20%;"></a>
If you are building a <abbr title="Command Line Interface">CLI</abbr> app to be used in the terminal instead of a web API, check out <a href="https://typer.tiangolo.com/" class="external-link" target="_blank">**Typer**</a>.
**Typer** is FastAPI's little sibling. And it's intended to be the **FastAPI of CLIs**. ⌨️ 🚀
## Requirements ## Requirements
Python 3.6+ Python 3.6+
@ -88,16 +96,28 @@ FastAPI stands on the shoulders of giants:
## Installation ## Installation
```bash <div class="termy">
pip install fastapi
```console
$ pip install fastapi
---> 100%
``` ```
</div>
You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>. You will also need an ASGI server, for production such as <a href="http://www.uvicorn.org" class="external-link" target="_blank">Uvicorn</a> or <a href="https://gitlab.com/pgjones/hypercorn" class="external-link" target="_blank">Hypercorn</a>.
```bash <div class="termy">
pip install uvicorn
```console
$ pip install uvicorn
---> 100%
``` ```
</div>
## Example ## Example
### Create it ### Create it
@ -151,10 +171,20 @@ If you don't know, check the _"In a hurry?"_ section about <a href="https://fast
Run the server with: Run the server with:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
``` ```
</div>
<details markdown="1"> <details markdown="1">
<summary>About the command <code>uvicorn main:app --reload</code>...</summary> <summary>About the command <code>uvicorn main:app --reload</code>...</summary>

110
docs/js/custom.js

@ -20,6 +20,114 @@ async function getData() {
return data return data
} }
function setupTermynal() {
document.querySelectorAll(".use-termynal").forEach(node => {
node.style.display = "block";
new Termynal(node, {
lineDelay: 500
});
});
const progressLiteralStart = "---> 100%";
const promptLiteralStart = "$ ";
const customPromptLiteralStart = "# ";
const termynalActivateClass = "termy";
let termynals = [];
function createTermynals() {
document
.querySelectorAll(`.${termynalActivateClass} .codehilite`)
.forEach(node => {
const text = node.textContent;
const lines = text.split("\n");
const useLines = [];
let buffer = [];
function saveBuffer() {
if (buffer.length) {
let isBlankSpace = true;
buffer.forEach(line => {
if (line) {
isBlankSpace = false;
}
});
dataValue = {};
if (isBlankSpace) {
dataValue["delay"] = 0;
}
if (buffer[buffer.length - 1] === "") {
// A last single <br> won't have effect
// so put an additional one
buffer.push("");
}
const bufferValue = buffer.join("<br>");
dataValue["value"] = bufferValue;
useLines.push(dataValue);
buffer = [];
}
}
for (let line of lines) {
if (line === progressLiteralStart) {
saveBuffer();
useLines.push({
type: "progress"
});
} else if (line.startsWith(promptLiteralStart)) {
saveBuffer();
const value = line.replace(promptLiteralStart, "").trimEnd();
useLines.push({
type: "input",
value: value
});
} else if (line.startsWith("// ")) {
saveBuffer();
const value = "💬 " + line.replace("// ", "").trimEnd();
useLines.push({
value: value,
class: "termynal-comment",
delay: 0
});
} else if (line.startsWith(customPromptLiteralStart)) {
saveBuffer();
const promptStart = line.indexOf(promptLiteralStart);
if (promptStart === -1) {
console.error("Custom prompt found but no end delimiter", line)
}
const prompt = line.slice(0, promptStart).replace(customPromptLiteralStart, "")
let value = line.slice(promptStart + promptLiteralStart.length);
useLines.push({
type: "input",
value: value,
prompt: prompt
});
} else {
buffer.push(line);
}
}
saveBuffer();
const div = document.createElement("div");
node.replaceWith(div);
const termynal = new Termynal(div, {
lineData: useLines,
noInit: true,
lineDelay: 500
});
termynals.push(termynal);
});
}
function loadVisibleTermynals() {
termynals = termynals.filter(termynal => {
if (termynal.container.getBoundingClientRect().top - innerHeight <= 0) {
termynal.init();
return false;
}
return true;
});
}
window.addEventListener("scroll", loadVisibleTermynals);
createTermynals();
loadVisibleTermynals();
}
async function main() { async function main() {
if (div) { if (div) {
data = await getData() data = await getData()
@ -34,6 +142,8 @@ async function main() {
ul.append(li) ul.append(li)
}) })
} }
setupTermynal();
} }
main() main()

264
docs/js/termynal.js

@ -0,0 +1,264 @@
/**
* termynal.js
* A lightweight, modern and extensible animated terminal window, using
* async/await.
*
* @author Ines Montani <ines@ines.io>
* @version 0.0.1
* @license MIT
*/
'use strict';
/** Generate a terminal widget. */
class Termynal {
/**
* Construct the widget's settings.
* @param {(string|Node)=} container - Query selector or container element.
* @param {Object=} options - Custom settings.
* @param {string} options.prefix - Prefix to use for data attributes.
* @param {number} options.startDelay - Delay before animation, in ms.
* @param {number} options.typeDelay - Delay between each typed character, in ms.
* @param {number} options.lineDelay - Delay between each line, in ms.
* @param {number} options.progressLength - Number of characters displayed as progress bar.
* @param {string} options.progressChar Character to use for progress bar, defaults to .
* @param {number} options.progressPercent - Max percent of progress.
* @param {string} options.cursor Character to use for cursor, defaults to .
* @param {Object[]} lineData - Dynamically loaded line data objects.
* @param {boolean} options.noInit - Don't initialise the animation.
*/
constructor(container = '#termynal', options = {}) {
this.container = (typeof container === 'string') ? document.querySelector(container) : container;
this.pfx = `data-${options.prefix || 'ty'}`;
this.originalStartDelay = this.startDelay = options.startDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-startDelay`)) || 600;
this.originalTypeDelay = this.typeDelay = options.typeDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-typeDelay`)) || 90;
this.originalLineDelay = this.lineDelay = options.lineDelay
|| parseFloat(this.container.getAttribute(`${this.pfx}-lineDelay`)) || 1500;
this.progressLength = options.progressLength
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressLength`)) || 40;
this.progressChar = options.progressChar
|| this.container.getAttribute(`${this.pfx}-progressChar`) || '█';
this.progressPercent = options.progressPercent
|| parseFloat(this.container.getAttribute(`${this.pfx}-progressPercent`)) || 100;
this.cursor = options.cursor
|| this.container.getAttribute(`${this.pfx}-cursor`) || '▋';
this.lineData = this.lineDataToElements(options.lineData || []);
this.loadLines()
if (!options.noInit) this.init()
}
loadLines() {
// Load all the lines and create the container so that the size is fixed
// Otherwise it would be changing and the user viewport would be constantly
// moving as she/he scrolls
const finish = this.generateFinish()
finish.style.visibility = 'hidden'
this.container.appendChild(finish)
// Appends dynamically loaded lines to existing line elements.
this.lines = [...this.container.querySelectorAll(`[${this.pfx}]`)].concat(this.lineData);
for (let line of this.lines) {
line.style.visibility = 'hidden'
this.container.appendChild(line)
}
const restart = this.generateRestart()
restart.style.visibility = 'hidden'
this.container.appendChild(restart)
this.container.setAttribute('data-termynal', '');
}
/**
* Initialise the widget, get lines, clear container and start animation.
*/
init() {
/**
* Calculates width and height of Termynal container.
* If container is empty and lines are dynamically loaded, defaults to browser `auto` or CSS.
*/
const containerStyle = getComputedStyle(this.container);
this.container.style.width = containerStyle.width !== '0px' ?
containerStyle.width : undefined;
this.container.style.minHeight = containerStyle.height !== '0px' ?
containerStyle.height : undefined;
this.container.setAttribute('data-termynal', '');
this.container.innerHTML = '';
for (let line of this.lines) {
line.style.visibility = 'visible'
}
this.start();
}
/**
* Start the animation and rener the lines depending on their data attributes.
*/
async start() {
this.addFinish()
await this._wait(this.startDelay);
for (let line of this.lines) {
const type = line.getAttribute(this.pfx);
const delay = line.getAttribute(`${this.pfx}-delay`) || this.lineDelay;
if (type == 'input') {
line.setAttribute(`${this.pfx}-cursor`, this.cursor);
await this.type(line);
await this._wait(delay);
}
else if (type == 'progress') {
await this.progress(line);
await this._wait(delay);
}
else {
this.container.appendChild(line);
await this._wait(delay);
}
line.removeAttribute(`${this.pfx}-cursor`);
}
this.addRestart()
this.finishElement.style.visibility = 'hidden'
this.lineDelay = this.originalLineDelay
this.typeDelay = this.originalTypeDelay
this.startDelay = this.originalStartDelay
}
generateRestart() {
const restart = document.createElement('a')
restart.onclick = (e) => {
e.preventDefault()
this.container.innerHTML = ''
this.init()
}
restart.href = '#'
restart.setAttribute('data-terminal-control', '')
restart.innerHTML = "restart ↻"
return restart
}
generateFinish() {
const finish = document.createElement('a')
finish.onclick = (e) => {
e.preventDefault()
this.lineDelay = 0
this.typeDelay = 0
this.startDelay = 0
}
finish.href = '#'
finish.setAttribute('data-terminal-control', '')
finish.innerHTML = "fast →"
this.finishElement = finish
return finish
}
addRestart() {
const restart = this.generateRestart()
this.container.appendChild(restart)
}
addFinish() {
const finish = this.generateFinish()
this.container.appendChild(finish)
}
/**
* Animate a typed line.
* @param {Node} line - The line element to render.
*/
async type(line) {
const chars = [...line.textContent];
line.textContent = '';
this.container.appendChild(line);
for (let char of chars) {
const delay = line.getAttribute(`${this.pfx}-typeDelay`) || this.typeDelay;
await this._wait(delay);
line.textContent += char;
}
}
/**
* Animate a progress bar.
* @param {Node} line - The line element to render.
*/
async progress(line) {
const progressLength = line.getAttribute(`${this.pfx}-progressLength`)
|| this.progressLength;
const progressChar = line.getAttribute(`${this.pfx}-progressChar`)
|| this.progressChar;
const chars = progressChar.repeat(progressLength);
const progressPercent = line.getAttribute(`${this.pfx}-progressPercent`)
|| this.progressPercent;
line.textContent = '';
this.container.appendChild(line);
for (let i = 1; i < chars.length + 1; i++) {
await this._wait(this.typeDelay);
const percent = Math.round(i / chars.length * 100);
line.textContent = `${chars.slice(0, i)} ${percent}%`;
if (percent>progressPercent) {
break;
}
}
}
/**
* Helper function for animation delays, called with `await`.
* @param {number} time - Timeout, in ms.
*/
_wait(time) {
return new Promise(resolve => setTimeout(resolve, time));
}
/**
* Converts line data objects into line elements.
*
* @param {Object[]} lineData - Dynamically loaded lines.
* @param {Object} line - Line data object.
* @returns {Element[]} - Array of line elements.
*/
lineDataToElements(lineData) {
return lineData.map(line => {
let div = document.createElement('div');
div.innerHTML = `<span ${this._attributes(line)}>${line.value || ''}</span>`;
return div.firstElementChild;
});
}
/**
* Helper function for generating attributes string.
*
* @param {Object} line - Line data object.
* @returns {string} - String of attributes.
*/
_attributes(line) {
let attrs = '';
for (let prop in line) {
// Custom add class
if (prop === 'class') {
attrs += ` class=${line[prop]} `
continue
}
if (prop === 'type') {
attrs += `${this.pfx}="${line[prop]}" `
} else if (prop !== 'value') {
attrs += `${this.pfx}-${prop}="${line[prop]}" `
}
}
return attrs;
}
}
/**
* HTML API: If current script has container(s) specified, initialise Termynal.
*/
if (document.currentScript.hasAttribute('data-termynal-container')) {
const containers = document.currentScript.getAttribute('data-termynal-container');
containers.split('|')
.forEach(container => new Termynal(container))
}

10
docs/tutorial/bigger-applications.md

@ -292,10 +292,16 @@ The end result is that the item paths are now:
Now, run `uvicorn`, using the module `app.main` and the variable `app`: Now, run `uvicorn`, using the module `app.main` and the variable `app`:
```bash <div class="termy">
uvicorn app.main:app --reload
```console
$ uvicorn app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
And open the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. And open the docs at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags: You will see the automatic API docs, including the paths from all the submodules, using the correct paths (and prefixes) and the correct tags:

16
docs/tutorial/debugging.md

@ -12,10 +12,14 @@ In your FastAPI application, import and run `uvicorn` directly:
The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with: The main purpose of the `__name__ == "__main__"` is to have some code that is executed when your file is called with:
```bash <div class="termy">
python myapp.py
```console
$ python myapp.py
``` ```
</div>
but is not called when another file imports it, like in: but is not called when another file imports it, like in:
```Python ```Python
@ -28,10 +32,14 @@ Let's say your file is named `myapp.py`.
If you run it with: If you run it with:
```bash <div class="termy">
python myapp.py
```console
$ python myapp.py
``` ```
</div>
then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`. then the internal variable `__name__` in your file, created automatically by Python, will have as value the string `"__main__"`.
So, the section: So, the section:

43
docs/tutorial/first-steps.md

@ -8,10 +8,20 @@ Copy that to a file `main.py`.
Run the live server: Run the live server:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
``` ```
</div>
!!! note !!! note
The command `uvicorn main:app` refers to: The command `uvicorn main:app` refers to:
@ -19,16 +29,13 @@ uvicorn main:app --reload
* `app`: the object created inside of `main.py` with the line `app = FastAPI()`. * `app`: the object created inside of `main.py` with the line `app = FastAPI()`.
* `--reload`: make the server restart after code changes. Only use for development. * `--reload`: make the server restart after code changes. Only use for development.
You will see an output like: In the output, there's a line with something like:
```hl_lines="4" ```hl_lines="4"
INFO: Started reloader process [17961] INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started server process [17962]
INFO: Waiting for application startup.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
That last line shows the URL where your app is being served, in your local machine. That line shows the URL where your app is being served, in your local machine.
### Check it ### Check it
@ -144,10 +151,16 @@ This will be the main point of interaction to create all your API.
This `app` is the same one referred by `uvicorn` in the command: This `app` is the same one referred by `uvicorn` in the command:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
If you create your app like: If you create your app like:
```Python hl_lines="3" ```Python hl_lines="3"
@ -156,10 +169,16 @@ If you create your app like:
And put it in a file `main.py`, then you would call `uvicorn` like: And put it in a file `main.py`, then you would call `uvicorn` like:
```bash <div class="termy">
uvicorn main:my_awesome_api --reload
```console
$ uvicorn main:my_awesome_api --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
### Step 3: create a *path operation* ### Step 3: create a *path operation*
#### Path #### Path

24
docs/tutorial/index.md

@ -12,10 +12,20 @@ All the code blocks can be copied and used directly (they are actually tested Py
To run any of the examples, copy the code to a file `main.py`, and start `uvicorn` with: To run any of the examples, copy the code to a file `main.py`, and start `uvicorn` with:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
<span style="color: green;">INFO</span>: Started reloader process [28720]
<span style="color: green;">INFO</span>: Started server process [28722]
<span style="color: green;">INFO</span>: Waiting for application startup.
<span style="color: green;">INFO</span>: Application startup complete.
``` ```
</div>
It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally. It is **HIGHLY encouraged** that you write or copy the code, edit it and run it locally.
Using it in your editor is what really shows you the benefits of FastAPI, seeing how little code you have to write, all the type checks, autocompletion, etc. Using it in your editor is what really shows you the benefits of FastAPI, seeing how little code you have to write, all the type checks, autocompletion, etc.
@ -28,10 +38,16 @@ The first step is to install FastAPI.
For the tutorial, you might want to install it with all the optional dependencies and features: For the tutorial, you might want to install it with all the optional dependencies and features:
```bash <div class="termy">
pip install fastapi[all]
```console
$ pip install fastapi[all]
---> 100%
``` ```
</div>
...that also includes `uvicorn`, that you can use as the server that runs your code. ...that also includes `uvicorn`, that you can use as the server that runs your code.
!!! note !!! note

10
docs/tutorial/security/first-steps.md

@ -33,10 +33,16 @@ Copy the example in a file `main.py`:
Run the example with: Run the example with:
```bash <div class="termy">
uvicorn main:app --reload
```console
$ uvicorn main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
## Check it ## Check it
Go to the interactive docs at: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. Go to the interactive docs at: <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.

30
docs/tutorial/security/oauth2-jwt.md

@ -28,10 +28,16 @@ If you want to play with JWT tokens and see how they work, check <a href="https:
We need to install `PyJWT` to generate and verify the JWT tokens in Python: We need to install `PyJWT` to generate and verify the JWT tokens in Python:
```bash <div class="termy">
pip install pyjwt
```console
$ pip install pyjwt
---> 100%
``` ```
</div>
## Password hashing ## Password hashing
"Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish. "Hashing" means converting some content (a password in this case) into a sequence of bytes (just a string) that looks like gibberish.
@ -56,10 +62,16 @@ The recommended algorithm is "Bcrypt".
So, install PassLib with Bcrypt: So, install PassLib with Bcrypt:
```bash <div class="termy">
pip install passlib[bcrypt]
```console
$ pip install passlib[bcrypt]
---> 100%
``` ```
</div>
!!! tip !!! tip
With `passlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others. With `passlib`, you could even configure it to be able to read passwords created by **Django**, a **Flask** security plug-in or many others.
@ -101,10 +113,16 @@ Create a random secret key that will be used to sign the JWT tokens.
To generate a secure random secret key use the command: To generate a secure random secret key use the command:
```bash <div class="termy">
openssl rand -hex 32
```console
$ openssl rand -hex 32
09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7
``` ```
</div>
And copy the output to the variable `SECRET_KEY` (don't use the one in the example). And copy the output to the variable `SECRET_KEY` (don't use the one in the example).
Create a variable `ALGORITHM` with the algorithm used to sign the JWT token and set it to `"HS256"`. Create a variable `ALGORITHM` with the algorithm used to sign the JWT token and set it to `"HS256"`.

15
docs/tutorial/sql-databases.md

@ -440,8 +440,8 @@ A "migration" is the set of steps needed whenever you change the structure of yo
!!! info !!! info
For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports": For this to work, you need to use **Python 3.7** or above, or in **Python 3.6**, install the "backports":
```bash ```console
pip install async-exit-stack async-generator $ pip install async-exit-stack async-generator
``` ```
This installs <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>. This installs <a href="https://github.com/sorcio/async_exit_stack" class="external-link" target="_blank">async-exit-stack</a> and <a href="https://github.com/python-trio/async_generator" class="external-link" target="_blank">async-generator</a>.
@ -596,10 +596,17 @@ You can copy this code and use it as is.
Then you can run it with Uvicorn: Then you can run it with Uvicorn:
```bash
uvicorn sql_app.main:app --reload <div class="termy">
```console
$ uvicorn sql_app.main:app --reload
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
``` ```
</div>
And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>. And then, you can open your browser at <a href="http://127.0.0.1:8000/docs" class="external-link" target="_blank">http://127.0.0.1:8000/docs</a>.
And you will be able to interact with your **FastAPI** application, reading data from a real database: And you will be able to interact with your **FastAPI** application, reading data from a real database:

10
docs/tutorial/static-files.md

@ -4,10 +4,16 @@ You can serve static files automatically from a directory using `StaticFiles`.
First you need to install `aiofiles`: First you need to install `aiofiles`:
```bash <div class="termy">
pip install aiofiles
```console
$ pip install aiofiles
---> 100%
``` ```
</div>
## Use `StaticFiles` ## Use `StaticFiles`
* Import `StaticFiles`. * Import `StaticFiles`.

30
docs/tutorial/testing.md

@ -103,14 +103,36 @@ For more information about how to pass data to the backend (using `requests` or
After that, you just need to install `pytest`: After that, you just need to install `pytest`:
```bash <div class="termy">
pip install pytest
```console
$ pip install pytest
---> 100%
``` ```
</div>
It will detect the files and tests automatically, execute them, and report the results back to you. It will detect the files and tests automatically, execute them, and report the results back to you.
Run the tests with: Run the tests with:
```bash <div class="termy">
pytest
```console
$ pytest
================ test session starts ================
platform linux -- Python 3.6.9, pytest-5.3.5, py-1.8.1, pluggy-0.13.1
rootdir: /home/user/code/superawesome-cli/app
plugins: forked-1.1.3, xdist-1.31.0, cov-2.8.1
collected 6 items
---> 100%
test_main.py <span style="color: green; white-space: pre;">...... [100%]</span>
<span style="color: green;">================= 1 passed in 0.03s =================</span>
``` ```
</div>

2
mkdocs.yml

@ -141,8 +141,10 @@ extra:
link: 'https://tiangolo.com' link: 'https://tiangolo.com'
extra_css: extra_css:
- 'css/termynal.css'
- 'css/custom.css' - 'css/custom.css'
extra_javascript: extra_javascript:
- 'https://unpkg.com/[email protected]/dist/mermaid.min.js' - 'https://unpkg.com/[email protected]/dist/mermaid.min.js'
- 'js/termynal.js'
- 'js/custom.js' - 'js/custom.js'

Loading…
Cancel
Save