Skip to main content

zZzZ

Project description

Quart-Keycloak

zZzZ

Redis cache

app.setup_cache()

After which you may use it directly;

from quart_session.sessions import SessionInterface
@app.route("/")
async def hello():
    cache: SessionInterface = app.session_interface
    await cache.set("random_key", "val", expiry=3600)
    data = await cache.get("random_key")

multi-subscriber websocket

from asyncio_multisubscriber_queue import MultisubscriberQueue
broadcast = MultisubscriberQueue()

await broadcast.put({"payload": 1})

async for msg in broadcast.subscribe():
    [...]

Utils

from quart.kroket.utils import ...
def make_slug(inp: str)
def program_exists(name: str)
def safu(func)  (decorator)
def get_visitor_ipv4(enforce_forwarded_header=True)
def random_str(num_chars: int)

image APIs

app.setup_image_apis()

Registers the following routes:

  • /_/avatar/<path:inp>
  • /_/gravatar/<path:inp>
  • /_/avatar/<path:inp>
  • /_/qr/<path:inp>/<path:color_from>/<path:color_to>/ (colors need to be tuple)
url_for("kquart.route_avatar", inp="test")
url_for("kquart.route_gravatar", inp="test")
url_for("kquart.route_qr", inp="test")

Error templates

Create these:

  • templates/errors/500.html
  • templates/errors/403.html
  • templates/errors/404.html

OIDC

app.setup_keycloak(
    client_id='', 
    client_secret='', 
    configuration='url')

auto-registered routes:

  • /oidc/login
  • /oidc/logout
  • /oidc/after_logout

After login, will set g.user and session['user'] with instance of quart.kroket.oidc.models.KeycloakUser

So in factory.py we'll have before_request look like:

@app.before_request
async def set_request_ctx():
    from app.db.models import User
    from quart.kroket.database.enums import UserRole
    auth_token: KeycloakAuthToken = g.ses

    if not auth_token:
        return

    if g.user:
        return

    # get and update (or create) user
    try:
        user: User = User.by_uid(auth_token.sub)
        user.username = auth_token.username
        user.role = UserRole.member
        user.save()
        g.user = user
    except pw.DoesNotExist as ex:
        user: User = User.create(
            uuid=auth_token.sub,
            username=auth_token.username,
            role=UserRole.member)
        g.user = user
    except Exception as ex:
        current_app.logger.error(ex)
        session.clear()
        raise Exception(ex)

Template filters

hash_sha256(val)
def hash_md5(val)
def size_human(val)
def dt_ago(val: datetime)
def dt_human(val)

Rate limiting

rate_limiter = RateLimiter()

def create_app():
    app = Quart(__name__)
    rate_limiter.init_app(app)
    return app

@app.route('/')
@rate_limit(1, timedelta(seconds=10))
async def handler():
    [...]

Database

Enum

Usage:
  from enum import IntEnum, unique

  @unique
  class UserStatus(IntEnum):
    disabled = 0
    enable = 1
    banned = 2

  [...]
  status = EnumIntField(enum_class=UserStatus, default=UserStatus.active)
  [...]
  Model.select().where(Model.status != UserStatus.banned)

Usage:
  from enum import IntEnum, unique

  @unique
  class UserTags(IntEnum):
    has_car = 0
    has_plane = 1
    has_helicopter = 2

  [...]
  vehicles = EnumArrayField(enum_class=UserTags, field_class=IntegerField, default=[UserTags.has_bank_account])

  # Fetch results with `has_car` OR `has_helicopter`:
  Model.select().where(
    Model.vehicles.contains_any(UserTags.has_car, UserTags.has_helicopter)).get()

database example

from quart.kroket.database import create_db
database = create_db()
class Company(pw.Model):
    uid: Union[str, UUID] = pw.UUIDField(primary_key=True, default=uuid4)
    created = pw.DateTimeField(default=datetime.now)
    modified = pw.DateTimeField(default=datetime.now)

    name: str = pw.CharField(unique=True, index=True, max_length=256)
    users: List['User'] = None

    class Meta:
        from my_app.factory import database
        database = database


class User(pw.Model):
    uid: Union[str, UUID] = pw.UUIDField(primary_key=True)  # uuid4 from keycloak
    created = pw.DateTimeField(default=datetime.now)
    modified = pw.DateTimeField(default=datetime.now)

    name = pw.CharField(unique=True, index=True, max_length=256)
    username = pw.CharField(unique=True, index=True, max_length=64)
    company = pw.ForeignKeyField(Company, backref='users')

    roles: List[UserRole] = EnumArrayField(enum_class=UserRole, field_class=pw.IntegerField, default=[UserRole.anonymous])
    status: UserStatus = EnumIntField(enum_class=UserStatus, default=UserStatus.active)

    things: List['Thing'] = None

    class Meta:
        from my_app.factory import database
        database = database

    @property
    def is_admin(self):
        return UserRole.admin in self.roles

    @property
    def is_anon(self):
        return UserRole.anonymous in self.roles

    @property
    def has_auth(self):
        return not self.is_anon

    @staticmethod
    def by_uid(uuid: Union[str, UUID]):
        return User.select() \
            .join(File, pw.JOIN.LEFT_OUTER) \
            .where(User.uid == uuid).get()
        # .join(Download, pw.JOIN.LEFT_OUTER) \

    async def to_json(self):
        return {
            "uid": self.uid
        }


class Thing(pw.Model):
    uid: Union[str, UUID] = pw.UUIDField(primary_key=True, default=uuid4)
    created = pw.DateTimeField(default=datetime.now)
    modified = pw.DateTimeField(default=datetime.now)

    title: str = pw.CharField(max_length=512, index=True)
    markdown: str = pw.TextField(null=True)

    roles_view: List[UserRole] = EnumArrayField(enum_class=UserRole, field_class=pw.IntegerField, default=[])
    roles_edit: List[UserRole] = EnumArrayField(enum_class=UserRole, field_class=pw.IntegerField, default=[])
    participants: List[User] = pw.ManyToManyField(User, backref='thing_participations')

    ip = pw.IPField(index=True, null=True)

    creator = pw.ForeignKeyField(User, backref='things')

    class Meta:
        from my_app.factory import database
        database = database

    @property
    def ago(self):
        return humanize.naturaltime(self.created)

    @classmethod
    async def new_ticket(cls, user: User, title: str, markdown: str, remote_address: str) -> 'Thing':
        if not title:
            raise Exception("title cannot be empty")
        if not validate_ipv4(remote_address):
            raise Exception("upload ipv4 address invalid")

        return cls.create(
            title=title,
            markdown=markdown,
            creator=user,
            ip=remote_address
        )

UserThingParticipant = Thing.participants.get_through_model()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Quart-Kroket-1.0.6.tar.gz (299.2 kB view details)

Uploaded Source

File details

Details for the file Quart-Kroket-1.0.6.tar.gz.

File metadata

  • Download URL: Quart-Kroket-1.0.6.tar.gz
  • Upload date:
  • Size: 299.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/4.0.2 CPython/3.10.6

File hashes

Hashes for Quart-Kroket-1.0.6.tar.gz
Algorithm Hash digest
SHA256 406a17e25b6d26f94ae8e0cd9bbe82b39bc6df422332955e7017e8ea4e7cd9b1
MD5 0053366e6b1f3867dfc2f5b002af1684
BLAKE2b-256 f5189e579e1c095b4f4db40aa477a5e703f709ff78b3f7fe11681956cf4454ec

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page