I stuck in bunch of errors with dynamic filtering in SQLAlchemy,
I tried to make a Repository Pattern in FastAPI app.
from sqlalchemy.orm import Session
from . import models
class MessageRepository:
def __init__(self, db:Session):
self.db = db
def get_query(self, **filters):
return self.db.query(models.Messages).filter(getattr(models.Messages, attr) == value for attr, value in filters.items()).all()
def inbox(self, user):
return self.get_query(filters={'receiver': user})
then I tried to use class "MessageRepository" in my FastAPI app;
from fastapi import FastAPI, Response, status, HTTPException, Depends, APIRouter
from sqlalchemy.orm import Session
from typing import List
from .. import schemas
from ..data_access.database import get_db
from ..data_access.crud import MessageRepository
router = APIRouter(prefix='/msg', tags=['Messages'])
#router.get('/', response_model=list[schemas.MessageBack])
def get_inbox(db:Session=Depends(get_db), user="9123456789"):
msgs = MessageRepository(db).inbox(user)
return msgs
the actual error is:
sqlalchemy.exc.ArgumentError: SQL expression for WHERE/HAVING role expected, got <generator object MessageRepository.get_query.. at 0x00000229823675B0>.
in the meanwhile, when I'm trying this Repository without dynamic filtering in queries, everything works!
class MessageRepository:
def __init__(self, db:Session):
self.db = db
def get_all_msgs(self, user):
return self.db.query(models.Messages).filter(models.Messages.receiver == user).all()
#router.get('/', response_model=list[schemas.MessageBack])
def get_inbox(db:Session=Depends(get_db), user="9123456789"):
msgs = MessageRepository(db).get_all_msgs(user)
return msgs
How I can fix this?!
getattr(models.Messages, attr) == value for attr, value in filters.items() is a generator expression, and SQLAlchemy does not know how to use this in a filter method. The solution is to use the unpacking operator (*) to get the filter expressions from the generator expression. Like this:
self.db.query(models.Messages).filter(
*(getattr(models.Messages, attr) == value for attr, value in filters.items())
).all()
You can also use query.filter_by(**filters), since it does exacly what you wants and accepts the filters as keyword arguments. Then, you don’t need the getattr or the generator.
So I need to have some routes inside a class, but the route methods need to have the self attr (to access the class' attributes).
However, FastAPI then assumes self is its own required argument and puts it in as a query param
This is what I've got:
app = FastAPI()
class Foo:
def __init__(y: int):
self.x = y
#app.get("/somewhere")
def bar(self): return self.x
However, this returns 422 unless you go to /somewhere?self=something. The issue with this, is that self is then str, and thus useless.
I need some way that I can still access self without having it as a required argument.
This can be done by using an APIRouter's add_api_route method:
from fastapi import FastAPI, APIRouter
class Hello:
def __init__(self, name: str):
self.name = name
self.router = APIRouter()
self.router.add_api_route("/hello", self.hello, methods=["GET"])
def hello(self):
return {"Hello": self.name}
app = FastAPI()
hello = Hello("World")
app.include_router(hello.router)
Example:
$ curl 127.0.0.1:5000/hello
{"Hello":"World"}
add_api_route's second argument (endpoint) has type Callable[..., Any], so any callable should work (as long as FastAPI can find out how to parse its arguments HTTP request data). This callable is also known in the FastAPI docs as the path operation function (referred to as "POF" below).
Why decorating methods doesn't work
WARNING: Ignore the rest of this answer if you're not interested in a technical explanation of why the code in the OP's answer doesn't work
Decorating a method with #app.get and friends in the class body doesn't work because you'd be effectively passing Hello.hello, not hello.hello (a.k.a. self.hello) to add_api_route. Bound and unbound methods (a.k.a simply as "functions" since Python 3) have different signatures:
import inspect
inspect.signature(Hello.hello) # <Signature (self)>
inspect.signature(hello.hello) # <Signature ()>
FastAPI does a lot of magic to try to automatically parse the data in the HTTP request (body or query parameters) into the objects actually used by the POF.
By using an unbound method (=regular function) (Hello.hello) as the POF, FastAPI would either have to:
Make assumptions about the nature of the class that contains the route and generate self (a.k.a call Hello.__init__) on the fly. This would likely add a lot of complexity to FastAPI and is a use case that FastAPI devs (understandably) don't seem interested in supporting. It seems the recommended way of dealing with application/resource state is deferring the whole problem to an external dependency with Depends.
Somehow be able to generate a self object from the HTTP request data (usually JSON) sent by the caller. This is not technically feasible for anything other than strings or other builtins and therefore not really usable.
What happens in the OP's code is #2. FastAPI tries to parse the first argument of Hello.hello (=self, of type Hello) from the HTTP request query parameters, obviously fails and raises a RequestValidationError which is shown to the caller as an HTTP 422 response.
Parsing self from query parameters
Just to prove #2 above, here's a (useless) example of when FastAPI can actually "parse" self from the HTTP request:
(Disclaimer: Do not use the code below for any real application)
from fastapi import FastAPI
app = FastAPI()
class Hello(str):
#app.get("/hello")
def hello(self):
return {"Hello": self}
Example:
$ curl '127.0.0.1:5000/hello?self=World'
{"Hello":"World"}
For creating class-based views you can use #cbv decorator from fastapi-utils. The motivation of using it:
Stop repeating the same dependencies over and over in the signature of related endpoints.
Your sample could be rewritten like this:
from fastapi import Depends, FastAPI
from fastapi_utils.cbv import cbv
from fastapi_utils.inferring_router import InferringRouter
def get_x():
return 10
app = FastAPI()
router = InferringRouter() # Step 1: Create a router
#cbv(router) # Step 2: Create and decorate a class to hold the endpoints
class Foo:
# Step 3: Add dependencies as class attributes
x: int = Depends(get_x)
#router.get("/somewhere")
def bar(self) -> int:
# Step 4: Use `self.<dependency_name>` to access shared dependencies
return self.x
app.include_router(router)
I didn't like the standard way of doing this, so I wrote my own library. You can install it like this:
$ pip install cbfa
Here is an example of how to use it:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
from cbfa import ClassBased
app = FastAPI()
wrapper = ClassBased(app)
class Item(BaseModel):
name: str
price: float
is_offer: Optional[bool] = None
#wrapper('/item')
class Item:
def get(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
def post(item_id: int, item: Item):
return {"item_name": item.name, "item_id": item_id}
Note that you don't need to wrap decorators around each method. It is enough to name the methods according to their purpose in the HTTP protocol. The whole class is turned into a decorator.
I put routes to def __init__. It works normally.
Example:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
class CustomAPI(FastAPI):
def __init__(self, title: str = "CustomAPI") -> None:
super().__init__(title=title)
#self.get('/')
async def home():
"""
Home page
"""
return HTMLResponse("<h1>CustomAPI</h1><br/><a href='/docs'>Try api now!</a>", status_code=status.HTTP_200_OK)
I've just released a project that lets you use a class instance for route handling with simple decorators. cbv is cool but the routing is on the class itself, not instances of the class. Being able to use a class instance lets you do dependency injection in a way that feels simpler and more intuitive to me.
For example, the following works as expected:
from classy_fastapi import Routable, get, delete
class UserRoutes(Routable):
"""Inherits from Routable."""
# Note injection here by simply passing values
# to the constructor. Other injection frameworks also
# supported as there's nothing special about this __init__ method.
def __init__(self, dao: Dao) -> None:
"""Constructor. The Dao is injected here."""
super().__init__()
self.__dao = Dao
#get('/user/{name}')
def get_user_by_name(name: str) -> User:
# Use our injected DAO instance.
return self.__dao.get_user_by_name(name)
#delete('/user/{name}')
def delete_user(name: str) -> None:
self.__dao.delete(name)
def main():
args = parse_args()
# Configure the DAO per command line arguments
dao = Dao(args.url, args.user, args.password)
# Simple intuitive injection
user_routes = UserRoutes(dao)
app = FastAPI()
# router member inherited from Routable and configured per the annotations.
app.include_router(user_routes.router)
You can find it on PyPi and install via pip install classy-fastapi.
In this case I'm able to wire controller using python class and use a collaborator passing it by dep injection.
Here full example plus tests
class UseCase:
#abstractmethod
def run(self):
pass
class ProductionUseCase(UseCase):
def run(self):
return "Production Code"
class AppController:
def __init__(self, app: FastAPI, use_case: UseCase):
#app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {
"item_id": item_id, "q": q, "use_case": use_case.run()
}
def startup(use_case: UseCase = ProductionUseCase()):
app = FastAPI()
AppController(app, use_case)
return app
if __name__ == "__main__":
uvicorn.run(startup(), host="0.0.0.0", port=8080)
Another approach is to have a decorator class that takes parameters. The routes are registered before and added at run-time:
from functools import wraps
_api_routes_registry = []
class api_route(object):
def __init__(self, path, **kwargs):
self._path = path
self._kwargs = kwargs
def __call__(self, fn):
cls, method = fn.__repr__().split(" ")[1].split(".")
_api_routes_registry.append(
{
"fn": fn,
"path": self._path,
"kwargs": self._kwargs,
"cls": cls,
"method": method,
}
)
#wraps(fn)
def decorated(*args, **kwargs):
return fn(*args, **kwargs)
return decorated
#classmethod
def add_api_routes(cls, router):
for reg in _api_routes_registry:
if router.__class__.__name__ == reg["cls"]:
router.add_api_route(
path=reg["path"],
endpoint=getattr(router, reg["method"]),
**reg["kwargs"],
)
And define a custom router that inherits the APIRouter and add the routes at __init__:
class ItemRouter(APIRouter):
#api_route("/", description="this reads an item")
def read_item(a: str = "de"):
return [7262, 324323, a]
#api_route("/", methods=["POST"], description="add an item")
def post_item(a: str = "de"):
return a
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
add_api_routes(self)
app.include_router(
ItemRouter(
prefix="/items",
)
)
You inherit from FastAPI in your class and use the FastAPI decorators as method calls (I am going to show it using APIRouter, but your example should work anlog):
class Foo(FastAPI):
def __init__(y: int):
self.x = y
self.include_router(
health.router,
prefix="/api/v1/health",
)
I have a problem roughly looking like this:
In a file data.py I have
from typing import ClassVar
from tinydb import TinyDB
from dataclasses import dataclass
#dataclass
class Data:
db: ClassVar = TinyDB("some_path")
#property
def some_data(self):
return 100
I would like to mock the some_data method.
I tried:
import pytest
import pandas as pd
from package1.data import Data
#pytest.fixture
def mocked_raw_data(mocker):
m = mocker.patch.object(
Data, "some_data", return_value=10, new_callable=mocker.PropertyMock
)
)
return m
def test_some_data(mocked_raw_data):
assert Data().some_data == 2
But obviously this gives an error with the db method class variable. How can I mock this variable as well? Does my approach generally make sense?
Did you use #pytest.mark.django_db?
This would help in testing data on a separate DB rather than the production one.
And regarding your question on mocking, you can use monkey patch for mocking
For eg,
def test_user_details(monkeypatch):
mommy.make('Hallpass', user=user)
return_data =
{
'user_created':'done'
}
monkeypatch.setattr(
'user.create_user', lambda *args, **kwargs: return_data)
user_1 = create_user(user="+123456789")
assert user_1.return_data == return_data
I'm not sure what's the proper way to test functions which are used inside views/permission classes.
This is the payload of my request:
{"name": "John"}
And this is the function I want to test:
def get_name(request):
return request.data['name']
This is the view that will be using the function:
class SomeView(APIView):
def get(self, request):
name = get_name(request=request)
return Response(status=200)
How should I create a fixture to test the get_name function? I've tried this:
#pytest.fixture
def request_fixture()
factory = APIRequestFactory()
return factory.get(
path='',
data={"name": "John"},
format='json')
def test_get_name(request_fixture):
assert get_name(request=request_fixture) == "John"
But I'm getting an error:
AttributeError: 'WSGIRequest' object has no attribute data.
One workaround seems to be decoding the body attribute:
def get_name(request):
data = json.loads(request.body.decode('utf-8'))
return data['name']
But it doesn't feel like the right way to do this and I guess I'm missing something about the WSGIRequest class. Can someone explain to me how it should be tested? It would be great if I could use the same fixture to test the view too.
I don't think you need the test fixture. You aren't testing the whole view, just a helper function. You can make a request-like object very easily by adding a property to a lambda:
def test_get_name():
request = lambda: None
request.data = {"name": "John"}
assert get_name(request=request) == "John"
I'm trying to implement client for JSON API. I've worked with Django and i found the way it handles queries pretty interesting.
For example:
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='johndoe')
>>> user
<User: johndoe>
>>> user.objects.get(username='johndoe')
AttributeError: Manager isn't accessible via User instances
I'm interested what Manager exactly is and if there is a simple way to implement it. I tried to do something similar, but failed miserably. Here is my first attempt:
class Api(object):
def __init__(self, api_key):
self.api_key = key
#property
def player(self):
return Player(api=self)
class Player(object):
def __init__(self, api):
self.api = api
def get(self, id_):
'''
gets json and turns it into dictionary
'''
self.name = data['name']
self.id_ = id_
return self
So the usage would look like this:
>>> api = Api('api_key_here')
>>> player = api.player.get('player_id_here')
However to me it feels sketchy and simply wrong to do it this way(which is certainly true). Problems here are:
I have to send whole Api object to Player object
Player object will still have get() method
I don't know if using #property this way is acceptable
Here is how i would like to use my Api class:
api = Api('api_key_here')
player = api.player.get('player_id') #getting player object
item = api.item.get('item_id') #getting item object
recent_games = api.recent_games.filter(player=player, how_many=10) #getting list of game objects
I recommend you look at https://github.com/samgiles/slumber which implements exactly what you may be doing (a Rest API Client). But even if you are doing something else, slumber is implemented doing what you are trying to do. Something like:
class RestResource(object):
def __init__(self, *args, **kwargs):
self._store = kwargs
def __call__(self, id=None, action=None):
"""
Returns a new instance of self modified by one or more of the available
parameters. These allows us to do things like override format for a
specific request, and enables the api.resource(ID).get() syntax to get
a specific resource by it's ID.
"""
kwargs = {
'foo': self._store['name1'],
'bar': self._store['name2']
}
return self.__class__(**kwargs)
class Api(object):
resource_class = RestResource
def __getattr__(self, item):
kwargs = {
'name1': 'val1',
'name2': 'val2'
}
return self.resource_class(**kwargs)
and you can use like this
api = Api()
api.player.get()