FastAPI RuntimeError: Use params or add_pagination - fastapi

I'm writing my second project on FastAPI. And I got this error.
For example I have this code in my routers.users.py:
#router.get('/', response_model=Page[Users])
async def get_all_users(db: Session = Depends(get_db)):
return paginate(db.query(models.User).order_by(models.User.id))
And it works. It has fields limit and page in swagger documentation.
I tried to write the same for routers.recipes.py, but in this case I have no fields for pagination(limit, page) in swagger. Ok, I googled and found out that adding dependencies could help me. And now I see pagination parameters in swagger, but error is still the same.
routers.recipes:
#router.get('/', response_model=Page[PostRecipes], dependencies=[Depends(Params)])
async def get_all_recipes(db: Session = Depends(get_db)):
return paginate(db.query(models.Recipe).order_by(models.Recipe.id))
pagination:
class Params(BaseModel, AbstractParams):
page: int = Query(1, ge=1, description="Page number")
limit: int = Query(50, ge=1, le=100, description="Page size")
def to_raw_params(self) -> RawParams:
return RawParams(
limit=self.limit,
offset=self.limit * (self.page - 1),
)
class Page(BasePage[T], Generic[T]):
page: conint(ge=1) # type: ignore
limit: conint(ge=1) # type: ignore
__params_type__ = Params
#classmethod
def create(
cls,
items: Sequence[T],
total: int,
params: AbstractParams,
) -> Page[T]:
if not isinstance(params, Params):
raise ValueError("Page should be used with Params")
return cls(
total=total,
items=items,
page=params.page,
limit=params.limit,
)
__all__ = [
"Params",
"Page",
]
So, does anyone have ideas about it?

according to doc you have to specify default parameters,
your code should look like paginate(iterable, params)

Related

phonenumbers.PhoneNumber as a FastAPI response_model field

FastAPI supports having some (predefined) classes as pydantic model fields and have them be converted to JSON. For example datetime:
class MyModel(pydantic.BaseModel):
created_at: datetime.datetime
When used this model would convert datetime to/from str in the output/input JSON, when used as a response model or request body model, respectively.
I would like to have similar type safety for my own classes:
class MyModel(pydantic.BaseModel):
phone_number: phonenumbers.PhoneNumber
This can be made to work for request body models by using a custom validator but I also need MyModel to be convertible to JSON. Is this possible to achieve today? Note that I don't control the PhoneNumber class so the solution can't involve modifying that class.
Edit: the best I've come up with but still doesn't work:
def phone_number_validator(value: str) -> phonenumbers.PhoneNumber:
...
class MyModel(pydantic.BaseModel):
phone_number: phonenumbers.PhoneNumber
_validate_phone_number = pydantic.validator(
'phone_number', pre=True, allow_reuse=True)(phone_number_validator)
class Config:
arbitrary_types_allowed = True
json_encoders = {
phonenumbers.PhoneNumber: lambda p: phonenumbers.format_number(
p, phonenumbers.PhoneNumberFormat.E164),
}
This fails in FastAPI with:
fastapi.exceptions.FastAPIError: Invalid args for response field! Hint: check that <class 'phonenumbers.phonenumber.PhoneNumber'> is a valid pydantic field type
As you have already noticed, this is a bug in FastAPI. I just created a PR to fix it.
The arbitrary_types_allowed config directive is lost during the processing of the response model.
Until the PR is merged, you can use the workaround of monkey-patching the Pydantic BaseConfig like this:
from pydantic import BaseConfig
...
BaseConfig.arbitrary_types_allowed = True
# Your routes here:
...
But keep in mind that independent from this bug you might also need to adjust the JSON schema for the custom type, if you want the OpenAPI docs to work properly. Arbitrary types are not generally supported by the BaseModel.schema() method.
For that you can probably just inherit from phonenumbers.PhoneNumber and set a proper __modify_schema__ classmethod. See here for an example. Though I have not looked thoroughly into phonenumbers.
Check the example code in my PR text, if you want to see how you could implement validation and schema modification on your PhoneNumber subclass.
PS
Here is a full working example:
from __future__ import annotations
from typing import Union
from fastapi import FastAPI
from phonenumbers import PhoneNumber as _PhoneNumber
from phonenumbers import NumberParseException, PhoneNumberFormat
from phonenumbers import format_number, is_possible_number, parse
from pydantic import BaseModel, BaseConfig
class PhoneNumber(_PhoneNumber):
#classmethod
def __get_validators__(cls):
yield cls.validate
#classmethod
def validate(cls, v: Union[str, PhoneNumber]) -> PhoneNumber:
if isinstance(v, PhoneNumber):
return v
try:
number = parse(v, None)
except NumberParseException as ex:
raise ValueError(f'Invalid phone number: {v}') from ex
if not is_possible_number(number):
raise ValueError(f'Invalid phone number: {v}')
return number
#classmethod
def __modify_schema__(cls, field_schema: dict) -> None:
field_schema.update(
type="string",
# pattern='^SOMEPATTERN?$',
examples=["+49123456789"],
)
def json_encode(self) -> str:
return format_number(self, PhoneNumberFormat.E164)
class MyModel(BaseModel):
phone_number: PhoneNumber
class Config:
arbitrary_types_allowed = True
json_encoders = {
PhoneNumber: PhoneNumber.json_encode,
}
test_number = PhoneNumber(
country_code=49,
national_number=123456789
)
# Test:
obj = MyModel(phone_number=test_number)
obj_json = obj.json()
parsed_obj = MyModel.parse_raw(obj_json)
assert obj == parsed_obj
BaseConfig.arbitrary_types_allowed = True
api = FastAPI()
#api.get("/model/", response_model=MyModel)
def example_route():
return MyModel(phone_number=test_number)

