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.
Related
I am using a python library (ccxt) in which one base exchange class is inherited by exchange-specific classes, to provide a unified interface to several exchanges (coinbase, binance etc.).
The function definition for a sub-class might look something like this (not necessarily exactly): def fetch_ledger(self, symbols = None, since = None, params = {}):
The thing is, for e.g. the coinbase class, this method calls another method called prepareAccountRequestWithCurrencyCode(), which raises the exception:
raise ArgumentsRequired(self.id + ' prepareAccountRequestWithCurrencyCode() method requires an account_id(or accountId) parameter OR a currency code argument') if "accountId" or "code" is not provided in the params dict. These arguments are not in the function signature, as they are to be provided in the params dict (e.g. params = {"accountId" : "0x123"}).
I want to know that these arguments are required before I use the method, as I want to implement some automation and GUI-elements which can work across several exchanges (sub-classes). Some of these sub-classes have their own fetch_ledger methods which might not require e.g. the "accountId" argument to be provided in the params dict.
What is a god way to automatically obtain required aguments that are not in the function signature, for all exchanges?
I am providing the relevant ccxt code below since it's open-source:
def fetch_ledger(self, code=None, since=None, limit=None, params={}):
self.load_markets()
currency = None
if code is not None:
currency = self.currency(code)
request = self.prepare_account_request_with_currency_code(code, limit, params) # REQUIRES "accountId" in params
query = self.omit(params, ['account_id', 'accountId'])
response = self.v2PrivateGetAccountsAccountIdTransactions(self.extend(request, query))
return self.parse_ledger(response['data'], currency, since, limit)
def prepare_account_request_with_currency_code(self, code=None, limit=None, params={}):
accountId = self.safe_string_2(params, 'account_id', 'accountId')
if accountId is None:
if code is None:
raise ArgumentsRequired(self.id + ' prepareAccountRequestWithCurrencyCode() method requires an account_id(or accountId) parameter OR a currency code argument')
accountId = self.find_account_id(code)
if accountId is None:
raise ExchangeError(self.id + ' prepareAccountRequestWithCurrencyCode() could not find account id for ' + code)
request = {
'account_id': accountId,
}
if limit is not None:
request['limit'] = limit
return request
I've already thought of a few ways of doing it, such as running the function, catching the exception and dissecting the string to prompt the user for any missing arguments during run-time. I've also thought about making a source code parser, and even making changes to the library code, but I'm currently not sure what is best. I'd prefer to not have to look at the documentation of each unified method for all 100 exchanges and having to do it manually.
I'm wondering if anyone knows of an elegant or best-practice way of obtaining such optionally provided, yet required arguments for such methods (or just for the library I am currently using).
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 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.
How do I write my Request class to say that the payload can be Union[EcrPayload] or Union[S3Payload]?
class EcrPayload(CamelModel):
repository_name: str
version: str
class S3Payload(CamelModel):
bucket_name: str
object_key_name: str
class Request(CamelModel):
payload: Union[EcrPayload]
Just use Union with both the classes you wish to include:
class Request(CamelModel):
payload: Union[EcrPayload, S3Payload] # accepts ECR and S3 payloads, but nothing else
Note that this means that the member variable payload has to be either an instance of EcrPayload or S3Payload, but nothing else.
If you wish to include any type of CamelModel instead (including perhaps new models EcsPayload, LambdaPayload or others that you may define in the future), you can write:
class Request(CamelModel):
payload: CamelModel # any CamelModel is accepted, including ECR and S3
You can use the same CamelModel.
class Request(CamelModel):
payload: Union[CamelModel]
I want to add type annotations to a view function that returns a call to redirect. What does redirect return, and how do I add an annotation for that to my view function?
I thought it might be str, or the redirect function, but I'm not sure.
def setalarm() -> redirect:
# Retrieves the information to create new alarms.
return redirect("/")
The straightforward answer is to annotate your view with whatever you're writing it to return. In your specific example, redirect returns an instance of werkzeug.wrappers.Response.
from werkzeug.wrappers import Response
def set_alarm() -> Response:
return redirect()
Rather than figuring out what any given function returns in order to annotate your view, it might seem easier to come up with a Union annotation that represents anything a Flask view is allowed to return. However, Flask doesn't provide typing information, and its dynamic nature makes representing the possibilities difficult.
By default, a Flask view can return:
A str or bytes.
A subclass of werkzeug.wrappers.BaseResponse.
A tuple in one of these forms, where data is any of the other types a Flask view can return:
(data,)
(data, status), where status can be either an int or a str or bytes.
(data, headers), where headers is either a dict, iterable of (key, value) tuples, or a werkzeug.datastructures.Headers object.
(data, status, headers)
A dict to be converted to JSON. The values should be types that app.json_encoder supports.
A WSGI callable.
Flask can support more or different return types by overriding the Flask.make_response method. The data it can serialize to JSON can be extended by overriding Flask.json_encoder. If you have customized Flask's behavior you'll need to customize the type information as well.
Here's a view_return_type that represents the possible return types from a Flask view, ignoring JSON typing. Once you define the type, you can annotate any view with it.
import typing as t
from werkzeug.datastructures import Headers
from werkzeug.wrappers import BaseResponse
_str_bytes = t.Union[str, bytes]
_data_type = t.Union[
_str_bytes,
BaseResponse,
t.Dict[str, t.Any],
t.Callable[
[t.Dict[str, t.Any], t.Callable[[str, t.List[t.Tuple[str, str]]], None]], t.Iterable[bytes]
],
]
_status_type = t.Union[int, _str_bytes]
_headers_type = t.Union[
Headers, t.Dict[_str_bytes, _str_bytes], t.Iterable[t.Tuple[_str_bytes, _str_bytes]],
]
view_return_type = t.Union[
_data_type,
t.Tuple[_data_type],
t.Tuple[_data_type, _status_type],
t.Tuple[_data_type, _headers_type],
t.Tuple[_data_type, _status_type, _headers_type],
]
#app.route("/users/<int:id>/")
def user_detail(id: int) -> view_return_type:
...
In Flask 2 you can use flask.typing.ResponseReturnValue.
from flask.typing import ResponseReturnValue
#app.get("/")
def index() -> ResponseReturnValue:
return "OK"