Skip to content

Commit 2358210

Browse files
authored
Merge pull request #10 from Denperidge-Redpencil/docstring-rework
README helper functions are now generated
2 parents 8dd8f76 + c7ed473 commit 2358210

File tree

4 files changed

+324
-68
lines changed

4 files changed

+324
-68
lines changed

README.md

Lines changed: 195 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -51,79 +51,219 @@ example docker-compose parameters:
5151
```
5252
5353
### Helper methods
54+
<a id="helpers.generate_uuid"></a>
5455
55-
The template provides the user with several helper methods. They aim to give you a step ahead for:
56+
#### `generate_uuid`
5657

57-
- logging
58-
- JSONAPI-compliancy
59-
- SPARQL querying
58+
```python
59+
def generate_uuid()
60+
```
6061

61-
The below helpers can be imported from the `helpers` module. For example:
62-
```py
63-
from helpers import *
62+
> Generates a random unique user id (UUID) based on the host ID and current time
63+
64+
<a id="helpers.log"></a>
65+
66+
#### `log`
67+
68+
```python
69+
def log(msg, *args, **kwargs)
6470
```
65-
Available functions:
66-
#### log(msg)
6771

68-
Works exactly the same as the [logging.info](https://docs.python.org/3/library/logging.html#logging.info) method from pythons' logging module.
69-
Logs are written to the /logs directory in the docker container.
70-
Note that the `helpers` module also exposes `logger`, which is the [logger instance](https://docs.python.org/3/library/logging.html#logger-objects) used by the template. The methods provided by this instance can be used for more fine-grained logging.
72+
> Write a log message to the log file.
73+
>
74+
> Works exactly the same as the logging.info (https://docs.python.org/3/library/logging.html#logging.info) method from pythons' logging module.
75+
> Logs are written to the /logs directory in the docker container.
76+
>
77+
> Note that the `helpers` module also exposes `logger`, which is the logger instance (https://docs.python.org/3/library/logging.html#logger-objects)
78+
> used by the template. The methods provided by this instance can be used for more fine-grained logging.
7179

72-
#### generate_uuid()
80+
<a id="helpers.error"></a>
7381

74-
Generate a random UUID (String).
82+
#### `error`
7583

76-
#### session_id_header(request)
84+
```python
85+
def error(msg, status=400, **kwargs)
86+
```
7787

78-
Get the session id from the HTTP request headers.
88+
> Returns a Response object containing a JSONAPI compliant error response with the given status code (400 by default).
89+
>
90+
> Response object documentation: https://flask.palletsprojects.com/en/1.1.x/api/#response-objects
91+
> The kwargs can be any other key supported by JSONAPI error objects: https://jsonapi.org/format/#error-objects
7992

80-
#### rewrite_url_header(request)
93+
<a id="helpers.session_id_header"></a>
8194

82-
Get the rewrite URL from the HTTP request headers.
95+
#### `session_id_header`
8396

84-
#### validate_json_api_content_type(request)
97+
```python
98+
def session_id_header(request)
99+
```
85100

86-
Validate whether the Content-Type header contains the JSONAPI `content-type`-header. Returns a 400 otherwise.
101+
> Returns the MU-SESSION-ID header from the given requests' headers
87102

88-
#### validate_resource_type(expected_type, data)
103+
<a id="helpers.rewrite_url_header"></a>
89104

90-
Validate whether the type specified in the JSONAPI data is equal to the expected type. Returns a 409 otherwise.
105+
#### `rewrite_url_header`
91106

92-
#### error(title, status=400, **kwargs)
107+
```python
108+
def rewrite_url_header(request)
109+
```
110+
111+
> Returns the X-REWRITE-URL header from the given requests' headers
93112

94-
Returns a JSONAPI compliant error [Response object](https://flask.palletsprojects.com/en/1.1.x/api/#response-objects) with the given status code (default: 400). `kwargs` can be any other keys supported by [JSONAPI error objects](https://jsonapi.org/format/#error-objects).
113+
<a id="helpers.validate_json_api_content_type"></a>
95114

96-
#### query(query)
115+
#### `validate_json_api_content_type`
97116

98-
Executes the given SPARQL select/ask/construct query.
117+
```python
118+
def validate_json_api_content_type(request)
119+
```
99120

100-
#### update(query)
121+
> Validate whether the request contains the JSONAPI content-type header (application/vnd.api+json). Returns a 404 otherwise
101122

102-
Executes the given SPARQL update query.
123+
<a id="helpers.validate_resource_type"></a>
103124

125+
#### `validate_resource_type`
104126

105-
The template provides one other helper module, being the `escape_helpers`-module. It contains functions for SPARQL query-escaping. Example import:
106-
```py
107-
from escape_helpers import *
127+
```python
128+
def validate_resource_type(expected_type, data)
108129
```
109130

110-
Available functions:
111-
#### sparql_escape ; sparql_escape_{string|uri|date|datetime|time|bool|int|float}(value)
131+
> Validate whether the type specified in the JSON data is equal to the expected type. Returns a `409` otherwise.
132+
133+
<a id="helpers.query"></a>
134+
135+
#### `query`
136+
137+
```python
138+
def query(the_query)
139+
```
112140

113-
Converts the given object to a SPARQL-safe RDF object string with the right RDF-datatype.
114-
This functions should be used especially when inserting user-input to avoid SPARQL-injection.
141+
> Execute the given SPARQL query (select/ask/construct) on the triplestore and returns the results in the given return Format (JSON by default).
115142

116-
Separate functions are available for different python datatypes, the `sparql_escape` function however can automatically select the right method to use, for following Python datatypes:
143+
<a id="helpers.update"></a>
144+
145+
#### `update`
146+
147+
```python
148+
def update(the_query)
149+
```
150+
151+
> Execute the given update SPARQL query on the triplestore. If the given query is not an update query, nothing happens.
152+
153+
<a id="helpers.update_modified"></a>
154+
155+
#### `update_modified`
156+
157+
```python
158+
def update_modified(subject, modified=datetime.datetime.now())
159+
```
160+
161+
> (DEPRECATED) Executes a SPARQL query to update the modification date of the given subject URI (string).
162+
> The default date is now.
163+
164+
<a id="escape_helpers.sparql_escape_string"></a>
165+
166+
#### `sparql_escape_string`
167+
168+
```python
169+
def sparql_escape_string(obj)
170+
```
171+
172+
> Converts the given string to a SPARQL-safe RDF object string with the right RDF-datatype.
173+
174+
<a id="escape_helpers.sparql_escape_datetime"></a>
175+
176+
#### `sparql_escape_datetime`
177+
178+
```python
179+
def sparql_escape_datetime(obj)
180+
```
117181

118-
- `str`
119-
- `int`
120-
- `float`
121-
- `datetime.datetime`
122-
- `datetime.date`
123-
- `datetime.time`
124-
- `boolean`
182+
> Converts the given datetime to a SPARQL-safe RDF object string with the right RDF-datatype.
125183

126-
The `sparql_escape_uri`-function can be used for escaping URI's.
184+
<a id="escape_helpers.sparql_escape_date"></a>
185+
186+
#### `sparql_escape_date`
187+
188+
```python
189+
def sparql_escape_date(obj)
190+
```
191+
192+
> Converts the given date to a SPARQL-safe RDF object string with the right RDF-datatype.
193+
194+
<a id="escape_helpers.sparql_escape_time"></a>
195+
196+
#### `sparql_escape_time`
197+
198+
```python
199+
def sparql_escape_time(obj)
200+
```
201+
202+
> Converts the given time to a SPARQL-safe RDF object string with the right RDF-datatype.
203+
204+
<a id="escape_helpers.sparql_escape_int"></a>
205+
206+
#### `sparql_escape_int`
207+
208+
```python
209+
def sparql_escape_int(obj)
210+
```
211+
212+
> Converts the given int to a SPARQL-safe RDF object string with the right RDF-datatype.
213+
214+
<a id="escape_helpers.sparql_escape_float"></a>
215+
216+
#### `sparql_escape_float`
217+
218+
```python
219+
def sparql_escape_float(obj)
220+
```
221+
222+
> Converts the given float to a SPARQL-safe RDF object string with the right RDF-datatype.
223+
224+
<a id="escape_helpers.sparql_escape_bool"></a>
225+
226+
#### `sparql_escape_bool`
227+
228+
```python
229+
def sparql_escape_bool(obj)
230+
```
231+
232+
> Converts the given bool to a SPARQL-safe RDF object string with the right RDF-datatype.
233+
234+
<a id="escape_helpers.sparql_escape_uri"></a>
235+
236+
#### `sparql_escape_uri`
237+
238+
```python
239+
def sparql_escape_uri(obj)
240+
```
241+
242+
> Converts the given URI to a SPARQL-safe RDF object string with the right RDF-datatype.
243+
244+
<a id="escape_helpers.sparql_escape"></a>
245+
246+
#### `sparql_escape`
247+
248+
```python
249+
def sparql_escape(obj)
250+
```
251+
252+
> Converts the given object to a SPARQL-safe RDF object string with the right RDF-datatype.
253+
>
254+
> These functions should be used especially when inserting user-input to avoid SPARQL-injection.
255+
> Separate functions are available for different python datatypes.
256+
> The `sparql_escape` function however can automatically select the right method to use, for the following Python datatypes:
257+
>
258+
> - `str`
259+
> - `int`
260+
> - `float`
261+
> - `datetime.datetime`
262+
> - `datetime.date`
263+
> - `datetime.time`
264+
> - `boolean`
265+
>
266+
> The `sparql_escape_uri`-function can be used for escaping URI's.
127267

128268
### Writing SPARQL Queries
129269

@@ -183,3 +323,14 @@ Since this template is based on the meinheld-gunicorn-docker image, all possible
183323
### Production
184324

185325
For hosting the app in a production setting, the template depends on [meinheld-gunicorn-docker](https://github.com/tiangolo/meinheld-gunicorn-docker). All [environment variables](https://github.com/tiangolo/meinheld-gunicorn-docker#environment-variables) used by meinheld-gunicorn can be used to configure your service as well.
326+
327+
## Other
328+
### readme.py
329+
To simplify documenting the helper functions, `README.py` can be used to import & render the docstrings into README.md.
330+
Usage:
331+
```python3
332+
python3 -m pip install pydoc-markdown
333+
python3 README.py
334+
```
335+
You can customise the output through the API configuration! See [README.py](README.py) && the [pydoc-markdown docs](https://niklasrosenstein.github.io/pydoc-markdown/).
336+

README.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from re import search, RegexFlag
2+
3+
from pydoc_markdown.interfaces import Context
4+
from pydoc_markdown.contrib.loaders.python import PythonLoader
5+
from pydoc_markdown.contrib.renderers.markdown import MarkdownRenderer, MarkdownReferenceResolver
6+
from pydoc_markdown.contrib.processors.filter import FilterProcessor
7+
8+
"""
9+
This script:
10+
- Extracts & renders docstrings from helpers & escape_helpers
11+
- Inserts that render into README.md in the right position, replacing the previous docstrings but nothing else
12+
"""
13+
14+
def open_readme(mode="r"):
15+
return open("README.md", mode=mode, encoding="UTF-8")
16+
17+
if __name__ == "__main__":
18+
19+
context = Context(directory=".")
20+
loader = PythonLoader(modules=["helpers", "escape_helpers"])
21+
renderer = MarkdownRenderer(render_module_header=False, insert_header_anchors=True, code_headers=True, render_typehint_in_data_header=True, docstrings_as_blockquote=True)
22+
23+
loader.init(context)
24+
renderer.init(context)
25+
26+
# https://github.com/ahopkins/mayim/blob/main/build_api_docs.py#L256
27+
modules = list(loader.load())
28+
29+
resolver = MarkdownReferenceResolver()
30+
31+
# https://github.com/NiklasRosenstein/pydoc-markdown/discussions/263#discussioncomment-3409760
32+
processor = FilterProcessor()
33+
processor.process(modules, resolver=resolver)
34+
35+
doc_string = renderer.render_to_string(modules)
36+
37+
38+
with open_readme("r") as readme:
39+
old_contents = readme.read()
40+
to_replace = search(r"### Helper methods((.|\n)*?)^###[^#]", old_contents, RegexFlag.MULTILINE).group(1)
41+
42+
43+
new_contents = old_contents.replace(to_replace, "\n" + doc_string)
44+
45+
with open_readme("w") as readme:
46+
readme.write(new_contents)
47+
48+

0 commit comments

Comments
 (0)