RESTful web service API documentation with Sphinx [closed] - rest

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
What's the best way to markup methods/URLs for a RESTful webservice using ReST/Sphinx? Is there a default domain that's suitable for marking up URLs with their possible parameters, HTTP methods, headers and body content?
Something along the lines of:
.. rest:method:: GET /api/foo
:param bar: A valid bar
:extension: json or xml
Retrieve foos for the given parameters. E.g.::
GET /api/foo.json?bar=baz
Does something like this already exist or is one of the default extensions usable, or will I have to write one myself?

The Sphinx Contrib project also seems to have an HTTP Domain package for documenting RESTful HTTP APIs. You can find its documentation on the Python packages site. I can't speak to its fitness: I'm only just starting to look into Sphinx, and I have a need to document RESTful APIs as well, and noticed this contributed package.

Since there doesn't appear to be any existing solution, I have implemented a very simple HTTP domain that I can use to mark up API methods:
import re
from docutils import nodes
from sphinx import addnodes
from sphinx.locale import l_, _
from sphinx.domains import Domain, ObjType
from sphinx.directives import ObjectDescription
http_method_sig_re = re.compile(r'^(GET|POST|PUT|DELETE)?\s?(\S+)(.*)$')
class HTTPMethod(ObjectDescription):
def handle_signature(self, sig, signode):
m = http_method_sig_re.match(sig)
if m is None:
raise ValueError
verb, url, query = m.groups()
if verb is None:
verb = 'GET'
signode += addnodes.desc_addname(verb, verb)
signode += addnodes.desc_name(url, url)
if query:
params = query.strip().split()
for param in params:
signode += addnodes.desc_optional(param, param)
return url
class HTTPDomain(Domain):
"""HTTP language domain."""
name = 'http'
label = 'HTTP'
object_types = {
'method': ObjType(l_('method'), 'method')
}
directives = {
'method': HTTPMethod
}
def setup(app):
app.add_domain(HTTPDomain)
It allows me to mark up methods like this and they'll be styled somewhat visually nicely:
.. http:method:: GET /api/foo/bar/:id/:slug
:param id: An id
:param slug: A slug
Retrieve list of foobars matching given id.
This was my first foray into both Sphinx and Python, so this should be considered very rudimentary code. If anybody is interested in fleshing this out, please fork this project on Github!

Related

Different OpenAPI schema in FastAPI depending on environment

We have a FastApi application that is hosted behind a reverse proxy.
The proxy authenticates the user using Kerberos and adds a X-Remote-User HTTP header to the request.
This header is required by the FastApi application. Here is an example route:
#app.get("/user/me")
async def get_user_me(x_remote_user: str = Header(...)):
return {"User": x_remote_user}
The X-Remote-User header is required for the request which is expected behavior.
When we now open the Swagger Ui, the header is documented and when clicking on "Try it out", we can provide the header value.
This behavior is great for development, but in all other cases it is undesired, because that header is provided by the reverse proxy. For instance, we generate clients using OpenAPI Generator and the clients then all require the X-Remote-User parameter in their requests.
Hence, it would be useful to have a configuration that distinguishes between the environments. If we are behind a reverse proxy, then the generated OpenAPI Schema by FastApi should not include the X-Remote-Header, otherwise if we are in development, it should be included.
What I did so far:
I checked the documentation about security and also some source code of these modules, but I was not able to find a solution.
In the documentation, I read the section Behind a Proxy, but nothing there points me to a potential solution.
I also read about Middleware, but again, no solution.
We could change the generated OpenApi schema. I sketched this in my answer below, but this is not a very elegant solution
Does anyone have a good solution to this problem?
We can use APIKeyHeader to remove the X-Remote-User header from the API signature, but still enforcing the header to be present.
from fastapi.security import APIKeyHeader
apiKey = APIKeyHeader(name="X-Remote-User")
#app.get("/user/me")
async def get_user_me(x_remote_user: str = Depends(apiKey)):
return {"User": x_remote_user}
When the header is not present, we get a "403 Forbidden". If it is present, we retrieve the header value.
The Swagger UI now has a button "Authorize" where we can fill-in the value of the X-Remote-User for testing purposes.
One approach is to generate the OpenApi schema as described in the documentation Extending OpenAPI. After the generation, remove the X-Remote-User from the schema. In the configuration could be a flag that the application it is behind a reverse proxy to execute the code conditionally:
from fastapi import FastAPI
from fastapi.openapi.utils import get_openapi
from MyConfig import Config
app = FastAPI()
#app.get("/items/")
async def read_items():
return [{"name": "Foo"}]
if Config.reverse_proxy:
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Custom title",
version="2.5.0",
description="This is a very custom OpenAPI schema",
routes=app.routes,
)
// remove X-Remote-User here
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
However this is not a very elegant solution, as we need to parse the Json string and remove the different deeply-nested occurrences of the X-Remote-User header everywhere. This is prone to bugs resulting in an invalid schema. Furthermore it could break if new Rest endpoints are added.
A new param will be soon available for Header, Query and other to exclude elements from the openAPI output: include_in_schema=False
Example:
def test(x_forwarded_for: str = Header(None, include_in_schema=False)):
...
Here the patch state: https://github.com/tiangolo/fastapi/pull/3144

