I am trying to set Optional some params in a PUT method from my API.
Using fastAPI and mongodb I've build a simple API to insert students and delete the ones, now I am looking to allow me update the entries but not mandatory "params".
I've checked this Fastapi: put method and looks like something I am looking for mongodb.
And this response from art049 looks similar what I already have in my #api_router.put('/update-student/{id}', tags=['Student']) MongoDb with FastAPI
As example for my question here I have this structure:
Models:
class Student(BaseModel):
age:int
name:str
address:str
class UpdateStudent(BaseModel):
age: Optional[int] = None
name: Optional[str] = None
address: Optional[str] = None
Schemas:
def serializeDict(a) -> dict:
return {**{i:str(a[i]) for i in a if i=='_id'},**{i:a[i] for i in a if i!='_id'}}
def serializeList(entity) -> list:
return [serializeDict(a) for a in entity]
Routes:
#api_router.post('/create-student', tags=['Students'])
async def create_students(student: Student):
client.collegedb.students_collection.insert_one(dict(student))
return serializeList(client.collegedb.students_collection.find())
Also I know I can update the entry without problems in this way:
#api_router.put('/update-student/{id}', tags=['Student'])
async def update_student(id,ustudent: UpdateStudent):
client.collegedb.students_collection.find_one_and_update({"_id":ObjectId(id)},{
"$set":dict(ustudent)
})
return serializeDict(client.collegedb.students_collection.find_one({"_id":ObjectId(id)}))
My problem as you can see with my Models I need a way to validate which params are modified and update the ones only:
If right now I Update for example the age only; since the other params are not required, name and address will be stored as None (null actually) because I set this in my model.
Maybe I can do something like this:
if ustudent.age != None:
students_collection[ObjectId(id)] = ustudent.age
if ustudent.name != None:
students_collection[ObjectId(id)] = ustudent.name
if ustudent.address != None:
students_collection[ObjectId(id)] = ustudent.address
I know I can use this in a simple dictionary but never tried before in a collection in mongodb since pydantic not support ObjectId for iterations and that's why serializeDict was created.
I will really appreciate if somebody can give a hint with my concern
You can use exclude_unset=True argument as suggested in FastAPI documentation:
#api_router.put('/update-student/{id}', tags=['Student'])
async def update_student(id,ustudent: UpdateStudent):
client.collegedb.students_collection.find_one_and_update({"_id":ObjectId(id)},{
"$set":ustudent.dict(exclude_unset=True)
})
return serializeDict(client.collegedb.students_collection.find_one({"_id":ObjectId(id)}))
Here is the documentation for exporting Pydantic models.
Related
I am using these classes as part of a FastAPI response:
class SomeRules(str, Enum):
a_rule = "something"
b_rule = "something_else"
class RuleChooser(BaseModel):
rule: SomeRules = List[SomeRules]
In the JSON response, I want to get rule as:
{
rule: ["something", "something_else"]
}
How do I achieve this?
Right now I am getting the output as:
rule: type.literal["something", "something_else"]
or
literal.type["A", "B", "C"]
I have tested multiple versions and none of them return the desired result.
rules = Literal["A", "B", "C"]
rule: SomeRules = Somerules.rules
Any help will be appreciated.
I am using Pydantic version 1.9.1
The simplest, straightforward answer is to fix your definition for rule.
This
class RuleChooser(BaseModel):
rule: SomeRules = List[SomeRules]
says that rule is of type SomeRules and its value is a typing.List of SomeRules...which is definitely wrong because the value doesn't match the type. If you want rule to simply contain the string values of the enums, you can type it as List[str] then get all the values of the enums:
from pydantic import Field
class SomeRules(str, Enum):
a_rule = "something"
b_rule = "something_else"
class RuleChooser(BaseModel):
rule: List[str] = Field(default=[rule.value for rule in SomeRules])
#app.get("/")
async def root():
return RuleChooser()
$ curl http://127.0.0.1:8000
{"rule":["something","something_else"]}
Now, I'm guessing you are using the actual enum members in your app (not their string values), and you just want RuleChooser.rule to be strings only as part of the JSON response. Then, you need to again fix the definition of rule to:
from pydantic import Field
class RuleChooser(BaseModel):
rule: List[SomeRules] = Field(default=list(SomeRules))
which says that rule is of type typing.List of SomeRules, and its value are all the members of that Enum.
If you print an instance of RuleChooser().rule, you'll get:
[<SomeRules.a_rule: 'something'>, <SomeRules.b_rule: 'something_else'>]
But as part of the JSON response, FastAPI can handle the conversion from enum to string, and you'll get:
#app.get("/")
async def root():
return RuleChooser()
$ curl http://127.0.0.1:8000
{"rule":["something","something_else"]}
which is the same as what you wanted.
I have a function generating a dict template. This function consists of several generators and requires one parameter (i.e., carrier) and has many optional parameters (keyword arguments - **kwargs).
def main_builder(carrier, **params):
output = SamplerBuilder(DEFAULT_JSON)
output.generate_flight(carrier)
output.generate_airline_info(carrier)
output.generate_locations()
output.generate_passengers()
output.generate_contact_info()
output.generate_payment_card_info()
output.configs(**params)
result = output.input_json
return result
# example of function call
examplex = main_builder("3M", proxy="5.39.69.171:8888", card=Visa, passengers={"ADT":2, "CHD":1}, bags=2)
I want to deploy this function to FastAPI endpoint. I managed to do it for carrier but how can I set **kwargs as params to the function?
#app.get("/carrier/{carrier_code}", response_class=PrettyJSONResponse) # params/kwargs??
async def get_carrier(carrier_code):
output_json = main_builder(carrier_code)
return airline_input_json
Using Pydantic Model
Since your function "..has many optional parameters" and passengers parameter requires a dictionary as an input, I would suggest creating a Pydantic model, where you define the parameters, and which would allow you sending the data in JSON format and getting them automatically validated by Pydantci as well. Once the endpoint is called, you can use Pydantic's dict() method to convert the model into a dictionary.
Example
from pydantic import BaseModel
from typing import Optional
class MyModel(BaseModel):
proxy: Optional[str] = None
card: Optional[str] = None
passengers: Optional[dict] = None
bags: Optional[int] = None
#app.post("/carrier/{carrier_code}")
async def get_carrier(carrier_code: int, m: MyModel):
return main_builder(carrier_code, **m.dict())
Sending arbitrary JSON data
In case you had to send arbitrary JSON data, and hence, pre-defining the parameters of an endpoint wouldn't be possible, you could use an approach similar to the one described in this answer (see Options 3 and 4), as well as this answer and this answer.
I'm making a GraphQL api with strawberry using SQLModel as the database manager. I'm trying to get a list of items where a substring is in a string so I did this
def resolve_user(id: int):
with Session(engine) as session:
user = session.exec(
select(UserModel).where(UserModel.id == id)
).one()
return create_user_instance(user)
# I belive the error comes from here
def resolve_users_by_name(name: str):
with Session(engine) as session:
users = session.exec(
select(UserModel).where(name in UserModel.name)
).all()
return [create_user_instance(user) for user in users]
#strawberry.type
class Query:
user: User = strawberry.field(resolver=resolve_user)
users_by_name: list[User] = strawberry.field(
resolver=resolve_users_by_name
)
When calling the usersByName query I get the error Operator 'contains' is not supported on this expression. I'm guessing it has to do with the name in UserModel.name. I also looked if SQLModel had a LIKE operator similar to SQL but I couldn't find anything. How can I solve this?
The line that is having the issue is:
select(UserModel).where(name in UserModel.name)
Try changing it to this format:
select(UserModel).where(UserModel.name.contains(name))
Sqlalchemy's comparator class has a set of methods you have to leverage, instead of vanilla python.
(I think this is a related thread)
Is it possible to create a new instance of type T using generics in python?
I know how to do it in C# and have found plenty of examples but cant find anything for python.
For example:
public class Animal<T> where T : ISound, new(){
public T GetInstance()
{
return new T();
}}
Is there a python equivalent to the above C# snippet?
This is what I thought my python code needed to look like:
from typing import TypeVar, Generic
T = TypeVar('T'
class crud(Generic[T])
def create(self, endpoint: str, body, files=None) ->T:
url = self._build_url_str(endpoint)
res = self.http.post(url, json=body).json()
return T.__init__(res)
But I get TypeError: A single constraint is not allowed.
I google that and what I found didn't work or seemed irelevant.
Also, type T has what it needs to parse the response in the constructor so it has to be parametrized.
As stated in the answer here, you can do the following:
class Animal(Generic[T]):
def get_instance(self) -> T:
return self.__orig_class__.__args__[0]()
I am trying to create an audio server where I can upload various audio files, I have a requirement that I can only create one endpoint for creating, I have come up with this but it does show the request form to input data.
class AudioType(str, Enum):
Song = "Song"
Podcast = "Podcast"
Audiobook = "Audiobook"
#app.post("/{audio_type}", status_code=status.HTTP_200_OK)
def audio(audio_type: AudioType):
if audio_type == AudioType.Song:
def create_song(request: schemas.Song, db: Session = Depends(database.get_db)):
new_song = models.Song(name=request.name, duration=request.duration, uploadTime=request.uploadTime)
db.add(new_song)
db.commit()
db.refresh(new_song)
return new_song
elif audio_type == AudioType.Podcast:
def create_podcast(request: schemas.Podcast, db: Session = Depends(database.get_db)):
new_podcast = models.Podcast(name=request.name, duration=request.duration, uploadTime=request.uploadTime, host=request.host)
db.add(new_podcast)
db.commit()
db.refresh(new_podcast)
return new_podcast
elif audio_type == AudioType.Audiobook:
def create_audiobook(request: schemas.Audiobook, db: Session = Depends(database.get_db)):
new_audiobook = models.Audiobook(titile=request.title, author=request.author, narrator=request.narrator, duration=request.duration, uploadTime=request.uploadTime)
db.add(new_audiobook)
db.commit()
db.refresh(new_audiobook)
return new_audiobook
Your method doesn't accept the request object but only the audio_type.
Also from what I understand from your code, you may have multiple request bodies (schemas as you refer to them)
There are 2 options to what you want:
You need to declare your endpoint as follows:
from typing import Union
#app.post("/{audio_type}", status_code=status.HTTP_200_OK)
def audio(
request: Union[schemas.Song, schemas.Podcast,
schemas.Audiobook], audio_type: AudioType
):
... Your method ...
But the auto swagger of fastapi will not provide a schema example and you will have to provide examples manually, (which may or may not be possible, I don't really know and haven't tried it :/)
OR you can have a schema that can accept everything as Optional and the audio_type parameter:
from typing import Optional
class EndpointSchema(BaseModel):
audio_type: AudioType
song: Optional[schemas.Song]
podcast: Optional[schemas.Podcast]
audiobook: Optional[schemas.Audiobook]
#app.post("/audio", status_code=status.HTTP_200_OK)
def audio(request_body: EndpointSchema):
if request_body.audio_type == AudioType.Song:
... Continue with your request processing ...
Finally, very important: You are declaring internal methods (create_song etc.) that you are not calling afterward, so your code will do nothing. You don't need to do that, use the code you want to create a song, podcast, or audiobook directly inside the if, elif ... blocks!