"user" table with sqlmodel

This commit is contained in:
Jörn-Michael Miehe 2022-03-27 01:17:48 +00:00
parent 8daeb946f8
commit e7030eb521
7 changed files with 172 additions and 1 deletions

View file

@ -180,6 +180,13 @@ class CryptoConfig(BaseModel):
schemes: list[str] = ["bcrypt"]
@property
def crypt_context_sync(self) -> CryptContext:
return CryptContext(
schemes=self.schemes,
deprecated="auto",
)
@property
async def crypt_context(self) -> CryptContext:
return CryptContext(
@ -197,6 +204,19 @@ class Config(BaseModel):
jwt: JWTConfig = Field(default_factory=JWTConfig)
crypto: CryptoConfig = Field(default_factory=CryptoConfig)
@staticmethod
def load_sync() -> Config | None:
"""
Load configuration from config file
"""
try:
with open(Settings.get().config_file, "r") as config_file:
return Config.parse_obj(json.load(config_file))
except FileNotFoundError:
return None
@staticmethod
async def load() -> Config | None:
"""

View file

View file

@ -0,0 +1,30 @@
from sqlmodel import Session, SQLModel, create_engine
class Connection:
"""
Namespace for the database connection.
"""
engine = None
@classmethod
def connect(cls, connection_url: str) -> None:
"""
Connect ORM to a database engine.
"""
cls.engine = create_engine(connection_url)
SQLModel.metadata.create_all(cls.engine)
@classmethod
@property
def session(cls) -> Session | None:
"""
Create an ORM session using a context manager.
"""
if cls.engine is None:
return None
return Session(cls.engine)

View file

@ -0,0 +1,77 @@
from __future__ import annotations
from typing import Any
from pydantic import root_validator
from sqlalchemy.exc import IntegrityError
from sqlmodel import Field, SQLModel
from ..config import Config
from .connection import Connection
class UserBase(SQLModel):
name: str = Field(primary_key=True)
email: str
country: str | None = Field(default=None)
state: str | None = Field(default=None)
city: str | None = Field(default=None)
organization: str | None = Field(default=None)
organizational_unit: str | None = Field(default=None)
class User(UserBase, table=True):
password: str
@classmethod
def create(cls, **kwargs) -> User | None:
"""
Create a new user in the database.
"""
try:
with Connection.session as db:
user = User.from_orm(UserCreate(**kwargs))
db.add(user)
db.commit()
db.refresh(user)
return user
except IntegrityError:
# user already existed
return None
@classmethod
def get(cls, name: str) -> User | None:
with Connection.session as db:
return db.get(cls, name)
class UserCreate(UserBase):
password: str | None = Field(default=None)
password_clear: str | None = Field(default=None)
@root_validator
@classmethod
def hash_password(cls, values: dict[str, Any]) -> dict[str, Any]:
if (values.get("password")) is not None:
# password is set
return values
if (password_clear := values.get("password_clear")) is None:
raise ValueError("No password to hash")
if (current_config := Config.load_sync()) is None:
raise ValueError("Not configured")
values["password"] = current_config.crypto.crypt_context_sync.hash(
password_clear)
return values
class UserRead(UserBase):
pass

View file

@ -15,6 +15,7 @@ from fastapi import FastAPI
from .config import Config, Settings
from .db import Connection
from .db.schemata import User
from .db_new import connection, user
from .routers import main_router
settings = Settings.get()
@ -51,6 +52,16 @@ async def on_startup() -> None:
print(User.from_db(db, "admin"))
print(User.from_db(db, "nonexistent"))
connection.Connection.connect("sqlite:///tmp/v2.db")
user.User.create(
name="Uwe",
password_clear="ulf",
email="uwe@feh.de",
)
print(user.User.get("Uwe"))
def main() -> None:
uvicorn.run(

34
api/poetry.lock generated
View file

@ -428,6 +428,30 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
pymysql = ["pymysql (<1)", "pymysql"]
sqlcipher = ["sqlcipher3-binary"]
[[package]]
name = "sqlalchemy2-stubs"
version = "0.0.2a21"
description = "Typing Stubs for SQLAlchemy 1.4"
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
typing-extensions = ">=3.7.4"
[[package]]
name = "sqlmodel"
version = "0.0.6"
description = "SQLModel, SQL databases in Python, designed for simplicity, compatibility, and robustness."
category = "main"
optional = false
python-versions = ">=3.6.1,<4.0.0"
[package.dependencies]
pydantic = ">=1.8.2,<2.0.0"
SQLAlchemy = ">=1.4.17,<1.5.0"
sqlalchemy2-stubs = "*"
[[package]]
name = "starlette"
version = "0.17.1"
@ -477,7 +501,7 @@ standard = ["websockets (>=10.0)", "httptools (>=0.4.0)", "watchgod (>=0.6)", "p
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "432d2933102f8a0091cec1b5484944a0211ca74c5dc9b65877d99d7bd160e4bb"
content-hash = "a580f9fe4c68667e4cbdf385ac11d5c7a2925e3c990b7164faa922ec8b6f9555"
[metadata.files]
anyio = [
@ -834,6 +858,14 @@ sqlalchemy = [
{file = "SQLAlchemy-1.4.32-cp39-cp39-win_amd64.whl", hash = "sha256:b3f1d9b3aa09ab9adc7f8c4b40fc3e081eb903054c9a6f9ae1633fe15ae503b4"},
{file = "SQLAlchemy-1.4.32.tar.gz", hash = "sha256:6fdd2dc5931daab778c2b65b03df6ae68376e028a3098eb624d0909d999885bc"},
]
sqlalchemy2-stubs = [
{file = "sqlalchemy2-stubs-0.0.2a21.tar.gz", hash = "sha256:207e3d8a36fc032d325f4eec89e0c6760efe81d07e978513d8c9b14f108dcd0c"},
{file = "sqlalchemy2_stubs-0.0.2a21-py3-none-any.whl", hash = "sha256:bd4a3d5ca7ff9d01b2245e1b26304d6b2ec4daf43a01faf40db9e09245679433"},
]
sqlmodel = [
{file = "sqlmodel-0.0.6-py3-none-any.whl", hash = "sha256:c5fd8719e09da348cd32ce2a5b6a44f289d3029fa8f1c9818229b6f34f1201b4"},
{file = "sqlmodel-0.0.6.tar.gz", hash = "sha256:3b4f966b9671b24d85529d274e6c4dbc7753b468e35d2d6a40bd75cad1f66813"},
]
starlette = [
{file = "starlette-0.17.1-py3-none-any.whl", hash = "sha256:26a18cbda5e6b651c964c12c88b36d9898481cd428ed6e063f5f29c418f73050"},
{file = "starlette-0.17.1.tar.gz", hash = "sha256:57eab3cc975a28af62f6faec94d355a410634940f10b30d68d31cb5ec1b44ae8"},

View file

@ -13,6 +13,7 @@ python-jose = {extras = ["cryptography"], version = "^3.3.0"}
passlib = {extras = ["argon2", "bcrypt"], version = "^1.7.4"}
SQLAlchemy = "^1.4.32"
pyOpenSSL = "^22.0.0"
sqlmodel = "^0.0.6"
[tool.poetry.dev-dependencies]
pytest = "^7.1.0"