Skip to content

Commit

Permalink
feat: add fastapi template and api extra field
Browse files Browse the repository at this point in the history
  • Loading branch information
zumuta committed Nov 22, 2024
1 parent cb5ad4a commit 1253a62
Show file tree
Hide file tree
Showing 8 changed files with 100 additions and 13 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to this project will be documented in this file.

## [0.1.4](https://github.com/fbraem/griffe-fastapi/releases/tag/0.1.2) - 2024-11-22

- Feat: add api to the extra dictionary
- Feat: add fastapi template

## [0.1.3](https://github.com/fbraem/griffe-fastapi/releases/tag/0.1.2) - 2024-11-19

- Fix generated table in docstring.
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ Griffe FastAPI Extension
This extension will search for functions that are decorated with an APIRouter and adds the following extra
fields to a function:

+ api: the path of the api
+ method: the HTTP method
+ responses: A dictionary with the responses

These fields are stored in the extra property of the function. The extra property is a dictionary and `griffe_fastapi`
is the key for the fields of this extension.

Create a custom function template to handle these extra fields in your documentation.
A fastapi template is used to handle these extra fields in your documentation.

Installation
------------
Expand Down
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "griffe-fastapi"
version = "0.1.3"
version = "0.1.4"
description = "Griffe extension for FastAPI."
authors = ["fbraem <[email protected]>"]
readme = "README.md"
Expand All @@ -24,3 +24,6 @@ black = "^24.10.0"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[project.entry-points."mkdocstrings.python.templates"]
griffe-fastapi = "griffe_fastapi:get_templates_path"
11 changes: 10 additions & 1 deletion src/griffe_fastapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
Griffe extension for FastAPI.
"""

from pathlib import Path

from griffe_fastapi._extension import FastAPIExtension

__all__: list[str] = ["FastAPIExtension"]

def get_templates_path() -> Path:
"""Return the templates directory path."""
print("Hey!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
return Path(__file__).parent / "templates"


__all__: list[str] = ["get_templates_path", "FastAPIExtension"]
12 changes: 10 additions & 2 deletions src/griffe_fastapi/_extension.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""griffe_fastapi extension."""

