Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contracts in API with icontract #1996

Closed
mristin opened this issue Sep 1, 2020 · 19 comments
Closed

Contracts in API with icontract #1996

mristin opened this issue Sep 1, 2020 · 19 comments

Comments

@mristin
Copy link

mristin commented Sep 1, 2020

Hi,
I was thinking about integrating contracts (as in design-by-contract) into FastAPI. While Swagger allows for some trivial contracts (e.g., string patterns and bounds on numeric parameters), more complex logic is not directly supported.

Do you already have any thoughts on this?

I maintain icontract library (http://github.com/Parquery/icontract), so I could try to integrate it somehow with FastAPI and include the code of the contracts in the generated documentation (similar to sphinx-icontract, http://github.com/Parquery/sphinx-icontract).

Please let me know if you are interested.

@mristin mristin added the feature New feature or request label Sep 1, 2020
@tiangolo
Copy link
Member

Hey, congrats on your project! 🎉

So, I'm actually not sure I'm familiar with that terminology, but I think here this is all covered by Pydantic.

Otherwise, please check the docs to see if there's a specific use case that you need to solve and you don't find a way to do it. And if that's the case, then you can create a new issue following the template and I (or someone else) can help you figure out how to do it.

But anyway, thanks for the interest.

@mristin
Copy link
Author

mristin commented Dec 16, 2020

Hi @tiangolo ,
Pydantic checks only the data types (and some basic logic via the root validators, https://pydantic-docs.helpmanual.io/usage/validators/#root-validators).

Contracts are about the logic. A minimal example would be a precondition that an integer argument x must always be smaller than another argument y: x < y.

You could also have it reversed and enforce postconditions: the result of the function needs to be larger than an argument x etc. Another common example for a postcondition would be that the item should not exist after a successful delete.

You can have a look at https://en.wikipedia.org/wiki/Design_by_contract for a brief intro into the topic.

Being able to write down the preconditions and postconditions and include them somehow in the spec (as verbatim Python code?) would be really great since the contracts would be automatically testable.

@github-actions github-actions bot removed the answered label Dec 16, 2020
@Mause
Copy link
Contributor

Mause commented Dec 17, 2020

Are you able to give an example of the sort of integration you are envisioning?

@mristin
Copy link
Author

mristin commented Dec 17, 2020

Hi @Mause ,
Here is a (very rudimentary) code snippet:

@app.get("/items/{item_id}")
def item_exists(item_id: int, q: Optional[str] = None):
    ...

@app.delete("/items/{item_id}")
@icontract.ensure(lambda item_id, q: not item_exists(item_id, q)
def delete_item(item_id: int, q: Optional[str] = None):
    ...

The body of the postcondition (not item_exist(item_id, q)) would be included in the spec somehow (e.g., as verbatim Python code). icontract already supports extracting the code as text from the contracts, so it just needs to be passed up to FastAPI. For example, app.delete decorator could inspect if there are any preconditions and postconditions already defined for a function at decoration time and include them in the spec.

@mristin
Copy link
Author

mristin commented Dec 17, 2020

As a side note, apart from the more precise specs, you might also get automatic testing for free (e.g., I am currently working on integration with Hypothesis, https://github.com/mristin/icontract-hypothesis) and compile-time analysis (using something like https://github.com/pschanely/CrossHair).

@Mause
Copy link
Contributor

Mause commented Dec 17, 2020

Do you mean in the openapi spec?

@mristin
Copy link
Author

mristin commented Dec 17, 2020

Exactly, documented automatically in the OpenAPI spec that FastAPI generates. In that way we can obtain testable documentation.

@Mause
Copy link
Contributor

Mause commented Dec 17, 2020

It sounds like an interesting idea, though would likely be best suited to being implemented as a library that integrates with fastapi - I look forward to seeing it :)

@mristin
Copy link
Author

mristin commented Dec 17, 2020

Hi @Mause ,
Is this section from the documentation "Extending OpenAPI" (https://fastapi.tiangolo.com/advanced/extending-openapi/) a good start? Or you would implement it somehow differently?

I suppose the package should be called fastapi-icontract?

@Mause
Copy link
Contributor

Mause commented Dec 19, 2020

That would be a good place to start yes

@Zac-HD
Copy link

Zac-HD commented Jan 25, 2021

pydantic/pydantic#2097 might also be relevant 🙂

@mristin
Copy link
Author

mristin commented Jan 25, 2021

@Zac-HD indeed! Mind, though, that pydantic models only the structures and relationships within those structures.

The contracts should be able to model a more general relations between the endpoints and even wider relations to context, dependencies etc.

@mristin
Copy link
Author

mristin commented Feb 11, 2021

Just a notice for all the interested: I started working on https://github.com/mristin/fastapi-icontract/.

@mristin
Copy link
Author

mristin commented Feb 14, 2021

I tried to implement a prototype, but I am hitting the wall since Python does not support async lambda functions (see this Python issue).

Do you happen to know the portion of people that use async code compared to those who still use synchronuous code, @tiangolo?

Alternatively, I could write the contracts in-line in the function body (see this example), but this would make it much harder for tools like icontract-hypothesis to automatically infer test strategies. Maybe you have some ideas how to approach this?

I added the async support in icontract and made a recipe for a workaround for the lack of async lambdas (see this Python issue).

@mristin
Copy link
Author

mristin commented Feb 21, 2021

I am having a hard time patching get_swagger_ui_html. The contracts are added as extensions (e.g., x-contracts), I need to render them in Swagger UI by using a plugin (see Section "Customization" in Swagger UI docs).

However, as get_swagger_ui_html is directly called in a nested function, I can not patch it in an easy way:

(From fastapi.applications.setup(.))

async def swagger_ui_html(req: Request) -> HTMLResponse:
	root_path = req.scope.get("root_path", "").rstrip("/")
	openapi_url = root_path + self.openapi_url
	oauth2_redirect_url = self.swagger_ui_oauth2_redirect_url
	if oauth2_redirect_url:
		oauth2_redirect_url = root_path + oauth2_redirect_url
	return get_swagger_ui_html(
		openapi_url=openapi_url,
		title=self.title + " - Swagger UI",
		oauth2_redirect_url=oauth2_redirect_url,
		init_oauth=self.swagger_ui_init_oauth,
	)

I further need to monkey-patch get_swagger_ui_html, but I can't do that as the address is already hard-coded in the nested swagger_ui_html, so setting astapi.openapi.docs.get_swagger_ui_html to a different function has no effect.

Additionally, once routes are added to the router, I can not just change route.endpoint to call an arbitrary endpoint, as the address of the endpoint is already hard-coded in the router.

@tiangolo @Mause any ideas how I could add my plugin to Swagger UI?

Edit: formatting

@mristin
Copy link
Author

mristin commented Feb 26, 2021

I have just published the initial version of fastapi-icontract: https://github.com/mristin/fastapi-icontract/ together with the plugin to visualize contracts in Swagger UI: https://github.com/mristin/swagger-ui-plugin-contracts.

I will battle-test them at work in the next couple of weeks and then plan to publish the first major version.

@mristin
Copy link
Author

mristin commented Feb 26, 2021

(@tiangolo @Mause I suppose this is not a priority, but maybe you would like to consider at some point how to allow for more modular inclusion of Swagger UI plugins as the current architecture for that particular point of FastAPI is completely rigid, as opposed to openapi method. The only remedy is to manually copy/paste and modify get_swagger_ui_html, which is brittle in the long run.)

@Mause
Copy link
Contributor

Mause commented Feb 27, 2021

@mristin I absolutely support the inclusion of swagger customisation, and there's a PR up for it already, but unfortunately the final merge decision is up to @tiangolo

@mristin
Copy link
Author

mristin commented Mar 6, 2021

Just a brief update for all the interested: I released an alpha version of fastapi-icontract 0.0.1 on pypi.org: https://pypi.org/project/fastapi-icontract/

Bug reports and feature requests are highly welcome!

@tiangolo tiangolo added question Question or problem reviewed question-migrate and removed feature New feature or request labels Feb 24, 2023
@fastapi fastapi locked and limited conversation to collaborators Feb 28, 2023
@tiangolo tiangolo converted this issue into discussion #9123 Feb 28, 2023

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Projects
None yet
Development

No branches or pull requests

4 participants