Compare commits
No commits in common. "b27e18a3a05b7eecc49efdec3b97140a9ec384ac" and "b1d0c62b448d490dc18f2c8a246e329b4993416c" have entirely different histories.
b27e18a3a0
...
b1d0c62b44
6 changed files with 13 additions and 136 deletions
|
|
@ -6,7 +6,6 @@
|
||||||
* New module `mat` adds a shallow reimplementation of `Matrix()` in order to implement matrix multiplication
|
* New module `mat` adds a shallow reimplementation of `Matrix()` in order to implement matrix multiplication
|
||||||
* Removed obsolete `configparse` implementation that has been around since 0.3 and shelved since 0.4.
|
* Removed obsolete `configparse` implementation that has been around since 0.3 and shelved since 0.4.
|
||||||
* `color`: added support for conversion from RGB to sRGB, XYZ, OKLab and OKLCH.
|
* `color`: added support for conversion from RGB to sRGB, XYZ, OKLab and OKLCH.
|
||||||
* Added `user-loader` for Quart-Auth and SQLAlchemy
|
|
||||||
|
|
||||||
## 0.11.2
|
## 0.11.2
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ Documentation = "https://suou.readthedocs.io"
|
||||||
# the below are all dev dependencies (and probably already installed)
|
# the below are all dev dependencies (and probably already installed)
|
||||||
sqlalchemy = [
|
sqlalchemy = [
|
||||||
"SQLAlchemy[asyncio]>=2.0.0",
|
"SQLAlchemy[asyncio]>=2.0.0",
|
||||||
"flask-sqlalchemy" # glue code
|
"flask-sqlalchemy"
|
||||||
]
|
]
|
||||||
flask = [
|
flask = [
|
||||||
"Flask>=2.0.0",
|
"Flask>=2.0.0",
|
||||||
|
|
@ -61,10 +61,6 @@ quart = [
|
||||||
"Quart-Schema",
|
"Quart-Schema",
|
||||||
"starlette>=0.47.2"
|
"starlette>=0.47.2"
|
||||||
]
|
]
|
||||||
quart_auth = [
|
|
||||||
"Quart-Auth",
|
|
||||||
"suou[sqlalchemy]" # glue code
|
|
||||||
]
|
|
||||||
sass = [
|
sass = [
|
||||||
## HEADS UP!! libsass carries a C extension + uses setup.py
|
## HEADS UP!! libsass carries a C extension + uses setup.py
|
||||||
"libsass"
|
"libsass"
|
||||||
|
|
@ -74,7 +70,6 @@ full = [
|
||||||
"suou[sqlalchemy]",
|
"suou[sqlalchemy]",
|
||||||
"suou[flask]",
|
"suou[flask]",
|
||||||
"suou[quart]",
|
"suou[quart]",
|
||||||
"suou[quart_auth]",
|
|
||||||
"suou[peewee]",
|
"suou[peewee]",
|
||||||
"suou[markdown]",
|
"suou[markdown]",
|
||||||
"suou[sass]"
|
"suou[sass]"
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ from .http import WantsContentType
|
||||||
from .color import OKLabColor, chalk, WebColor, RGBColor, SRGBColor, XYZColor, OKLabColor
|
from .color import OKLabColor, chalk, WebColor, RGBColor, SRGBColor, XYZColor, OKLabColor
|
||||||
from .mat import Matrix
|
from .mat import Matrix
|
||||||
|
|
||||||
__version__ = "0.12.0a7"
|
__version__ = "0.12.0a5"
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
'ConfigOptions', 'ConfigParserConfigSource', 'ConfigSource', 'ConfigValue',
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ from collections import namedtuple
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from suou.functools import deprecated
|
|
||||||
from suou.mat import Matrix
|
from suou.mat import Matrix
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -103,8 +102,6 @@ class RGBColor(namedtuple('_WebColor', 'red green blue')):
|
||||||
|
|
||||||
Useful for theming.
|
Useful for theming.
|
||||||
|
|
||||||
XXX CURRENTLY THE OKLCH CONVERSION DOES NOT WORK
|
|
||||||
|
|
||||||
*Changed in 0.12.0*: name is now RGBColor, with WebColor being an alias.
|
*Changed in 0.12.0*: name is now RGBColor, with WebColor being an alias.
|
||||||
Added conversions to and from OKLCH, OKLab, sRGB, and XYZ.
|
Added conversions to and from OKLCH, OKLab, sRGB, and XYZ.
|
||||||
"""
|
"""
|
||||||
|
|
@ -171,7 +168,7 @@ class RGBColor(namedtuple('_WebColor', 'red green blue')):
|
||||||
])
|
])
|
||||||
|
|
||||||
def to_xyz(self):
|
def to_xyz(self):
|
||||||
return XYZColor(*(self.RGB_TO_XYZ @ Matrix.as_column([x / 255 for x in self])).get_column())
|
return XYZColor(*(self.RGB_TO_XYZ @ Matrix.as_column(self)).get_column())
|
||||||
|
|
||||||
def to_oklch(self):
|
def to_oklch(self):
|
||||||
return self.to_xyz().to_oklch()
|
return self.to_xyz().to_oklch()
|
||||||
|
|
@ -195,9 +192,7 @@ class SRGBColor(namedtuple('_SRGBColor', 'red green blue')):
|
||||||
blue: float
|
blue: float
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
r, g, b = round(self.red, 4), round(self.green, 4), round(self.blue, 4)
|
return f"srgb({self.red}, {self.green}, {self.blue})"
|
||||||
|
|
||||||
return f"srgb({r}, {g}, {b})"
|
|
||||||
|
|
||||||
def to_rgb(self):
|
def to_rgb(self):
|
||||||
return RGBColor(*(
|
return RGBColor(*(
|
||||||
|
|
@ -237,7 +232,7 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
|
||||||
])
|
])
|
||||||
|
|
||||||
def to_rgb(self):
|
def to_rgb(self):
|
||||||
return RGBColor(*[int(x * 255) for x in (self.XYZ_TO_RGB @ Matrix.as_column(self)).get_column()])
|
return RGBColor(*(self.XYZ_TO_RGB @ Matrix.as_column(self)).get_column())
|
||||||
|
|
||||||
def to_oklab(self):
|
def to_oklab(self):
|
||||||
lms = (self.XYZ_TO_LMS @ Matrix.as_column(self)).get_column()
|
lms = (self.XYZ_TO_LMS @ Matrix.as_column(self)).get_column()
|
||||||
|
|
@ -248,11 +243,6 @@ class XYZColor(namedtuple('_XYZColor', 'x y z')):
|
||||||
def to_oklch(self):
|
def to_oklch(self):
|
||||||
return self.to_oklab().to_oklch()
|
return self.to_oklab().to_oklch()
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
x, y, z = round(self.x, 4), round(self.y, 4), round(self.z, 4)
|
|
||||||
|
|
||||||
return f'xyz({x} {y} {z})'
|
|
||||||
|
|
||||||
|
|
||||||
class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
|
class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
|
||||||
"""
|
"""
|
||||||
|
|
@ -286,11 +276,6 @@ class OKLabColor(namedtuple('_OKLabColor', 'l a b')):
|
||||||
0 if abs(self.a) < .0002 and abs(self.b) < .0002 else (((math.atan2(self.b, self.a) * 180) / math.pi % 360) + 360) % 360
|
0 if abs(self.a) < .0002 and abs(self.b) < .0002 else (((math.atan2(self.b, self.a) * 180) / math.pi % 360) + 360) % 360
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
l, c, h = round(self.l, 4), round(self.a, 4), round(self.b, 4)
|
|
||||||
|
|
||||||
return f'oklab({l} {c} {h})'
|
|
||||||
|
|
||||||
def to_rgb(self):
|
def to_rgb(self):
|
||||||
return self.to_xyz().to_rgb()
|
return self.to_xyz().to_rgb()
|
||||||
|
|
||||||
|
|
@ -304,18 +289,19 @@ class OKLCHColor(namedtuple('_OKLCHColor', 'l c h')):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
l, c, h = round(self.l, 4), round(self.c, 4), round(self.h, 2)
|
l, c, h = round(self.l, 4), round(self.c, 4), round(self.h, 4)
|
||||||
|
|
||||||
|
return f'oklch({l}, {c}, {h})'
|
||||||
|
|
||||||
return f'oklch({l} {c} {h})'
|
|
||||||
|
|
||||||
def to_oklab(self):
|
def to_oklab(self):
|
||||||
return OKLabColor(
|
return OKLabColor(
|
||||||
self.l,
|
self.l,
|
||||||
self.c * math.cos(self.h * math.pi / 180),
|
self.c * math.cos(self.h * math.pi / 180),
|
||||||
self.c * math.sin(self.h * math.pi / 180)
|
self.h * math.cos(self.h * math.pi / 180)
|
||||||
)
|
)
|
||||||
|
|
||||||
def to_rgb(self):
|
def to_rgb(self):
|
||||||
return self.to_oklab().to_rgb()
|
return self.to_oklab().to_rgb()
|
||||||
|
|
||||||
__all__ = ('chalk', 'WebColor', "RGBColor", 'SRGBColor', 'XYZColor', 'OKLabColor', 'OKLCHColor')
|
__all__ = ('chalk', 'WebColor', "RGBColor", 'SRGBColor', 'XYZColor', 'OKLabColor')
|
||||||
|
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
"""
|
|
||||||
Utilities for Quart-Auth
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Copyright (c) 2025 Sakuragasaki46.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
See LICENSE for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
This software is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import Callable, TypeVar
|
|
||||||
from quart_auth import AuthUser, Action
|
|
||||||
from sqlalchemy import select
|
|
||||||
from sqlalchemy.orm import DeclarativeBase
|
|
||||||
from .sqlalchemy.asyncio import AsyncSession, SQLAlchemy
|
|
||||||
|
|
||||||
_T = TypeVar('_T')
|
|
||||||
|
|
||||||
def user_loader(database: SQLAlchemy, user_class: type[DeclarativeBase], *,
|
|
||||||
attr_loader: Callable[[type[AuthUser], str], _T] = lambda x, y: x.id == int(y)
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Returns a properly subclassed AuthUser loader for use in Quart-Auth.
|
|
||||||
|
|
||||||
Actual User object is at .user; other attributes are proxied.
|
|
||||||
|
|
||||||
Requires to be awaited before request before usage.
|
|
||||||
|
|
||||||
Uses SQLAlchemy's AsyncSession.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
* database The database instance.
|
|
||||||
* user_class The user class.
|
|
||||||
* attr_loader A lambda taking user_class and auth_id, default (user_class, auth_id : user_class.id == int(auth_id))
|
|
||||||
|
|
||||||
*New in 0.12.0*
|
|
||||||
"""
|
|
||||||
class UserLoader(AuthUser):
|
|
||||||
_auth_id: str | None
|
|
||||||
_auth_obj: user_class | None
|
|
||||||
id: _T
|
|
||||||
|
|
||||||
def __init__(self, auth_id: str | None, action: Action = Action.PASS):
|
|
||||||
self._auth_id = auth_id
|
|
||||||
self._auth_obj = None
|
|
||||||
self._auth_sess: AsyncSession | None = None
|
|
||||||
self.action = action
|
|
||||||
|
|
||||||
@property
|
|
||||||
def auth_id(self) -> str | None:
|
|
||||||
return self._auth_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
async def is_authenticated(self) -> bool:
|
|
||||||
await self._load()
|
|
||||||
return self._auth_id is not None
|
|
||||||
|
|
||||||
async def _load(self):
|
|
||||||
if self._auth_obj is None and self._auth_id is not None:
|
|
||||||
async with database as session:
|
|
||||||
self._auth_obj = (await session.execute(select(user_class).where(attr_loader(user_class, self._auth_id)))).scalar()
|
|
||||||
if self._auth_obj is None:
|
|
||||||
raise RuntimeError('failed to fetch user')
|
|
||||||
|
|
||||||
def __getattr__(self, key):
|
|
||||||
if self._auth_obj is None:
|
|
||||||
raise RuntimeError('user is not loaded')
|
|
||||||
return getattr(self._auth_obj, key)
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return self._auth_obj is not None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def session(self):
|
|
||||||
return self._auth_sess
|
|
||||||
|
|
||||||
async def _unload(self):
|
|
||||||
# user is not expected to mutate
|
|
||||||
if self._auth_sess:
|
|
||||||
await self._auth_sess.rollback()
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user(self):
|
|
||||||
return self._auth_obj
|
|
||||||
|
|
||||||
return UserLoader
|
|
||||||
|
|
||||||
# Optional dependency: do not import into __init__.py
|
|
||||||
__all__ = ('user_loader',)
|
|
||||||
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
from suou import RGBColor, chalk
|
from suou import chalk
|
||||||
from suou.color import OKLCHColor
|
|
||||||
|
|
||||||
class TestColor(unittest.TestCase):
|
class TestColor(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
|
|
@ -26,8 +25,3 @@ class TestColor(unittest.TestCase):
|
||||||
self.assertEqual(f'\x1b[1m{strg}\x1b[22m', chalk.bold(strg))
|
self.assertEqual(f'\x1b[1m{strg}\x1b[22m', chalk.bold(strg))
|
||||||
self.assertEqual(f'\x1b[2m{strg}\x1b[22m', chalk.faint(strg))
|
self.assertEqual(f'\x1b[2m{strg}\x1b[22m', chalk.faint(strg))
|
||||||
self.assertEqual(f'\x1b[1m\x1b[33m{strg}\x1b[39m\x1b[22m', chalk.bold.yellow(strg))
|
self.assertEqual(f'\x1b[1m\x1b[33m{strg}\x1b[39m\x1b[22m', chalk.bold.yellow(strg))
|
||||||
|
|
||||||
def test_oklch_to_rgb(self):
|
|
||||||
self.assertEqual(OKLCHColor(0.628, 0.2577, 29.23).to_rgb(), RGBColor(255, 0, 0))
|
|
||||||
self.assertEqual(OKLCHColor(0.7653, 0.1306, 194.77).to_rgb(), RGBColor(0, 204, 204))
|
|
||||||
self.assertEqual(OKLCHColor(0.5931, 0., 0.).to_rgb(), RGBColor(126, 126, 126))
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue