api, storage: POST user; + more fixes and improvements

This commit is contained in:
Helmut Merz 2026-11-06 14:29:26 +01:00
parent 7d309491fc
commit 8494f3737d
8 changed files with 39 additions and 12 deletions

View file

@ -29,7 +29,8 @@ zope = [
"zope.traversing",
]
api = ["fastapi[standard]"]
auth = ["authlib", "pyjwt[crypto]", "cryptography", "pwdlib[argon2]", "requests"]
auth = ["authlib", "pyjwt[crypto]", "cryptography", "httpx2",
"pwdlib[argon2]", "requests"]
zopetest = ["zope.testrunner"]
apitest = ["pytest", "httpx2"]
@ -43,5 +44,7 @@ testpaths = ["tests"]
# use .pytest.ini file for local test config
[tool.mypy]
packages = ['scopes.api', 'scopes.core', 'scopes.shape',
'scopes.storage', 'scopes.util']
follow_imports = 'skip'

View file

@ -1,13 +1,15 @@
# scopes.api.auth user
from fastapi import APIRouter
from fastapi import APIRouter, Body
import jwt
from pwdlib import PasswordHash
from pydantic import BaseModel, Field
from typing import List
from typing import Annotated, List
from scopes.api import deps
from scopes.config import settings
from scopes.storage import user as st_user
from scopes.storage.common import Storage
class User(BaseModel):
uid: str | None = None
@ -28,6 +30,11 @@ def hashPassword(pw):
def verifyPassword(plain, hashed):
return pw_hash.verify(plain, hashed)
def getUser(storage: Storage, userName: str) -> User:
users = storage.create(st_user.Users)
user = users.queryLast(name=userName)
return user.toObject(User)
async def authenticate(storage: deps.Storage, token: deps.Auth):
payload = jwt.decode(token, settings.jwt_key, algorithms=[settings.jwt_algorithm])
userName = payload.get('sub')
@ -44,3 +51,10 @@ async def get_users(storage: deps.Storage):
result = [tr.toObject(User) for tr in users.query()]
return result
@router.post('/users', response_model=User)
async def post_user(user: Annotated[User, Body()], storage: deps.Storage):
users = storage.create(st_user.Users)
tr = st_user.User.fromObject(user)
users.save(tr)
return tr.toObject(User)

View file

@ -3,7 +3,7 @@
"""basic (generic) routes and views for tracks (records)"""
from datetime import datetime
from fastapi import APIRouter, Body, Depends
from fastapi import APIRouter, Body
from pydantic import BaseModel, Field
from typing import Annotated, Any, Self

View file

@ -6,7 +6,7 @@ from scopes import shape
class Message(shape.Record):
headFields = ('domain', 'action', 'class', 'item')
headFields: tuple[str, ...] = ('domain', 'action', 'class', 'item')
headFieldsAsKeywords = {'domain', 'action', 'class'}
customer = None

View file

@ -6,7 +6,7 @@ from scopes.util import keywords as k
class Record:
headFields: tuple = () # to be defined by subclass
headFields: tuple[str, ...] = () # to be defined by subclass
headFieldsAsKeywords: set[str] = set()
defaultHeadValue = ''

View file

@ -4,19 +4,18 @@
from scopes.core.message import Message as BaseMessage
from scopes.storage.common import registerContainerClass
#from scopes.storage.tracking import Container, Track
from scopes.storage.tracking import Container, Record
from scopes.storage import tracking
class Message(Record, BaseMessage):
class Message(BaseMessage, tracking.Track):
prefix = 'msg'
@registerContainerClass
class Messages(Container):
class Messages(tracking.Container):
itemFactory = Message
itemFactory: type[Message] = Message
indexes = [('domain', 'action', 'class', 'item'), ('domain', 'class', 'item')]
tableName = 'messages'
insertOnChange = True

View file

@ -33,6 +33,12 @@ class Record(shape.Record):
return base | self.head | self.data
return base | super().asDict()
@classmethod
def fromObject(cls, obj):
tr = super().fromObject(obj)
tr.data.pop('uid', None)
return tr
@property
def uid(self):
if self.trackId is None:
@ -64,7 +70,7 @@ class Track(Record):
@registerContainerClass
class Container:
itemFactory = Track
itemFactory: type[Track] = Track
tableName = 'tracks'
insertOnChange = True # always insert new track when data are changed
indexes: Indexes = None # is set in registerContainerClass()

View file

@ -72,4 +72,9 @@ def test_user(storage, caplog):
assert len(users) == 1
u01i = users[0]
assert u01i.get(pw_hash) is None
resp = clt.post('/users', json={'name': 'jane', 'login': 'jane',
'display_name': 'Jane', 'email': 'jane@xmpl.org'})
assert resp.status_code == 200
u02 = resp.json()
assert u02['uid'] == 'user-2'