REST API designing for resource with aggregated property

We are currently trying to come up with a set of REST API that would fit our resource models.
A simplified example of the resource is:
CompanyInfo: {
totalNumberOfEmployees: Number,
employees: [...employees],
}
Employee: {
name: String,
}
In this case, "CompanyInfo" is like a virtual resource that does not exist in DB. It is a short cut for getting all the data related to the Company resource. The idea was to reduce the amount of logic on FE and create more convenient endpoint instead.
Our current endpoint design is:
GET /api/companyInfos/{companyId}/employees
GET,POST,PUT,DELETE /api/companyInfos/{companyId}/employees/{employeeId}
The reason for the extra {companyId} is because these endpoint does not return "Employees", it instead return a "CompanyInfo" that contains "Employees" embedded in the payload.
This is to avoid the aggregated property "totalNumberOfEmployees" not being updated in case sync when we call POST to create a new "Employee"
So my questions are:
Is this the correct approach to the problem of "too many requests" or "too much logic in FE"?
Is it acceptable for the endpoint to return a completely different resource than what its url describe?
Thanks a lot :)
For your Fist question
Is this the correct approach to the problem of "too many requests" or "too much logic in FE"?
yes Sometimes this is how it is suppose to be done. when data sent is small in each request. to many request does not affect the performance so This is how it is suppose to be done .
And Generally it is recommended to write one monolithic Ajax call in front end which will be capable of making any kind of call , By taking callback as parameter, and method , arguments as parameters .
So it will not be to much of logic if you follow this approach . All you have to write is callback for each of Ajax call . How ever sometimes situation may not allow for this Example:if you are using content-type like 'multipart/mixed'
there you have to write another ajax call code
However nowdays most front end has too much of logic based on how interactive website is . So your primary concern should be about look of web site .
For you second question
Is it acceptable for the endpoint to return a completely different resource than what its url describe?
yes . It is acceptable . but it is recommended that client mention all the MIME types which it expects in Accept header and Only those MIME types should be returned by Api.

Stack Exchange API - getting answers for the questions object

