In Python's FastAPI autogenerated OpenAPI/Swagger documentation page, how can I add more error http status codes? - fastapi

FastAPI generates automatic swagger/openapi documentation.
In the tutorial at https://fastapi.tiangolo.com/tutorial/response-status-code there's an example
from fastapi import FastAPI
app = FastAPI()
#app.post("/items/", status_code=201)
async def create_item(name: str):
return {"name": name}
If you run this, the .../docs page shows two http response options:
Status Code 201 for success and Status Code 422 for Validation error
The above tutorial shows a picture of this page)
I would like to document more responde status_code descriptions in the docs, for example
code 403, "Forbidden"
While I can run exceptions like this in code
raise HTTPException(status_code=403, detail="Forbidden")
I have not found a way to describe them in the autogenerated docs.
Any idea how to do that?

Does this solve your problem?
https://fastapi.tiangolo.com/advanced/additional-responses/
EDIT
With the response model you can add the different responses your API may return.
from pydantic import BaseModel
# Define your models here like
class model200(BaseModel):
message: str = ""
#api.get("/my-route/", responses={200: {"response": model200}, 404: {"response": model404}, 500: {"response": model500}})
async def api_route():
return "I'm a wonderful route"
This will provide examples of your response models, making it simpler for users to interact with the api

Related

converting from python requests method of http auth to aiohttp.BasicAuth

I have a working HTTP post request:
`requests.post('http://' + str(ip_address) + addrsuffix, auth=HTTPDigestAuth(username, password), data='payload')`
I'm trying to convert this request to use to asyncio, but using aiohttp I get unauthorised 401 status as a reply.
This is what I tried. I was expecting this to just work, using the BasicAuth username and password method.
async def post_request(session, ip, username, password, data):
digest_auth = aiohttp.BasicAuth(username, password)
url = f'http://{ip}/addrsuffix/'
headers = {CONTENT_TYPE: 'application/json'}
try:
print(f'{username}, {password}, {data}, {digest_auth}')
async with session.post(url, auth=digest_auth, headers=headers, json=data) as resp:
if resp.status == 200:
print(f'Successful post request to {ip}')
else:
print(f'Error {resp.status} for post request to {ip}')
except Exception as e:
print(f'Error: {e} for post request to {ip}')
when I look at how the original python requests method encodes the auth data, I get:
<requests.auth.HTTPDigestAuth object at 0x<a_hex_string> >
but the new aiohttp.BasicAuth method produces:
BasicAuth(login='myusername', password='mypass', encoding='latin1')
It seems to me to be the way it is presenting the credentials as "login" (but I might be wrong).
Can I please get guidance on how to achieve the same auth method as the requests.auth produces?
I've tried to read aiohttp's documentation on BasicAuth but find it hard to comprehend.
Happily answering my own dumb question: aiohttp doesn't support Digest Authentication.
So I stumbled on httpx, which seems to work great for this purpose.

FastApi Test Client executing the internal api call

This is the first time I'm trying to write test cases.
I've got a simple FastAPI application and I'm trying to create tests with unittest module.
My goal is to test how app behaves for success case
I've got a simple route in my app:
from fastapi import APIRouter,Request
import requests
router = APIRouter()
#router.post("/demo_router")
async def demo_api(request: Request):
# now calling some private APi's
resp = requests.post("https://example.com", json=data)
return {"resp_data": resp.json()}
Now in my unittest module I'm trying to patch above api. I'm using unittest.mock but I'm getting very strange behavior.
import unittest
from fastapi.testclient import TestClient
from unittest.mock import patch
from main import app
class DemoViewTestCase(unittest.TestCase):
def test_demo_api(self):
with patch('src.endpoints.demo_module.demo_api') as mocked_post:
mocked_post.return_value.status_code = 200
mocked_post.return_value.json = {
"message": "request accepted",
"success": True
}
url = router.url_path_for('demo_api') #fetch the api router
client = TestClient(app)
response = client.post(url, json={"id": "BXBksk8920", "name": "Pjp"})
My problem is TestClient is calling the api and executing it. So it is triggering the internal call "https://example.com" which is causing some execution in the pipelines. So how can I overcome this?
Internal Api shouldn't trigger, I should even mock that? Any solution for that?
When testing all the code will be executed. Thus also the calls to the APIs. If you don't want this, you have to provide mock APIs (postman, mockon and many others provide this).
Because you don't want to be bothered to change the URL's etc when you are testing etc, you could look at automating this.
One way of doing this is to provide all URLs for external APIs using pedantic BaseSettings
config.py:
from pydantic import BaseSettings
class Settings(BaseSettings):
external_api_url: str = "https://api.example.com"
And use this in your code:
settings = Settings() # scans environment for any matching env settings!
...
resp = requests.post(setting.external_api_url, json=data)
In your tests you can override these settings:
settings = Settings(external_api_url="https://mockservice")
This is documented further in Pydantic BaseSettings
There are more way do enhance testing and this is found at the FastAPI documentation:
Dependency Override: https://fastapi.tiangolo.com/advanced/testing-dependencies/
Use different databases for testing: https://fastapi.tiangolo.com/advanced/testing-database/

