Skip to content

Commit

Permalink
fix: fix sphinx identifiers (#714)
Browse files Browse the repository at this point in the history
Cross-references like `~.ImageAnnotatorClient` don't always work correctly with sphinx. This PR changes the `sphinx()` method to always produce a full path like `google.cloud.vision_v1.ImageAnnotatorClient`. 

Also some other smaller changes:
- Generate a separate `.rst` page for each service, which improves readability for APIs that have (1) a lot of services or (2) a lot of methods in a service. `services.rst` acts as an index page instead.
- Add pagers to the generated docs
- Use `undoc-members` to list enum attributes in generated docs (fixes #625) 
- Add newlines after bulleted lists by removing `nl=False`. Fixes #604 
- Add a 'docs' session to the templated `noxfile.py` so folks using the self-service model can have generated docs.
- Fix reference to LRO result type in `Returns:`
- Fix `{@api.name}` reference in the `from_service_account..`. methods  to reference the client type instead
- Remove `:class:` notation when specifying types for attributes (sphinx doesn't need it to create a link)
  • Loading branch information
busunkim96 authored Dec 22, 2020
1 parent edadb22 commit 39be474
Show file tree
Hide file tree
Showing 18 changed files with 110 additions and 34 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ htmlcov
# JetBrains
.idea

# VS Code
.vscode

# Built documentation
docs/_build
docs/_build_doc2dash
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
kwargs: Additional arguments to pass to the constructor.

Returns:
{@api.name}: The constructed client.
{{ service.client_name }}: The constructed client.
"""
credentials = service_account.Credentials.from_service_account_info(info)
kwargs["credentials"] = credentials
Expand All @@ -125,7 +125,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
kwargs: Additional arguments to pass to the constructor.

Returns:
{@api.name}: The constructed client.
{{ service.client_name }}: The constructed client.
"""
credentials = service_account.Credentials.from_service_account_file(
filename)
Expand Down Expand Up @@ -188,7 +188,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
transport (Union[str, ~.{{ service.name }}Transport]): The
transport to use. If set to None, a transport is chosen
automatically.
client_options (client_options_lib.ClientOptions): Custom options for the
client_options (google.api_core.client_options.ClientOptions): Custom options for the
client. It won't take effect if a ``transport`` instance is provided.
(1) The ``api_endpoint`` property can be used to override the
default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
Expand Down Expand Up @@ -306,7 +306,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
{{ method.input.meta.doc|wrap(width=72, offset=36, indent=16) }}
{% for key, field in method.flattened_fields.items() -%}
{{ field.name }} (:class:`{{ field.ident.sphinx }}`):
{{ field.meta.doc|rst(width=72, indent=16, nl=False) }}
{{ field.meta.doc|rst(width=72, indent=16) }}
This corresponds to the ``{{ key }}`` field
on the ``request`` instance; if ``request`` is provided, this
should not be set.
Expand All @@ -329,7 +329,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
{%- else %}
Iterable[{{ method.client_output.ident.sphinx }}]:
{%- endif %}
{{ method.client_output.meta.doc|rst(width=72, indent=16) }}
{{ method.client_output.meta.doc|rst(width=72, indent=16, source_format='rst') }}
{%- endif %}
"""
{%- if not method.client_streaming %}
Expand Down
1 change: 1 addition & 0 deletions gapic/ads-templates/docs/%name_%version/types.rst.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ Types for {{ api.naming.long_name }} {{ api.naming.version }} API

.. automodule:: {{ api.naming.namespace|join('.')|lower }}.{{ api.naming.versioned_module_name }}.types
:members:
:undoc-members:
3 changes: 3 additions & 0 deletions gapic/ads-templates/docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dl.field-list > dt {
min-width: 100px
}
2 changes: 1 addition & 1 deletion gapic/ads-templates/docs/conf.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = []
html_static_path = ["_static"]

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down
19 changes: 16 additions & 3 deletions gapic/schema/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,9 +173,22 @@ def python_import(self) -> imp.Import:
@property
def sphinx(self) -> str:
"""Return the Sphinx identifier for this type."""
if self.module:
return f'~.{self}'
return self.name

if not self.api_naming:
if self.package:
return '.'.join(self.package + (self.module, self.name))
else:
return str(self)

# Check if this is a generated type
# Use the original module name rather than the module_alias
if self.proto_package.startswith(self.api_naming.proto_package):
return '.'.join(self.api_naming.module_namespace + (
self.api_naming.versioned_module_name,
) + self.subpackage + ('types',) + self.parent + (self.name, ))

# Anything left is a standard _pb2 type
return f'{self.proto_package}.{self.module}_pb2.{self.name}'

@property
def subpackage(self) -> Tuple[str, ...]:
Expand Down
2 changes: 1 addition & 1 deletion gapic/schema/wrappers.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,7 +690,7 @@ def _client_output(self, enable_asyncio: bool):
documentation=utils.doc(
'An object representing a long-running operation. \n\n'
'The result type for the operation will be '
':class:`{ident}`: {doc}'.format(
':class:`{ident}` {doc}'.format(
doc=self.lro.response_type.meta.doc,
ident=self.lro.response_type.ident.sphinx,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ class {{ service.async_client_name }}:
{{ method.input.meta.doc|wrap(width=72, offset=36, indent=16) }}
{% for key, field in method.flattened_fields.items() -%}
{{ field.name }} (:class:`{{ field.ident.sphinx }}`):
{{ field.meta.doc|rst(width=72, indent=16, nl=False) }}
{{ field.meta.doc|rst(width=72, indent=16) }}
This corresponds to the ``{{ key }}`` field
on the ``request`` instance; if ``request`` is provided, this
should not be set.
Expand All @@ -163,7 +163,7 @@ class {{ service.async_client_name }}:
{%- else %}
AsyncIterable[{{ method.client_output_async.ident.sphinx }}]:
{%- endif %}
{{ method.client_output_async.meta.doc|rst(width=72, indent=16) }}
{{ method.client_output_async.meta.doc|rst(width=72, indent=16, source_format='rst') }}
{%- endif %}
"""
{%- if not method.client_streaming %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
kwargs: Additional arguments to pass to the constructor.

Returns:
{@api.name}: The constructed client.
{{ service.client_name }}: The constructed client.
"""
credentials = service_account.Credentials.from_service_account_info(info)
kwargs["credentials"] = credentials
Expand All @@ -141,7 +141,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
kwargs: Additional arguments to pass to the constructor.

Returns:
{@api.name}: The constructed client.
{{ service.client_name }}: The constructed client.
"""
credentials = service_account.Credentials.from_service_account_file(
filename)
Expand Down Expand Up @@ -202,10 +202,10 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
credentials identify the application to the service; if none
are specified, the client will attempt to ascertain the
credentials from the environment.
transport (Union[str, ~.{{ service.name }}Transport]): The
transport (Union[str, {{ service.name }}Transport]): The
transport to use. If set to None, a transport is chosen
automatically.
client_options (client_options_lib.ClientOptions): Custom options for the
client_options (google.api_core.client_options.ClientOptions): Custom options for the
client. It won't take effect if a ``transport`` instance is provided.
(1) The ``api_endpoint`` property can be used to override the
default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT
Expand Down Expand Up @@ -322,18 +322,18 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):

Args:
{%- if not method.client_streaming %}
request (:class:`{{ method.input.ident.sphinx }}`):
request ({{ method.input.ident.sphinx }}):
The request object.{{ ' ' -}}
{{ method.input.meta.doc|wrap(width=72, offset=36, indent=16) }}
{% for key, field in method.flattened_fields.items() -%}
{{ field.name }} (:class:`{{ field.ident.sphinx }}`):
{{ field.meta.doc|rst(width=72, indent=16, nl=False) }}
{{ field.name }} ({{ field.ident.sphinx }}):
{{ field.meta.doc|rst(width=72, indent=16) }}
This corresponds to the ``{{ key }}`` field
on the ``request`` instance; if ``request`` is provided, this
should not be set.
{% endfor -%}
{%- else %}
requests (Iterator[`{{ method.input.ident.sphinx }}`]):
requests (Iterator[{{ method.input.ident.sphinx }}]):
The request object iterator.{{ ' ' -}}
{{ method.input.meta.doc|wrap(width=72, offset=36, indent=16) }}
{%- endif %}
Expand All @@ -350,7 +350,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
{%- else %}
Iterable[{{ method.client_output.ident.sphinx }}]:
{%- endif %}
{{ method.client_output.meta.doc|rst(width=72, indent=16) }}
{{ method.client_output.meta.doc|rst(width=72, indent=16, source_format='rst') }}
{%- endif %}
"""
{%- if not method.client_streaming %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ class {{ method.name }}Pager:
Args:
method (Callable): The method that was originally called, and
which instantiated this pager.
request (:class:`{{ method.input.ident.sphinx }}`):
request ({{ method.input.ident.sphinx }}):
The initial request object.
response (:class:`{{ method.output.ident.sphinx }}`):
response ({{ method.output.ident.sphinx }}):
The initial response object.
metadata (Sequence[Tuple[str, str]]): Strings which should be
sent along with the request as metadata.
Expand Down Expand Up @@ -104,9 +104,9 @@ class {{ method.name }}AsyncPager:
Args:
method (Callable): The method that was originally called, and
which instantiated this pager.
request (:class:`{{ method.input.ident.sphinx }}`):
request ({{ method.input.ident.sphinx }}):
The initial request object.
response (:class:`{{ method.output.ident.sphinx }}`):
response ({{ method.output.ident.sphinx }}):
The initial response object.
metadata (Sequence[Tuple[str, str]]): Strings which should be
sent along with the request as metadata.
Expand Down
12 changes: 12 additions & 0 deletions gapic/templates/docs/%name_%version/%service.rst.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{{ service.name }}
{{ '-' * (18 + service.name|length) }}

.. automodule:: {{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join(".") }}.services.{{ service.name|snake_case }}
:members:
:inherited-members:

{% if service.has_pagers %}
.. automodule:: {{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join(".") }}.services.{{ service.name|snake_case }}.pagers
:members:
:inherited-members:
{% endif %}
10 changes: 5 additions & 5 deletions gapic/templates/docs/%name_%version/services.rst.j2
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Services for {{ api.naming.long_name }} {{ api.naming.version }} API
{{ '=' * (18 + api.naming.long_name|length + api.naming.version|length) }}
.. toctree::
:maxdepth: 2

{% for service in api.services.values()|sort(attribute='name') -%}
.. automodule:: {{ (api.naming.module_namespace + (api.naming.versioned_module_name,) + service.meta.address.subpackage)|join(".") }}.services.{{ service.name|snake_case }}
:members:
:inherited-members:
{% endfor %}
{% for service in api.services.values()|sort(attribute='name') -%}
{{ service.name|snake_case }}
{% endfor %}
1 change: 1 addition & 0 deletions gapic/templates/docs/%name_%version/types.rst.j2
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ Types for {{ api.naming.long_name }} {{ api.naming.version }} API

.. automodule:: {{ api.naming.namespace|join('.')|lower }}.{{ api.naming.versioned_module_name }}.types
:members:
:undoc-members:
:show-inheritance:
3 changes: 3 additions & 0 deletions gapic/templates/docs/_static/custom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
dl.field-list > dt {
min-width: 100px
}
3 changes: 2 additions & 1 deletion gapic/templates/docs/conf.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ html_theme_options = {
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = []
html_static_path = ["_static"]

# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
Expand Down Expand Up @@ -347,6 +347,7 @@ intersphinx_mapping = {
"grpc": ("https://grpc.io/grpc/python/", None),
"requests": ("http://requests.kennethreitz.org/en/stable/", None),
"proto": ("https://proto-plus-python.readthedocs.io/en/stable", None),
"protobuf": ("https://googleapis.dev/python/protobuf/latest/", None),
}


Expand Down
22 changes: 22 additions & 0 deletions gapic/templates/noxfile.py.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

{% block content %}
import os
import shutil

import nox # type: ignore

Expand Down Expand Up @@ -37,4 +38,25 @@ def mypy(session):
'{{ api.naming.versioned_module_name }}',
{%- endif %}
)

@nox.session(python='3.6')
def docs(session):
"""Build the docs for this library."""

session.install("-e", ".")
session.install("sphinx<3.0.0", "alabaster", "recommonmark")
shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True)
session.run(
"sphinx-build",
"-W", # warnings as errors
"-T", # show full traceback on exception
"-N", # no colors
"-b",
"html",
"-d",
os.path.join("docs", "_build", "doctrees", ""),
os.path.join("docs", ""),
os.path.join("docs", "_build", "html", ""),
)
{% endblock %}
2 changes: 1 addition & 1 deletion tests/unit/schema/wrappers/test_enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,4 @@ def test_enum_value_properties():
def test_enum_ident():
message = make_enum('Baz', package='foo.v1', module='bar')
assert str(message.ident) == 'bar.Baz'
assert message.ident.sphinx == '~.bar.Baz'
assert message.ident.sphinx == 'foo.v1.bar.Baz'
21 changes: 19 additions & 2 deletions tests/unit/schema/wrappers/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from google.api import resource_pb2
from google.protobuf import descriptor_pb2

from gapic.schema import naming
from gapic.schema import metadata
from gapic.schema import wrappers

Expand Down Expand Up @@ -50,15 +51,31 @@ def test_message_docstring():
def test_message_ident():
message = make_message('Baz', package='foo.v1', module='bar')
assert str(message.ident) == 'bar.Baz'
assert message.ident.sphinx == '~.bar.Baz'
assert message.ident.sphinx == 'foo.v1.bar.Baz'


def test_message_ident_collisions():
message = make_message('Baz', package='foo.v1', module='bar').with_context(
collisions={'bar'},
)
assert str(message.ident) == 'fv_bar.Baz'
assert message.ident.sphinx == '~.fv_bar.Baz'
assert message.ident.sphinx == 'foo.v1.bar.Baz'


def test_message_pb2_sphinx_ident():
meta = metadata.Metadata(
address=metadata.Address(
name='Timestamp',
package=('google', 'protobuf'),
module='timestamp',
api_naming=naming.NewNaming(
proto_package="foo.bar"
)
)
)
message = make_message("Timestamp", package='google.protobuf',
module='timestamp', meta=meta)
assert message.ident.sphinx == 'google.protobuf.timestamp_pb2.Timestamp'


def test_get_field():
Expand Down

0 comments on commit 39be474

Please sign in to comment.