I use https://api.stackexchange.com/docs. To get a sample question, I use the address https://api.stackexchange.com/2.2/questions/6827752?&site=stackoverflow. Thanks to this, he gains a question. The following page shows https://api.stackexchange.com/docs/types/question that with the default filter the question will not have a body. I know I use a filter to get a body filter=withbody that is, the address comes out
https://api.stackexchange.com/2.2/questions/6827752?&site=stackoverflow&filter=withbody
Now I would like to get answers for the question. That's why I want to use a filter filter=withanswers. This address
https://api.stackexchange.com/2.2/questions/6827752?&site=stackoverflow&filter=withanswers
returns an error
{
"error_id": 400,
"error_message": "Invalid filter specified",
"error_name": "bad_parameter"
}
How do get answers or comments using a filter?
I think that 2 patterns can be thought for your solution. One is a method using custom filter. Another is a method using the endpoint for retrieving answers.
Pattern 1 :
You can create a custom filter for retrieving answers and answer's body using the endpoint of https://api.stackexchange.com/2.2/questions/### questionId ###.
You can create the custom filters at http://api.stackexchange.com/docs/create-filter.
In your case, you can use this.
question.answers;answer.body;question.body was used as including filters.
The delimiter is ;.
As a result, you retrieve "filter": "!T*hPNRA69ofM1izkPP".
When you use the filter, please do URL encode it.
!T*hPNRA69ofM1izkPP becomes %21T%2ahPNRA69ofM1izkPP.
You can retrieve answers and answer's body using the endpoint.
When the question ID of your question is used, it's https://api.stackexchange.com/2.2/questions/6827752?&site=stackoverflow&filter=%21T%2ahPNRA69ofM1izkPP.
Result of pattern 1
When you can access to https://api.stackexchange.com/2.2/questions/6827752?&site=stackoverflow&filter=%21T%2ahPNRA69ofM1izkPP, you can get answers and answer's body.
Pattern 2 :
As the endpoint for retrieving answers, you can use /questions/{ids}/answers. If the question ID is 6827752 in your question, the endpoint is as follows. In this case, in order to retrieve answer's body, filter=withbody is used.
Result of pattern 2
When you can access to https://api.stackexchange.com/2.2/questions/6827752/answers?order=desc&sort=activity&site=stackoverflow&filter=withbody, you can get answer's body.
If this was not useful for you, I'm sorry.

SoundCloud parsing basic search results, is there api support?

...just wanted to confirm that since the latest soundcloud api does not provide a data interface, we are left with parsing the results from an http request.
my concern is that the resulting structure could change at anytime and thus render my parsing schema invalid. is anyone else doing something similar? or better?
This is correct. All SoundCloud API responses will be serialized as JSON or XML. We take backwards compatibility pretty seriously, so you can rely on the format and data returned.
Most languages have at least one library capable of automatically parsing JSON into an appropriate data type (i.e. an array of hashes). You can always check to ensure a key exists before you try to access it, for example in Python:
import json
import urllib
url = 'https://api.soundcloud.com/tracks.json'
fp = urllib.urlopen('%s?%s' % (url, urllib.urlencode({
'client_id': 'YOUR_CLIENT_ID',
'limit': 2
})))
data = fp.read()
tracks = json.loads(data)
for track in tracks:
print track.get('title', 'No title available')
Does that help answer your question?

How to post a file in grails

I am trying to use HTTP to POST a file to an outside API from within a grails service. I've installed the rest plugin and I'm using code like the following:
def theFile = new File("/tmp/blah.txt")
def postBody = [myFile: theFile, foo:'bar']
withHttp(uri: "http://picard:8080/breeze/project/acceptFile") {
def html = post(body: postBody, requestContentType: URLENC)
}
The post works, however, the 'myFile' param appears to be a string rather than an actual file. I have not had any success trying to google for things like "how to post a file in grails" since most of the results end up dealing with handling an uploaded file from a form.
I think I'm using the right requestContentType, but I might have missed something in the documentation.
POSTing a file is not as simple as what you have included in your question (sadly). Also, it depends on what the API you are calling is expecting, e.g. some API expect files as base64 encoded text, while others accept them as mime-multipart.
Since you are using the rest plugin, as far as I can recall it uses the Apache HttpClient, I think this link should provide enough info to get you started (assuming you are dealing with mime-multipart). It shouldn't be too hard to change it around to work with your API and perhaps make it a bit 'groovy-ier'