import ast
from typing import Any
from typing import Any, cast
from griffe import (
Decorator,
Docstring,
Expand Down Expand Up @@ -116,13 +116,21 @@ def on_function_instance(
if decorator is None:
return

decorator_expr: ExprCall = cast(ExprCall, decorator.value)

func.extra[self_namespace] = {"method": decorator.value.canonical_name}
func.extra["mkdocstrings"]["template"] = "fastapi.html.jinja"

if len(decorator_expr.arguments) > 0:
func.extra[self_namespace]["api"] = ast.literal_eval(
decorator_expr.arguments[0]
)

# Search the "responses" keyword in the arguments of the function.
responses = next(
(
x
for x in decorator.value.arguments
for x in decorator_expr
if isinstance(x, ExprKeyword) and x.name == "responses"
),
None,
Expand Down
59 changes: 59 additions & 0 deletions src/griffe_fastapi/templates/material/_base/fastapi.html.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{#- Template for Python FastAPI.
This template renders a Python function that is decorated with a FastAPI APIRouter.
Context:
function (griffe.Function): The function that implements the api to render.
root (bool): Whether this is the root object, injected with `:::` in a Markdown page.
heading_level (int): The HTML heading level to use.
config (dict): The configuration options.
-#}

{% block logs scoped %}
{#- Logging block.
This block can be used to log debug messages, deprecation messages, warnings, etc.
-#}
{{ log.debug("Rendering " + function.path) }}
{% endblock logs %}

{% import "language"|get_template as lang with context %}
{#- Language module providing the `t` translation method. -#}

<div class="doc doc-object doc-function">
{% with obj = function, html_id = function.extra.griffe_fastapi.api %}

{% set function_name = function.extra.griffe_fastapi.api %}

{% filter heading(
heading_level,
role="api",
id=html_id,
class="doc doc-heading",
toc_label=(('<code class="doc-symbol doc-symbol-toc doc-symbol-' + symbol_type + '"></code>&nbsp;')|safe if config.show_symbol_type_toc else '') + function.name,
) %}
{% endfilter %}

<div class="doc doc-contents">
{% block contents scoped %}
{#- Contents block.
This block renders the contents of the function.
It contains other blocks that users can override.
Overriding the contents block allows to rearrange the order of the blocks.
-#}
{% block docstring scoped %}
{#- Docstring block.
This block renders the docstring for the function.
-#}
{% with docstring_sections = function.docstring.parsed %}
{% include "docstring"|get_template with context %}
{% endwith %}
{% endblock docstring %}

{% endblock contents %}
</div>

{% endwith %}
</div>
1 change: 1 addition & 0 deletions src/griffe_fastapi/templates/material/fastapi.html.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% extends "_base/fastapi.html.jinja" %}
17 changes: 9 additions & 8 deletions tests/test_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def test_extension() -> None:
router = APIRouter()
@router.get("/", responses={200:{"description": "Ok"}})
@router.get("/teams", responses={200:{"description": "Ok"}})
def get_teams() -> list[str]:
'''Get the teams.
'''
Expand All @@ -28,6 +28,7 @@ def get_teams() -> list[str]:
assert "griffe_fastapi" in package.functions["get_teams"].extra
extra = package.functions["get_teams"].extra["griffe_fastapi"]
assert extra["method"] == "get"
assert extra["api"] == "/teams"
assert extra["responses"]["200"]["description"] == "Ok"


Expand All @@ -37,7 +38,7 @@ def test_extension_with_constant() -> None:
router = APIRouter()
@router.get("/", responses={status.HTTP_200_OK:{"description": "Ok"}})
@router.get("/teams", responses={status.HTTP_200_OK:{"description": "Ok"}})
def get_teams() -> list[str]:
'''Get the teams.'''
return []
Expand All @@ -63,7 +64,7 @@ def test_extension_with_multiple_responses() -> None:
router = APIRouter()
@router.get("/", responses={
@router.get("/teams", responses={
200:{"description": "Ok"},
404:{"description": "Not found"}
})
Expand Down Expand Up @@ -93,7 +94,7 @@ def test_extension_with_a_response_with_headers() -> None:
router = APIRouter()
@router.get("/", responses={
@router.get("/teams", responses={
200:{"description": "Ok", "content": {"image/png": {}}},
404:{"description": "Not found"}
})
Expand Down Expand Up @@ -129,7 +130,7 @@ def test_extension_with_a_dict() -> None:
404: {"description": "Not Found"},
}
@router.get("/", responses={**responses})
@router.get("/teams", responses={**responses})
def get_teams() -> list[str]:
'''Get the teams.'''
return []
Expand Down Expand Up @@ -160,7 +161,7 @@ def test_extension_mixed() -> None:
200: {"description": "Ok"}
}
@router.get("/", responses={404: {"description": "Not Found"}, **responses})
@router.get("/teams", responses={404: {"description": "Not Found"}, **responses})
def get_teams() -> list[str]:
'''Get the teams.'''
return []
Expand All @@ -187,7 +188,7 @@ def test_with_paths() -> None:
router = APIRouter()
@router.get("/", responses={200:{"description": "Ok"}})
@router.get("/teams", responses={200:{"description": "Ok"}})
def get_teams() -> list[str]:
'''Get the teams.'''
return []
Expand All @@ -211,7 +212,7 @@ def test_extension_with_table() -> None:
router = APIRouter()
@router.get("/", responses={200:{"description": "Ok"}})
@router.get("/teams", responses={200:{"description": "Ok"}})
def get_teams() -> list[str]:
'''Get the teams.
'''
Expand Down

0 comments on commit 1253a62

Please sign in to comment.