Code of the Programm:
from fastapi import FastAPI
from pydantic import BaseModel
import os
from dotenv import load_dotenv
from neo4j import GraphDatabase
load_dotenv()
uri=os.getenv("uri")
user=os.getenv("user")
pwd=os.getenv("pwd")
class nodemodel(BaseModel):
labels:str
attribute:str
name:str
class database(BaseModel):
db:str
def connection():
driver=GraphDatabase.driver(uri=uri,auth=(user,pwd))
return driver
app=FastAPI()
#app.post("/listdb")
def selectdb():
driver_neo4j=connection()
session=driver_neo4j.session()
query = """
SHOW DATABASES
"""
resultquery = session.run(query)
resultprint = []
for record in resultquery:
resultprint.append(record)
return {"response: " + str(resultprint)}
Response Body in my API:
[
"response: [<Record **name='neo4j'** type='standard' aliases=[] access='read-write' address='localhost:7687' role='primary' writer=True requestedStatus='online' currentStatus='online' statusMessage='' default=True home=True constituents=[]>, <Record **name='neo4jtest'** type='standard' aliases=[] access='read-write' address='localhost:7687' role='primary' writer=True requestedStatus='online' currentStatus='online' statusMessage='' default=False home=False constituents=[]>, <Record **name='system'** type='system' aliases=[] access='read-write' address='localhost:7687' role='primary' writer=True requestedStatus='online' currentStatus='online' statusMessage='' default=False home=False constituents=[]>]"
]
What is the most efficient way to filter the reponse, so that is just get the Bold paramaters with the name?
I tried using dict, but the Sequence is too long.
Related
I am playing around with using pydantic/SQLModels to specify query parameters for FastAPI endpoints. The model would have all optional types, and then get passed the endpoint using query_params:QuerySchema=Depends(QuerySchema). This works fine but I am having issues distinguishing between values that are unset and values that the user has explicitly set to None as to query for Null values.
I have tried using .dict(exclude_unset==True) model but it seems FastAPI is doing some magic behind the scenes to set optional params to None which means exclude_unset doesn't exclude anything from the returned dictionary.
Anyone have and ideas on how to approach this? I've include an example of what I'm trying below.
crud/base.py
import logging
from datetime import datetime
from typing import Any, Callable, Generic, Optional, TypeVar, Union
from uuid import UUID
from app.models.base_models import _SQLModel
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel
from sqlmodel import SQLModel, select
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel.sql.expression import Select
ModelType = TypeVar("ModelType", bound=_SQLModel)
CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
# SchemaType = TypeVar("SchemaType", bound=BaseModel)
T = TypeVar("T", bound=SQLModel)
IdType = Union[str, UUID, int]
class SessionNotFoundException(BaseException):
...
class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
def __init__(
self,
model: type[ModelType],
session_factory: Optional[Callable[[], AsyncSession]] = None,
) -> None:
self.model: type[ModelType] = model
self.session_factory = session_factory
self._logger = logging.getLogger(str(self.model.__tablename__))
#property
def _local_session(self) -> AsyncSession:
if not self.session_factory:
raise SessionNotFoundException(
"No session provided or session_factory initialized."
)
return self.session_factory()
### OMITTED FOR LENGTH
async def get_multi(
self,
*,
offset: int = 0,
limit: Optional[int] = 100,
query: Optional[Select[T]] = None,
db_session: Optional[AsyncSession] = None,
) -> list[ModelType]:
session = db_session or self._local_session
statement = query if query is not None else select(self.model)
statement = (
statement.offset(offset).limit(limit).order_by(self.model.created_at)
)
response = await session.execute(statement)
return response.scalars().all()
### OMITTED FOR LENGTH
Usage example. This doesn't work. I think because FastAPI is setting unset optional params to none, so the exclude unset option doesn't exclude anything.
crud/sports_crud.py
from typing import Optional, Union
from app.models import Sport
from app.schemas.sports import ISportCreate, ISportUpdate
from sqlmodel import select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.db.session import SessionLocal
from .base import CRUDBase, IdType
class CRUDSports(CRUDBase[Sport, ISportCreate, ISportUpdate]):
...
sports = CRUDSports(Sport, session_factory=SessionLocal)
router
#sports_router_v1.get("", response_model=list[ISportRead])
async def get_sports(
*,
query_params: ISportQuery = Depends(ISportQuery),
page_params: PageParams = Depends(PageParams),
) -> list[Sport]:
# Get explicitly set query params
query = query_params.dict(exclude_unset=True) # debugger shows this includes all keys with None values
# build select query
stmt = select(Sport)
if query != {}:
for k, v in query.items():
stmt = stmt.where(getattr(Sport, k) == v)
sports = await crud.sports.get_multi(query=stmt, **page_params.dict())
return sports
schemas/sports.py
def optional(*fields):
# https://github.com/pydantic/pydantic/issues/1223
# https://github.com/pydantic/pydantic/pull/3179
def dec(_cls):
for field in fields:
_cls.__fields__[field].required = False
return _cls
if fields and inspect.isclass(fields[0]) and issubclass(fields[0], BaseModel):
cls = fields[0]
fields = cls.__fields__
return dec(cls)
return dec
class ISportRead(SportBase):
id: UUID
#optional
class ISportQuery(ISportRead):
...
I have updated the Django version for my project from Django-2.2.16 --> Django3.2.14.
But with this update, some of my test cases are failing and I cannot understand the reason for the failure.
My test-case file:
import json
from os import access
from unittest import mock
from unittest.mock import patch
import asyncio
from app.models import UserProfile
from django.test import TestCase, TransactionTestCase
from requests.models import Response
from services.check_status import check_status
loop = asyncio.get_event_loop()
#mock.patch("services.check_status.save_status")
#mock.patch("services..check_status.send_wss_message")
class TestUserStatus(TransactionTestCase):
def setUp(self):
super().setUp()
self.account_id = 4
self.sim_profile = UserProfile.objects.create()
def test_check_status_completed(
self,
mock_send_wss_message,
mock_save_status,
):
mock_save_status.return_value = {}
send_wss_message_future = asyncio.Future()
send_wss_message_future.set_result(True)
mock_send_wss_message.return_value = send_wss_message_future
loop.run_until_complete(
check_status(
self.sim_profile.id,
)
)
self.assertTrue(mock_save_status.called)
self.assertTrue(mock_send_wss_message.called)
My pseudo check_status file is :-
import logging
from app.models import UserProfile, UserStatus
from services.constants import WebsocketGroups
from services.user.constants import USER
from app.api.serializers import UserStatusSerializer
from services.utils import send_wss_message, Client
logger = logging.getLogger(__name__)
def save_status(**kwargs):
status = UserStatus.objects.filter(
status_id=kwargs.get("status_id")
).first()
data = kwargs
user_status_serializer = UserStatusSerializer(status, data, partial=True)
if user_status_serializer.is_valid():
user_status_serializer.save()
async def check_status(
profile_id
):
user_profile = UserProfile.objects.get(id=profile_id)
login_token = get_login_token(user_profile)
user_creds = env["user_api"]
headers = USER["headers"]
subscription_details = Client.get(
USER["url"], headers
)
transaction_status = subscription_details.json()["Status"]
subscription_data = subscription_details.json()["Data"][0]
transaction_status_details = subscription_data["TransactionStatusDetails"]
error_message = ""
status = ""
if transaction_status == "Success":
#perform some actions and save status...
message = {
"type": "user_profile",
"data": [user_profile.id, transaction_status, {"results": {}},],
}
await send_wss_message(
user_profile.id, message=message, group_name=WebsocketGroups.USER_PROFILE,
)
else:
#perform some actions ...
When I am running my test-case file it's creating the UserProfile object but when control goes to the check_status function in int UserProfile.objects.all returns <QuerySet []>.
I made a temporary sync function to return a list of all user profiles and called it inside my test_check_status_completed and it returned the list. But for async functions that are called through the loop.run_until_complete, they all returned <QuerySet []>.
So I have a simple fastapi model as follows:
from typing import List
from fastapi import Query, Depends, FastAPI
from pydantic import BaseModel
class QueryParams(BaseModel):
req1: float = Query(...)
opt1: int = Query(None)
req_list: List[str] = Query(...)
app = FastAPI()
#app.post("/test", response_model=QueryParams)
def foo(q: QueryParams = Depends()):
return q
which works with the following command: curl -X "POST" "http://localhost:8000/test?req1=1" -d '{["foo"]}'
However! I need it to work additionally with allowing the params in the uri request as such:
curl -X "POST" "http://localhost:8000/test?req1=1&req_list=foo"
I know that if I took out the req_list from the BaseModel, and shoved it in the function header as such
from typing import List
from fastapi import Query, Depends, FastAPI
from pydantic import BaseModel
class QueryParams(BaseModel):
req1: float = Query(...)
opt1: int = Query(None)
app = FastAPI()
#app.post("/test", response_model=QueryParams)
def foo(q: QueryParams = Depends(), req_list: List[str] = Query(...)):
return q
that it would work, but is there any way to keep it in the basemodel?
Well I figured it out:
from typing import List
from fastapi import Query, Depends, FastAPI
from pydantic.dataclasses import dataclass
#dataclass
class QueryParams:
req1: float = Query(...)
opt1: int = Query(None)
req_list: List[str] = Query(...)
app = FastAPI()
#app.post("/test", response_model=QueryParams)
def foo(q: QueryParams = Depends()):
return q
I am writing a Fast API server that accepts requests, checks if users are authorized and then redirects them to another URL if successful.
I need to carry over URL parameters, e.g. http://localhost:80/data/?param1=val1¶m2=val2 should redirect to
http://some.other.api/?param1=val1¶m2=val2, thus keeping previously allotted parameters.
The parameters are not controlled by me and could change at any moment.
How can I achieve this?
Code:
from fastapi import FastAPI
from starlette.responses import RedirectResponse
app = FastAPI()
#app.get("/data/")
async def api_data():
params = '' # I need this value
url = f'http://some.other.api/{params}'
response = RedirectResponse(url=url)
return response
In the docs they talk about using the Request directly, which then lead me to this:
from fastapi import FastAPI, Request
from starlette.responses import RedirectResponse
app = FastAPI()
#app.get("/data/")
async def api_data(request: Request):
params = request.query_params
url = f'http://some.other.api/?{params}'
response = RedirectResponse(url=url)
return response
If the query parameters are known when starting the API but you still wish to have them dynamically set:
from fastapi import FastAPI, Depends
from pydantic import create_model
app = FastAPI()
# Put your query arguments in this dict
query_params = {"name": (str, "me")}
query_model = create_model("Query", **query_params) # This is subclass of pydantic BaseModel
# Create a route
#app.get("/items")
async def get_items(params: query_model = Depends()):
params_as_dict = params.dict()
...
This has the benefit that you see the parameters in the automatic documentation:
But you are still able to define them dynamically (when starting the API).
Note: if your model has dicts, lists or other BaseModels as field types, the request body pops up. GET should not have body content so you might want to avoid those types.
See more about dynamic model creation from Pydantic documentation.
As mention in docs of FastAPI https://fastapi.tiangolo.com/tutorial/query-params-str-validations/.
#app.get("/")
def read_root(param1: Optional[str] = None, param2: Optional[str] = None):
url = f'http://some.other.api/{param1}/{param2}'
return {'url': str(url)}
output
I use a combination of Depends, BaseModel and the Request object itself.
Here's an example for a HTTP request like localhost:5000/api?requiredParam1=value1&optionalParam2=value2&dynamicParam1=value3&dynamicParam2=value4
# imports
from typing import Union
from pydantic import BaseModel
from fastapi import Depends, Request
# the base model
class QueryParams(BaseModel):
required: str
optional: Union[None, str] = None
dynamic: dict
# dependency
async def query_params(
request: Request, requiredParam1: str, optionalParam1: Union[None, str] = None
):
# process the request here
dynamicParams = {}
for k in request.query_params.keys():
if 'dynamicParam' not in k:
continue
dynamicParams[k] = request.query_params[k]
# also maybe do some other things on the arguments
# ...
return {
'required': requiredParam1,
'optional': optionalParam1,
'dynamic': dynamicParams
}
# the endpoint
#app.get("api/")
async def hello(params: QueryParams = Depends(query_params)):
# Maybe do domething with params here,
# Use it as you would any BaseModel object
# ...
return params
Refer the Starlette documentation on how to use the request object: https://www.starlette.io/requests/
Note that you can put query_params in a different module, and need not add any more code to explicitly pass the Request object. FastAPI already does that when you make a call to the endpoint :)
This is a code I derived from #Hajar Razip using a more pydantic like approach:
from pydantic import (
BaseModel,
)
from typing import (
Dict,
List,
Optional,
)
from fastapi import (
Depends,
FastAPI,
Query,
Request,
)
class QueryParameters(BaseModel):
"""Model for query parameter."""
fixId: Optional[str]
fixStr: Optional[str]
fixList: Optional[List[str]]
fixBool: Optional[bool]
dynFields: Dict
_aliases: Dict[str,str] = {"id": "fixId"}
#classmethod
def parser(
cls,
request: Request,
fixId: Optional[str] = Query(None, alias="id"),
fixStr: Optional[str] = Query(None),
fixList: Optional[List[str]] = Query(None),
fixBool: bool = Query(True),
) -> Dict:
"""Parse query string parameters."""
dynFields = {}
reserved_keys = cls.__fields__
query_keys = request.query_params
for key in query_keys:
key = cls._aliases.get(key, key)
if key in reserved_keys:
continue
dynFields[key] = request.query_params[key]
return {
"fixId": fixId,
"fixStr": fixStr,
"fixList": fixList,
"fixBool": fixBool,
"dynFields": dynFields
}
app = FastAPI()
#app.get("/msg")
def get_msg(
parameters: QueryParameters = Depends(
QueryParameters.parser,
),
) -> None:
return parameters
The output documentation is then
Here it is the result of calling GET /msg
> curl -s -X 'GET' 'http://127.0.0.1:8000/msg?id=Victor&fixStr=hi&fixList=eggs&fixList=milk&fixList=oranges&fixBool=true' -H 'accept: application/json' | python3 -m json.tool
{
"fixId": "Victor",
"fixStr": "hi",
"fixList": [
"eggs",
"milk",
"oranges"
],
"fixBool": true,
"dynFields": {}
}
Here it is the GET /msg call using dynamic fields
> curl -s -X 'GET' 'http://127.0.0.1:8000/msg?id=Victor&fixStr=hi&fixList=eggs&fixList=milk&fixList=oranges&fixBool=true&key1=value1&key2=value2' -H 'accept: application/json' | python3 -m json.tool
{
"fixId": "Victor",
"fixStr": "hi",
"fixList": [
"eggs",
"milk",
"oranges"
],
"fixBool": true,
"dynFields": {
"key1": "value1",
"key2": "value2"
}
}
class MsgConnection(tornado.websocket.WebSocketHandler):
def on_message(self,message):
session_id = self.get_cookie('session')
session = get_session_in_flask(session_id)
How to code get_session_in_flask?
def get_session_in_flask(secret_key, cookie_str):
import hashlib
from itsdangerous import URLSafeTimedSerializer
from flask.sessions import TaggedJSONSerializer
salt = 'cookie-session'
serializer = TaggedJSONSerializer()
signer_kwargs = {
'key_derivation': 'hmac',
'digest_method': hashlib.sha1
}
s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
return s.loads(cookie_str)