-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from pbs-data-solutions/db
Add user model and db connection
- Loading branch information
Showing
17 changed files
with
260 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
github: [sanders41] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,23 @@ | ||
from __future__ import annotations | ||
|
||
import logging | ||
from typing import Annotated | ||
|
||
from fastapi import Depends | ||
from motor.motor_asyncio import AsyncIOMotorClient | ||
|
||
from open_edc.core.config import config | ||
from open_edc.db import db_client | ||
|
||
logging.basicConfig(format="%asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s") | ||
logging.root.setLevel(level=config.log_level) | ||
logger = logging | ||
|
||
|
||
# motor 3.3.0 broke types see: https://www.mongodb.com/community/forums/t/motor-3-3-0-released/241116 | ||
# and https://jira.mongodb.org/browse/MOTOR-1177 | ||
def get_db_client() -> AsyncIOMotorClient: # type: ignore | ||
return db_client | ||
|
||
|
||
MongoClient = Annotated[AsyncIOMotorClient, Depends(get_db_client)] # type: ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,23 @@ | ||
from typing import Dict | ||
from __future__ import annotations | ||
|
||
from open_edc.api.deps import MongoClient, logger | ||
from open_edc.core.config import config | ||
from open_edc.core.utils import APIRouter | ||
|
||
router = APIRouter(tags=["Health"], prefix=config.V1_API_PREFIX) | ||
|
||
|
||
@router.get("/health", include_in_schema=False) | ||
async def health() -> Dict[str, str]: | ||
return {"system": "healthy"} | ||
async def health(mongo_client: MongoClient) -> dict[str, str]: | ||
status = {"system": "healthy"} | ||
logger.info("Checking MongoDb health") | ||
try: | ||
# motor 3.3.0 broke types see: https://www.mongodb.com/community/forums/t/motor-3-3-0-released/241116 | ||
# and https://jira.mongodb.org/browse/MOTOR-1177 | ||
await mongo_client.server_info() # type: ignore[attr-defined] | ||
status["db"] = "healthy" | ||
except Exception as e: | ||
logger.error("%s: %s", type(e).__name__, e) | ||
status["db"] = "unhealthy" | ||
|
||
return status |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from __future__ import annotations | ||
|
||
from beanie import init_beanie | ||
from motor.motor_asyncio import AsyncIOMotorClient | ||
|
||
from open_edc.core.config import config | ||
from open_edc.models.user import User | ||
|
||
db_client = AsyncIOMotorClient( # type: ignore | ||
host=config.mongo_host, | ||
username=config.mongo_initdb_root_username, | ||
password=config.mongo_initdb_root_password, | ||
port=config.mongo_port, | ||
) | ||
|
||
|
||
async def init_db() -> None: | ||
await init_beanie(database=db_client.db_name, document_models=[User]) # type: ignore[arg-type] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Annotated, Any, Callable | ||
|
||
from bson import ObjectId | ||
from pydantic import GetJsonSchemaHandler | ||
from pydantic.json_schema import JsonSchemaValue | ||
from pydantic_core import core_schema | ||
|
||
|
||
# Based on https://docs.pydantic.dev/latest/usage/types/custom/#handling-third-party-types | ||
class _ObjectIdPydanticAnnotation: | ||
@classmethod | ||
def __get_pydantic_core_schema__( | ||
cls, | ||
_source_type: Any, | ||
_handler: Callable[[Any], core_schema.CoreSchema], | ||
) -> core_schema.CoreSchema: | ||
def validate_from_str(id_: str) -> ObjectId: | ||
return ObjectId(id_) | ||
|
||
from_str_schema = core_schema.chain_schema( | ||
[ | ||
core_schema.str_schema(), | ||
core_schema.no_info_plain_validator_function(validate_from_str), | ||
] | ||
) | ||
|
||
return core_schema.json_or_python_schema( | ||
json_schema=from_str_schema, | ||
python_schema=core_schema.union_schema( | ||
[ | ||
# check if it's an instance first before doing any further work | ||
core_schema.is_instance_schema(ObjectId), | ||
from_str_schema, | ||
] | ||
), | ||
serialization=core_schema.plain_serializer_function_ser_schema(lambda x: str(x)), | ||
) | ||
|
||
@classmethod | ||
def __get_pydantic_json_schema__( | ||
cls, _core_schema: core_schema.CoreSchema, handler: GetJsonSchemaHandler | ||
) -> JsonSchemaValue: | ||
# Use the same schema that would be used for `str` | ||
return handler(core_schema.str_schema()) | ||
|
||
|
||
ObjectIdStr = Annotated[ObjectId, _ObjectIdPydanticAnnotation] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from __future__ import annotations | ||
|
||
from datetime import datetime | ||
|
||
from beanie import Document | ||
from camel_converter.pydantic_base import CamelBase | ||
from pydantic import BaseModel, Field | ||
from pymongo import ASCENDING, IndexModel | ||
|
||
from open_edc.models.object_id import ObjectIdStr | ||
|
||
|
||
class PasswordReset(CamelBase): | ||
user_name: str | ||
security_question_answer: str | ||
new_password: str | ||
|
||
|
||
class UserCreate(CamelBase): | ||
user_name: str | ||
first_name: str | ||
last_name: str | ||
password: str | ||
security_question_answer: str | ||
|
||
|
||
class UserNoPassword(BaseModel): | ||
id: ObjectIdStr | ||
user_name: str | ||
first_name: str | ||
last_name: str | ||
country: str | None = None | ||
|
||
class Settings: | ||
projection = { | ||
"id": "$_id", | ||
"user_name": "$user_name", | ||
"first_name": "$first_name", | ||
"last_name": "$last_name", | ||
} | ||
|
||
|
||
class UserUpdateMe(CamelBase): | ||
id: ObjectIdStr | ||
password: str | ||
user_name: str | ||
first_name: str | ||
last_name: str | ||
security_question_answer: str | ||
country: str | None = None | ||
|
||
|
||
class UserUpdate(UserUpdateMe): | ||
is_active: bool | ||
is_admin: bool | ||
|
||
|
||
class User(Document): | ||
user_name: str | ||
first_name: str | ||
last_name: str | ||
hashed_password: str | ||
security_question_answer: str | ||
is_active: bool = True | ||
is_admin: bool = False | ||
date_created: datetime = Field(default_factory=datetime.now) | ||
last_update: datetime = Field(default_factory=datetime.now) | ||
last_login: datetime = Field(default_factory=datetime.now) | ||
|
||
class Settings: | ||
name = "users" | ||
indexes = [ | ||
IndexModel(keys=[("user_name", ASCENDING)], name="user_name", unique=True), | ||
IndexModel(keys=[("is_active", ASCENDING)], name="is_active"), | ||
IndexModel(keys=[("is_admin", ASCENDING)], name="is_admin"), | ||
] |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,41 @@ | ||
import pytest | ||
from httpx import AsyncClient | ||
from motor.motor_asyncio import AsyncIOMotorClient | ||
|
||
|
||
@pytest.fixture | ||
async def test_client_bad_db(): | ||
from open_edc.api.deps import get_db_client | ||
from open_edc.core.config import config | ||
from open_edc.main import app | ||
|
||
def get_db_client_override(): | ||
return AsyncIOMotorClient( | ||
host="bad", | ||
username=config.mongo_initdb_root_username, | ||
password=config.mongo_initdb_root_password, | ||
port=config.mongo_port, | ||
serverSelectionTimeoutMS=100, | ||
) | ||
|
||
app.dependency_overrides[get_db_client] = get_db_client_override | ||
|
||
async with AsyncClient(app=app, base_url=f"http://127.0.0.1{config.V1_API_PREFIX}") as client: | ||
yield client | ||
|
||
app.dependency_overrides = {} | ||
|
||
|
||
async def test_health(test_client): | ||
result = await test_client.get("health") | ||
|
||
assert result.status_code == 200 | ||
assert result.json()["system"] == "healthy" | ||
assert result.json()["db"] == "healthy" | ||
|
||
|
||
async def test_health_unhealthy_db(test_client_bad_db): | ||
result = await test_client_bad_db.get("health") | ||
|
||
assert result.status_code == 200 | ||
assert result.json()["db"] == "unhealthy" |