django rest framework post method not allowed

I am creating an api and no idea why post method not allowed on any url.
views
class MessagesView(APIView):
permission_classes = (IsAuthenticated,)
def post(self, request):
serializer = MessageSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
chat.urls
urlpatterns = [
path("<str:pk>/", ChatDetail.as_view()),
path("messages/", MessagesView.as_view()),
]
response
{
"detail": "Method \"POST\" not allowed."
}
I am providing the token for the request, so isAuthenticated does not do anything wrong here.
Your first pattern will fire if you visit messages/. Indeed, its <str:pk> parameter matches any string (with at least one character and without slashes). But messages is thus also matched with this view.
What you can do is swap the places of the two urls, then calling messages/ will fire the correct view:
urlpatterns = [
# &downarrow; messages first
path('messages/', MessagesView.as_view()),
path('<str:pk>/', ChatDetail.as_view()),
]
If pk is an integer, you can further restrict the pk with the <int:…> path converter:
urlpatterns = [
path('messages/', MessagesView.as_view()),
path('<int:pk>/', ChatDetail.as_view()),
]

Fastapi and Pydantic to build POST API: TypeError: Object of type is not JSON serializable

I have a problem with FastAPI and Pydantic.
I want to build a post api, program show this:
#router.post('/productRoute', response_model=SuccessCreate, status_code=status.HTTP_201_CREATED)
async def create_product_route(create: CreatePR):
query = ProductRouteModel.insert().values(
user_id=create.user_id,
route_id=create.route_id,
route_name=create.route_name,
head=create.head.dict(),
body=create.body,
route=create.route
)
await database.execute(query)
return {"status": "Successfully Created!"}
This is Pydantic class:
class RouteSchema(BaseModel):
id: str
next: Optional[List[str]]
prev: List[str]
class HeadSchema(BaseModel):
b1: str
b2: str
b3: str
class BodySchema(BaseModel):
a1: Optional[str]
a2: Optional[str]
class CreatePR(BaseModel):
user_id: str
route_id: str
route_name: str
head: HeadSchema
body: List[BodySchema]
route: List[RouteSchema]
Finally, this i want to post's parameter format:
{
user_id: "test1",
route_id: "route_1",
route_name: "route_name",
head: {...},
body: [{...}, {...}, ..., {...}],
route: [{...}, {...}, ..., {...}]
}
When I executed, I got TypeError: Object of type BodySchema is not JSON serializable.
How can I fix the program to normal operation?
Your code seems OK. I would not make a strong statement, but I suppose your post body is erroneous. Could you please verify whether your JSON format is correct or not. You could check it by using an online JSON editor (ex: https://jsonbeautifier.org/). Probable errors could be the usage of single quotes, missing/extra commas or even perhaps you forgot to put any quotes on your keys.

missing method exceptions with Groovy

I am new to groovy. I have a code like this.
String flavor
HashMap config = new HashMap([ ttl: 0, url: url, appName: appName, enable: true ])
client.put("${data}.json", config)
From this client Map I need to iterate the values of appName and enable.
For that I used get method... I am not sure about this.
def values = client.get("${data}.json");
while using this get method am getting following error. Since I am new to groovy i don't know what is happening here
groovy.lang.MissingMethodException: No signature of method: com.comcast.csv.haxor.SecureFirebaseRestClient.get() is applicable for argument types: (org.codehaus.groovy.runtime.GStringImpl) values: [testJson.json]
Possible solutions: get(com.comcast.tvx.megahttp.utils.URL, java.lang.Class), get(java.lang.String, java.lang.Class), grep(), grep(java.lang.Object), getAt(java.lang.String), wait()
not sure what you are trying to do, but (without knowing other details) I'd put your code that way:
Map config = [ ttl: 0, url: url, appName: appName, enable: true ]
client[ "${data}.json" ] = config
def values = client[ "${data}.json" ]
assuming, that you wanted to use getAt() (short-cut with [] ) method instead of get()
Try this:
def config = [ ttl: 0, url: url, appName: appName, enable: true ]
def endpoint = "${data}.json" as String
client.put(endpoint, config)
def values = client.get(endpoint, HashMap)
def appName = values.appName
def enable = values.enable
I couldn't find any info on SecureFirebaseRestClient, so I'm guessing about how it works.

ngMock fails to recognize request

I've got the following specs wrapped around a controller. I've got the following Jasmine Spec:
describe 'MyApp.Controller.SomeController', ->
beforeEach module('mymodule')
beforeEach inject ($rootScope , $httpBackend, $controller, SomeService) ->
#scope = $rootScope.$new()
#httpBackend = $httpBackend
someService = SomeService
#someResource = someService.someResource
$controller 'MyApp.Controller.SomeController', $scope: #scope
describe "#fetchMethod", ->
describe "given an object", ->
beforeEach ->
#id = 17
#scope.fetchMethod(#id)
it "sets due to true", ->
#httpBackend.whenGET().respond(200, {"someStrings": ["foo", "bar"], otherStrings: ["bar", "goo"]})
expect(#scope.someStrings).toBeDefined()
expect(#scope.otherStrings).toBeDefined()
Wrapped around the following Controller:
MyApp.Controller.SomeController = (scope, someService) ->
scope.fetchMethod = (ID)->
someService.someResource.fetch
Id: artID
,(response) ->
scope.someStrings = response['someStrings']
scope.otherStrings = response['otherStrings']
scope.someStringsExist = true if scope.someStrings
MyApp.Controller.SomeController.$inject = ['$scope', 'SomeService']
Where SomeService is defined as follows:
MyApp.Service.SomeService = (resource) ->
#someResource = resource '/api/foos/:ID', {},
fetch:
method: 'GET'
#
MyApp.myModule.service 'SomeService', ['$resource', MyApp.Service.SomeService]
This setup appears to function on the site, correctly executing the request and returning values from the (rails) api endpoint.
However, when the jasmine specs are run it fails with:
Error: Unexpected request: GET /api/foos/17 No more request expected in http://localhost:3000/assets/helpers/angular-mocks.js?body=1 (line 889)
What am I missing? Why is httpBackend failing to recognize the GET request?
scope.initialize = (artifactId, artifactType)->
scope.artifactId = artifactId
scope.artifactType = artifactType
scope.currentVersionExists = false
scope.attachmentFetcher(scope.artifactId)
MyApp.Controller.SomeController.$inject = ['$scope', 'SomeService']
This line where you stub the response should go before you make the request:
#httpBackend.whenGET().respond(200, {"someStrings": ["foo", "bar"], otherStrings: ["bar", "goo"]})

Resources