Skip to content

Commit

Permalink
122 refactoring jubilee (#129)
Browse files Browse the repository at this point in the history
* deps(main/dev): Add coverage.py and update de deps (#125)

* Update requirements

Add new requirements for refactor

* Add user domain

This change adds the user domain and associated unit tests

* Update dependencies

Add Beanie ORM and dependency injection framework.

* Add refactored user stuff

* Add changed queries interface

* Add queries interface and models
  • Loading branch information
elais authored Aug 8, 2022
1 parent 6dadba8 commit dcb77b2
Show file tree
Hide file tree
Showing 35 changed files with 1,420 additions and 647 deletions.
16 changes: 16 additions & 0 deletions nmdc_runtime/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# from fastapi import FastAPI

# from .containers import Container
# from .api.v1 import users


# def create_app() -> FastAPI:
# container = Container()

# app = FastAPI()
# app.container = container
# app.include_router(users.router)
# return app


# application = create_app()
Empty file added nmdc_runtime/api/v1/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions nmdc_runtime/api/v1/models/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Optional, List

from pydantic import BaseModel


from nmdc_runtime.domain.users.userSchema import UserOut


class Response(BaseModel):
query: str
limit: int


class UserResponse(Response):
users: List[UserOut]
9 changes: 9 additions & 0 deletions nmdc_runtime/api/v1/router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from fastapi import APIRouter

from . import users

router = APIRouter(
prefix="/v1", tags=["v1"], responses={404: {"description": "Not found"}}
)

router.include_router(users.router, tags=["users"])
38 changes: 38 additions & 0 deletions nmdc_runtime/api/v1/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""Endpoints module."""
from typing import List, Optional

from fastapi import APIRouter, HTTPException, Depends, Response, status
from dependency_injector.wiring import inject, Provide

from nmdc_runtime.containers import Container

from nmdc_runtime.domain.users.userService import UserService
from nmdc_runtime.domain.users.userSchema import UserAuth, UserOut


router = APIRouter(prefix="/users", tags=["users"])


# @router.get("", response_model=Response)
# @inject
# async def index(
# query: Optional[str] = None,
# limit: Optional[str] = None,
# user_service: UserService = Depends(Provide[Container.user_service]),
# ) -> List[UserOut]:
# query = query
# limit = limit

# users = await user_service.search(query, limit)

# return {"query": query, "limit": limit, "users": users}


@router.post("", response_model=Response, status_code=status.HTTP_201_CREATED)
@inject
async def add(
user: UserAuth,
user_service: UserService = Depends(Provide[Container.user_service]),
) -> UserOut:
new_user = await user_service.create_user(user)
return new_user
15 changes: 15 additions & 0 deletions nmdc_runtime/containers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Containers module."""

from dependency_injector import containers, providers

from nmdc_runtime.domain.users.userService import UserService
from nmdc_runtime.infrastructure.database.impl.mongo.models.user import (
UserQueries,
)


class Container(containers.DeclarativeContainer):

user_queries = providers.Singleton(UserQueries)

user_service = providers.Factory(UserService, user_queries=user_queries)
Empty file added nmdc_runtime/core/__init__.py
Empty file.
15 changes: 15 additions & 0 deletions nmdc_runtime/core/db/Database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from contextlib import contextmanager, AbstractContextManager
from typing import Callable
import logging

from motor import motor_asyncio


class Database:
def __init__(self, db_url: str) -> None:
self._client = motor_asyncio.AsyncIOMotorClient(db_url)
self._db = self._client["database"]

@contextmanager
def session(self):
return self._db
Empty file.
23 changes: 23 additions & 0 deletions nmdc_runtime/core/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from .base import (
CustomException,
BadRequestException,
NotFoundException,
ForbiddenException,
UnprocessableEntity,
DuplicateValueException,
UnauthorizedException,
)
from .token import DecodeTokenException, ExpiredTokenException


__all__ = [
"CustomException",
"BadRequestException",
"NotFoundException",
"ForbiddenException",
"UnprocessableEntity",
"DuplicateValueException",
"UnauthorizedException",
"DecodeTokenException",
"ExpiredTokenException",
]
47 changes: 47 additions & 0 deletions nmdc_runtime/core/exceptions/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from http import HTTPStatus


class CustomException(Exception):
code = HTTPStatus.BAD_GATEWAY
error_code = HTTPStatus.BAD_GATEWAY
message = HTTPStatus.BAD_GATEWAY.description

def __init__(self, message=None):
if message:
self.message = message


class BadRequestException(CustomException):
code = HTTPStatus.BAD_REQUEST
error_code = HTTPStatus.BAD_REQUEST
message = HTTPStatus.BAD_REQUEST.description


class NotFoundException(CustomException):
code = HTTPStatus.NOT_FOUND
error_code = HTTPStatus.NOT_FOUND
message = HTTPStatus.NOT_FOUND.description


class ForbiddenException(CustomException):
code = HTTPStatus.FORBIDDEN
error_code = HTTPStatus.FORBIDDEN
message = HTTPStatus.FORBIDDEN.description


class UnauthorizedException(CustomException):
code = HTTPStatus.UNAUTHORIZED
error_code = HTTPStatus.UNAUTHORIZED
message = HTTPStatus.UNAUTHORIZED.description


class UnprocessableEntity(CustomException):
code = HTTPStatus.UNPROCESSABLE_ENTITY
error_code = HTTPStatus.UNPROCESSABLE_ENTITY
message = HTTPStatus.UNPROCESSABLE_ENTITY.description


class DuplicateValueException(CustomException):
code = HTTPStatus.UNPROCESSABLE_ENTITY
error_code = HTTPStatus.UNPROCESSABLE_ENTITY
message = HTTPStatus.UNPROCESSABLE_ENTITY.description
13 changes: 13 additions & 0 deletions nmdc_runtime/core/exceptions/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from core.exceptions import CustomException


class DecodeTokenException(CustomException):
code = 400
error_code = 10000
message = "token decode error"


class ExpiredTokenException(CustomException):
code = 400
error_code = 10001
message = "expired token"
Empty file added nmdc_runtime/domain/__init__.py
Empty file.
Empty file.
18 changes: 18 additions & 0 deletions nmdc_runtime/domain/users/queriesInterface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from __future__ import annotations
from abc import ABC

from abc import abstractmethod

from nmdc_runtime.domain.users.userSchema import UserAuth, UserUpdate, UserOut


class IUserQueries(ABC):
@abstractmethod
async def create(self, user: UserAuth) -> UserOut:
"""Create new user"""
raise NotImplementedError

@abstractmethod
async def update(self, user: UserUpdate) -> UserOut:
"""Update user data"""
raise NotImplementedError
37 changes: 37 additions & 0 deletions nmdc_runtime/domain/users/userSchema.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from typing import Optional, List


from pydantic import BaseModel, EmailStr


class UserBase(BaseModel):
username: Optional[str] = None
email: Optional[str] = None
full_name: Optional[str] = None
site_admin: Optional[List[str]] = []
disabled: Optional[bool] = False


class UserAuth(UserBase):
"""User register and login auth"""

username: str
password: str


# Properties to receive via API on update
class UserUpdate(UserBase):
"""Updatable user fields"""

email: Optional[EmailStr] = None

# User information
full_name: Optional[str] = None
password: Optional[str] = None


class UserOut(UserUpdate):
"""User fields pushed to the client"""

email: EmailStr
disabled: Optional[bool] = False
16 changes: 16 additions & 0 deletions nmdc_runtime/domain/users/userService.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Any

from nmdc_runtime.domain.users.userSchema import UserAuth, UserUpdate, UserOut


class UserService:
def __init__(self, user_queries: Any) -> None:
self.__user_queries = user_queries

async def create_user(self, user: UserAuth) -> UserOut:
return await self.__user_queries.create(user)

async def update_user(
self, username: str, new_user: UserUpdate
) -> UserOut:
pass
Empty file.
Empty file.
3 changes: 3 additions & 0 deletions nmdc_runtime/infrastructure/database/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Database initialization
"""
Empty file.
19 changes: 19 additions & 0 deletions nmdc_runtime/infrastructure/database/impl/mongo/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""
Database initialization
"""
import os

from beanie import init_beanie
from motor.motor_asyncio import AsyncIOMotorClient

from nmdc_runtime.infrastructure.database.impl.mongo.models import User


async def mongo_init(app):
"""Initialize database service"""
app.db = AsyncIOMotorClient(
host=os.getenv("MONGO_HOST"),
username=os.getenv("MONGO_USERNAME"),
password=os.getenv("MONGO_DBNAME"),
).account
await init_beanie(app.db, document_models=[User])
Empty file.
57 changes: 57 additions & 0 deletions nmdc_runtime/infrastructure/database/impl/mongo/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""
User models
"""
from typing import Optional, List

from beanie import Document, Indexed
from pydantic import EmailStr

from nmdc_runtime.api.core.auth import verify_password
from nmdc_runtime.domain.users.userSchema import UserAuth, UserUpdate, UserOut
from nmdc_runtime.domain.users.queriesInterface import IUserQueries


# User database representation
class User(Document):
class DocumentMeta:
collection_name = "users"

username: Indexed(str, unique=True)
email: Indexed(EmailStr, unique=True)
full_name: Optional[str] = None
site_admin: Optional[List[str]] = []
disabled: Optional[bool] = False

class Config:
schema_extra = {
"username": "bob",
"email": "[email protected]",
"full_name": "test",
"password": "test",
"site_admin": ["test_site"],
"created_date": "1/1/2020",
}


class UserQueries(IUserQueries):
"""Implementation of the User query interface"""

async def create(self, user: UserAuth) -> UserOut:

auth_user = await User.get(user.username)
if not auth_user:
auth_user = User(
username=user.username,
email=user.email,
full_name=user.full_name,
site_admin=user.site_admin,
password=user.password,
)
await auth_user.insert()

if not verify_password(user.password, auth_user.password):
return False
return UserOut(auth_user)

async def update(self, user: UserUpdate) -> UserOut:
pass
Empty file.
10 changes: 10 additions & 0 deletions nmdc_runtime/infrastructure/database/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

from abc import abstractmethod
from typing import List

from nmdc_runtime.domain.users.userSchema import (
UserAuth,
UserUpdate,
UserOut,
)
Empty file added nmdc_runtime/main.py
Empty file.
3 changes: 3 additions & 0 deletions requirements/dev.in
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
-c main.txt
black
coverage
flake8
invoke
pytest
pytest-asyncio
pytest-cov
setuptools-scm
twine
Loading

0 comments on commit dcb77b2

Please sign in to comment.