Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add attrs export to python client #262

Merged
merged 12 commits into from
Jan 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 31 additions & 5 deletions stone/backends/python_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@
type=str,
help='The auth type of the client to generate.',
)
_cmdline_parser.add_argument(
'-a',
'--attribute-comment',
action='append',
type=str,
default=[],
help=('Route attributes that the backend will have access to and '
'presumably expose in generated code. Use ":all" to select all '
'attributes defined in stone_cfg.Route. Attributes will be '
"exposed in the documentation, as the client doesn't use them."),
)


class PythonClientBackend(CodeBackend):
Expand Down Expand Up @@ -259,6 +270,7 @@ def _generate_route_helper(self, namespace, route, download_to_file=False):
extra_request_args=extra_request_args,
extra_return_arg=extra_return_arg,
footer=footer,
attrs=route.attrs,
)

self._maybe_generate_deprecation_warning(route)
Expand Down Expand Up @@ -356,7 +368,7 @@ def _maybe_generate_deprecation_warning(self, route):
def _generate_docstring_for_func(self, namespace, arg_data_type,
result_data_type=None, error_data_type=None,
overview=None, extra_request_args=None,
extra_return_arg=None, footer=None):
extra_return_arg=None, footer=None, attrs=None):
"""
Generates a docstring for a function or method.

Expand All @@ -380,7 +392,14 @@ def _generate_docstring_for_func(self, namespace, arg_data_type,
:param str footer: Additional notes at the end of the docstring.
"""
fields = [] if is_void_type(arg_data_type) else arg_data_type.fields
if not fields and not overview:

attrs_lines = []
if self.args.attribute_comment and attrs:
for attribute in self.args.attribute_comment:
if attribute in attrs:
attrs_lines.append('{}: {}'.format(attribute, attrs[attribute]))

if not fields and not overview and not attrs_lines:
# If we don't have an overview or any input parameters, we skip the
# docstring altogether.
return
Expand All @@ -389,10 +408,17 @@ def _generate_docstring_for_func(self, namespace, arg_data_type,
if overview:
self.emit_wrapped_text(overview)

if attrs_lines:
if overview:
self.emit()
self.emit('Route attributes:')
for a in attrs_lines:
self.emit_wrapped_text(a, ' ')

# Description of all input parameters
if extra_request_args or fields:
if overview:
# Add a blank line if we had an overview
if overview or attrs_lines:
# Add a blank line if we had an overview or attrs
self.emit()

if extra_request_args:
Expand Down Expand Up @@ -446,7 +472,7 @@ def _generate_docstring_for_func(self, namespace, arg_data_type,
self.emit(':type arg: {}'.format(
self._format_type_in_doc(namespace, arg_data_type)))

if overview and not (extra_request_args or fields):
if (overview or attrs_lines) and not (extra_request_args or fields):
# Only output an empty line if we had an overview and haven't
# started a section on declaring types.
self.emit()
Expand Down
115 changes: 114 additions & 1 deletion test/test_python_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
Nullable,
String,
Void,
StructField,
Struct,
)

MYPY = False
Expand All @@ -27,7 +29,8 @@ def _evaluate_namespace(self, ns):

backend = PythonClientBackend(
target_folder_path='output',
args=['-m', 'files', '-c', 'DropboxBase', '-t', 'dropbox'])
args=['-a', 'scope', '-a', 'another_attribute',
'-m', 'files', '-c', 'DropboxBase', '-t', 'dropbox'])
backend._generate_routes(ns)
return backend.output_buffer_to_string()

Expand Down Expand Up @@ -218,4 +221,114 @@ def test_route_argument_doc_string(self):
self.assertEqual(backend._format_type_in_doc(ns, Map(String(), Int32())),
'Map[str, int]')

def test_route_with_attributes_in_docstring(self):
# type: () -> None

route = ApiRoute('get_metadata', 1, None)
route.set_attributes(None, None, Void(), Void(), Void(), {
'scope': 'events.read', 'another_attribute': 'foo'
})
ns = ApiNamespace('files')
ns.add_route(route)

result = self._evaluate_namespace(ns)
expected = textwrap.dedent('''\
def files_get_metadata(self):
"""
Route attributes:
scope: events.read
another_attribute: foo

:rtype: None
"""
arg = None
r = self.request(
files.get_metadata,
'files',
arg,
None,
)
return None

''')
self.assertEqual(result, expected)

def test_route_with_attributes_and_doc_in_docstring(self):
# type: () -> None
"""
In particular make sure there's spacing b/w overview and attrs.
"""

route = ApiRoute('get_metadata', 1, None)
route.set_attributes(None, "Test string.", Void(), Void(), Void(),
{'scope': 'events.read'})
ns = ApiNamespace('files')
ns.add_route(route)

result = self._evaluate_namespace(ns)
expected = textwrap.dedent('''\
def files_get_metadata(self):
"""
Test string.

Route attributes:
scope: events.read
sderickson marked this conversation as resolved.
Show resolved Hide resolved

:rtype: None
"""
arg = None
r = self.request(
files.get_metadata,
'files',
arg,
None,
)
return None

''')
self.assertEqual(result, expected)

def test_route_with_doc_and_attribute_and_data_types(self):
# type: () -> None
ns = ApiNamespace('files')
struct = Struct('MyStruct', ns, None)
struct.set_attributes(None, [
StructField('field1', Int32(), None, None),
StructField('field2', Int32(), None, None),
])

route = ApiRoute('test/route', 1, None)
route.set_attributes(
None, "Test string.", struct, Int32(), Void(), {'scope': 'events.read'}
)
ns.add_route(route)

result = self._evaluate_namespace(ns)
expected = textwrap.dedent('''\
def files_test_route(self,
field1,
field2):
"""
Test string.

Route attributes:
scope: events.read

:type field1: int
:type field2: int
:rtype: int
"""
arg = files.MyStruct(field1,
field2)
r = self.request(
files.test_route,
'files',
arg,
None,
)
return r

''')
self.assertEqual(result, expected)

# TODO: add more unit tests for client code generation