api, storage: POST user; + more fixes and improvements
This commit is contained in:
parent
7d309491fc
commit
8494f3737d
8 changed files with 39 additions and 12 deletions
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = ''
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue