WIP: Documentation with Github Flavored Markdown, allow direct Validators to query params, and fill up blank descriptions


#1

This is something my team and I been working for a while, our main reason to choose Apistar was its documentation engine (more than openapi), for us visual documentation is crucial, but it lacked some stuff we wanted to add:

  • Allow markdown (GFM if possible) in descriptions and doc’s
  • Allow a way to define the route encoding for the body, right now only application/json is printed, and custom headers sometimes are necessary even in the same content type application/x-my-custom-json
  • Allow direct Validator objects as annotations, both in GET/DELETE and POST/PUT

So there we go, for this app:

from apistar import App
from apistar import Route, TestClient, validators
from apistar.types import Type
from apistar.validators import Boolean, Integer, Number, String

md_description = """
_Some_ **formatted** `description`

#### Pending:

- [x] Finish my changes
- [x] Push my commits to GitHub
- [ ] Open a pull request

#### Ordered list:

1. Everything
1. in
1. order

#### Unordered list:

*  Some inline `Code`
*  Some _previewed_ code in list: 
   <pre class="highlight">
   while True:
        print('This is taking forever')</pre>

#### Some outer code:

<pre class="highlight">print("Hellow world!")</pre>

"""


class Sometype(Type):
    a = validators.String(description=md_description)
    b = Integer(allow_null=True)
    f = Number()
    c = Boolean()


def get_do_nothing(
    p: Integer(allow_null=False, description="`Integer` **path** parameter"),
    a,
    s: str,
    f: float,
    n: Number(),
    b: Boolean(),
    d: String(description="This is **not required** _parameter_", allow_null=True),
) -> validators.String:
    """_Modest_ **formatted** `documentation` <hr>"""
    return f"I did: {p}|{a}|{s}|{f}|{n}|{b}|{d}"


def post_do_nothing(
    p: int,
    a,
    b: Boolean(description="Just gimme a **true** _or_ **false**"),
    i: Integer(description="**Query** _parameter_"),
    d: String(allow_null=True),
    data: Sometype,
) -> validators.String:
    """
_Some_ **formatted** `documentation` using [Apistar](https://github.com/encode/apistar)

For GET doc go to [get_test](#get_test)

![Lets go](https://pic.chinesefontdesign.com/uploads/2016/08/chinesefontdesign.com_2016-08-19_17-58-43.gif)

### Pending:

- [x] Finish my changes
- [ ] Push my commits to GitHub
- [ ] Open a pull request

### Ordered list:

1. Everything
1. in
1. order

### Unordered list:

*  Some inline `Code`
*  Some _previewed_ code in list:
   <pre class="highlight">
   while True:
        print('This is taking forever')</pre>


### Some outer code:

<pre class="highlight">print("Hellow world!")</pre>

<hr>
    """
    return f"I did: {p}|{a}|{b}|{d}|{i}|{dict(data)}"


routes = [
    Route(
        "/{p}",
        name="post_test",
        method="POST",
        handler=post_do_nothing,
        documented=True,
        encoding="some/encoding",
    ),
    Route(
        "/{p}",
        name="get_test",
        method="GET",
        handler=get_do_nothing,
        documented=True,
        encoding="some/encoding",
    ),
]

app = App(routes=routes)

# Good
client = TestClient(app=app)
response = client.get("/1?a=2&b=true&s=s&i=4&f=1.0&n=2.0")
print(f"GET response: \n{response.content}\n")

response = client.post(
    "/1?a=2&b=true&s=s&i=4&f=1.0", json={"a": "a", "b": 1, "f": 2.0, "c": True}
)
print(f"POST response: \n{response.content}\n")

# Bad
client = TestClient(app=app)
response = client.get("/1?a=2&b=true&s=s&i=4&f=1.0")

print(f"GET response: {response.content}")
response = client.post(
    "/1?a=2&b=true&s=s&f=1.0&n=2.0", json={"a": "a", "b": 1, "f": 2.0, "c": True}
)
print(f"POST response: \n{response.content}\n")

This results in:

GET response: 
b'I did: None|2|s|1.0|2.0|True|None'

POST response: 
b"I did: 1|2|True|None|4|{'a': 'a', 'b': 1, 'f': 2.0, 'c': True}"

GET response: b'{"n":"The \\"n\\" field is required."}'
POST response: 
b'{"i":"The \\"i\\" field is required."}'

And the best for last, docs:

Here the branch, not PR’d yet:

It took one day and a half to do it, of course it has its pros and cons:

  • pros: better documentation, links, images, etc. It also allows to avoid misleading body message content-type. It’s merged against latest 0.5.40. It passes tests. It has in fact, an additional test.
  • pros: It only requires 2 libs that have no system deps, gfm and BeautifulSoup4
  • cons: now openapi schema parameters has always descriptions, at least with the type, as it does in docs. So it forced us to change the test_schema test a little bit :frowning_face:
  • cons: the encoding kwarg of Route is pretty smelly, but in order to auto-detect the body content type these PRs should be accepted:

(with those PR it can be possible to remove the Route(encoding="") kwarg and discover it automagically.

I know PR’s are hard and it takes time to review and one is not always eager to look at all hacks people send, but on our part in our company for now with the PR#570 and this branch we are pretty satisfied with having an easy API that allow us to build microservices in a morning with enough flexibility in types and parsers and with proper even funny documentation. Happy to PR if authors want, or wait for the PR’s listed above and then modify it to rip the route parameter and do some magic.


How to describe parameter in API call