Mocked request doing actual requests call, not mock

I am mocking a get request in my unittest code using requests-mock, but when I run the code during testing, it still tries to hit the actual URL instead of returning the mocked data.
This is my code:
try:
response = requests.get(api_url, auth=requests.auth.HTTPBasicAuth(username, password))
response.raise_for_status()
except requests.ConnectionError as e:
raise dke.CLIError(f"Could not connect to Artifactory server to get NPM auth information: {str(e)}")
This is my test code
class MyTest(unittest.TestCase):
#classmethod
def setUpClass(cls):
m = requests_mock.Mocker()
m.get('https://artifactory.apps.openshift-sandbox.example.com/artifactory/api/npm/auth',
text=("_auth = base64string==\n"
"always-auth = true\n"
"email = shareduser#fake.com"))
The api_url in my code matches the URL I pass to m.get(). However, when I run the test, I do not get the value of "text" but instead I get a 401 Client Error: Unauthorized and a response from the server indicating "Bad credentials" which tells me it actually tried to contact the server instead of returning the text I had requested in the Mock.
I'm lost. As far as I can tell, I'm using this exactly as the docs indicate. Any ideas?
So, it seems you can't use the request_mocker that way. It has to be a decorator or context manager.
I mean the context manager/decorator is just a pattern around normal code. I haven't tested it but i think you just have to call m.start()/m.stop().
It's generally used as a context manager or decorator because if you instantiate it once like that then your request history is going to include all requests across all unit tests which is very hard to make assertions about.

Azure Logic Apps -- HTTP Action -- POST Error

So I am trying to get an authorization token from MSFT.
I am using the HTTP Request object as an action.
I set the method to POST, provide URI, set a Content_Type in the Header, and add a grant type to the body.
Screenshot of LogicApps HTTP Setup
When I run my logic app I receive the following error:
"error": "invalid_request",
"error_description": "AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: ef137edb-87d4-43e2-88b7-d119b2c00500\r\nCorrelation ID: 4ea88c05-7f28-4e3f-bb31-052c3baac198\r\nTimestamp: 2020-05-22 17:33:21Z",
"error_codes": [
900144
So the error says I am missing 'grant_type' but I have it in the body.
Can anyone point me in a direction to get this resolved?
The Body in this case is not a JSON object, it's a Form encode. Here's a screen shot where I do this same task:
You'll need to pass the client_id and client_secret as well.

Encoded / Encrypted body before verifying a Pact

A server I need to integrate with returns its answers encoded as a JWT. Worse, the response body actually is a json, of the form:
{d: token} with token = JWT.encode({id: 123, field: "John", etc.})
I'd like to use a pact verification on the content of the decoded token. I know I can easily have a pact verifying that I get back a {d: string}, I can't do an exact match on the string (as the JWT contains some varying IDs). What I want is the following, which presumes the addition of a new Pact.JWT functionality.
my_provider.
upon_receiving('my request')
.with(method: :post,
path: '/order',
headers: {'Content-Type' => 'application/json'}
).will_respond_with(
status: 200,
headers: {'Content-Type' => 'application/json; charset=utf-8'},
body: {
d: Pact::JWT( {
id: Pact.like(123),
field: Pact.term(generate: "John", matcher: /J.*/
},signing_key,algo
)
})
Short of adding this Pact::JWT, is there a way to achive this kind of result?
I am already using the pact proxy to run my verification. I know you can modify the request before sending it for verification (How do I verify pacts against an API that requires an auth token?). Can you modify the request once you receive it from the proxy server?
If that's the case, I can plan for the following work around:
a switch in my actual code to sometimes expect the answers decoded instead of in the JWT
run my tests once with the swich off (normal code behaviour, mocks returns JWT data encoded.
run my tests a second time with the swich off (code expect data already decoded, mocks return decoded data.)
use the contract json from this second run
hook into the proxy:verify task to decode the JWT on the fly, and use the existing pact mechanisms for verification. (Step that I do not know how to do).
My code is in ruby. I do not have access to the provider.
Any suggestions appreciated! Thanks
You can modify the request or response by using (another) proxy app.
class ProxyApp
def initialize real_provider_app
#real_provider_app = real_provider_app
end
def call env
response = #real_provider_app.call(env)
# modify response here
response
end
end
Pact.service_provider "My Service Provider" do
app { ProxyApp.new(RealApp) }
end
Pact as a tool, I don't expect it to give this behavior out of the box.
In my opinion, the best is,
Do not change source code only for tests
Make sure your tests verifies encoded json only (generate encoded expected json in test & verify that with actual)

Resources