Skip to content

Commit 3a91a21

Browse files
authored
Add function_database option; add docstring parsing for parameter des… (#82)
* Add function_database option; add docstring parsing for parameter descriptions and examples
1 parent c61bce5 commit 3a91a21

26 files changed

+6199
-17
lines changed

docs/src/api.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ Stage Files
319319
...........
320320

321321
To interact with files in your Stage, use the
322-
:attr:`WorkspaceManager.stage` attribute.
322+
:attr:`WorkspaceGroup.stage` attribute.
323323
It will return a :class:`Stage` object which defines the following
324324
methods and attributes.
325325

singlestoredb/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,12 @@
419419
environ=['SINGLESTOREDB_EXT_FUNC_NAME_SUFFIX'],
420420
)
421421

422+
register_option(
423+
'external_function.function_database', 'string', check_str, '',
424+
'Database to use for the function definitions.',
425+
environ=['SINGLESTOREDB_EXT_FUNC_FUNCTION_DATABASE'],
426+
)
427+
422428
register_option(
423429
'external_function.connection', 'string', check_str,
424430
os.environ.get('SINGLESTOREDB_URL') or None,

singlestoredb/docstring/LICENSE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2018 Marcin Kurczewski
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

singlestoredb/docstring/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
docstring_parser
2+
================
3+
4+
[![Build](https://github.com/rr-/docstring_parser/actions/workflows/build.yml/badge.svg)](https://github.com/rr-/docstring_parser/actions/workflows/build.yml)
5+
6+
Parse Python docstrings. Currently support ReST, Google, Numpydoc-style and
7+
Epydoc docstrings.
8+
9+
Example usage:
10+
11+
```python
12+
>>> from docstring_parser import parse
13+
>>>
14+
>>>
15+
>>> docstring = parse(
16+
... '''
17+
... Short description
18+
...
19+
... Long description spanning multiple lines
20+
... - First line
21+
... - Second line
22+
... - Third line
23+
...
24+
... :param name: description 1
25+
... :param int priority: description 2
26+
... :param str sender: description 3
27+
... :raises ValueError: if name is invalid
28+
... ''')
29+
>>>
30+
>>> docstring.long_description
31+
'Long description spanning multiple lines\n- First line\n- Second line\n- Third line'
32+
>>> docstring.params[1].arg_name
33+
'priority'
34+
>>> docstring.raises[0].type_name
35+
'ValueError'
36+
```
37+
38+
Read [API Documentation](https://rr-.github.io/docstring_parser/).
39+
40+
# Installation
41+
42+
Installation using pip
43+
44+
```shell
45+
pip install docstring_parser
46+
47+
# or if you want to install it in a virtual environment
48+
49+
python -m venv venv # create environment
50+
source venv/bin/activate # activate environment
51+
python -m pip install docstring_parser
52+
```
53+
54+
Installation using conda
55+
56+
57+
1. Download and install miniconda or anaconda
58+
2. Install the package from the conda-forge channel via:
59+
- `conda install -c conda-forge docstring_parser`
60+
- or create a new conda environment via `conda create -n my-new-environment -c conda-forge docstring_parser`
61+
62+
63+
# Contributing
64+
65+
To set up the project:
66+
```sh
67+
git clone https://github.com/rr-/docstring_parser.git
68+
cd docstring_parser
69+
70+
python -m venv venv # create environment
71+
source venv/bin/activate # activate environment
72+
73+
pip install -e ".[dev]" # install as editable
74+
pre-commit install # make sure pre-commit is setup
75+
```
76+
77+
To run tests:
78+
```
79+
source venv/bin/activate
80+
pytest
81+
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Parse docstrings as per Sphinx notation."""
2+
from .common import Docstring
3+
from .common import DocstringDeprecated
4+
from .common import DocstringMeta
5+
from .common import DocstringParam
6+
from .common import DocstringRaises
7+
from .common import DocstringReturns
8+
from .common import DocstringStyle
9+
from .common import ParseError
10+
from .common import RenderingStyle
11+
from .parser import compose
12+
from .parser import parse
13+
from .parser import parse_from_object
14+
from .util import combine_docstrings
15+
16+
Style = DocstringStyle # backwards compatibility
17+
18+
__all__ = [
19+
'parse',
20+
'parse_from_object',
21+
'combine_docstrings',
22+
'compose',
23+
'ParseError',
24+
'Docstring',
25+
'DocstringMeta',
26+
'DocstringParam',
27+
'DocstringRaises',
28+
'DocstringReturns',
29+
'DocstringDeprecated',
30+
'DocstringStyle',
31+
'RenderingStyle',
32+
'Style',
33+
]

singlestoredb/docstring/attrdoc.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Attribute docstrings parsing.
2+
3+
.. seealso:: https://peps.python.org/pep-0257/#what-is-a-docstring
4+
"""
5+
import ast
6+
import inspect
7+
import textwrap
8+
import typing as T
9+
from types import ModuleType
10+
11+
from .common import Docstring
12+
from .common import DocstringParam
13+
14+
15+
def ast_get_constant_value(node: ast.AST) -> T.Any:
16+
"""Return the constant's value if the given node is a constant."""
17+
return getattr(node, 'value')
18+
19+
20+
def ast_unparse(node: ast.AST) -> T.Optional[str]:
21+
"""Convert the AST node to source code as a string."""
22+
if hasattr(ast, 'unparse'):
23+
return ast.unparse(node)
24+
# Support simple cases in Python < 3.9
25+
if isinstance(node, ast.Constant):
26+
return str(ast_get_constant_value(node))
27+
if isinstance(node, ast.Name):
28+
return node.id
29+
return None
30+
31+
32+
def ast_is_literal_str(node: ast.AST) -> bool:
33+
"""Return True if the given node is a literal string."""
34+
return (
35+
isinstance(node, ast.Expr)
36+
and isinstance(node.value, ast.Constant)
37+
and isinstance(ast_get_constant_value(node.value), str)
38+
)
39+
40+
41+
def ast_get_attribute(
42+
node: ast.AST,
43+
) -> T.Optional[T.Tuple[str, T.Optional[str], T.Optional[str]]]:
44+
"""Return name, type and default if the given node is an attribute."""
45+
if isinstance(node, (ast.Assign, ast.AnnAssign)):
46+
target = (
47+
node.targets[0] if isinstance(node, ast.Assign) else node.target
48+
)
49+
if isinstance(target, ast.Name):
50+
type_str = None
51+
if isinstance(node, ast.AnnAssign):
52+
type_str = ast_unparse(node.annotation)
53+
default = None
54+
if node.value:
55+
default = ast_unparse(node.value)
56+
return target.id, type_str, default
57+
return None
58+
59+
60+
class AttributeDocstrings(ast.NodeVisitor):
61+
"""An ast.NodeVisitor that collects attribute docstrings."""
62+
63+
attr_docs: T.Dict[str, T.Tuple[str, T.Optional[str], T.Optional[str]]] = {}
64+
prev_attr = None
65+
66+
def visit(self, node: T.Any) -> None:
67+
if self.prev_attr and ast_is_literal_str(node):
68+
attr_name, attr_type, attr_default = self.prev_attr
69+
self.attr_docs[attr_name] = (
70+
ast_get_constant_value(node.value),
71+
attr_type,
72+
attr_default,
73+
)
74+
self.prev_attr = ast_get_attribute(node)
75+
if isinstance(node, (ast.ClassDef, ast.Module)):
76+
self.generic_visit(node)
77+
78+
def get_attr_docs(
79+
self, component: T.Any,
80+
) -> T.Dict[str, T.Tuple[str, T.Optional[str], T.Optional[str]]]:
81+
"""Get attribute docstrings from the given component.
82+
83+
:param component: component to process (class or module)
84+
:returns: for each attribute docstring, a tuple with (description,
85+
type, default)
86+
"""
87+
self.attr_docs = {}
88+
self.prev_attr = None
89+
try:
90+
source = textwrap.dedent(inspect.getsource(component))
91+
except OSError:
92+
pass
93+
else:
94+
tree = ast.parse(source)
95+
if inspect.ismodule(component):
96+
self.visit(tree)
97+
elif isinstance(tree, ast.Module) and isinstance(
98+
tree.body[0], ast.ClassDef,
99+
):
100+
self.visit(tree.body[0])
101+
return self.attr_docs
102+
103+
104+
def add_attribute_docstrings(
105+
obj: T.Union[type, ModuleType], docstring: Docstring,
106+
) -> None:
107+
"""Add attribute docstrings found in the object's source code.
108+
109+
:param obj: object from which to parse attribute docstrings
110+
:param docstring: Docstring object where found attributes are added
111+
:returns: list with names of added attributes
112+
"""
113+
params = set(p.arg_name for p in docstring.params)
114+
for arg_name, (description, type_name, default) in (
115+
AttributeDocstrings().get_attr_docs(obj).items()
116+
):
117+
if arg_name not in params:
118+
param = DocstringParam(
119+
args=['attribute', arg_name],
120+
description=description,
121+
arg_name=arg_name,
122+
type_name=type_name,
123+
is_optional=default is not None,
124+
default=default,
125+
)
126+
docstring.meta.append(param)

0 commit comments

Comments
 (0)