diff --git a/CHANGELOG.md b/CHANGELOG.md index a4ee51ff14..e3ad3d4eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +### [0.34.3](https://www.github.com/googleapis/gapic-generator-python/compare/v0.34.2...v0.34.3) (2020-10-08) + + +### Bug Fixes + +* fix types on server and bidi streaming callables ([#641](https://www.github.com/googleapis/gapic-generator-python/issues/641)) ([d92c202](https://www.github.com/googleapis/gapic-generator-python/commit/d92c2029398c969ebf2a68a5bf77c5eb4fff7b31)) + ### [0.34.2](https://www.github.com/googleapis/gapic-generator-python/compare/v0.34.1...v0.34.2) (2020-09-30) diff --git a/docs/reference/generator.rst b/docs/reference/generator.rst index 088a092326..eb2192fb2c 100644 --- a/docs/reference/generator.rst +++ b/docs/reference/generator.rst @@ -5,6 +5,3 @@ generator .. automodule:: gapic.generator.generator :members: - -.. automodule:: gapic.generator.options - :members: diff --git a/gapic/cli/generate.py b/gapic/cli/generate.py index 9ca2f82174..b0e50ccf2e 100644 --- a/gapic/cli/generate.py +++ b/gapic/cli/generate.py @@ -22,7 +22,7 @@ from gapic import generator from gapic.schema import api -from gapic.generator import options +from gapic.utils import Options @click.command() @@ -41,7 +41,7 @@ def generate( req = plugin_pb2.CodeGeneratorRequest.FromString(request.read()) # Pull apart arguments in the request. - opts = options.Options.build(req.parameter) + opts = Options.build(req.parameter) # Determine the appropriate package. # This generator uses a slightly different mechanism for determining diff --git a/gapic/generator/generator.py b/gapic/generator/generator.py index 8cc380e105..5bd8a4f3c9 100644 --- a/gapic/generator/generator.py +++ b/gapic/generator/generator.py @@ -22,10 +22,10 @@ from gapic.samplegen_utils.utils import coerce_response_name, is_valid_sample_cfg from gapic.samplegen_utils.types import DuplicateSample from gapic.samplegen import manifest, samplegen -from gapic.generator import options from gapic.generator import formatter from gapic.schema import api from gapic import utils +from gapic.utils import Options from google.protobuf.compiler.plugin_pb2 import CodeGeneratorResponse @@ -43,7 +43,7 @@ class Generator: this application are used. """ - def __init__(self, opts: options.Options) -> None: + def __init__(self, opts: Options) -> None: # Create the jinja environment with which to render templates. self._env = jinja2.Environment( loader=jinja2.FileSystemLoader(searchpath=opts.templates), @@ -61,7 +61,7 @@ def __init__(self, opts: options.Options) -> None: self._sample_configs = opts.sample_configs def get_response( - self, api_schema: api.API, opts: options.Options + self, api_schema: api.API, opts: Options ) -> CodeGeneratorResponse: """Return a :class:`~.CodeGeneratorResponse` for this library. @@ -209,7 +209,7 @@ def _generate_samples_and_manifest( return output_files def _render_template( - self, template_name: str, *, api_schema: api.API, opts: options.Options, + self, template_name: str, *, api_schema: api.API, opts: Options, ) -> Dict[str, CodeGeneratorResponse.File]: """Render the requested templates. @@ -297,7 +297,7 @@ def _get_file( self, template_name: str, *, - opts: options.Options, + opts: Options, api_schema=api.API, **context: Mapping, ): diff --git a/gapic/schema/api.py b/gapic/schema/api.py index a0c878517c..92c2b741c0 100644 --- a/gapic/schema/api.py +++ b/gapic/schema/api.py @@ -30,12 +30,12 @@ import grpc # type: ignore -from gapic.generator import options from gapic.schema import metadata from gapic.schema import wrappers from gapic.schema import naming as api_naming from gapic.utils import cached_property from gapic.utils import nth +from gapic.utils import Options from gapic.utils import to_snake_case from gapic.utils import RESERVED_NAMES @@ -60,7 +60,7 @@ def __getattr__(self, name: str): def build( cls, file_descriptor: descriptor_pb2.FileDescriptorProto, file_to_generate: bool, naming: api_naming.Naming, - opts: options.Options = options.Options(), + opts: Options = Options(), prior_protos: Mapping[str, 'Proto'] = None, load_services: bool = True ) -> 'Proto': @@ -201,7 +201,7 @@ def build( cls, file_descriptors: Sequence[descriptor_pb2.FileDescriptorProto], package: str = '', - opts: options.Options = options.Options(), + opts: Options = Options(), prior_protos: Mapping[str, 'Proto'] = None, ) -> 'API': """Build the internal API schema based on the request. @@ -388,7 +388,7 @@ def __init__( file_descriptor: descriptor_pb2.FileDescriptorProto, file_to_generate: bool, naming: api_naming.Naming, - opts: options.Options = options.Options(), + opts: Options = Options(), prior_protos: Mapping[str, Proto] = None, load_services: bool = True ): diff --git a/gapic/schema/naming.py b/gapic/schema/naming.py index 21a075b95d..c591ad59cc 100644 --- a/gapic/schema/naming.py +++ b/gapic/schema/naming.py @@ -21,7 +21,7 @@ from google.protobuf import descriptor_pb2 from gapic import utils -from gapic.generator import options +from gapic.utils import Options # See https://github.com/python/mypy/issues/5374 for details on the mypy false # positive. @@ -50,7 +50,7 @@ def __post_init__(self): @staticmethod def build( *file_descriptors: descriptor_pb2.FileDescriptorProto, - opts: options.Options = options.Options(), + opts: Options = Options(), ) -> 'Naming': """Return a full Naming instance based on these file descriptors. diff --git a/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 b/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 index 14be05325d..bdc7ce4d0d 100644 --- a/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 +++ b/gapic/templates/%namespace/%name_%version/%sub/services/%service/async_client.py.j2 @@ -4,7 +4,7 @@ from collections import OrderedDict import functools import re -from typing import Dict, {% if service.any_server_streaming %}AsyncIterable, {% endif %}{% if service.any_client_streaming %}AsyncIterator, {% endif %}Sequence, Tuple, Type, Union +from typing import Dict, {% if service.any_server_streaming %}AsyncIterable, Awaitable, {% endif %}{% if service.any_client_streaming %}AsyncIterator, {% endif %}Sequence, Tuple, Type, Union import pkg_resources import google.api_core.client_options as ClientOptions # type: ignore @@ -117,7 +117,7 @@ class {{ service.async_client_name }}: {%- if not method.server_streaming %} ) -> {{ method.client_output_async.ident }}: {%- else %} - ) -> AsyncIterable[{{ method.client_output_async.ident }}]: + ) -> Awaitable[AsyncIterable[{{ method.client_output_async.ident }}]]: {%- endif %} r"""{{ method.meta.doc|rst(width=72, indent=8) }} diff --git a/gapic/utils/__init__.py b/gapic/utils/__init__.py index 905fcbdec2..9719a8f7a2 100644 --- a/gapic/utils/__init__.py +++ b/gapic/utils/__init__.py @@ -22,6 +22,7 @@ from gapic.utils.filename import to_valid_module_name from gapic.utils.lines import sort_lines from gapic.utils.lines import wrap +from gapic.utils.options import Options from gapic.utils.reserved_names import RESERVED_NAMES from gapic.utils.rst import rst @@ -31,6 +32,7 @@ 'doc', 'empty', 'nth', + 'Options', 'partition', 'RESERVED_NAMES', 'rst', diff --git a/gapic/generator/options.py b/gapic/utils/options.py similarity index 100% rename from gapic/generator/options.py rename to gapic/utils/options.py diff --git a/requirements.txt b/requirements.txt index ed7f530ffa..2422d03832 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ click==7.1.2 -google-api-core==1.22.2 +google-api-core==1.22.4 googleapis-common-protos==1.52.0 jinja2==2.11.2 MarkupSafe==1.1.1 diff --git a/setup.py b/setup.py index 7d6c9685ae..bd032f056c 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ PACKAGE_ROOT = os.path.abspath(os.path.dirname(__file__)) -version = "0.34.2" +version = "0.34.3" with io.open(os.path.join(PACKAGE_ROOT, "README.rst")) as file_obj: README = file_obj.read() diff --git a/tests/unit/generator/test_generator.py b/tests/unit/generator/test_generator.py index ede00ba51a..6a120bbe1d 100644 --- a/tests/unit/generator/test_generator.py +++ b/tests/unit/generator/test_generator.py @@ -23,16 +23,16 @@ from google.protobuf.compiler.plugin_pb2 import CodeGeneratorResponse from gapic.generator import generator -from gapic.generator import options from gapic.samplegen_utils import types, yaml from gapic.schema import api from gapic.schema import naming from gapic.schema import wrappers +from gapic.utils import Options def test_custom_template_directory(): # Create a generator. - opts = options.Options.build("python-gapic-templates=/templates/") + opts = Options.build("python-gapic-templates=/templates/") g = generator.Generator(opts) # Assert that the Jinja loader will pull from the correct location. @@ -46,7 +46,7 @@ def test_get_response(): with mock.patch.object(jinja2.Environment, "get_template") as gt: gt.return_value = jinja2.Template("I am a template result.") cgr = g.get_response(api_schema=make_api(), - opts=options.Options.build("")) + opts=Options.build("")) lt.assert_called_once() gt.assert_has_calls( [ @@ -66,7 +66,7 @@ def test_get_response_ignores_empty_files(): with mock.patch.object(jinja2.Environment, "get_template") as gt: gt.return_value = jinja2.Template("# Meaningless comment") cgr = g.get_response(api_schema=make_api(), - opts=options.Options.build("")) + opts=Options.build("")) lt.assert_called_once() gt.assert_has_calls( [ @@ -88,7 +88,7 @@ def test_get_response_ignores_private_files(): with mock.patch.object(jinja2.Environment, "get_template") as gt: gt.return_value = jinja2.Template("I am a template result.") cgr = g.get_response(api_schema=make_api(), - opts=options.Options.build("")) + opts=Options.build("")) lt.assert_called_once() gt.assert_has_calls( [ @@ -110,7 +110,7 @@ def test_get_response_fails_invalid_file_paths(): ] with pytest.raises(ValueError) as ex: g.get_response(api_schema=make_api(), - opts=options.Options.build("")) + opts=Options.build("")) ex_str = str(ex.value) assert "%proto" in ex_str and "%service" in ex_str @@ -139,7 +139,7 @@ def test_get_response_enumerates_services(): ), ) ), - opts=options.Options.build(""), + opts=Options.build(""), ) assert len(cgr.file) == 2 assert {i.name for i in cgr.file} == { @@ -164,7 +164,7 @@ def test_get_response_enumerates_proto(): make_proto( descriptor_pb2.FileDescriptorProto(name="b.proto")), ), - opts=options.Options.build(""), + opts=Options.build(""), ) assert len(cgr.file) == 2 assert {i.name for i in cgr.file} == {"foo/a.py", "foo/b.py"} @@ -206,7 +206,7 @@ def test_get_response_divides_subpackages(): """.strip() ) cgr = g.get_response(api_schema=api_schema, - opts=options.Options.build("")) + opts=Options.build("")) assert len(cgr.file) == 6 assert {i.name for i in cgr.file} == { "foo/types/top.py", @@ -327,7 +327,7 @@ def test_parse_sample_paths(fs): ) with pytest.raises(types.InvalidConfig): - options.Options.build("samples=sampledir/,") + Options.build("samples=sampledir/,") @mock.patch( @@ -365,14 +365,14 @@ def test_samplegen_config_to_output_files( ), ) - g = generator.Generator(options.Options.build("samples=samples.yaml",)) + g = generator.Generator(Options.build("samples=samples.yaml",)) # Need to have the sample template visible to the generator. g._env.loader = jinja2.DictLoader({"sample.py.j2": ""}) api_schema = make_api(naming=naming.NewNaming( name="Mollusc", version="v6")) actual_response = g.get_response( - api_schema, opts=options.Options.build("")) + api_schema, opts=Options.build("")) expected_response = CodeGeneratorResponse( file=[ CodeGeneratorResponse.File( @@ -451,14 +451,14 @@ def test_samplegen_id_disambiguation(mock_gmtime, mock_generate_sample, fs): """ ), ) - g = generator.Generator(options.Options.build("samples=samples.yaml")) + g = generator.Generator(Options.build("samples=samples.yaml")) # Need to have the sample template visible to the generator. g._env.loader = jinja2.DictLoader({"sample.py.j2": ""}) api_schema = make_api(naming=naming.NewNaming( name="Mollusc", version="v6")) actual_response = g.get_response(api_schema, - opts=options.Options.build("")) + opts=Options.build("")) expected_response = CodeGeneratorResponse( file=[ CodeGeneratorResponse.File( @@ -532,7 +532,7 @@ def test_generator_duplicate_samples(fs): with pytest.raises(types.DuplicateSample): generator.get_response(api_schema=api_schema, - opts=options.Options.build("")) + opts=Options.build("")) @mock.patch("gapic.samplegen.samplegen.generate_sample", return_value="") @@ -637,13 +637,13 @@ def test_dont_generate_in_code_samples(mock_gmtime, mock_generate_sample, fs): expected.supported_features |= CodeGeneratorResponse.Feature.FEATURE_PROTO3_OPTIONAL actual = generator.get_response( - api_schema=api_schema, opts=options.Options.build("") + api_schema=api_schema, opts=Options.build("") ) assert actual == expected def make_generator(opts_str: str = "") -> generator.Generator: - return generator.Generator(options.Options.build(opts_str)) + return generator.Generator(Options.build(opts_str)) def make_proto( diff --git a/tests/unit/generator/test_options.py b/tests/unit/generator/test_options.py index e4bac805ee..5235c2e453 100644 --- a/tests/unit/generator/test_options.py +++ b/tests/unit/generator/test_options.py @@ -17,12 +17,12 @@ from unittest import mock import warnings -from gapic.generator import options from gapic.samplegen_utils import types +from gapic.utils import Options def test_options_empty(): - opts = options.Options.build('') + opts = Options.build('') assert len(opts.templates) == 1 assert opts.templates[0].endswith('gapic/templates') assert not opts.lazy_import @@ -30,13 +30,13 @@ def test_options_empty(): def test_options_replace_templates(): - opts = options.Options.build('python-gapic-templates=/foo/') + opts = Options.build('python-gapic-templates=/foo/') assert len(opts.templates) == 1 assert opts.templates[0] == '/foo' def test_options_relative_templates(): - opts = options.Options.build('python-gapic-templates=../../squid/clam') + opts = Options.build('python-gapic-templates=../../squid/clam') expected = (os.path.abspath('../squid/clam'),) assert opts.templates == expected @@ -44,26 +44,26 @@ def test_options_relative_templates(): def test_options_unrecognized(): with mock.patch.object(warnings, 'warn') as warn: - options.Options.build('python-gapic-abc=xyz') + Options.build('python-gapic-abc=xyz') warn.assert_called_once_with('Unrecognized option: `python-gapic-abc`.') def test_flags_unrecognized(): with mock.patch.object(warnings, 'warn') as warn: - options.Options.build('python-gapic-abc') + Options.build('python-gapic-abc') warn.assert_called_once_with('Unrecognized option: `python-gapic-abc`.') def test_options_unrecognized_likely_typo(): with mock.patch.object(warnings, 'warn') as warn: - options.Options.build('go-gapic-abc=xyz') + Options.build('go-gapic-abc=xyz') assert len(warn.mock_calls) == 0 def test_options_trim_whitespace(): # When writing shell scripts, users may construct options strings with # whitespace that needs to be trimmed after tokenizing. - opts = options.Options.build( + opts = Options.build( ''' python-gapic-templates=/squid/clam/whelk , python-gapic-name=mollusca , @@ -75,11 +75,11 @@ def test_options_trim_whitespace(): def test_options_no_valid_sample_config(fs): fs.create_file("sampledir/not_a_config.yaml") with pytest.raises(types.InvalidConfig): - options.Options.build("samples=sampledir/") + Options.build("samples=sampledir/") def test_options_service_config(fs): - opts = options.Options.build("") + opts = Options.build("") assert opts.retry is None # Default of None is okay, verify build can read a config. @@ -109,7 +109,7 @@ def test_options_service_config(fs): }""") opt_string = f"retry-config={service_config_fpath}" - opts = options.Options.build(opt_string) + opts = Options.build(opt_string) # Verify the config was read in correctly. expected_cfg = { @@ -140,15 +140,15 @@ def test_options_service_config(fs): def test_options_lazy_import(): - opts = options.Options.build('lazy-import') + opts = Options.build('lazy-import') assert opts.lazy_import def test_options_old_naming(): - opts = options.Options.build('old-naming') + opts = Options.build('old-naming') assert opts.old_naming def test_options_add_iam_methods(): - opts = options.Options.build('add-iam-methods') + opts = Options.build('add-iam-methods') assert opts.add_iam_methods diff --git a/tests/unit/schema/test_api.py b/tests/unit/schema/test_api.py index b3f023054c..fbe82a8f9b 100644 --- a/tests/unit/schema/test_api.py +++ b/tests/unit/schema/test_api.py @@ -22,11 +22,11 @@ from google.longrunning import operations_pb2 from google.protobuf import descriptor_pb2 -from gapic.generator import options from gapic.schema import api from gapic.schema import imp from gapic.schema import naming from gapic.schema import wrappers +from gapic.utils import Options from test_utils.test_utils import ( make_enum_pb2, @@ -737,7 +737,7 @@ def _n(method_name: str): } # Set up retry information. - opts = options.Options(retry={'methodConfig': [ + opts = Options(retry={'methodConfig': [ {'name': [_n('TimeoutableGetFoo')], 'timeout': '30s'}, {'name': [_n('RetryableGetFoo')], 'retryPolicy': { 'maxAttempts': 3, diff --git a/tests/unit/schema/test_naming.py b/tests/unit/schema/test_naming.py index 8418f37300..ec1e0dad64 100644 --- a/tests/unit/schema/test_naming.py +++ b/tests/unit/schema/test_naming.py @@ -16,8 +16,8 @@ from google.protobuf import descriptor_pb2 -from gapic.generator import options from gapic.schema import naming +from gapic.utils import Options from test_utils.test_utils import make_naming @@ -151,7 +151,7 @@ def test_cli_override_name(): FileDesc = descriptor_pb2.FileDescriptorProto proto1 = FileDesc(package='google.cloud.videointelligence.v1') n = naming.Naming.build(proto1, - opts=options.Options(name='Video Intelligence'), + opts=Options(name='Video Intelligence'), ) assert n.namespace == ('Google', 'Cloud') assert n.name == 'Video Intelligence' @@ -162,7 +162,7 @@ def test_cli_override_name_underscores(): FileDesc = descriptor_pb2.FileDescriptorProto proto1 = FileDesc(package='google.cloud.videointelligence.v1') n = naming.Naming.build(proto1, - opts=options.Options(name='video_intelligence'), + opts=Options(name='video_intelligence'), ) assert n.namespace == ('Google', 'Cloud') assert n.name == 'Video Intelligence' @@ -174,7 +174,7 @@ def test_cli_override_namespace(): proto1 = FileDesc(package='google.spanner.v1') n = naming.Naming.build( proto1, - opts=options.Options(namespace=('google', 'cloud')), + opts=Options(namespace=('google', 'cloud')), ) assert n.namespace == ('Google', 'Cloud') assert n.name == 'Spanner' @@ -185,7 +185,7 @@ def test_cli_override_namespace_dotted(): FileDesc = descriptor_pb2.FileDescriptorProto proto1 = FileDesc(package='google.spanner.v1') n = naming.Naming.build(proto1, - opts=options.Options(namespace=('google.cloud',)), + opts=Options(namespace=('google.cloud',)), ) assert n.namespace == ('Google', 'Cloud') assert n.name == 'Spanner' @@ -197,7 +197,7 @@ def test_cli_override_name_and_namespace(): proto1 = FileDesc(package='google.translation.v2') n = naming.Naming.build( proto1, - opts=options.Options( + opts=Options( namespace=('google', 'cloud'), name='translate' ), ) @@ -211,7 +211,7 @@ def test_cli_override_name_and_namespace_versionless(): proto1 = FileDesc(package='google.translation') n = naming.Naming.build( proto1, - opts=options.Options(namespace=('google', 'cloud'), name='translate'), + opts=Options(namespace=('google', 'cloud'), name='translate'), ) assert n.namespace == ('Google', 'Cloud') assert n.name == 'Translate' @@ -224,12 +224,12 @@ def test_build_factory(): ) old = naming.Naming.build( proto, - opts=options.Options(old_naming=True) + opts=Options(old_naming=True) ) assert old.versioned_module_name == 'mollusc.v1alpha1' new = naming.Naming.build( proto, - opts=options.Options() + opts=Options() ) assert new.versioned_module_name == 'mollusc_v1alpha1'