Skip to content

Commit 702a07d

Browse files
author
Matthew R. Laue
committed
Initial commit
0 parents  commit 702a07d

28 files changed

+701
-0
lines changed

.flake8

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[flake8]
2+
exclude = alembic

.gitignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
.env
2+
.idea
3+
*.egg-info
4+
build
5+
dist
6+
*.db
7+
venv
8+
__pycache__

Makefile

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
all: flake8 test
2+
.PHONY: all
3+
4+
flake8:
5+
flake8 fastapi_sqlalchemy_example tests
6+
.PHONY: flake8
7+
8+
test:
9+
pytest -xv tests
10+
.PHONY: test
11+
12+
requirements.txt:
13+
virtualenv -p python3 venv
14+
venv/bin/pip3 install git+https://github.com/zuarbase/fastapi-sqlalchemy#egg=fastapi-sqlalchemy[dev,prod]
15+
venv/bin/pip3 install -e .[dev,prod]
16+
venv/bin/pip3 freeze | egrep -v "fastapi_sqlalchemy_example|pkg-resources" > $@
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import endpoints # noqa
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts
5+
script_location = /home/matt/Projects/zuar/fastapi_sqlalchemy_example/fastapi_sqlalchemy_example/alembic
6+
7+
# template used to generate migration files
8+
# file_template = %%(rev)s_%%(slug)s
9+
10+
# timezone to use when rendering the date
11+
# within the migration file as well as the filename.
12+
# string value is passed to dateutil.tz.gettz()
13+
# leave blank for localtime
14+
# timezone =
15+
16+
# max length of characters to apply to the
17+
# "slug" field
18+
# truncate_slug_length = 40
19+
20+
# set to 'true' to run the environment during
21+
# the 'revision' command, regardless of autogenerate
22+
# revision_environment = false
23+
24+
# set to 'true' to allow .pyc and .pyo files without
25+
# a source .py file to be detected as revisions in the
26+
# versions/ directory
27+
# sourceless = false
28+
29+
# version location specification; this defaults
30+
# to alembic/versions. When using multiple version
31+
# directories, initial revisions must be specified with --version-path
32+
# version_locations = %(here)s/bar %(here)s/bat alembic/versions
33+
34+
# the output encoding used when revision files
35+
# are written from script.py.mako
36+
# output_encoding = utf-8
37+
38+
sqlalchemy.url = sqlite:////home/matt/Projects/zuar/fastapi_sqlalchemy_example/fastapi_sqlalchemy_example.db
39+
version_table = migrations
40+
41+
# Logging configuration
42+
[loggers]
43+
keys = root,sqlalchemy,alembic
44+
45+
[handlers]
46+
keys = console
47+
48+
[formatters]
49+
keys = generic
50+
51+
[logger_root]
52+
level = WARN
53+
handlers = console
54+
qualname =
55+
56+
[logger_sqlalchemy]
57+
level = WARN
58+
handlers =
59+
qualname = sqlalchemy.engine
60+
61+
[logger_alembic]
62+
level = INFO
63+
handlers =
64+
qualname = alembic
65+
66+
[handler_console]
67+
class = StreamHandler
68+
args = (sys.stderr,)
69+
level = NOTSET
70+
formatter = generic
71+
72+
[formatter_generic]
73+
format = %(levelname)-5.5s [%(name)s] %(message)s
74+
datefmt = %H:%M:%S
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.
+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
2+
from logging.config import fileConfig
3+
4+
from sqlalchemy import engine_from_config
5+
from sqlalchemy import pool
6+
7+
from alembic import context
8+
9+
# this is the Alembic Config object, which provides
10+
# access to the values within the .ini file in use.
11+
config = context.config
12+
13+
# Interpret the config file for Python logging.
14+
# This line sets up loggers basically.
15+
fileConfig(config.config_file_name)
16+
17+
# add your model's MetaData object here
18+
# for 'autogenerate' support
19+
# from myapp import mymodel
20+
# target_metadata = mymodel.Base.metadata
21+
target_metadata = None
22+
23+
# other values from the config, defined by the needs of env.py,
24+
# can be acquired:
25+
# my_important_option = config.get_main_option("my_important_option")
26+
# ... etc.
27+
28+
29+
def run_migrations_offline():
30+
"""Run migrations in 'offline' mode.
31+
32+
This configures the context with just a URL
33+
and not an Engine, though an Engine is acceptable
34+
here as well. By skipping the Engine creation
35+
we don't even need a DBAPI to be available.
36+
37+
Calls to context.execute() here emit the given string to the
38+
script output.
39+
40+
"""
41+
url = config.get_main_option("sqlalchemy.url")
42+
context.configure(
43+
url=url, target_metadata=target_metadata, literal_binds=True
44+
)
45+
46+
with context.begin_transaction():
47+
context.run_migrations()
48+
49+
50+
def run_migrations_online():
51+
"""Run migrations in 'online' mode.
52+
53+
In this scenario we need to create an Engine
54+
and associate a connection with the context.
55+
56+
"""
57+
connectable = engine_from_config(
58+
config.get_section(config.config_ini_section),
59+
prefix="sqlalchemy.",
60+
poolclass=pool.NullPool,
61+
)
62+
63+
with connectable.connect() as connection:
64+
context.configure(
65+
connection=connection, target_metadata=target_metadata
66+
)
67+
68+
with context.begin_transaction():
69+
context.run_migrations()
70+
71+
72+
if context.is_offline_mode():
73+
run_migrations_offline()
74+
else:
75+
run_migrations_online()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
${imports if imports else ""}
11+
12+
# revision identifiers, used by Alembic.
13+
revision = ${repr(up_revision)}
14+
down_revision = ${repr(down_revision)}
15+
branch_labels = ${repr(branch_labels)}
16+
depends_on = ${repr(depends_on)}
17+
18+
19+
def upgrade():
20+
${upgrades if upgrades else "pass"}
21+
22+
23+
def downgrade():
24+
${downgrades if downgrades else "pass"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# flake8: noqa
2+
from .config import get_config
3+
from .login import login_get, login_post
4+
from .logout import login_post
5+
from .register import register_get, register_post
6+
from .confirm import confirm_get
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from fastapi_sqlalchemy.auth import authenticated
2+
from fastapi_sqlalchemy_example.main import app
3+
4+
from starlette.requests import Request
5+
6+
7+
@app.get('/api/config')
8+
@authenticated()
9+
async def get_config(request: Request) -> dict:
10+
return {
11+
"user": request.user.as_dict(),
12+
"scopes": request.auth.scopes,
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import fastapi
2+
from starlette.requests import Request
3+
4+
from fastapi_sqlalchemy.endpoints import ConfirmEndpoint
5+
6+
from fastapi_sqlalchemy_example import settings, models
7+
from fastapi_sqlalchemy_example.main import app
8+
9+
ENDPOINT = ConfirmEndpoint(
10+
user_cls=models.User,
11+
secret=str(settings.SECRET),
12+
)
13+
14+
15+
@app.get('/confirm/{token}')
16+
async def confirm_get(
17+
request: Request,
18+
token: str
19+
):
20+
""" confirmation of email link """
21+
return await ENDPOINT.on_get(request.state.session, token)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import fastapi
2+
from starlette.requests import Request
3+
4+
from fastapi_sqlalchemy.endpoints import LoginEndpoint
5+
6+
from fastapi_sqlalchemy_example import settings, models
7+
from fastapi_sqlalchemy_example.main import app
8+
9+
ENDPOINT = LoginEndpoint(models.User, secret=str(settings.SECRET), secure=False)
10+
11+
12+
@app.get('/login')
13+
async def login_get():
14+
""" Login form """
15+
return await ENDPOINT.on_get()
16+
17+
18+
@app.post('/login')
19+
async def login_post(
20+
request: Request,
21+
username: str = fastapi.Form(...),
22+
password: str = fastapi.Form(...),
23+
):
24+
""" Login handler """
25+
return await ENDPOINT.on_post(
26+
request.state.session, username, password
27+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from fastapi_sqlalchemy.endpoints import LogoutEndpoint
2+
3+
from fastapi_sqlalchemy_example.main import app
4+
5+
ENDPOINT = LogoutEndpoint()
6+
7+
8+
@app.post('/logout')
9+
async def login_post(
10+
):
11+
""" Logout handler """
12+
return await ENDPOINT.on_post()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import fastapi
2+
from starlette.requests import Request
3+
4+
from fastapi_sqlalchemy.endpoints import RegisterEndpoint
5+
6+
from fastapi_sqlalchemy_example import settings, models
7+
from fastapi_sqlalchemy_example.main import app
8+
9+
ENDPOINT = RegisterEndpoint(
10+
user_cls=models.User,
11+
sender=settings.EMAIL_SENDER,
12+
secret=str(settings.SECRET),
13+
14+
email_server=settings.EMAIL_SERVER,
15+
email_port=settings.EMAIL_PORT,
16+
email_use_ssl=settings.EMAIL_USE_SSL,
17+
email_use_tls=settings.EMAIL_USE_TLS,
18+
email_login=settings.EMAIL_LOGIN,
19+
email_password=settings.EMAIL_PASSWORD,
20+
)
21+
22+
23+
@app.get('/register')
24+
async def register_get():
25+
""" registration form """
26+
return await ENDPOINT.on_get()
27+
28+
29+
@app.post('/register')
30+
async def register_post(
31+
request: Request,
32+
username: str = fastapi.Form(...),
33+
email: str = fastapi.Form(...),
34+
password: str = fastapi.Form(...),
35+
confirm_password: str = fastapi.Form(None),
36+
):
37+
""" Login handler """
38+
base_url = request.url.scheme + '://' + request.url.netloc
39+
return await ENDPOINT.on_post(
40+
base_url, request.state.session,
41+
username=username, email=email,
42+
password=password, confirm_password=confirm_password
43+
)

fastapi_sqlalchemy_example/main.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
""" application main """
2+
import logging
3+
4+
from fastapi_sqlalchemy import FastAPI_SQLAlchemy, middleware, auth
5+
from fastapi_sqlalchemy_example import models
6+
7+
from starlette.middleware.authentication import AuthenticationMiddleware
8+
from starlette.staticfiles import StaticFiles
9+
10+
from . import settings
11+
12+
13+
logger = logging.getLogger(__name__)
14+
15+
16+
def make_app() -> FastAPI_SQLAlchemy:
17+
""" Create an ASGI Application
18+
"""
19+
logging.basicConfig()
20+
logging.getLogger('fastapi_sqlalchemy').setLevel(logging.INFO)
21+
22+
myapp = FastAPI_SQLAlchemy(
23+
str(settings.DATABASE_URL),
24+
title='FastAPI-SQLAlchemy Example',
25+
version='0.0.0'
26+
)
27+
myapp.add_middleware(
28+
AuthenticationMiddleware,
29+
backend=auth.PayloadAuthBackend(user_cls=models.User)
30+
)
31+
myapp.add_middleware(middleware.JwtMiddleware, secret=str(settings.SECRET))
32+
myapp.add_middleware(middleware.SessionMiddleware)
33+
return myapp
34+
35+
36+
app = make_app()
37+
app.router.default = StaticFiles(directory='static', html=True)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .users import User # noqa
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from fastapi_sqlalchemy import models
2+
from fastapi_sqlalchemy.models import mixins
3+
4+
5+
class User(models.User, mixins.ConfirmationMixin):
6+
pass

0 commit comments

Comments
 (0)