-
Notifications
You must be signed in to change notification settings - Fork 47
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
Use Swagger #390
Comments
Automating development procedures is always a great idea, especially if it reduces code complexity. However, I wonder how would that fit in our current development process? At this moment, we have REST endpoints documentation and examples embedded in the source files. If we opt to use this, we'll have to remove the stuff from the code. In addition, each pull request will have to get an additional hook for documentation generation. And the documentation will have to be kept out of the main source tree, lest we face the problem of patching pull requests 'on the go'. How do you see the real place of this in our development stack? |
We can keep the documentation, but right now that documentation is just for humans. With this new system I hope that we can use the same specification for both (a) generating human documentation and (b) runtime pre- and postcondition violations. The former, generating human documentation, would gain nothing over the status quo for programmers (except in IPv8, which has virtually no documentation on its REST endpoints). An added benefit is that we can export the documentation to others (god forbid, like Javascript developers) without shipping the source code of the service though. Most importantly the latter, being able to check preconditions and postconditions at runtime, can be a great help in maturing our code. Right now any violations of our own REST specification will remain unnoticed by the service itself, any violations will have to be caught by a higher layer.
Yep.
Most of this is based on the whole theory of provable execution/security/software engineering. We know that precondition/postcondition specification programming is fallible. How this will work out in practice, I do not know. However, I'm bored, on vacation and in the mood for something adventurous. So, I'll implement this and report back once I know more about the practical side of this and whether or not we should want it at all 😃. |
Being a great fan of pre- post- condition checking, I totally support this one. Though, we need @xoriole's opinion on how to best integrate it in our development process |
After reflecting on my own answer a bit, I realized I basically want the REST API to guarantee the following two things at runtime:
Of course validity in this context only applies to the typechecking of the data. |
Well, the NetworkEndpoint prototype for runtime precondition and postcondition checking is working: qstokkink@d057bf4#diff-963a914f2490b6181d59564ab191dbc1 I still need to finish the documentation generator. Smooth sailing so far. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
I added a specification to the @RESTOutput(lambda input: True,
({
"statistics": [
{
(STR_TYPE["ASCII"], "The Community name."): {
(STR_TYPE["ASCII"], "The message name."): {
"identifier": (NUMBER_TYPE, "The message number."),
"num_up": NUMBER_TYPE,
"num_down": NUMBER_TYPE,
"bytes_up": NUMBER_TYPE,
"bytes_down": NUMBER_TYPE,
"first_measured_up": NUMBER_TYPE,
"first_measured_down": NUMBER_TYPE,
"last_measured_up": NUMBER_TYPE,
"last_measured_down": NUMBER_TYPE
}
}
}
]
},
"The statistics per message per community, if available."))
def render_GET(self, _):
...
@RESTInput("enable", (BOOLEAN_TYPE, "Whether to enable or disable the statistics."))
@RESTInput("overlay_name", (STR_TYPE["ASCII"], "Class name of the overlay."))
@RESTInput("all", (BOOLEAN_TYPE, "Update applies to all overlays."))
@RESTOutput(lambda input: True,
{
"success": (BOOLEAN_TYPE, "Whether the request succeeded, see error."),
"error": (STR_TYPE["ASCII"], "The available information in case of no success.")
})
def render_POST(self, request):
... Here's what the generated |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
And here is the complete, corrected and auto-generated IPv8 REST API specification: |
Lessons learned:
We will have to check out the sizable list of Python Swagger libraries and see what works best for our Twisted-based stack (https://github.com/gangverk/flask-swagger seems like a good contender). I'll close #397 in favor of this plan. |
After taking a long look into the world of web frameworks for Python I have reached a few conclusions. Moreover, I've also written 4 prototypes to exemplify how some candidate frameworks would work in a real application. There is a prototype for each of the following frameworks: Flask, Klein, Quart, and Quart-Trio (with Trio as the asynchronous framework). These prototypes are in a public repository. Each of the prototypes has a module called
I should also mention that the initial goal of this task was to look through the stack of Swagger integrations, and identify those which provide support for the current asynchronous nature of the Tribler ecosystem, and also seamlessly add support for the OpenAPI Specification. Unfortunately, there are no such integrations available. And the Swagger part will have to be developed in-house. As a conclusion, I think the decision of which web framework to use depends entirely on which asynchronous framework is to be used. If Twisted is here to stay, then Klein is the choice. If a move is made towards Asyncio, then Quart is probably our best way forward. Finally, if Trio is favored, then Quart-Trio is the way to go. I do encourage those interested in getting a glimpse of how the endpoints and frameworks (both the web and the aysnc ones) are used, and generally how these multi-module applications should be wired up for each of the discussed frameworks, to look into the prototype repository. |
BTW, |
Indeed a googling for |
True, decorators are an option, but the decorators are essentially a shorthand to I essentially didn't use decorators in those cases, since I instantiated the |
This will coincide with the move to Python 3, as such I will put it on hold. |
At the moment, @egbertbouman almost completed porting Tribler to asyncio. He used |
With the merge of #612, we now have Swagger docs. |
We should use Swagger for our REST endpoints. See below for the original exploration of this idea.
Old
Nobody wants to write documentation. Instead, we should automatically generate this. Also, nobody likes to do precondition and postcondition checks. We can do both as follows.
First we can scan all the distinct endpoints available by instantiating a
RootEndpoint
and inspecting its children. We can then automatically generate documentation from a.restapi
variable tied to the REST API handler functions (render_POST
,render_GET
, etc.).We can attach and generate the
.restapi
variable to these functions statically by using annotations/wrapper functions. Specifically we should expose:RESTInput
, which takes anoption
, ajsontype
and adescription
argument.RESTOutput
, which takes aconditional_lambda
and aoutput_format
argument.RESTInput
The first input a
RESTInput
takes is theoption
. Thisoption
specifies what option in the request URI is being described. Thejsontype
then specifies the JSON datatype, which can be a combination of:number
/boolean
/null
string
with specified encoding, for exampleSTR_TYPE["UTF-8"]
orSTR_TYPE["ASCII"]
array
/object
tuple
of(jsontype, description)
which adds additional documentation to an inner constructionLastly, each input should have a
description
. This is not very useful when usingRESTInput
for runtime precondition checking, but mostly for humans reading generated documentation.RESTOutput
The
RESTOutput
takes aconditional_lambda
, which is a lambda function taking the input and returningTrue
if its output format should be applied. I have created a function for converting lambda bytecode to human readable form already, so this is a multi-purpose argument (both for checking postconditions programmatically and for generating documentation). Lastly theoutput_format
specifies what the output matches to (or should match to). Theoutput_format
follows the same rules as thejsontype
for theRESTInput
. Thedescription
is once again to explain in human terms what the output is used for.Example
Would generate the following documentation:
NetworkEndpoint
This endpoint is responsible for handing all requests regarding the state of the network.Input
Output
"peers": {
string (base64):
"ip": string (base64),
"port": number,
"public_key": string (base64),
"services": [
string (base64)
]
}
}
}
The sha1 of the peer's public key.
The sha1 of the Community's public key.
The text was updated successfully, but these errors were encountered: