From 80bef1f07d38785aa1dc32a66e34d54d3ef04591 Mon Sep 17 00:00:00 2001 From: Bu Sun Kim <8822365+busunkim96@users.noreply.github.com> Date: Mon, 28 Sep 2020 17:05:01 -0600 Subject: [PATCH] feat: add v4 (#29) --- UPGRADING.md | 54 +- docs/index.rst | 7 +- docs/talent_v4/services.rst | 18 + docs/talent_v4/types.rst | 5 + google/cloud/talent/__init__.py | 280 +- google/cloud/talent_v4/__init__.py | 153 + google/cloud/talent_v4/proto/common.proto | 928 +++++ google/cloud/talent_v4/proto/company.proto | 118 + .../talent_v4/proto/company_service.proto | 184 + .../talent_v4/proto/completion_service.proto | 163 + google/cloud/talent_v4/proto/event.proto | 180 + .../cloud/talent_v4/proto/event_service.proto | 68 + google/cloud/talent_v4/proto/filters.proto | 358 ++ google/cloud/talent_v4/proto/histogram.proto | 56 + google/cloud/talent_v4/proto/job.proto | 370 ++ .../cloud/talent_v4/proto/job_service.proto | 865 +++++ google/cloud/talent_v4/proto/tenant.proto | 53 + .../talent_v4/proto/tenant_service.proto | 175 + google/cloud/talent_v4/py.typed | 2 + google/cloud/talent_v4/services/__init__.py | 16 + .../services/company_service/__init__.py | 24 + .../services/company_service/async_client.py | 559 +++ .../services/company_service/client.py | 706 ++++ .../services/company_service/pagers.py | 149 + .../company_service/transports/__init__.py | 36 + .../company_service/transports/base.py | 209 ++ .../company_service/transports/grpc.py | 371 ++ .../transports/grpc_asyncio.py | 376 ++ .../talent_v4/services/completion/__init__.py | 24 + .../services/completion/async_client.py | 169 + .../talent_v4/services/completion/client.py | 308 ++ .../completion/transports/__init__.py | 36 + .../services/completion/transports/base.py | 138 + .../services/completion/transports/grpc.py | 264 ++ .../completion/transports/grpc_asyncio.py | 264 ++ .../services/event_service/__init__.py | 24 + .../services/event_service/async_client.py | 210 ++ .../services/event_service/client.py | 358 ++ .../event_service/transports/__init__.py | 36 + .../services/event_service/transports/base.py | 126 + .../services/event_service/transports/grpc.py | 266 ++ .../event_service/transports/grpc_asyncio.py | 268 ++ .../services/job_service/__init__.py | 24 + .../services/job_service/async_client.py | 1001 ++++++ .../talent_v4/services/job_service/client.py | 1154 ++++++ .../talent_v4/services/job_service/pagers.py | 149 + .../job_service/transports/__init__.py | 36 + .../services/job_service/transports/base.py | 282 ++ .../services/job_service/transports/grpc.py | 533 +++ .../job_service/transports/grpc_asyncio.py | 551 +++ .../services/tenant_service/__init__.py | 24 + .../services/tenant_service/async_client.py | 555 +++ .../services/tenant_service/client.py | 697 ++++ .../services/tenant_service/pagers.py | 149 + .../tenant_service/transports/__init__.py | 36 + .../tenant_service/transports/base.py | 209 ++ .../tenant_service/transports/grpc.py | 367 ++ .../tenant_service/transports/grpc_asyncio.py | 370 ++ google/cloud/talent_v4/types/__init__.py | 137 + google/cloud/talent_v4/types/common.py | 746 ++++ google/cloud/talent_v4/types/company.py | 144 + .../cloud/talent_v4/types/company_service.py | 178 + .../talent_v4/types/completion_service.py | 136 + google/cloud/talent_v4/types/event.py | 118 + google/cloud/talent_v4/types/event_service.py | 50 + google/cloud/talent_v4/types/filters.py | 396 +++ google/cloud/talent_v4/types/histogram.py | 72 + google/cloud/talent_v4/types/job.py | 479 +++ google/cloud/talent_v4/types/job_service.py | 923 +++++ google/cloud/talent_v4/types/tenant.py | 51 + .../cloud/talent_v4/types/tenant_service.py | 162 + .../application_service/async_client.py | 50 +- .../services/application_service/client.py | 101 +- .../application_service/transports/base.py | 32 +- .../application_service/transports/grpc.py | 62 +- .../transports/grpc_asyncio.py | 61 +- .../services/company_service/async_client.py | 48 +- .../services/company_service/client.py | 101 +- .../company_service/transports/base.py | 32 +- .../company_service/transports/grpc.py | 62 +- .../transports/grpc_asyncio.py | 61 +- .../services/completion/async_client.py | 35 +- .../services/completion/client.py | 101 +- .../services/completion/transports/base.py | 20 +- .../services/completion/transports/grpc.py | 62 +- .../completion/transports/grpc_asyncio.py | 61 +- .../services/event_service/async_client.py | 33 +- .../services/event_service/client.py | 101 +- .../services/event_service/transports/base.py | 20 +- .../services/event_service/transports/grpc.py | 62 +- .../event_service/transports/grpc_asyncio.py | 61 +- .../services/job_service/async_client.py | 62 +- .../services/job_service/client.py | 105 +- .../services/job_service/transports/base.py | 42 +- .../services/job_service/transports/grpc.py | 62 +- .../job_service/transports/grpc_asyncio.py | 61 +- .../services/profile_service/async_client.py | 50 +- .../services/profile_service/client.py | 101 +- .../profile_service/transports/base.py | 34 +- .../profile_service/transports/grpc.py | 62 +- .../transports/grpc_asyncio.py | 61 +- .../services/tenant_service/async_client.py | 48 +- .../services/tenant_service/client.py | 101 +- .../tenant_service/transports/base.py | 32 +- .../tenant_service/transports/grpc.py | 62 +- .../tenant_service/transports/grpc_asyncio.py | 61 +- google/cloud/talent_v4beta1/types/common.py | 16 +- .../types/completion_service.py | 8 +- google/cloud/talent_v4beta1/types/event.py | 8 +- google/cloud/talent_v4beta1/types/filters.py | 12 +- google/cloud/talent_v4beta1/types/profile.py | 8 +- noxfile.py | 2 +- .../job_search_autocomplete_job_title.py | 2 +- samples/snippets/job_search_commute_search.py | 2 +- .../job_search_custom_ranking_search.py | 2 +- .../snippets/job_search_histogram_search.py | 2 +- scripts/fixup_talent_v4_keywords.py | 199 ++ synth.metadata | 9 + synth.py | 6 +- tests/unit/gapic/talent_v4/__init__.py | 1 + .../gapic/talent_v4/test_company_service.py | 2111 +++++++++++ tests/unit/gapic/talent_v4/test_completion.py | 851 +++++ .../gapic/talent_v4/test_event_service.py | 958 +++++ .../unit/gapic/talent_v4/test_job_service.py | 3087 +++++++++++++++++ .../gapic/talent_v4/test_tenant_service.py | 1882 ++++++++++ .../test_application_service.py | 549 +-- .../talent_v4beta1/test_company_service.py | 539 +-- .../gapic/talent_v4beta1/test_completion.py | 511 +-- .../talent_v4beta1/test_event_service.py | 513 +-- .../gapic/talent_v4beta1/test_job_service.py | 554 +-- .../talent_v4beta1/test_profile_service.py | 551 +-- .../talent_v4beta1/test_tenant_service.py | 539 +-- 132 files changed, 31099 insertions(+), 2636 deletions(-) create mode 100644 docs/talent_v4/services.rst create mode 100644 docs/talent_v4/types.rst create mode 100644 google/cloud/talent_v4/__init__.py create mode 100644 google/cloud/talent_v4/proto/common.proto create mode 100644 google/cloud/talent_v4/proto/company.proto create mode 100644 google/cloud/talent_v4/proto/company_service.proto create mode 100644 google/cloud/talent_v4/proto/completion_service.proto create mode 100644 google/cloud/talent_v4/proto/event.proto create mode 100644 google/cloud/talent_v4/proto/event_service.proto create mode 100644 google/cloud/talent_v4/proto/filters.proto create mode 100644 google/cloud/talent_v4/proto/histogram.proto create mode 100644 google/cloud/talent_v4/proto/job.proto create mode 100644 google/cloud/talent_v4/proto/job_service.proto create mode 100644 google/cloud/talent_v4/proto/tenant.proto create mode 100644 google/cloud/talent_v4/proto/tenant_service.proto create mode 100644 google/cloud/talent_v4/py.typed create mode 100644 google/cloud/talent_v4/services/__init__.py create mode 100644 google/cloud/talent_v4/services/company_service/__init__.py create mode 100644 google/cloud/talent_v4/services/company_service/async_client.py create mode 100644 google/cloud/talent_v4/services/company_service/client.py create mode 100644 google/cloud/talent_v4/services/company_service/pagers.py create mode 100644 google/cloud/talent_v4/services/company_service/transports/__init__.py create mode 100644 google/cloud/talent_v4/services/company_service/transports/base.py create mode 100644 google/cloud/talent_v4/services/company_service/transports/grpc.py create mode 100644 google/cloud/talent_v4/services/company_service/transports/grpc_asyncio.py create mode 100644 google/cloud/talent_v4/services/completion/__init__.py create mode 100644 google/cloud/talent_v4/services/completion/async_client.py create mode 100644 google/cloud/talent_v4/services/completion/client.py create mode 100644 google/cloud/talent_v4/services/completion/transports/__init__.py create mode 100644 google/cloud/talent_v4/services/completion/transports/base.py create mode 100644 google/cloud/talent_v4/services/completion/transports/grpc.py create mode 100644 google/cloud/talent_v4/services/completion/transports/grpc_asyncio.py create mode 100644 google/cloud/talent_v4/services/event_service/__init__.py create mode 100644 google/cloud/talent_v4/services/event_service/async_client.py create mode 100644 google/cloud/talent_v4/services/event_service/client.py create mode 100644 google/cloud/talent_v4/services/event_service/transports/__init__.py create mode 100644 google/cloud/talent_v4/services/event_service/transports/base.py create mode 100644 google/cloud/talent_v4/services/event_service/transports/grpc.py create mode 100644 google/cloud/talent_v4/services/event_service/transports/grpc_asyncio.py create mode 100644 google/cloud/talent_v4/services/job_service/__init__.py create mode 100644 google/cloud/talent_v4/services/job_service/async_client.py create mode 100644 google/cloud/talent_v4/services/job_service/client.py create mode 100644 google/cloud/talent_v4/services/job_service/pagers.py create mode 100644 google/cloud/talent_v4/services/job_service/transports/__init__.py create mode 100644 google/cloud/talent_v4/services/job_service/transports/base.py create mode 100644 google/cloud/talent_v4/services/job_service/transports/grpc.py create mode 100644 google/cloud/talent_v4/services/job_service/transports/grpc_asyncio.py create mode 100644 google/cloud/talent_v4/services/tenant_service/__init__.py create mode 100644 google/cloud/talent_v4/services/tenant_service/async_client.py create mode 100644 google/cloud/talent_v4/services/tenant_service/client.py create mode 100644 google/cloud/talent_v4/services/tenant_service/pagers.py create mode 100644 google/cloud/talent_v4/services/tenant_service/transports/__init__.py create mode 100644 google/cloud/talent_v4/services/tenant_service/transports/base.py create mode 100644 google/cloud/talent_v4/services/tenant_service/transports/grpc.py create mode 100644 google/cloud/talent_v4/services/tenant_service/transports/grpc_asyncio.py create mode 100644 google/cloud/talent_v4/types/__init__.py create mode 100644 google/cloud/talent_v4/types/common.py create mode 100644 google/cloud/talent_v4/types/company.py create mode 100644 google/cloud/talent_v4/types/company_service.py create mode 100644 google/cloud/talent_v4/types/completion_service.py create mode 100644 google/cloud/talent_v4/types/event.py create mode 100644 google/cloud/talent_v4/types/event_service.py create mode 100644 google/cloud/talent_v4/types/filters.py create mode 100644 google/cloud/talent_v4/types/histogram.py create mode 100644 google/cloud/talent_v4/types/job.py create mode 100644 google/cloud/talent_v4/types/job_service.py create mode 100644 google/cloud/talent_v4/types/tenant.py create mode 100644 google/cloud/talent_v4/types/tenant_service.py create mode 100644 scripts/fixup_talent_v4_keywords.py create mode 100644 tests/unit/gapic/talent_v4/__init__.py create mode 100644 tests/unit/gapic/talent_v4/test_company_service.py create mode 100644 tests/unit/gapic/talent_v4/test_completion.py create mode 100644 tests/unit/gapic/talent_v4/test_event_service.py create mode 100644 tests/unit/gapic/talent_v4/test_job_service.py create mode 100644 tests/unit/gapic/talent_v4/test_tenant_service.py diff --git a/UPGRADING.md b/UPGRADING.md index cb385ee6..9a5a84b4 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -37,7 +37,7 @@ response = client.batch_create_jobs(parent, jobs) **After:** ```py -response = client.batch_create_jobs(request={"parent": "''", "jobs": "[]"}) +response = client.batch_create_jobs(request={"parent": "parent", "jobs": []}) ``` ### More Details @@ -103,4 +103,56 @@ response = client.batch_create_jobs( }, jobs=jobs ) +``` + +## v4beta1 -> v4 + +In addition, this release adds the v4 API surface, which has breaking changes from the v4beta1. +The `google.cloud.talent` import now resolves to `google.cloud.talent_v4`. To continue +to use the `v4beta1`, import from it explicitly. + +The `v4` surface merges functionalty in the `v3p1beta1` and `v4beta1`. + +```py +from google.cloud import talent_v4beta1 +``` + + +### search_jobs + +`search_jobs` returns `SearchJobsResponse`. Matching jobs are in `SearchJobsResponse.matching_jobs`. + + +**v4beta1:** +```py +from google.cloud import talent_v4beta1 + + +client = talent_v4beta1.JobServiceClient() + +#... +request = talent_v4beta1.SearchJobsRequest( + parent=parent, + request_metadata=request_metadata, + job_query=job_query, +) +for response_item in client.search_jobs(request=request): + # ... +``` + +**v4** +```py +from google.cloud import talent_v4 + + +client = talent_v4.JobServiceClient() + +#... +request = talent_v4.SearchJobsRequest( + parent=parent, + request_metadata=request_metadata, + job_query=job_query, +) +for response_item in client.search_jobs(request=request).matching_jobs: + # ... ``` \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index d181d6e9..ef884a75 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,19 +2,20 @@ .. include:: multiprocessing.rst -Api Reference +API Reference ------------- .. toctree:: :maxdepth: 2 + talent_v4/services + talent_v4/types talent_v4beta1/services talent_v4beta1/types - changelog Migration Guide --------------- -See the guide below for instructions on migrating to the 2.x release of this library. +See the guide below for instructions on migrating to the 1.x release of this library. .. toctree:: :maxdepth: 2 diff --git a/docs/talent_v4/services.rst b/docs/talent_v4/services.rst new file mode 100644 index 00000000..5efed8a2 --- /dev/null +++ b/docs/talent_v4/services.rst @@ -0,0 +1,18 @@ +Services for Google Cloud Talent v4 API +======================================= + +.. automodule:: google.cloud.talent_v4.services.company_service + :members: + :inherited-members: +.. automodule:: google.cloud.talent_v4.services.completion + :members: + :inherited-members: +.. automodule:: google.cloud.talent_v4.services.event_service + :members: + :inherited-members: +.. automodule:: google.cloud.talent_v4.services.job_service + :members: + :inherited-members: +.. automodule:: google.cloud.talent_v4.services.tenant_service + :members: + :inherited-members: diff --git a/docs/talent_v4/types.rst b/docs/talent_v4/types.rst new file mode 100644 index 00000000..9b5991f6 --- /dev/null +++ b/docs/talent_v4/types.rst @@ -0,0 +1,5 @@ +Types for Google Cloud Talent v4 API +==================================== + +.. automodule:: google.cloud.talent_v4.types + :members: diff --git a/google/cloud/talent/__init__.py b/google/cloud/talent/__init__.py index cb19a827..13571c97 100644 --- a/google/cloud/talent/__init__.py +++ b/google/cloud/talent/__init__.py @@ -15,183 +15,96 @@ # limitations under the License. # -from google.cloud.talent_v4beta1.services.application_service.async_client import ( - ApplicationServiceAsyncClient, -) -from google.cloud.talent_v4beta1.services.application_service.client import ( - ApplicationServiceClient, -) -from google.cloud.talent_v4beta1.services.company_service.async_client import ( +from google.cloud.talent_v4.services.company_service.async_client import ( CompanyServiceAsyncClient, ) -from google.cloud.talent_v4beta1.services.company_service.client import ( - CompanyServiceClient, -) -from google.cloud.talent_v4beta1.services.completion.async_client import ( +from google.cloud.talent_v4.services.company_service.client import CompanyServiceClient +from google.cloud.talent_v4.services.completion.async_client import ( CompletionAsyncClient, ) -from google.cloud.talent_v4beta1.services.completion.client import CompletionClient -from google.cloud.talent_v4beta1.services.event_service.async_client import ( +from google.cloud.talent_v4.services.completion.client import CompletionClient +from google.cloud.talent_v4.services.event_service.async_client import ( EventServiceAsyncClient, ) -from google.cloud.talent_v4beta1.services.event_service.client import EventServiceClient -from google.cloud.talent_v4beta1.services.job_service.async_client import ( +from google.cloud.talent_v4.services.event_service.client import EventServiceClient +from google.cloud.talent_v4.services.job_service.async_client import ( JobServiceAsyncClient, ) -from google.cloud.talent_v4beta1.services.job_service.client import JobServiceClient -from google.cloud.talent_v4beta1.services.profile_service.async_client import ( - ProfileServiceAsyncClient, -) -from google.cloud.talent_v4beta1.services.profile_service.client import ( - ProfileServiceClient, -) -from google.cloud.talent_v4beta1.services.tenant_service.async_client import ( +from google.cloud.talent_v4.services.job_service.client import JobServiceClient +from google.cloud.talent_v4.services.tenant_service.async_client import ( TenantServiceAsyncClient, ) -from google.cloud.talent_v4beta1.services.tenant_service.client import ( - TenantServiceClient, -) -from google.cloud.talent_v4beta1.types.application import Application -from google.cloud.talent_v4beta1.types.application_service import ( - CreateApplicationRequest, -) -from google.cloud.talent_v4beta1.types.application_service import ( - DeleteApplicationRequest, -) -from google.cloud.talent_v4beta1.types.application_service import GetApplicationRequest -from google.cloud.talent_v4beta1.types.application_service import ( - ListApplicationsRequest, -) -from google.cloud.talent_v4beta1.types.application_service import ( - ListApplicationsResponse, -) -from google.cloud.talent_v4beta1.types.application_service import ( - UpdateApplicationRequest, -) -from google.cloud.talent_v4beta1.types.common import AvailabilitySignalType -from google.cloud.talent_v4beta1.types.common import BatchOperationMetadata -from google.cloud.talent_v4beta1.types.common import Certification -from google.cloud.talent_v4beta1.types.common import CommuteMethod -from google.cloud.talent_v4beta1.types.common import CompanySize -from google.cloud.talent_v4beta1.types.common import CompensationInfo -from google.cloud.talent_v4beta1.types.common import ContactInfoUsage -from google.cloud.talent_v4beta1.types.common import CustomAttribute -from google.cloud.talent_v4beta1.types.common import DegreeType -from google.cloud.talent_v4beta1.types.common import DeviceInfo -from google.cloud.talent_v4beta1.types.common import EmploymentType -from google.cloud.talent_v4beta1.types.common import HtmlSanitization -from google.cloud.talent_v4beta1.types.common import Interview -from google.cloud.talent_v4beta1.types.common import JobBenefit -from google.cloud.talent_v4beta1.types.common import JobCategory -from google.cloud.talent_v4beta1.types.common import JobLevel -from google.cloud.talent_v4beta1.types.common import Location -from google.cloud.talent_v4beta1.types.common import Outcome -from google.cloud.talent_v4beta1.types.common import PostingRegion -from google.cloud.talent_v4beta1.types.common import Rating -from google.cloud.talent_v4beta1.types.common import RequestMetadata -from google.cloud.talent_v4beta1.types.common import ResponseMetadata -from google.cloud.talent_v4beta1.types.common import Skill -from google.cloud.talent_v4beta1.types.common import SkillProficiencyLevel -from google.cloud.talent_v4beta1.types.common import SpellingCorrection -from google.cloud.talent_v4beta1.types.common import TimestampRange -from google.cloud.talent_v4beta1.types.common import Visibility -from google.cloud.talent_v4beta1.types.company import Company -from google.cloud.talent_v4beta1.types.company_service import CreateCompanyRequest -from google.cloud.talent_v4beta1.types.company_service import DeleteCompanyRequest -from google.cloud.talent_v4beta1.types.company_service import GetCompanyRequest -from google.cloud.talent_v4beta1.types.company_service import ListCompaniesRequest -from google.cloud.talent_v4beta1.types.company_service import ListCompaniesResponse -from google.cloud.talent_v4beta1.types.company_service import UpdateCompanyRequest -from google.cloud.talent_v4beta1.types.completion_service import CompleteQueryRequest -from google.cloud.talent_v4beta1.types.completion_service import CompleteQueryResponse -from google.cloud.talent_v4beta1.types.event import ClientEvent -from google.cloud.talent_v4beta1.types.event import JobEvent -from google.cloud.talent_v4beta1.types.event import ProfileEvent -from google.cloud.talent_v4beta1.types.event_service import CreateClientEventRequest -from google.cloud.talent_v4beta1.types.filters import ApplicationDateFilter -from google.cloud.talent_v4beta1.types.filters import ApplicationJobFilter -from google.cloud.talent_v4beta1.types.filters import ApplicationOutcomeNotesFilter -from google.cloud.talent_v4beta1.types.filters import AvailabilityFilter -from google.cloud.talent_v4beta1.types.filters import CandidateAvailabilityFilter -from google.cloud.talent_v4beta1.types.filters import CommuteFilter -from google.cloud.talent_v4beta1.types.filters import CompensationFilter -from google.cloud.talent_v4beta1.types.filters import EducationFilter -from google.cloud.talent_v4beta1.types.filters import EmployerFilter -from google.cloud.talent_v4beta1.types.filters import JobQuery -from google.cloud.talent_v4beta1.types.filters import JobTitleFilter -from google.cloud.talent_v4beta1.types.filters import LocationFilter -from google.cloud.talent_v4beta1.types.filters import PersonNameFilter -from google.cloud.talent_v4beta1.types.filters import ProfileQuery -from google.cloud.talent_v4beta1.types.filters import SkillFilter -from google.cloud.talent_v4beta1.types.filters import TimeFilter -from google.cloud.talent_v4beta1.types.filters import WorkExperienceFilter -from google.cloud.talent_v4beta1.types.histogram import HistogramQuery -from google.cloud.talent_v4beta1.types.histogram import HistogramQueryResult -from google.cloud.talent_v4beta1.types.job import Job -from google.cloud.talent_v4beta1.types.job_service import BatchCreateJobsRequest -from google.cloud.talent_v4beta1.types.job_service import BatchDeleteJobsRequest -from google.cloud.talent_v4beta1.types.job_service import BatchUpdateJobsRequest -from google.cloud.talent_v4beta1.types.job_service import CreateJobRequest -from google.cloud.talent_v4beta1.types.job_service import DeleteJobRequest -from google.cloud.talent_v4beta1.types.job_service import GetJobRequest -from google.cloud.talent_v4beta1.types.job_service import JobOperationResult -from google.cloud.talent_v4beta1.types.job_service import JobView -from google.cloud.talent_v4beta1.types.job_service import ListJobsRequest -from google.cloud.talent_v4beta1.types.job_service import ListJobsResponse -from google.cloud.talent_v4beta1.types.job_service import SearchJobsRequest -from google.cloud.talent_v4beta1.types.job_service import SearchJobsResponse -from google.cloud.talent_v4beta1.types.job_service import UpdateJobRequest -from google.cloud.talent_v4beta1.types.profile import Activity -from google.cloud.talent_v4beta1.types.profile import AdditionalContactInfo -from google.cloud.talent_v4beta1.types.profile import Address -from google.cloud.talent_v4beta1.types.profile import AvailabilitySignal -from google.cloud.talent_v4beta1.types.profile import Degree -from google.cloud.talent_v4beta1.types.profile import EducationRecord -from google.cloud.talent_v4beta1.types.profile import Email -from google.cloud.talent_v4beta1.types.profile import EmploymentRecord -from google.cloud.talent_v4beta1.types.profile import Patent -from google.cloud.talent_v4beta1.types.profile import PersonName -from google.cloud.talent_v4beta1.types.profile import PersonalUri -from google.cloud.talent_v4beta1.types.profile import Phone -from google.cloud.talent_v4beta1.types.profile import Profile -from google.cloud.talent_v4beta1.types.profile import Publication -from google.cloud.talent_v4beta1.types.profile import Resume -from google.cloud.talent_v4beta1.types.profile_service import CreateProfileRequest -from google.cloud.talent_v4beta1.types.profile_service import DeleteProfileRequest -from google.cloud.talent_v4beta1.types.profile_service import GetProfileRequest -from google.cloud.talent_v4beta1.types.profile_service import ListProfilesRequest -from google.cloud.talent_v4beta1.types.profile_service import ListProfilesResponse -from google.cloud.talent_v4beta1.types.profile_service import SearchProfilesRequest -from google.cloud.talent_v4beta1.types.profile_service import SearchProfilesResponse -from google.cloud.talent_v4beta1.types.profile_service import SummarizedProfile -from google.cloud.talent_v4beta1.types.profile_service import UpdateProfileRequest -from google.cloud.talent_v4beta1.types.tenant import Tenant -from google.cloud.talent_v4beta1.types.tenant_service import CreateTenantRequest -from google.cloud.talent_v4beta1.types.tenant_service import DeleteTenantRequest -from google.cloud.talent_v4beta1.types.tenant_service import GetTenantRequest -from google.cloud.talent_v4beta1.types.tenant_service import ListTenantsRequest -from google.cloud.talent_v4beta1.types.tenant_service import ListTenantsResponse -from google.cloud.talent_v4beta1.types.tenant_service import UpdateTenantRequest +from google.cloud.talent_v4.services.tenant_service.client import TenantServiceClient +from google.cloud.talent_v4.types.common import BatchOperationMetadata +from google.cloud.talent_v4.types.common import CommuteMethod +from google.cloud.talent_v4.types.common import CompanySize +from google.cloud.talent_v4.types.common import CompensationInfo +from google.cloud.talent_v4.types.common import CustomAttribute +from google.cloud.talent_v4.types.common import DegreeType +from google.cloud.talent_v4.types.common import DeviceInfo +from google.cloud.talent_v4.types.common import EmploymentType +from google.cloud.talent_v4.types.common import HtmlSanitization +from google.cloud.talent_v4.types.common import JobBenefit +from google.cloud.talent_v4.types.common import JobCategory +from google.cloud.talent_v4.types.common import JobLevel +from google.cloud.talent_v4.types.common import Location +from google.cloud.talent_v4.types.common import PostingRegion +from google.cloud.talent_v4.types.common import RequestMetadata +from google.cloud.talent_v4.types.common import ResponseMetadata +from google.cloud.talent_v4.types.common import SpellingCorrection +from google.cloud.talent_v4.types.common import TimestampRange +from google.cloud.talent_v4.types.common import Visibility +from google.cloud.talent_v4.types.company import Company +from google.cloud.talent_v4.types.company_service import CreateCompanyRequest +from google.cloud.talent_v4.types.company_service import DeleteCompanyRequest +from google.cloud.talent_v4.types.company_service import GetCompanyRequest +from google.cloud.talent_v4.types.company_service import ListCompaniesRequest +from google.cloud.talent_v4.types.company_service import ListCompaniesResponse +from google.cloud.talent_v4.types.company_service import UpdateCompanyRequest +from google.cloud.talent_v4.types.completion_service import CompleteQueryRequest +from google.cloud.talent_v4.types.completion_service import CompleteQueryResponse +from google.cloud.talent_v4.types.event import ClientEvent +from google.cloud.talent_v4.types.event import JobEvent +from google.cloud.talent_v4.types.event_service import CreateClientEventRequest +from google.cloud.talent_v4.types.filters import CommuteFilter +from google.cloud.talent_v4.types.filters import CompensationFilter +from google.cloud.talent_v4.types.filters import JobQuery +from google.cloud.talent_v4.types.filters import LocationFilter +from google.cloud.talent_v4.types.histogram import HistogramQuery +from google.cloud.talent_v4.types.histogram import HistogramQueryResult +from google.cloud.talent_v4.types.job import Job +from google.cloud.talent_v4.types.job_service import BatchCreateJobsRequest +from google.cloud.talent_v4.types.job_service import BatchCreateJobsResponse +from google.cloud.talent_v4.types.job_service import BatchDeleteJobsRequest +from google.cloud.talent_v4.types.job_service import BatchDeleteJobsResponse +from google.cloud.talent_v4.types.job_service import BatchUpdateJobsRequest +from google.cloud.talent_v4.types.job_service import BatchUpdateJobsResponse +from google.cloud.talent_v4.types.job_service import CreateJobRequest +from google.cloud.talent_v4.types.job_service import DeleteJobRequest +from google.cloud.talent_v4.types.job_service import GetJobRequest +from google.cloud.talent_v4.types.job_service import JobResult +from google.cloud.talent_v4.types.job_service import JobView +from google.cloud.talent_v4.types.job_service import ListJobsRequest +from google.cloud.talent_v4.types.job_service import ListJobsResponse +from google.cloud.talent_v4.types.job_service import SearchJobsRequest +from google.cloud.talent_v4.types.job_service import SearchJobsResponse +from google.cloud.talent_v4.types.job_service import UpdateJobRequest +from google.cloud.talent_v4.types.tenant import Tenant +from google.cloud.talent_v4.types.tenant_service import CreateTenantRequest +from google.cloud.talent_v4.types.tenant_service import DeleteTenantRequest +from google.cloud.talent_v4.types.tenant_service import GetTenantRequest +from google.cloud.talent_v4.types.tenant_service import ListTenantsRequest +from google.cloud.talent_v4.types.tenant_service import ListTenantsResponse +from google.cloud.talent_v4.types.tenant_service import UpdateTenantRequest __all__ = ( - "Activity", - "AdditionalContactInfo", - "Address", - "Application", - "ApplicationDateFilter", - "ApplicationJobFilter", - "ApplicationOutcomeNotesFilter", - "ApplicationServiceAsyncClient", - "ApplicationServiceClient", - "AvailabilityFilter", - "AvailabilitySignal", - "AvailabilitySignalType", "BatchCreateJobsRequest", + "BatchCreateJobsResponse", "BatchDeleteJobsRequest", + "BatchDeleteJobsResponse", "BatchOperationMetadata", "BatchUpdateJobsRequest", - "CandidateAvailabilityFilter", - "Certification", + "BatchUpdateJobsResponse", "ClientEvent", "CommuteFilter", "CommuteMethod", @@ -205,98 +118,55 @@ "CompleteQueryResponse", "CompletionAsyncClient", "CompletionClient", - "ContactInfoUsage", - "CreateApplicationRequest", "CreateClientEventRequest", "CreateCompanyRequest", "CreateJobRequest", - "CreateProfileRequest", "CreateTenantRequest", "CustomAttribute", - "Degree", "DegreeType", - "DeleteApplicationRequest", "DeleteCompanyRequest", "DeleteJobRequest", - "DeleteProfileRequest", "DeleteTenantRequest", "DeviceInfo", - "EducationFilter", - "EducationRecord", - "Email", - "EmployerFilter", - "EmploymentRecord", "EmploymentType", "EventServiceAsyncClient", "EventServiceClient", - "GetApplicationRequest", "GetCompanyRequest", "GetJobRequest", - "GetProfileRequest", "GetTenantRequest", "HistogramQuery", "HistogramQueryResult", "HtmlSanitization", - "Interview", "Job", "JobBenefit", "JobCategory", "JobEvent", "JobLevel", - "JobOperationResult", "JobQuery", + "JobResult", "JobServiceAsyncClient", "JobServiceClient", - "JobTitleFilter", "JobView", - "ListApplicationsRequest", - "ListApplicationsResponse", "ListCompaniesRequest", "ListCompaniesResponse", "ListJobsRequest", "ListJobsResponse", - "ListProfilesRequest", - "ListProfilesResponse", "ListTenantsRequest", "ListTenantsResponse", "Location", "LocationFilter", - "Outcome", - "Patent", - "PersonName", - "PersonNameFilter", - "PersonalUri", - "Phone", "PostingRegion", - "Profile", - "ProfileEvent", - "ProfileQuery", - "ProfileServiceAsyncClient", - "ProfileServiceClient", - "Publication", - "Rating", "RequestMetadata", "ResponseMetadata", - "Resume", "SearchJobsRequest", "SearchJobsResponse", - "SearchProfilesRequest", - "SearchProfilesResponse", - "Skill", - "SkillFilter", - "SkillProficiencyLevel", "SpellingCorrection", - "SummarizedProfile", "Tenant", "TenantServiceAsyncClient", "TenantServiceClient", - "TimeFilter", "TimestampRange", - "UpdateApplicationRequest", "UpdateCompanyRequest", "UpdateJobRequest", - "UpdateProfileRequest", "UpdateTenantRequest", "Visibility", - "WorkExperienceFilter", ) diff --git a/google/cloud/talent_v4/__init__.py b/google/cloud/talent_v4/__init__.py new file mode 100644 index 00000000..b193e4de --- /dev/null +++ b/google/cloud/talent_v4/__init__.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .services.company_service import CompanyServiceClient +from .services.completion import CompletionClient +from .services.event_service import EventServiceClient +from .services.job_service import JobServiceClient +from .services.tenant_service import TenantServiceClient +from .types.common import BatchOperationMetadata +from .types.common import CommuteMethod +from .types.common import CompanySize +from .types.common import CompensationInfo +from .types.common import CustomAttribute +from .types.common import DegreeType +from .types.common import DeviceInfo +from .types.common import EmploymentType +from .types.common import HtmlSanitization +from .types.common import JobBenefit +from .types.common import JobCategory +from .types.common import JobLevel +from .types.common import Location +from .types.common import PostingRegion +from .types.common import RequestMetadata +from .types.common import ResponseMetadata +from .types.common import SpellingCorrection +from .types.common import TimestampRange +from .types.common import Visibility +from .types.company import Company +from .types.company_service import CreateCompanyRequest +from .types.company_service import DeleteCompanyRequest +from .types.company_service import GetCompanyRequest +from .types.company_service import ListCompaniesRequest +from .types.company_service import ListCompaniesResponse +from .types.company_service import UpdateCompanyRequest +from .types.completion_service import CompleteQueryRequest +from .types.completion_service import CompleteQueryResponse +from .types.event import ClientEvent +from .types.event import JobEvent +from .types.event_service import CreateClientEventRequest +from .types.filters import CommuteFilter +from .types.filters import CompensationFilter +from .types.filters import JobQuery +from .types.filters import LocationFilter +from .types.histogram import HistogramQuery +from .types.histogram import HistogramQueryResult +from .types.job import Job +from .types.job_service import BatchCreateJobsRequest +from .types.job_service import BatchCreateJobsResponse +from .types.job_service import BatchDeleteJobsRequest +from .types.job_service import BatchDeleteJobsResponse +from .types.job_service import BatchUpdateJobsRequest +from .types.job_service import BatchUpdateJobsResponse +from .types.job_service import CreateJobRequest +from .types.job_service import DeleteJobRequest +from .types.job_service import GetJobRequest +from .types.job_service import JobResult +from .types.job_service import JobView +from .types.job_service import ListJobsRequest +from .types.job_service import ListJobsResponse +from .types.job_service import SearchJobsRequest +from .types.job_service import SearchJobsResponse +from .types.job_service import UpdateJobRequest +from .types.tenant import Tenant +from .types.tenant_service import CreateTenantRequest +from .types.tenant_service import DeleteTenantRequest +from .types.tenant_service import GetTenantRequest +from .types.tenant_service import ListTenantsRequest +from .types.tenant_service import ListTenantsResponse +from .types.tenant_service import UpdateTenantRequest + + +__all__ = ( + "BatchCreateJobsRequest", + "BatchCreateJobsResponse", + "BatchDeleteJobsRequest", + "BatchDeleteJobsResponse", + "BatchOperationMetadata", + "BatchUpdateJobsRequest", + "BatchUpdateJobsResponse", + "ClientEvent", + "CommuteFilter", + "CommuteMethod", + "Company", + "CompanyServiceClient", + "CompanySize", + "CompensationFilter", + "CompensationInfo", + "CompleteQueryRequest", + "CompleteQueryResponse", + "CompletionClient", + "CreateClientEventRequest", + "CreateCompanyRequest", + "CreateJobRequest", + "CreateTenantRequest", + "CustomAttribute", + "DegreeType", + "DeleteCompanyRequest", + "DeleteJobRequest", + "DeleteTenantRequest", + "DeviceInfo", + "EmploymentType", + "EventServiceClient", + "GetCompanyRequest", + "GetJobRequest", + "GetTenantRequest", + "HistogramQuery", + "HistogramQueryResult", + "HtmlSanitization", + "Job", + "JobBenefit", + "JobCategory", + "JobEvent", + "JobLevel", + "JobQuery", + "JobResult", + "JobServiceClient", + "JobView", + "ListCompaniesRequest", + "ListCompaniesResponse", + "ListJobsRequest", + "ListJobsResponse", + "ListTenantsRequest", + "ListTenantsResponse", + "Location", + "LocationFilter", + "PostingRegion", + "RequestMetadata", + "ResponseMetadata", + "SearchJobsRequest", + "SearchJobsResponse", + "SpellingCorrection", + "Tenant", + "TimestampRange", + "UpdateCompanyRequest", + "UpdateJobRequest", + "UpdateTenantRequest", + "Visibility", + "TenantServiceClient", +) diff --git a/google/cloud/talent_v4/proto/common.proto b/google/cloud/talent_v4/proto/common.proto new file mode 100644 index 00000000..a7daca9e --- /dev/null +++ b/google/cloud/talent_v4/proto/common.proto @@ -0,0 +1,928 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; +import "google/type/latlng.proto"; +import "google/type/money.proto"; +import "google/type/postal_address.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "CommonProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// Message representing a period of time between two timestamps. +message TimestampRange { + // Begin of the period (inclusive). + google.protobuf.Timestamp start_time = 1; + + // End of the period (exclusive). + google.protobuf.Timestamp end_time = 2; +} + +// An enum that represents the size of the company. +enum CompanySize { + // Default value if the size isn't specified. + COMPANY_SIZE_UNSPECIFIED = 0; + + // The company has less than 50 employees. + MINI = 1; + + // The company has between 50 and 99 employees. + SMALL = 2; + + // The company has between 100 and 499 employees. + SMEDIUM = 3; + + // The company has between 500 and 999 employees. + MEDIUM = 4; + + // The company has between 1,000 and 4,999 employees. + BIG = 5; + + // The company has between 5,000 and 9,999 employees. + BIGGER = 6; + + // The company has 10,000 or more employees. + GIANT = 7; +} + +// An enum that represents employee benefits included with the job. +enum JobBenefit { + // Default value if the type isn't specified. + JOB_BENEFIT_UNSPECIFIED = 0; + + // The job includes access to programs that support child care, such + // as daycare. + CHILD_CARE = 1; + + // The job includes dental services covered by a dental + // insurance plan. + DENTAL = 2; + + // The job offers specific benefits to domestic partners. + DOMESTIC_PARTNER = 3; + + // The job allows for a flexible work schedule. + FLEXIBLE_HOURS = 4; + + // The job includes health services covered by a medical insurance plan. + MEDICAL = 5; + + // The job includes a life insurance plan provided by the employer or + // available for purchase by the employee. + LIFE_INSURANCE = 6; + + // The job allows for a leave of absence to a parent to care for a newborn + // child. + PARENTAL_LEAVE = 7; + + // The job includes a workplace retirement plan provided by the + // employer or available for purchase by the employee. + RETIREMENT_PLAN = 8; + + // The job allows for paid time off due to illness. + SICK_DAYS = 9; + + // The job includes paid time off for vacation. + VACATION = 10; + + // The job includes vision services covered by a vision + // insurance plan. + VISION = 11; +} + +// Educational degree level defined in International Standard Classification +// of Education (ISCED). +enum DegreeType { + // Default value. Represents no degree, or early childhood education. + // Maps to ISCED code 0. + // Ex) Kindergarten + DEGREE_TYPE_UNSPECIFIED = 0; + + // Primary education which is typically the first stage of compulsory + // education. ISCED code 1. + // Ex) Elementary school + PRIMARY_EDUCATION = 1; + + // Lower secondary education; First stage of secondary education building on + // primary education, typically with a more subject-oriented curriculum. + // ISCED code 2. + // Ex) Middle school + LOWER_SECONDARY_EDUCATION = 2; + + // Middle education; Second/final stage of secondary education preparing for + // tertiary education and/or providing skills relevant to employment. + // Usually with an increased range of subject options and streams. ISCED + // code 3. + // Ex) High school + UPPER_SECONDARY_EDUCATION = 3; + + // Adult Remedial Education; Programmes providing learning experiences that + // build on secondary education and prepare for labour market entry and/or + // tertiary education. The content is broader than secondary but not as + // complex as tertiary education. ISCED code 4. + ADULT_REMEDIAL_EDUCATION = 4; + + // Associate's or equivalent; Short first tertiary programmes that are + // typically practically-based, occupationally-specific and prepare for + // labour market entry. These programmes may also provide a pathway to other + // tertiary programmes. ISCED code 5. + ASSOCIATES_OR_EQUIVALENT = 5; + + // Bachelor's or equivalent; Programmes designed to provide intermediate + // academic and/or professional knowledge, skills and competencies leading + // to a first tertiary degree or equivalent qualification. ISCED code 6. + BACHELORS_OR_EQUIVALENT = 6; + + // Master's or equivalent; Programmes designed to provide advanced academic + // and/or professional knowledge, skills and competencies leading to a + // second tertiary degree or equivalent qualification. ISCED code 7. + MASTERS_OR_EQUIVALENT = 7; + + // Doctoral or equivalent; Programmes designed primarily to lead to an + // advanced research qualification, usually concluding with the submission + // and defense of a substantive dissertation of publishable quality based on + // original research. ISCED code 8. + DOCTORAL_OR_EQUIVALENT = 8; +} + +// An enum that represents the employment type of a job. +enum EmploymentType { + // The default value if the employment type isn't specified. + EMPLOYMENT_TYPE_UNSPECIFIED = 0; + + // The job requires working a number of hours that constitute full + // time employment, typically 40 or more hours per week. + FULL_TIME = 1; + + // The job entails working fewer hours than a full time job, + // typically less than 40 hours a week. + PART_TIME = 2; + + // The job is offered as a contracted, as opposed to a salaried employee, + // position. + CONTRACTOR = 3; + + // The job is offered as a contracted position with the understanding + // that it's converted into a full-time position at the end of the + // contract. Jobs of this type are also returned by a search for + // [EmploymentType.CONTRACTOR][google.cloud.talent.v4.EmploymentType.CONTRACTOR] + // jobs. + CONTRACT_TO_HIRE = 4; + + // The job is offered as a temporary employment opportunity, usually + // a short-term engagement. + TEMPORARY = 5; + + // The job is a fixed-term opportunity for students or entry-level job + // seekers to obtain on-the-job training, typically offered as a summer + // position. + INTERN = 6; + + // The is an opportunity for an individual to volunteer, where there's no + // expectation of compensation for the provided services. + VOLUNTEER = 7; + + // The job requires an employee to work on an as-needed basis with a + // flexible schedule. + PER_DIEM = 8; + + // The job involves employing people in remote areas and flying them + // temporarily to the work site instead of relocating employees and their + // families permanently. + FLY_IN_FLY_OUT = 9; + + // The job does not fit any of the other listed types. + OTHER_EMPLOYMENT_TYPE = 10; +} + +// An enum that represents the required experience level required for the job. +enum JobLevel { + // The default value if the level isn't specified. + JOB_LEVEL_UNSPECIFIED = 0; + + // Entry-level individual contributors, typically with less than 2 years of + // experience in a similar role. Includes interns. + ENTRY_LEVEL = 1; + + // Experienced individual contributors, typically with 2+ years of + // experience in a similar role. + EXPERIENCED = 2; + + // Entry- to mid-level managers responsible for managing a team of people. + MANAGER = 3; + + // Senior-level managers responsible for managing teams of managers. + DIRECTOR = 4; + + // Executive-level managers and above, including C-level positions. + EXECUTIVE = 5; +} + +// An enum that represents the categorization or primary focus of specific +// role. This value is different than the "industry" associated with a role, +// which is related to the categorization of the company listing the job. +enum JobCategory { + // The default value if the category isn't specified. + JOB_CATEGORY_UNSPECIFIED = 0; + + // An accounting and finance job, such as an Accountant. + ACCOUNTING_AND_FINANCE = 1; + + // An administrative and office job, such as an Administrative Assistant. + ADMINISTRATIVE_AND_OFFICE = 2; + + // An advertising and marketing job, such as Marketing Manager. + ADVERTISING_AND_MARKETING = 3; + + // An animal care job, such as Veterinarian. + ANIMAL_CARE = 4; + + // An art, fashion, or design job, such as Designer. + ART_FASHION_AND_DESIGN = 5; + + // A business operations job, such as Business Operations Manager. + BUSINESS_OPERATIONS = 6; + + // A cleaning and facilities job, such as Custodial Staff. + CLEANING_AND_FACILITIES = 7; + + // A computer and IT job, such as Systems Administrator. + COMPUTER_AND_IT = 8; + + // A construction job, such as General Laborer. + CONSTRUCTION = 9; + + // A customer service job, such s Cashier. + CUSTOMER_SERVICE = 10; + + // An education job, such as School Teacher. + EDUCATION = 11; + + // An entertainment and travel job, such as Flight Attendant. + ENTERTAINMENT_AND_TRAVEL = 12; + + // A farming or outdoor job, such as Park Ranger. + FARMING_AND_OUTDOORS = 13; + + // A healthcare job, such as Registered Nurse. + HEALTHCARE = 14; + + // A human resources job, such as Human Resources Director. + HUMAN_RESOURCES = 15; + + // An installation, maintenance, or repair job, such as Electrician. + INSTALLATION_MAINTENANCE_AND_REPAIR = 16; + + // A legal job, such as Law Clerk. + LEGAL = 17; + + // A management job, often used in conjunction with another category, + // such as Store Manager. + MANAGEMENT = 18; + + // A manufacturing or warehouse job, such as Assembly Technician. + MANUFACTURING_AND_WAREHOUSE = 19; + + // A media, communications, or writing job, such as Media Relations. + MEDIA_COMMUNICATIONS_AND_WRITING = 20; + + // An oil, gas or mining job, such as Offshore Driller. + OIL_GAS_AND_MINING = 21; + + // A personal care and services job, such as Hair Stylist. + PERSONAL_CARE_AND_SERVICES = 22; + + // A protective services job, such as Security Guard. + PROTECTIVE_SERVICES = 23; + + // A real estate job, such as Buyer's Agent. + REAL_ESTATE = 24; + + // A restaurant and hospitality job, such as Restaurant Server. + RESTAURANT_AND_HOSPITALITY = 25; + + // A sales and/or retail job, such Sales Associate. + SALES_AND_RETAIL = 26; + + // A science and engineering job, such as Lab Technician. + SCIENCE_AND_ENGINEERING = 27; + + // A social services or non-profit job, such as Case Worker. + SOCIAL_SERVICES_AND_NON_PROFIT = 28; + + // A sports, fitness, or recreation job, such as Personal Trainer. + SPORTS_FITNESS_AND_RECREATION = 29; + + // A transportation or logistics job, such as Truck Driver. + TRANSPORTATION_AND_LOGISTICS = 30; +} + +// An enum that represents the job posting region. In most cases, job postings +// don't need to specify a region. If a region is given, jobs are +// eligible for searches in the specified region. +enum PostingRegion { + // If the region is unspecified, the job is only returned if it + // matches the [LocationFilter][google.cloud.talent.v4.LocationFilter]. + POSTING_REGION_UNSPECIFIED = 0; + + // In addition to exact location matching, job posting is returned when the + // [LocationFilter][google.cloud.talent.v4.LocationFilter] in the search query + // is in the same administrative area as the returned job posting. For + // example, if a `ADMINISTRATIVE_AREA` job is posted in "CA, USA", it's + // returned if [LocationFilter][google.cloud.talent.v4.LocationFilter] has + // "Mountain View". + // + // Administrative area refers to top-level administrative subdivision of this + // country. For example, US state, IT region, UK constituent nation and + // JP prefecture. + ADMINISTRATIVE_AREA = 1; + + // In addition to exact location matching, job is returned when + // [LocationFilter][google.cloud.talent.v4.LocationFilter] in search query is + // in the same country as this job. For example, if a `NATION_WIDE` job is + // posted in "USA", it's returned if + // [LocationFilter][google.cloud.talent.v4.LocationFilter] has 'Mountain + // View'. + NATION = 2; + + // Job allows employees to work remotely (telecommute). + // If locations are provided with this value, the job is + // considered as having a location, but telecommuting is allowed. + TELECOMMUTE = 3; +} + +// Deprecated. All resources are only visible to the owner. +// +// An enum that represents who has view access to the resource. +enum Visibility { + option deprecated = true; + + // Default value. + VISIBILITY_UNSPECIFIED = 0; + + // The resource is only visible to the GCP account who owns it. + ACCOUNT_ONLY = 1; + + // The resource is visible to the owner and may be visible to other + // applications and processes at Google. + SHARED_WITH_GOOGLE = 2; + + // The resource is visible to the owner and may be visible to all other API + // clients. + SHARED_WITH_PUBLIC = 3; +} + +// Option for HTML content sanitization on user input fields, for example, job +// description. By setting this option, user can determine whether and how +// sanitization is performed on these fields. +enum HtmlSanitization { + // Default value. + HTML_SANITIZATION_UNSPECIFIED = 0; + + // Disables sanitization on HTML input. + HTML_SANITIZATION_DISABLED = 1; + + // Sanitizes HTML input, only accepts bold, italic, ordered list, and + // unordered list markup tags. + SIMPLE_FORMATTING_ONLY = 2; +} + +// Method for commute. +enum CommuteMethod { + // Commute method isn't specified. + COMMUTE_METHOD_UNSPECIFIED = 0; + + // Commute time is calculated based on driving time. + DRIVING = 1; + + // Commute time is calculated based on public transit including bus, metro, + // subway, and so on. + TRANSIT = 2; + + // Commute time is calculated based on walking time. + WALKING = 3; + + // Commute time is calculated based on biking time. + CYCLING = 4; +} + +// A resource that represents a location with full geographic information. +message Location { + // An enum which represents the type of a location. + enum LocationType { + // Default value if the type isn't specified. + LOCATION_TYPE_UNSPECIFIED = 0; + + // A country level location. + COUNTRY = 1; + + // A state or equivalent level location. + ADMINISTRATIVE_AREA = 2; + + // A county or equivalent level location. + SUB_ADMINISTRATIVE_AREA = 3; + + // A city or equivalent level location. + LOCALITY = 4; + + // A postal code level location. + POSTAL_CODE = 5; + + // A sublocality is a subdivision of a locality, for example a city borough, + // ward, or arrondissement. Sublocalities are usually recognized by a local + // political authority. For example, Manhattan and Brooklyn are recognized + // as boroughs by the City of New York, and are therefore modeled as + // sublocalities. + SUB_LOCALITY = 6; + + // A district or equivalent level location. + SUB_LOCALITY_1 = 7; + + // A smaller district or equivalent level display. + SUB_LOCALITY_2 = 8; + + // A neighborhood level location. + NEIGHBORHOOD = 9; + + // A street address level location. + STREET_ADDRESS = 10; + } + + // The type of a location, which corresponds to the address lines field of + // [google.type.PostalAddress][google.type.PostalAddress]. For example, + // "Downtown, Atlanta, GA, USA" has a type of + // [LocationType.NEIGHBORHOOD][google.cloud.talent.v4.Location.LocationType.NEIGHBORHOOD], + // and "Kansas City, KS, USA" has a type of + // [LocationType.LOCALITY][google.cloud.talent.v4.Location.LocationType.LOCALITY]. + LocationType location_type = 1; + + // Postal address of the location that includes human readable information, + // such as postal delivery and payments addresses. Given a postal address, + // a postal service can deliver items to a premises, P.O. Box, or other + // delivery location. + google.type.PostalAddress postal_address = 2; + + // An object representing a latitude/longitude pair. + google.type.LatLng lat_lng = 3; + + // Radius in miles of the job location. This value is derived from the + // location bounding box in which a circle with the specified radius + // centered from [google.type.LatLng][google.type.LatLng] covers the area + // associated with the job location. For example, currently, "Mountain View, + // CA, USA" has a radius of 6.17 miles. + double radius_miles = 4; +} + +// Meta information related to the job searcher or entity +// conducting the job search. This information is used to improve the +// performance of the service. +message RequestMetadata { + // Required if + // [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + // is unset or `false`. + // + // The client-defined scope or source of the service call, which typically + // is the domain on + // which the service has been implemented and is currently being run. + // + // For example, if the service is being run by client Foo, Inc., on + // job board www.foo.com and career site www.bar.com, then this field is + // set to "foo.com" for use on the job board, and "bar.com" for use on the + // career site. + // + // Note that any improvements to the model for a particular tenant site rely + // on this field being set correctly to a unique domain. + // + // The maximum number of allowed characters is 255. + string domain = 1; + + // Required if + // [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + // is unset or `false`. + // + // A unique session identification string. A session is defined as the + // duration of an end user's interaction with the service over a certain + // period. + // Obfuscate this field for privacy concerns before + // providing it to the service. + // + // Note that any improvements to the model for a particular tenant site rely + // on this field being set correctly to a unique session ID. + // + // The maximum number of allowed characters is 255. + string session_id = 2; + + // Required if + // [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + // is unset or `false`. + // + // A unique user identification string, as determined by the client. + // To have the strongest positive impact on search quality + // make sure the client-level is unique. + // Obfuscate this field for privacy concerns before + // providing it to the service. + // + // Note that any improvements to the model for a particular tenant site rely + // on this field being set correctly to a unique user ID. + // + // The maximum number of allowed characters is 255. + string user_id = 3; + + // Only set when any of + // [domain][google.cloud.talent.v4.RequestMetadata.domain], + // [session_id][google.cloud.talent.v4.RequestMetadata.session_id] and + // [user_id][google.cloud.talent.v4.RequestMetadata.user_id] isn't available + // for some reason. It is highly recommended not to set this field and provide + // accurate [domain][google.cloud.talent.v4.RequestMetadata.domain], + // [session_id][google.cloud.talent.v4.RequestMetadata.session_id] and + // [user_id][google.cloud.talent.v4.RequestMetadata.user_id] for the best + // service experience. + bool allow_missing_ids = 4; + + // The type of device used by the job seeker at the time of the call to the + // service. + DeviceInfo device_info = 5; +} + +// Additional information returned to client, such as debugging information. +message ResponseMetadata { + // A unique id associated with this call. + // This id is logged for tracking purposes. + string request_id = 1; +} + +// Device information collected from the job seeker, candidate, or +// other entity conducting the job search. Providing this information improves +// the quality of the search results across devices. +message DeviceInfo { + // An enumeration describing an API access portal and exposure mechanism. + enum DeviceType { + // The device type isn't specified. + DEVICE_TYPE_UNSPECIFIED = 0; + + // A desktop web browser, such as, Chrome, Firefox, Safari, or Internet + // Explorer) + WEB = 1; + + // A mobile device web browser, such as a phone or tablet with a Chrome + // browser. + MOBILE_WEB = 2; + + // An Android device native application. + ANDROID = 3; + + // An iOS device native application. + IOS = 4; + + // A bot, as opposed to a device operated by human beings, such as a web + // crawler. + BOT = 5; + + // Other devices types. + OTHER = 6; + } + + // Type of the device. + DeviceType device_type = 1; + + // A device-specific ID. The ID must be a unique identifier that + // distinguishes the device from other devices. + string id = 2; +} + +// Custom attribute values that are either filterable or non-filterable. +message CustomAttribute { + // Exactly one of + // [string_values][google.cloud.talent.v4.CustomAttribute.string_values] or + // [long_values][google.cloud.talent.v4.CustomAttribute.long_values] must be + // specified. + // + // This field is used to perform a string match (`CASE_SENSITIVE_MATCH` or + // `CASE_INSENSITIVE_MATCH`) search. + // For filterable `string_value`s, a maximum total number of 200 values + // is allowed, with each `string_value` has a byte size of no more than + // 500B. For unfilterable `string_values`, the maximum total byte size of + // unfilterable `string_values` is 50KB. + // + // Empty string isn't allowed. + repeated string string_values = 1; + + // Exactly one of + // [string_values][google.cloud.talent.v4.CustomAttribute.string_values] or + // [long_values][google.cloud.talent.v4.CustomAttribute.long_values] must be + // specified. + // + // This field is used to perform number range search. + // (`EQ`, `GT`, `GE`, `LE`, `LT`) over filterable `long_value`. + // + // Currently at most 1 + // [long_values][google.cloud.talent.v4.CustomAttribute.long_values] is + // supported. + repeated int64 long_values = 2; + + // If the `filterable` flag is true, the custom field values may be used for + // custom attribute filters + // [JobQuery.custom_attribute_filter][google.cloud.talent.v4.JobQuery.custom_attribute_filter]. + // If false, these values may not be used for custom attribute filters. + // + // Default is false. + bool filterable = 3; + + // If the `keyword_searchable` flag is true, the keywords in custom fields are + // searchable by keyword match. + // If false, the values are not searchable by keyword match. + // + // Default is false. + bool keyword_searchable = 4; +} + +// Spell check result. +message SpellingCorrection { + // Indicates if the query was corrected by the spell checker. + bool corrected = 1; + + // Correction output consisting of the corrected keyword string. + string corrected_text = 2; + + // Corrected output with html tags to highlight the corrected words. + // Corrected words are called out with the "..." html tags. + // + // For example, the user input query is "software enginear", where the second + // word, "enginear," is incorrect. It should be "engineer". When spelling + // correction is enabled, this value is + // "software engineer". + string corrected_html = 3; +} + +// Job compensation details. +message CompensationInfo { + // A compensation entry that represents one component of compensation, such + // as base pay, bonus, or other compensation type. + // + // Annualization: One compensation entry can be annualized if + // - it contains valid + // [amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // or + // [range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]. + // - and its + // [expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year] + // is set or can be derived. Its annualized range is determined as + // ([amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // or + // [range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]) + // times + // [expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + message CompensationEntry { + // Compensation type. + // + // Default is + // [CompensationType.COMPENSATION_TYPE_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationType.COMPENSATION_TYPE_UNSPECIFIED]. + CompensationType type = 1; + + // Frequency of the specified amount. + // + // Default is + // [CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED]. + CompensationUnit unit = 2; + + // Compensation amount. It could be a fixed amount or a floating range. + oneof compensation_amount { + // Compensation amount. + google.type.Money amount = 3; + + // Compensation range. + CompensationRange range = 4; + } + + // Compensation description. For example, could + // indicate equity terms or provide additional context to an estimated + // bonus. + string description = 5; + + // Expected number of units paid each year. If not specified, when + // [Job.employment_types][google.cloud.talent.v4.Job.employment_types] is + // FULLTIME, a default value is inferred based on + // [unit][google.cloud.talent.v4.CompensationInfo.CompensationEntry.unit]. + // Default values: + // - HOURLY: 2080 + // - DAILY: 260 + // - WEEKLY: 52 + // - MONTHLY: 12 + // - ANNUAL: 1 + google.protobuf.DoubleValue expected_units_per_year = 6; + } + + // Compensation range. + message CompensationRange { + // The maximum amount of compensation. If left empty, the value is set + // to a maximal compensation value and the currency code is set to + // match the [currency code][google.type.Money.currency_code] of + // min_compensation. + google.type.Money max_compensation = 2; + + // The minimum amount of compensation. If left empty, the value is set + // to zero and the currency code is set to match the + // [currency code][google.type.Money.currency_code] of max_compensation. + google.type.Money min_compensation = 1; + } + + // The type of compensation. + // + // For compensation amounts specified in non-monetary amounts, + // describe the compensation scheme in the + // [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description]. + // + // For example, tipping format is described in + // [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + // (for example, "expect 15-20% tips based on customer bill.") and an estimate + // of the tips provided in + // [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // or + // [CompensationEntry.range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range] + // ($10 per hour). + // + // For example, equity is described in + // [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + // (for example, "1% - 2% equity vesting over 4 years, 1 year cliff") and + // value estimated in + // [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // or + // [CompensationEntry.range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]. + // If no value estimate is possible, units are + // [CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED] + // and then further clarified in + // [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + // field. + enum CompensationType { + // Default value. + COMPENSATION_TYPE_UNSPECIFIED = 0; + + // Base compensation: Refers to the fixed amount of money paid to an + // employee by an employer in return for work performed. Base compensation + // does not include benefits, bonuses or any other potential compensation + // from an employer. + BASE = 1; + + // Bonus. + BONUS = 2; + + // Signing bonus. + SIGNING_BONUS = 3; + + // Equity. + EQUITY = 4; + + // Profit sharing. + PROFIT_SHARING = 5; + + // Commission. + COMMISSIONS = 6; + + // Tips. + TIPS = 7; + + // Other compensation type. + OTHER_COMPENSATION_TYPE = 8; + } + + // Pay frequency. + enum CompensationUnit { + // Default value. + COMPENSATION_UNIT_UNSPECIFIED = 0; + + // Hourly. + HOURLY = 1; + + // Daily. + DAILY = 2; + + // Weekly + WEEKLY = 3; + + // Monthly. + MONTHLY = 4; + + // Yearly. + YEARLY = 5; + + // One time. + ONE_TIME = 6; + + // Other compensation units. + OTHER_COMPENSATION_UNIT = 7; + } + + // Job compensation information. + // + // At most one entry can be of type + // [CompensationInfo.CompensationType.BASE][google.cloud.talent.v4.CompensationInfo.CompensationType.BASE], + // which is referred as **base compensation entry** for the job. + repeated CompensationEntry entries = 1; + + // Output only. Annualized base compensation range. Computed as base + // compensation entry's + // [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // times + // [CompensationEntry.expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + // + // See + // [CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + // for explanation on compensation annualization. + CompensationRange annualized_base_compensation_range = 2 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Annualized total compensation range. Computed as all + // compensation entries' + // [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + // times + // [CompensationEntry.expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + // + // See + // [CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + // for explanation on compensation annualization. + CompensationRange annualized_total_compensation_range = 3 + [(google.api.field_behavior) = OUTPUT_ONLY]; +} + +// Metadata used for long running operations returned by CTS batch APIs. +// It's used to replace +// [google.longrunning.Operation.metadata][google.longrunning.Operation.metadata]. +message BatchOperationMetadata { + enum State { + // Default value. + STATE_UNSPECIFIED = 0; + + // The batch operation is being prepared for processing. + INITIALIZING = 1; + + // The batch operation is actively being processed. + PROCESSING = 2; + + // The batch operation is processed, and at least one item has been + // successfully processed. + SUCCEEDED = 3; + + // The batch operation is done and no item has been successfully processed. + FAILED = 4; + + // The batch operation is in the process of cancelling after + // [google.longrunning.Operations.CancelOperation][google.longrunning.Operations.CancelOperation] + // is called. + CANCELLING = 5; + + // The batch operation is done after + // [google.longrunning.Operations.CancelOperation][google.longrunning.Operations.CancelOperation] + // is called. Any items processed before cancelling are returned in the + // response. + CANCELLED = 6; + } + + // The state of a long running operation. + State state = 1; + + // More detailed information about operation state. + string state_description = 2; + + // Count of successful item(s) inside an operation. + int32 success_count = 3; + + // Count of failed item(s) inside an operation. + int32 failure_count = 4; + + // Count of total item(s) inside an operation. + int32 total_count = 5; + + // The time when the batch operation is created. + google.protobuf.Timestamp create_time = 6; + + // The time when the batch operation status is updated. The metadata and the + // [update_time][google.cloud.talent.v4.BatchOperationMetadata.update_time] is + // refreshed every minute otherwise cached data is returned. + google.protobuf.Timestamp update_time = 7; + + // The time when the batch operation is finished and + // [google.longrunning.Operation.done][google.longrunning.Operation.done] is + // set to `true`. + google.protobuf.Timestamp end_time = 8; +} diff --git a/google/cloud/talent_v4/proto/company.proto b/google/cloud/talent_v4/proto/company.proto new file mode 100644 index 00000000..5c1de8f2 --- /dev/null +++ b/google/cloud/talent_v4/proto/company.proto @@ -0,0 +1,118 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "CompanyProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A Company resource represents a company in the service. A company is the +// entity that owns job postings, that is, the hiring entity responsible for +// employing applicants for the job position. +message Company { + option (google.api.resource) = { + type: "jobs.googleapis.com/Company" + pattern: "projects/{project}/tenants/{tenant}/companies/{company}" + }; + + // Derived details about the company. + message DerivedInfo { + // A structured headquarters location of the company, resolved from + // [Company.headquarters_address][google.cloud.talent.v4.Company.headquarters_address] + // if provided. + Location headquarters_location = 1; + } + + // Required during company update. + // + // The resource name for a company. This is generated by the service when a + // company is created. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", for + // example, "projects/foo/tenants/bar/companies/baz". + string name = 1; + + // Required. The display name of the company, for example, "Google LLC". + string display_name = 2 [(google.api.field_behavior) = REQUIRED]; + + // Required. Client side company identifier, used to uniquely identify the + // company. + // + // The maximum number of allowed characters is 255. + string external_id = 3 [(google.api.field_behavior) = REQUIRED]; + + // The employer's company size. + CompanySize size = 4; + + // The street address of the company's main headquarters, which may be + // different from the job location. The service attempts + // to geolocate the provided address, and populates a more specific + // location wherever possible in + // [DerivedInfo.headquarters_location][google.cloud.talent.v4.Company.DerivedInfo.headquarters_location]. + string headquarters_address = 5; + + // Set to true if it is the hiring agency that post jobs for other + // employers. + // + // Defaults to false if not provided. + bool hiring_agency = 6; + + // Equal Employment Opportunity legal disclaimer text to be + // associated with all jobs, and typically to be displayed in all + // roles. + // + // The maximum number of allowed characters is 500. + string eeo_text = 7; + + // The URI representing the company's primary web site or home page, + // for example, "https://www.google.com". + // + // The maximum number of allowed characters is 255. + string website_uri = 8; + + // The URI to employer's career site or careers page on the employer's web + // site, for example, "https://careers.google.com". + string career_site_uri = 9; + + // A URI that hosts the employer's company logo. + string image_uri = 10; + + // A list of keys of filterable + // [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes], + // whose corresponding `string_values` are used in keyword searches. Jobs with + // `string_values` under these specified field keys are returned if any + // of the values match the search keyword. Custom field values with + // parenthesis, brackets and special symbols are not searchable as-is, + // and those keyword queries must be surrounded by quotes. + repeated string keyword_searchable_job_custom_attributes = 11; + + // Output only. Derived details about the company. + DerivedInfo derived_info = 12 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Indicates whether a company is flagged to be suspended from + // public availability by the service when job content appears suspicious, + // abusive, or spammy. + bool suspended = 13 [(google.api.field_behavior) = OUTPUT_ONLY]; +} diff --git a/google/cloud/talent_v4/proto/company_service.proto b/google/cloud/talent_v4/proto/company_service.proto new file mode 100644 index 00000000..78bba7cc --- /dev/null +++ b/google/cloud/talent_v4/proto/company_service.proto @@ -0,0 +1,184 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; +import "google/cloud/talent/v4/company.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "CompanyServiceProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A service that handles company management, including CRUD and enumeration. +service CompanyService { + option (google.api.default_host) = "jobs.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/jobs"; + + // Creates a new company entity. + rpc CreateCompany(CreateCompanyRequest) returns (Company) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/companies" + body: "company" + }; + option (google.api.method_signature) = "parent,company"; + } + + // Retrieves specified company. + rpc GetCompany(GetCompanyRequest) returns (Company) { + option (google.api.http) = { + get: "/v4/{name=projects/*/tenants/*/companies/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Updates specified company. + rpc UpdateCompany(UpdateCompanyRequest) returns (Company) { + option (google.api.http) = { + patch: "/v4/{company.name=projects/*/tenants/*/companies/*}" + body: "company" + }; + option (google.api.method_signature) = "company,update_mask"; + } + + // Deletes specified company. + // Prerequisite: The company has no jobs associated with it. + rpc DeleteCompany(DeleteCompanyRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v4/{name=projects/*/tenants/*/companies/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Lists all companies associated with the project. + rpc ListCompanies(ListCompaniesRequest) returns (ListCompaniesResponse) { + option (google.api.http) = { + get: "/v4/{parent=projects/*/tenants/*}/companies" + }; + option (google.api.method_signature) = "parent"; + } +} + +// The Request of the CreateCompany method. +message CreateCompanyRequest { + // Required. Resource name of the tenant under which the company is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; + + // Required. The company to be created. + Company company = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Request for getting a company by name. +message GetCompanyRequest { + // Required. The resource name of the company to be retrieved. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", for + // example, "projects/api-test-project/tenants/foo/companies/bar". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Company" } + ]; +} + +// Request for updating a specified company. +message UpdateCompanyRequest { + // Required. The company resource to replace the current resource in the + // system. + Company company = 1 [(google.api.field_behavior) = REQUIRED]; + + // Strongly recommended for the best service experience. + // + // If [update_mask][google.cloud.talent.v4.UpdateCompanyRequest.update_mask] + // is provided, only the specified fields in + // [company][google.cloud.talent.v4.UpdateCompanyRequest.company] are updated. + // Otherwise all the fields are updated. + // + // A field mask to specify the company fields to be updated. Only + // top level fields of [Company][google.cloud.talent.v4.Company] are + // supported. + google.protobuf.FieldMask update_mask = 2; +} + +// Request to delete a company. +message DeleteCompanyRequest { + // Required. The resource name of the company to be deleted. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", for + // example, "projects/foo/tenants/bar/companies/baz". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Company" } + ]; +} + +// List companies for which the client has ACL visibility. +message ListCompaniesRequest { + // Required. Resource name of the tenant under which the company is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; + + // The starting indicator from which to return results. + string page_token = 2; + + // The maximum number of companies to be returned, at most 100. + // Default is 100 if a non-positive number is provided. + int32 page_size = 3; + + // Set to true if the companies requested must have open jobs. + // + // Defaults to false. + // + // If true, at most + // [page_size][google.cloud.talent.v4.ListCompaniesRequest.page_size] of + // companies are fetched, among which only those with open jobs are returned. + bool require_open_jobs = 4; +} + +// The List companies response object. +message ListCompaniesResponse { + // Companies for the current client. + repeated Company companies = 1; + + // A token to retrieve the next page of results. + string next_page_token = 2; + + // Additional information for the API invocation, such as the request + // tracking id. + ResponseMetadata metadata = 3; +} diff --git a/google/cloud/talent_v4/proto/completion_service.proto b/google/cloud/talent_v4/proto/completion_service.proto new file mode 100644 index 00000000..e910d580 --- /dev/null +++ b/google/cloud/talent_v4/proto/completion_service.proto @@ -0,0 +1,163 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "CompletionServiceProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A service handles auto completion. +service Completion { + option (google.api.default_host) = "jobs.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/jobs"; + + // Completes the specified prefix with keyword suggestions. + // Intended for use by a job search auto-complete search box. + rpc CompleteQuery(CompleteQueryRequest) returns (CompleteQueryResponse) { + option (google.api.http) = { + get: "/v4/{tenant=projects/*/tenants/*}:completeQuery" + }; + } +} + +// Auto-complete parameters. +message CompleteQueryRequest { + // Enum to specify the scope of completion. + enum CompletionScope { + // Default value. + COMPLETION_SCOPE_UNSPECIFIED = 0; + + // Suggestions are based only on the data provided by the client. + TENANT = 1; + + // Suggestions are based on all jobs data in the system that's visible to + // the client + PUBLIC = 2; + } + + // Enum to specify auto-completion topics. + enum CompletionType { + // Default value. + COMPLETION_TYPE_UNSPECIFIED = 0; + + // Suggest job titles for jobs autocomplete. + // + // For + // [CompletionType.JOB_TITLE][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.JOB_TITLE] + // type, only open jobs with the same + // [language_codes][google.cloud.talent.v4.CompleteQueryRequest.language_codes] + // are returned. + JOB_TITLE = 1; + + // Suggest company names for jobs autocomplete. + // + // For + // [CompletionType.COMPANY_NAME][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMPANY_NAME] + // type, only companies having open jobs with the same + // [language_codes][google.cloud.talent.v4.CompleteQueryRequest.language_codes] + // are returned. + COMPANY_NAME = 2; + + // Suggest both job titles and company names for jobs autocomplete. + // + // For + // [CompletionType.COMBINED][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMBINED] + // type, only open jobs with the same + // [language_codes][google.cloud.talent.v4.CompleteQueryRequest.language_codes] + // or companies having open jobs with the same + // [language_codes][google.cloud.talent.v4.CompleteQueryRequest.language_codes] + // are returned. + COMBINED = 3; + } + + // Required. Resource name of tenant the completion is performed within. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string tenant = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; + + // Required. The query used to generate suggestions. + // + // The maximum number of allowed characters is 255. + string query = 2 [(google.api.field_behavior) = REQUIRED]; + + // The list of languages of the query. This is + // the BCP-47 language code, such as "en-US" or "sr-Latn". + // For more information, see + // [Tags for Identifying Languages](https://tools.ietf.org/html/bcp47). + // + // The maximum number of allowed characters is 255. + repeated string language_codes = 3; + + // Required. Completion result count. + // + // The maximum allowed page size is 10. + int32 page_size = 4 [(google.api.field_behavior) = REQUIRED]; + + // If provided, restricts completion to specified company. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", for + // example, "projects/foo/tenants/bar/companies/baz". + string company = 5 [ + (google.api.resource_reference) = { type: "jobs.googleapis.com/Company" } + ]; + + // The scope of the completion. The defaults is + // [CompletionScope.PUBLIC][google.cloud.talent.v4.CompleteQueryRequest.CompletionScope.PUBLIC]. + CompletionScope scope = 6; + + // The completion topic. The default is + // [CompletionType.COMBINED][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMBINED]. + CompletionType type = 7; +} + +// Response of auto-complete query. +message CompleteQueryResponse { + // Resource that represents completion results. + message CompletionResult { + // The suggestion for the query. + string suggestion = 1; + + // The completion topic. + CompleteQueryRequest.CompletionType type = 2; + + // The URI of the company image for + // [COMPANY_NAME][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMPANY_NAME]. + string image_uri = 3; + } + + // Results of the matching job/company candidates. + repeated CompletionResult completion_results = 1; + + // Additional information for the API invocation, such as the request + // tracking id. + ResponseMetadata metadata = 2; +} diff --git a/google/cloud/talent_v4/proto/event.proto b/google/cloud/talent_v4/proto/event.proto new file mode 100644 index 00000000..862a4b9a --- /dev/null +++ b/google/cloud/talent_v4/proto/event.proto @@ -0,0 +1,180 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "EventProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// An event issued when an end user interacts with the application that +// implements Cloud Talent Solution. Providing this information improves the +// quality of results for the API clients, enabling the +// service to perform optimally. The number of events sent must be consistent +// with other calls, such as job searches, issued to the service by the client. +message ClientEvent { + // Strongly recommended for the best service experience. + // + // A unique ID generated in the API responses. It can be found in + // [ResponseMetadata.request_id][google.cloud.talent.v4.ResponseMetadata.request_id]. + string request_id = 1; + + // Required. A unique identifier, generated by the client application. + string event_id = 2 [(google.api.field_behavior) = REQUIRED]; + + // Required. The timestamp of the event. + google.protobuf.Timestamp create_time = 4 + [(google.api.field_behavior) = REQUIRED]; + + // Required. + // + // The detail information of a specific event type. + oneof event { + // An event issued when a job seeker interacts with the application that + // implements Cloud Talent Solution. + JobEvent job_event = 5; + } + + // Notes about the event provided by recruiters or other users, for example, + // feedback on why a job was bookmarked. + string event_notes = 9; +} + +// An event issued when a job seeker interacts with the application that +// implements Cloud Talent Solution. +message JobEvent { + // An enumeration of an event attributed to the behavior of the end user, + // such as a job seeker. + enum JobEventType { + // The event is unspecified by other provided values. + JOB_EVENT_TYPE_UNSPECIFIED = 0; + + // The job seeker or other entity interacting with the service has + // had a job rendered in their view, such as in a list of search results in + // a compressed or clipped format. This event is typically associated with + // the viewing of a jobs list on a single page by a job seeker. + IMPRESSION = 1; + + // The job seeker, or other entity interacting with the service, has + // viewed the details of a job, including the full description. This + // event doesn't apply to the viewing a snippet of a job appearing as a + // part of the job search results. Viewing a snippet is associated with an + // [impression][google.cloud.talent.v4.JobEvent.JobEventType.IMPRESSION]). + VIEW = 2; + + // The job seeker or other entity interacting with the service + // performed an action to view a job and was redirected to a different + // website for job. + VIEW_REDIRECT = 3; + + // The job seeker or other entity interacting with the service + // began the process or demonstrated the intention of applying for a job. + APPLICATION_START = 4; + + // The job seeker or other entity interacting with the service + // submitted an application for a job. + APPLICATION_FINISH = 5; + + // The job seeker or other entity interacting with the service + // submitted an application for a job with a single click without + // entering information. If a job seeker performs this action, send only + // this event to the service. Do not also send + // [JobEventType.APPLICATION_START][google.cloud.talent.v4.JobEvent.JobEventType.APPLICATION_START] + // or + // [JobEventType.APPLICATION_FINISH][google.cloud.talent.v4.JobEvent.JobEventType.APPLICATION_FINISH] + // events. + APPLICATION_QUICK_SUBMISSION = 6; + + // The job seeker or other entity interacting with the service + // performed an action to apply to a job and was redirected to a different + // website to complete the application. + APPLICATION_REDIRECT = 7; + + // The job seeker or other entity interacting with the service began the + // process or demonstrated the intention of applying for a job from the + // search results page without viewing the details of the job posting. + // If sending this event, JobEventType.VIEW event shouldn't be sent. + APPLICATION_START_FROM_SEARCH = 8; + + // The job seeker, or other entity interacting with the service, performs an + // action with a single click from the search results page to apply to a job + // (without viewing the details of the job posting), and is redirected + // to a different website to complete the application. If a candidate + // performs this action, send only this event to the service. Do not also + // send + // [JobEventType.APPLICATION_START][google.cloud.talent.v4.JobEvent.JobEventType.APPLICATION_START], + // [JobEventType.APPLICATION_FINISH][google.cloud.talent.v4.JobEvent.JobEventType.APPLICATION_FINISH] + // or [JobEventType.VIEW][google.cloud.talent.v4.JobEvent.JobEventType.VIEW] + // events. + APPLICATION_REDIRECT_FROM_SEARCH = 9; + + // This event should be used when a company submits an application + // on behalf of a job seeker. This event is intended for use by staffing + // agencies attempting to place candidates. + APPLICATION_COMPANY_SUBMIT = 10; + + // The job seeker or other entity interacting with the service demonstrated + // an interest in a job by bookmarking or saving it. + BOOKMARK = 11; + + // The job seeker or other entity interacting with the service was + // sent a notification, such as an email alert or device notification, + // containing one or more jobs listings generated by the service. + NOTIFICATION = 12; + + // The job seeker or other entity interacting with the service was + // employed by the hiring entity (employer). Send this event + // only if the job seeker was hired through an application that was + // initiated by a search conducted through the Cloud Talent Solution + // service. + HIRED = 13; + + // A recruiter or staffing agency submitted an application on behalf of the + // candidate after interacting with the service to identify a suitable job + // posting. + SENT_CV = 14; + + // The entity interacting with the service (for example, the job seeker), + // was granted an initial interview by the hiring entity (employer). This + // event should only be sent if the job seeker was granted an interview as + // part of an application that was initiated by a search conducted through / + // recommendation provided by the Cloud Talent Solution service. + INTERVIEW_GRANTED = 15; + } + + // Required. The type of the event (see + // [JobEventType][google.cloud.talent.v4.JobEvent.JobEventType]). + JobEventType type = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. The [job name(s)][google.cloud.talent.v4.Job.name] associated + // with this event. For example, if this is an + // [impression][google.cloud.talent.v4.JobEvent.JobEventType.IMPRESSION] + // event, this field contains the identifiers of all jobs shown to the job + // seeker. If this was a + // [view][google.cloud.talent.v4.JobEvent.JobEventType.VIEW] event, this field + // contains the identifier of the viewed job. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}", for + // example, "projects/foo/tenants/bar/jobs/baz". + repeated string jobs = 2 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/google/cloud/talent_v4/proto/event_service.proto b/google/cloud/talent_v4/proto/event_service.proto new file mode 100644 index 00000000..7cd25f11 --- /dev/null +++ b/google/cloud/talent_v4/proto/event_service.proto @@ -0,0 +1,68 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/event.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "EventServiceProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A service handles client event report. +service EventService { + option (google.api.default_host) = "jobs.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/jobs"; + + // Report events issued when end user interacts with customer's application + // that uses Cloud Talent Solution. You may inspect the created events in + // [self service + // tools](https://console.cloud.google.com/talent-solution/overview). + // [Learn + // more](https://cloud.google.com/talent-solution/docs/management-tools) + // about self service tools. + rpc CreateClientEvent(CreateClientEventRequest) returns (ClientEvent) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/clientEvents" + body: "client_event" + }; + option (google.api.method_signature) = "parent,client_event"; + } +} + +// The report event request. +message CreateClientEventRequest { + // Required. Resource name of the tenant under which the event is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; + + // Required. Events issued when end user interacts with customer's application + // that uses Cloud Talent Solution. + ClientEvent client_event = 2 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/google/cloud/talent_v4/proto/filters.proto b/google/cloud/talent_v4/proto/filters.proto new file mode 100644 index 00000000..fb3ffc4f --- /dev/null +++ b/google/cloud/talent_v4/proto/filters.proto @@ -0,0 +1,358 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/cloud/talent/v4/common.proto"; +import "google/protobuf/duration.proto"; +import "google/type/latlng.proto"; +import "google/type/timeofday.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "FiltersProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// The query required to perform a search query. +message JobQuery { + // The query string that matches against the job title, description, and + // location fields. + // + // The maximum number of allowed characters is 255. + string query = 1; + + // The language code of [query][google.cloud.talent.v4.JobQuery.query]. For + // example, "en-US". This field helps to better interpret the query. + // + // If a value isn't specified, the query language code is automatically + // detected, which may not be accurate. + // + // Language code should be in BCP-47 format, such as "en-US" or "sr-Latn". + // For more information, see + // [Tags for Identifying Languages](https://tools.ietf.org/html/bcp47). + string query_language_code = 14; + + // This filter specifies the company entities to search against. + // + // If a value isn't specified, jobs are searched for against all + // companies. + // + // If multiple values are specified, jobs are searched against the + // companies specified. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}". For + // example, "projects/foo/tenants/bar/companies/baz". + // + // At most 20 company filters are allowed. + repeated string companies = 2; + + // The location filter specifies geo-regions containing the jobs to + // search against. See [LocationFilter][google.cloud.talent.v4.LocationFilter] + // for more information. + // + // If a location value isn't specified, jobs fitting the other search + // criteria are retrieved regardless of where they're located. + // + // If multiple values are specified, jobs are retrieved from any of the + // specified locations. If different values are specified for the + // [LocationFilter.distance_in_miles][google.cloud.talent.v4.LocationFilter.distance_in_miles] + // parameter, the maximum provided distance is used for all locations. + // + // At most 5 location filters are allowed. + repeated LocationFilter location_filters = 3; + + // The category filter specifies the categories of jobs to search against. + // See [JobCategory][google.cloud.talent.v4.JobCategory] for more information. + // + // If a value isn't specified, jobs from any category are searched against. + // + // If multiple values are specified, jobs from any of the specified + // categories are searched against. + repeated JobCategory job_categories = 4; + + // Allows filtering jobs by commute time with different travel methods (for + // example, driving or public transit). + // + // Note: This only works when you specify a + // [CommuteMethod][google.cloud.talent.v4.CommuteMethod]. In this case, + // [location_filters][google.cloud.talent.v4.JobQuery.location_filters] is + // ignored. + // + // Currently we don't support sorting by commute time. + CommuteFilter commute_filter = 5; + + // This filter specifies the exact company + // [Company.display_name][google.cloud.talent.v4.Company.display_name] of the + // jobs to search against. + // + // If a value isn't specified, jobs within the search results are + // associated with any company. + // + // If multiple values are specified, jobs within the search results may be + // associated with any of the specified companies. + // + // At most 20 company display name filters are allowed. + repeated string company_display_names = 6; + + // This search filter is applied only to + // [Job.compensation_info][google.cloud.talent.v4.Job.compensation_info]. For + // example, if the filter is specified as "Hourly job with per-hour + // compensation > $15", only jobs meeting these criteria are searched. If a + // filter isn't defined, all open jobs are searched. + CompensationFilter compensation_filter = 7; + + // This filter specifies a structured syntax to match against the + // [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes] + // marked as `filterable`. + // + // The syntax for this expression is a subset of SQL syntax. + // + // Supported operators are: `=`, `!=`, `<`, `<=`, `>`, and `>=` where the + // left of the operator is a custom field key and the right of the operator + // is a number or a quoted string. You must escape backslash (\\) and + // quote (\") characters. + // + // Supported functions are `LOWER([field_name])` to + // perform a case insensitive match and `EMPTY([field_name])` to filter on the + // existence of a key. + // + // Boolean expressions (AND/OR/NOT) are supported up to 3 levels of + // nesting (for example, "((A AND B AND C) OR NOT D) AND E"), a maximum of 100 + // comparisons or functions are allowed in the expression. The expression + // must be < 6000 bytes in length. + // + // Sample Query: + // `(LOWER(driving_license)="class \"a\"" OR EMPTY(driving_license)) AND + // driving_years > 10` + string custom_attribute_filter = 8; + + // This flag controls the spell-check feature. If false, the + // service attempts to correct a misspelled query, + // for example, "enginee" is corrected to "engineer". + // + // Defaults to false: a spell check is performed. + bool disable_spell_check = 9; + + // The employment type filter specifies the employment type of jobs to + // search against, such as + // [EmploymentType.FULL_TIME][google.cloud.talent.v4.EmploymentType.FULL_TIME]. + // + // If a value isn't specified, jobs in the search results includes any + // employment type. + // + // If multiple values are specified, jobs in the search results include + // any of the specified employment types. + repeated EmploymentType employment_types = 10; + + // This filter specifies the locale of jobs to search against, + // for example, "en-US". + // + // If a value isn't specified, the search results can contain jobs in any + // locale. + // + // + // Language codes should be in BCP-47 format, such as "en-US" or "sr-Latn". + // For more information, see + // [Tags for Identifying Languages](https://tools.ietf.org/html/bcp47). + // + // At most 10 language code filters are allowed. + repeated string language_codes = 11; + + // Jobs published within a range specified by this filter are searched + // against. + TimestampRange publish_time_range = 12; + + // This filter specifies a list of job names to be excluded during search. + // + // At most 400 excluded job names are allowed. + repeated string excluded_jobs = 13; +} + +// Geographic region of the search. +message LocationFilter { + // Specify whether to include telecommute jobs. + enum TelecommutePreference { + // Default value if the telecommute preference isn't specified. + TELECOMMUTE_PREFERENCE_UNSPECIFIED = 0; + + // Exclude telecommute jobs. + TELECOMMUTE_EXCLUDED = 1; + + // Allow telecommute jobs. + TELECOMMUTE_ALLOWED = 2; + } + + // The address name, such as "Mountain View" or "Bay Area". + string address = 1; + + // CLDR region code of the country/region of the address. This is used + // to address ambiguity of the user-input location, for example, "Liverpool" + // against "Liverpool, NY, US" or "Liverpool, UK". + // + // Set this field to bias location resolution toward a specific country + // or territory. If this field is not set, application behavior is biased + // toward the United States by default. + // + // See + // https://www.unicode.org/cldr/charts/30/supplemental/territory_information.html + // for details. Example: "CH" for Switzerland. + string region_code = 2; + + // The latitude and longitude of the geographic center to search from. This + // field is ignored if `address` is provided. + google.type.LatLng lat_lng = 3; + + // The distance_in_miles is applied when the location being searched for is + // identified as a city or smaller. This field is ignored if the location + // being searched for is a state or larger. + double distance_in_miles = 4; + + // Allows the client to return jobs without a + // set location, specifically, telecommuting jobs (telecommuting is considered + // by the service as a special location. + // [Job.posting_region][google.cloud.talent.v4.Job.posting_region] indicates + // if a job permits telecommuting. If this field is set to + // [TelecommutePreference.TELECOMMUTE_ALLOWED][google.cloud.talent.v4.LocationFilter.TelecommutePreference.TELECOMMUTE_ALLOWED], + // telecommuting jobs are searched, and + // [address][google.cloud.talent.v4.LocationFilter.address] and + // [lat_lng][google.cloud.talent.v4.LocationFilter.lat_lng] are ignored. If + // not set or set to + // [TelecommutePreference.TELECOMMUTE_EXCLUDED][google.cloud.talent.v4.LocationFilter.TelecommutePreference.TELECOMMUTE_EXCLUDED], + // telecommute job are not searched. + // + // This filter can be used by itself to search exclusively for telecommuting + // jobs, or it can be combined with another location + // filter to search for a combination of job locations, + // such as "Mountain View" or "telecommuting" jobs. However, when used in + // combination with other location filters, telecommuting jobs can be + // treated as less relevant than other jobs in the search response. + // + // This field is only used for job search requests. + TelecommutePreference telecommute_preference = 5; +} + +// Filter on job compensation type and amount. +message CompensationFilter { + // Specify the type of filtering. + enum FilterType { + // Filter type unspecified. Position holder, INVALID, should never be used. + FILTER_TYPE_UNSPECIFIED = 0; + + // Filter by `base compensation entry's` unit. A job is a match if and + // only if the job contains a base CompensationEntry and the base + // CompensationEntry's unit matches provided + // [units][google.cloud.talent.v4.CompensationFilter.units]. Populate one or + // more [units][google.cloud.talent.v4.CompensationFilter.units]. + // + // See + // [CompensationInfo.CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + // for definition of base compensation entry. + UNIT_ONLY = 1; + + // Filter by `base compensation entry's` unit and amount / range. A job + // is a match if and only if the job contains a base CompensationEntry, and + // the base entry's unit matches provided + // [CompensationUnit][google.cloud.talent.v4.CompensationInfo.CompensationUnit] + // and amount or range overlaps with provided + // [CompensationRange][google.cloud.talent.v4.CompensationInfo.CompensationRange]. + // + // See + // [CompensationInfo.CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + // for definition of base compensation entry. + // + // Set exactly one [units][google.cloud.talent.v4.CompensationFilter.units] + // and populate [range][google.cloud.talent.v4.CompensationFilter.range]. + UNIT_AND_AMOUNT = 2; + + // Filter by annualized base compensation amount and `base compensation + // entry's` unit. Populate + // [range][google.cloud.talent.v4.CompensationFilter.range] and zero or more + // [units][google.cloud.talent.v4.CompensationFilter.units]. + ANNUALIZED_BASE_AMOUNT = 3; + + // Filter by annualized total compensation amount and `base compensation + // entry's` unit . Populate + // [range][google.cloud.talent.v4.CompensationFilter.range] and zero or more + // [units][google.cloud.talent.v4.CompensationFilter.units]. + ANNUALIZED_TOTAL_AMOUNT = 4; + } + + // Required. Type of filter. + FilterType type = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. Specify desired `base compensation entry's` + // [CompensationInfo.CompensationUnit][google.cloud.talent.v4.CompensationInfo.CompensationUnit]. + repeated CompensationInfo.CompensationUnit units = 2 + [(google.api.field_behavior) = REQUIRED]; + + // Compensation range. + CompensationInfo.CompensationRange range = 3; + + // If set to true, jobs with unspecified compensation range fields are + // included. + bool include_jobs_with_unspecified_compensation_range = 4; +} + +// Parameters needed for commute search. +message CommuteFilter { + // The traffic density to use when calculating commute time. + enum RoadTraffic { + // Road traffic situation isn't specified. + ROAD_TRAFFIC_UNSPECIFIED = 0; + + // Optimal commute time without considering any traffic impact. + TRAFFIC_FREE = 1; + + // Commute time calculation takes in account the peak traffic impact. + BUSY_HOUR = 2; + } + + // Required. The method of transportation to calculate the commute time for. + CommuteMethod commute_method = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. The latitude and longitude of the location to calculate the + // commute time from. + google.type.LatLng start_coordinates = 2 + [(google.api.field_behavior) = REQUIRED]; + + // Required. The maximum travel time in seconds. The maximum allowed value is + // `3600s` (one hour). Format is `123s`. + google.protobuf.Duration travel_duration = 3 + [(google.api.field_behavior) = REQUIRED]; + + // If `true`, jobs without street level addresses may also be returned. + // For city level addresses, the city center is used. For state and coarser + // level addresses, text matching is used. + // If this field is set to `false` or isn't specified, only jobs that include + // street level addresses will be returned by commute search. + bool allow_imprecise_addresses = 4; + + // Traffic factor to take into account while searching by commute. + oneof traffic_option { + // Specifies the traffic density to use when calculating commute time. + RoadTraffic road_traffic = 5; + + // The departure time used to calculate traffic impact, represented as + // [google.type.TimeOfDay][google.type.TimeOfDay] in local time zone. + // + // Currently traffic model is restricted to hour level resolution. + google.type.TimeOfDay departure_time = 6; + } +} diff --git a/google/cloud/talent_v4/proto/histogram.proto b/google/cloud/talent_v4/proto/histogram.proto new file mode 100644 index 00000000..f58ac1c0 --- /dev/null +++ b/google/cloud/talent_v4/proto/histogram.proto @@ -0,0 +1,56 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "HistogramProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// The histogram request. +message HistogramQuery { + // An expression specifies a histogram request against matching jobs for + // searches. + // + // See + // [SearchJobsRequest.histogram_queries][google.cloud.talent.v4.SearchJobsRequest.histogram_queries] + // for details about syntax. + string histogram_query = 1; +} + +// Histogram result that matches +// [HistogramQuery][google.cloud.talent.v4.HistogramQuery] specified in +// searches. +message HistogramQueryResult { + // Requested histogram expression. + string histogram_query = 1; + + // A map from the values of the facet associated with distinct values to the + // number of matching entries with corresponding value. + // + // The key format is: + // + // * (for string histogram) string values stored in the field. + // * (for named numeric bucket) name specified in `bucket()` function, like + // for `bucket(0, MAX, "non-negative")`, the key will be `non-negative`. + // * (for anonymous numeric bucket) range formatted as `-`, for + // example, `0-1000`, `MIN-0`, and `0-MAX`. + map histogram = 2; +} diff --git a/google/cloud/talent_v4/proto/job.proto b/google/cloud/talent_v4/proto/job.proto new file mode 100644 index 00000000..66242e10 --- /dev/null +++ b/google/cloud/talent_v4/proto/job.proto @@ -0,0 +1,370 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; +import "google/protobuf/timestamp.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "JobProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A Job resource represents a job posting (also referred to as a "job listing" +// or "job requisition"). A job belongs to a +// [Company][google.cloud.talent.v4.Company], which is the hiring entity +// responsible for the job. +message Job { + option (google.api.resource) = { + type: "jobs.googleapis.com/Job" + pattern: "projects/{project}/tenants/{tenant}/jobs/{job}" + }; + + // Application related details of a job posting. + message ApplicationInfo { + // Use this field to specify email address(es) to which resumes or + // applications can be sent. + // + // The maximum number of allowed characters for each entry is 255. + repeated string emails = 1; + + // Use this field to provide instructions, such as "Mail your application + // to ...", that a candidate can follow to apply for the job. + // + // This field accepts and sanitizes HTML input, and also accepts + // bold, italic, ordered list, and unordered list markup tags. + // + // The maximum number of allowed characters is 3,000. + string instruction = 2; + + // Use this URI field to direct an applicant to a website, for example to + // link to an online application form. + // + // The maximum number of allowed characters for each entry is 2,000. + repeated string uris = 3; + } + + // Derived details about the job posting. + message DerivedInfo { + // Structured locations of the job, resolved from + // [Job.addresses][google.cloud.talent.v4.Job.addresses]. + // + // [locations][google.cloud.talent.v4.Job.DerivedInfo.locations] are exactly + // matched to [Job.addresses][google.cloud.talent.v4.Job.addresses] in the + // same order. + repeated Location locations = 1; + + // Job categories derived from [Job.title][google.cloud.talent.v4.Job.title] + // and [Job.description][google.cloud.talent.v4.Job.description]. + repeated JobCategory job_categories = 3; + } + + // Options for job processing. + message ProcessingOptions { + // If set to `true`, the service does not attempt to resolve a + // more precise address for the job. + bool disable_street_address_resolution = 1; + + // Option for job HTML content sanitization. Applied fields are: + // + // * description + // * applicationInfo.instruction + // * incentives + // * qualifications + // * responsibilities + // + // HTML tags in these fields may be stripped if sanitiazation isn't + // disabled. + // + // Defaults to + // [HtmlSanitization.SIMPLE_FORMATTING_ONLY][google.cloud.talent.v4.HtmlSanitization.SIMPLE_FORMATTING_ONLY]. + HtmlSanitization html_sanitization = 2; + } + + // Required during job update. + // + // The resource name for the job. This is generated by the service when a + // job is created. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". For + // example, "projects/foo/tenants/bar/jobs/baz". + // + // Use of this field in job queries and API calls is preferred over the use of + // [requisition_id][google.cloud.talent.v4.Job.requisition_id] since this + // value is unique. + string name = 1; + + // Required. The resource name of the company listing the job. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}". For + // example, "projects/foo/tenants/bar/companies/baz". + string company = 2 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Company" } + ]; + + // Required. The requisition ID, also referred to as the posting ID, is + // assigned by the client to identify a job. This field is intended to be used + // by clients for client identification and tracking of postings. A job isn't + // allowed to be created if there is another job with the same + // [company][google.cloud.talent.v4.Job.name], + // [language_code][google.cloud.talent.v4.Job.language_code] and + // [requisition_id][google.cloud.talent.v4.Job.requisition_id]. + // + // The maximum number of allowed characters is 255. + string requisition_id = 3 [(google.api.field_behavior) = REQUIRED]; + + // Required. The title of the job, such as "Software Engineer" + // + // The maximum number of allowed characters is 500. + string title = 4 [(google.api.field_behavior) = REQUIRED]; + + // Required. The description of the job, which typically includes a + // multi-paragraph description of the company and related information. + // Separate fields are provided on the job object for + // [responsibilities][google.cloud.talent.v4.Job.responsibilities], + // [qualifications][google.cloud.talent.v4.Job.qualifications], and other job + // characteristics. Use of these separate job fields is recommended. + // + // This field accepts and sanitizes HTML input, and also accepts + // bold, italic, ordered list, and unordered list markup tags. + // + // The maximum number of allowed characters is 100,000. + string description = 5 [(google.api.field_behavior) = REQUIRED]; + + // Strongly recommended for the best service experience. + // + // Location(s) where the employer is looking to hire for this job posting. + // + // Specifying the full street address(es) of the hiring location enables + // better API results, especially job searches by commute time. + // + // At most 50 locations are allowed for best search performance. If a job has + // more locations, it is suggested to split it into multiple jobs with unique + // [requisition_id][google.cloud.talent.v4.Job.requisition_id]s (e.g. 'ReqA' + // becomes 'ReqA-1', 'ReqA-2', and so on.) as multiple jobs with the same + // [company][google.cloud.talent.v4.Job.company], + // [language_code][google.cloud.talent.v4.Job.language_code] and + // [requisition_id][google.cloud.talent.v4.Job.requisition_id] are not + // allowed. If the original + // [requisition_id][google.cloud.talent.v4.Job.requisition_id] must be + // preserved, a custom field should be used for storage. It is also suggested + // to group the locations that close to each other in the same job for better + // search experience. + // + // The maximum number of allowed characters is 500. + repeated string addresses = 6; + + // Job application information. + ApplicationInfo application_info = 7; + + // The benefits included with the job. + repeated JobBenefit job_benefits = 8; + + // Job compensation information (a.k.a. "pay rate") i.e., the compensation + // that will paid to the employee. + CompensationInfo compensation_info = 9; + + // A map of fields to hold both filterable and non-filterable custom job + // attributes that are not covered by the provided structured fields. + // + // The keys of the map are strings up to 64 bytes and must match the + // pattern: [a-zA-Z][a-zA-Z0-9_]*. For example, key0LikeThis or + // KEY_1_LIKE_THIS. + // + // At most 100 filterable and at most 100 unfilterable keys are supported. + // For filterable `string_values`, across all keys at most 200 values are + // allowed, with each string no more than 255 characters. For unfilterable + // `string_values`, the maximum total size of `string_values` across all keys + // is 50KB. + map custom_attributes = 10; + + // The desired education degrees for the job, such as Bachelors, Masters. + repeated DegreeType degree_types = 11; + + // The department or functional area within the company with the open + // position. + // + // The maximum number of allowed characters is 255. + string department = 12; + + // The employment type(s) of a job, for example, + // [full time][google.cloud.talent.v4.EmploymentType.FULL_TIME] or + // [part time][google.cloud.talent.v4.EmploymentType.PART_TIME]. + repeated EmploymentType employment_types = 13; + + // A description of bonus, commission, and other compensation + // incentives associated with the job not including salary or pay. + // + // The maximum number of allowed characters is 10,000. + string incentives = 14; + + // The language of the posting. This field is distinct from + // any requirements for fluency that are associated with the job. + // + // Language codes must be in BCP-47 format, such as "en-US" or "sr-Latn". + // For more information, see + // [Tags for Identifying Languages](https://tools.ietf.org/html/bcp47){: + // class="external" target="_blank" }. + // + // If this field is unspecified and + // [Job.description][google.cloud.talent.v4.Job.description] is present, + // detected language code based on + // [Job.description][google.cloud.talent.v4.Job.description] is assigned, + // otherwise defaults to 'en_US'. + string language_code = 15; + + // The experience level associated with the job, such as "Entry Level". + JobLevel job_level = 16; + + // A promotion value of the job, as determined by the client. + // The value determines the sort order of the jobs returned when searching for + // jobs using the featured jobs search call, with higher promotional values + // being returned first and ties being resolved by relevance sort. Only the + // jobs with a promotionValue >0 are returned in a FEATURED_JOB_SEARCH. + // + // Default value is 0, and negative values are treated as 0. + int32 promotion_value = 17; + + // A description of the qualifications required to perform the + // job. The use of this field is recommended + // as an alternative to using the more general + // [description][google.cloud.talent.v4.Job.description] field. + // + // This field accepts and sanitizes HTML input, and also accepts + // bold, italic, ordered list, and unordered list markup tags. + // + // The maximum number of allowed characters is 10,000. + string qualifications = 18; + + // A description of job responsibilities. The use of this field is + // recommended as an alternative to using the more general + // [description][google.cloud.talent.v4.Job.description] field. + // + // This field accepts and sanitizes HTML input, and also accepts + // bold, italic, ordered list, and unordered list markup tags. + // + // The maximum number of allowed characters is 10,000. + string responsibilities = 19; + + // The job [PostingRegion][google.cloud.talent.v4.PostingRegion] (for example, + // state, country) throughout which the job is available. If this field is + // set, a [LocationFilter][google.cloud.talent.v4.LocationFilter] in a search + // query within the job region finds this job posting if an exact location + // match isn't specified. If this field is set to + // [PostingRegion.NATION][google.cloud.talent.v4.PostingRegion.NATION] or + // [PostingRegion.ADMINISTRATIVE_AREA][google.cloud.talent.v4.PostingRegion.ADMINISTRATIVE_AREA], + // setting job [Job.addresses][google.cloud.talent.v4.Job.addresses] to the + // same location level as this field is strongly recommended. + PostingRegion posting_region = 20; + + // Deprecated. The job is only visible to the owner. + // + // The visibility of the job. + // + // Defaults to + // [Visibility.ACCOUNT_ONLY][google.cloud.talent.v4.Visibility.ACCOUNT_ONLY] + // if not specified. + Visibility visibility = 21 [deprecated = true]; + + // The start timestamp of the job in UTC time zone. Typically this field + // is used for contracting engagements. Invalid timestamps are ignored. + google.protobuf.Timestamp job_start_time = 22; + + // The end timestamp of the job. Typically this field is used for contracting + // engagements. Invalid timestamps are ignored. + google.protobuf.Timestamp job_end_time = 23; + + // The timestamp this job posting was most recently published. The default + // value is the time the request arrives at the server. Invalid timestamps are + // ignored. + google.protobuf.Timestamp posting_publish_time = 24; + + // Strongly recommended for the best service experience. + // + // The expiration timestamp of the job. After this timestamp, the + // job is marked as expired, and it no longer appears in search results. The + // expired job can't be listed by the + // [ListJobs][google.cloud.talent.v4.JobService.ListJobs] API, but it can be + // retrieved with the [GetJob][google.cloud.talent.v4.JobService.GetJob] API + // or updated with the + // [UpdateJob][google.cloud.talent.v4.JobService.UpdateJob] API or deleted + // with the [DeleteJob][google.cloud.talent.v4.JobService.DeleteJob] API. An + // expired job can be updated and opened again by using a future expiration + // timestamp. Updating an expired job fails if there is another existing open + // job with same [company][google.cloud.talent.v4.Job.company], + // [language_code][google.cloud.talent.v4.Job.language_code] and + // [requisition_id][google.cloud.talent.v4.Job.requisition_id]. + // + // The expired jobs are retained in our system for 90 days. However, the + // overall expired job count cannot exceed 3 times the maximum number of + // open jobs over previous 7 days. If this threshold is exceeded, + // expired jobs are cleaned out in order of earliest expire time. + // Expired jobs are no longer accessible after they are cleaned + // out. + // + // Invalid timestamps are ignored, and treated as expire time not provided. + // + // If the timestamp is before the instant request is made, the job + // is treated as expired immediately on creation. This kind of job can + // not be updated. And when creating a job with past timestamp, the + // [posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + // must be set before + // [posting_expire_time][google.cloud.talent.v4.Job.posting_expire_time]. The + // purpose of this feature is to allow other objects, such as [Application][], + // to refer a job that didn't exist in the system prior to becoming expired. + // If you want to modify a job that was expired on creation, delete it and + // create a new one. + // + // If this value isn't provided at the time of job creation or is invalid, + // the job posting expires after 30 days from the job's creation time. For + // example, if the job was created on 2017/01/01 13:00AM UTC with an + // unspecified expiration date, the job expires after 2017/01/31 13:00AM UTC. + // + // If this value isn't provided on job update, it depends on the field masks + // set by + // [UpdateJobRequest.update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask]. + // If the field masks include + // [job_end_time][google.cloud.talent.v4.Job.job_end_time], or the masks are + // empty meaning that every field is updated, the job posting expires after 30 + // days from the job's last update time. Otherwise the expiration date isn't + // updated. + google.protobuf.Timestamp posting_expire_time = 25; + + // Output only. The timestamp when this job posting was created. + google.protobuf.Timestamp posting_create_time = 26 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. The timestamp when this job posting was last updated. + google.protobuf.Timestamp posting_update_time = 27 + [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Display name of the company listing the job. + string company_display_name = 28 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Output only. Derived details about the job posting. + DerivedInfo derived_info = 29 [(google.api.field_behavior) = OUTPUT_ONLY]; + + // Options for job processing. + ProcessingOptions processing_options = 30; +} diff --git a/google/cloud/talent_v4/proto/job_service.proto b/google/cloud/talent_v4/proto/job_service.proto new file mode 100644 index 00000000..37642927 --- /dev/null +++ b/google/cloud/talent_v4/proto/job_service.proto @@ -0,0 +1,865 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; +import "google/cloud/talent/v4/filters.proto"; +import "google/cloud/talent/v4/histogram.proto"; +import "google/cloud/talent/v4/job.proto"; +import "google/longrunning/operations.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; +import "google/rpc/status.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "JobServiceProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A service handles job management, including job CRUD, enumeration and search. +service JobService { + option (google.api.default_host) = "jobs.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/jobs"; + + // Creates a new job. + // + // Typically, the job becomes searchable within 10 seconds, but it may take + // up to 5 minutes. + rpc CreateJob(CreateJobRequest) returns (Job) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs" + body: "job" + }; + option (google.api.method_signature) = "parent,job"; + } + + // Begins executing a batch create jobs operation. + rpc BatchCreateJobs(BatchCreateJobsRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs:batchCreate" + body: "*" + }; + option (google.api.method_signature) = "parent,jobs"; + option (google.longrunning.operation_info) = { + response_type: "BatchCreateJobsResponse" + metadata_type: "BatchOperationMetadata" + }; + } + + // Retrieves the specified job, whose status is OPEN or recently EXPIRED + // within the last 90 days. + rpc GetJob(GetJobRequest) returns (Job) { + option (google.api.http) = { + get: "/v4/{name=projects/*/tenants/*/jobs/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Updates specified job. + // + // Typically, updated contents become visible in search results within 10 + // seconds, but it may take up to 5 minutes. + rpc UpdateJob(UpdateJobRequest) returns (Job) { + option (google.api.http) = { + patch: "/v4/{job.name=projects/*/tenants/*/jobs/*}" + body: "job" + }; + option (google.api.method_signature) = "job,update_mask"; + } + + // Begins executing a batch update jobs operation. + rpc BatchUpdateJobs(BatchUpdateJobsRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs:batchUpdate" + body: "*" + }; + option (google.api.method_signature) = "parent,jobs"; + option (google.longrunning.operation_info) = { + response_type: "BatchUpdateJobsResponse" + metadata_type: "BatchOperationMetadata" + }; + } + + // Deletes the specified job. + // + // Typically, the job becomes unsearchable within 10 seconds, but it may take + // up to 5 minutes. + rpc DeleteJob(DeleteJobRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v4/{name=projects/*/tenants/*/jobs/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Begins executing a batch delete jobs operation. + rpc BatchDeleteJobs(BatchDeleteJobsRequest) returns (google.longrunning.Operation) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs:batchDelete" + body: "*" + }; + option (google.api.method_signature) = "parent,names"; + option (google.longrunning.operation_info) = { + response_type: "BatchDeleteJobsResponse" + metadata_type: "BatchOperationMetadata" + }; + } + + // Lists jobs by filter. + rpc ListJobs(ListJobsRequest) returns (ListJobsResponse) { + option (google.api.http) = { + get: "/v4/{parent=projects/*/tenants/*}/jobs" + }; + option (google.api.method_signature) = "parent,filter"; + } + + // Searches for jobs using the provided [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + // + // This call constrains the [visibility][google.cloud.talent.v4.Job.visibility] of jobs + // present in the database, and only returns jobs that the caller has + // permission to search against. + rpc SearchJobs(SearchJobsRequest) returns (SearchJobsResponse) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs:search" + body: "*" + }; + } + + // Searches for jobs using the provided [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + // + // This API call is intended for the use case of targeting passive job + // seekers (for example, job seekers who have signed up to receive email + // alerts about potential job opportunities), it has different algorithmic + // adjustments that are designed to specifically target passive job seekers. + // + // This call constrains the [visibility][google.cloud.talent.v4.Job.visibility] of jobs + // present in the database, and only returns jobs the caller has + // permission to search against. + rpc SearchJobsForAlert(SearchJobsRequest) returns (SearchJobsResponse) { + option (google.api.http) = { + post: "/v4/{parent=projects/*/tenants/*}/jobs:searchForAlert" + body: "*" + }; + } +} + +// Create job request. +message CreateJobRequest { + // Required. The resource name of the tenant under which the job is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "jobs.googleapis.com/Job" + } + ]; + + // Required. The Job to be created. + Job job = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Get job request. +message GetJobRequest { + // Required. The resource name of the job to retrieve. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". For + // example, "projects/foo/tenants/bar/jobs/baz". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "jobs.googleapis.com/Job" + } + ]; +} + +// Update job request. +message UpdateJobRequest { + // Required. The Job to be updated. + Job job = 1 [(google.api.field_behavior) = REQUIRED]; + + // Strongly recommended for the best service experience. + // + // If [update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask] is provided, only the specified fields in + // [job][google.cloud.talent.v4.UpdateJobRequest.job] are updated. Otherwise all the fields are updated. + // + // A field mask to restrict the fields that are updated. Only + // top level fields of [Job][google.cloud.talent.v4.Job] are supported. + google.protobuf.FieldMask update_mask = 2; +} + +// Delete job request. +message DeleteJobRequest { + // Required. The resource name of the job to be deleted. + // + // The format is + // "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". For + // example, "projects/foo/tenants/bar/jobs/baz". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "jobs.googleapis.com/Job" + } + ]; +} + +// List jobs request. +message ListJobsRequest { + // Required. The resource name of the tenant under which the job is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "jobs.googleapis.com/Job" + } + ]; + + // Required. The filter string specifies the jobs to be enumerated. + // + // Supported operator: =, AND + // + // The fields eligible for filtering are: + // + // * `companyName` (Required) + // * `requisitionId` + // * `status` Available values: OPEN, EXPIRED, ALL. Defaults to + // OPEN if no value is specified. + // + // Sample Query: + // + // * companyName = "projects/foo/tenants/bar/companies/baz" + // * companyName = "projects/foo/tenants/bar/companies/baz" AND + // requisitionId = "req-1" + // * companyName = "projects/foo/tenants/bar/companies/baz" AND + // status = "EXPIRED" + string filter = 2 [(google.api.field_behavior) = REQUIRED]; + + // The starting point of a query result. + string page_token = 3; + + // The maximum number of jobs to be returned per page of results. + // + // If [job_view][google.cloud.talent.v4.ListJobsRequest.job_view] is set to [JobView.JOB_VIEW_ID_ONLY][google.cloud.talent.v4.JobView.JOB_VIEW_ID_ONLY], the maximum allowed + // page size is 1000. Otherwise, the maximum allowed page size is 100. + // + // Default is 100 if empty or a number < 1 is specified. + int32 page_size = 4; + + // The desired job attributes returned for jobs in the + // search response. Defaults to [JobView.JOB_VIEW_FULL][google.cloud.talent.v4.JobView.JOB_VIEW_FULL] if no value is + // specified. + JobView job_view = 5; +} + +// An enum that specifies the job attributes that are returned in the +// [MatchingJob.job][google.cloud.talent.v4.SearchJobsResponse.MatchingJob.job] or +// [ListJobsResponse.jobs][google.cloud.talent.v4.ListJobsResponse.jobs] fields. +enum JobView { + // Default value. + JOB_VIEW_UNSPECIFIED = 0; + + // A ID only view of job, with following attributes: + // [Job.name][google.cloud.talent.v4.Job.name], [Job.requisition_id][google.cloud.talent.v4.Job.requisition_id], [Job.language_code][google.cloud.talent.v4.Job.language_code]. + JOB_VIEW_ID_ONLY = 1; + + // A minimal view of the job, with the following attributes: + // [Job.name][google.cloud.talent.v4.Job.name], [Job.requisition_id][google.cloud.talent.v4.Job.requisition_id], [Job.title][google.cloud.talent.v4.Job.title], + // [Job.company][google.cloud.talent.v4.Job.company], [Job.DerivedInfo.locations][google.cloud.talent.v4.Job.DerivedInfo.locations], [Job.language_code][google.cloud.talent.v4.Job.language_code]. + JOB_VIEW_MINIMAL = 2; + + // A small view of the job, with the following attributes in the search + // results: [Job.name][google.cloud.talent.v4.Job.name], [Job.requisition_id][google.cloud.talent.v4.Job.requisition_id], [Job.title][google.cloud.talent.v4.Job.title], + // [Job.company][google.cloud.talent.v4.Job.company], [Job.DerivedInfo.locations][google.cloud.talent.v4.Job.DerivedInfo.locations], [Job.visibility][google.cloud.talent.v4.Job.visibility], + // [Job.language_code][google.cloud.talent.v4.Job.language_code], [Job.description][google.cloud.talent.v4.Job.description]. + JOB_VIEW_SMALL = 3; + + // All available attributes are included in the search results. + JOB_VIEW_FULL = 4; +} + +// List jobs response. +message ListJobsResponse { + // The Jobs for a given company. + // + // The maximum number of items returned is based on the limit field + // provided in the request. + repeated Job jobs = 1; + + // A token to retrieve the next page of results. + string next_page_token = 2; + + // Additional information for the API invocation, such as the request + // tracking id. + ResponseMetadata metadata = 3; +} + +// The Request body of the `SearchJobs` call. +message SearchJobsRequest { + // Custom ranking information for [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + message CustomRankingInfo { + // The importance level for [CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression]. + enum ImportanceLevel { + // Default value if the importance level isn't specified. + IMPORTANCE_LEVEL_UNSPECIFIED = 0; + + // The given ranking expression is of None importance, existing relevance + // score (determined by API algorithm) dominates job's final ranking + // position. + NONE = 1; + + // The given ranking expression is of Low importance in terms of job's + // final ranking position compared to existing relevance + // score (determined by API algorithm). + LOW = 2; + + // The given ranking expression is of Mild importance in terms of job's + // final ranking position compared to existing relevance + // score (determined by API algorithm). + MILD = 3; + + // The given ranking expression is of Medium importance in terms of job's + // final ranking position compared to existing relevance + // score (determined by API algorithm). + MEDIUM = 4; + + // The given ranking expression is of High importance in terms of job's + // final ranking position compared to existing relevance + // score (determined by API algorithm). + HIGH = 5; + + // The given ranking expression is of Extreme importance, and dominates + // job's final ranking position with existing relevance + // score (determined by API algorithm) ignored. + EXTREME = 6; + } + + // Required. Controls over how important the score of + // [CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression] gets applied to job's final + // ranking position. + // + // An error is thrown if not specified. + ImportanceLevel importance_level = 1 [(google.api.field_behavior) = REQUIRED]; + + // Required. Controls over how job documents get ranked on top of existing relevance + // score (determined by API algorithm). A combination of the ranking + // expression and relevance score is used to determine job's final ranking + // position. + // + // The syntax for this expression is a subset of Google SQL syntax. + // + // Supported operators are: +, -, *, /, where the left and right side of + // the operator is either a numeric [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes] key, + // integer/double value or an expression that can be evaluated to a number. + // + // Parenthesis are supported to adjust calculation precedence. The + // expression must be < 100 characters in length. + // + // The expression is considered invalid for a job if the expression + // references custom attributes that are not populated on the job or if the + // expression results in a divide by zero. If an expression is invalid for a + // job, that job is demoted to the end of the results. + // + // Sample ranking expression + // (year + 25) * 0.25 - (freshness / 0.5) + string ranking_expression = 2 [(google.api.field_behavior) = REQUIRED]; + } + + // A string-represented enumeration of the job search mode. The service + // operate differently for different modes of service. + enum SearchMode { + // The mode of the search method isn't specified. The default search + // behavior is identical to JOB_SEARCH search behavior. + SEARCH_MODE_UNSPECIFIED = 0; + + // The job search matches against all jobs, and featured jobs + // (jobs with promotionValue > 0) are not specially handled. + JOB_SEARCH = 1; + + // The job search matches only against featured jobs (jobs with a + // promotionValue > 0). This method doesn't return any jobs having a + // promotionValue <= 0. The search results order is determined by the + // promotionValue (jobs with a higher promotionValue are returned higher up + // in the search results), with relevance being used as a tiebreaker. + FEATURED_JOB_SEARCH = 2; + } + + // Controls whether highly similar jobs are returned next to each other in + // the search results. Jobs are identified as highly similar based on + // their titles, job categories, and locations. Highly similar results are + // clustered so that only one representative job of the cluster is + // displayed to the job seeker higher up in the results, with the other jobs + // being displayed lower down in the results. + enum DiversificationLevel { + // The diversification level isn't specified. + DIVERSIFICATION_LEVEL_UNSPECIFIED = 0; + + // Disables diversification. Jobs that would normally be pushed to the last + // page would not have their positions altered. This may result in highly + // similar jobs appearing in sequence in the search results. + DISABLED = 1; + + // Default diversifying behavior. The result list is ordered so that + // highly similar results are pushed to the end of the last page of search + // results. If you are using pageToken to page through the result set, + // latency might be lower but we can't guarantee that all results are + // returned. If you are using page offset, latency might be higher but all + // results are returned. + SIMPLE = 2; + } + + // Required. The resource name of the tenant to search within. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "jobs.googleapis.com/Job" + } + ]; + + // Mode of a search. + // + // Defaults to [SearchMode.JOB_SEARCH][google.cloud.talent.v4.SearchJobsRequest.SearchMode.JOB_SEARCH]. + SearchMode search_mode = 2; + + // Required. The meta information collected about the job searcher, used to improve the + // search quality of the service. The identifiers (such as `user_id`) are + // provided by users, and must be unique and consistent. + RequestMetadata request_metadata = 3 [(google.api.field_behavior) = REQUIRED]; + + // Query used to search against jobs, such as keyword, location filters, etc. + JobQuery job_query = 4; + + // Controls whether to broaden the search when it produces sparse results. + // Broadened queries append results to the end of the matching results + // list. + // + // Defaults to false. + bool enable_broadening = 5; + + // An expression specifies a histogram request against matching jobs. + // + // Expression syntax is an aggregation function call with histogram facets and + // other options. + // + // Available aggregation function calls are: + // * `count(string_histogram_facet)`: Count the number of matching entities, + // for each distinct attribute value. + // * `count(numeric_histogram_facet, list of buckets)`: Count the number of + // matching entities within each bucket. + // + // Data types: + // + // * Histogram facet: facet names with format [a-zA-Z][a-zA-Z0-9_]+. + // * String: string like "any string with backslash escape for quote(\")." + // * Number: whole number and floating point number like 10, -1 and -0.01. + // * List: list of elements with comma(,) separator surrounded by square + // brackets, for example, [1, 2, 3] and ["one", "two", "three"]. + // + // Built-in constants: + // + // * MIN (minimum number similar to java Double.MIN_VALUE) + // * MAX (maximum number similar to java Double.MAX_VALUE) + // + // Built-in functions: + // + // * bucket(start, end[, label]): bucket built-in function creates a bucket + // with range of [start, end). Note that the end is exclusive, for example, + // bucket(1, MAX, "positive number") or bucket(1, 10). + // + // Job histogram facets: + // + // * company_display_name: histogram by [Job.company_display_name][google.cloud.talent.v4.Job.company_display_name]. + // * employment_type: histogram by [Job.employment_types][google.cloud.talent.v4.Job.employment_types], for example, + // "FULL_TIME", "PART_TIME". + // * company_size: histogram by [CompanySize][google.cloud.talent.v4.CompanySize], for example, "SMALL", + // "MEDIUM", "BIG". + // * publish_time_in_month: histogram by the [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + // in months. + // Must specify list of numeric buckets in spec. + // * publish_time_in_year: histogram by the [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + // in years. + // Must specify list of numeric buckets in spec. + // * degree_types: histogram by the [Job.degree_types][google.cloud.talent.v4.Job.degree_types], for example, + // "Bachelors", "Masters". + // * job_level: histogram by the [Job.job_level][google.cloud.talent.v4.Job.job_level], for example, "Entry + // Level". + // * country: histogram by the country code of jobs, for example, "US", "FR". + // * admin1: histogram by the admin1 code of jobs, which is a global + // placeholder referring to the state, province, or the particular term a + // country uses to define the geographic structure below the country level, + // for example, "CA", "IL". + // * city: histogram by a combination of the "city name, admin1 code". For + // example, "Mountain View, CA", "New York, NY". + // * admin1_country: histogram by a combination of the "admin1 code, country", + // for example, "CA, US", "IL, US". + // * city_coordinate: histogram by the city center's GPS coordinates (latitude + // and longitude), for example, 37.4038522,-122.0987765. Since the + // coordinates of a city center can change, customers may need to refresh + // them periodically. + // * locale: histogram by the [Job.language_code][google.cloud.talent.v4.Job.language_code], for example, "en-US", + // "fr-FR". + // * language: histogram by the language subtag of the [Job.language_code][google.cloud.talent.v4.Job.language_code], + // for example, "en", "fr". + // * category: histogram by the [JobCategory][google.cloud.talent.v4.JobCategory], for example, + // "COMPUTER_AND_IT", "HEALTHCARE". + // * base_compensation_unit: histogram by the + // [CompensationInfo.CompensationUnit][google.cloud.talent.v4.CompensationInfo.CompensationUnit] of base + // salary, for example, "WEEKLY", "MONTHLY". + // * base_compensation: histogram by the base salary. Must specify list of + // numeric buckets to group results by. + // * annualized_base_compensation: histogram by the base annualized salary. + // Must specify list of numeric buckets to group results by. + // * annualized_total_compensation: histogram by the total annualized salary. + // Must specify list of numeric buckets to group results by. + // * string_custom_attribute: histogram by string [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes]. + // Values can be accessed via square bracket notations like + // string_custom_attribute["key1"]. + // * numeric_custom_attribute: histogram by numeric [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes]. + // Values can be accessed via square bracket notations like + // numeric_custom_attribute["key1"]. Must specify list of numeric buckets to + // group results by. + // + // Example expressions: + // + // * `count(admin1)` + // * `count(base_compensation, [bucket(1000, 10000), bucket(10000, 100000), + // bucket(100000, MAX)])` + // * `count(string_custom_attribute["some-string-custom-attribute"])` + // * `count(numeric_custom_attribute["some-numeric-custom-attribute"], + // [bucket(MIN, 0, "negative"), bucket(0, MAX, "non-negative"])` + repeated HistogramQuery histogram_queries = 7; + + // The desired job attributes returned for jobs in the search response. + // Defaults to [JobView.JOB_VIEW_SMALL][google.cloud.talent.v4.JobView.JOB_VIEW_SMALL] if no value is specified. + JobView job_view = 8; + + // An integer that specifies the current offset (that is, starting result + // location, amongst the jobs deemed by the API as relevant) in search + // results. This field is only considered if [page_token][google.cloud.talent.v4.SearchJobsRequest.page_token] is unset. + // + // The maximum allowed value is 5000. Otherwise an error is thrown. + // + // For example, 0 means to return results starting from the first matching + // job, and 10 means to return from the 11th job. This can be used for + // pagination, (for example, pageSize = 10 and offset = 10 means to return + // from the second page). + int32 offset = 9; + + // A limit on the number of jobs returned in the search results. + // Increasing this value above the default value of 10 can increase search + // response time. The value can be between 1 and 100. + int32 max_page_size = 10; + + // The token specifying the current offset within + // search results. See [SearchJobsResponse.next_page_token][google.cloud.talent.v4.SearchJobsResponse.next_page_token] for + // an explanation of how to obtain the next set of query results. + string page_token = 11; + + // The criteria determining how search results are sorted. Default is + // `"relevance desc"`. + // + // Supported options are: + // + // * `"relevance desc"`: By relevance descending, as determined by the API + // algorithms. Relevance thresholding of query results is only available + // with this ordering. + // * `"posting_publish_time desc"`: By [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + // descending. + // * `"posting_update_time desc"`: By [Job.posting_update_time][google.cloud.talent.v4.Job.posting_update_time] + // descending. + // * `"title"`: By [Job.title][google.cloud.talent.v4.Job.title] ascending. + // * `"title desc"`: By [Job.title][google.cloud.talent.v4.Job.title] descending. + // * `"annualized_base_compensation"`: By job's + // [CompensationInfo.annualized_base_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_base_compensation_range] ascending. Jobs + // whose annualized base compensation is unspecified are put at the end of + // search results. + // * `"annualized_base_compensation desc"`: By job's + // [CompensationInfo.annualized_base_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_base_compensation_range] descending. Jobs + // whose annualized base compensation is unspecified are put at the end of + // search results. + // * `"annualized_total_compensation"`: By job's + // [CompensationInfo.annualized_total_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_total_compensation_range] ascending. Jobs + // whose annualized base compensation is unspecified are put at the end of + // search results. + // * `"annualized_total_compensation desc"`: By job's + // [CompensationInfo.annualized_total_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_total_compensation_range] descending. Jobs + // whose annualized base compensation is unspecified are put at the end of + // search results. + // * `"custom_ranking desc"`: By the relevance score adjusted to the + // [SearchJobsRequest.CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression] with weight + // factor assigned by + // [SearchJobsRequest.CustomRankingInfo.importance_level][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.importance_level] in descending + // order. + // * Location sorting: Use the special syntax to order jobs by distance:
+ // `"distance_from('Hawaii')"`: Order by distance from Hawaii.
+ // `"distance_from(19.89, 155.5)"`: Order by distance from a coordinate.
+ // `"distance_from('Hawaii'), distance_from('Puerto Rico')"`: Order by + // multiple locations. See details below.
+ // `"distance_from('Hawaii'), distance_from(19.89, 155.5)"`: Order by + // multiple locations. See details below.
+ // The string can have a maximum of 256 characters. When multiple distance + // centers are provided, a job that is close to any of the distance centers + // would have a high rank. When a job has multiple locations, the job + // location closest to one of the distance centers will be used. Jobs that + // don't have locations will be ranked at the bottom. Distance is calculated + // with a precision of 11.3 meters (37.4 feet). Diversification strategy is + // still applied unless explicitly disabled in + // [diversification_level][google.cloud.talent.v4.SearchJobsRequest.diversification_level]. + string order_by = 12; + + // Controls whether highly similar jobs are returned next to each other in + // the search results. Jobs are identified as highly similar based on + // their titles, job categories, and locations. Highly similar results are + // clustered so that only one representative job of the cluster is + // displayed to the job seeker higher up in the results, with the other jobs + // being displayed lower down in the results. + // + // Defaults to [DiversificationLevel.SIMPLE][google.cloud.talent.v4.SearchJobsRequest.DiversificationLevel.SIMPLE] if no value + // is specified. + DiversificationLevel diversification_level = 13; + + // Controls over how job documents get ranked on top of existing relevance + // score (determined by API algorithm). + CustomRankingInfo custom_ranking_info = 14; + + // Controls whether to disable exact keyword match on [Job.title][google.cloud.talent.v4.Job.title], + // [Job.description][google.cloud.talent.v4.Job.description], [Job.company_display_name][google.cloud.talent.v4.Job.company_display_name], [Job.addresses][google.cloud.talent.v4.Job.addresses], + // [Job.qualifications][google.cloud.talent.v4.Job.qualifications]. When disable keyword match is turned off, a + // keyword match returns jobs that do not match given category filters when + // there are matching keywords. For example, for the query "program manager," + // a result is returned even if the job posting has the title "software + // developer," which doesn't fall into "program manager" ontology, but does + // have "program manager" appearing in its description. + // + // For queries like "cloud" that don't contain title or + // location specific ontology, jobs with "cloud" keyword matches are returned + // regardless of this flag's value. + // + // Use [Company.keyword_searchable_job_custom_attributes][google.cloud.talent.v4.Company.keyword_searchable_job_custom_attributes] if + // company-specific globally matched custom field/attribute string values are + // needed. Enabling keyword match improves recall of subsequent search + // requests. + // + // Defaults to false. + bool disable_keyword_match = 16; +} + +// Response for SearchJob method. +message SearchJobsResponse { + // Job entry with metadata inside [SearchJobsResponse][google.cloud.talent.v4.SearchJobsResponse]. + message MatchingJob { + // Job resource that matches the specified [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + Job job = 1; + + // A summary of the job with core information that's displayed on the search + // results listing page. + string job_summary = 2; + + // Contains snippets of text from the [Job.title][google.cloud.talent.v4.Job.title] field most + // closely matching a search query's keywords, if available. The matching + // query keywords are enclosed in HTML bold tags. + string job_title_snippet = 3; + + // Contains snippets of text from the [Job.description][google.cloud.talent.v4.Job.description] and similar + // fields that most closely match a search query's keywords, if available. + // All HTML tags in the original fields are stripped when returned in this + // field, and matching query keywords are enclosed in HTML bold tags. + string search_text_snippet = 4; + + // Commute information which is generated based on specified + // [CommuteFilter][google.cloud.talent.v4.CommuteFilter]. + CommuteInfo commute_info = 5; + } + + // Commute details related to this job. + message CommuteInfo { + // Location used as the destination in the commute calculation. + Location job_location = 1; + + // The number of seconds required to travel to the job location from the + // query location. A duration of 0 seconds indicates that the job isn't + // reachable within the requested duration, but was returned as part of an + // expanded query. + google.protobuf.Duration travel_duration = 2; + } + + // The Job entities that match the specified [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + repeated MatchingJob matching_jobs = 1; + + // The histogram results that match with specified + // [SearchJobsRequest.histogram_queries][google.cloud.talent.v4.SearchJobsRequest.histogram_queries]. + repeated HistogramQueryResult histogram_query_results = 2; + + // The token that specifies the starting position of the next page of results. + // This field is empty if there are no more results. + string next_page_token = 3; + + // The location filters that the service applied to the specified query. If + // any filters are lat-lng based, the [Location.location_type][google.cloud.talent.v4.Location.location_type] is + // [Location.LocationType.LOCATION_TYPE_UNSPECIFIED][google.cloud.talent.v4.Location.LocationType.LOCATION_TYPE_UNSPECIFIED]. + repeated Location location_filters = 4; + + // Number of jobs that match the specified query. + // + // Note: This size is precise only if the total is less than 100,000. + int32 total_size = 6; + + // Additional information for the API invocation, such as the request + // tracking id. + ResponseMetadata metadata = 7; + + // If query broadening is enabled, we may append additional results from the + // broadened query. This number indicates how many of the jobs returned in the + // jobs field are from the broadened query. These results are always at the + // end of the jobs list. In particular, a value of 0, or if the field isn't + // set, all the jobs in the jobs list are from the original + // (without broadening) query. If this field is non-zero, subsequent requests + // with offset after this result set should contain all broadened results. + int32 broadened_query_jobs_count = 8; + + // The spell checking result, and correction. + SpellingCorrection spell_correction = 9; +} + +// Request to create a batch of jobs. +message BatchCreateJobsRequest { + // Required. The resource name of the tenant under which the job is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "jobs.googleapis.com/Job" + } + ]; + + // Required. The jobs to be created. + // A maximum of 200 jobs can be created in a batch. + repeated Job jobs = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Request to update a batch of jobs. +message BatchUpdateJobsRequest { + // Required. The resource name of the tenant under which the job is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + child_type: "jobs.googleapis.com/Job" + } + ]; + + // Required. The jobs to be updated. + // A maximum of 200 jobs can be updated in a batch. + repeated Job jobs = 2 [(google.api.field_behavior) = REQUIRED]; + + // Strongly recommended for the best service experience. Be aware that it will + // also increase latency when checking the status of a batch operation. + // + // If [update_mask][google.cloud.talent.v4.BatchUpdateJobsRequest.update_mask] is provided, only the specified fields in + // [Job][google.cloud.talent.v4.Job] are updated. Otherwise all the fields are updated. + // + // A field mask to restrict the fields that are updated. Only + // top level fields of [Job][google.cloud.talent.v4.Job] are supported. + // + // If [update_mask][google.cloud.talent.v4.BatchUpdateJobsRequest.update_mask] is provided, The [Job][google.cloud.talent.v4.Job] inside + // [JobResult][JobOperationResult.JobResult] + // will only contains fields that is updated, plus the Id of the Job. + // Otherwise, [Job][google.cloud.talent.v4.Job] will include all fields, which can yield a very + // large response. + google.protobuf.FieldMask update_mask = 3; +} + +// Request to delete a batch of jobs. +message BatchDeleteJobsRequest { + // Required. The resource name of the tenant under which the job is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}". For example, + // "projects/foo/tenants/bar". + // + // The parent of all of the jobs specified in `names` must match this field. + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "jobs.googleapis.com/Tenant" + } + ]; + + // The names of the jobs to delete. + // + // The format is "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + // For example, "projects/foo/tenants/bar/jobs/baz". + // + // A maximum of 200 jobs can be deleted in a batch. + repeated string names = 2 [(google.api.resource_reference) = { + type: "jobs.googleapis.com/Job" + }]; +} + +// Mutation result of a job from a batch operation. +message JobResult { + // Here [Job][google.cloud.talent.v4.Job] only contains basic information including [name][google.cloud.talent.v4.Job.name], + // [company][google.cloud.talent.v4.Job.company], [language_code][google.cloud.talent.v4.Job.language_code] + // and [requisition_id][google.cloud.talent.v4.Job.requisition_id], use getJob method to retrieve + // detailed information of the created/updated job. + Job job = 1; + + // The status of the job processed. This field is populated if the + // processing of the [job][google.cloud.talent.v4.JobResult.job] fails. + google.rpc.Status status = 2; +} + +// The result of [JobService.BatchCreateJobs][google.cloud.talent.v4.JobService.BatchCreateJobs]. It's used to +// replace [google.longrunning.Operation.response][google.longrunning.Operation.response] in case of success. +message BatchCreateJobsResponse { + // List of job mutation results from a batch create operation. It can change + // until operation status is FINISHED, FAILED or CANCELLED. + repeated JobResult job_results = 1; +} + +// The result of [JobService.BatchUpdateJobs][google.cloud.talent.v4.JobService.BatchUpdateJobs]. It's used to +// replace [google.longrunning.Operation.response][google.longrunning.Operation.response] in case of success. +message BatchUpdateJobsResponse { + // List of job mutation results from a batch update operation. It can change + // until operation status is FINISHED, FAILED or CANCELLED. + repeated JobResult job_results = 1; +} + +// The result of [JobService.BatchDeleteJobs][google.cloud.talent.v4.JobService.BatchDeleteJobs]. It's used to +// replace [google.longrunning.Operation.response][google.longrunning.Operation.response] in case of success. +message BatchDeleteJobsResponse { + // List of job mutation results from a batch delete operation. It can change + // until operation status is FINISHED, FAILED or CANCELLED. + repeated JobResult job_results = 1; +} diff --git a/google/cloud/talent_v4/proto/tenant.proto b/google/cloud/talent_v4/proto/tenant.proto new file mode 100644 index 00000000..a21fc06a --- /dev/null +++ b/google/cloud/talent_v4/proto/tenant.proto @@ -0,0 +1,53 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "TenantProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A Tenant resource represents a tenant in the service. A tenant is a group or +// entity that shares common access with specific privileges for resources like +// jobs. Customer may create multiple tenants to provide data isolation for +// different groups. +message Tenant { + option (google.api.resource) = { + type: "jobs.googleapis.com/Tenant" + pattern: "projects/{project}/tenants/{tenant}" + }; + + // Required during tenant update. + // + // The resource name for a tenant. This is generated by the service when a + // tenant is created. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string name = 1; + + // Required. Client side tenant identifier, used to uniquely identify the + // tenant. + // + // The maximum number of allowed characters is 255. + string external_id = 2 [(google.api.field_behavior) = REQUIRED]; +} diff --git a/google/cloud/talent_v4/proto/tenant_service.proto b/google/cloud/talent_v4/proto/tenant_service.proto new file mode 100644 index 00000000..8941ba7f --- /dev/null +++ b/google/cloud/talent_v4/proto/tenant_service.proto @@ -0,0 +1,175 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto3"; + +package google.cloud.talent.v4; + +import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; +import "google/api/resource.proto"; +import "google/cloud/talent/v4/common.proto"; +import "google/cloud/talent/v4/tenant.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/field_mask.proto"; + +option go_package = "google.golang.org/genproto/googleapis/cloud/talent/v4;talent"; +option java_multiple_files = true; +option java_outer_classname = "TenantServiceProto"; +option java_package = "com.google.cloud.talent.v4"; +option objc_class_prefix = "CTS"; + +// A service that handles tenant management, including CRUD and enumeration. +service TenantService { + option (google.api.default_host) = "jobs.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/jobs"; + + // Creates a new tenant entity. + rpc CreateTenant(CreateTenantRequest) returns (Tenant) { + option (google.api.http) = { + post: "/v4/{parent=projects/*}/tenants" + body: "tenant" + }; + option (google.api.method_signature) = "parent,tenant"; + } + + // Retrieves specified tenant. + rpc GetTenant(GetTenantRequest) returns (Tenant) { + option (google.api.http) = { + get: "/v4/{name=projects/*/tenants/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Updates specified tenant. + rpc UpdateTenant(UpdateTenantRequest) returns (Tenant) { + option (google.api.http) = { + patch: "/v4/{tenant.name=projects/*/tenants/*}" + body: "tenant" + }; + option (google.api.method_signature) = "tenant,update_mask"; + } + + // Deletes specified tenant. + rpc DeleteTenant(DeleteTenantRequest) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/v4/{name=projects/*/tenants/*}" + }; + option (google.api.method_signature) = "name"; + } + + // Lists all tenants associated with the project. + rpc ListTenants(ListTenantsRequest) returns (ListTenantsResponse) { + option (google.api.http) = { + get: "/v4/{parent=projects/*}/tenants" + }; + option (google.api.method_signature) = "parent"; + } +} + +// The Request of the CreateTenant method. +message CreateTenantRequest { + // Required. Resource name of the project under which the tenant is created. + // + // The format is "projects/{project_id}", for example, + // "projects/foo". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + // Required. The tenant to be created. + Tenant tenant = 2 [(google.api.field_behavior) = REQUIRED]; +} + +// Request for getting a tenant by name. +message GetTenantRequest { + // Required. The resource name of the tenant to be retrieved. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; +} + +// Request for updating a specified tenant. +message UpdateTenantRequest { + // Required. The tenant resource to replace the current resource in the + // system. + Tenant tenant = 1 [(google.api.field_behavior) = REQUIRED]; + + // Strongly recommended for the best service experience. + // + // If [update_mask][google.cloud.talent.v4.UpdateTenantRequest.update_mask] is + // provided, only the specified fields in + // [tenant][google.cloud.talent.v4.UpdateTenantRequest.tenant] are updated. + // Otherwise all the fields are updated. + // + // A field mask to specify the tenant fields to be updated. Only + // top level fields of [Tenant][google.cloud.talent.v4.Tenant] are supported. + google.protobuf.FieldMask update_mask = 2; +} + +// Request to delete a tenant. +message DeleteTenantRequest { + // Required. The resource name of the tenant to be deleted. + // + // The format is "projects/{project_id}/tenants/{tenant_id}", for example, + // "projects/foo/tenants/bar". + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { type: "jobs.googleapis.com/Tenant" } + ]; +} + +// List tenants for which the client has ACL visibility. +message ListTenantsRequest { + // Required. Resource name of the project under which the tenant is created. + // + // The format is "projects/{project_id}", for example, + // "projects/foo". + string parent = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference) = { + type: "cloudresourcemanager.googleapis.com/Project" + } + ]; + + // The starting indicator from which to return results. + string page_token = 2; + + // The maximum number of tenants to be returned, at most 100. + // Default is 100 if a non-positive number is provided. + int32 page_size = 3; +} + +// The List tenants response object. +message ListTenantsResponse { + // Tenants for the current client. + repeated Tenant tenants = 1; + + // A token to retrieve the next page of results. + string next_page_token = 2; + + // Additional information for the API invocation, such as the request + // tracking id. + ResponseMetadata metadata = 3; +} diff --git a/google/cloud/talent_v4/py.typed b/google/cloud/talent_v4/py.typed new file mode 100644 index 00000000..d9645927 --- /dev/null +++ b/google/cloud/talent_v4/py.typed @@ -0,0 +1,2 @@ +# Marker file for PEP 561. +# The google-cloud-talent package uses inline types. diff --git a/google/cloud/talent_v4/services/__init__.py b/google/cloud/talent_v4/services/__init__.py new file mode 100644 index 00000000..42ffdf2b --- /dev/null +++ b/google/cloud/talent_v4/services/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# diff --git a/google/cloud/talent_v4/services/company_service/__init__.py b/google/cloud/talent_v4/services/company_service/__init__.py new file mode 100644 index 00000000..a7154172 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .client import CompanyServiceClient +from .async_client import CompanyServiceAsyncClient + +__all__ = ( + "CompanyServiceClient", + "CompanyServiceAsyncClient", +) diff --git a/google/cloud/talent_v4/services/company_service/async_client.py b/google/cloud/talent_v4/services/company_service/async_client.py new file mode 100644 index 00000000..37769378 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/async_client.py @@ -0,0 +1,559 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.services.company_service import pagers +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + +from .transports.base import CompanyServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import CompanyServiceGrpcAsyncIOTransport +from .client import CompanyServiceClient + + +class CompanyServiceAsyncClient: + """A service that handles company management, including CRUD and + enumeration. + """ + + _client: CompanyServiceClient + + DEFAULT_ENDPOINT = CompanyServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = CompanyServiceClient.DEFAULT_MTLS_ENDPOINT + + company_path = staticmethod(CompanyServiceClient.company_path) + parse_company_path = staticmethod(CompanyServiceClient.parse_company_path) + + from_service_account_file = CompanyServiceClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(CompanyServiceClient).get_transport_class, type(CompanyServiceClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, CompanyServiceTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the company service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.CompanyServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + 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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = CompanyServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def create_company( + self, + request: company_service.CreateCompanyRequest = None, + *, + parent: str = None, + company: gct_company.Company = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_company.Company: + r"""Creates a new company entity. + + Args: + request (:class:`~.company_service.CreateCompanyRequest`): + The request object. The Request of the CreateCompany + method. + parent (:class:`str`): + Required. Resource name of the tenant under which the + company is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + company (:class:`~.gct_company.Company`): + Required. The company to be created. + This corresponds to the ``company`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, company]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = company_service.CreateCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if company is not None: + request.company = company + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_company, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def get_company( + self, + request: company_service.GetCompanyRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> company.Company: + r"""Retrieves specified company. + + Args: + request (:class:`~.company_service.GetCompanyRequest`): + The request object. Request for getting a company by + name. + name (:class:`str`): + Required. The resource name of the company to be + retrieved. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, + "projects/api-test-project/tenants/foo/companies/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = company_service.GetCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.get_company, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def update_company( + self, + request: company_service.UpdateCompanyRequest = None, + *, + company: gct_company.Company = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_company.Company: + r"""Updates specified company. + + Args: + request (:class:`~.company_service.UpdateCompanyRequest`): + The request object. Request for updating a specified + company. + company (:class:`~.gct_company.Company`): + Required. The company resource to + replace the current resource in the + system. + This corresponds to the ``company`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateCompanyRequest.update_mask] + is provided, only the specified fields in + [company][google.cloud.talent.v4.UpdateCompanyRequest.company] + are updated. Otherwise all the fields are updated. + + A field mask to specify the company fields to be + updated. Only top level fields of + [Company][google.cloud.talent.v4.Company] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([company, update_mask]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = company_service.UpdateCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if company is not None: + request.company = company + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.update_company, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("company.name", request.company.name),) + ), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def delete_company( + self, + request: company_service.DeleteCompanyRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes specified company. + Prerequisite: The company has no jobs associated with + it. + + Args: + request (:class:`~.company_service.DeleteCompanyRequest`): + The request object. Request to delete a company. + name (:class:`str`): + Required. The resource name of the company to be + deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, "projects/foo/tenants/bar/companies/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = company_service.DeleteCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.delete_company, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + await rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + async def list_companies( + self, + request: company_service.ListCompaniesRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListCompaniesAsyncPager: + r"""Lists all companies associated with the project. + + Args: + request (:class:`~.company_service.ListCompaniesRequest`): + The request object. List companies for which the client + has ACL visibility. + parent (:class:`str`): + Required. Resource name of the tenant under which the + company is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListCompaniesAsyncPager: + The List companies response object. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = company_service.ListCompaniesRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_companies, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListCompaniesAsyncPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("CompanyServiceAsyncClient",) diff --git a/google/cloud/talent_v4/services/company_service/client.py b/google/cloud/talent_v4/services/company_service/client.py new file mode 100644 index 00000000..30d6bff0 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/client.py @@ -0,0 +1,706 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.services.company_service import pagers +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + +from .transports.base import CompanyServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import CompanyServiceGrpcTransport +from .transports.grpc_asyncio import CompanyServiceGrpcAsyncIOTransport + + +class CompanyServiceClientMeta(type): + """Metaclass for the CompanyService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = ( + OrderedDict() + ) # type: Dict[str, Type[CompanyServiceTransport]] + _transport_registry["grpc"] = CompanyServiceGrpcTransport + _transport_registry["grpc_asyncio"] = CompanyServiceGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[CompanyServiceTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class CompanyServiceClient(metaclass=CompanyServiceClientMeta): + """A service that handles company management, including CRUD and + enumeration. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "jobs.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @staticmethod + def company_path(project: str, tenant: str, company: str,) -> str: + """Return a fully-qualified company string.""" + return "projects/{project}/tenants/{tenant}/companies/{company}".format( + project=project, tenant=tenant, company=company, + ) + + @staticmethod + def parse_company_path(path: str) -> Dict[str, str]: + """Parse a company path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/tenants/(?P.+?)/companies/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, CompanyServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the company service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.CompanyServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, CompanyServiceTransport): + # transport is a CompanyServiceTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def create_company( + self, + request: company_service.CreateCompanyRequest = None, + *, + parent: str = None, + company: gct_company.Company = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_company.Company: + r"""Creates a new company entity. + + Args: + request (:class:`~.company_service.CreateCompanyRequest`): + The request object. The Request of the CreateCompany + method. + parent (:class:`str`): + Required. Resource name of the tenant under which the + company is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + company (:class:`~.gct_company.Company`): + Required. The company to be created. + This corresponds to the ``company`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, company]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a company_service.CreateCompanyRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, company_service.CreateCompanyRequest): + request = company_service.CreateCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if company is not None: + request.company = company + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.create_company] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def get_company( + self, + request: company_service.GetCompanyRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> company.Company: + r"""Retrieves specified company. + + Args: + request (:class:`~.company_service.GetCompanyRequest`): + The request object. Request for getting a company by + name. + name (:class:`str`): + Required. The resource name of the company to be + retrieved. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, + "projects/api-test-project/tenants/foo/companies/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a company_service.GetCompanyRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, company_service.GetCompanyRequest): + request = company_service.GetCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.get_company] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def update_company( + self, + request: company_service.UpdateCompanyRequest = None, + *, + company: gct_company.Company = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_company.Company: + r"""Updates specified company. + + Args: + request (:class:`~.company_service.UpdateCompanyRequest`): + The request object. Request for updating a specified + company. + company (:class:`~.gct_company.Company`): + Required. The company resource to + replace the current resource in the + system. + This corresponds to the ``company`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateCompanyRequest.update_mask] + is provided, only the specified fields in + [company][google.cloud.talent.v4.UpdateCompanyRequest.company] + are updated. Otherwise all the fields are updated. + + A field mask to specify the company fields to be + updated. Only top level fields of + [Company][google.cloud.talent.v4.Company] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_company.Company: + A Company resource represents a + company in the service. A company is the + entity that owns job postings, that is, + the hiring entity responsible for + employing applicants for the job + position. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([company, update_mask]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a company_service.UpdateCompanyRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, company_service.UpdateCompanyRequest): + request = company_service.UpdateCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if company is not None: + request.company = company + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.update_company] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("company.name", request.company.name),) + ), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def delete_company( + self, + request: company_service.DeleteCompanyRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes specified company. + Prerequisite: The company has no jobs associated with + it. + + Args: + request (:class:`~.company_service.DeleteCompanyRequest`): + The request object. Request to delete a company. + name (:class:`str`): + Required. The resource name of the company to be + deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, "projects/foo/tenants/bar/companies/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a company_service.DeleteCompanyRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, company_service.DeleteCompanyRequest): + request = company_service.DeleteCompanyRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.delete_company] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + def list_companies( + self, + request: company_service.ListCompaniesRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListCompaniesPager: + r"""Lists all companies associated with the project. + + Args: + request (:class:`~.company_service.ListCompaniesRequest`): + The request object. List companies for which the client + has ACL visibility. + parent (:class:`str`): + Required. Resource name of the tenant under which the + company is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListCompaniesPager: + The List companies response object. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a company_service.ListCompaniesRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, company_service.ListCompaniesRequest): + request = company_service.ListCompaniesRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_companies] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListCompaniesPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("CompanyServiceClient",) diff --git a/google/cloud/talent_v4/services/company_service/pagers.py b/google/cloud/talent_v4/services/company_service/pagers.py new file mode 100644 index 00000000..992c4b53 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/pagers.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple + +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company_service + + +class ListCompaniesPager: + """A pager for iterating through ``list_companies`` requests. + + This class thinly wraps an initial + :class:`~.company_service.ListCompaniesResponse` object, and + provides an ``__iter__`` method to iterate through its + ``companies`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListCompanies`` requests and continue to iterate + through the ``companies`` field on the + corresponding responses. + + All the usual :class:`~.company_service.ListCompaniesResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., company_service.ListCompaniesResponse], + request: company_service.ListCompaniesRequest, + response: company_service.ListCompaniesResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.company_service.ListCompaniesRequest`): + The initial request object. + response (:class:`~.company_service.ListCompaniesResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = company_service.ListCompaniesRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterable[company_service.ListCompaniesResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method(self._request, metadata=self._metadata) + yield self._response + + def __iter__(self) -> Iterable[company.Company]: + for page in self.pages: + yield from page.companies + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListCompaniesAsyncPager: + """A pager for iterating through ``list_companies`` requests. + + This class thinly wraps an initial + :class:`~.company_service.ListCompaniesResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``companies`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListCompanies`` requests and continue to iterate + through the ``companies`` field on the + corresponding responses. + + All the usual :class:`~.company_service.ListCompaniesResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., Awaitable[company_service.ListCompaniesResponse]], + request: company_service.ListCompaniesRequest, + response: company_service.ListCompaniesResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.company_service.ListCompaniesRequest`): + The initial request object. + response (:class:`~.company_service.ListCompaniesResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = company_service.ListCompaniesRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages(self) -> AsyncIterable[company_service.ListCompaniesResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method(self._request, metadata=self._metadata) + yield self._response + + def __aiter__(self) -> AsyncIterable[company.Company]: + async def async_generator(): + async for page in self.pages: + for response in page.companies: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/google/cloud/talent_v4/services/company_service/transports/__init__.py b/google/cloud/talent_v4/services/company_service/transports/__init__.py new file mode 100644 index 00000000..ad1eb728 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import CompanyServiceTransport +from .grpc import CompanyServiceGrpcTransport +from .grpc_asyncio import CompanyServiceGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[CompanyServiceTransport]] +_transport_registry["grpc"] = CompanyServiceGrpcTransport +_transport_registry["grpc_asyncio"] = CompanyServiceGrpcAsyncIOTransport + + +__all__ = ( + "CompanyServiceTransport", + "CompanyServiceGrpcTransport", + "CompanyServiceGrpcAsyncIOTransport", +) diff --git a/google/cloud/talent_v4/services/company_service/transports/base.py b/google/cloud/talent_v4/services/company_service/transports/base.py new file mode 100644 index 00000000..849a9354 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/transports/base.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore + +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.protobuf import empty_pb2 as empty # type: ignore + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class CompanyServiceTransport(abc.ABC): + """Abstract transport class for CompanyService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.create_company: gapic_v1.method.wrap_method( + self.create_company, default_timeout=30.0, client_info=client_info, + ), + self.get_company: gapic_v1.method.wrap_method( + self.get_company, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.update_company: gapic_v1.method.wrap_method( + self.update_company, default_timeout=30.0, client_info=client_info, + ), + self.delete_company: gapic_v1.method.wrap_method( + self.delete_company, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.list_companies: gapic_v1.method.wrap_method( + self.list_companies, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + } + + @property + def create_company( + self, + ) -> typing.Callable[ + [company_service.CreateCompanyRequest], + typing.Union[gct_company.Company, typing.Awaitable[gct_company.Company]], + ]: + raise NotImplementedError() + + @property + def get_company( + self, + ) -> typing.Callable[ + [company_service.GetCompanyRequest], + typing.Union[company.Company, typing.Awaitable[company.Company]], + ]: + raise NotImplementedError() + + @property + def update_company( + self, + ) -> typing.Callable[ + [company_service.UpdateCompanyRequest], + typing.Union[gct_company.Company, typing.Awaitable[gct_company.Company]], + ]: + raise NotImplementedError() + + @property + def delete_company( + self, + ) -> typing.Callable[ + [company_service.DeleteCompanyRequest], + typing.Union[empty.Empty, typing.Awaitable[empty.Empty]], + ]: + raise NotImplementedError() + + @property + def list_companies( + self, + ) -> typing.Callable[ + [company_service.ListCompaniesRequest], + typing.Union[ + company_service.ListCompaniesResponse, + typing.Awaitable[company_service.ListCompaniesResponse], + ], + ]: + raise NotImplementedError() + + +__all__ = ("CompanyServiceTransport",) diff --git a/google/cloud/talent_v4/services/company_service/transports/grpc.py b/google/cloud/talent_v4/services/company_service/transports/grpc.py new file mode 100644 index 00000000..0f89386c --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/transports/grpc.py @@ -0,0 +1,371 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import CompanyServiceTransport, DEFAULT_CLIENT_INFO + + +class CompanyServiceGrpcTransport(CompanyServiceTransport): + """gRPC backend transport for CompanyService. + + A service that handles company management, including CRUD and + enumeration. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optionsl[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_company( + self, + ) -> Callable[[company_service.CreateCompanyRequest], gct_company.Company]: + r"""Return a callable for the create company method over gRPC. + + Creates a new company entity. + + Returns: + Callable[[~.CreateCompanyRequest], + ~.Company]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_company" not in self._stubs: + self._stubs["create_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/CreateCompany", + request_serializer=company_service.CreateCompanyRequest.serialize, + response_deserializer=gct_company.Company.deserialize, + ) + return self._stubs["create_company"] + + @property + def get_company( + self, + ) -> Callable[[company_service.GetCompanyRequest], company.Company]: + r"""Return a callable for the get company method over gRPC. + + Retrieves specified company. + + Returns: + Callable[[~.GetCompanyRequest], + ~.Company]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_company" not in self._stubs: + self._stubs["get_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/GetCompany", + request_serializer=company_service.GetCompanyRequest.serialize, + response_deserializer=company.Company.deserialize, + ) + return self._stubs["get_company"] + + @property + def update_company( + self, + ) -> Callable[[company_service.UpdateCompanyRequest], gct_company.Company]: + r"""Return a callable for the update company method over gRPC. + + Updates specified company. + + Returns: + Callable[[~.UpdateCompanyRequest], + ~.Company]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_company" not in self._stubs: + self._stubs["update_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/UpdateCompany", + request_serializer=company_service.UpdateCompanyRequest.serialize, + response_deserializer=gct_company.Company.deserialize, + ) + return self._stubs["update_company"] + + @property + def delete_company( + self, + ) -> Callable[[company_service.DeleteCompanyRequest], empty.Empty]: + r"""Return a callable for the delete company method over gRPC. + + Deletes specified company. + Prerequisite: The company has no jobs associated with + it. + + Returns: + Callable[[~.DeleteCompanyRequest], + ~.Empty]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_company" not in self._stubs: + self._stubs["delete_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/DeleteCompany", + request_serializer=company_service.DeleteCompanyRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_company"] + + @property + def list_companies( + self, + ) -> Callable[ + [company_service.ListCompaniesRequest], company_service.ListCompaniesResponse + ]: + r"""Return a callable for the list companies method over gRPC. + + Lists all companies associated with the project. + + Returns: + Callable[[~.ListCompaniesRequest], + ~.ListCompaniesResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_companies" not in self._stubs: + self._stubs["list_companies"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/ListCompanies", + request_serializer=company_service.ListCompaniesRequest.serialize, + response_deserializer=company_service.ListCompaniesResponse.deserialize, + ) + return self._stubs["list_companies"] + + +__all__ = ("CompanyServiceGrpcTransport",) diff --git a/google/cloud/talent_v4/services/company_service/transports/grpc_asyncio.py b/google/cloud/talent_v4/services/company_service/transports/grpc_asyncio.py new file mode 100644 index 00000000..cbe2f0f3 --- /dev/null +++ b/google/cloud/talent_v4/services/company_service/transports/grpc_asyncio.py @@ -0,0 +1,376 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import CompanyServiceTransport, DEFAULT_CLIENT_INFO +from .grpc import CompanyServiceGrpcTransport + + +class CompanyServiceGrpcAsyncIOTransport(CompanyServiceTransport): + """gRPC AsyncIO backend transport for CompanyService. + + A service that handles company management, including CRUD and + enumeration. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_company( + self, + ) -> Callable[ + [company_service.CreateCompanyRequest], Awaitable[gct_company.Company] + ]: + r"""Return a callable for the create company method over gRPC. + + Creates a new company entity. + + Returns: + Callable[[~.CreateCompanyRequest], + Awaitable[~.Company]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_company" not in self._stubs: + self._stubs["create_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/CreateCompany", + request_serializer=company_service.CreateCompanyRequest.serialize, + response_deserializer=gct_company.Company.deserialize, + ) + return self._stubs["create_company"] + + @property + def get_company( + self, + ) -> Callable[[company_service.GetCompanyRequest], Awaitable[company.Company]]: + r"""Return a callable for the get company method over gRPC. + + Retrieves specified company. + + Returns: + Callable[[~.GetCompanyRequest], + Awaitable[~.Company]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_company" not in self._stubs: + self._stubs["get_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/GetCompany", + request_serializer=company_service.GetCompanyRequest.serialize, + response_deserializer=company.Company.deserialize, + ) + return self._stubs["get_company"] + + @property + def update_company( + self, + ) -> Callable[ + [company_service.UpdateCompanyRequest], Awaitable[gct_company.Company] + ]: + r"""Return a callable for the update company method over gRPC. + + Updates specified company. + + Returns: + Callable[[~.UpdateCompanyRequest], + Awaitable[~.Company]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_company" not in self._stubs: + self._stubs["update_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/UpdateCompany", + request_serializer=company_service.UpdateCompanyRequest.serialize, + response_deserializer=gct_company.Company.deserialize, + ) + return self._stubs["update_company"] + + @property + def delete_company( + self, + ) -> Callable[[company_service.DeleteCompanyRequest], Awaitable[empty.Empty]]: + r"""Return a callable for the delete company method over gRPC. + + Deletes specified company. + Prerequisite: The company has no jobs associated with + it. + + Returns: + Callable[[~.DeleteCompanyRequest], + Awaitable[~.Empty]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_company" not in self._stubs: + self._stubs["delete_company"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/DeleteCompany", + request_serializer=company_service.DeleteCompanyRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_company"] + + @property + def list_companies( + self, + ) -> Callable[ + [company_service.ListCompaniesRequest], + Awaitable[company_service.ListCompaniesResponse], + ]: + r"""Return a callable for the list companies method over gRPC. + + Lists all companies associated with the project. + + Returns: + Callable[[~.ListCompaniesRequest], + Awaitable[~.ListCompaniesResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_companies" not in self._stubs: + self._stubs["list_companies"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.CompanyService/ListCompanies", + request_serializer=company_service.ListCompaniesRequest.serialize, + response_deserializer=company_service.ListCompaniesResponse.deserialize, + ) + return self._stubs["list_companies"] + + +__all__ = ("CompanyServiceGrpcAsyncIOTransport",) diff --git a/google/cloud/talent_v4/services/completion/__init__.py b/google/cloud/talent_v4/services/completion/__init__.py new file mode 100644 index 00000000..0c274210 --- /dev/null +++ b/google/cloud/talent_v4/services/completion/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .client import CompletionClient +from .async_client import CompletionAsyncClient + +__all__ = ( + "CompletionClient", + "CompletionAsyncClient", +) diff --git a/google/cloud/talent_v4/services/completion/async_client.py b/google/cloud/talent_v4/services/completion/async_client.py new file mode 100644 index 00000000..e53fba44 --- /dev/null +++ b/google/cloud/talent_v4/services/completion/async_client.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import completion_service + +from .transports.base import CompletionTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import CompletionGrpcAsyncIOTransport +from .client import CompletionClient + + +class CompletionAsyncClient: + """A service handles auto completion.""" + + _client: CompletionClient + + DEFAULT_ENDPOINT = CompletionClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = CompletionClient.DEFAULT_MTLS_ENDPOINT + + from_service_account_file = CompletionClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(CompletionClient).get_transport_class, type(CompletionClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, CompletionTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the completion client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.CompletionTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + 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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = CompletionClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def complete_query( + self, + request: completion_service.CompleteQueryRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> completion_service.CompleteQueryResponse: + r"""Completes the specified prefix with keyword + suggestions. Intended for use by a job search auto- + complete search box. + + Args: + request (:class:`~.completion_service.CompleteQueryRequest`): + The request object. Auto-complete parameters. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.completion_service.CompleteQueryResponse: + Response of auto-complete query. + """ + # Create or coerce a protobuf request object. + + request = completion_service.CompleteQueryRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.complete_query, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("tenant", request.tenant),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("CompletionAsyncClient",) diff --git a/google/cloud/talent_v4/services/completion/client.py b/google/cloud/talent_v4/services/completion/client.py new file mode 100644 index 00000000..af9b83dc --- /dev/null +++ b/google/cloud/talent_v4/services/completion/client.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import completion_service + +from .transports.base import CompletionTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import CompletionGrpcTransport +from .transports.grpc_asyncio import CompletionGrpcAsyncIOTransport + + +class CompletionClientMeta(type): + """Metaclass for the Completion client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = OrderedDict() # type: Dict[str, Type[CompletionTransport]] + _transport_registry["grpc"] = CompletionGrpcTransport + _transport_registry["grpc_asyncio"] = CompletionGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[CompletionTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class CompletionClient(metaclass=CompletionClientMeta): + """A service handles auto completion.""" + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "jobs.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, CompletionTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the completion client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.CompletionTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, CompletionTransport): + # transport is a CompletionTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def complete_query( + self, + request: completion_service.CompleteQueryRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> completion_service.CompleteQueryResponse: + r"""Completes the specified prefix with keyword + suggestions. Intended for use by a job search auto- + complete search box. + + Args: + request (:class:`~.completion_service.CompleteQueryRequest`): + The request object. Auto-complete parameters. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.completion_service.CompleteQueryResponse: + Response of auto-complete query. + """ + # Create or coerce a protobuf request object. + + # Minor optimization to avoid making a copy if the user passes + # in a completion_service.CompleteQueryRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, completion_service.CompleteQueryRequest): + request = completion_service.CompleteQueryRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.complete_query] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("tenant", request.tenant),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("CompletionClient",) diff --git a/google/cloud/talent_v4/services/completion/transports/__init__.py b/google/cloud/talent_v4/services/completion/transports/__init__.py new file mode 100644 index 00000000..9256febd --- /dev/null +++ b/google/cloud/talent_v4/services/completion/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import CompletionTransport +from .grpc import CompletionGrpcTransport +from .grpc_asyncio import CompletionGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[CompletionTransport]] +_transport_registry["grpc"] = CompletionGrpcTransport +_transport_registry["grpc_asyncio"] = CompletionGrpcAsyncIOTransport + + +__all__ = ( + "CompletionTransport", + "CompletionGrpcTransport", + "CompletionGrpcAsyncIOTransport", +) diff --git a/google/cloud/talent_v4/services/completion/transports/base.py b/google/cloud/talent_v4/services/completion/transports/base.py new file mode 100644 index 00000000..9f016fcc --- /dev/null +++ b/google/cloud/talent_v4/services/completion/transports/base.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore + +from google.cloud.talent_v4.types import completion_service + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class CompletionTransport(abc.ABC): + """Abstract transport class for Completion.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.complete_query: gapic_v1.method.wrap_method( + self.complete_query, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + } + + @property + def complete_query( + self, + ) -> typing.Callable[ + [completion_service.CompleteQueryRequest], + typing.Union[ + completion_service.CompleteQueryResponse, + typing.Awaitable[completion_service.CompleteQueryResponse], + ], + ]: + raise NotImplementedError() + + +__all__ = ("CompletionTransport",) diff --git a/google/cloud/talent_v4/services/completion/transports/grpc.py b/google/cloud/talent_v4/services/completion/transports/grpc.py new file mode 100644 index 00000000..68ed6ef3 --- /dev/null +++ b/google/cloud/talent_v4/services/completion/transports/grpc.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.cloud.talent_v4.types import completion_service + +from .base import CompletionTransport, DEFAULT_CLIENT_INFO + + +class CompletionGrpcTransport(CompletionTransport): + """gRPC backend transport for Completion. + + A service handles auto completion. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optionsl[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def complete_query( + self, + ) -> Callable[ + [completion_service.CompleteQueryRequest], + completion_service.CompleteQueryResponse, + ]: + r"""Return a callable for the complete query method over gRPC. + + Completes the specified prefix with keyword + suggestions. Intended for use by a job search auto- + complete search box. + + Returns: + Callable[[~.CompleteQueryRequest], + ~.CompleteQueryResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "complete_query" not in self._stubs: + self._stubs["complete_query"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.Completion/CompleteQuery", + request_serializer=completion_service.CompleteQueryRequest.serialize, + response_deserializer=completion_service.CompleteQueryResponse.deserialize, + ) + return self._stubs["complete_query"] + + +__all__ = ("CompletionGrpcTransport",) diff --git a/google/cloud/talent_v4/services/completion/transports/grpc_asyncio.py b/google/cloud/talent_v4/services/completion/transports/grpc_asyncio.py new file mode 100644 index 00000000..b24f3e89 --- /dev/null +++ b/google/cloud/talent_v4/services/completion/transports/grpc_asyncio.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.talent_v4.types import completion_service + +from .base import CompletionTransport, DEFAULT_CLIENT_INFO +from .grpc import CompletionGrpcTransport + + +class CompletionGrpcAsyncIOTransport(CompletionTransport): + """gRPC AsyncIO backend transport for Completion. + + A service handles auto completion. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def complete_query( + self, + ) -> Callable[ + [completion_service.CompleteQueryRequest], + Awaitable[completion_service.CompleteQueryResponse], + ]: + r"""Return a callable for the complete query method over gRPC. + + Completes the specified prefix with keyword + suggestions. Intended for use by a job search auto- + complete search box. + + Returns: + Callable[[~.CompleteQueryRequest], + Awaitable[~.CompleteQueryResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "complete_query" not in self._stubs: + self._stubs["complete_query"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.Completion/CompleteQuery", + request_serializer=completion_service.CompleteQueryRequest.serialize, + response_deserializer=completion_service.CompleteQueryResponse.deserialize, + ) + return self._stubs["complete_query"] + + +__all__ = ("CompletionGrpcAsyncIOTransport",) diff --git a/google/cloud/talent_v4/services/event_service/__init__.py b/google/cloud/talent_v4/services/event_service/__init__.py new file mode 100644 index 00000000..f321845c --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .client import EventServiceClient +from .async_client import EventServiceAsyncClient + +__all__ = ( + "EventServiceClient", + "EventServiceAsyncClient", +) diff --git a/google/cloud/talent_v4/services/event_service/async_client.py b/google/cloud/talent_v4/services/event_service/async_client.py new file mode 100644 index 00000000..2178dad2 --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/async_client.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + +from .transports.base import EventServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import EventServiceGrpcAsyncIOTransport +from .client import EventServiceClient + + +class EventServiceAsyncClient: + """A service handles client event report.""" + + _client: EventServiceClient + + DEFAULT_ENDPOINT = EventServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = EventServiceClient.DEFAULT_MTLS_ENDPOINT + + from_service_account_file = EventServiceClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(EventServiceClient).get_transport_class, type(EventServiceClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, EventServiceTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the event service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.EventServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + 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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = EventServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def create_client_event( + self, + request: event_service.CreateClientEventRequest = None, + *, + parent: str = None, + client_event: event.ClientEvent = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> event.ClientEvent: + r"""Report events issued when end user interacts with customer's + application that uses Cloud Talent Solution. You may inspect the + created events in `self service + tools `__. + `Learn + more `__ + about self service tools. + + Args: + request (:class:`~.event_service.CreateClientEventRequest`): + The request object. The report event request. + parent (:class:`str`): + Required. Resource name of the tenant under which the + event is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + client_event (:class:`~.event.ClientEvent`): + Required. Events issued when end user + interacts with customer's application + that uses Cloud Talent Solution. + This corresponds to the ``client_event`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.event.ClientEvent: + An event issued when an end user + interacts with the application that + implements Cloud Talent Solution. + Providing this information improves the + quality of results for the API clients, + enabling the service to perform + optimally. The number of events sent + must be consistent with other calls, + such as job searches, issued to the + service by the client. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, client_event]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = event_service.CreateClientEventRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if client_event is not None: + request.client_event = client_event + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_client_event, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("EventServiceAsyncClient",) diff --git a/google/cloud/talent_v4/services/event_service/client.py b/google/cloud/talent_v4/services/event_service/client.py new file mode 100644 index 00000000..cc9aaed5 --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/client.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + +from .transports.base import EventServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import EventServiceGrpcTransport +from .transports.grpc_asyncio import EventServiceGrpcAsyncIOTransport + + +class EventServiceClientMeta(type): + """Metaclass for the EventService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = OrderedDict() # type: Dict[str, Type[EventServiceTransport]] + _transport_registry["grpc"] = EventServiceGrpcTransport + _transport_registry["grpc_asyncio"] = EventServiceGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[EventServiceTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class EventServiceClient(metaclass=EventServiceClientMeta): + """A service handles client event report.""" + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "jobs.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, EventServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the event service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.EventServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, EventServiceTransport): + # transport is a EventServiceTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def create_client_event( + self, + request: event_service.CreateClientEventRequest = None, + *, + parent: str = None, + client_event: event.ClientEvent = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> event.ClientEvent: + r"""Report events issued when end user interacts with customer's + application that uses Cloud Talent Solution. You may inspect the + created events in `self service + tools `__. + `Learn + more `__ + about self service tools. + + Args: + request (:class:`~.event_service.CreateClientEventRequest`): + The request object. The report event request. + parent (:class:`str`): + Required. Resource name of the tenant under which the + event is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + client_event (:class:`~.event.ClientEvent`): + Required. Events issued when end user + interacts with customer's application + that uses Cloud Talent Solution. + This corresponds to the ``client_event`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.event.ClientEvent: + An event issued when an end user + interacts with the application that + implements Cloud Talent Solution. + Providing this information improves the + quality of results for the API clients, + enabling the service to perform + optimally. The number of events sent + must be consistent with other calls, + such as job searches, issued to the + service by the client. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, client_event]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a event_service.CreateClientEventRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, event_service.CreateClientEventRequest): + request = event_service.CreateClientEventRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if client_event is not None: + request.client_event = client_event + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.create_client_event] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("EventServiceClient",) diff --git a/google/cloud/talent_v4/services/event_service/transports/__init__.py b/google/cloud/talent_v4/services/event_service/transports/__init__.py new file mode 100644 index 00000000..18ca2a9d --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import EventServiceTransport +from .grpc import EventServiceGrpcTransport +from .grpc_asyncio import EventServiceGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[EventServiceTransport]] +_transport_registry["grpc"] = EventServiceGrpcTransport +_transport_registry["grpc_asyncio"] = EventServiceGrpcAsyncIOTransport + + +__all__ = ( + "EventServiceTransport", + "EventServiceGrpcTransport", + "EventServiceGrpcAsyncIOTransport", +) diff --git a/google/cloud/talent_v4/services/event_service/transports/base.py b/google/cloud/talent_v4/services/event_service/transports/base.py new file mode 100644 index 00000000..88f54bbc --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/transports/base.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore + +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class EventServiceTransport(abc.ABC): + """Abstract transport class for EventService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.create_client_event: gapic_v1.method.wrap_method( + self.create_client_event, default_timeout=30.0, client_info=client_info, + ), + } + + @property + def create_client_event( + self, + ) -> typing.Callable[ + [event_service.CreateClientEventRequest], + typing.Union[event.ClientEvent, typing.Awaitable[event.ClientEvent]], + ]: + raise NotImplementedError() + + +__all__ = ("EventServiceTransport",) diff --git a/google/cloud/talent_v4/services/event_service/transports/grpc.py b/google/cloud/talent_v4/services/event_service/transports/grpc.py new file mode 100644 index 00000000..77b026c2 --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/transports/grpc.py @@ -0,0 +1,266 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service + +from .base import EventServiceTransport, DEFAULT_CLIENT_INFO + + +class EventServiceGrpcTransport(EventServiceTransport): + """gRPC backend transport for EventService. + + A service handles client event report. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optionsl[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_client_event( + self, + ) -> Callable[[event_service.CreateClientEventRequest], event.ClientEvent]: + r"""Return a callable for the create client event method over gRPC. + + Report events issued when end user interacts with customer's + application that uses Cloud Talent Solution. You may inspect the + created events in `self service + tools `__. + `Learn + more `__ + about self service tools. + + Returns: + Callable[[~.CreateClientEventRequest], + ~.ClientEvent]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_client_event" not in self._stubs: + self._stubs["create_client_event"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.EventService/CreateClientEvent", + request_serializer=event_service.CreateClientEventRequest.serialize, + response_deserializer=event.ClientEvent.deserialize, + ) + return self._stubs["create_client_event"] + + +__all__ = ("EventServiceGrpcTransport",) diff --git a/google/cloud/talent_v4/services/event_service/transports/grpc_asyncio.py b/google/cloud/talent_v4/services/event_service/transports/grpc_asyncio.py new file mode 100644 index 00000000..e79b22ae --- /dev/null +++ b/google/cloud/talent_v4/services/event_service/transports/grpc_asyncio.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service + +from .base import EventServiceTransport, DEFAULT_CLIENT_INFO +from .grpc import EventServiceGrpcTransport + + +class EventServiceGrpcAsyncIOTransport(EventServiceTransport): + """gRPC AsyncIO backend transport for EventService. + + A service handles client event report. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_client_event( + self, + ) -> Callable[ + [event_service.CreateClientEventRequest], Awaitable[event.ClientEvent] + ]: + r"""Return a callable for the create client event method over gRPC. + + Report events issued when end user interacts with customer's + application that uses Cloud Talent Solution. You may inspect the + created events in `self service + tools `__. + `Learn + more `__ + about self service tools. + + Returns: + Callable[[~.CreateClientEventRequest], + Awaitable[~.ClientEvent]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_client_event" not in self._stubs: + self._stubs["create_client_event"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.EventService/CreateClientEvent", + request_serializer=event_service.CreateClientEventRequest.serialize, + response_deserializer=event.ClientEvent.deserialize, + ) + return self._stubs["create_client_event"] + + +__all__ = ("EventServiceGrpcAsyncIOTransport",) diff --git a/google/cloud/talent_v4/services/job_service/__init__.py b/google/cloud/talent_v4/services/job_service/__init__.py new file mode 100644 index 00000000..5f157047 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .client import JobServiceClient +from .async_client import JobServiceAsyncClient + +__all__ = ( + "JobServiceClient", + "JobServiceAsyncClient", +) diff --git a/google/cloud/talent_v4/services/job_service/async_client.py b/google/cloud/talent_v4/services/job_service/async_client.py new file mode 100644 index 00000000..d0039885 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/async_client.py @@ -0,0 +1,1001 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore +from google.cloud.talent_v4.services.job_service import pagers +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import histogram +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + +from .transports.base import JobServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import JobServiceGrpcAsyncIOTransport +from .client import JobServiceClient + + +class JobServiceAsyncClient: + """A service handles job management, including job CRUD, + enumeration and search. + """ + + _client: JobServiceClient + + DEFAULT_ENDPOINT = JobServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = JobServiceClient.DEFAULT_MTLS_ENDPOINT + + job_path = staticmethod(JobServiceClient.job_path) + parse_job_path = staticmethod(JobServiceClient.parse_job_path) + + from_service_account_file = JobServiceClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(JobServiceClient).get_transport_class, type(JobServiceClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, JobServiceTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the job service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.JobServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + 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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = JobServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def create_job( + self, + request: job_service.CreateJobRequest = None, + *, + parent: str = None, + job: gct_job.Job = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_job.Job: + r"""Creates a new job. + Typically, the job becomes searchable within 10 seconds, + but it may take up to 5 minutes. + + Args: + request (:class:`~.job_service.CreateJobRequest`): + The request object. Create job request. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + job (:class:`~.gct_job.Job`): + Required. The Job to be created. + This corresponds to the ``job`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, job]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.CreateJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if job is not None: + request.job = job + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_job, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def batch_create_jobs( + self, + request: job_service.BatchCreateJobsRequest = None, + *, + parent: str = None, + jobs: Sequence[job.Job] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation_async.AsyncOperation: + r"""Begins executing a batch create jobs operation. + + Args: + request (:class:`~.job_service.BatchCreateJobsRequest`): + The request object. Request to create a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + jobs (:class:`Sequence[~.job.Job]`): + Required. The jobs to be created. + A maximum of 200 jobs can be created in + a batch. + This corresponds to the ``jobs`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation_async.AsyncOperation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchCreateJobsResponse``: The + result of + [JobService.BatchCreateJobs][google.cloud.talent.v4.JobService.BatchCreateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, jobs]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.BatchCreateJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if jobs is not None: + request.jobs = jobs + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.batch_create_jobs, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation_async.from_gapic( + response, + self._client._transport.operations_client, + job_service.BatchCreateJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + async def get_job( + self, + request: job_service.GetJobRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job.Job: + r"""Retrieves the specified job, whose status is OPEN or + recently EXPIRED within the last 90 days. + + Args: + request (:class:`~.job_service.GetJobRequest`): + The request object. Get job request. + name (:class:`str`): + Required. The resource name of the job to retrieve. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.GetJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.get_job, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def update_job( + self, + request: job_service.UpdateJobRequest = None, + *, + job: gct_job.Job = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_job.Job: + r"""Updates specified job. + Typically, updated contents become visible in search + results within 10 seconds, but it may take up to 5 + minutes. + + Args: + request (:class:`~.job_service.UpdateJobRequest`): + The request object. Update job request. + job (:class:`~.gct_job.Job`): + Required. The Job to be updated. + This corresponds to the ``job`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask] + is provided, only the specified fields in + [job][google.cloud.talent.v4.UpdateJobRequest.job] are + updated. Otherwise all the fields are updated. + + A field mask to restrict the fields that are updated. + Only top level fields of + [Job][google.cloud.talent.v4.Job] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([job, update_mask]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.UpdateJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if job is not None: + request.job = job + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.update_job, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("job.name", request.job.name),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def batch_update_jobs( + self, + request: job_service.BatchUpdateJobsRequest = None, + *, + parent: str = None, + jobs: Sequence[job.Job] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation_async.AsyncOperation: + r"""Begins executing a batch update jobs operation. + + Args: + request (:class:`~.job_service.BatchUpdateJobsRequest`): + The request object. Request to update a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + jobs (:class:`Sequence[~.job.Job]`): + Required. The jobs to be updated. + A maximum of 200 jobs can be updated in + a batch. + This corresponds to the ``jobs`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation_async.AsyncOperation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchUpdateJobsResponse``: The + result of + [JobService.BatchUpdateJobs][google.cloud.talent.v4.JobService.BatchUpdateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, jobs]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.BatchUpdateJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if jobs is not None: + request.jobs = jobs + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.batch_update_jobs, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation_async.from_gapic( + response, + self._client._transport.operations_client, + job_service.BatchUpdateJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + async def delete_job( + self, + request: job_service.DeleteJobRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes the specified job. + Typically, the job becomes unsearchable within 10 + seconds, but it may take up to 5 minutes. + + Args: + request (:class:`~.job_service.DeleteJobRequest`): + The request object. Delete job request. + name (:class:`str`): + Required. The resource name of the job to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.DeleteJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.delete_job, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + await rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + async def batch_delete_jobs( + self, + request: job_service.BatchDeleteJobsRequest = None, + *, + parent: str = None, + names: Sequence[str] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation_async.AsyncOperation: + r"""Begins executing a batch delete jobs operation. + + Args: + request (:class:`~.job_service.BatchDeleteJobsRequest`): + The request object. Request to delete a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + + The parent of all of the jobs specified in ``names`` + must match this field. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + names (:class:`Sequence[str]`): + The names of the jobs to delete. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + + A maximum of 200 jobs can be deleted in a batch. + This corresponds to the ``names`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation_async.AsyncOperation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchDeleteJobsResponse``: The + result of + [JobService.BatchDeleteJobs][google.cloud.talent.v4.JobService.BatchDeleteJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, names]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.BatchDeleteJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if names is not None: + request.names = names + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.batch_delete_jobs, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation_async.from_gapic( + response, + self._client._transport.operations_client, + job_service.BatchDeleteJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + async def list_jobs( + self, + request: job_service.ListJobsRequest = None, + *, + parent: str = None, + filter: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListJobsAsyncPager: + r"""Lists jobs by filter. + + Args: + request (:class:`~.job_service.ListJobsRequest`): + The request object. List jobs request. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + filter (:class:`str`): + Required. The filter string specifies the jobs to be + enumerated. + + Supported operator: =, AND + + The fields eligible for filtering are: + + - ``companyName`` (Required) + - ``requisitionId`` + - ``status`` Available values: OPEN, EXPIRED, ALL. + Defaults to OPEN if no value is specified. + + Sample Query: + + - companyName = + "projects/foo/tenants/bar/companies/baz" + - companyName = + "projects/foo/tenants/bar/companies/baz" AND + requisitionId = "req-1" + - companyName = + "projects/foo/tenants/bar/companies/baz" AND status = + "EXPIRED". + This corresponds to the ``filter`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListJobsAsyncPager: + List jobs response. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, filter]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = job_service.ListJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if filter is not None: + request.filter = filter + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_jobs, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListJobsAsyncPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + async def search_jobs( + self, + request: job_service.SearchJobsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job_service.SearchJobsResponse: + r"""Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs that the caller + has permission to search against. + + Args: + request (:class:`~.job_service.SearchJobsRequest`): + The request object. The Request body of the `SearchJobs` + call. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job_service.SearchJobsResponse: + Response for SearchJob method. + """ + # Create or coerce a protobuf request object. + + request = job_service.SearchJobsRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.search_jobs, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def search_jobs_for_alert( + self, + request: job_service.SearchJobsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job_service.SearchJobsResponse: + r"""Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This API call is intended for the use case of targeting passive + job seekers (for example, job seekers who have signed up to + receive email alerts about potential job opportunities), it has + different algorithmic adjustments that are designed to + specifically target passive job seekers. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs the caller has + permission to search against. + + Args: + request (:class:`~.job_service.SearchJobsRequest`): + The request object. The Request body of the `SearchJobs` + call. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job_service.SearchJobsResponse: + Response for SearchJob method. + """ + # Create or coerce a protobuf request object. + + request = job_service.SearchJobsRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.search_jobs_for_alert, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("JobServiceAsyncClient",) diff --git a/google/cloud/talent_v4/services/job_service/client.py b/google/cloud/talent_v4/services/job_service/client.py new file mode 100644 index 00000000..0bb4a4e9 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/client.py @@ -0,0 +1,1154 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore +from google.cloud.talent_v4.services.job_service import pagers +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import histogram +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + +from .transports.base import JobServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import JobServiceGrpcTransport +from .transports.grpc_asyncio import JobServiceGrpcAsyncIOTransport + + +class JobServiceClientMeta(type): + """Metaclass for the JobService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = OrderedDict() # type: Dict[str, Type[JobServiceTransport]] + _transport_registry["grpc"] = JobServiceGrpcTransport + _transport_registry["grpc_asyncio"] = JobServiceGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[JobServiceTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class JobServiceClient(metaclass=JobServiceClientMeta): + """A service handles job management, including job CRUD, + enumeration and search. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "jobs.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @staticmethod + def job_path(project: str, tenant: str, job: str,) -> str: + """Return a fully-qualified job string.""" + return "projects/{project}/tenants/{tenant}/jobs/{job}".format( + project=project, tenant=tenant, job=job, + ) + + @staticmethod + def parse_job_path(path: str) -> Dict[str, str]: + """Parse a job path into its component segments.""" + m = re.match( + r"^projects/(?P.+?)/tenants/(?P.+?)/jobs/(?P.+?)$", + path, + ) + return m.groupdict() if m else {} + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, JobServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the job service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.JobServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, JobServiceTransport): + # transport is a JobServiceTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def create_job( + self, + request: job_service.CreateJobRequest = None, + *, + parent: str = None, + job: gct_job.Job = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_job.Job: + r"""Creates a new job. + Typically, the job becomes searchable within 10 seconds, + but it may take up to 5 minutes. + + Args: + request (:class:`~.job_service.CreateJobRequest`): + The request object. Create job request. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + job (:class:`~.gct_job.Job`): + Required. The Job to be created. + This corresponds to the ``job`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, job]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.CreateJobRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.CreateJobRequest): + request = job_service.CreateJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if job is not None: + request.job = job + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.create_job] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def batch_create_jobs( + self, + request: job_service.BatchCreateJobsRequest = None, + *, + parent: str = None, + jobs: Sequence[job.Job] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation.Operation: + r"""Begins executing a batch create jobs operation. + + Args: + request (:class:`~.job_service.BatchCreateJobsRequest`): + The request object. Request to create a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + jobs (:class:`Sequence[~.job.Job]`): + Required. The jobs to be created. + A maximum of 200 jobs can be created in + a batch. + This corresponds to the ``jobs`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation.Operation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchCreateJobsResponse``: The + result of + [JobService.BatchCreateJobs][google.cloud.talent.v4.JobService.BatchCreateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, jobs]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.BatchCreateJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.BatchCreateJobsRequest): + request = job_service.BatchCreateJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if jobs is not None: + request.jobs = jobs + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.batch_create_jobs] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation.from_gapic( + response, + self._transport.operations_client, + job_service.BatchCreateJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + def get_job( + self, + request: job_service.GetJobRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job.Job: + r"""Retrieves the specified job, whose status is OPEN or + recently EXPIRED within the last 90 days. + + Args: + request (:class:`~.job_service.GetJobRequest`): + The request object. Get job request. + name (:class:`str`): + Required. The resource name of the job to retrieve. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.GetJobRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.GetJobRequest): + request = job_service.GetJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.get_job] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def update_job( + self, + request: job_service.UpdateJobRequest = None, + *, + job: gct_job.Job = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_job.Job: + r"""Updates specified job. + Typically, updated contents become visible in search + results within 10 seconds, but it may take up to 5 + minutes. + + Args: + request (:class:`~.job_service.UpdateJobRequest`): + The request object. Update job request. + job (:class:`~.gct_job.Job`): + Required. The Job to be updated. + This corresponds to the ``job`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask] + is provided, only the specified fields in + [job][google.cloud.talent.v4.UpdateJobRequest.job] are + updated. Otherwise all the fields are updated. + + A field mask to restrict the fields that are updated. + Only top level fields of + [Job][google.cloud.talent.v4.Job] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_job.Job: + A Job resource represents a job posting (also referred + to as a "job listing" or "job requisition"). A job + belongs to a [Company][google.cloud.talent.v4.Company], + which is the hiring entity responsible for the job. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([job, update_mask]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.UpdateJobRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.UpdateJobRequest): + request = job_service.UpdateJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if job is not None: + request.job = job + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.update_job] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("job.name", request.job.name),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def batch_update_jobs( + self, + request: job_service.BatchUpdateJobsRequest = None, + *, + parent: str = None, + jobs: Sequence[job.Job] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation.Operation: + r"""Begins executing a batch update jobs operation. + + Args: + request (:class:`~.job_service.BatchUpdateJobsRequest`): + The request object. Request to update a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + jobs (:class:`Sequence[~.job.Job]`): + Required. The jobs to be updated. + A maximum of 200 jobs can be updated in + a batch. + This corresponds to the ``jobs`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation.Operation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchUpdateJobsResponse``: The + result of + [JobService.BatchUpdateJobs][google.cloud.talent.v4.JobService.BatchUpdateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, jobs]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.BatchUpdateJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.BatchUpdateJobsRequest): + request = job_service.BatchUpdateJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if jobs is not None: + request.jobs = jobs + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.batch_update_jobs] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation.from_gapic( + response, + self._transport.operations_client, + job_service.BatchUpdateJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + def delete_job( + self, + request: job_service.DeleteJobRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes the specified job. + Typically, the job becomes unsearchable within 10 + seconds, but it may take up to 5 minutes. + + Args: + request (:class:`~.job_service.DeleteJobRequest`): + The request object. Delete job request. + name (:class:`str`): + Required. The resource name of the job to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.DeleteJobRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.DeleteJobRequest): + request = job_service.DeleteJobRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.delete_job] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + def batch_delete_jobs( + self, + request: job_service.BatchDeleteJobsRequest = None, + *, + parent: str = None, + names: Sequence[str] = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> operation.Operation: + r"""Begins executing a batch delete jobs operation. + + Args: + request (:class:`~.job_service.BatchDeleteJobsRequest`): + The request object. Request to delete a batch of jobs. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + + The parent of all of the jobs specified in ``names`` + must match this field. + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + names (:class:`Sequence[str]`): + The names of the jobs to delete. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + + A maximum of 200 jobs can be deleted in a batch. + This corresponds to the ``names`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.operation.Operation: + An object representing a long-running operation. + + The result type for the operation will be + :class:``~.job_service.BatchDeleteJobsResponse``: The + result of + [JobService.BatchDeleteJobs][google.cloud.talent.v4.JobService.BatchDeleteJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, names]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.BatchDeleteJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.BatchDeleteJobsRequest): + request = job_service.BatchDeleteJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if names is not None: + request.names = names + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.batch_delete_jobs] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Wrap the response in an operation future. + response = operation.from_gapic( + response, + self._transport.operations_client, + job_service.BatchDeleteJobsResponse, + metadata_type=common.BatchOperationMetadata, + ) + + # Done; return the response. + return response + + def list_jobs( + self, + request: job_service.ListJobsRequest = None, + *, + parent: str = None, + filter: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListJobsPager: + r"""Lists jobs by filter. + + Args: + request (:class:`~.job_service.ListJobsRequest`): + The request object. List jobs request. + parent (:class:`str`): + Required. The resource name of the tenant under which + the job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}". For + example, "projects/foo/tenants/bar". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + filter (:class:`str`): + Required. The filter string specifies the jobs to be + enumerated. + + Supported operator: =, AND + + The fields eligible for filtering are: + + - ``companyName`` (Required) + - ``requisitionId`` + - ``status`` Available values: OPEN, EXPIRED, ALL. + Defaults to OPEN if no value is specified. + + Sample Query: + + - companyName = + "projects/foo/tenants/bar/companies/baz" + - companyName = + "projects/foo/tenants/bar/companies/baz" AND + requisitionId = "req-1" + - companyName = + "projects/foo/tenants/bar/companies/baz" AND status = + "EXPIRED". + This corresponds to the ``filter`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListJobsPager: + List jobs response. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, filter]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.ListJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.ListJobsRequest): + request = job_service.ListJobsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if filter is not None: + request.filter = filter + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_jobs] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListJobsPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + def search_jobs( + self, + request: job_service.SearchJobsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job_service.SearchJobsResponse: + r"""Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs that the caller + has permission to search against. + + Args: + request (:class:`~.job_service.SearchJobsRequest`): + The request object. The Request body of the `SearchJobs` + call. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job_service.SearchJobsResponse: + Response for SearchJob method. + """ + # Create or coerce a protobuf request object. + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.SearchJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.SearchJobsRequest): + request = job_service.SearchJobsRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.search_jobs] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def search_jobs_for_alert( + self, + request: job_service.SearchJobsRequest = None, + *, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> job_service.SearchJobsResponse: + r"""Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This API call is intended for the use case of targeting passive + job seekers (for example, job seekers who have signed up to + receive email alerts about potential job opportunities), it has + different algorithmic adjustments that are designed to + specifically target passive job seekers. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs the caller has + permission to search against. + + Args: + request (:class:`~.job_service.SearchJobsRequest`): + The request object. The Request body of the `SearchJobs` + call. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.job_service.SearchJobsResponse: + Response for SearchJob method. + """ + # Create or coerce a protobuf request object. + + # Minor optimization to avoid making a copy if the user passes + # in a job_service.SearchJobsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, job_service.SearchJobsRequest): + request = job_service.SearchJobsRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.search_jobs_for_alert] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("JobServiceClient",) diff --git a/google/cloud/talent_v4/services/job_service/pagers.py b/google/cloud/talent_v4/services/job_service/pagers.py new file mode 100644 index 00000000..e759d8e3 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/pagers.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple + +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job_service + + +class ListJobsPager: + """A pager for iterating through ``list_jobs`` requests. + + This class thinly wraps an initial + :class:`~.job_service.ListJobsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``jobs`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListJobs`` requests and continue to iterate + through the ``jobs`` field on the + corresponding responses. + + All the usual :class:`~.job_service.ListJobsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., job_service.ListJobsResponse], + request: job_service.ListJobsRequest, + response: job_service.ListJobsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.job_service.ListJobsRequest`): + The initial request object. + response (:class:`~.job_service.ListJobsResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = job_service.ListJobsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterable[job_service.ListJobsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method(self._request, metadata=self._metadata) + yield self._response + + def __iter__(self) -> Iterable[job.Job]: + for page in self.pages: + yield from page.jobs + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListJobsAsyncPager: + """A pager for iterating through ``list_jobs`` requests. + + This class thinly wraps an initial + :class:`~.job_service.ListJobsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``jobs`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListJobs`` requests and continue to iterate + through the ``jobs`` field on the + corresponding responses. + + All the usual :class:`~.job_service.ListJobsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., Awaitable[job_service.ListJobsResponse]], + request: job_service.ListJobsRequest, + response: job_service.ListJobsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.job_service.ListJobsRequest`): + The initial request object. + response (:class:`~.job_service.ListJobsResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = job_service.ListJobsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages(self) -> AsyncIterable[job_service.ListJobsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method(self._request, metadata=self._metadata) + yield self._response + + def __aiter__(self) -> AsyncIterable[job.Job]: + async def async_generator(): + async for page in self.pages: + for response in page.jobs: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/google/cloud/talent_v4/services/job_service/transports/__init__.py b/google/cloud/talent_v4/services/job_service/transports/__init__.py new file mode 100644 index 00000000..ca4d929c --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import JobServiceTransport +from .grpc import JobServiceGrpcTransport +from .grpc_asyncio import JobServiceGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[JobServiceTransport]] +_transport_registry["grpc"] = JobServiceGrpcTransport +_transport_registry["grpc_asyncio"] = JobServiceGrpcAsyncIOTransport + + +__all__ = ( + "JobServiceTransport", + "JobServiceGrpcTransport", + "JobServiceGrpcAsyncIOTransport", +) diff --git a/google/cloud/talent_v4/services/job_service/transports/base.py b/google/cloud/talent_v4/services/job_service/transports/base.py new file mode 100644 index 00000000..c5256160 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/transports/base.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.api_core import operations_v1 # type: ignore +from google.auth import credentials # type: ignore + +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.longrunning import operations_pb2 as operations # type: ignore +from google.protobuf import empty_pb2 as empty # type: ignore + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class JobServiceTransport(abc.ABC): + """Abstract transport class for JobService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.create_job: gapic_v1.method.wrap_method( + self.create_job, default_timeout=30.0, client_info=client_info, + ), + self.batch_create_jobs: gapic_v1.method.wrap_method( + self.batch_create_jobs, default_timeout=30.0, client_info=client_info, + ), + self.get_job: gapic_v1.method.wrap_method( + self.get_job, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.update_job: gapic_v1.method.wrap_method( + self.update_job, default_timeout=30.0, client_info=client_info, + ), + self.batch_update_jobs: gapic_v1.method.wrap_method( + self.batch_update_jobs, default_timeout=30.0, client_info=client_info, + ), + self.delete_job: gapic_v1.method.wrap_method( + self.delete_job, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.batch_delete_jobs: gapic_v1.method.wrap_method( + self.batch_delete_jobs, default_timeout=30.0, client_info=client_info, + ), + self.list_jobs: gapic_v1.method.wrap_method( + self.list_jobs, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.search_jobs: gapic_v1.method.wrap_method( + self.search_jobs, default_timeout=30.0, client_info=client_info, + ), + self.search_jobs_for_alert: gapic_v1.method.wrap_method( + self.search_jobs_for_alert, + default_timeout=30.0, + client_info=client_info, + ), + } + + @property + def operations_client(self) -> operations_v1.OperationsClient: + """Return the client designed to process long-running operations.""" + raise NotImplementedError() + + @property + def create_job( + self, + ) -> typing.Callable[ + [job_service.CreateJobRequest], + typing.Union[gct_job.Job, typing.Awaitable[gct_job.Job]], + ]: + raise NotImplementedError() + + @property + def batch_create_jobs( + self, + ) -> typing.Callable[ + [job_service.BatchCreateJobsRequest], + typing.Union[operations.Operation, typing.Awaitable[operations.Operation]], + ]: + raise NotImplementedError() + + @property + def get_job( + self, + ) -> typing.Callable[ + [job_service.GetJobRequest], typing.Union[job.Job, typing.Awaitable[job.Job]] + ]: + raise NotImplementedError() + + @property + def update_job( + self, + ) -> typing.Callable[ + [job_service.UpdateJobRequest], + typing.Union[gct_job.Job, typing.Awaitable[gct_job.Job]], + ]: + raise NotImplementedError() + + @property + def batch_update_jobs( + self, + ) -> typing.Callable[ + [job_service.BatchUpdateJobsRequest], + typing.Union[operations.Operation, typing.Awaitable[operations.Operation]], + ]: + raise NotImplementedError() + + @property + def delete_job( + self, + ) -> typing.Callable[ + [job_service.DeleteJobRequest], + typing.Union[empty.Empty, typing.Awaitable[empty.Empty]], + ]: + raise NotImplementedError() + + @property + def batch_delete_jobs( + self, + ) -> typing.Callable[ + [job_service.BatchDeleteJobsRequest], + typing.Union[operations.Operation, typing.Awaitable[operations.Operation]], + ]: + raise NotImplementedError() + + @property + def list_jobs( + self, + ) -> typing.Callable[ + [job_service.ListJobsRequest], + typing.Union[ + job_service.ListJobsResponse, typing.Awaitable[job_service.ListJobsResponse] + ], + ]: + raise NotImplementedError() + + @property + def search_jobs( + self, + ) -> typing.Callable[ + [job_service.SearchJobsRequest], + typing.Union[ + job_service.SearchJobsResponse, + typing.Awaitable[job_service.SearchJobsResponse], + ], + ]: + raise NotImplementedError() + + @property + def search_jobs_for_alert( + self, + ) -> typing.Callable[ + [job_service.SearchJobsRequest], + typing.Union[ + job_service.SearchJobsResponse, + typing.Awaitable[job_service.SearchJobsResponse], + ], + ]: + raise NotImplementedError() + + +__all__ = ("JobServiceTransport",) diff --git a/google/cloud/talent_v4/services/job_service/transports/grpc.py b/google/cloud/talent_v4/services/job_service/transports/grpc.py new file mode 100644 index 00000000..a8a82f6a --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/transports/grpc.py @@ -0,0 +1,533 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import operations_v1 # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.longrunning import operations_pb2 as operations # type: ignore +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import JobServiceTransport, DEFAULT_CLIENT_INFO + + +class JobServiceGrpcTransport(JobServiceTransport): + """gRPC backend transport for JobService. + + A service handles job management, including job CRUD, + enumeration and search. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optionsl[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def operations_client(self) -> operations_v1.OperationsClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Sanity check: Only create a new client if we do not already have one. + if "operations_client" not in self.__dict__: + self.__dict__["operations_client"] = operations_v1.OperationsClient( + self.grpc_channel + ) + + # Return the client from cache. + return self.__dict__["operations_client"] + + @property + def create_job(self) -> Callable[[job_service.CreateJobRequest], gct_job.Job]: + r"""Return a callable for the create job method over gRPC. + + Creates a new job. + Typically, the job becomes searchable within 10 seconds, + but it may take up to 5 minutes. + + Returns: + Callable[[~.CreateJobRequest], + ~.Job]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_job" not in self._stubs: + self._stubs["create_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/CreateJob", + request_serializer=job_service.CreateJobRequest.serialize, + response_deserializer=gct_job.Job.deserialize, + ) + return self._stubs["create_job"] + + @property + def batch_create_jobs( + self, + ) -> Callable[[job_service.BatchCreateJobsRequest], operations.Operation]: + r"""Return a callable for the batch create jobs method over gRPC. + + Begins executing a batch create jobs operation. + + Returns: + Callable[[~.BatchCreateJobsRequest], + ~.Operation]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_create_jobs" not in self._stubs: + self._stubs["batch_create_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchCreateJobs", + request_serializer=job_service.BatchCreateJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_create_jobs"] + + @property + def get_job(self) -> Callable[[job_service.GetJobRequest], job.Job]: + r"""Return a callable for the get job method over gRPC. + + Retrieves the specified job, whose status is OPEN or + recently EXPIRED within the last 90 days. + + Returns: + Callable[[~.GetJobRequest], + ~.Job]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_job" not in self._stubs: + self._stubs["get_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/GetJob", + request_serializer=job_service.GetJobRequest.serialize, + response_deserializer=job.Job.deserialize, + ) + return self._stubs["get_job"] + + @property + def update_job(self) -> Callable[[job_service.UpdateJobRequest], gct_job.Job]: + r"""Return a callable for the update job method over gRPC. + + Updates specified job. + Typically, updated contents become visible in search + results within 10 seconds, but it may take up to 5 + minutes. + + Returns: + Callable[[~.UpdateJobRequest], + ~.Job]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_job" not in self._stubs: + self._stubs["update_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/UpdateJob", + request_serializer=job_service.UpdateJobRequest.serialize, + response_deserializer=gct_job.Job.deserialize, + ) + return self._stubs["update_job"] + + @property + def batch_update_jobs( + self, + ) -> Callable[[job_service.BatchUpdateJobsRequest], operations.Operation]: + r"""Return a callable for the batch update jobs method over gRPC. + + Begins executing a batch update jobs operation. + + Returns: + Callable[[~.BatchUpdateJobsRequest], + ~.Operation]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_update_jobs" not in self._stubs: + self._stubs["batch_update_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchUpdateJobs", + request_serializer=job_service.BatchUpdateJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_update_jobs"] + + @property + def delete_job(self) -> Callable[[job_service.DeleteJobRequest], empty.Empty]: + r"""Return a callable for the delete job method over gRPC. + + Deletes the specified job. + Typically, the job becomes unsearchable within 10 + seconds, but it may take up to 5 minutes. + + Returns: + Callable[[~.DeleteJobRequest], + ~.Empty]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_job" not in self._stubs: + self._stubs["delete_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/DeleteJob", + request_serializer=job_service.DeleteJobRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_job"] + + @property + def batch_delete_jobs( + self, + ) -> Callable[[job_service.BatchDeleteJobsRequest], operations.Operation]: + r"""Return a callable for the batch delete jobs method over gRPC. + + Begins executing a batch delete jobs operation. + + Returns: + Callable[[~.BatchDeleteJobsRequest], + ~.Operation]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_delete_jobs" not in self._stubs: + self._stubs["batch_delete_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchDeleteJobs", + request_serializer=job_service.BatchDeleteJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_delete_jobs"] + + @property + def list_jobs( + self, + ) -> Callable[[job_service.ListJobsRequest], job_service.ListJobsResponse]: + r"""Return a callable for the list jobs method over gRPC. + + Lists jobs by filter. + + Returns: + Callable[[~.ListJobsRequest], + ~.ListJobsResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_jobs" not in self._stubs: + self._stubs["list_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/ListJobs", + request_serializer=job_service.ListJobsRequest.serialize, + response_deserializer=job_service.ListJobsResponse.deserialize, + ) + return self._stubs["list_jobs"] + + @property + def search_jobs( + self, + ) -> Callable[[job_service.SearchJobsRequest], job_service.SearchJobsResponse]: + r"""Return a callable for the search jobs method over gRPC. + + Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs that the caller + has permission to search against. + + Returns: + Callable[[~.SearchJobsRequest], + ~.SearchJobsResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_jobs" not in self._stubs: + self._stubs["search_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/SearchJobs", + request_serializer=job_service.SearchJobsRequest.serialize, + response_deserializer=job_service.SearchJobsResponse.deserialize, + ) + return self._stubs["search_jobs"] + + @property + def search_jobs_for_alert( + self, + ) -> Callable[[job_service.SearchJobsRequest], job_service.SearchJobsResponse]: + r"""Return a callable for the search jobs for alert method over gRPC. + + Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This API call is intended for the use case of targeting passive + job seekers (for example, job seekers who have signed up to + receive email alerts about potential job opportunities), it has + different algorithmic adjustments that are designed to + specifically target passive job seekers. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs the caller has + permission to search against. + + Returns: + Callable[[~.SearchJobsRequest], + ~.SearchJobsResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_jobs_for_alert" not in self._stubs: + self._stubs["search_jobs_for_alert"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/SearchJobsForAlert", + request_serializer=job_service.SearchJobsRequest.serialize, + response_deserializer=job_service.SearchJobsResponse.deserialize, + ) + return self._stubs["search_jobs_for_alert"] + + +__all__ = ("JobServiceGrpcTransport",) diff --git a/google/cloud/talent_v4/services/job_service/transports/grpc_asyncio.py b/google/cloud/talent_v4/services/job_service/transports/grpc_asyncio.py new file mode 100644 index 00000000..90ccdde9 --- /dev/null +++ b/google/cloud/talent_v4/services/job_service/transports/grpc_asyncio.py @@ -0,0 +1,551 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google.api_core import operations_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.longrunning import operations_pb2 as operations # type: ignore +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import JobServiceTransport, DEFAULT_CLIENT_INFO +from .grpc import JobServiceGrpcTransport + + +class JobServiceGrpcAsyncIOTransport(JobServiceTransport): + """gRPC AsyncIO backend transport for JobService. + + A service handles job management, including job CRUD, + enumeration and search. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def operations_client(self) -> operations_v1.OperationsAsyncClient: + """Create the client designed to process long-running operations. + + This property caches on the instance; repeated calls return the same + client. + """ + # Sanity check: Only create a new client if we do not already have one. + if "operations_client" not in self.__dict__: + self.__dict__["operations_client"] = operations_v1.OperationsAsyncClient( + self.grpc_channel + ) + + # Return the client from cache. + return self.__dict__["operations_client"] + + @property + def create_job( + self, + ) -> Callable[[job_service.CreateJobRequest], Awaitable[gct_job.Job]]: + r"""Return a callable for the create job method over gRPC. + + Creates a new job. + Typically, the job becomes searchable within 10 seconds, + but it may take up to 5 minutes. + + Returns: + Callable[[~.CreateJobRequest], + Awaitable[~.Job]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_job" not in self._stubs: + self._stubs["create_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/CreateJob", + request_serializer=job_service.CreateJobRequest.serialize, + response_deserializer=gct_job.Job.deserialize, + ) + return self._stubs["create_job"] + + @property + def batch_create_jobs( + self, + ) -> Callable[ + [job_service.BatchCreateJobsRequest], Awaitable[operations.Operation] + ]: + r"""Return a callable for the batch create jobs method over gRPC. + + Begins executing a batch create jobs operation. + + Returns: + Callable[[~.BatchCreateJobsRequest], + Awaitable[~.Operation]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_create_jobs" not in self._stubs: + self._stubs["batch_create_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchCreateJobs", + request_serializer=job_service.BatchCreateJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_create_jobs"] + + @property + def get_job(self) -> Callable[[job_service.GetJobRequest], Awaitable[job.Job]]: + r"""Return a callable for the get job method over gRPC. + + Retrieves the specified job, whose status is OPEN or + recently EXPIRED within the last 90 days. + + Returns: + Callable[[~.GetJobRequest], + Awaitable[~.Job]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_job" not in self._stubs: + self._stubs["get_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/GetJob", + request_serializer=job_service.GetJobRequest.serialize, + response_deserializer=job.Job.deserialize, + ) + return self._stubs["get_job"] + + @property + def update_job( + self, + ) -> Callable[[job_service.UpdateJobRequest], Awaitable[gct_job.Job]]: + r"""Return a callable for the update job method over gRPC. + + Updates specified job. + Typically, updated contents become visible in search + results within 10 seconds, but it may take up to 5 + minutes. + + Returns: + Callable[[~.UpdateJobRequest], + Awaitable[~.Job]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_job" not in self._stubs: + self._stubs["update_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/UpdateJob", + request_serializer=job_service.UpdateJobRequest.serialize, + response_deserializer=gct_job.Job.deserialize, + ) + return self._stubs["update_job"] + + @property + def batch_update_jobs( + self, + ) -> Callable[ + [job_service.BatchUpdateJobsRequest], Awaitable[operations.Operation] + ]: + r"""Return a callable for the batch update jobs method over gRPC. + + Begins executing a batch update jobs operation. + + Returns: + Callable[[~.BatchUpdateJobsRequest], + Awaitable[~.Operation]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_update_jobs" not in self._stubs: + self._stubs["batch_update_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchUpdateJobs", + request_serializer=job_service.BatchUpdateJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_update_jobs"] + + @property + def delete_job( + self, + ) -> Callable[[job_service.DeleteJobRequest], Awaitable[empty.Empty]]: + r"""Return a callable for the delete job method over gRPC. + + Deletes the specified job. + Typically, the job becomes unsearchable within 10 + seconds, but it may take up to 5 minutes. + + Returns: + Callable[[~.DeleteJobRequest], + Awaitable[~.Empty]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_job" not in self._stubs: + self._stubs["delete_job"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/DeleteJob", + request_serializer=job_service.DeleteJobRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_job"] + + @property + def batch_delete_jobs( + self, + ) -> Callable[ + [job_service.BatchDeleteJobsRequest], Awaitable[operations.Operation] + ]: + r"""Return a callable for the batch delete jobs method over gRPC. + + Begins executing a batch delete jobs operation. + + Returns: + Callable[[~.BatchDeleteJobsRequest], + Awaitable[~.Operation]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "batch_delete_jobs" not in self._stubs: + self._stubs["batch_delete_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/BatchDeleteJobs", + request_serializer=job_service.BatchDeleteJobsRequest.serialize, + response_deserializer=operations.Operation.FromString, + ) + return self._stubs["batch_delete_jobs"] + + @property + def list_jobs( + self, + ) -> Callable[ + [job_service.ListJobsRequest], Awaitable[job_service.ListJobsResponse] + ]: + r"""Return a callable for the list jobs method over gRPC. + + Lists jobs by filter. + + Returns: + Callable[[~.ListJobsRequest], + Awaitable[~.ListJobsResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_jobs" not in self._stubs: + self._stubs["list_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/ListJobs", + request_serializer=job_service.ListJobsRequest.serialize, + response_deserializer=job_service.ListJobsResponse.deserialize, + ) + return self._stubs["list_jobs"] + + @property + def search_jobs( + self, + ) -> Callable[ + [job_service.SearchJobsRequest], Awaitable[job_service.SearchJobsResponse] + ]: + r"""Return a callable for the search jobs method over gRPC. + + Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs that the caller + has permission to search against. + + Returns: + Callable[[~.SearchJobsRequest], + Awaitable[~.SearchJobsResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_jobs" not in self._stubs: + self._stubs["search_jobs"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/SearchJobs", + request_serializer=job_service.SearchJobsRequest.serialize, + response_deserializer=job_service.SearchJobsResponse.deserialize, + ) + return self._stubs["search_jobs"] + + @property + def search_jobs_for_alert( + self, + ) -> Callable[ + [job_service.SearchJobsRequest], Awaitable[job_service.SearchJobsResponse] + ]: + r"""Return a callable for the search jobs for alert method over gRPC. + + Searches for jobs using the provided + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + This API call is intended for the use case of targeting passive + job seekers (for example, job seekers who have signed up to + receive email alerts about potential job opportunities), it has + different algorithmic adjustments that are designed to + specifically target passive job seekers. + + This call constrains the + [visibility][google.cloud.talent.v4.Job.visibility] of jobs + present in the database, and only returns jobs the caller has + permission to search against. + + Returns: + Callable[[~.SearchJobsRequest], + Awaitable[~.SearchJobsResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_jobs_for_alert" not in self._stubs: + self._stubs["search_jobs_for_alert"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.JobService/SearchJobsForAlert", + request_serializer=job_service.SearchJobsRequest.serialize, + response_deserializer=job_service.SearchJobsResponse.deserialize, + ) + return self._stubs["search_jobs_for_alert"] + + +__all__ = ("JobServiceGrpcAsyncIOTransport",) diff --git a/google/cloud/talent_v4/services/tenant_service/__init__.py b/google/cloud/talent_v4/services/tenant_service/__init__.py new file mode 100644 index 00000000..c3e2ad3b --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .client import TenantServiceClient +from .async_client import TenantServiceAsyncClient + +__all__ = ( + "TenantServiceClient", + "TenantServiceAsyncClient", +) diff --git a/google/cloud/talent_v4/services/tenant_service/async_client.py b/google/cloud/talent_v4/services/tenant_service/async_client.py new file mode 100644 index 00000000..5b57b42d --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/async_client.py @@ -0,0 +1,555 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +import functools +import re +from typing import Dict, Sequence, Tuple, Type, Union +import pkg_resources + +import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.services.tenant_service import pagers +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + +from .transports.base import TenantServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc_asyncio import TenantServiceGrpcAsyncIOTransport +from .client import TenantServiceClient + + +class TenantServiceAsyncClient: + """A service that handles tenant management, including CRUD and + enumeration. + """ + + _client: TenantServiceClient + + DEFAULT_ENDPOINT = TenantServiceClient.DEFAULT_ENDPOINT + DEFAULT_MTLS_ENDPOINT = TenantServiceClient.DEFAULT_MTLS_ENDPOINT + + tenant_path = staticmethod(TenantServiceClient.tenant_path) + parse_tenant_path = staticmethod(TenantServiceClient.parse_tenant_path) + + from_service_account_file = TenantServiceClient.from_service_account_file + from_service_account_json = from_service_account_file + + get_transport_class = functools.partial( + type(TenantServiceClient).get_transport_class, type(TenantServiceClient) + ) + + def __init__( + self, + *, + credentials: credentials.Credentials = None, + transport: Union[str, TenantServiceTransport] = "grpc_asyncio", + client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the tenant service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.TenantServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + 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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + """ + + self._client = TenantServiceClient( + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, + ) + + async def create_tenant( + self, + request: tenant_service.CreateTenantRequest = None, + *, + parent: str = None, + tenant: gct_tenant.Tenant = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_tenant.Tenant: + r"""Creates a new tenant entity. + + Args: + request (:class:`~.tenant_service.CreateTenantRequest`): + The request object. The Request of the CreateTenant + method. + parent (:class:`str`): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + tenant (:class:`~.gct_tenant.Tenant`): + Required. The tenant to be created. + This corresponds to the ``tenant`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent, tenant]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = tenant_service.CreateTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if tenant is not None: + request.tenant = tenant + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.create_tenant, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def get_tenant( + self, + request: tenant_service.GetTenantRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> tenant.Tenant: + r"""Retrieves specified tenant. + + Args: + request (:class:`~.tenant_service.GetTenantRequest`): + The request object. Request for getting a tenant by + name. + name (:class:`str`): + Required. The resource name of the tenant to be + retrieved. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = tenant_service.GetTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.get_tenant, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def update_tenant( + self, + request: tenant_service.UpdateTenantRequest = None, + *, + tenant: gct_tenant.Tenant = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_tenant.Tenant: + r"""Updates specified tenant. + + Args: + request (:class:`~.tenant_service.UpdateTenantRequest`): + The request object. Request for updating a specified + tenant. + tenant (:class:`~.gct_tenant.Tenant`): + Required. The tenant resource to + replace the current resource in the + system. + This corresponds to the ``tenant`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateTenantRequest.update_mask] + is provided, only the specified fields in + [tenant][google.cloud.talent.v4.UpdateTenantRequest.tenant] + are updated. Otherwise all the fields are updated. + + A field mask to specify the tenant fields to be updated. + Only top level fields of + [Tenant][google.cloud.talent.v4.Tenant] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([tenant, update_mask]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = tenant_service.UpdateTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if tenant is not None: + request.tenant = tenant + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.update_tenant, + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("tenant.name", request.tenant.name),) + ), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + async def delete_tenant( + self, + request: tenant_service.DeleteTenantRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes specified tenant. + + Args: + request (:class:`~.tenant_service.DeleteTenantRequest`): + The request object. Request to delete a tenant. + name (:class:`str`): + Required. The resource name of the tenant to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([name]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = tenant_service.DeleteTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.delete_tenant, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + await rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + async def list_tenants( + self, + request: tenant_service.ListTenantsRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListTenantsAsyncPager: + r"""Lists all tenants associated with the project. + + Args: + request (:class:`~.tenant_service.ListTenantsRequest`): + The request object. List tenants for which the client + has ACL visibility. + parent (:class:`str`): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListTenantsAsyncPager: + The List tenants response object. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + if request is not None and any([parent]): + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + request = tenant_service.ListTenantsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.list_tenants, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__aiter__` convenience method. + response = pagers.ListTenantsAsyncPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("TenantServiceAsyncClient",) diff --git a/google/cloud/talent_v4/services/tenant_service/client.py b/google/cloud/talent_v4/services/tenant_service/client.py new file mode 100644 index 00000000..09b14ba1 --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/client.py @@ -0,0 +1,697 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from distutils import util +import os +import re +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union +import pkg_resources + +from google.api_core import client_options as client_options_lib # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore +from google.auth.exceptions import MutualTLSChannelError # type: ignore +from google.oauth2 import service_account # type: ignore + +from google.cloud.talent_v4.services.tenant_service import pagers +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + +from .transports.base import TenantServiceTransport, DEFAULT_CLIENT_INFO +from .transports.grpc import TenantServiceGrpcTransport +from .transports.grpc_asyncio import TenantServiceGrpcAsyncIOTransport + + +class TenantServiceClientMeta(type): + """Metaclass for the TenantService client. + + This provides class-level methods for building and retrieving + support objects (e.g. transport) without polluting the client instance + objects. + """ + + _transport_registry = OrderedDict() # type: Dict[str, Type[TenantServiceTransport]] + _transport_registry["grpc"] = TenantServiceGrpcTransport + _transport_registry["grpc_asyncio"] = TenantServiceGrpcAsyncIOTransport + + def get_transport_class(cls, label: str = None,) -> Type[TenantServiceTransport]: + """Return an appropriate transport class. + + Args: + label: The name of the desired transport. If none is + provided, then the first transport in the registry is used. + + Returns: + The transport class to use. + """ + # If a specific transport is requested, return that one. + if label: + return cls._transport_registry[label] + + # No transport is requested; return the default (that is, the first one + # in the dictionary). + return next(iter(cls._transport_registry.values())) + + +class TenantServiceClient(metaclass=TenantServiceClientMeta): + """A service that handles tenant management, including CRUD and + enumeration. + """ + + @staticmethod + def _get_default_mtls_endpoint(api_endpoint): + """Convert api endpoint to mTLS endpoint. + Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to + "*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively. + Args: + api_endpoint (Optional[str]): the api endpoint to convert. + Returns: + str: converted mTLS api endpoint. + """ + if not api_endpoint: + return api_endpoint + + mtls_endpoint_re = re.compile( + r"(?P[^.]+)(?P\.mtls)?(?P\.sandbox)?(?P\.googleapis\.com)?" + ) + + m = mtls_endpoint_re.match(api_endpoint) + name, mtls, sandbox, googledomain = m.groups() + if mtls or not googledomain: + return api_endpoint + + if sandbox: + return api_endpoint.replace( + "sandbox.googleapis.com", "mtls.sandbox.googleapis.com" + ) + + return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com") + + DEFAULT_ENDPOINT = "jobs.googleapis.com" + DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore + DEFAULT_ENDPOINT + ) + + @classmethod + def from_service_account_file(cls, filename: str, *args, **kwargs): + """Creates an instance of this client using the provided credentials + file. + + Args: + filename (str): The path to the service account private key json + file. + args: Additional arguments to pass to the constructor. + kwargs: Additional arguments to pass to the constructor. + + Returns: + {@api.name}: The constructed client. + """ + credentials = service_account.Credentials.from_service_account_file(filename) + kwargs["credentials"] = credentials + return cls(*args, **kwargs) + + from_service_account_json = from_service_account_file + + @staticmethod + def tenant_path(project: str, tenant: str,) -> str: + """Return a fully-qualified tenant string.""" + return "projects/{project}/tenants/{tenant}".format( + project=project, tenant=tenant, + ) + + @staticmethod + def parse_tenant_path(path: str) -> Dict[str, str]: + """Parse a tenant path into its component segments.""" + m = re.match(r"^projects/(?P.+?)/tenants/(?P.+?)$", path) + return m.groupdict() if m else {} + + def __init__( + self, + *, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TenantServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the tenant service client. + + Args: + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + 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, ~.TenantServiceTransport]): The + transport to use. If set to None, a transport is chosen + automatically. + client_options (client_options_lib.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 + environment variable can also be used to override the endpoint: + "always" (always use the default mTLS endpoint), "never" (always + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + """ + if isinstance(client_options, dict): + client_options = client_options_lib.from_dict(client_options) + if client_options is None: + client_options = client_options_lib.ClientOptions() + + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") + if use_mtls_env == "never": + api_endpoint = self.DEFAULT_ENDPOINT + elif use_mtls_env == "always": + api_endpoint = self.DEFAULT_MTLS_ENDPOINT + elif use_mtls_env == "auto": + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT + ) + else: + raise MutualTLSChannelError( + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" + ) + + # Save or instantiate the transport. + # Ordinarily, we provide the transport, but allowing a custom transport + # instance provides an extensibility point for unusual situations. + if isinstance(transport, TenantServiceTransport): + # transport is a TenantServiceTransport instance. + if credentials or client_options.credentials_file: + raise ValueError( + "When providing a transport instance, " + "provide its credentials directly." + ) + if client_options.scopes: + raise ValueError( + "When providing a transport instance, " + "provide its scopes directly." + ) + self._transport = transport + else: + Transport = type(self).get_transport_class(transport) + self._transport = Transport( + credentials=credentials, + credentials_file=client_options.credentials_file, + host=api_endpoint, + scopes=client_options.scopes, + ssl_channel_credentials=ssl_credentials, + quota_project_id=client_options.quota_project_id, + client_info=client_info, + ) + + def create_tenant( + self, + request: tenant_service.CreateTenantRequest = None, + *, + parent: str = None, + tenant: gct_tenant.Tenant = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_tenant.Tenant: + r"""Creates a new tenant entity. + + Args: + request (:class:`~.tenant_service.CreateTenantRequest`): + The request object. The Request of the CreateTenant + method. + parent (:class:`str`): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + tenant (:class:`~.gct_tenant.Tenant`): + Required. The tenant to be created. + This corresponds to the ``tenant`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent, tenant]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a tenant_service.CreateTenantRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, tenant_service.CreateTenantRequest): + request = tenant_service.CreateTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + if tenant is not None: + request.tenant = tenant + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.create_tenant] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def get_tenant( + self, + request: tenant_service.GetTenantRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> tenant.Tenant: + r"""Retrieves specified tenant. + + Args: + request (:class:`~.tenant_service.GetTenantRequest`): + The request object. Request for getting a tenant by + name. + name (:class:`str`): + Required. The resource name of the tenant to be + retrieved. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a tenant_service.GetTenantRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, tenant_service.GetTenantRequest): + request = tenant_service.GetTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.get_tenant] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def update_tenant( + self, + request: tenant_service.UpdateTenantRequest = None, + *, + tenant: gct_tenant.Tenant = None, + update_mask: field_mask.FieldMask = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> gct_tenant.Tenant: + r"""Updates specified tenant. + + Args: + request (:class:`~.tenant_service.UpdateTenantRequest`): + The request object. Request for updating a specified + tenant. + tenant (:class:`~.gct_tenant.Tenant`): + Required. The tenant resource to + replace the current resource in the + system. + This corresponds to the ``tenant`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + update_mask (:class:`~.field_mask.FieldMask`): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateTenantRequest.update_mask] + is provided, only the specified fields in + [tenant][google.cloud.talent.v4.UpdateTenantRequest.tenant] + are updated. Otherwise all the fields are updated. + + A field mask to specify the tenant fields to be updated. + Only top level fields of + [Tenant][google.cloud.talent.v4.Tenant] are supported. + This corresponds to the ``update_mask`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.gct_tenant.Tenant: + A Tenant resource represents a tenant + in the service. A tenant is a group or + entity that shares common access with + specific privileges for resources like + jobs. Customer may create multiple + tenants to provide data isolation for + different groups. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([tenant, update_mask]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a tenant_service.UpdateTenantRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, tenant_service.UpdateTenantRequest): + request = tenant_service.UpdateTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if tenant is not None: + request.tenant = tenant + if update_mask is not None: + request.update_mask = update_mask + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.update_tenant] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata( + (("tenant.name", request.tenant.name),) + ), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # Done; return the response. + return response + + def delete_tenant( + self, + request: tenant_service.DeleteTenantRequest = None, + *, + name: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> None: + r"""Deletes specified tenant. + + Args: + request (:class:`~.tenant_service.DeleteTenantRequest`): + The request object. Request to delete a tenant. + name (:class:`str`): + Required. The resource name of the tenant to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}", for + example, "projects/foo/tenants/bar". + This corresponds to the ``name`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([name]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a tenant_service.DeleteTenantRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, tenant_service.DeleteTenantRequest): + request = tenant_service.DeleteTenantRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if name is not None: + request.name = name + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.delete_tenant] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("name", request.name),)), + ) + + # Send the request. + rpc( + request, retry=retry, timeout=timeout, metadata=metadata, + ) + + def list_tenants( + self, + request: tenant_service.ListTenantsRequest = None, + *, + parent: str = None, + retry: retries.Retry = gapic_v1.method.DEFAULT, + timeout: float = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> pagers.ListTenantsPager: + r"""Lists all tenants associated with the project. + + Args: + request (:class:`~.tenant_service.ListTenantsRequest`): + The request object. List tenants for which the client + has ACL visibility. + parent (:class:`str`): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + This corresponds to the ``parent`` field + on the ``request`` instance; if ``request`` is provided, this + should not be set. + + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.pagers.ListTenantsPager: + The List tenants response object. + Iterating over this object will yield + results and resolve additional pages + automatically. + + """ + # Create or coerce a protobuf request object. + # Sanity check: If we got a request object, we should *not* have + # gotten any keyword arguments that map to the request. + has_flattened_params = any([parent]) + if request is not None and has_flattened_params: + raise ValueError( + "If the `request` argument is set, then none of " + "the individual field arguments should be set." + ) + + # Minor optimization to avoid making a copy if the user passes + # in a tenant_service.ListTenantsRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, tenant_service.ListTenantsRequest): + request = tenant_service.ListTenantsRequest(request) + + # If we have keyword arguments corresponding to fields on the + # request, apply these. + + if parent is not None: + request.parent = parent + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.list_tenants] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc(request, retry=retry, timeout=timeout, metadata=metadata,) + + # This method is paged; wrap the response in a pager, which provides + # an `__iter__` convenience method. + response = pagers.ListTenantsPager( + method=rpc, request=request, response=response, metadata=metadata, + ) + + # Done; return the response. + return response + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +__all__ = ("TenantServiceClient",) diff --git a/google/cloud/talent_v4/services/tenant_service/pagers.py b/google/cloud/talent_v4/services/tenant_service/pagers.py new file mode 100644 index 00000000..d671fdb7 --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/pagers.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from typing import Any, AsyncIterable, Awaitable, Callable, Iterable, Sequence, Tuple + +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant_service + + +class ListTenantsPager: + """A pager for iterating through ``list_tenants`` requests. + + This class thinly wraps an initial + :class:`~.tenant_service.ListTenantsResponse` object, and + provides an ``__iter__`` method to iterate through its + ``tenants`` field. + + If there are more pages, the ``__iter__`` method will make additional + ``ListTenants`` requests and continue to iterate + through the ``tenants`` field on the + corresponding responses. + + All the usual :class:`~.tenant_service.ListTenantsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., tenant_service.ListTenantsResponse], + request: tenant_service.ListTenantsRequest, + response: tenant_service.ListTenantsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.tenant_service.ListTenantsRequest`): + The initial request object. + response (:class:`~.tenant_service.ListTenantsResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = tenant_service.ListTenantsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + def pages(self) -> Iterable[tenant_service.ListTenantsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = self._method(self._request, metadata=self._metadata) + yield self._response + + def __iter__(self) -> Iterable[tenant.Tenant]: + for page in self.pages: + yield from page.tenants + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) + + +class ListTenantsAsyncPager: + """A pager for iterating through ``list_tenants`` requests. + + This class thinly wraps an initial + :class:`~.tenant_service.ListTenantsResponse` object, and + provides an ``__aiter__`` method to iterate through its + ``tenants`` field. + + If there are more pages, the ``__aiter__`` method will make additional + ``ListTenants`` requests and continue to iterate + through the ``tenants`` field on the + corresponding responses. + + All the usual :class:`~.tenant_service.ListTenantsResponse` + attributes are available on the pager. If multiple requests are made, only + the most recent response is retained, and thus used for attribute lookup. + """ + + def __init__( + self, + method: Callable[..., Awaitable[tenant_service.ListTenantsResponse]], + request: tenant_service.ListTenantsRequest, + response: tenant_service.ListTenantsResponse, + *, + metadata: Sequence[Tuple[str, str]] = () + ): + """Instantiate the pager. + + Args: + method (Callable): The method that was originally called, and + which instantiated this pager. + request (:class:`~.tenant_service.ListTenantsRequest`): + The initial request object. + response (:class:`~.tenant_service.ListTenantsResponse`): + The initial response object. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + """ + self._method = method + self._request = tenant_service.ListTenantsRequest(request) + self._response = response + self._metadata = metadata + + def __getattr__(self, name: str) -> Any: + return getattr(self._response, name) + + @property + async def pages(self) -> AsyncIterable[tenant_service.ListTenantsResponse]: + yield self._response + while self._response.next_page_token: + self._request.page_token = self._response.next_page_token + self._response = await self._method(self._request, metadata=self._metadata) + yield self._response + + def __aiter__(self) -> AsyncIterable[tenant.Tenant]: + async def async_generator(): + async for page in self.pages: + for response in page.tenants: + yield response + + return async_generator() + + def __repr__(self) -> str: + return "{0}<{1!r}>".format(self.__class__.__name__, self._response) diff --git a/google/cloud/talent_v4/services/tenant_service/transports/__init__.py b/google/cloud/talent_v4/services/tenant_service/transports/__init__.py new file mode 100644 index 00000000..49abfaa7 --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/transports/__init__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from collections import OrderedDict +from typing import Dict, Type + +from .base import TenantServiceTransport +from .grpc import TenantServiceGrpcTransport +from .grpc_asyncio import TenantServiceGrpcAsyncIOTransport + + +# Compile a registry of transports. +_transport_registry = OrderedDict() # type: Dict[str, Type[TenantServiceTransport]] +_transport_registry["grpc"] = TenantServiceGrpcTransport +_transport_registry["grpc_asyncio"] = TenantServiceGrpcAsyncIOTransport + + +__all__ = ( + "TenantServiceTransport", + "TenantServiceGrpcTransport", + "TenantServiceGrpcAsyncIOTransport", +) diff --git a/google/cloud/talent_v4/services/tenant_service/transports/base.py b/google/cloud/talent_v4/services/tenant_service/transports/base.py new file mode 100644 index 00000000..a545754c --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/transports/base.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import abc +import typing +import pkg_resources + +from google import auth # type: ignore +from google.api_core import exceptions # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google.api_core import retry as retries # type: ignore +from google.auth import credentials # type: ignore + +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.protobuf import empty_pb2 as empty # type: ignore + + +try: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( + gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, + ) +except pkg_resources.DistributionNotFound: + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() + + +class TenantServiceTransport(abc.ABC): + """Abstract transport class for TenantService.""" + + AUTH_SCOPES = ( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: typing.Optional[str] = None, + scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, + quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + **kwargs, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scope (Optional[Sequence[str]]): A list of scopes. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + """ + # Save the hostname. Default to port 443 (HTTPS) if none is specified. + if ":" not in host: + host += ":443" + self._host = host + + # If no credentials are provided, then determine the appropriate + # defaults. + if credentials and credentials_file: + raise exceptions.DuplicateCredentialArgs( + "'credentials_file' and 'credentials' are mutually exclusive" + ) + + if credentials_file is not None: + credentials, _ = auth.load_credentials_from_file( + credentials_file, scopes=scopes, quota_project_id=quota_project_id + ) + + elif credentials is None: + credentials, _ = auth.default( + scopes=scopes, quota_project_id=quota_project_id + ) + + # Save the credentials. + self._credentials = credentials + + # Lifted into its own function so it can be stubbed out during tests. + self._prep_wrapped_messages(client_info) + + def _prep_wrapped_messages(self, client_info): + # Precompute the wrapped methods. + self._wrapped_methods = { + self.create_tenant: gapic_v1.method.wrap_method( + self.create_tenant, default_timeout=30.0, client_info=client_info, + ), + self.get_tenant: gapic_v1.method.wrap_method( + self.get_tenant, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.update_tenant: gapic_v1.method.wrap_method( + self.update_tenant, default_timeout=30.0, client_info=client_info, + ), + self.delete_tenant: gapic_v1.method.wrap_method( + self.delete_tenant, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + self.list_tenants: gapic_v1.method.wrap_method( + self.list_tenants, + default_retry=retries.Retry( + initial=0.1, + maximum=60.0, + multiplier=1.3, + predicate=retries.if_exception_type( + exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + ), + ), + default_timeout=30.0, + client_info=client_info, + ), + } + + @property + def create_tenant( + self, + ) -> typing.Callable[ + [tenant_service.CreateTenantRequest], + typing.Union[gct_tenant.Tenant, typing.Awaitable[gct_tenant.Tenant]], + ]: + raise NotImplementedError() + + @property + def get_tenant( + self, + ) -> typing.Callable[ + [tenant_service.GetTenantRequest], + typing.Union[tenant.Tenant, typing.Awaitable[tenant.Tenant]], + ]: + raise NotImplementedError() + + @property + def update_tenant( + self, + ) -> typing.Callable[ + [tenant_service.UpdateTenantRequest], + typing.Union[gct_tenant.Tenant, typing.Awaitable[gct_tenant.Tenant]], + ]: + raise NotImplementedError() + + @property + def delete_tenant( + self, + ) -> typing.Callable[ + [tenant_service.DeleteTenantRequest], + typing.Union[empty.Empty, typing.Awaitable[empty.Empty]], + ]: + raise NotImplementedError() + + @property + def list_tenants( + self, + ) -> typing.Callable[ + [tenant_service.ListTenantsRequest], + typing.Union[ + tenant_service.ListTenantsResponse, + typing.Awaitable[tenant_service.ListTenantsResponse], + ], + ]: + raise NotImplementedError() + + +__all__ = ("TenantServiceTransport",) diff --git a/google/cloud/talent_v4/services/tenant_service/transports/grpc.py b/google/cloud/talent_v4/services/tenant_service/transports/grpc.py new file mode 100644 index 00000000..acdf8307 --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/transports/grpc.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore + +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import TenantServiceTransport, DEFAULT_CLIENT_INFO + + +class TenantServiceGrpcTransport(TenantServiceTransport): + """gRPC backend transport for TenantService. + + A service that handles tenant management, including CRUD and + enumeration. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _stubs: Dict[str, Callable] + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Sequence[str] = None, + channel: grpc.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional(Sequence[str])): A list of scopes. This argument is + ignored if ``channel`` is provided. + channel (Optional[grpc.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + self._stubs = {} # type: Dict[str, Callable] + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: str = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> grpc.Channel: + """Create and return a gRPC channel object. + Args: + address (Optionsl[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is mutually exclusive with credentials. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + grpc.Channel: A gRPC channel object. + + Raises: + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + @property + def grpc_channel(self) -> grpc.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_tenant( + self, + ) -> Callable[[tenant_service.CreateTenantRequest], gct_tenant.Tenant]: + r"""Return a callable for the create tenant method over gRPC. + + Creates a new tenant entity. + + Returns: + Callable[[~.CreateTenantRequest], + ~.Tenant]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_tenant" not in self._stubs: + self._stubs["create_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/CreateTenant", + request_serializer=tenant_service.CreateTenantRequest.serialize, + response_deserializer=gct_tenant.Tenant.deserialize, + ) + return self._stubs["create_tenant"] + + @property + def get_tenant(self) -> Callable[[tenant_service.GetTenantRequest], tenant.Tenant]: + r"""Return a callable for the get tenant method over gRPC. + + Retrieves specified tenant. + + Returns: + Callable[[~.GetTenantRequest], + ~.Tenant]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_tenant" not in self._stubs: + self._stubs["get_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/GetTenant", + request_serializer=tenant_service.GetTenantRequest.serialize, + response_deserializer=tenant.Tenant.deserialize, + ) + return self._stubs["get_tenant"] + + @property + def update_tenant( + self, + ) -> Callable[[tenant_service.UpdateTenantRequest], gct_tenant.Tenant]: + r"""Return a callable for the update tenant method over gRPC. + + Updates specified tenant. + + Returns: + Callable[[~.UpdateTenantRequest], + ~.Tenant]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_tenant" not in self._stubs: + self._stubs["update_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/UpdateTenant", + request_serializer=tenant_service.UpdateTenantRequest.serialize, + response_deserializer=gct_tenant.Tenant.deserialize, + ) + return self._stubs["update_tenant"] + + @property + def delete_tenant( + self, + ) -> Callable[[tenant_service.DeleteTenantRequest], empty.Empty]: + r"""Return a callable for the delete tenant method over gRPC. + + Deletes specified tenant. + + Returns: + Callable[[~.DeleteTenantRequest], + ~.Empty]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_tenant" not in self._stubs: + self._stubs["delete_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/DeleteTenant", + request_serializer=tenant_service.DeleteTenantRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_tenant"] + + @property + def list_tenants( + self, + ) -> Callable[ + [tenant_service.ListTenantsRequest], tenant_service.ListTenantsResponse + ]: + r"""Return a callable for the list tenants method over gRPC. + + Lists all tenants associated with the project. + + Returns: + Callable[[~.ListTenantsRequest], + ~.ListTenantsResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_tenants" not in self._stubs: + self._stubs["list_tenants"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/ListTenants", + request_serializer=tenant_service.ListTenantsRequest.serialize, + response_deserializer=tenant_service.ListTenantsResponse.deserialize, + ) + return self._stubs["list_tenants"] + + +__all__ = ("TenantServiceGrpcTransport",) diff --git a/google/cloud/talent_v4/services/tenant_service/transports/grpc_asyncio.py b/google/cloud/talent_v4/services/tenant_service/transports/grpc_asyncio.py new file mode 100644 index 00000000..9624c5b7 --- /dev/null +++ b/google/cloud/talent_v4/services/tenant_service/transports/grpc_asyncio.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import warnings +from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple + +from google.api_core import gapic_v1 # type: ignore +from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore +from google.auth import credentials # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore + +import grpc # type: ignore +from grpc.experimental import aio # type: ignore + +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.protobuf import empty_pb2 as empty # type: ignore + +from .base import TenantServiceTransport, DEFAULT_CLIENT_INFO +from .grpc import TenantServiceGrpcTransport + + +class TenantServiceGrpcAsyncIOTransport(TenantServiceTransport): + """gRPC AsyncIO backend transport for TenantService. + + A service that handles tenant management, including CRUD and + enumeration. + + This class defines the same methods as the primary client, so the + primary client can load the underlying transport implementation + and call it. + + It sends protocol buffers over the wire using gRPC (which is built on + top of HTTP/2); the ``grpcio`` package must be installed. + """ + + _grpc_channel: aio.Channel + _stubs: Dict[str, Callable] = {} + + @classmethod + def create_channel( + cls, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + quota_project_id: Optional[str] = None, + **kwargs, + ) -> aio.Channel: + """Create and return a gRPC AsyncIO channel object. + Args: + address (Optional[str]): The host for the channel to use. + credentials (Optional[~.Credentials]): The + authorization credentials to attach to requests. These + credentials identify this application to the service. If + none are specified, the client will attempt to ascertain + the credentials from the environment. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + kwargs (Optional[dict]): Keyword arguments, which are passed to the + channel creation. + Returns: + aio.Channel: A gRPC AsyncIO channel object. + """ + scopes = scopes or cls.AUTH_SCOPES + return grpc_helpers_async.create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes, + quota_project_id=quota_project_id, + **kwargs, + ) + + def __init__( + self, + *, + host: str = "jobs.googleapis.com", + credentials: credentials.Credentials = None, + credentials_file: Optional[str] = None, + scopes: Optional[Sequence[str]] = None, + channel: aio.Channel = None, + api_mtls_endpoint: str = None, + client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, + ) -> None: + """Instantiate the transport. + + Args: + host (Optional[str]): The hostname to connect to. + credentials (Optional[google.auth.credentials.Credentials]): The + authorization credentials to attach to requests. These + credentials identify the application to the service; if none + are specified, the client will attempt to ascertain the + credentials from the environment. + This argument is ignored if ``channel`` is provided. + credentials_file (Optional[str]): A file with credentials that can + be loaded with :func:`google.auth.load_credentials_from_file`. + This argument is ignored if ``channel`` is provided. + scopes (Optional[Sequence[str]]): A optional list of scopes needed for this + service. These are only used when credentials are not specified and + are passed to :func:`google.auth.default`. + channel (Optional[aio.Channel]): A ``Channel`` instance through + which to make calls. + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create + a mutual TLS channel with client SSL credentials from + ``client_cert_source`` or applicatin default SSL credentials. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. + quota_project_id (Optional[str]): An optional project to use for billing + and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. + + Raises: + google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport + creation failed for any reason. + google.api_core.exceptions.DuplicateCredentialArgs: If both ``credentials`` + and ``credentials_file`` are passed. + """ + if channel: + # Sanity check: Ensure that channel and credentials are not both + # provided. + credentials = False + + # If a channel was explicitly provided, set it. + self._grpc_channel = channel + elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + + host = ( + api_mtls_endpoint + if ":" in api_mtls_endpoint + else api_mtls_endpoint + ":443" + ) + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # Create SSL credentials with client_cert_source or application + # default SSL credentials. + if client_cert_source: + cert, key = client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + else: + ssl_credentials = SslCredentials().ssl_credentials + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) + + # Run the base constructor. + super().__init__( + host=host, + credentials=credentials, + credentials_file=credentials_file, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + client_info=client_info, + ) + + self._stubs = {} + + @property + def grpc_channel(self) -> aio.Channel: + """Create the channel designed to connect to this service. + + This property caches on the instance; repeated calls return + the same channel. + """ + # Return the channel from cache. + return self._grpc_channel + + @property + def create_tenant( + self, + ) -> Callable[[tenant_service.CreateTenantRequest], Awaitable[gct_tenant.Tenant]]: + r"""Return a callable for the create tenant method over gRPC. + + Creates a new tenant entity. + + Returns: + Callable[[~.CreateTenantRequest], + Awaitable[~.Tenant]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "create_tenant" not in self._stubs: + self._stubs["create_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/CreateTenant", + request_serializer=tenant_service.CreateTenantRequest.serialize, + response_deserializer=gct_tenant.Tenant.deserialize, + ) + return self._stubs["create_tenant"] + + @property + def get_tenant( + self, + ) -> Callable[[tenant_service.GetTenantRequest], Awaitable[tenant.Tenant]]: + r"""Return a callable for the get tenant method over gRPC. + + Retrieves specified tenant. + + Returns: + Callable[[~.GetTenantRequest], + Awaitable[~.Tenant]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "get_tenant" not in self._stubs: + self._stubs["get_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/GetTenant", + request_serializer=tenant_service.GetTenantRequest.serialize, + response_deserializer=tenant.Tenant.deserialize, + ) + return self._stubs["get_tenant"] + + @property + def update_tenant( + self, + ) -> Callable[[tenant_service.UpdateTenantRequest], Awaitable[gct_tenant.Tenant]]: + r"""Return a callable for the update tenant method over gRPC. + + Updates specified tenant. + + Returns: + Callable[[~.UpdateTenantRequest], + Awaitable[~.Tenant]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "update_tenant" not in self._stubs: + self._stubs["update_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/UpdateTenant", + request_serializer=tenant_service.UpdateTenantRequest.serialize, + response_deserializer=gct_tenant.Tenant.deserialize, + ) + return self._stubs["update_tenant"] + + @property + def delete_tenant( + self, + ) -> Callable[[tenant_service.DeleteTenantRequest], Awaitable[empty.Empty]]: + r"""Return a callable for the delete tenant method over gRPC. + + Deletes specified tenant. + + Returns: + Callable[[~.DeleteTenantRequest], + Awaitable[~.Empty]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "delete_tenant" not in self._stubs: + self._stubs["delete_tenant"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/DeleteTenant", + request_serializer=tenant_service.DeleteTenantRequest.serialize, + response_deserializer=empty.Empty.FromString, + ) + return self._stubs["delete_tenant"] + + @property + def list_tenants( + self, + ) -> Callable[ + [tenant_service.ListTenantsRequest], + Awaitable[tenant_service.ListTenantsResponse], + ]: + r"""Return a callable for the list tenants method over gRPC. + + Lists all tenants associated with the project. + + Returns: + Callable[[~.ListTenantsRequest], + Awaitable[~.ListTenantsResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "list_tenants" not in self._stubs: + self._stubs["list_tenants"] = self.grpc_channel.unary_unary( + "/google.cloud.talent.v4.TenantService/ListTenants", + request_serializer=tenant_service.ListTenantsRequest.serialize, + response_deserializer=tenant_service.ListTenantsResponse.deserialize, + ) + return self._stubs["list_tenants"] + + +__all__ = ("TenantServiceGrpcAsyncIOTransport",) diff --git a/google/cloud/talent_v4/types/__init__.py b/google/cloud/talent_v4/types/__init__.py new file mode 100644 index 00000000..7e2f5b87 --- /dev/null +++ b/google/cloud/talent_v4/types/__init__.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from .common import ( + TimestampRange, + Location, + RequestMetadata, + ResponseMetadata, + DeviceInfo, + CustomAttribute, + SpellingCorrection, + CompensationInfo, + BatchOperationMetadata, +) +from .company import Company +from .company_service import ( + CreateCompanyRequest, + GetCompanyRequest, + UpdateCompanyRequest, + DeleteCompanyRequest, + ListCompaniesRequest, + ListCompaniesResponse, +) +from .completion_service import ( + CompleteQueryRequest, + CompleteQueryResponse, +) +from .event import ( + ClientEvent, + JobEvent, +) +from .event_service import CreateClientEventRequest +from .filters import ( + JobQuery, + LocationFilter, + CompensationFilter, + CommuteFilter, +) +from .histogram import ( + HistogramQuery, + HistogramQueryResult, +) +from .job import Job +from .job_service import ( + CreateJobRequest, + GetJobRequest, + UpdateJobRequest, + DeleteJobRequest, + ListJobsRequest, + ListJobsResponse, + SearchJobsRequest, + SearchJobsResponse, + BatchCreateJobsRequest, + BatchUpdateJobsRequest, + BatchDeleteJobsRequest, + JobResult, + BatchCreateJobsResponse, + BatchUpdateJobsResponse, + BatchDeleteJobsResponse, +) +from .tenant import Tenant +from .tenant_service import ( + CreateTenantRequest, + GetTenantRequest, + UpdateTenantRequest, + DeleteTenantRequest, + ListTenantsRequest, + ListTenantsResponse, +) + + +__all__ = ( + "TimestampRange", + "Location", + "RequestMetadata", + "ResponseMetadata", + "DeviceInfo", + "CustomAttribute", + "SpellingCorrection", + "CompensationInfo", + "BatchOperationMetadata", + "Company", + "CreateCompanyRequest", + "GetCompanyRequest", + "UpdateCompanyRequest", + "DeleteCompanyRequest", + "ListCompaniesRequest", + "ListCompaniesResponse", + "CompleteQueryRequest", + "CompleteQueryResponse", + "ClientEvent", + "JobEvent", + "CreateClientEventRequest", + "JobQuery", + "LocationFilter", + "CompensationFilter", + "CommuteFilter", + "HistogramQuery", + "HistogramQueryResult", + "Job", + "CreateJobRequest", + "GetJobRequest", + "UpdateJobRequest", + "DeleteJobRequest", + "ListJobsRequest", + "ListJobsResponse", + "SearchJobsRequest", + "SearchJobsResponse", + "BatchCreateJobsRequest", + "BatchUpdateJobsRequest", + "BatchDeleteJobsRequest", + "JobResult", + "BatchCreateJobsResponse", + "BatchUpdateJobsResponse", + "BatchDeleteJobsResponse", + "Tenant", + "CreateTenantRequest", + "GetTenantRequest", + "UpdateTenantRequest", + "DeleteTenantRequest", + "ListTenantsRequest", + "ListTenantsResponse", +) diff --git a/google/cloud/talent_v4/types/common.py b/google/cloud/talent_v4/types/common.py new file mode 100644 index 00000000..e4041fc4 --- /dev/null +++ b/google/cloud/talent_v4/types/common.py @@ -0,0 +1,746 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.protobuf import timestamp_pb2 as timestamp # type: ignore +from google.protobuf import wrappers_pb2 as wrappers # type: ignore +from google.type import latlng_pb2 as latlng # type: ignore +from google.type import money_pb2 as money # type: ignore +from google.type import postal_address_pb2 as gt_postal_address # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={ + "CompanySize", + "JobBenefit", + "DegreeType", + "EmploymentType", + "JobLevel", + "JobCategory", + "PostingRegion", + "Visibility", + "HtmlSanitization", + "CommuteMethod", + "TimestampRange", + "Location", + "RequestMetadata", + "ResponseMetadata", + "DeviceInfo", + "CustomAttribute", + "SpellingCorrection", + "CompensationInfo", + "BatchOperationMetadata", + }, +) + + +class CompanySize(proto.Enum): + r"""An enum that represents the size of the company.""" + COMPANY_SIZE_UNSPECIFIED = 0 + MINI = 1 + SMALL = 2 + SMEDIUM = 3 + MEDIUM = 4 + BIG = 5 + BIGGER = 6 + GIANT = 7 + + +class JobBenefit(proto.Enum): + r"""An enum that represents employee benefits included with the + job. + """ + JOB_BENEFIT_UNSPECIFIED = 0 + CHILD_CARE = 1 + DENTAL = 2 + DOMESTIC_PARTNER = 3 + FLEXIBLE_HOURS = 4 + MEDICAL = 5 + LIFE_INSURANCE = 6 + PARENTAL_LEAVE = 7 + RETIREMENT_PLAN = 8 + SICK_DAYS = 9 + VACATION = 10 + VISION = 11 + + +class DegreeType(proto.Enum): + r"""Educational degree level defined in International Standard + Classification of Education (ISCED). + """ + DEGREE_TYPE_UNSPECIFIED = 0 + PRIMARY_EDUCATION = 1 + LOWER_SECONDARY_EDUCATION = 2 + UPPER_SECONDARY_EDUCATION = 3 + ADULT_REMEDIAL_EDUCATION = 4 + ASSOCIATES_OR_EQUIVALENT = 5 + BACHELORS_OR_EQUIVALENT = 6 + MASTERS_OR_EQUIVALENT = 7 + DOCTORAL_OR_EQUIVALENT = 8 + + +class EmploymentType(proto.Enum): + r"""An enum that represents the employment type of a job.""" + EMPLOYMENT_TYPE_UNSPECIFIED = 0 + FULL_TIME = 1 + PART_TIME = 2 + CONTRACTOR = 3 + CONTRACT_TO_HIRE = 4 + TEMPORARY = 5 + INTERN = 6 + VOLUNTEER = 7 + PER_DIEM = 8 + FLY_IN_FLY_OUT = 9 + OTHER_EMPLOYMENT_TYPE = 10 + + +class JobLevel(proto.Enum): + r"""An enum that represents the required experience level + required for the job. + """ + JOB_LEVEL_UNSPECIFIED = 0 + ENTRY_LEVEL = 1 + EXPERIENCED = 2 + MANAGER = 3 + DIRECTOR = 4 + EXECUTIVE = 5 + + +class JobCategory(proto.Enum): + r"""An enum that represents the categorization or primary focus + of specific role. This value is different than the "industry" + associated with a role, which is related to the categorization + of the company listing the job. + """ + JOB_CATEGORY_UNSPECIFIED = 0 + ACCOUNTING_AND_FINANCE = 1 + ADMINISTRATIVE_AND_OFFICE = 2 + ADVERTISING_AND_MARKETING = 3 + ANIMAL_CARE = 4 + ART_FASHION_AND_DESIGN = 5 + BUSINESS_OPERATIONS = 6 + CLEANING_AND_FACILITIES = 7 + COMPUTER_AND_IT = 8 + CONSTRUCTION = 9 + CUSTOMER_SERVICE = 10 + EDUCATION = 11 + ENTERTAINMENT_AND_TRAVEL = 12 + FARMING_AND_OUTDOORS = 13 + HEALTHCARE = 14 + HUMAN_RESOURCES = 15 + INSTALLATION_MAINTENANCE_AND_REPAIR = 16 + LEGAL = 17 + MANAGEMENT = 18 + MANUFACTURING_AND_WAREHOUSE = 19 + MEDIA_COMMUNICATIONS_AND_WRITING = 20 + OIL_GAS_AND_MINING = 21 + PERSONAL_CARE_AND_SERVICES = 22 + PROTECTIVE_SERVICES = 23 + REAL_ESTATE = 24 + RESTAURANT_AND_HOSPITALITY = 25 + SALES_AND_RETAIL = 26 + SCIENCE_AND_ENGINEERING = 27 + SOCIAL_SERVICES_AND_NON_PROFIT = 28 + SPORTS_FITNESS_AND_RECREATION = 29 + TRANSPORTATION_AND_LOGISTICS = 30 + + +class PostingRegion(proto.Enum): + r"""An enum that represents the job posting region. In most + cases, job postings don't need to specify a region. If a region + is given, jobs are eligible for searches in the specified + region. + """ + POSTING_REGION_UNSPECIFIED = 0 + ADMINISTRATIVE_AREA = 1 + NATION = 2 + TELECOMMUTE = 3 + + +class Visibility(proto.Enum): + r"""Deprecated. All resources are only visible to the owner. + An enum that represents who has view access to the resource. + """ + VISIBILITY_UNSPECIFIED = 0 + ACCOUNT_ONLY = 1 + SHARED_WITH_GOOGLE = 2 + SHARED_WITH_PUBLIC = 3 + + +class HtmlSanitization(proto.Enum): + r"""Option for HTML content sanitization on user input fields, + for example, job description. By setting this option, user can + determine whether and how sanitization is performed on these + fields. + """ + HTML_SANITIZATION_UNSPECIFIED = 0 + HTML_SANITIZATION_DISABLED = 1 + SIMPLE_FORMATTING_ONLY = 2 + + +class CommuteMethod(proto.Enum): + r"""Method for commute.""" + COMMUTE_METHOD_UNSPECIFIED = 0 + DRIVING = 1 + TRANSIT = 2 + WALKING = 3 + CYCLING = 4 + + +class TimestampRange(proto.Message): + r"""Message representing a period of time between two timestamps. + + Attributes: + start_time (~.timestamp.Timestamp): + Begin of the period (inclusive). + end_time (~.timestamp.Timestamp): + End of the period (exclusive). + """ + + start_time = proto.Field(proto.MESSAGE, number=1, message=timestamp.Timestamp,) + + end_time = proto.Field(proto.MESSAGE, number=2, message=timestamp.Timestamp,) + + +class Location(proto.Message): + r"""A resource that represents a location with full geographic + information. + + Attributes: + location_type (~.common.Location.LocationType): + The type of a location, which corresponds to the address + lines field of + [google.type.PostalAddress][google.type.PostalAddress]. For + example, "Downtown, Atlanta, GA, USA" has a type of + [LocationType.NEIGHBORHOOD][google.cloud.talent.v4.Location.LocationType.NEIGHBORHOOD], + and "Kansas City, KS, USA" has a type of + [LocationType.LOCALITY][google.cloud.talent.v4.Location.LocationType.LOCALITY]. + postal_address (~.gt_postal_address.PostalAddress): + Postal address of the location that includes + human readable information, such as postal + delivery and payments addresses. Given a postal + address, a postal service can deliver items to a + premises, P.O. Box, or other delivery location. + lat_lng (~.latlng.LatLng): + An object representing a latitude/longitude + pair. + radius_miles (float): + Radius in miles of the job location. This value is derived + from the location bounding box in which a circle with the + specified radius centered from + [google.type.LatLng][google.type.LatLng] covers the area + associated with the job location. For example, currently, + "Mountain View, CA, USA" has a radius of 6.17 miles. + """ + + class LocationType(proto.Enum): + r"""An enum which represents the type of a location.""" + LOCATION_TYPE_UNSPECIFIED = 0 + COUNTRY = 1 + ADMINISTRATIVE_AREA = 2 + SUB_ADMINISTRATIVE_AREA = 3 + LOCALITY = 4 + POSTAL_CODE = 5 + SUB_LOCALITY = 6 + SUB_LOCALITY_1 = 7 + SUB_LOCALITY_2 = 8 + NEIGHBORHOOD = 9 + STREET_ADDRESS = 10 + + location_type = proto.Field(proto.ENUM, number=1, enum=LocationType,) + + postal_address = proto.Field( + proto.MESSAGE, number=2, message=gt_postal_address.PostalAddress, + ) + + lat_lng = proto.Field(proto.MESSAGE, number=3, message=latlng.LatLng,) + + radius_miles = proto.Field(proto.DOUBLE, number=4) + + +class RequestMetadata(proto.Message): + r"""Meta information related to the job searcher or entity + conducting the job search. This information is used to improve + the performance of the service. + + Attributes: + domain (str): + Required if + [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + is unset or ``false``. + + The client-defined scope or source of the service call, + which typically is the domain on which the service has been + implemented and is currently being run. + + For example, if the service is being run by client Foo, + Inc., on job board www.foo.com and career site www.bar.com, + then this field is set to "foo.com" for use on the job + board, and "bar.com" for use on the career site. + + Note that any improvements to the model for a particular + tenant site rely on this field being set correctly to a + unique domain. + + The maximum number of allowed characters is 255. + session_id (str): + Required if + [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + is unset or ``false``. + + A unique session identification string. A session is defined + as the duration of an end user's interaction with the + service over a certain period. Obfuscate this field for + privacy concerns before providing it to the service. + + Note that any improvements to the model for a particular + tenant site rely on this field being set correctly to a + unique session ID. + + The maximum number of allowed characters is 255. + user_id (str): + Required if + [allow_missing_ids][google.cloud.talent.v4.RequestMetadata.allow_missing_ids] + is unset or ``false``. + + A unique user identification string, as determined by the + client. To have the strongest positive impact on search + quality make sure the client-level is unique. Obfuscate this + field for privacy concerns before providing it to the + service. + + Note that any improvements to the model for a particular + tenant site rely on this field being set correctly to a + unique user ID. + + The maximum number of allowed characters is 255. + allow_missing_ids (bool): + Only set when any of + [domain][google.cloud.talent.v4.RequestMetadata.domain], + [session_id][google.cloud.talent.v4.RequestMetadata.session_id] + and + [user_id][google.cloud.talent.v4.RequestMetadata.user_id] + isn't available for some reason. It is highly recommended + not to set this field and provide accurate + [domain][google.cloud.talent.v4.RequestMetadata.domain], + [session_id][google.cloud.talent.v4.RequestMetadata.session_id] + and + [user_id][google.cloud.talent.v4.RequestMetadata.user_id] + for the best service experience. + device_info (~.common.DeviceInfo): + The type of device used by the job seeker at + the time of the call to the service. + """ + + domain = proto.Field(proto.STRING, number=1) + + session_id = proto.Field(proto.STRING, number=2) + + user_id = proto.Field(proto.STRING, number=3) + + allow_missing_ids = proto.Field(proto.BOOL, number=4) + + device_info = proto.Field(proto.MESSAGE, number=5, message="DeviceInfo",) + + +class ResponseMetadata(proto.Message): + r"""Additional information returned to client, such as debugging + information. + + Attributes: + request_id (str): + A unique id associated with this call. + This id is logged for tracking purposes. + """ + + request_id = proto.Field(proto.STRING, number=1) + + +class DeviceInfo(proto.Message): + r"""Device information collected from the job seeker, candidate, + or other entity conducting the job search. Providing this + information improves the quality of the search results across + devices. + + Attributes: + device_type (~.common.DeviceInfo.DeviceType): + Type of the device. + id (str): + A device-specific ID. The ID must be a unique + identifier that distinguishes the device from + other devices. + """ + + class DeviceType(proto.Enum): + r"""An enumeration describing an API access portal and exposure + mechanism. + """ + DEVICE_TYPE_UNSPECIFIED = 0 + WEB = 1 + MOBILE_WEB = 2 + ANDROID = 3 + IOS = 4 + BOT = 5 + OTHER = 6 + + device_type = proto.Field(proto.ENUM, number=1, enum=DeviceType,) + + id = proto.Field(proto.STRING, number=2) + + +class CustomAttribute(proto.Message): + r"""Custom attribute values that are either filterable or non- + ilterable. + + Attributes: + string_values (Sequence[str]): + Exactly one of + [string_values][google.cloud.talent.v4.CustomAttribute.string_values] + or + [long_values][google.cloud.talent.v4.CustomAttribute.long_values] + must be specified. + + This field is used to perform a string match + (``CASE_SENSITIVE_MATCH`` or ``CASE_INSENSITIVE_MATCH``) + search. For filterable ``string_value``\ s, a maximum total + number of 200 values is allowed, with each ``string_value`` + has a byte size of no more than 500B. For unfilterable + ``string_values``, the maximum total byte size of + unfilterable ``string_values`` is 50KB. + + Empty string isn't allowed. + long_values (Sequence[int]): + Exactly one of + [string_values][google.cloud.talent.v4.CustomAttribute.string_values] + or + [long_values][google.cloud.talent.v4.CustomAttribute.long_values] + must be specified. + + This field is used to perform number range search. (``EQ``, + ``GT``, ``GE``, ``LE``, ``LT``) over filterable + ``long_value``. + + Currently at most 1 + [long_values][google.cloud.talent.v4.CustomAttribute.long_values] + is supported. + filterable (bool): + If the ``filterable`` flag is true, the custom field values + may be used for custom attribute filters + [JobQuery.custom_attribute_filter][google.cloud.talent.v4.JobQuery.custom_attribute_filter]. + If false, these values may not be used for custom attribute + filters. + + Default is false. + keyword_searchable (bool): + If the ``keyword_searchable`` flag is true, the keywords in + custom fields are searchable by keyword match. If false, the + values are not searchable by keyword match. + + Default is false. + """ + + string_values = proto.RepeatedField(proto.STRING, number=1) + + long_values = proto.RepeatedField(proto.INT64, number=2) + + filterable = proto.Field(proto.BOOL, number=3) + + keyword_searchable = proto.Field(proto.BOOL, number=4) + + +class SpellingCorrection(proto.Message): + r"""Spell check result. + + Attributes: + corrected (bool): + Indicates if the query was corrected by the + spell checker. + corrected_text (str): + Correction output consisting of the corrected + keyword string. + corrected_html (str): + Corrected output with html tags to highlight + the corrected words. Corrected words are called + out with the "..." html tags. + For example, the user input query is "software + enginear", where the second word, "enginear," is + incorrect. It should be "engineer". When + spelling correction is enabled, this value is + "software engineer". + """ + + corrected = proto.Field(proto.BOOL, number=1) + + corrected_text = proto.Field(proto.STRING, number=2) + + corrected_html = proto.Field(proto.STRING, number=3) + + +class CompensationInfo(proto.Message): + r"""Job compensation details. + + Attributes: + entries (Sequence[~.common.CompensationInfo.CompensationEntry]): + Job compensation information. + + At most one entry can be of type + [CompensationInfo.CompensationType.BASE][google.cloud.talent.v4.CompensationInfo.CompensationType.BASE], + which is referred as **base compensation entry** for the + job. + annualized_base_compensation_range (~.common.CompensationInfo.CompensationRange): + Output only. Annualized base compensation range. Computed as + base compensation entry's + [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + times + [CompensationEntry.expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + + See + [CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + for explanation on compensation annualization. + annualized_total_compensation_range (~.common.CompensationInfo.CompensationRange): + Output only. Annualized total compensation range. Computed + as all compensation entries' + [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + times + [CompensationEntry.expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + + See + [CompensationEntry][google.cloud.talent.v4.CompensationInfo.CompensationEntry] + for explanation on compensation annualization. + """ + + class CompensationType(proto.Enum): + r"""The type of compensation. + + For compensation amounts specified in non-monetary amounts, describe + the compensation scheme in the + [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description]. + + For example, tipping format is described in + [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + (for example, "expect 15-20% tips based on customer bill.") and an + estimate of the tips provided in + [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + or + [CompensationEntry.range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range] + ($10 per hour). + + For example, equity is described in + [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + (for example, "1% - 2% equity vesting over 4 years, 1 year cliff") + and value estimated in + [CompensationEntry.amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + or + [CompensationEntry.range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]. + If no value estimate is possible, units are + [CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED] + and then further clarified in + [CompensationEntry.description][google.cloud.talent.v4.CompensationInfo.CompensationEntry.description] + field. + """ + COMPENSATION_TYPE_UNSPECIFIED = 0 + BASE = 1 + BONUS = 2 + SIGNING_BONUS = 3 + EQUITY = 4 + PROFIT_SHARING = 5 + COMMISSIONS = 6 + TIPS = 7 + OTHER_COMPENSATION_TYPE = 8 + + class CompensationUnit(proto.Enum): + r"""Pay frequency.""" + COMPENSATION_UNIT_UNSPECIFIED = 0 + HOURLY = 1 + DAILY = 2 + WEEKLY = 3 + MONTHLY = 4 + YEARLY = 5 + ONE_TIME = 6 + OTHER_COMPENSATION_UNIT = 7 + + class CompensationEntry(proto.Message): + r"""A compensation entry that represents one component of compensation, + such as base pay, bonus, or other compensation type. + + Annualization: One compensation entry can be annualized if + + - it contains valid + [amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + or + [range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]. + - and its + [expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year] + is set or can be derived. Its annualized range is determined as + ([amount][google.cloud.talent.v4.CompensationInfo.CompensationEntry.amount] + or + [range][google.cloud.talent.v4.CompensationInfo.CompensationEntry.range]) + times + [expected_units_per_year][google.cloud.talent.v4.CompensationInfo.CompensationEntry.expected_units_per_year]. + + Attributes: + type_ (~.common.CompensationInfo.CompensationType): + Compensation type. + + Default is + [CompensationType.COMPENSATION_TYPE_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationType.COMPENSATION_TYPE_UNSPECIFIED]. + unit (~.common.CompensationInfo.CompensationUnit): + Frequency of the specified amount. + + Default is + [CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED][google.cloud.talent.v4.CompensationInfo.CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED]. + amount (~.money.Money): + Compensation amount. + range_ (~.common.CompensationInfo.CompensationRange): + Compensation range. + description (str): + Compensation description. For example, could + indicate equity terms or provide additional + context to an estimated bonus. + expected_units_per_year (~.wrappers.DoubleValue): + Expected number of units paid each year. If not specified, + when + [Job.employment_types][google.cloud.talent.v4.Job.employment_types] + is FULLTIME, a default value is inferred based on + [unit][google.cloud.talent.v4.CompensationInfo.CompensationEntry.unit]. + Default values: + + - HOURLY: 2080 + - DAILY: 260 + - WEEKLY: 52 + - MONTHLY: 12 + - ANNUAL: 1 + """ + + type_ = proto.Field( + proto.ENUM, number=1, enum="CompensationInfo.CompensationType", + ) + + unit = proto.Field( + proto.ENUM, number=2, enum="CompensationInfo.CompensationUnit", + ) + + amount = proto.Field( + proto.MESSAGE, number=3, oneof="compensation_amount", message=money.Money, + ) + + range_ = proto.Field( + proto.MESSAGE, + number=4, + oneof="compensation_amount", + message="CompensationInfo.CompensationRange", + ) + + description = proto.Field(proto.STRING, number=5) + + expected_units_per_year = proto.Field( + proto.MESSAGE, number=6, message=wrappers.DoubleValue, + ) + + class CompensationRange(proto.Message): + r"""Compensation range. + + Attributes: + max_compensation (~.money.Money): + The maximum amount of compensation. If left empty, the value + is set to a maximal compensation value and the currency code + is set to match the [currency + code][google.type.Money.currency_code] of min_compensation. + min_compensation (~.money.Money): + The minimum amount of compensation. If left empty, the value + is set to zero and the currency code is set to match the + [currency code][google.type.Money.currency_code] of + max_compensation. + """ + + max_compensation = proto.Field(proto.MESSAGE, number=2, message=money.Money,) + + min_compensation = proto.Field(proto.MESSAGE, number=1, message=money.Money,) + + entries = proto.RepeatedField(proto.MESSAGE, number=1, message=CompensationEntry,) + + annualized_base_compensation_range = proto.Field( + proto.MESSAGE, number=2, message=CompensationRange, + ) + + annualized_total_compensation_range = proto.Field( + proto.MESSAGE, number=3, message=CompensationRange, + ) + + +class BatchOperationMetadata(proto.Message): + r"""Metadata used for long running operations returned by CTS batch + APIs. It's used to replace + [google.longrunning.Operation.metadata][google.longrunning.Operation.metadata]. + + Attributes: + state (~.common.BatchOperationMetadata.State): + The state of a long running operation. + state_description (str): + More detailed information about operation + state. + success_count (int): + Count of successful item(s) inside an + operation. + failure_count (int): + Count of failed item(s) inside an operation. + total_count (int): + Count of total item(s) inside an operation. + create_time (~.timestamp.Timestamp): + The time when the batch operation is created. + update_time (~.timestamp.Timestamp): + The time when the batch operation status is updated. The + metadata and the + [update_time][google.cloud.talent.v4.BatchOperationMetadata.update_time] + is refreshed every minute otherwise cached data is returned. + end_time (~.timestamp.Timestamp): + The time when the batch operation is finished and + [google.longrunning.Operation.done][google.longrunning.Operation.done] + is set to ``true``. + """ + + class State(proto.Enum): + r"""""" + STATE_UNSPECIFIED = 0 + INITIALIZING = 1 + PROCESSING = 2 + SUCCEEDED = 3 + FAILED = 4 + CANCELLING = 5 + CANCELLED = 6 + + state = proto.Field(proto.ENUM, number=1, enum=State,) + + state_description = proto.Field(proto.STRING, number=2) + + success_count = proto.Field(proto.INT32, number=3) + + failure_count = proto.Field(proto.INT32, number=4) + + total_count = proto.Field(proto.INT32, number=5) + + create_time = proto.Field(proto.MESSAGE, number=6, message=timestamp.Timestamp,) + + update_time = proto.Field(proto.MESSAGE, number=7, message=timestamp.Timestamp,) + + end_time = proto.Field(proto.MESSAGE, number=8, message=timestamp.Timestamp,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/company.py b/google/cloud/talent_v4/types/company.py new file mode 100644 index 00000000..9955928e --- /dev/null +++ b/google/cloud/talent_v4/types/company.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common + + +__protobuf__ = proto.module(package="google.cloud.talent.v4", manifest={"Company",},) + + +class Company(proto.Message): + r"""A Company resource represents a company in the service. A + company is the entity that owns job postings, that is, the + hiring entity responsible for employing applicants for the job + position. + + Attributes: + name (str): + Required during company update. + + The resource name for a company. This is generated by the + service when a company is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, "projects/foo/tenants/bar/companies/baz". + display_name (str): + Required. The display name of the company, + for example, "Google LLC". + external_id (str): + Required. Client side company identifier, + used to uniquely identify the company. + + The maximum number of allowed characters is 255. + size (~.common.CompanySize): + The employer's company size. + headquarters_address (str): + The street address of the company's main headquarters, which + may be different from the job location. The service attempts + to geolocate the provided address, and populates a more + specific location wherever possible in + [DerivedInfo.headquarters_location][google.cloud.talent.v4.Company.DerivedInfo.headquarters_location]. + hiring_agency (bool): + Set to true if it is the hiring agency that + post jobs for other employers. + + Defaults to false if not provided. + eeo_text (str): + Equal Employment Opportunity legal disclaimer + text to be associated with all jobs, and + typically to be displayed in all roles. + + The maximum number of allowed characters is 500. + website_uri (str): + The URI representing the company's primary + web site or home page, for example, + "https://www.google.com". + The maximum number of allowed characters is 255. + career_site_uri (str): + The URI to employer's career site or careers + page on the employer's web site, for example, + "https://careers.google.com". + image_uri (str): + A URI that hosts the employer's company logo. + keyword_searchable_job_custom_attributes (Sequence[str]): + A list of keys of filterable + [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes], + whose corresponding ``string_values`` are used in keyword + searches. Jobs with ``string_values`` under these specified + field keys are returned if any of the values match the + search keyword. Custom field values with parenthesis, + brackets and special symbols are not searchable as-is, and + those keyword queries must be surrounded by quotes. + derived_info (~.company.Company.DerivedInfo): + Output only. Derived details about the + company. + suspended (bool): + Output only. Indicates whether a company is + flagged to be suspended from public availability + by the service when job content appears + suspicious, abusive, or spammy. + """ + + class DerivedInfo(proto.Message): + r"""Derived details about the company. + + Attributes: + headquarters_location (~.common.Location): + A structured headquarters location of the company, resolved + from + [Company.headquarters_address][google.cloud.talent.v4.Company.headquarters_address] + if provided. + """ + + headquarters_location = proto.Field( + proto.MESSAGE, number=1, message=common.Location, + ) + + name = proto.Field(proto.STRING, number=1) + + display_name = proto.Field(proto.STRING, number=2) + + external_id = proto.Field(proto.STRING, number=3) + + size = proto.Field(proto.ENUM, number=4, enum=common.CompanySize,) + + headquarters_address = proto.Field(proto.STRING, number=5) + + hiring_agency = proto.Field(proto.BOOL, number=6) + + eeo_text = proto.Field(proto.STRING, number=7) + + website_uri = proto.Field(proto.STRING, number=8) + + career_site_uri = proto.Field(proto.STRING, number=9) + + image_uri = proto.Field(proto.STRING, number=10) + + keyword_searchable_job_custom_attributes = proto.RepeatedField( + proto.STRING, number=11 + ) + + derived_info = proto.Field(proto.MESSAGE, number=12, message=DerivedInfo,) + + suspended = proto.Field(proto.BOOL, number=13) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/company_service.py b/google/cloud/talent_v4/types/company_service.py new file mode 100644 index 00000000..0e166ef9 --- /dev/null +++ b/google/cloud/talent_v4/types/company_service.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import company as gct_company +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={ + "CreateCompanyRequest", + "GetCompanyRequest", + "UpdateCompanyRequest", + "DeleteCompanyRequest", + "ListCompaniesRequest", + "ListCompaniesResponse", + }, +) + + +class CreateCompanyRequest(proto.Message): + r"""The Request of the CreateCompany method. + + Attributes: + parent (str): + Required. Resource name of the tenant under which the + company is created. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + company (~.gct_company.Company): + Required. The company to be created. + """ + + parent = proto.Field(proto.STRING, number=1) + + company = proto.Field(proto.MESSAGE, number=2, message=gct_company.Company,) + + +class GetCompanyRequest(proto.Message): + r"""Request for getting a company by name. + + Attributes: + name (str): + Required. The resource name of the company to be retrieved. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, + "projects/api-test-project/tenants/foo/companies/bar". + """ + + name = proto.Field(proto.STRING, number=1) + + +class UpdateCompanyRequest(proto.Message): + r"""Request for updating a specified company. + + Attributes: + company (~.gct_company.Company): + Required. The company resource to replace the + current resource in the system. + update_mask (~.field_mask.FieldMask): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateCompanyRequest.update_mask] + is provided, only the specified fields in + [company][google.cloud.talent.v4.UpdateCompanyRequest.company] + are updated. Otherwise all the fields are updated. + + A field mask to specify the company fields to be updated. + Only top level fields of + [Company][google.cloud.talent.v4.Company] are supported. + """ + + company = proto.Field(proto.MESSAGE, number=1, message=gct_company.Company,) + + update_mask = proto.Field(proto.MESSAGE, number=2, message=field_mask.FieldMask,) + + +class DeleteCompanyRequest(proto.Message): + r"""Request to delete a company. + + Attributes: + name (str): + Required. The resource name of the company to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, "projects/foo/tenants/bar/companies/baz". + """ + + name = proto.Field(proto.STRING, number=1) + + +class ListCompaniesRequest(proto.Message): + r"""List companies for which the client has ACL visibility. + + Attributes: + parent (str): + Required. Resource name of the tenant under which the + company is created. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + page_token (str): + The starting indicator from which to return + results. + page_size (int): + The maximum number of companies to be + returned, at most 100. Default is 100 if a non- + positive number is provided. + require_open_jobs (bool): + Set to true if the companies requested must have open jobs. + + Defaults to false. + + If true, at most + [page_size][google.cloud.talent.v4.ListCompaniesRequest.page_size] + of companies are fetched, among which only those with open + jobs are returned. + """ + + parent = proto.Field(proto.STRING, number=1) + + page_token = proto.Field(proto.STRING, number=2) + + page_size = proto.Field(proto.INT32, number=3) + + require_open_jobs = proto.Field(proto.BOOL, number=4) + + +class ListCompaniesResponse(proto.Message): + r"""The List companies response object. + + Attributes: + companies (Sequence[~.gct_company.Company]): + Companies for the current client. + next_page_token (str): + A token to retrieve the next page of results. + metadata (~.common.ResponseMetadata): + Additional information for the API + invocation, such as the request tracking id. + """ + + @property + def raw_page(self): + return self + + companies = proto.RepeatedField( + proto.MESSAGE, number=1, message=gct_company.Company, + ) + + next_page_token = proto.Field(proto.STRING, number=2) + + metadata = proto.Field(proto.MESSAGE, number=3, message=common.ResponseMetadata,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/completion_service.py b/google/cloud/talent_v4/types/completion_service.py new file mode 100644 index 00000000..1e25c9a9 --- /dev/null +++ b/google/cloud/talent_v4/types/completion_service.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={"CompleteQueryRequest", "CompleteQueryResponse",}, +) + + +class CompleteQueryRequest(proto.Message): + r"""Auto-complete parameters. + + Attributes: + tenant (str): + Required. Resource name of tenant the completion is + performed within. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + query (str): + Required. The query used to generate + suggestions. + The maximum number of allowed characters is 255. + language_codes (Sequence[str]): + The list of languages of the query. This is the BCP-47 + language code, such as "en-US" or "sr-Latn". For more + information, see `Tags for Identifying + Languages `__. + + The maximum number of allowed characters is 255. + page_size (int): + Required. Completion result count. + The maximum allowed page size is 10. + company (str): + If provided, restricts completion to specified company. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}", + for example, "projects/foo/tenants/bar/companies/baz". + scope (~.completion_service.CompleteQueryRequest.CompletionScope): + The scope of the completion. The defaults is + [CompletionScope.PUBLIC][google.cloud.talent.v4.CompleteQueryRequest.CompletionScope.PUBLIC]. + type_ (~.completion_service.CompleteQueryRequest.CompletionType): + The completion topic. The default is + [CompletionType.COMBINED][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMBINED]. + """ + + class CompletionScope(proto.Enum): + r"""Enum to specify the scope of completion.""" + COMPLETION_SCOPE_UNSPECIFIED = 0 + TENANT = 1 + PUBLIC = 2 + + class CompletionType(proto.Enum): + r"""Enum to specify auto-completion topics.""" + COMPLETION_TYPE_UNSPECIFIED = 0 + JOB_TITLE = 1 + COMPANY_NAME = 2 + COMBINED = 3 + + tenant = proto.Field(proto.STRING, number=1) + + query = proto.Field(proto.STRING, number=2) + + language_codes = proto.RepeatedField(proto.STRING, number=3) + + page_size = proto.Field(proto.INT32, number=4) + + company = proto.Field(proto.STRING, number=5) + + scope = proto.Field(proto.ENUM, number=6, enum=CompletionScope,) + + type_ = proto.Field(proto.ENUM, number=7, enum=CompletionType,) + + +class CompleteQueryResponse(proto.Message): + r"""Response of auto-complete query. + + Attributes: + completion_results (Sequence[~.completion_service.CompleteQueryResponse.CompletionResult]): + Results of the matching job/company + candidates. + metadata (~.common.ResponseMetadata): + Additional information for the API + invocation, such as the request tracking id. + """ + + class CompletionResult(proto.Message): + r"""Resource that represents completion results. + + Attributes: + suggestion (str): + The suggestion for the query. + type_ (~.completion_service.CompleteQueryRequest.CompletionType): + The completion topic. + image_uri (str): + The URI of the company image for + [COMPANY_NAME][google.cloud.talent.v4.CompleteQueryRequest.CompletionType.COMPANY_NAME]. + """ + + suggestion = proto.Field(proto.STRING, number=1) + + type_ = proto.Field( + proto.ENUM, number=2, enum=CompleteQueryRequest.CompletionType, + ) + + image_uri = proto.Field(proto.STRING, number=3) + + completion_results = proto.RepeatedField( + proto.MESSAGE, number=1, message=CompletionResult, + ) + + metadata = proto.Field(proto.MESSAGE, number=2, message=common.ResponseMetadata,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/event.py b/google/cloud/talent_v4/types/event.py new file mode 100644 index 00000000..8ecb3a87 --- /dev/null +++ b/google/cloud/talent_v4/types/event.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", manifest={"ClientEvent", "JobEvent",}, +) + + +class ClientEvent(proto.Message): + r"""An event issued when an end user interacts with the + application that implements Cloud Talent Solution. Providing + this information improves the quality of results for the API + clients, enabling the service to perform optimally. The number + of events sent must be consistent with other calls, such as job + searches, issued to the service by the client. + + Attributes: + request_id (str): + Strongly recommended for the best service experience. + + A unique ID generated in the API responses. It can be found + in + [ResponseMetadata.request_id][google.cloud.talent.v4.ResponseMetadata.request_id]. + event_id (str): + Required. A unique identifier, generated by + the client application. + create_time (~.timestamp.Timestamp): + Required. The timestamp of the event. + job_event (~.event.JobEvent): + An event issued when a job seeker interacts + with the application that implements Cloud + Talent Solution. + event_notes (str): + Notes about the event provided by recruiters + or other users, for example, feedback on why a + job was bookmarked. + """ + + request_id = proto.Field(proto.STRING, number=1) + + event_id = proto.Field(proto.STRING, number=2) + + create_time = proto.Field(proto.MESSAGE, number=4, message=timestamp.Timestamp,) + + job_event = proto.Field(proto.MESSAGE, number=5, oneof="event", message="JobEvent",) + + event_notes = proto.Field(proto.STRING, number=9) + + +class JobEvent(proto.Message): + r"""An event issued when a job seeker interacts with the + application that implements Cloud Talent Solution. + + Attributes: + type_ (~.event.JobEvent.JobEventType): + Required. The type of the event (see + [JobEventType][google.cloud.talent.v4.JobEvent.JobEventType]). + jobs (Sequence[str]): + Required. The [job name(s)][google.cloud.talent.v4.Job.name] + associated with this event. For example, if this is an + [impression][google.cloud.talent.v4.JobEvent.JobEventType.IMPRESSION] + event, this field contains the identifiers of all jobs shown + to the job seeker. If this was a + [view][google.cloud.talent.v4.JobEvent.JobEventType.VIEW] + event, this field contains the identifier of the viewed job. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}", + for example, "projects/foo/tenants/bar/jobs/baz". + """ + + class JobEventType(proto.Enum): + r"""An enumeration of an event attributed to the behavior of the + end user, such as a job seeker. + """ + JOB_EVENT_TYPE_UNSPECIFIED = 0 + IMPRESSION = 1 + VIEW = 2 + VIEW_REDIRECT = 3 + APPLICATION_START = 4 + APPLICATION_FINISH = 5 + APPLICATION_QUICK_SUBMISSION = 6 + APPLICATION_REDIRECT = 7 + APPLICATION_START_FROM_SEARCH = 8 + APPLICATION_REDIRECT_FROM_SEARCH = 9 + APPLICATION_COMPANY_SUBMIT = 10 + BOOKMARK = 11 + NOTIFICATION = 12 + HIRED = 13 + SENT_CV = 14 + INTERVIEW_GRANTED = 15 + + type_ = proto.Field(proto.ENUM, number=1, enum=JobEventType,) + + jobs = proto.RepeatedField(proto.STRING, number=2) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/event_service.py b/google/cloud/talent_v4/types/event_service.py new file mode 100644 index 00000000..264d2f2d --- /dev/null +++ b/google/cloud/talent_v4/types/event_service.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import event + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", manifest={"CreateClientEventRequest",}, +) + + +class CreateClientEventRequest(proto.Message): + r"""The report event request. + + Attributes: + parent (str): + Required. Resource name of the tenant under which the event + is created. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + client_event (~.event.ClientEvent): + Required. Events issued when end user + interacts with customer's application that uses + Cloud Talent Solution. + """ + + parent = proto.Field(proto.STRING, number=1) + + client_event = proto.Field(proto.MESSAGE, number=2, message=event.ClientEvent,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/filters.py b/google/cloud/talent_v4/types/filters.py new file mode 100644 index 00000000..62a8aa57 --- /dev/null +++ b/google/cloud/talent_v4/types/filters.py @@ -0,0 +1,396 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common +from google.protobuf import duration_pb2 as duration # type: ignore +from google.type import latlng_pb2 as latlng # type: ignore +from google.type import timeofday_pb2 as timeofday # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={"JobQuery", "LocationFilter", "CompensationFilter", "CommuteFilter",}, +) + + +class JobQuery(proto.Message): + r"""The query required to perform a search query. + + Attributes: + query (str): + The query string that matches against the job + title, description, and location fields. + + The maximum number of allowed characters is 255. + query_language_code (str): + The language code of + [query][google.cloud.talent.v4.JobQuery.query]. For example, + "en-US". This field helps to better interpret the query. + + If a value isn't specified, the query language code is + automatically detected, which may not be accurate. + + Language code should be in BCP-47 format, such as "en-US" or + "sr-Latn". For more information, see `Tags for Identifying + Languages `__. + companies (Sequence[str]): + This filter specifies the company entities to search + against. + + If a value isn't specified, jobs are searched for against + all companies. + + If multiple values are specified, jobs are searched against + the companies specified. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}". + For example, "projects/foo/tenants/bar/companies/baz". + + At most 20 company filters are allowed. + location_filters (Sequence[~.filters.LocationFilter]): + The location filter specifies geo-regions containing the + jobs to search against. See + [LocationFilter][google.cloud.talent.v4.LocationFilter] for + more information. + + If a location value isn't specified, jobs fitting the other + search criteria are retrieved regardless of where they're + located. + + If multiple values are specified, jobs are retrieved from + any of the specified locations. If different values are + specified for the + [LocationFilter.distance_in_miles][google.cloud.talent.v4.LocationFilter.distance_in_miles] + parameter, the maximum provided distance is used for all + locations. + + At most 5 location filters are allowed. + job_categories (Sequence[~.common.JobCategory]): + The category filter specifies the categories of jobs to + search against. See + [JobCategory][google.cloud.talent.v4.JobCategory] for more + information. + + If a value isn't specified, jobs from any category are + searched against. + + If multiple values are specified, jobs from any of the + specified categories are searched against. + commute_filter (~.filters.CommuteFilter): + Allows filtering jobs by commute time with different travel + methods (for example, driving or public transit). + + Note: This only works when you specify a + [CommuteMethod][google.cloud.talent.v4.CommuteMethod]. In + this case, + [location_filters][google.cloud.talent.v4.JobQuery.location_filters] + is ignored. + + Currently we don't support sorting by commute time. + company_display_names (Sequence[str]): + This filter specifies the exact company + [Company.display_name][google.cloud.talent.v4.Company.display_name] + of the jobs to search against. + + If a value isn't specified, jobs within the search results + are associated with any company. + + If multiple values are specified, jobs within the search + results may be associated with any of the specified + companies. + + At most 20 company display name filters are allowed. + compensation_filter (~.filters.CompensationFilter): + This search filter is applied only to + [Job.compensation_info][google.cloud.talent.v4.Job.compensation_info]. + For example, if the filter is specified as "Hourly job with + per-hour compensation > $15", only jobs meeting these + criteria are searched. If a filter isn't defined, all open + jobs are searched. + custom_attribute_filter (str): + This filter specifies a structured syntax to match against + the + [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes] + marked as ``filterable``. + + The syntax for this expression is a subset of SQL syntax. + + Supported operators are: ``=``, ``!=``, ``<``, ``<=``, + ``>``, and ``>=`` where the left of the operator is a custom + field key and the right of the operator is a number or a + quoted string. You must escape backslash (\) and quote (") + characters. + + Supported functions are ``LOWER([field_name])`` to perform a + case insensitive match and ``EMPTY([field_name])`` to filter + on the existence of a key. + + Boolean expressions (AND/OR/NOT) are supported up to 3 + levels of nesting (for example, "((A AND B AND C) OR NOT D) + AND E"), a maximum of 100 comparisons or functions are + allowed in the expression. The expression must be < 6000 + bytes in length. + + Sample Query: + ``(LOWER(driving_license)="class \"a\"" OR EMPTY(driving_license)) AND driving_years > 10`` + disable_spell_check (bool): + This flag controls the spell-check feature. + If false, the service attempts to correct a + misspelled query, for example, "enginee" is + corrected to "engineer". + Defaults to false: a spell check is performed. + employment_types (Sequence[~.common.EmploymentType]): + The employment type filter specifies the employment type of + jobs to search against, such as + [EmploymentType.FULL_TIME][google.cloud.talent.v4.EmploymentType.FULL_TIME]. + + If a value isn't specified, jobs in the search results + includes any employment type. + + If multiple values are specified, jobs in the search results + include any of the specified employment types. + language_codes (Sequence[str]): + This filter specifies the locale of jobs to search against, + for example, "en-US". + + If a value isn't specified, the search results can contain + jobs in any locale. + + Language codes should be in BCP-47 format, such as "en-US" + or "sr-Latn". For more information, see `Tags for + Identifying + Languages `__. + + At most 10 language code filters are allowed. + publish_time_range (~.common.TimestampRange): + Jobs published within a range specified by + this filter are searched against. + excluded_jobs (Sequence[str]): + This filter specifies a list of job names to + be excluded during search. + At most 400 excluded job names are allowed. + """ + + query = proto.Field(proto.STRING, number=1) + + query_language_code = proto.Field(proto.STRING, number=14) + + companies = proto.RepeatedField(proto.STRING, number=2) + + location_filters = proto.RepeatedField( + proto.MESSAGE, number=3, message="LocationFilter", + ) + + job_categories = proto.RepeatedField(proto.ENUM, number=4, enum=common.JobCategory,) + + commute_filter = proto.Field(proto.MESSAGE, number=5, message="CommuteFilter",) + + company_display_names = proto.RepeatedField(proto.STRING, number=6) + + compensation_filter = proto.Field( + proto.MESSAGE, number=7, message="CompensationFilter", + ) + + custom_attribute_filter = proto.Field(proto.STRING, number=8) + + disable_spell_check = proto.Field(proto.BOOL, number=9) + + employment_types = proto.RepeatedField( + proto.ENUM, number=10, enum=common.EmploymentType, + ) + + language_codes = proto.RepeatedField(proto.STRING, number=11) + + publish_time_range = proto.Field( + proto.MESSAGE, number=12, message=common.TimestampRange, + ) + + excluded_jobs = proto.RepeatedField(proto.STRING, number=13) + + +class LocationFilter(proto.Message): + r"""Geographic region of the search. + + Attributes: + address (str): + The address name, such as "Mountain View" or + "Bay Area". + region_code (str): + CLDR region code of the country/region of the address. This + is used to address ambiguity of the user-input location, for + example, "Liverpool" against "Liverpool, NY, US" or + "Liverpool, UK". + + Set this field to bias location resolution toward a specific + country or territory. If this field is not set, application + behavior is biased toward the United States by default. + + See + https://www.unicode.org/cldr/charts/30/supplemental/territory_information.html + for details. Example: "CH" for Switzerland. + lat_lng (~.latlng.LatLng): + The latitude and longitude of the geographic center to + search from. This field is ignored if ``address`` is + provided. + distance_in_miles (float): + The distance_in_miles is applied when the location being + searched for is identified as a city or smaller. This field + is ignored if the location being searched for is a state or + larger. + telecommute_preference (~.filters.LocationFilter.TelecommutePreference): + Allows the client to return jobs without a set location, + specifically, telecommuting jobs (telecommuting is + considered by the service as a special location. + [Job.posting_region][google.cloud.talent.v4.Job.posting_region] + indicates if a job permits telecommuting. If this field is + set to + [TelecommutePreference.TELECOMMUTE_ALLOWED][google.cloud.talent.v4.LocationFilter.TelecommutePreference.TELECOMMUTE_ALLOWED], + telecommuting jobs are searched, and + [address][google.cloud.talent.v4.LocationFilter.address] and + [lat_lng][google.cloud.talent.v4.LocationFilter.lat_lng] are + ignored. If not set or set to + [TelecommutePreference.TELECOMMUTE_EXCLUDED][google.cloud.talent.v4.LocationFilter.TelecommutePreference.TELECOMMUTE_EXCLUDED], + telecommute job are not searched. + + This filter can be used by itself to search exclusively for + telecommuting jobs, or it can be combined with another + location filter to search for a combination of job + locations, such as "Mountain View" or "telecommuting" jobs. + However, when used in combination with other location + filters, telecommuting jobs can be treated as less relevant + than other jobs in the search response. + + This field is only used for job search requests. + """ + + class TelecommutePreference(proto.Enum): + r"""Specify whether to include telecommute jobs.""" + TELECOMMUTE_PREFERENCE_UNSPECIFIED = 0 + TELECOMMUTE_EXCLUDED = 1 + TELECOMMUTE_ALLOWED = 2 + + address = proto.Field(proto.STRING, number=1) + + region_code = proto.Field(proto.STRING, number=2) + + lat_lng = proto.Field(proto.MESSAGE, number=3, message=latlng.LatLng,) + + distance_in_miles = proto.Field(proto.DOUBLE, number=4) + + telecommute_preference = proto.Field( + proto.ENUM, number=5, enum=TelecommutePreference, + ) + + +class CompensationFilter(proto.Message): + r"""Filter on job compensation type and amount. + + Attributes: + type_ (~.filters.CompensationFilter.FilterType): + Required. Type of filter. + units (Sequence[~.common.CompensationInfo.CompensationUnit]): + Required. Specify desired ``base compensation entry's`` + [CompensationInfo.CompensationUnit][google.cloud.talent.v4.CompensationInfo.CompensationUnit]. + range_ (~.common.CompensationInfo.CompensationRange): + Compensation range. + include_jobs_with_unspecified_compensation_range (bool): + If set to true, jobs with unspecified + compensation range fields are included. + """ + + class FilterType(proto.Enum): + r"""Specify the type of filtering.""" + FILTER_TYPE_UNSPECIFIED = 0 + UNIT_ONLY = 1 + UNIT_AND_AMOUNT = 2 + ANNUALIZED_BASE_AMOUNT = 3 + ANNUALIZED_TOTAL_AMOUNT = 4 + + type_ = proto.Field(proto.ENUM, number=1, enum=FilterType,) + + units = proto.RepeatedField( + proto.ENUM, number=2, enum=common.CompensationInfo.CompensationUnit, + ) + + range_ = proto.Field( + proto.MESSAGE, number=3, message=common.CompensationInfo.CompensationRange, + ) + + include_jobs_with_unspecified_compensation_range = proto.Field(proto.BOOL, number=4) + + +class CommuteFilter(proto.Message): + r"""Parameters needed for commute search. + + Attributes: + commute_method (~.common.CommuteMethod): + Required. The method of transportation to + calculate the commute time for. + start_coordinates (~.latlng.LatLng): + Required. The latitude and longitude of the + location to calculate the commute time from. + travel_duration (~.duration.Duration): + Required. The maximum travel time in seconds. The maximum + allowed value is ``3600s`` (one hour). Format is ``123s``. + allow_imprecise_addresses (bool): + If ``true``, jobs without street level addresses may also be + returned. For city level addresses, the city center is used. + For state and coarser level addresses, text matching is + used. If this field is set to ``false`` or isn't specified, + only jobs that include street level addresses will be + returned by commute search. + road_traffic (~.filters.CommuteFilter.RoadTraffic): + Specifies the traffic density to use when + calculating commute time. + departure_time (~.timeofday.TimeOfDay): + The departure time used to calculate traffic impact, + represented as + [google.type.TimeOfDay][google.type.TimeOfDay] in local time + zone. + + Currently traffic model is restricted to hour level + resolution. + """ + + class RoadTraffic(proto.Enum): + r"""The traffic density to use when calculating commute time.""" + ROAD_TRAFFIC_UNSPECIFIED = 0 + TRAFFIC_FREE = 1 + BUSY_HOUR = 2 + + commute_method = proto.Field(proto.ENUM, number=1, enum=common.CommuteMethod,) + + start_coordinates = proto.Field(proto.MESSAGE, number=2, message=latlng.LatLng,) + + travel_duration = proto.Field(proto.MESSAGE, number=3, message=duration.Duration,) + + allow_imprecise_addresses = proto.Field(proto.BOOL, number=4) + + road_traffic = proto.Field( + proto.ENUM, number=5, oneof="traffic_option", enum=RoadTraffic, + ) + + departure_time = proto.Field( + proto.MESSAGE, number=6, oneof="traffic_option", message=timeofday.TimeOfDay, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/histogram.py b/google/cloud/talent_v4/types/histogram.py new file mode 100644 index 00000000..ac6443df --- /dev/null +++ b/google/cloud/talent_v4/types/histogram.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={"HistogramQuery", "HistogramQueryResult",}, +) + + +class HistogramQuery(proto.Message): + r"""The histogram request. + + Attributes: + histogram_query (str): + An expression specifies a histogram request against matching + jobs for searches. + + See + [SearchJobsRequest.histogram_queries][google.cloud.talent.v4.SearchJobsRequest.histogram_queries] + for details about syntax. + """ + + histogram_query = proto.Field(proto.STRING, number=1) + + +class HistogramQueryResult(proto.Message): + r"""Histogram result that matches + [HistogramQuery][google.cloud.talent.v4.HistogramQuery] specified in + searches. + + Attributes: + histogram_query (str): + Requested histogram expression. + histogram (Sequence[~.gct_histogram.HistogramQueryResult.HistogramEntry]): + A map from the values of the facet associated with distinct + values to the number of matching entries with corresponding + value. + + The key format is: + + - (for string histogram) string values stored in the field. + - (for named numeric bucket) name specified in ``bucket()`` + function, like for ``bucket(0, MAX, "non-negative")``, + the key will be ``non-negative``. + - (for anonymous numeric bucket) range formatted as + ``-``, for example, ``0-1000``, ``MIN-0``, and + ``0-MAX``. + """ + + histogram_query = proto.Field(proto.STRING, number=1) + + histogram = proto.MapField(proto.STRING, proto.INT64, number=2) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/job.py b/google/cloud/talent_v4/types/job.py new file mode 100644 index 00000000..f1320f94 --- /dev/null +++ b/google/cloud/talent_v4/types/job.py @@ -0,0 +1,479 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + + +__protobuf__ = proto.module(package="google.cloud.talent.v4", manifest={"Job",},) + + +class Job(proto.Message): + r"""A Job resource represents a job posting (also referred to as a "job + listing" or "job requisition"). A job belongs to a + [Company][google.cloud.talent.v4.Company], which is the hiring + entity responsible for the job. + + Attributes: + name (str): + Required during job update. + + The resource name for the job. This is generated by the + service when a job is created. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + + Use of this field in job queries and API calls is preferred + over the use of + [requisition_id][google.cloud.talent.v4.Job.requisition_id] + since this value is unique. + company (str): + Required. The resource name of the company listing the job. + + The format is + "projects/{project_id}/tenants/{tenant_id}/companies/{company_id}". + For example, "projects/foo/tenants/bar/companies/baz". + requisition_id (str): + Required. The requisition ID, also referred to as the + posting ID, is assigned by the client to identify a job. + This field is intended to be used by clients for client + identification and tracking of postings. A job isn't allowed + to be created if there is another job with the same + [company][google.cloud.talent.v4.Job.name], + [language_code][google.cloud.talent.v4.Job.language_code] + and + [requisition_id][google.cloud.talent.v4.Job.requisition_id]. + + The maximum number of allowed characters is 255. + title (str): + Required. The title of the job, such as + "Software Engineer" + The maximum number of allowed characters is 500. + description (str): + Required. The description of the job, which typically + includes a multi-paragraph description of the company and + related information. Separate fields are provided on the job + object for + [responsibilities][google.cloud.talent.v4.Job.responsibilities], + [qualifications][google.cloud.talent.v4.Job.qualifications], + and other job characteristics. Use of these separate job + fields is recommended. + + This field accepts and sanitizes HTML input, and also + accepts bold, italic, ordered list, and unordered list + markup tags. + + The maximum number of allowed characters is 100,000. + addresses (Sequence[str]): + Strongly recommended for the best service experience. + + Location(s) where the employer is looking to hire for this + job posting. + + Specifying the full street address(es) of the hiring + location enables better API results, especially job searches + by commute time. + + At most 50 locations are allowed for best search + performance. If a job has more locations, it is suggested to + split it into multiple jobs with unique + [requisition_id][google.cloud.talent.v4.Job.requisition_id]s + (e.g. 'ReqA' becomes 'ReqA-1', 'ReqA-2', and so on.) as + multiple jobs with the same + [company][google.cloud.talent.v4.Job.company], + [language_code][google.cloud.talent.v4.Job.language_code] + and + [requisition_id][google.cloud.talent.v4.Job.requisition_id] + are not allowed. If the original + [requisition_id][google.cloud.talent.v4.Job.requisition_id] + must be preserved, a custom field should be used for + storage. It is also suggested to group the locations that + close to each other in the same job for better search + experience. + + The maximum number of allowed characters is 500. + application_info (~.job.Job.ApplicationInfo): + Job application information. + job_benefits (Sequence[~.common.JobBenefit]): + The benefits included with the job. + compensation_info (~.common.CompensationInfo): + Job compensation information (a.k.a. "pay + rate") i.e., the compensation that will paid to + the employee. + custom_attributes (Sequence[~.job.Job.CustomAttributesEntry]): + A map of fields to hold both filterable and non-filterable + custom job attributes that are not covered by the provided + structured fields. + + The keys of the map are strings up to 64 bytes and must + match the pattern: [a-zA-Z][a-zA-Z0-9\_]*. For example, + key0LikeThis or KEY_1_LIKE_THIS. + + At most 100 filterable and at most 100 unfilterable keys are + supported. For filterable ``string_values``, across all keys + at most 200 values are allowed, with each string no more + than 255 characters. For unfilterable ``string_values``, the + maximum total size of ``string_values`` across all keys is + 50KB. + degree_types (Sequence[~.common.DegreeType]): + The desired education degrees for the job, + such as Bachelors, Masters. + department (str): + The department or functional area within the + company with the open position. + + The maximum number of allowed characters is 255. + employment_types (Sequence[~.common.EmploymentType]): + The employment type(s) of a job, for example, [full + time][google.cloud.talent.v4.EmploymentType.FULL_TIME] or + [part + time][google.cloud.talent.v4.EmploymentType.PART_TIME]. + incentives (str): + A description of bonus, commission, and other + compensation incentives associated with the job + not including salary or pay. + The maximum number of allowed characters is + 10,000. + language_code (str): + The language of the posting. This field is distinct from any + requirements for fluency that are associated with the job. + + Language codes must be in BCP-47 format, such as "en-US" or + "sr-Latn". For more information, see `Tags for Identifying + Languages `__\ {: + class="external" target="_blank" }. + + If this field is unspecified and + [Job.description][google.cloud.talent.v4.Job.description] is + present, detected language code based on + [Job.description][google.cloud.talent.v4.Job.description] is + assigned, otherwise defaults to 'en_US'. + job_level (~.common.JobLevel): + The experience level associated with the job, + such as "Entry Level". + promotion_value (int): + A promotion value of the job, as determined by the client. + The value determines the sort order of the jobs returned + when searching for jobs using the featured jobs search call, + with higher promotional values being returned first and ties + being resolved by relevance sort. Only the jobs with a + promotionValue >0 are returned in a FEATURED_JOB_SEARCH. + + Default value is 0, and negative values are treated as 0. + qualifications (str): + A description of the qualifications required to perform the + job. The use of this field is recommended as an alternative + to using the more general + [description][google.cloud.talent.v4.Job.description] field. + + This field accepts and sanitizes HTML input, and also + accepts bold, italic, ordered list, and unordered list + markup tags. + + The maximum number of allowed characters is 10,000. + responsibilities (str): + A description of job responsibilities. The use of this field + is recommended as an alternative to using the more general + [description][google.cloud.talent.v4.Job.description] field. + + This field accepts and sanitizes HTML input, and also + accepts bold, italic, ordered list, and unordered list + markup tags. + + The maximum number of allowed characters is 10,000. + posting_region (~.common.PostingRegion): + The job + [PostingRegion][google.cloud.talent.v4.PostingRegion] (for + example, state, country) throughout which the job is + available. If this field is set, a + [LocationFilter][google.cloud.talent.v4.LocationFilter] in a + search query within the job region finds this job posting if + an exact location match isn't specified. If this field is + set to + [PostingRegion.NATION][google.cloud.talent.v4.PostingRegion.NATION] + or + [PostingRegion.ADMINISTRATIVE_AREA][google.cloud.talent.v4.PostingRegion.ADMINISTRATIVE_AREA], + setting job + [Job.addresses][google.cloud.talent.v4.Job.addresses] to the + same location level as this field is strongly recommended. + visibility (~.common.Visibility): + Deprecated. The job is only visible to the owner. + + The visibility of the job. + + Defaults to + [Visibility.ACCOUNT_ONLY][google.cloud.talent.v4.Visibility.ACCOUNT_ONLY] + if not specified. + job_start_time (~.timestamp.Timestamp): + The start timestamp of the job in UTC time + zone. Typically this field is used for + contracting engagements. Invalid timestamps are + ignored. + job_end_time (~.timestamp.Timestamp): + The end timestamp of the job. Typically this + field is used for contracting engagements. + Invalid timestamps are ignored. + posting_publish_time (~.timestamp.Timestamp): + The timestamp this job posting was most + recently published. The default value is the + time the request arrives at the server. Invalid + timestamps are ignored. + posting_expire_time (~.timestamp.Timestamp): + Strongly recommended for the best service experience. + + The expiration timestamp of the job. After this timestamp, + the job is marked as expired, and it no longer appears in + search results. The expired job can't be listed by the + [ListJobs][google.cloud.talent.v4.JobService.ListJobs] API, + but it can be retrieved with the + [GetJob][google.cloud.talent.v4.JobService.GetJob] API or + updated with the + [UpdateJob][google.cloud.talent.v4.JobService.UpdateJob] API + or deleted with the + [DeleteJob][google.cloud.talent.v4.JobService.DeleteJob] + API. An expired job can be updated and opened again by using + a future expiration timestamp. Updating an expired job fails + if there is another existing open job with same + [company][google.cloud.talent.v4.Job.company], + [language_code][google.cloud.talent.v4.Job.language_code] + and + [requisition_id][google.cloud.talent.v4.Job.requisition_id]. + + The expired jobs are retained in our system for 90 days. + However, the overall expired job count cannot exceed 3 times + the maximum number of open jobs over previous 7 days. If + this threshold is exceeded, expired jobs are cleaned out in + order of earliest expire time. Expired jobs are no longer + accessible after they are cleaned out. + + Invalid timestamps are ignored, and treated as expire time + not provided. + + If the timestamp is before the instant request is made, the + job is treated as expired immediately on creation. This kind + of job can not be updated. And when creating a job with past + timestamp, the + [posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + must be set before + [posting_expire_time][google.cloud.talent.v4.Job.posting_expire_time]. + The purpose of this feature is to allow other objects, such + as [Application][], to refer a job that didn't exist in the + system prior to becoming expired. If you want to modify a + job that was expired on creation, delete it and create a new + one. + + If this value isn't provided at the time of job creation or + is invalid, the job posting expires after 30 days from the + job's creation time. For example, if the job was created on + 2017/01/01 13:00AM UTC with an unspecified expiration date, + the job expires after 2017/01/31 13:00AM UTC. + + If this value isn't provided on job update, it depends on + the field masks set by + [UpdateJobRequest.update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask]. + If the field masks include + [job_end_time][google.cloud.talent.v4.Job.job_end_time], or + the masks are empty meaning that every field is updated, the + job posting expires after 30 days from the job's last update + time. Otherwise the expiration date isn't updated. + posting_create_time (~.timestamp.Timestamp): + Output only. The timestamp when this job + posting was created. + posting_update_time (~.timestamp.Timestamp): + Output only. The timestamp when this job + posting was last updated. + company_display_name (str): + Output only. Display name of the company + listing the job. + derived_info (~.job.Job.DerivedInfo): + Output only. Derived details about the job + posting. + processing_options (~.job.Job.ProcessingOptions): + Options for job processing. + """ + + class ApplicationInfo(proto.Message): + r"""Application related details of a job posting. + + Attributes: + emails (Sequence[str]): + Use this field to specify email address(es) + to which resumes or applications can be sent. + The maximum number of allowed characters for + each entry is 255. + instruction (str): + Use this field to provide instructions, such + as "Mail your application to ...", that a + candidate can follow to apply for the job. + This field accepts and sanitizes HTML input, and + also accepts bold, italic, ordered list, and + unordered list markup tags. + The maximum number of allowed characters is + 3,000. + uris (Sequence[str]): + Use this URI field to direct an applicant to + a website, for example to link to an online + application form. + The maximum number of allowed characters for + each entry is 2,000. + """ + + emails = proto.RepeatedField(proto.STRING, number=1) + + instruction = proto.Field(proto.STRING, number=2) + + uris = proto.RepeatedField(proto.STRING, number=3) + + class DerivedInfo(proto.Message): + r"""Derived details about the job posting. + + Attributes: + locations (Sequence[~.common.Location]): + Structured locations of the job, resolved from + [Job.addresses][google.cloud.talent.v4.Job.addresses]. + + [locations][google.cloud.talent.v4.Job.DerivedInfo.locations] + are exactly matched to + [Job.addresses][google.cloud.talent.v4.Job.addresses] in the + same order. + job_categories (Sequence[~.common.JobCategory]): + Job categories derived from + [Job.title][google.cloud.talent.v4.Job.title] and + [Job.description][google.cloud.talent.v4.Job.description]. + """ + + locations = proto.RepeatedField( + proto.MESSAGE, number=1, message=common.Location, + ) + + job_categories = proto.RepeatedField( + proto.ENUM, number=3, enum=common.JobCategory, + ) + + class ProcessingOptions(proto.Message): + r"""Options for job processing. + + Attributes: + disable_street_address_resolution (bool): + If set to ``true``, the service does not attempt to resolve + a more precise address for the job. + html_sanitization (~.common.HtmlSanitization): + Option for job HTML content sanitization. Applied fields + are: + + - description + - applicationInfo.instruction + - incentives + - qualifications + - responsibilities + + HTML tags in these fields may be stripped if sanitiazation + isn't disabled. + + Defaults to + [HtmlSanitization.SIMPLE_FORMATTING_ONLY][google.cloud.talent.v4.HtmlSanitization.SIMPLE_FORMATTING_ONLY]. + """ + + disable_street_address_resolution = proto.Field(proto.BOOL, number=1) + + html_sanitization = proto.Field( + proto.ENUM, number=2, enum=common.HtmlSanitization, + ) + + name = proto.Field(proto.STRING, number=1) + + company = proto.Field(proto.STRING, number=2) + + requisition_id = proto.Field(proto.STRING, number=3) + + title = proto.Field(proto.STRING, number=4) + + description = proto.Field(proto.STRING, number=5) + + addresses = proto.RepeatedField(proto.STRING, number=6) + + application_info = proto.Field(proto.MESSAGE, number=7, message=ApplicationInfo,) + + job_benefits = proto.RepeatedField(proto.ENUM, number=8, enum=common.JobBenefit,) + + compensation_info = proto.Field( + proto.MESSAGE, number=9, message=common.CompensationInfo, + ) + + custom_attributes = proto.MapField( + proto.STRING, proto.MESSAGE, number=10, message=common.CustomAttribute, + ) + + degree_types = proto.RepeatedField(proto.ENUM, number=11, enum=common.DegreeType,) + + department = proto.Field(proto.STRING, number=12) + + employment_types = proto.RepeatedField( + proto.ENUM, number=13, enum=common.EmploymentType, + ) + + incentives = proto.Field(proto.STRING, number=14) + + language_code = proto.Field(proto.STRING, number=15) + + job_level = proto.Field(proto.ENUM, number=16, enum=common.JobLevel,) + + promotion_value = proto.Field(proto.INT32, number=17) + + qualifications = proto.Field(proto.STRING, number=18) + + responsibilities = proto.Field(proto.STRING, number=19) + + posting_region = proto.Field(proto.ENUM, number=20, enum=common.PostingRegion,) + + visibility = proto.Field(proto.ENUM, number=21, enum=common.Visibility,) + + job_start_time = proto.Field(proto.MESSAGE, number=22, message=timestamp.Timestamp,) + + job_end_time = proto.Field(proto.MESSAGE, number=23, message=timestamp.Timestamp,) + + posting_publish_time = proto.Field( + proto.MESSAGE, number=24, message=timestamp.Timestamp, + ) + + posting_expire_time = proto.Field( + proto.MESSAGE, number=25, message=timestamp.Timestamp, + ) + + posting_create_time = proto.Field( + proto.MESSAGE, number=26, message=timestamp.Timestamp, + ) + + posting_update_time = proto.Field( + proto.MESSAGE, number=27, message=timestamp.Timestamp, + ) + + company_display_name = proto.Field(proto.STRING, number=28) + + derived_info = proto.Field(proto.MESSAGE, number=29, message=DerivedInfo,) + + processing_options = proto.Field( + proto.MESSAGE, number=30, message=ProcessingOptions, + ) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/job_service.py b/google/cloud/talent_v4/types/job_service.py new file mode 100644 index 00000000..4a1b765a --- /dev/null +++ b/google/cloud/talent_v4/types/job_service.py @@ -0,0 +1,923 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import filters +from google.cloud.talent_v4.types import histogram +from google.cloud.talent_v4.types import job as gct_job +from google.protobuf import duration_pb2 as duration # type: ignore +from google.protobuf import field_mask_pb2 as field_mask # type: ignore +from google.rpc import status_pb2 as gr_status # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={ + "JobView", + "CreateJobRequest", + "GetJobRequest", + "UpdateJobRequest", + "DeleteJobRequest", + "ListJobsRequest", + "ListJobsResponse", + "SearchJobsRequest", + "SearchJobsResponse", + "BatchCreateJobsRequest", + "BatchUpdateJobsRequest", + "BatchDeleteJobsRequest", + "JobResult", + "BatchCreateJobsResponse", + "BatchUpdateJobsResponse", + "BatchDeleteJobsResponse", + }, +) + + +class JobView(proto.Enum): + r"""An enum that specifies the job attributes that are returned in the + [MatchingJob.job][google.cloud.talent.v4.SearchJobsResponse.MatchingJob.job] + or + [ListJobsResponse.jobs][google.cloud.talent.v4.ListJobsResponse.jobs] + fields. + """ + JOB_VIEW_UNSPECIFIED = 0 + JOB_VIEW_ID_ONLY = 1 + JOB_VIEW_MINIMAL = 2 + JOB_VIEW_SMALL = 3 + JOB_VIEW_FULL = 4 + + +class CreateJobRequest(proto.Message): + r"""Create job request. + + Attributes: + parent (str): + Required. The resource name of the tenant under which the + job is created. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + job (~.gct_job.Job): + Required. The Job to be created. + """ + + parent = proto.Field(proto.STRING, number=1) + + job = proto.Field(proto.MESSAGE, number=2, message=gct_job.Job,) + + +class GetJobRequest(proto.Message): + r"""Get job request. + + Attributes: + name (str): + Required. The resource name of the job to retrieve. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + """ + + name = proto.Field(proto.STRING, number=1) + + +class UpdateJobRequest(proto.Message): + r"""Update job request. + + Attributes: + job (~.gct_job.Job): + Required. The Job to be updated. + update_mask (~.field_mask.FieldMask): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateJobRequest.update_mask] + is provided, only the specified fields in + [job][google.cloud.talent.v4.UpdateJobRequest.job] are + updated. Otherwise all the fields are updated. + + A field mask to restrict the fields that are updated. Only + top level fields of [Job][google.cloud.talent.v4.Job] are + supported. + """ + + job = proto.Field(proto.MESSAGE, number=1, message=gct_job.Job,) + + update_mask = proto.Field(proto.MESSAGE, number=2, message=field_mask.FieldMask,) + + +class DeleteJobRequest(proto.Message): + r"""Delete job request. + + Attributes: + name (str): + Required. The resource name of the job to be deleted. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + """ + + name = proto.Field(proto.STRING, number=1) + + +class ListJobsRequest(proto.Message): + r"""List jobs request. + + Attributes: + parent (str): + Required. The resource name of the tenant under which the + job is created. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + filter (str): + Required. The filter string specifies the jobs to be + enumerated. + + Supported operator: =, AND + + The fields eligible for filtering are: + + - ``companyName`` (Required) + - ``requisitionId`` + - ``status`` Available values: OPEN, EXPIRED, ALL. Defaults + to OPEN if no value is specified. + + Sample Query: + + - companyName = "projects/foo/tenants/bar/companies/baz" + - companyName = "projects/foo/tenants/bar/companies/baz" + AND requisitionId = "req-1" + - companyName = "projects/foo/tenants/bar/companies/baz" + AND status = "EXPIRED". + page_token (str): + The starting point of a query result. + page_size (int): + The maximum number of jobs to be returned per page of + results. + + If + [job_view][google.cloud.talent.v4.ListJobsRequest.job_view] + is set to + [JobView.JOB_VIEW_ID_ONLY][google.cloud.talent.v4.JobView.JOB_VIEW_ID_ONLY], + the maximum allowed page size is 1000. Otherwise, the + maximum allowed page size is 100. + + Default is 100 if empty or a number < 1 is specified. + job_view (~.job_service.JobView): + The desired job attributes returned for jobs in the search + response. Defaults to + [JobView.JOB_VIEW_FULL][google.cloud.talent.v4.JobView.JOB_VIEW_FULL] + if no value is specified. + """ + + parent = proto.Field(proto.STRING, number=1) + + filter = proto.Field(proto.STRING, number=2) + + page_token = proto.Field(proto.STRING, number=3) + + page_size = proto.Field(proto.INT32, number=4) + + job_view = proto.Field(proto.ENUM, number=5, enum="JobView",) + + +class ListJobsResponse(proto.Message): + r"""List jobs response. + + Attributes: + jobs (Sequence[~.gct_job.Job]): + The Jobs for a given company. + The maximum number of items returned is based on + the limit field provided in the request. + next_page_token (str): + A token to retrieve the next page of results. + metadata (~.common.ResponseMetadata): + Additional information for the API + invocation, such as the request tracking id. + """ + + @property + def raw_page(self): + return self + + jobs = proto.RepeatedField(proto.MESSAGE, number=1, message=gct_job.Job,) + + next_page_token = proto.Field(proto.STRING, number=2) + + metadata = proto.Field(proto.MESSAGE, number=3, message=common.ResponseMetadata,) + + +class SearchJobsRequest(proto.Message): + r"""The Request body of the ``SearchJobs`` call. + + Attributes: + parent (str): + Required. The resource name of the tenant to search within. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + search_mode (~.job_service.SearchJobsRequest.SearchMode): + Mode of a search. + + Defaults to + [SearchMode.JOB_SEARCH][google.cloud.talent.v4.SearchJobsRequest.SearchMode.JOB_SEARCH]. + request_metadata (~.common.RequestMetadata): + Required. The meta information collected about the job + searcher, used to improve the search quality of the service. + The identifiers (such as ``user_id``) are provided by users, + and must be unique and consistent. + job_query (~.filters.JobQuery): + Query used to search against jobs, such as + keyword, location filters, etc. + enable_broadening (bool): + Controls whether to broaden the search when + it produces sparse results. Broadened queries + append results to the end of the matching + results list. + + Defaults to false. + histogram_queries (Sequence[~.histogram.HistogramQuery]): + An expression specifies a histogram request against matching + jobs. + + Expression syntax is an aggregation function call with + histogram facets and other options. + + Available aggregation function calls are: + + - ``count(string_histogram_facet)``: Count the number of + matching entities, for each distinct attribute value. + - ``count(numeric_histogram_facet, list of buckets)``: + Count the number of matching entities within each bucket. + + Data types: + + - Histogram facet: facet names with format + [a-zA-Z][a-zA-Z0-9\_]+. + - String: string like "any string with backslash escape for + quote(")." + - Number: whole number and floating point number like 10, + -1 and -0.01. + - List: list of elements with comma(,) separator surrounded + by square brackets, for example, [1, 2, 3] and ["one", + "two", "three"]. + + Built-in constants: + + - MIN (minimum number similar to java Double.MIN_VALUE) + - MAX (maximum number similar to java Double.MAX_VALUE) + + Built-in functions: + + - bucket(start, end[, label]): bucket built-in function + creates a bucket with range of [start, end). Note that + the end is exclusive, for example, bucket(1, MAX, + "positive number") or bucket(1, 10). + + Job histogram facets: + + - company_display_name: histogram by + [Job.company_display_name][google.cloud.talent.v4.Job.company_display_name]. + - employment_type: histogram by + [Job.employment_types][google.cloud.talent.v4.Job.employment_types], + for example, "FULL_TIME", "PART_TIME". + - company_size: histogram by + [CompanySize][google.cloud.talent.v4.CompanySize], for + example, "SMALL", "MEDIUM", "BIG". + - publish_time_in_month: histogram by the + [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + in months. Must specify list of numeric buckets in spec. + - publish_time_in_year: histogram by the + [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + in years. Must specify list of numeric buckets in spec. + - degree_types: histogram by the + [Job.degree_types][google.cloud.talent.v4.Job.degree_types], + for example, "Bachelors", "Masters". + - job_level: histogram by the + [Job.job_level][google.cloud.talent.v4.Job.job_level], + for example, "Entry Level". + - country: histogram by the country code of jobs, for + example, "US", "FR". + - admin1: histogram by the admin1 code of jobs, which is a + global placeholder referring to the state, province, or + the particular term a country uses to define the + geographic structure below the country level, for + example, "CA", "IL". + - city: histogram by a combination of the "city name, + admin1 code". For example, "Mountain View, CA", "New + York, NY". + - admin1_country: histogram by a combination of the "admin1 + code, country", for example, "CA, US", "IL, US". + - city_coordinate: histogram by the city center's GPS + coordinates (latitude and longitude), for example, + 37.4038522,-122.0987765. Since the coordinates of a city + center can change, customers may need to refresh them + periodically. + - locale: histogram by the + [Job.language_code][google.cloud.talent.v4.Job.language_code], + for example, "en-US", "fr-FR". + - language: histogram by the language subtag of the + [Job.language_code][google.cloud.talent.v4.Job.language_code], + for example, "en", "fr". + - category: histogram by the + [JobCategory][google.cloud.talent.v4.JobCategory], for + example, "COMPUTER_AND_IT", "HEALTHCARE". + - base_compensation_unit: histogram by the + [CompensationInfo.CompensationUnit][google.cloud.talent.v4.CompensationInfo.CompensationUnit] + of base salary, for example, "WEEKLY", "MONTHLY". + - base_compensation: histogram by the base salary. Must + specify list of numeric buckets to group results by. + - annualized_base_compensation: histogram by the base + annualized salary. Must specify list of numeric buckets + to group results by. + - annualized_total_compensation: histogram by the total + annualized salary. Must specify list of numeric buckets + to group results by. + - string_custom_attribute: histogram by string + [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes]. + Values can be accessed via square bracket notations like + string_custom_attribute["key1"]. + - numeric_custom_attribute: histogram by numeric + [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes]. + Values can be accessed via square bracket notations like + numeric_custom_attribute["key1"]. Must specify list of + numeric buckets to group results by. + + Example expressions: + + - ``count(admin1)`` + - ``count(base_compensation, [bucket(1000, 10000), bucket(10000, 100000), bucket(100000, MAX)])`` + - ``count(string_custom_attribute["some-string-custom-attribute"])`` + - ``count(numeric_custom_attribute["some-numeric-custom-attribute"], [bucket(MIN, 0, "negative"), bucket(0, MAX, "non-negative"])`` + job_view (~.job_service.JobView): + The desired job attributes returned for jobs in the search + response. Defaults to + [JobView.JOB_VIEW_SMALL][google.cloud.talent.v4.JobView.JOB_VIEW_SMALL] + if no value is specified. + offset (int): + An integer that specifies the current offset (that is, + starting result location, amongst the jobs deemed by the API + as relevant) in search results. This field is only + considered if + [page_token][google.cloud.talent.v4.SearchJobsRequest.page_token] + is unset. + + The maximum allowed value is 5000. Otherwise an error is + thrown. + + For example, 0 means to return results starting from the + first matching job, and 10 means to return from the 11th + job. This can be used for pagination, (for example, pageSize + = 10 and offset = 10 means to return from the second page). + max_page_size (int): + A limit on the number of jobs returned in the + search results. Increasing this value above the + default value of 10 can increase search response + time. The value can be between 1 and 100. + page_token (str): + The token specifying the current offset within search + results. See + [SearchJobsResponse.next_page_token][google.cloud.talent.v4.SearchJobsResponse.next_page_token] + for an explanation of how to obtain the next set of query + results. + order_by (str): + The criteria determining how search results are sorted. + Default is ``"relevance desc"``. + + Supported options are: + + - ``"relevance desc"``: By relevance descending, as + determined by the API algorithms. Relevance thresholding + of query results is only available with this ordering. + - ``"posting_publish_time desc"``: By + [Job.posting_publish_time][google.cloud.talent.v4.Job.posting_publish_time] + descending. + - ``"posting_update_time desc"``: By + [Job.posting_update_time][google.cloud.talent.v4.Job.posting_update_time] + descending. + - ``"title"``: By + [Job.title][google.cloud.talent.v4.Job.title] ascending. + - ``"title desc"``: By + [Job.title][google.cloud.talent.v4.Job.title] descending. + - ``"annualized_base_compensation"``: By job's + [CompensationInfo.annualized_base_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_base_compensation_range] + ascending. Jobs whose annualized base compensation is + unspecified are put at the end of search results. + - ``"annualized_base_compensation desc"``: By job's + [CompensationInfo.annualized_base_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_base_compensation_range] + descending. Jobs whose annualized base compensation is + unspecified are put at the end of search results. + - ``"annualized_total_compensation"``: By job's + [CompensationInfo.annualized_total_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_total_compensation_range] + ascending. Jobs whose annualized base compensation is + unspecified are put at the end of search results. + - ``"annualized_total_compensation desc"``: By job's + [CompensationInfo.annualized_total_compensation_range][google.cloud.talent.v4.CompensationInfo.annualized_total_compensation_range] + descending. Jobs whose annualized base compensation is + unspecified are put at the end of search results. + - ``"custom_ranking desc"``: By the relevance score + adjusted to the + [SearchJobsRequest.CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression] + with weight factor assigned by + [SearchJobsRequest.CustomRankingInfo.importance_level][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.importance_level] + in descending order. + - Location sorting: Use the special syntax to order jobs by + distance: ``"distance_from('Hawaii')"``: Order by + distance from Hawaii. ``"distance_from(19.89, 155.5)"``: + Order by distance from a coordinate. + ``"distance_from('Hawaii'), distance_from('Puerto Rico')"``: + Order by multiple locations. See details below. + ``"distance_from('Hawaii'), distance_from(19.89, 155.5)"``: + Order by multiple locations. See details below. The + string can have a maximum of 256 characters. When + multiple distance centers are provided, a job that is + close to any of the distance centers would have a high + rank. When a job has multiple locations, the job location + closest to one of the distance centers will be used. Jobs + that don't have locations will be ranked at the bottom. + Distance is calculated with a precision of 11.3 meters + (37.4 feet). Diversification strategy is still applied + unless explicitly disabled in + [diversification_level][google.cloud.talent.v4.SearchJobsRequest.diversification_level]. + diversification_level (~.job_service.SearchJobsRequest.DiversificationLevel): + Controls whether highly similar jobs are returned next to + each other in the search results. Jobs are identified as + highly similar based on their titles, job categories, and + locations. Highly similar results are clustered so that only + one representative job of the cluster is displayed to the + job seeker higher up in the results, with the other jobs + being displayed lower down in the results. + + Defaults to + [DiversificationLevel.SIMPLE][google.cloud.talent.v4.SearchJobsRequest.DiversificationLevel.SIMPLE] + if no value is specified. + custom_ranking_info (~.job_service.SearchJobsRequest.CustomRankingInfo): + Controls over how job documents get ranked on + top of existing relevance score (determined by + API algorithm). + disable_keyword_match (bool): + Controls whether to disable exact keyword match on + [Job.title][google.cloud.talent.v4.Job.title], + [Job.description][google.cloud.talent.v4.Job.description], + [Job.company_display_name][google.cloud.talent.v4.Job.company_display_name], + [Job.addresses][google.cloud.talent.v4.Job.addresses], + [Job.qualifications][google.cloud.talent.v4.Job.qualifications]. + When disable keyword match is turned off, a keyword match + returns jobs that do not match given category filters when + there are matching keywords. For example, for the query + "program manager," a result is returned even if the job + posting has the title "software developer," which doesn't + fall into "program manager" ontology, but does have "program + manager" appearing in its description. + + For queries like "cloud" that don't contain title or + location specific ontology, jobs with "cloud" keyword + matches are returned regardless of this flag's value. + + Use + [Company.keyword_searchable_job_custom_attributes][google.cloud.talent.v4.Company.keyword_searchable_job_custom_attributes] + if company-specific globally matched custom field/attribute + string values are needed. Enabling keyword match improves + recall of subsequent search requests. + + Defaults to false. + """ + + class SearchMode(proto.Enum): + r"""A string-represented enumeration of the job search mode. The + service operate differently for different modes of service. + """ + SEARCH_MODE_UNSPECIFIED = 0 + JOB_SEARCH = 1 + FEATURED_JOB_SEARCH = 2 + + class DiversificationLevel(proto.Enum): + r"""Controls whether highly similar jobs are returned next to + each other in the search results. Jobs are identified as highly + similar based on their titles, job categories, and locations. + Highly similar results are clustered so that only one + representative job of the cluster is displayed to the job seeker + higher up in the results, with the other jobs being displayed + lower down in the results. + """ + DIVERSIFICATION_LEVEL_UNSPECIFIED = 0 + DISABLED = 1 + SIMPLE = 2 + + class CustomRankingInfo(proto.Message): + r"""Custom ranking information for + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + + Attributes: + importance_level (~.job_service.SearchJobsRequest.CustomRankingInfo.ImportanceLevel): + Required. Controls over how important the score of + [CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression] + gets applied to job's final ranking position. + + An error is thrown if not specified. + ranking_expression (str): + Required. Controls over how job documents get ranked on top + of existing relevance score (determined by API algorithm). A + combination of the ranking expression and relevance score is + used to determine job's final ranking position. + + The syntax for this expression is a subset of Google SQL + syntax. + + Supported operators are: +, -, \*, /, where the left and + right side of the operator is either a numeric + [Job.custom_attributes][google.cloud.talent.v4.Job.custom_attributes] + key, integer/double value or an expression that can be + evaluated to a number. + + Parenthesis are supported to adjust calculation precedence. + The expression must be < 100 characters in length. + + The expression is considered invalid for a job if the + expression references custom attributes that are not + populated on the job or if the expression results in a + divide by zero. If an expression is invalid for a job, that + job is demoted to the end of the results. + + Sample ranking expression (year + 25) \* 0.25 - (freshness / + 0.5) + """ + + class ImportanceLevel(proto.Enum): + r"""The importance level for + [CustomRankingInfo.ranking_expression][google.cloud.talent.v4.SearchJobsRequest.CustomRankingInfo.ranking_expression]. + """ + IMPORTANCE_LEVEL_UNSPECIFIED = 0 + NONE = 1 + LOW = 2 + MILD = 3 + MEDIUM = 4 + HIGH = 5 + EXTREME = 6 + + importance_level = proto.Field( + proto.ENUM, + number=1, + enum="SearchJobsRequest.CustomRankingInfo.ImportanceLevel", + ) + + ranking_expression = proto.Field(proto.STRING, number=2) + + parent = proto.Field(proto.STRING, number=1) + + search_mode = proto.Field(proto.ENUM, number=2, enum=SearchMode,) + + request_metadata = proto.Field( + proto.MESSAGE, number=3, message=common.RequestMetadata, + ) + + job_query = proto.Field(proto.MESSAGE, number=4, message=filters.JobQuery,) + + enable_broadening = proto.Field(proto.BOOL, number=5) + + histogram_queries = proto.RepeatedField( + proto.MESSAGE, number=7, message=histogram.HistogramQuery, + ) + + job_view = proto.Field(proto.ENUM, number=8, enum="JobView",) + + offset = proto.Field(proto.INT32, number=9) + + max_page_size = proto.Field(proto.INT32, number=10) + + page_token = proto.Field(proto.STRING, number=11) + + order_by = proto.Field(proto.STRING, number=12) + + diversification_level = proto.Field( + proto.ENUM, number=13, enum=DiversificationLevel, + ) + + custom_ranking_info = proto.Field( + proto.MESSAGE, number=14, message=CustomRankingInfo, + ) + + disable_keyword_match = proto.Field(proto.BOOL, number=16) + + +class SearchJobsResponse(proto.Message): + r"""Response for SearchJob method. + + Attributes: + matching_jobs (Sequence[~.job_service.SearchJobsResponse.MatchingJob]): + The Job entities that match the specified + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + histogram_query_results (Sequence[~.histogram.HistogramQueryResult]): + The histogram results that match with specified + [SearchJobsRequest.histogram_queries][google.cloud.talent.v4.SearchJobsRequest.histogram_queries]. + next_page_token (str): + The token that specifies the starting + position of the next page of results. This field + is empty if there are no more results. + location_filters (Sequence[~.common.Location]): + The location filters that the service applied to the + specified query. If any filters are lat-lng based, the + [Location.location_type][google.cloud.talent.v4.Location.location_type] + is + [Location.LocationType.LOCATION_TYPE_UNSPECIFIED][google.cloud.talent.v4.Location.LocationType.LOCATION_TYPE_UNSPECIFIED]. + total_size (int): + Number of jobs that match the specified + query. + Note: This size is precise only if the total is + less than 100,000. + metadata (~.common.ResponseMetadata): + Additional information for the API + invocation, such as the request tracking id. + broadened_query_jobs_count (int): + If query broadening is enabled, we may append + additional results from the broadened query. + This number indicates how many of the jobs + returned in the jobs field are from the + broadened query. These results are always at the + end of the jobs list. In particular, a value of + 0, or if the field isn't set, all the jobs in + the jobs list are from the original (without + broadening) query. If this field is non-zero, + subsequent requests with offset after this + result set should contain all broadened results. + spell_correction (~.common.SpellingCorrection): + The spell checking result, and correction. + """ + + class MatchingJob(proto.Message): + r"""Job entry with metadata inside + [SearchJobsResponse][google.cloud.talent.v4.SearchJobsResponse]. + + Attributes: + job (~.gct_job.Job): + Job resource that matches the specified + [SearchJobsRequest][google.cloud.talent.v4.SearchJobsRequest]. + job_summary (str): + A summary of the job with core information + that's displayed on the search results listing + page. + job_title_snippet (str): + Contains snippets of text from the + [Job.title][google.cloud.talent.v4.Job.title] field most + closely matching a search query's keywords, if available. + The matching query keywords are enclosed in HTML bold tags. + search_text_snippet (str): + Contains snippets of text from the + [Job.description][google.cloud.talent.v4.Job.description] + and similar fields that most closely match a search query's + keywords, if available. All HTML tags in the original fields + are stripped when returned in this field, and matching query + keywords are enclosed in HTML bold tags. + commute_info (~.job_service.SearchJobsResponse.CommuteInfo): + Commute information which is generated based on specified + [CommuteFilter][google.cloud.talent.v4.CommuteFilter]. + """ + + job = proto.Field(proto.MESSAGE, number=1, message=gct_job.Job,) + + job_summary = proto.Field(proto.STRING, number=2) + + job_title_snippet = proto.Field(proto.STRING, number=3) + + search_text_snippet = proto.Field(proto.STRING, number=4) + + commute_info = proto.Field( + proto.MESSAGE, number=5, message="SearchJobsResponse.CommuteInfo", + ) + + class CommuteInfo(proto.Message): + r"""Commute details related to this job. + + Attributes: + job_location (~.common.Location): + Location used as the destination in the + commute calculation. + travel_duration (~.duration.Duration): + The number of seconds required to travel to + the job location from the query location. A + duration of 0 seconds indicates that the job + isn't reachable within the requested duration, + but was returned as part of an expanded query. + """ + + job_location = proto.Field(proto.MESSAGE, number=1, message=common.Location,) + + travel_duration = proto.Field( + proto.MESSAGE, number=2, message=duration.Duration, + ) + + @property + def raw_page(self): + return self + + matching_jobs = proto.RepeatedField(proto.MESSAGE, number=1, message=MatchingJob,) + + histogram_query_results = proto.RepeatedField( + proto.MESSAGE, number=2, message=histogram.HistogramQueryResult, + ) + + next_page_token = proto.Field(proto.STRING, number=3) + + location_filters = proto.RepeatedField( + proto.MESSAGE, number=4, message=common.Location, + ) + + total_size = proto.Field(proto.INT32, number=6) + + metadata = proto.Field(proto.MESSAGE, number=7, message=common.ResponseMetadata,) + + broadened_query_jobs_count = proto.Field(proto.INT32, number=8) + + spell_correction = proto.Field( + proto.MESSAGE, number=9, message=common.SpellingCorrection, + ) + + +class BatchCreateJobsRequest(proto.Message): + r"""Request to create a batch of jobs. + + Attributes: + parent (str): + Required. The resource name of the tenant under which the + job is created. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + jobs (Sequence[~.gct_job.Job]): + Required. The jobs to be created. + A maximum of 200 jobs can be created in a batch. + """ + + parent = proto.Field(proto.STRING, number=1) + + jobs = proto.RepeatedField(proto.MESSAGE, number=2, message=gct_job.Job,) + + +class BatchUpdateJobsRequest(proto.Message): + r"""Request to update a batch of jobs. + + Attributes: + parent (str): + Required. The resource name of the tenant under which the + job is created. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + jobs (Sequence[~.gct_job.Job]): + Required. The jobs to be updated. + A maximum of 200 jobs can be updated in a batch. + update_mask (~.field_mask.FieldMask): + Strongly recommended for the best service experience. Be + aware that it will also increase latency when checking the + status of a batch operation. + + If + [update_mask][google.cloud.talent.v4.BatchUpdateJobsRequest.update_mask] + is provided, only the specified fields in + [Job][google.cloud.talent.v4.Job] are updated. Otherwise all + the fields are updated. + + A field mask to restrict the fields that are updated. Only + top level fields of [Job][google.cloud.talent.v4.Job] are + supported. + + If + [update_mask][google.cloud.talent.v4.BatchUpdateJobsRequest.update_mask] + is provided, The [Job][google.cloud.talent.v4.Job] inside + [JobResult][JobOperationResult.JobResult] will only contains + fields that is updated, plus the Id of the Job. Otherwise, + [Job][google.cloud.talent.v4.Job] will include all fields, + which can yield a very large response. + """ + + parent = proto.Field(proto.STRING, number=1) + + jobs = proto.RepeatedField(proto.MESSAGE, number=2, message=gct_job.Job,) + + update_mask = proto.Field(proto.MESSAGE, number=3, message=field_mask.FieldMask,) + + +class BatchDeleteJobsRequest(proto.Message): + r"""Request to delete a batch of jobs. + + Attributes: + parent (str): + Required. The resource name of the tenant under which the + job is created. + + The format is "projects/{project_id}/tenants/{tenant_id}". + For example, "projects/foo/tenants/bar". + + The parent of all of the jobs specified in ``names`` must + match this field. + names (Sequence[str]): + The names of the jobs to delete. + + The format is + "projects/{project_id}/tenants/{tenant_id}/jobs/{job_id}". + For example, "projects/foo/tenants/bar/jobs/baz". + + A maximum of 200 jobs can be deleted in a batch. + """ + + parent = proto.Field(proto.STRING, number=1) + + names = proto.RepeatedField(proto.STRING, number=2) + + +class JobResult(proto.Message): + r"""Mutation result of a job from a batch operation. + + Attributes: + job (~.gct_job.Job): + Here [Job][google.cloud.talent.v4.Job] only contains basic + information including + [name][google.cloud.talent.v4.Job.name], + [company][google.cloud.talent.v4.Job.company], + [language_code][google.cloud.talent.v4.Job.language_code] + and + [requisition_id][google.cloud.talent.v4.Job.requisition_id], + use getJob method to retrieve detailed information of the + created/updated job. + status (~.gr_status.Status): + The status of the job processed. This field is populated if + the processing of the + [job][google.cloud.talent.v4.JobResult.job] fails. + """ + + job = proto.Field(proto.MESSAGE, number=1, message=gct_job.Job,) + + status = proto.Field(proto.MESSAGE, number=2, message=gr_status.Status,) + + +class BatchCreateJobsResponse(proto.Message): + r"""The result of + [JobService.BatchCreateJobs][google.cloud.talent.v4.JobService.BatchCreateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + Attributes: + job_results (Sequence[~.job_service.JobResult]): + List of job mutation results from a batch + create operation. It can change until operation + status is FINISHED, FAILED or CANCELLED. + """ + + job_results = proto.RepeatedField(proto.MESSAGE, number=1, message=JobResult,) + + +class BatchUpdateJobsResponse(proto.Message): + r"""The result of + [JobService.BatchUpdateJobs][google.cloud.talent.v4.JobService.BatchUpdateJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + Attributes: + job_results (Sequence[~.job_service.JobResult]): + List of job mutation results from a batch + update operation. It can change until operation + status is FINISHED, FAILED or CANCELLED. + """ + + job_results = proto.RepeatedField(proto.MESSAGE, number=1, message=JobResult,) + + +class BatchDeleteJobsResponse(proto.Message): + r"""The result of + [JobService.BatchDeleteJobs][google.cloud.talent.v4.JobService.BatchDeleteJobs]. + It's used to replace + [google.longrunning.Operation.response][google.longrunning.Operation.response] + in case of success. + + Attributes: + job_results (Sequence[~.job_service.JobResult]): + List of job mutation results from a batch + delete operation. It can change until operation + status is FINISHED, FAILED or CANCELLED. + """ + + job_results = proto.RepeatedField(proto.MESSAGE, number=1, message=JobResult,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/tenant.py b/google/cloud/talent_v4/types/tenant.py new file mode 100644 index 00000000..0299d6a3 --- /dev/null +++ b/google/cloud/talent_v4/types/tenant.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +__protobuf__ = proto.module(package="google.cloud.talent.v4", manifest={"Tenant",},) + + +class Tenant(proto.Message): + r"""A Tenant resource represents a tenant in the service. A + tenant is a group or entity that shares common access with + specific privileges for resources like jobs. Customer may create + multiple tenants to provide data isolation for different groups. + + Attributes: + name (str): + Required during tenant update. + + The resource name for a tenant. This is generated by the + service when a tenant is created. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + external_id (str): + Required. Client side tenant identifier, used + to uniquely identify the tenant. + + The maximum number of allowed characters is 255. + """ + + name = proto.Field(proto.STRING, number=1) + + external_id = proto.Field(proto.STRING, number=2) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4/types/tenant_service.py b/google/cloud/talent_v4/types/tenant_service.py new file mode 100644 index 00000000..2f77b0d1 --- /dev/null +++ b/google/cloud/talent_v4/types/tenant_service.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import proto # type: ignore + + +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + + +__protobuf__ = proto.module( + package="google.cloud.talent.v4", + manifest={ + "CreateTenantRequest", + "GetTenantRequest", + "UpdateTenantRequest", + "DeleteTenantRequest", + "ListTenantsRequest", + "ListTenantsResponse", + }, +) + + +class CreateTenantRequest(proto.Message): + r"""The Request of the CreateTenant method. + + Attributes: + parent (str): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + tenant (~.gct_tenant.Tenant): + Required. The tenant to be created. + """ + + parent = proto.Field(proto.STRING, number=1) + + tenant = proto.Field(proto.MESSAGE, number=2, message=gct_tenant.Tenant,) + + +class GetTenantRequest(proto.Message): + r"""Request for getting a tenant by name. + + Attributes: + name (str): + Required. The resource name of the tenant to be retrieved. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + """ + + name = proto.Field(proto.STRING, number=1) + + +class UpdateTenantRequest(proto.Message): + r"""Request for updating a specified tenant. + + Attributes: + tenant (~.gct_tenant.Tenant): + Required. The tenant resource to replace the + current resource in the system. + update_mask (~.field_mask.FieldMask): + Strongly recommended for the best service experience. + + If + [update_mask][google.cloud.talent.v4.UpdateTenantRequest.update_mask] + is provided, only the specified fields in + [tenant][google.cloud.talent.v4.UpdateTenantRequest.tenant] + are updated. Otherwise all the fields are updated. + + A field mask to specify the tenant fields to be updated. + Only top level fields of + [Tenant][google.cloud.talent.v4.Tenant] are supported. + """ + + tenant = proto.Field(proto.MESSAGE, number=1, message=gct_tenant.Tenant,) + + update_mask = proto.Field(proto.MESSAGE, number=2, message=field_mask.FieldMask,) + + +class DeleteTenantRequest(proto.Message): + r"""Request to delete a tenant. + + Attributes: + name (str): + Required. The resource name of the tenant to be deleted. + + The format is "projects/{project_id}/tenants/{tenant_id}", + for example, "projects/foo/tenants/bar". + """ + + name = proto.Field(proto.STRING, number=1) + + +class ListTenantsRequest(proto.Message): + r"""List tenants for which the client has ACL visibility. + + Attributes: + parent (str): + Required. Resource name of the project under which the + tenant is created. + + The format is "projects/{project_id}", for example, + "projects/foo". + page_token (str): + The starting indicator from which to return + results. + page_size (int): + The maximum number of tenants to be returned, + at most 100. Default is 100 if a non-positive + number is provided. + """ + + parent = proto.Field(proto.STRING, number=1) + + page_token = proto.Field(proto.STRING, number=2) + + page_size = proto.Field(proto.INT32, number=3) + + +class ListTenantsResponse(proto.Message): + r"""The List tenants response object. + + Attributes: + tenants (Sequence[~.gct_tenant.Tenant]): + Tenants for the current client. + next_page_token (str): + A token to retrieve the next page of results. + metadata (~.common.ResponseMetadata): + Additional information for the API + invocation, such as the request tracking id. + """ + + @property + def raw_page(self): + return self + + tenants = proto.RepeatedField(proto.MESSAGE, number=1, message=gct_tenant.Tenant,) + + next_page_token = proto.Field(proto.STRING, number=2) + + metadata = proto.Field(proto.MESSAGE, number=3, message=common.ResponseMetadata,) + + +__all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/talent_v4beta1/services/application_service/async_client.py b/google/cloud/talent_v4beta1/services/application_service/async_client.py index f91a96d2..1dd27354 100644 --- a/google/cloud/talent_v4beta1/services/application_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/application_service/async_client.py @@ -37,7 +37,7 @@ from google.protobuf import wrappers_pb2 as wrappers # type: ignore from google.type import date_pb2 as date # type: ignore -from .transports.base import ApplicationServiceTransport +from .transports.base import ApplicationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import ApplicationServiceGrpcAsyncIOTransport from .client import ApplicationServiceClient @@ -53,6 +53,9 @@ class ApplicationServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = ApplicationServiceClient.DEFAULT_MTLS_ENDPOINT application_path = staticmethod(ApplicationServiceClient.application_path) + parse_application_path = staticmethod( + ApplicationServiceClient.parse_application_path + ) from_service_account_file = ApplicationServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -68,6 +71,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, ApplicationServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the application service client. @@ -83,16 +87,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -100,7 +107,10 @@ def __init__( """ self._client = ApplicationServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def create_application( @@ -172,7 +182,7 @@ async def create_application( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_application, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -252,11 +262,11 @@ async def get_application( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -328,7 +338,7 @@ async def update_application( rpc = gapic_v1.method_async.wrap_method( self._client._transport.update_application, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -403,11 +413,11 @@ async def delete_application( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -488,11 +498,11 @@ async def list_applications( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -515,11 +525,11 @@ async def list_applications( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("ApplicationServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/application_service/client.py b/google/cloud/talent_v4beta1/services/application_service/client.py index 8c5153f2..451c12bd 100644 --- a/google/cloud/talent_v4beta1/services/application_service/client.py +++ b/google/cloud/talent_v4beta1/services/application_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -39,7 +41,7 @@ from google.protobuf import wrappers_pb2 as wrappers # type: ignore from google.type import date_pb2 as date # type: ignore -from .transports.base import ApplicationServiceTransport +from .transports.base import ApplicationServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ApplicationServiceGrpcTransport from .transports.grpc_asyncio import ApplicationServiceGrpcAsyncIOTransport @@ -159,9 +161,10 @@ def parse_application_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, ApplicationServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, ApplicationServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the application service client. @@ -174,48 +177,74 @@ def __init__( transport (Union[str, ~.ApplicationServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -239,11 +268,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def create_application( @@ -644,11 +673,11 @@ def list_applications( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("ApplicationServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/application_service/transports/base.py b/google/cloud/talent_v4beta1/services/application_service/transports/base.py index 5dbc0fd6..83ba6405 100644 --- a/google/cloud/talent_v4beta1/services/application_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/application_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -32,11 +32,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class ApplicationServiceTransport(abc.ABC): @@ -55,6 +55,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -72,6 +73,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -99,13 +105,13 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.create_application: gapic_v1.method.wrap_method( - self.create_application, default_timeout=30.0, client_info=_client_info, + self.create_application, default_timeout=30.0, client_info=client_info, ), self.get_application: gapic_v1.method.wrap_method( self.get_application, @@ -114,14 +120,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.update_application: gapic_v1.method.wrap_method( - self.update_application, default_timeout=30.0, client_info=_client_info, + self.update_application, default_timeout=30.0, client_info=client_info, ), self.delete_application: gapic_v1.method.wrap_method( self.delete_application, @@ -130,11 +136,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.list_applications: gapic_v1.method.wrap_method( self.list_applications, @@ -143,11 +149,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/application_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/application_service/transports/grpc.py index 2d6a96cb..44696413 100644 --- a/google/cloud/talent_v4beta1/services/application_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/application_service/transports/grpc.py @@ -15,14 +15,15 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import application @@ -30,7 +31,7 @@ from google.cloud.talent_v4beta1.types import application_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import ApplicationServiceTransport +from .base import ApplicationServiceTransport, DEFAULT_CLIENT_INFO class ApplicationServiceGrpcTransport(ApplicationServiceTransport): @@ -59,7 +60,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -78,16 +81,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -103,6 +113,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -133,6 +148,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -143,6 +175,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -153,7 +186,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -187,7 +220,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -197,13 +230,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/application_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/application_service/transports/grpc_asyncio.py index d4cc0a9a..9e466530 100644 --- a/google/cloud/talent_v4beta1/services/application_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/application_service/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -29,7 +32,7 @@ from google.cloud.talent_v4beta1.types import application_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import ApplicationServiceTransport +from .base import ApplicationServiceTransport, DEFAULT_CLIENT_INFO from .grpc import ApplicationServiceGrpcTransport @@ -101,7 +104,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -121,16 +126,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -146,12 +158,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -171,6 +193,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -179,6 +218,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -190,13 +230,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/company_service/async_client.py b/google/cloud/talent_v4beta1/services/company_service/async_client.py index 1fd21447..a26d1475 100644 --- a/google/cloud/talent_v4beta1/services/company_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/company_service/async_client.py @@ -34,7 +34,7 @@ from google.cloud.talent_v4beta1.types import company as gct_company from google.cloud.talent_v4beta1.types import company_service -from .transports.base import CompanyServiceTransport +from .transports.base import CompanyServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import CompanyServiceGrpcAsyncIOTransport from .client import CompanyServiceClient @@ -50,6 +50,7 @@ class CompanyServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = CompanyServiceClient.DEFAULT_MTLS_ENDPOINT company_path = staticmethod(CompanyServiceClient.company_path) + parse_company_path = staticmethod(CompanyServiceClient.parse_company_path) from_service_account_file = CompanyServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -64,6 +65,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, CompanyServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the company service client. @@ -79,16 +81,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -96,7 +101,10 @@ def __init__( """ self._client = CompanyServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def create_company( @@ -173,7 +181,7 @@ async def create_company( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_company, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -260,11 +268,11 @@ async def get_company( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -340,7 +348,7 @@ async def update_company( rpc = gapic_v1.method_async.wrap_method( self._client._transport.update_company, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -419,11 +427,11 @@ async def delete_company( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -506,11 +514,11 @@ async def list_companies( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -533,11 +541,11 @@ async def list_companies( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("CompanyServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/company_service/client.py b/google/cloud/talent_v4beta1/services/company_service/client.py index 0a323981..b180385e 100644 --- a/google/cloud/talent_v4beta1/services/company_service/client.py +++ b/google/cloud/talent_v4beta1/services/company_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -36,7 +38,7 @@ from google.cloud.talent_v4beta1.types import company as gct_company from google.cloud.talent_v4beta1.types import company_service -from .transports.base import CompanyServiceTransport +from .transports.base import CompanyServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import CompanyServiceGrpcTransport from .transports.grpc_asyncio import CompanyServiceGrpcAsyncIOTransport @@ -152,9 +154,10 @@ def parse_company_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, CompanyServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, CompanyServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the company service client. @@ -167,48 +170,74 @@ def __init__( transport (Union[str, ~.CompanyServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -232,11 +261,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def create_company( @@ -659,11 +688,11 @@ def list_companies( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("CompanyServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/company_service/transports/base.py b/google/cloud/talent_v4beta1/services/company_service/transports/base.py index 8a1ec615..39fb541b 100644 --- a/google/cloud/talent_v4beta1/services/company_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/company_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -32,11 +32,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class CompanyServiceTransport(abc.ABC): @@ -55,6 +55,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -72,6 +73,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -99,13 +105,13 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.create_company: gapic_v1.method.wrap_method( - self.create_company, default_timeout=30.0, client_info=_client_info, + self.create_company, default_timeout=30.0, client_info=client_info, ), self.get_company: gapic_v1.method.wrap_method( self.get_company, @@ -114,14 +120,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.update_company: gapic_v1.method.wrap_method( - self.update_company, default_timeout=30.0, client_info=_client_info, + self.update_company, default_timeout=30.0, client_info=client_info, ), self.delete_company: gapic_v1.method.wrap_method( self.delete_company, @@ -130,11 +136,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.list_companies: gapic_v1.method.wrap_method( self.list_companies, @@ -143,11 +149,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/company_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/company_service/transports/grpc.py index d1b583d6..47b84500 100644 --- a/google/cloud/talent_v4beta1/services/company_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/company_service/transports/grpc.py @@ -15,14 +15,15 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import company @@ -30,7 +31,7 @@ from google.cloud.talent_v4beta1.types import company_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import CompanyServiceTransport +from .base import CompanyServiceTransport, DEFAULT_CLIENT_INFO class CompanyServiceGrpcTransport(CompanyServiceTransport): @@ -59,7 +60,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -78,16 +81,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -103,6 +113,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -133,6 +148,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -143,6 +175,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -153,7 +186,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -187,7 +220,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -197,13 +230,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/company_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/company_service/transports/grpc_asyncio.py index c5a11616..9806cd0a 100644 --- a/google/cloud/talent_v4beta1/services/company_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/company_service/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -29,7 +32,7 @@ from google.cloud.talent_v4beta1.types import company_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import CompanyServiceTransport +from .base import CompanyServiceTransport, DEFAULT_CLIENT_INFO from .grpc import CompanyServiceGrpcTransport @@ -101,7 +104,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -121,16 +126,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -146,12 +158,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -171,6 +193,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -179,6 +218,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -190,13 +230,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/completion/async_client.py b/google/cloud/talent_v4beta1/services/completion/async_client.py index 268aabed..7c46b2c7 100644 --- a/google/cloud/talent_v4beta1/services/completion/async_client.py +++ b/google/cloud/talent_v4beta1/services/completion/async_client.py @@ -31,7 +31,7 @@ from google.cloud.talent_v4beta1.types import common from google.cloud.talent_v4beta1.types import completion_service -from .transports.base import CompletionTransport +from .transports.base import CompletionTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import CompletionGrpcAsyncIOTransport from .client import CompletionClient @@ -57,6 +57,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, CompletionTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the completion client. @@ -72,16 +73,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -89,7 +93,10 @@ def __init__( """ self._client = CompletionClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def complete_query( @@ -131,11 +138,11 @@ async def complete_query( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -152,11 +159,11 @@ async def complete_query( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("CompletionAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/completion/client.py b/google/cloud/talent_v4beta1/services/completion/client.py index 9efe4cd5..21fa1335 100644 --- a/google/cloud/talent_v4beta1/services/completion/client.py +++ b/google/cloud/talent_v4beta1/services/completion/client.py @@ -16,24 +16,26 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore from google.cloud.talent_v4beta1.types import common from google.cloud.talent_v4beta1.types import completion_service -from .transports.base import CompletionTransport +from .transports.base import CompletionTransport, DEFAULT_CLIENT_INFO from .transports.grpc import CompletionGrpcTransport from .transports.grpc_asyncio import CompletionGrpcAsyncIOTransport @@ -129,9 +131,10 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, CompletionTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, CompletionTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the completion client. @@ -144,48 +147,74 @@ def __init__( transport (Union[str, ~.CompletionTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -209,11 +238,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def complete_query( @@ -269,11 +298,11 @@ def complete_query( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("CompletionClient",) diff --git a/google/cloud/talent_v4beta1/services/completion/transports/base.py b/google/cloud/talent_v4beta1/services/completion/transports/base.py index 50688320..95e41990 100644 --- a/google/cloud/talent_v4beta1/services/completion/transports/base.py +++ b/google/cloud/talent_v4beta1/services/completion/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -29,11 +29,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class CompletionTransport(abc.ABC): @@ -52,6 +52,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -69,6 +70,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -96,9 +102,9 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.complete_query: gapic_v1.method.wrap_method( @@ -108,11 +114,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/completion/transports/grpc.py b/google/cloud/talent_v4beta1/services/completion/transports/grpc.py index 24e2add7..9661fd31 100644 --- a/google/cloud/talent_v4beta1/services/completion/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/completion/transports/grpc.py @@ -15,19 +15,20 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import completion_service -from .base import CompletionTransport +from .base import CompletionTransport, DEFAULT_CLIENT_INFO class CompletionGrpcTransport(CompletionTransport): @@ -55,7 +56,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -74,16 +77,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -99,6 +109,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -129,6 +144,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -139,6 +171,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -149,7 +182,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -183,7 +216,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -193,13 +226,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/completion/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/completion/transports/grpc_asyncio.py index 5e153a28..a47bd5d1 100644 --- a/google/cloud/talent_v4beta1/services/completion/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/completion/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -26,7 +29,7 @@ from google.cloud.talent_v4beta1.types import completion_service -from .base import CompletionTransport +from .base import CompletionTransport, DEFAULT_CLIENT_INFO from .grpc import CompletionGrpcTransport @@ -97,7 +100,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -117,16 +122,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -142,12 +154,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -167,6 +189,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -175,6 +214,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -186,13 +226,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/event_service/async_client.py b/google/cloud/talent_v4beta1/services/event_service/async_client.py index 668407fb..2404be11 100644 --- a/google/cloud/talent_v4beta1/services/event_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/event_service/async_client.py @@ -32,7 +32,7 @@ from google.cloud.talent_v4beta1.types import event_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import EventServiceTransport +from .transports.base import EventServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import EventServiceGrpcAsyncIOTransport from .client import EventServiceClient @@ -58,6 +58,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, EventServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the event service client. @@ -73,16 +74,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -90,7 +94,10 @@ def __init__( """ self._client = EventServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def create_client_event( @@ -178,7 +185,7 @@ async def create_client_event( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_client_event, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -195,11 +202,11 @@ async def create_client_event( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("EventServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/event_service/client.py b/google/cloud/talent_v4beta1/services/event_service/client.py index b24e2173..fc49c064 100644 --- a/google/cloud/talent_v4beta1/services/event_service/client.py +++ b/google/cloud/talent_v4beta1/services/event_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -34,7 +36,7 @@ from google.cloud.talent_v4beta1.types import event_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import EventServiceTransport +from .transports.base import EventServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import EventServiceGrpcTransport from .transports.grpc_asyncio import EventServiceGrpcAsyncIOTransport @@ -130,9 +132,10 @@ def from_service_account_file(cls, filename: str, *args, **kwargs): def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, EventServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, EventServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the event service client. @@ -145,48 +148,74 @@ def __init__( transport (Union[str, ~.EventServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -210,11 +239,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def create_client_event( @@ -321,11 +350,11 @@ def create_client_event( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("EventServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/event_service/transports/base.py b/google/cloud/talent_v4beta1/services/event_service/transports/base.py index 2394cfd0..cd643c9d 100644 --- a/google/cloud/talent_v4beta1/services/event_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/event_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -30,11 +30,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class EventServiceTransport(abc.ABC): @@ -53,6 +53,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -70,6 +71,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -97,15 +103,13 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.create_client_event: gapic_v1.method.wrap_method( - self.create_client_event, - default_timeout=30.0, - client_info=_client_info, + self.create_client_event, default_timeout=30.0, client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/event_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/event_service/transports/grpc.py index 50109487..e870b468 100644 --- a/google/cloud/talent_v4beta1/services/event_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/event_service/transports/grpc.py @@ -15,20 +15,21 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import event from google.cloud.talent_v4beta1.types import event_service -from .base import EventServiceTransport +from .base import EventServiceTransport, DEFAULT_CLIENT_INFO class EventServiceGrpcTransport(EventServiceTransport): @@ -56,7 +57,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -75,16 +78,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -100,6 +110,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -130,6 +145,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -140,6 +172,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -150,7 +183,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -184,7 +217,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -194,13 +227,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/event_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/event_service/transports/grpc_asyncio.py index 1d1235da..c2400f5d 100644 --- a/google/cloud/talent_v4beta1/services/event_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/event_service/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -27,7 +30,7 @@ from google.cloud.talent_v4beta1.types import event from google.cloud.talent_v4beta1.types import event_service -from .base import EventServiceTransport +from .base import EventServiceTransport, DEFAULT_CLIENT_INFO from .grpc import EventServiceGrpcTransport @@ -98,7 +101,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -118,16 +123,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -143,12 +155,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -168,6 +190,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -176,6 +215,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -187,13 +227,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/job_service/async_client.py b/google/cloud/talent_v4beta1/services/job_service/async_client.py index 876d3477..ec355cf2 100644 --- a/google/cloud/talent_v4beta1/services/job_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/job_service/async_client.py @@ -28,8 +28,8 @@ from google.auth import credentials # type: ignore from google.oauth2 import service_account # type: ignore -from google.api_core import operation -from google.api_core import operation_async +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore from google.cloud.talent_v4beta1.services.job_service import pagers from google.cloud.talent_v4beta1.types import common from google.cloud.talent_v4beta1.types import job @@ -37,7 +37,7 @@ from google.cloud.talent_v4beta1.types import job_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import JobServiceTransport +from .transports.base import JobServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import JobServiceGrpcAsyncIOTransport from .client import JobServiceClient @@ -53,6 +53,7 @@ class JobServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = JobServiceClient.DEFAULT_MTLS_ENDPOINT job_path = staticmethod(JobServiceClient.job_path) + parse_job_path = staticmethod(JobServiceClient.parse_job_path) from_service_account_file = JobServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -67,6 +68,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, JobServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the job service client. @@ -82,16 +84,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -99,7 +104,10 @@ def __init__( """ self._client = JobServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def create_job( @@ -176,7 +184,7 @@ async def create_job( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_job, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -269,7 +277,7 @@ async def batch_create_jobs( rpc = gapic_v1.method_async.wrap_method( self._client._transport.batch_create_jobs, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -361,11 +369,11 @@ async def get_job( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -440,7 +448,7 @@ async def update_job( rpc = gapic_v1.method_async.wrap_method( self._client._transport.update_job, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -533,7 +541,7 @@ async def batch_update_jobs( rpc = gapic_v1.method_async.wrap_method( self._client._transport.batch_update_jobs, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -617,11 +625,11 @@ async def delete_job( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -710,7 +718,7 @@ async def batch_delete_jobs( rpc = gapic_v1.method_async.wrap_method( self._client._transport.batch_delete_jobs, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -820,11 +828,11 @@ async def list_jobs( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -889,7 +897,7 @@ async def search_jobs( rpc = gapic_v1.method_async.wrap_method( self._client._transport.search_jobs, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -960,7 +968,7 @@ async def search_jobs_for_alert( rpc = gapic_v1.method_async.wrap_method( self._client._transport.search_jobs_for_alert, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -983,11 +991,11 @@ async def search_jobs_for_alert( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("JobServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/job_service/client.py b/google/cloud/talent_v4beta1/services/job_service/client.py index 79149b6a..bbcd5035 100644 --- a/google/cloud/talent_v4beta1/services/job_service/client.py +++ b/google/cloud/talent_v4beta1/services/job_service/client.py @@ -16,22 +16,24 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore -from google.api_core import operation -from google.api_core import operation_async +from google.api_core import operation # type: ignore +from google.api_core import operation_async # type: ignore from google.cloud.talent_v4beta1.services.job_service import pagers from google.cloud.talent_v4beta1.types import common from google.cloud.talent_v4beta1.types import job @@ -39,7 +41,7 @@ from google.cloud.talent_v4beta1.types import job_service from google.protobuf import timestamp_pb2 as timestamp # type: ignore -from .transports.base import JobServiceTransport +from .transports.base import JobServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import JobServiceGrpcTransport from .transports.grpc_asyncio import JobServiceGrpcAsyncIOTransport @@ -153,9 +155,10 @@ def parse_job_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, JobServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, JobServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the job service client. @@ -168,48 +171,74 @@ def __init__( transport (Union[str, ~.JobServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -233,11 +262,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def create_job( @@ -1115,11 +1144,11 @@ def search_jobs_for_alert( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("JobServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/job_service/transports/base.py b/google/cloud/talent_v4beta1/services/job_service/transports/base.py index 7214b944..ffef5f17 100644 --- a/google/cloud/talent_v4beta1/services/job_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/job_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -34,11 +34,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class JobServiceTransport(abc.ABC): @@ -57,6 +57,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -74,6 +75,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -101,16 +107,16 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.create_job: gapic_v1.method.wrap_method( - self.create_job, default_timeout=30.0, client_info=_client_info, + self.create_job, default_timeout=30.0, client_info=client_info, ), self.batch_create_jobs: gapic_v1.method.wrap_method( - self.batch_create_jobs, default_timeout=30.0, client_info=_client_info, + self.batch_create_jobs, default_timeout=30.0, client_info=client_info, ), self.get_job: gapic_v1.method.wrap_method( self.get_job, @@ -119,17 +125,17 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.update_job: gapic_v1.method.wrap_method( - self.update_job, default_timeout=30.0, client_info=_client_info, + self.update_job, default_timeout=30.0, client_info=client_info, ), self.batch_update_jobs: gapic_v1.method.wrap_method( - self.batch_update_jobs, default_timeout=30.0, client_info=_client_info, + self.batch_update_jobs, default_timeout=30.0, client_info=client_info, ), self.delete_job: gapic_v1.method.wrap_method( self.delete_job, @@ -138,14 +144,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.batch_delete_jobs: gapic_v1.method.wrap_method( - self.batch_delete_jobs, default_timeout=30.0, client_info=_client_info, + self.batch_delete_jobs, default_timeout=30.0, client_info=client_info, ), self.list_jobs: gapic_v1.method.wrap_method( self.list_jobs, @@ -154,19 +160,19 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.search_jobs: gapic_v1.method.wrap_method( - self.search_jobs, default_timeout=30.0, client_info=_client_info, + self.search_jobs, default_timeout=30.0, client_info=client_info, ), self.search_jobs_for_alert: gapic_v1.method.wrap_method( self.search_jobs_for_alert, default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/job_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/job_service/transports/grpc.py index 85549fe7..b1113951 100644 --- a/google/cloud/talent_v4beta1/services/job_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/job_service/transports/grpc.py @@ -15,15 +15,16 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore from google.api_core import operations_v1 # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import job @@ -32,7 +33,7 @@ from google.longrunning import operations_pb2 as operations # type: ignore from google.protobuf import empty_pb2 as empty # type: ignore -from .base import JobServiceTransport +from .base import JobServiceTransport, DEFAULT_CLIENT_INFO class JobServiceGrpcTransport(JobServiceTransport): @@ -61,7 +62,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -80,16 +83,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -105,6 +115,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -135,6 +150,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -145,6 +177,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -155,7 +188,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -189,7 +222,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -199,13 +232,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/job_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/job_service/transports/grpc_asyncio.py index b350d32d..0da963e3 100644 --- a/google/cloud/talent_v4beta1/services/job_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/job_service/transports/grpc_asyncio.py @@ -15,10 +15,13 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore from google.api_core import operations_v1 # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -31,7 +34,7 @@ from google.longrunning import operations_pb2 as operations # type: ignore from google.protobuf import empty_pb2 as empty # type: ignore -from .base import JobServiceTransport +from .base import JobServiceTransport, DEFAULT_CLIENT_INFO from .grpc import JobServiceGrpcTransport @@ -103,7 +106,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -123,16 +128,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -148,12 +160,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -173,6 +195,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -181,6 +220,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -192,13 +232,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/profile_service/async_client.py b/google/cloud/talent_v4beta1/services/profile_service/async_client.py index 786f1760..6f039380 100644 --- a/google/cloud/talent_v4beta1/services/profile_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/profile_service/async_client.py @@ -37,7 +37,7 @@ from google.protobuf import timestamp_pb2 as timestamp # type: ignore from google.protobuf import wrappers_pb2 as wrappers # type: ignore -from .transports.base import ProfileServiceTransport +from .transports.base import ProfileServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import ProfileServiceGrpcAsyncIOTransport from .client import ProfileServiceClient @@ -53,6 +53,7 @@ class ProfileServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = ProfileServiceClient.DEFAULT_MTLS_ENDPOINT profile_path = staticmethod(ProfileServiceClient.profile_path) + parse_profile_path = staticmethod(ProfileServiceClient.parse_profile_path) from_service_account_file = ProfileServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -67,6 +68,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, ProfileServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the profile service client. @@ -82,16 +84,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -99,7 +104,10 @@ def __init__( """ self._client = ProfileServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def list_profiles( @@ -167,11 +175,11 @@ async def list_profiles( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -261,7 +269,7 @@ async def create_profile( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_profile, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -340,11 +348,11 @@ async def get_profile( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -416,7 +424,7 @@ async def update_profile( rpc = gapic_v1.method_async.wrap_method( self._client._transport.update_profile, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -491,11 +499,11 @@ async def delete_profile( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -555,7 +563,7 @@ async def search_profiles( rpc = gapic_v1.method_async.wrap_method( self._client._transport.search_profiles, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -578,11 +586,11 @@ async def search_profiles( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("ProfileServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/profile_service/client.py b/google/cloud/talent_v4beta1/services/profile_service/client.py index 627735f6..89579796 100644 --- a/google/cloud/talent_v4beta1/services/profile_service/client.py +++ b/google/cloud/talent_v4beta1/services/profile_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -39,7 +41,7 @@ from google.protobuf import timestamp_pb2 as timestamp # type: ignore from google.protobuf import wrappers_pb2 as wrappers # type: ignore -from .transports.base import ProfileServiceTransport +from .transports.base import ProfileServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import ProfileServiceGrpcTransport from .transports.grpc_asyncio import ProfileServiceGrpcAsyncIOTransport @@ -155,9 +157,10 @@ def parse_profile_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, ProfileServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, ProfileServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the profile service client. @@ -170,48 +173,74 @@ def __init__( transport (Union[str, ~.ProfileServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -235,11 +264,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def list_profiles( @@ -705,11 +734,11 @@ def search_profiles( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("ProfileServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/profile_service/transports/base.py b/google/cloud/talent_v4beta1/services/profile_service/transports/base.py index 707cc262..dbd36ea2 100644 --- a/google/cloud/talent_v4beta1/services/profile_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/profile_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -32,11 +32,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class ProfileServiceTransport(abc.ABC): @@ -55,6 +55,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -72,6 +73,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -99,9 +105,9 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.list_profiles: gapic_v1.method.wrap_method( @@ -111,14 +117,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.create_profile: gapic_v1.method.wrap_method( - self.create_profile, default_timeout=30.0, client_info=_client_info, + self.create_profile, default_timeout=30.0, client_info=client_info, ), self.get_profile: gapic_v1.method.wrap_method( self.get_profile, @@ -127,14 +133,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.update_profile: gapic_v1.method.wrap_method( - self.update_profile, default_timeout=30.0, client_info=_client_info, + self.update_profile, default_timeout=30.0, client_info=client_info, ), self.delete_profile: gapic_v1.method.wrap_method( self.delete_profile, @@ -143,14 +149,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.search_profiles: gapic_v1.method.wrap_method( - self.search_profiles, default_timeout=30.0, client_info=_client_info, + self.search_profiles, default_timeout=30.0, client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/profile_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/profile_service/transports/grpc.py index 0970a782..1c4965c0 100644 --- a/google/cloud/talent_v4beta1/services/profile_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/profile_service/transports/grpc.py @@ -15,14 +15,15 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import profile @@ -30,7 +31,7 @@ from google.cloud.talent_v4beta1.types import profile_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import ProfileServiceTransport +from .base import ProfileServiceTransport, DEFAULT_CLIENT_INFO class ProfileServiceGrpcTransport(ProfileServiceTransport): @@ -59,7 +60,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -78,16 +81,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -103,6 +113,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -133,6 +148,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -143,6 +175,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -153,7 +186,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -187,7 +220,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -197,13 +230,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/profile_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/profile_service/transports/grpc_asyncio.py index c8ce2e0f..2a38ebf1 100644 --- a/google/cloud/talent_v4beta1/services/profile_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/profile_service/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -29,7 +32,7 @@ from google.cloud.talent_v4beta1.types import profile_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import ProfileServiceTransport +from .base import ProfileServiceTransport, DEFAULT_CLIENT_INFO from .grpc import ProfileServiceGrpcTransport @@ -101,7 +104,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -121,16 +126,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -146,12 +158,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -171,6 +193,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -179,6 +218,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -190,13 +230,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/tenant_service/async_client.py b/google/cloud/talent_v4beta1/services/tenant_service/async_client.py index 1c5a5bce..b0f64fc3 100644 --- a/google/cloud/talent_v4beta1/services/tenant_service/async_client.py +++ b/google/cloud/talent_v4beta1/services/tenant_service/async_client.py @@ -33,7 +33,7 @@ from google.cloud.talent_v4beta1.types import tenant as gct_tenant from google.cloud.talent_v4beta1.types import tenant_service -from .transports.base import TenantServiceTransport +from .transports.base import TenantServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc_asyncio import TenantServiceGrpcAsyncIOTransport from .client import TenantServiceClient @@ -49,6 +49,7 @@ class TenantServiceAsyncClient: DEFAULT_MTLS_ENDPOINT = TenantServiceClient.DEFAULT_MTLS_ENDPOINT tenant_path = staticmethod(TenantServiceClient.tenant_path) + parse_tenant_path = staticmethod(TenantServiceClient.parse_tenant_path) from_service_account_file = TenantServiceClient.from_service_account_file from_service_account_json = from_service_account_file @@ -63,6 +64,7 @@ def __init__( credentials: credentials.Credentials = None, transport: Union[str, TenantServiceTransport] = "grpc_asyncio", client_options: ClientOptions = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the tenant service client. @@ -78,16 +80,19 @@ def __init__( 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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -95,7 +100,10 @@ def __init__( """ self._client = TenantServiceClient( - credentials=credentials, transport=transport, client_options=client_options, + credentials=credentials, + transport=transport, + client_options=client_options, + client_info=client_info, ) async def create_tenant( @@ -170,7 +178,7 @@ async def create_tenant( rpc = gapic_v1.method_async.wrap_method( self._client._transport.create_tenant, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -254,11 +262,11 @@ async def get_tenant( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -335,7 +343,7 @@ async def update_tenant( rpc = gapic_v1.method_async.wrap_method( self._client._transport.update_tenant, default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -408,11 +416,11 @@ async def delete_tenant( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -491,11 +499,11 @@ async def list_tenants( maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=DEFAULT_CLIENT_INFO, ) # Certain fields should be provided within the metadata header; @@ -518,11 +526,11 @@ async def list_tenants( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TenantServiceAsyncClient",) diff --git a/google/cloud/talent_v4beta1/services/tenant_service/client.py b/google/cloud/talent_v4beta1/services/tenant_service/client.py index c896e9a6..401d2057 100644 --- a/google/cloud/talent_v4beta1/services/tenant_service/client.py +++ b/google/cloud/talent_v4beta1/services/tenant_service/client.py @@ -16,17 +16,19 @@ # from collections import OrderedDict +from distutils import util import os import re -from typing import Callable, Dict, Sequence, Tuple, Type, Union +from typing import Callable, Dict, Optional, Sequence, Tuple, Type, Union import pkg_resources -import google.api_core.client_options as ClientOptions # type: ignore +from google.api_core import client_options as client_options_lib # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore from google.auth import credentials # type: ignore from google.auth.transport import mtls # type: ignore +from google.auth.transport.grpc import SslCredentials # type: ignore from google.auth.exceptions import MutualTLSChannelError # type: ignore from google.oauth2 import service_account # type: ignore @@ -35,7 +37,7 @@ from google.cloud.talent_v4beta1.types import tenant as gct_tenant from google.cloud.talent_v4beta1.types import tenant_service -from .transports.base import TenantServiceTransport +from .transports.base import TenantServiceTransport, DEFAULT_CLIENT_INFO from .transports.grpc import TenantServiceGrpcTransport from .transports.grpc_asyncio import TenantServiceGrpcAsyncIOTransport @@ -146,9 +148,10 @@ def parse_tenant_path(path: str) -> Dict[str, str]: def __init__( self, *, - credentials: credentials.Credentials = None, - transport: Union[str, TenantServiceTransport] = None, - client_options: ClientOptions = None, + credentials: Optional[credentials.Credentials] = None, + transport: Union[str, TenantServiceTransport, None] = None, + client_options: Optional[client_options_lib.ClientOptions] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the tenant service client. @@ -161,48 +164,74 @@ def __init__( transport (Union[str, ~.TenantServiceTransport]): The transport to use. If set to None, a transport is chosen automatically. - client_options (ClientOptions): Custom options for the client. It - won't take effect if a ``transport`` instance is provided. + client_options (client_options_lib.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 + default endpoint provided by the client. GOOGLE_API_USE_MTLS_ENDPOINT environment variable can also be used to override the endpoint: "always" (always use the default mTLS endpoint), "never" (always - use the default regular endpoint, this is the default value for - the environment variable) and "auto" (auto switch to the default - mTLS endpoint if client SSL credentials is present). However, - the ``api_endpoint`` property takes precedence if provided. - (2) The ``client_cert_source`` property is used to provide client - SSL credentials for mutual TLS transport. If not provided, the - default SSL credentials will be used if present. + use the default regular endpoint) and "auto" (auto switch to the + default mTLS endpoint if client certificate is present, this is + the default value). However, the ``api_endpoint`` property takes + precedence if provided. + (2) If GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable + is "true", then the ``client_cert_source`` property can be used + to provide client certificate for mutual TLS transport. If + not provided, the default SSL client certificate will be used if + present. If GOOGLE_API_USE_CLIENT_CERTIFICATE is "false" or not + set, no client certificate will be used. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport creation failed for any reason. """ if isinstance(client_options, dict): - client_options = ClientOptions.from_dict(client_options) + client_options = client_options_lib.from_dict(client_options) if client_options is None: - client_options = ClientOptions.ClientOptions() + client_options = client_options_lib.ClientOptions() - if client_options.api_endpoint is None: - use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS", "never") + # Create SSL credentials for mutual TLS if needed. + use_client_cert = bool( + util.strtobool(os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")) + ) + + ssl_credentials = None + is_mtls = False + if use_client_cert: + if client_options.client_cert_source: + import grpc # type: ignore + + cert, key = client_options.client_cert_source() + ssl_credentials = grpc.ssl_channel_credentials( + certificate_chain=cert, private_key=key + ) + is_mtls = True + else: + creds = SslCredentials() + is_mtls = creds.is_mtls + ssl_credentials = creds.ssl_credentials if is_mtls else None + + # Figure out which api endpoint to use. + if client_options.api_endpoint is not None: + api_endpoint = client_options.api_endpoint + else: + use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto") if use_mtls_env == "never": - client_options.api_endpoint = self.DEFAULT_ENDPOINT + api_endpoint = self.DEFAULT_ENDPOINT elif use_mtls_env == "always": - client_options.api_endpoint = self.DEFAULT_MTLS_ENDPOINT + api_endpoint = self.DEFAULT_MTLS_ENDPOINT elif use_mtls_env == "auto": - has_client_cert_source = ( - client_options.client_cert_source is not None - or mtls.has_default_client_cert_source() - ) - client_options.api_endpoint = ( - self.DEFAULT_MTLS_ENDPOINT - if has_client_cert_source - else self.DEFAULT_ENDPOINT + api_endpoint = ( + self.DEFAULT_MTLS_ENDPOINT if is_mtls else self.DEFAULT_ENDPOINT ) else: raise MutualTLSChannelError( - "Unsupported GOOGLE_API_USE_MTLS value. Accepted values: never, auto, always" + "Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted values: never, auto, always" ) # Save or instantiate the transport. @@ -226,11 +255,11 @@ def __init__( self._transport = Transport( credentials=credentials, credentials_file=client_options.credentials_file, - host=client_options.api_endpoint, + host=api_endpoint, scopes=client_options.scopes, - api_mtls_endpoint=client_options.api_endpoint, - client_cert_source=client_options.client_cert_source, + ssl_channel_credentials=ssl_credentials, quota_project_id=client_options.quota_project_id, + client_info=client_info, ) def create_tenant( @@ -639,11 +668,11 @@ def list_tenants( try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() __all__ = ("TenantServiceClient",) diff --git a/google/cloud/talent_v4beta1/services/tenant_service/transports/base.py b/google/cloud/talent_v4beta1/services/tenant_service/transports/base.py index 1146ef46..b0f77c0d 100644 --- a/google/cloud/talent_v4beta1/services/tenant_service/transports/base.py +++ b/google/cloud/talent_v4beta1/services/tenant_service/transports/base.py @@ -19,7 +19,7 @@ import typing import pkg_resources -from google import auth +from google import auth # type: ignore from google.api_core import exceptions # type: ignore from google.api_core import gapic_v1 # type: ignore from google.api_core import retry as retries # type: ignore @@ -32,11 +32,11 @@ try: - _client_info = gapic_v1.client_info.ClientInfo( + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo( gapic_version=pkg_resources.get_distribution("google-cloud-talent",).version, ) except pkg_resources.DistributionNotFound: - _client_info = gapic_v1.client_info.ClientInfo() + DEFAULT_CLIENT_INFO = gapic_v1.client_info.ClientInfo() class TenantServiceTransport(abc.ABC): @@ -55,6 +55,7 @@ def __init__( credentials_file: typing.Optional[str] = None, scopes: typing.Optional[typing.Sequence[str]] = AUTH_SCOPES, quota_project_id: typing.Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, **kwargs, ) -> None: """Instantiate the transport. @@ -72,6 +73,11 @@ def __init__( scope (Optional[Sequence[str]]): A list of scopes. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. """ # Save the hostname. Default to port 443 (HTTPS) if none is specified. if ":" not in host: @@ -99,13 +105,13 @@ def __init__( self._credentials = credentials # Lifted into its own function so it can be stubbed out during tests. - self._prep_wrapped_messages() + self._prep_wrapped_messages(client_info) - def _prep_wrapped_messages(self): + def _prep_wrapped_messages(self, client_info): # Precompute the wrapped methods. self._wrapped_methods = { self.create_tenant: gapic_v1.method.wrap_method( - self.create_tenant, default_timeout=30.0, client_info=_client_info, + self.create_tenant, default_timeout=30.0, client_info=client_info, ), self.get_tenant: gapic_v1.method.wrap_method( self.get_tenant, @@ -114,14 +120,14 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.update_tenant: gapic_v1.method.wrap_method( - self.update_tenant, default_timeout=30.0, client_info=_client_info, + self.update_tenant, default_timeout=30.0, client_info=client_info, ), self.delete_tenant: gapic_v1.method.wrap_method( self.delete_tenant, @@ -130,11 +136,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), self.list_tenants: gapic_v1.method.wrap_method( self.list_tenants, @@ -143,11 +149,11 @@ def _prep_wrapped_messages(self): maximum=60.0, multiplier=1.3, predicate=retries.if_exception_type( - exceptions.ServiceUnavailable, exceptions.DeadlineExceeded, + exceptions.DeadlineExceeded, exceptions.ServiceUnavailable, ), ), default_timeout=30.0, - client_info=_client_info, + client_info=client_info, ), } diff --git a/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc.py b/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc.py index 267845be..a1a331df 100644 --- a/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc.py +++ b/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc.py @@ -15,14 +15,15 @@ # limitations under the License. # +import warnings from typing import Callable, Dict, Optional, Sequence, Tuple from google.api_core import grpc_helpers # type: ignore +from google.api_core import gapic_v1 # type: ignore from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore - import grpc # type: ignore from google.cloud.talent_v4beta1.types import tenant @@ -30,7 +31,7 @@ from google.cloud.talent_v4beta1.types import tenant_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import TenantServiceTransport +from .base import TenantServiceTransport, DEFAULT_CLIENT_INFO class TenantServiceGrpcTransport(TenantServiceTransport): @@ -59,7 +60,9 @@ def __init__( channel: grpc.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, - quota_project_id: Optional[str] = None + ssl_channel_credentials: grpc.ChannelCredentials = None, + quota_project_id: Optional[str] = None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -78,16 +81,23 @@ def __init__( ignored if ``channel`` is provided. channel (Optional[grpc.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport @@ -103,6 +113,11 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint @@ -133,6 +148,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) self._stubs = {} # type: Dict[str, Callable] @@ -143,6 +175,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) @classmethod @@ -153,7 +186,7 @@ def create_channel( credentials_file: str = None, scopes: Optional[Sequence[str]] = None, quota_project_id: Optional[str] = None, - **kwargs + **kwargs, ) -> grpc.Channel: """Create and return a gRPC channel object. Args: @@ -187,7 +220,7 @@ def create_channel( credentials_file=credentials_file, scopes=scopes, quota_project_id=quota_project_id, - **kwargs + **kwargs, ) @property @@ -197,13 +230,6 @@ def grpc_channel(self) -> grpc.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc_asyncio.py b/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc_asyncio.py index 95e6ddbc..b87d1e04 100644 --- a/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc_asyncio.py +++ b/google/cloud/talent_v4beta1/services/tenant_service/transports/grpc_asyncio.py @@ -15,9 +15,12 @@ # limitations under the License. # +import warnings from typing import Awaitable, Callable, Dict, Optional, Sequence, Tuple +from google.api_core import gapic_v1 # type: ignore from google.api_core import grpc_helpers_async # type: ignore +from google import auth # type: ignore from google.auth import credentials # type: ignore from google.auth.transport.grpc import SslCredentials # type: ignore @@ -29,7 +32,7 @@ from google.cloud.talent_v4beta1.types import tenant_service from google.protobuf import empty_pb2 as empty # type: ignore -from .base import TenantServiceTransport +from .base import TenantServiceTransport, DEFAULT_CLIENT_INFO from .grpc import TenantServiceGrpcTransport @@ -101,7 +104,9 @@ def __init__( channel: aio.Channel = None, api_mtls_endpoint: str = None, client_cert_source: Callable[[], Tuple[bytes, bytes]] = None, + ssl_channel_credentials: grpc.ChannelCredentials = None, quota_project_id=None, + client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO, ) -> None: """Instantiate the transport. @@ -121,16 +126,23 @@ def __init__( are passed to :func:`google.auth.default`. channel (Optional[aio.Channel]): A ``Channel`` instance through which to make calls. - api_mtls_endpoint (Optional[str]): The mutual TLS endpoint. If - provided, it overrides the ``host`` argument and tries to create + api_mtls_endpoint (Optional[str]): Deprecated. The mutual TLS endpoint. + If provided, it overrides the ``host`` argument and tries to create a mutual TLS channel with client SSL credentials from ``client_cert_source`` or applicatin default SSL credentials. - client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): A - callback to provide client SSL certificate bytes and private key - bytes, both in PEM format. It is ignored if ``api_mtls_endpoint`` - is None. + client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): + Deprecated. A callback to provide client SSL certificate bytes and + private key bytes, both in PEM format. It is ignored if + ``api_mtls_endpoint`` is None. + ssl_channel_credentials (grpc.ChannelCredentials): SSL credentials + for grpc channel. It is ignored if ``channel`` is provided. quota_project_id (Optional[str]): An optional project to use for billing and quota. + client_info (google.api_core.gapic_v1.client_info.ClientInfo): + The client info used to send a user-agent string along with + API requests. If ``None``, then default info will be used. + Generally, you only need to set this if you're developing + your own client library. Raises: google.auth.exceptions.MutualTlsChannelError: If mutual TLS transport @@ -146,12 +158,22 @@ def __init__( # If a channel was explicitly provided, set it. self._grpc_channel = channel elif api_mtls_endpoint: + warnings.warn( + "api_mtls_endpoint and client_cert_source are deprecated", + DeprecationWarning, + ) + host = ( api_mtls_endpoint if ":" in api_mtls_endpoint else api_mtls_endpoint + ":443" ) + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + # Create SSL credentials with client_cert_source or application # default SSL credentials. if client_cert_source: @@ -171,6 +193,23 @@ def __init__( scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, ) + else: + host = host if ":" in host else host + ":443" + + if credentials is None: + credentials, _ = auth.default( + scopes=self.AUTH_SCOPES, quota_project_id=quota_project_id + ) + + # create a new channel. The provided one is ignored. + self._grpc_channel = type(self).create_channel( + host, + credentials=credentials, + credentials_file=credentials_file, + ssl_credentials=ssl_channel_credentials, + scopes=scopes or self.AUTH_SCOPES, + quota_project_id=quota_project_id, + ) # Run the base constructor. super().__init__( @@ -179,6 +218,7 @@ def __init__( credentials_file=credentials_file, scopes=scopes or self.AUTH_SCOPES, quota_project_id=quota_project_id, + client_info=client_info, ) self._stubs = {} @@ -190,13 +230,6 @@ def grpc_channel(self) -> aio.Channel: This property caches on the instance; repeated calls return the same channel. """ - # Sanity check: Only create a new channel if we do not already - # have one. - if not hasattr(self, "_grpc_channel"): - self._grpc_channel = self.create_channel( - self._host, credentials=self._credentials, - ) - # Return the channel from cache. return self._grpc_channel diff --git a/google/cloud/talent_v4beta1/types/common.py b/google/cloud/talent_v4beta1/types/common.py index 6b7b74c7..61614a0a 100644 --- a/google/cloud/talent_v4beta1/types/common.py +++ b/google/cloud/talent_v4beta1/types/common.py @@ -631,7 +631,7 @@ class CompensationEntry(proto.Message): [expected_units_per_year][google.cloud.talent.v4beta1.CompensationInfo.CompensationEntry.expected_units_per_year]. Attributes: - type (~.common.CompensationInfo.CompensationType): + type_ (~.common.CompensationInfo.CompensationType): Compensation type. Default is @@ -643,7 +643,7 @@ class CompensationEntry(proto.Message): [CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED][google.cloud.talent.v4beta1.CompensationInfo.CompensationUnit.COMPENSATION_UNIT_UNSPECIFIED]. amount (~.money.Money): Compensation amount. - range (~.common.CompensationInfo.CompensationRange): + range_ (~.common.CompensationInfo.CompensationRange): Compensation range. description (str): Compensation description. For example, could @@ -664,7 +664,7 @@ class CompensationEntry(proto.Message): - ANNUAL: 1 """ - type = proto.Field( + type_ = proto.Field( proto.ENUM, number=1, enum="CompensationInfo.CompensationType", ) @@ -676,7 +676,7 @@ class CompensationEntry(proto.Message): proto.MESSAGE, number=3, oneof="compensation_amount", message=money.Money, ) - range = proto.Field( + range_ = proto.Field( proto.MESSAGE, number=4, oneof="compensation_amount", @@ -810,9 +810,9 @@ class Rating(proto.Message): Attributes: overall (float): Overall score. - min (float): + min_ (float): The minimum value for the score. - max (float): + max_ (float): The maximum value for the score. interval (float): The steps within the score (for example, @@ -822,9 +822,9 @@ class Rating(proto.Message): overall = proto.Field(proto.DOUBLE, number=1) - min = proto.Field(proto.DOUBLE, number=2) + min_ = proto.Field(proto.DOUBLE, number=2) - max = proto.Field(proto.DOUBLE, number=3) + max_ = proto.Field(proto.DOUBLE, number=3) interval = proto.Field(proto.DOUBLE, number=4) diff --git a/google/cloud/talent_v4beta1/types/completion_service.py b/google/cloud/talent_v4beta1/types/completion_service.py index 353336e3..662afd21 100644 --- a/google/cloud/talent_v4beta1/types/completion_service.py +++ b/google/cloud/talent_v4beta1/types/completion_service.py @@ -66,7 +66,7 @@ class CompleteQueryRequest(proto.Message): scope (~.completion_service.CompleteQueryRequest.CompletionScope): The scope of the completion. The defaults is [CompletionScope.PUBLIC][google.cloud.talent.v4beta1.CompleteQueryRequest.CompletionScope.PUBLIC]. - type (~.completion_service.CompleteQueryRequest.CompletionType): + type_ (~.completion_service.CompleteQueryRequest.CompletionType): The completion topic. The default is [CompletionType.COMBINED][google.cloud.talent.v4beta1.CompleteQueryRequest.CompletionType.COMBINED]. """ @@ -96,7 +96,7 @@ class CompletionType(proto.Enum): scope = proto.Field(proto.ENUM, number=6, enum=CompletionScope,) - type = proto.Field(proto.ENUM, number=7, enum=CompletionType,) + type_ = proto.Field(proto.ENUM, number=7, enum=CompletionType,) class CompleteQueryResponse(proto.Message): @@ -117,7 +117,7 @@ class CompletionResult(proto.Message): Attributes: suggestion (str): The suggestion for the query. - type (~.completion_service.CompleteQueryRequest.CompletionType): + type_ (~.completion_service.CompleteQueryRequest.CompletionType): The completion topic. image_uri (str): The URI of the company image for @@ -126,7 +126,7 @@ class CompletionResult(proto.Message): suggestion = proto.Field(proto.STRING, number=1) - type = proto.Field( + type_ = proto.Field( proto.ENUM, number=2, enum=CompleteQueryRequest.CompletionType, ) diff --git a/google/cloud/talent_v4beta1/types/event.py b/google/cloud/talent_v4beta1/types/event.py index e1726ebd..4583b654 100644 --- a/google/cloud/talent_v4beta1/types/event.py +++ b/google/cloud/talent_v4beta1/types/event.py @@ -81,7 +81,7 @@ class JobEvent(proto.Message): application that implements Cloud Talent Solution. Attributes: - type (~.event.JobEvent.JobEventType): + type_ (~.event.JobEvent.JobEventType): Required. The type of the event (see [JobEventType][google.cloud.talent.v4beta1.JobEvent.JobEventType]). jobs (Sequence[str]): @@ -127,7 +127,7 @@ class JobEventType(proto.Enum): SENT_CV = 14 INTERVIEW_GRANTED = 15 - type = proto.Field(proto.ENUM, number=1, enum=JobEventType,) + type_ = proto.Field(proto.ENUM, number=1, enum=JobEventType,) jobs = proto.RepeatedField(proto.STRING, number=2) @@ -139,7 +139,7 @@ class ProfileEvent(proto.Message): application that implements Cloud Talent Solution. Attributes: - type (~.event.ProfileEvent.ProfileEventType): + type_ (~.event.ProfileEvent.ProfileEventType): Required. Type of event. profiles (Sequence[str]): Required. The [profile @@ -168,7 +168,7 @@ class ProfileEventType(proto.Enum): VIEW = 2 BOOKMARK = 3 - type = proto.Field(proto.ENUM, number=1, enum=ProfileEventType,) + type_ = proto.Field(proto.ENUM, number=1, enum=ProfileEventType,) profiles = proto.RepeatedField(proto.STRING, number=2) diff --git a/google/cloud/talent_v4beta1/types/filters.py b/google/cloud/talent_v4beta1/types/filters.py index c30a4e26..48d631b4 100644 --- a/google/cloud/talent_v4beta1/types/filters.py +++ b/google/cloud/talent_v4beta1/types/filters.py @@ -701,12 +701,12 @@ class CompensationFilter(proto.Message): r"""Filter on job compensation type and amount. Attributes: - type (~.filters.CompensationFilter.FilterType): + type_ (~.filters.CompensationFilter.FilterType): Required. Type of filter. units (Sequence[~.common.CompensationInfo.CompensationUnit]): Required. Specify desired ``base compensation entry's`` [CompensationInfo.CompensationUnit][google.cloud.talent.v4beta1.CompensationInfo.CompensationUnit]. - range (~.common.CompensationInfo.CompensationRange): + range_ (~.common.CompensationInfo.CompensationRange): Compensation range. include_jobs_with_unspecified_compensation_range (bool): If set to true, jobs with unspecified @@ -721,13 +721,13 @@ class FilterType(proto.Enum): ANNUALIZED_BASE_AMOUNT = 3 ANNUALIZED_TOTAL_AMOUNT = 4 - type = proto.Field(proto.ENUM, number=1, enum=FilterType,) + type_ = proto.Field(proto.ENUM, number=1, enum=FilterType,) units = proto.RepeatedField( proto.ENUM, number=2, enum=common.CompensationInfo.CompensationUnit, ) - range = proto.Field( + range_ = proto.Field( proto.MESSAGE, number=3, message=common.CompensationInfo.CompensationRange, ) @@ -1045,7 +1045,7 @@ class AvailabilityFilter(proto.Message): Attributes: signal_type (~.common.AvailabilitySignalType): Required. Type of signal to apply filter on. - range (~.common.TimestampRange): + range_ (~.common.TimestampRange): Required. Range of times to filter candidate signals by. required (bool): @@ -1060,7 +1060,7 @@ class AvailabilityFilter(proto.Message): signal_type = proto.Field(proto.ENUM, number=1, enum=common.AvailabilitySignalType,) - range = proto.Field(proto.MESSAGE, number=2, message=common.TimestampRange,) + range_ = proto.Field(proto.MESSAGE, number=2, message=common.TimestampRange,) required = proto.Field(proto.BOOL, number=3) diff --git a/google/cloud/talent_v4beta1/types/profile.py b/google/cloud/talent_v4beta1/types/profile.py index ceba029a..7a63d345 100644 --- a/google/cloud/talent_v4beta1/types/profile.py +++ b/google/cloud/talent_v4beta1/types/profile.py @@ -390,7 +390,7 @@ class AvailabilitySignal(proto.Message): r"""Candidate availability signal. Attributes: - type (~.common.AvailabilitySignalType): + type_ (~.common.AvailabilitySignalType): Type of signal. last_update_time (~.timestamp.Timestamp): Timestamp of when the given availability @@ -411,7 +411,7 @@ class AvailabilitySignal(proto.Message): [type][google.cloud.talent.v4beta1.AvailabilitySignal.type]. """ - type = proto.Field(proto.ENUM, number=1, enum=common.AvailabilitySignalType,) + type_ = proto.Field(proto.ENUM, number=1, enum=common.AvailabilitySignalType,) last_update_time = proto.Field( proto.MESSAGE, number=2, message=timestamp.Timestamp, @@ -590,7 +590,7 @@ class Phone(proto.Message): usage (~.common.ContactInfoUsage): The usage of the phone. For example, SCHOOL, WORK, PERSONAL. - type (~.profile.Phone.PhoneType): + type_ (~.profile.Phone.PhoneType): The phone type. For example, LANDLINE, MOBILE, FAX. number (str): @@ -623,7 +623,7 @@ class PhoneType(proto.Enum): usage = proto.Field(proto.ENUM, number=1, enum=common.ContactInfoUsage,) - type = proto.Field(proto.ENUM, number=2, enum=PhoneType,) + type_ = proto.Field(proto.ENUM, number=2, enum=PhoneType,) number = proto.Field(proto.STRING, number=3) diff --git a/noxfile.py b/noxfile.py index 8e537c9d..0235144d 100644 --- a/noxfile.py +++ b/noxfile.py @@ -151,7 +151,7 @@ def docs(session): """Build the docs for this library.""" session.install("-e", ".") - session.install("sphinx<3.0.0", "alabaster", "recommonmark") + session.install("sphinx", "alabaster", "recommonmark") shutil.rmtree(os.path.join("docs", "_build"), ignore_errors=True) session.run( diff --git a/samples/snippets/job_search_autocomplete_job_title.py b/samples/snippets/job_search_autocomplete_job_title.py index e9f52868..782517c1 100644 --- a/samples/snippets/job_search_autocomplete_job_title.py +++ b/samples/snippets/job_search_autocomplete_job_title.py @@ -47,7 +47,7 @@ def complete_query(project_id, tenant_id, query): print(f"Suggested title: {result.suggestion}") # Suggestion type is JOB_TITLE or COMPANY_TITLE print( - f"Suggestion type: {talent_v4beta1.CompleteQueryRequest.CompletionType(result.type).name}" + f"Suggestion type: {talent_v4beta1.CompleteQueryRequest.CompletionType(result.type_).name}" ) diff --git a/samples/snippets/job_search_commute_search.py b/samples/snippets/job_search_commute_search.py index 0b768441..d38a0ae4 100644 --- a/samples/snippets/job_search_commute_search.py +++ b/samples/snippets/job_search_commute_search.py @@ -59,7 +59,7 @@ def search_jobs(project_id, tenant_id): request_metadata=request_metadata, job_query=job_query, ) - for response_item in client.search_jobs(request=request): + for response_item in client.search_jobs(request=request).matching_jobs: print(f"Job summary: {response_item.job_summary}") print(f"Job title snippet: {response_item.job_title_snippet}") job = response_item.job diff --git a/samples/snippets/job_search_custom_ranking_search.py b/samples/snippets/job_search_custom_ranking_search.py index 015291e4..6f1addc9 100644 --- a/samples/snippets/job_search_custom_ranking_search.py +++ b/samples/snippets/job_search_custom_ranking_search.py @@ -55,7 +55,7 @@ def search_jobs(project_id, tenant_id): custom_ranking_info=custom_ranking_info, order_by=order_by ) - for response_item in client.search_jobs(request=request): + for response_item in client.search_jobs(request=request).matching_jobs: print(f"Job summary: {response_item.job_summary}") print(f"Job title snippet: {response_item.job_title_snippet}") job = response_item.job diff --git a/samples/snippets/job_search_histogram_search.py b/samples/snippets/job_search_histogram_search.py index 73bbc974..5a6a9145 100644 --- a/samples/snippets/job_search_histogram_search.py +++ b/samples/snippets/job_search_histogram_search.py @@ -55,7 +55,7 @@ def search_jobs(project_id, tenant_id, query): request_metadata=request_metadata, histogram_queries=histogram_queries, ) - for response_item in client.search_jobs(request=request): + for response_item in client.search_jobs(request=request).matching_jobs: print("Job summary: {response_item.job_summary}") print("Job title snippet: {response_item.job_title_snippet}") job = response_item.job diff --git a/scripts/fixup_talent_v4_keywords.py b/scripts/fixup_talent_v4_keywords.py new file mode 100644 index 00000000..0f86462f --- /dev/null +++ b/scripts/fixup_talent_v4_keywords.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import argparse +import os +import libcst as cst +import pathlib +import sys +from typing import (Any, Callable, Dict, List, Sequence, Tuple) + + +def partition( + predicate: Callable[[Any], bool], + iterator: Sequence[Any] +) -> Tuple[List[Any], List[Any]]: + """A stable, out-of-place partition.""" + results = ([], []) + + for i in iterator: + results[int(predicate(i))].append(i) + + # Returns trueList, falseList + return results[1], results[0] + + +class talentCallTransformer(cst.CSTTransformer): + CTRL_PARAMS: Tuple[str] = ('retry', 'timeout', 'metadata') + METHOD_TO_PARAMS: Dict[str, Tuple[str]] = { + 'batch_create_jobs': ('parent', 'jobs', ), + 'batch_delete_jobs': ('parent', 'names', ), + 'batch_update_jobs': ('parent', 'jobs', 'update_mask', ), + 'complete_query': ('tenant', 'query', 'page_size', 'language_codes', 'company', 'scope', 'type_', ), + 'create_client_event': ('parent', 'client_event', ), + 'create_company': ('parent', 'company', ), + 'create_job': ('parent', 'job', ), + 'create_tenant': ('parent', 'tenant', ), + 'delete_company': ('name', ), + 'delete_job': ('name', ), + 'delete_tenant': ('name', ), + 'get_company': ('name', ), + 'get_job': ('name', ), + 'get_tenant': ('name', ), + 'list_companies': ('parent', 'page_token', 'page_size', 'require_open_jobs', ), + 'list_jobs': ('parent', 'filter', 'page_token', 'page_size', 'job_view', ), + 'list_tenants': ('parent', 'page_token', 'page_size', ), + 'search_jobs': ('parent', 'request_metadata', 'search_mode', 'job_query', 'enable_broadening', 'histogram_queries', 'job_view', 'offset', 'max_page_size', 'page_token', 'order_by', 'diversification_level', 'custom_ranking_info', 'disable_keyword_match', ), + 'search_jobs_for_alert': ('parent', 'request_metadata', 'search_mode', 'job_query', 'enable_broadening', 'histogram_queries', 'job_view', 'offset', 'max_page_size', 'page_token', 'order_by', 'diversification_level', 'custom_ranking_info', 'disable_keyword_match', ), + 'update_company': ('company', 'update_mask', ), + 'update_job': ('job', 'update_mask', ), + 'update_tenant': ('tenant', 'update_mask', ), + + } + + def leave_Call(self, original: cst.Call, updated: cst.Call) -> cst.CSTNode: + try: + key = original.func.attr.value + kword_params = self.METHOD_TO_PARAMS[key] + except (AttributeError, KeyError): + # Either not a method from the API or too convoluted to be sure. + return updated + + # If the existing code is valid, keyword args come after positional args. + # Therefore, all positional args must map to the first parameters. + args, kwargs = partition(lambda a: not bool(a.keyword), updated.args) + if any(k.keyword.value == "request" for k in kwargs): + # We've already fixed this file, don't fix it again. + return updated + + kwargs, ctrl_kwargs = partition( + lambda a: not a.keyword.value in self.CTRL_PARAMS, + kwargs + ) + + args, ctrl_args = args[:len(kword_params)], args[len(kword_params):] + ctrl_kwargs.extend(cst.Arg(value=a.value, keyword=cst.Name(value=ctrl)) + for a, ctrl in zip(ctrl_args, self.CTRL_PARAMS)) + + request_arg = cst.Arg( + value=cst.Dict([ + cst.DictElement( + cst.SimpleString("'{}'".format(name)), + cst.Element(value=arg.value) + ) + # Note: the args + kwargs looks silly, but keep in mind that + # the control parameters had to be stripped out, and that + # those could have been passed positionally or by keyword. + for name, arg in zip(kword_params, args + kwargs)]), + keyword=cst.Name("request") + ) + + return updated.with_changes( + args=[request_arg] + ctrl_kwargs + ) + + +def fix_files( + in_dir: pathlib.Path, + out_dir: pathlib.Path, + *, + transformer=talentCallTransformer(), +): + """Duplicate the input dir to the output dir, fixing file method calls. + + Preconditions: + * in_dir is a real directory + * out_dir is a real, empty directory + """ + pyfile_gen = ( + pathlib.Path(os.path.join(root, f)) + for root, _, files in os.walk(in_dir) + for f in files if os.path.splitext(f)[1] == ".py" + ) + + for fpath in pyfile_gen: + with open(fpath, 'r') as f: + src = f.read() + + # Parse the code and insert method call fixes. + tree = cst.parse_module(src) + updated = tree.visit(transformer) + + # Create the path and directory structure for the new file. + updated_path = out_dir.joinpath(fpath.relative_to(in_dir)) + updated_path.parent.mkdir(parents=True, exist_ok=True) + + # Generate the updated source file at the corresponding path. + with open(updated_path, 'w') as f: + f.write(updated.code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description="""Fix up source that uses the talent client library. + +The existing sources are NOT overwritten but are copied to output_dir with changes made. + +Note: This tool operates at a best-effort level at converting positional + parameters in client method calls to keyword based parameters. + Cases where it WILL FAIL include + A) * or ** expansion in a method call. + B) Calls via function or method alias (includes free function calls) + C) Indirect or dispatched calls (e.g. the method is looked up dynamically) + + These all constitute false negatives. The tool will also detect false + positives when an API method shares a name with another method. +""") + parser.add_argument( + '-d', + '--input-directory', + required=True, + dest='input_dir', + help='the input directory to walk for python files to fix up', + ) + parser.add_argument( + '-o', + '--output-directory', + required=True, + dest='output_dir', + help='the directory to output files fixed via un-flattening', + ) + args = parser.parse_args() + input_dir = pathlib.Path(args.input_dir) + output_dir = pathlib.Path(args.output_dir) + if not input_dir.is_dir(): + print( + f"input directory '{input_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if not output_dir.is_dir(): + print( + f"output directory '{output_dir}' does not exist or is not a directory", + file=sys.stderr, + ) + sys.exit(-1) + + if os.listdir(output_dir): + print( + f"output directory '{output_dir}' is not empty", + file=sys.stderr, + ) + sys.exit(-1) + + fix_files(input_dir, output_dir) diff --git a/synth.metadata b/synth.metadata index ddbee2fb..498d930d 100644 --- a/synth.metadata +++ b/synth.metadata @@ -39,6 +39,15 @@ "language": "python", "generator": "bazel" } + }, + { + "client": { + "source": "googleapis", + "apiName": "talent", + "apiVersion": "v4", + "language": "python", + "generator": "bazel" + } } ] } \ No newline at end of file diff --git a/synth.py b/synth.py index 6481c072..78cbbc9e 100644 --- a/synth.py +++ b/synth.py @@ -20,7 +20,7 @@ gapic = gcp.GAPICBazel() common = gcp.CommonTemplates() -versions = ["v4beta1"] +versions = ["v4beta1", "v4"] excludes = ["setup.py", "nox*.py", "README.rst", "docs/conf.py", "docs/index.rst"] # ---------------------------------------------------------------------------- @@ -30,7 +30,7 @@ library = gapic.py_library( service="talent", version=version, - bazel_target=f"//google/cloud/talent/{version}:talent-{version}-py", + bazel_target=f"//google/cloud/talent/{version}:talent-{version}-py", include_protos=True ) s.move(library, excludes=excludes) @@ -58,8 +58,6 @@ ) s.move(templated_files, excludes=[".coveragerc"]) # microgenerator has a good .coveragerc file -# TODO(busunkim): Use latest sphinx after microgenerator transition -s.replace("noxfile.py", """['"]sphinx['"]""", '"sphinx<3.0.0"') # Temporarily disable warnings due to # https://github.com/googleapis/gapic-generator-python/issues/525 s.replace("noxfile.py", '[\"\']-W[\"\']', '# "-W"') diff --git a/tests/unit/gapic/talent_v4/__init__.py b/tests/unit/gapic/talent_v4/__init__.py new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/tests/unit/gapic/talent_v4/__init__.py @@ -0,0 +1 @@ + diff --git a/tests/unit/gapic/talent_v4/test_company_service.py b/tests/unit/gapic/talent_v4/test_company_service.py new file mode 100644 index 00000000..7bf7ac24 --- /dev/null +++ b/tests/unit/gapic/talent_v4/test_company_service.py @@ -0,0 +1,2111 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.talent_v4.services.company_service import CompanyServiceAsyncClient +from google.cloud.talent_v4.services.company_service import CompanyServiceClient +from google.cloud.talent_v4.services.company_service import pagers +from google.cloud.talent_v4.services.company_service import transports +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import company +from google.cloud.talent_v4.types import company as gct_company +from google.cloud.talent_v4.types import company_service +from google.oauth2 import service_account +from google.protobuf import field_mask_pb2 as field_mask # type: ignore +from google.type import latlng_pb2 as latlng # type: ignore +from google.type import postal_address_pb2 as gt_postal_address # type: ignore + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert CompanyServiceClient._get_default_mtls_endpoint(None) is None + assert ( + CompanyServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + CompanyServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + CompanyServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + CompanyServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + CompanyServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + ) + + +@pytest.mark.parametrize( + "client_class", [CompanyServiceClient, CompanyServiceAsyncClient] +) +def test_company_service_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_company_service_client_get_transport_class(): + transport = CompanyServiceClient.get_transport_class() + assert transport == transports.CompanyServiceGrpcTransport + + transport = CompanyServiceClient.get_transport_class("grpc") + assert transport == transports.CompanyServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + CompanyServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceClient), +) +@mock.patch.object( + CompanyServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceAsyncClient), +) +def test_company_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(CompanyServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(CompanyServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc", "true"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc", "false"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + CompanyServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceClient), +) +@mock.patch.object( + CompanyServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_company_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_company_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_company_service_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_company_service_client_client_options_from_dict(): + with mock.patch( + "google.cloud.talent_v4.services.company_service.transports.CompanyServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CompanyServiceClient( + client_options={"api_endpoint": "squid.clam.whelk"} + ) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_create_company( + transport: str = "grpc", request_type=company_service.CreateCompanyRequest +): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + + response = client.create_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == company_service.CreateCompanyRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_create_company_from_dict(): + test_create_company(request_type=dict) + + +@pytest.mark.asyncio +async def test_create_company_async(transport: str = "grpc_asyncio"): + client = CompanyServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = company_service.CreateCompanyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + ) + + response = await client.create_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_create_company_field_headers(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.CreateCompanyRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_company), "__call__") as call: + call.return_value = gct_company.Company() + + client.create_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_company_field_headers_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.CreateCompanyRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_company), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_company.Company()) + + await client.create_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_create_company_flattened(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_company( + parent="parent_value", company=gct_company.Company(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].company == gct_company.Company(name="name_value") + + +def test_create_company_flattened_error(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_company( + company_service.CreateCompanyRequest(), + parent="parent_value", + company=gct_company.Company(name="name_value"), + ) + + +@pytest.mark.asyncio +async def test_create_company_flattened_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_company.Company()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_company( + parent="parent_value", company=gct_company.Company(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].company == gct_company.Company(name="name_value") + + +@pytest.mark.asyncio +async def test_create_company_flattened_error_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_company( + company_service.CreateCompanyRequest(), + parent="parent_value", + company=gct_company.Company(name="name_value"), + ) + + +def test_get_company( + transport: str = "grpc", request_type=company_service.GetCompanyRequest +): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + + response = client.get_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == company_service.GetCompanyRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_get_company_from_dict(): + test_get_company(request_type=dict) + + +@pytest.mark.asyncio +async def test_get_company_async(transport: str = "grpc_asyncio"): + client = CompanyServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = company_service.GetCompanyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + ) + + response = await client.get_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_get_company_field_headers(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.GetCompanyRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_company), "__call__") as call: + call.return_value = company.Company() + + client.get_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_company_field_headers_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.GetCompanyRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_company), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(company.Company()) + + await client.get_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_get_company_flattened(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = company.Company() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_company(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_get_company_flattened_error(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_company( + company_service.GetCompanyRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_company_flattened_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = company.Company() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(company.Company()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_company(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_get_company_flattened_error_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.get_company( + company_service.GetCompanyRequest(), name="name_value", + ) + + +def test_update_company( + transport: str = "grpc", request_type=company_service.UpdateCompanyRequest +): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + + response = client.update_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == company_service.UpdateCompanyRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_update_company_from_dict(): + test_update_company(request_type=dict) + + +@pytest.mark.asyncio +async def test_update_company_async(transport: str = "grpc_asyncio"): + client = CompanyServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = company_service.UpdateCompanyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_company.Company( + name="name_value", + display_name="display_name_value", + external_id="external_id_value", + size=common.CompanySize.MINI, + headquarters_address="headquarters_address_value", + hiring_agency=True, + eeo_text="eeo_text_value", + website_uri="website_uri_value", + career_site_uri="career_site_uri_value", + image_uri="image_uri_value", + keyword_searchable_job_custom_attributes=[ + "keyword_searchable_job_custom_attributes_value" + ], + suspended=True, + ) + ) + + response = await client.update_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_company.Company) + + assert response.name == "name_value" + + assert response.display_name == "display_name_value" + + assert response.external_id == "external_id_value" + + assert response.size == common.CompanySize.MINI + + assert response.headquarters_address == "headquarters_address_value" + + assert response.hiring_agency is True + + assert response.eeo_text == "eeo_text_value" + + assert response.website_uri == "website_uri_value" + + assert response.career_site_uri == "career_site_uri_value" + + assert response.image_uri == "image_uri_value" + + assert response.keyword_searchable_job_custom_attributes == [ + "keyword_searchable_job_custom_attributes_value" + ] + + assert response.suspended is True + + +def test_update_company_field_headers(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.UpdateCompanyRequest() + request.company.name = "company.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_company), "__call__") as call: + call.return_value = gct_company.Company() + + client.update_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "company.name=company.name/value",) in kw[ + "metadata" + ] + + +@pytest.mark.asyncio +async def test_update_company_field_headers_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.UpdateCompanyRequest() + request.company.name = "company.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_company), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_company.Company()) + + await client.update_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "company.name=company.name/value",) in kw[ + "metadata" + ] + + +def test_update_company_flattened(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_company( + company=gct_company.Company(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].company == gct_company.Company(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +def test_update_company_flattened_error(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_company( + company_service.UpdateCompanyRequest(), + company=gct_company.Company(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_company_flattened_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_company.Company() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_company.Company()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_company( + company=gct_company.Company(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].company == gct_company.Company(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +@pytest.mark.asyncio +async def test_update_company_flattened_error_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.update_company( + company_service.UpdateCompanyRequest(), + company=gct_company.Company(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +def test_delete_company( + transport: str = "grpc", request_type=company_service.DeleteCompanyRequest +): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + response = client.delete_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == company_service.DeleteCompanyRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_company_from_dict(): + test_delete_company(request_type=dict) + + +@pytest.mark.asyncio +async def test_delete_company_async(transport: str = "grpc_asyncio"): + client = CompanyServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = company_service.DeleteCompanyRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + response = await client.delete_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_company_field_headers(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.DeleteCompanyRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_company), "__call__") as call: + call.return_value = None + + client.delete_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_delete_company_field_headers_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.DeleteCompanyRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_company), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + await client.delete_company(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_delete_company_flattened(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_company), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.delete_company(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_delete_company_flattened_error(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_company( + company_service.DeleteCompanyRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_delete_company_flattened_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_company), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.delete_company(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_delete_company_flattened_error_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.delete_company( + company_service.DeleteCompanyRequest(), name="name_value", + ) + + +def test_list_companies( + transport: str = "grpc", request_type=company_service.ListCompaniesRequest +): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_companies), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = company_service.ListCompaniesResponse( + next_page_token="next_page_token_value", + ) + + response = client.list_companies(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == company_service.ListCompaniesRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListCompaniesPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_companies_from_dict(): + test_list_companies(request_type=dict) + + +@pytest.mark.asyncio +async def test_list_companies_async(transport: str = "grpc_asyncio"): + client = CompanyServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = company_service.ListCompaniesRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_companies), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + company_service.ListCompaniesResponse( + next_page_token="next_page_token_value", + ) + ) + + response = await client.list_companies(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListCompaniesAsyncPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_companies_field_headers(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.ListCompaniesRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_companies), "__call__") as call: + call.return_value = company_service.ListCompaniesResponse() + + client.list_companies(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_companies_field_headers_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = company_service.ListCompaniesRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_companies), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + company_service.ListCompaniesResponse() + ) + + await client.list_companies(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_list_companies_flattened(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_companies), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = company_service.ListCompaniesResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_companies(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +def test_list_companies_flattened_error(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_companies( + company_service.ListCompaniesRequest(), parent="parent_value", + ) + + +@pytest.mark.asyncio +async def test_list_companies_flattened_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_companies), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = company_service.ListCompaniesResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + company_service.ListCompaniesResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_companies(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +@pytest.mark.asyncio +async def test_list_companies_flattened_error_async(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_companies( + company_service.ListCompaniesRequest(), parent="parent_value", + ) + + +def test_list_companies_pager(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_companies), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(), company.Company(),], + next_page_token="abc", + ), + company_service.ListCompaniesResponse(companies=[], next_page_token="def",), + company_service.ListCompaniesResponse( + companies=[company.Company(),], next_page_token="ghi", + ), + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(),], + ), + RuntimeError, + ) + + metadata = () + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_companies(request={}) + + assert pager._metadata == metadata + + results = [i for i in pager] + assert len(results) == 6 + assert all(isinstance(i, company.Company) for i in results) + + +def test_list_companies_pages(): + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_companies), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(), company.Company(),], + next_page_token="abc", + ), + company_service.ListCompaniesResponse(companies=[], next_page_token="def",), + company_service.ListCompaniesResponse( + companies=[company.Company(),], next_page_token="ghi", + ), + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(),], + ), + RuntimeError, + ) + pages = list(client.list_companies(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_companies_async_pager(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_companies), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(), company.Company(),], + next_page_token="abc", + ), + company_service.ListCompaniesResponse(companies=[], next_page_token="def",), + company_service.ListCompaniesResponse( + companies=[company.Company(),], next_page_token="ghi", + ), + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(),], + ), + RuntimeError, + ) + async_pager = await client.list_companies(request={},) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, company.Company) for i in responses) + + +@pytest.mark.asyncio +async def test_list_companies_async_pages(): + client = CompanyServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_companies), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(), company.Company(),], + next_page_token="abc", + ), + company_service.ListCompaniesResponse(companies=[], next_page_token="def",), + company_service.ListCompaniesResponse( + companies=[company.Company(),], next_page_token="ghi", + ), + company_service.ListCompaniesResponse( + companies=[company.Company(), company.Company(),], + ), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_companies(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.CompanyServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.CompanyServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompanyServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.CompanyServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompanyServiceClient( + client_options={"scopes": ["1", "2"]}, transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.CompanyServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + client = CompanyServiceClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.CompanyServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.CompanyServiceGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.CompanyServiceGrpcTransport,) + + +def test_company_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.CompanyServiceTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_company_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.talent_v4.services.company_service.transports.CompanyServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.CompanyServiceTransport( + credentials=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_company", + "get_company", + "update_company", + "delete_company", + "list_companies", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_company_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.talent_v4.services.company_service.transports.CompanyServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompanyServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_company_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4.services.company_service.transports.CompanyServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompanyServiceTransport() + adc.assert_called_once() + + +def test_company_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + CompanyServiceClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id=None, + ) + + +def test_company_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.CompanyServiceGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_company_service_host_no_port(): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions(api_endpoint="jobs.googleapis.com"), + ) + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_company_service_host_with_port(): + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="jobs.googleapis.com:8000" + ), + ) + assert client._transport._host == "jobs.googleapis.com:8000" + + +def test_company_service_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.CompanyServiceGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +def test_company_service_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.CompanyServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], +) +def test_company_service_transport_channel_mtls_with_client_cert_source( + transport_class, +): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], +) +def test_company_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_company_path(): + project = "squid" + tenant = "clam" + company = "whelk" + + expected = "projects/{project}/tenants/{tenant}/companies/{company}".format( + project=project, tenant=tenant, company=company, + ) + actual = CompanyServiceClient.company_path(project, tenant, company) + assert expected == actual + + +def test_parse_company_path(): + expected = { + "project": "octopus", + "tenant": "oyster", + "company": "nudibranch", + } + path = CompanyServiceClient.company_path(**expected) + + # Check that the path construction is reversible. + actual = CompanyServiceClient.parse_company_path(path) + assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.CompanyServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.CompanyServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = CompanyServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4/test_completion.py b/tests/unit/gapic/talent_v4/test_completion.py new file mode 100644 index 00000000..390d8e24 --- /dev/null +++ b/tests/unit/gapic/talent_v4/test_completion.py @@ -0,0 +1,851 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.talent_v4.services.completion import CompletionAsyncClient +from google.cloud.talent_v4.services.completion import CompletionClient +from google.cloud.talent_v4.services.completion import transports +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import completion_service +from google.oauth2 import service_account + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert CompletionClient._get_default_mtls_endpoint(None) is None + assert ( + CompletionClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint + ) + assert ( + CompletionClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + CompletionClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + CompletionClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert CompletionClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + + +@pytest.mark.parametrize("client_class", [CompletionClient, CompletionAsyncClient]) +def test_completion_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_completion_client_get_transport_class(): + transport = CompletionClient.get_transport_class() + assert transport == transports.CompletionGrpcTransport + + transport = CompletionClient.get_transport_class("grpc") + assert transport == transports.CompletionGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompletionClient, transports.CompletionGrpcTransport, "grpc"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + CompletionClient, "DEFAULT_ENDPOINT", modify_default_endpoint(CompletionClient) +) +@mock.patch.object( + CompletionAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompletionAsyncClient), +) +def test_completion_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(CompletionClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(CompletionClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (CompletionClient, transports.CompletionGrpcTransport, "grpc", "true"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (CompletionClient, transports.CompletionGrpcTransport, "grpc", "false"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + CompletionClient, "DEFAULT_ENDPOINT", modify_default_endpoint(CompletionClient) +) +@mock.patch.object( + CompletionAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompletionAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_completion_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompletionClient, transports.CompletionGrpcTransport, "grpc"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_completion_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (CompletionClient, transports.CompletionGrpcTransport, "grpc"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_completion_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_completion_client_client_options_from_dict(): + with mock.patch( + "google.cloud.talent_v4.services.completion.transports.CompletionGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = CompletionClient(client_options={"api_endpoint": "squid.clam.whelk"}) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_complete_query( + transport: str = "grpc", request_type=completion_service.CompleteQueryRequest +): + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.complete_query), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = completion_service.CompleteQueryResponse() + + response = client.complete_query(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == completion_service.CompleteQueryRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, completion_service.CompleteQueryResponse) + + +def test_complete_query_from_dict(): + test_complete_query(request_type=dict) + + +@pytest.mark.asyncio +async def test_complete_query_async(transport: str = "grpc_asyncio"): + client = CompletionAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = completion_service.CompleteQueryRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.complete_query), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + completion_service.CompleteQueryResponse() + ) + + response = await client.complete_query(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, completion_service.CompleteQueryResponse) + + +def test_complete_query_field_headers(): + client = CompletionClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = completion_service.CompleteQueryRequest() + request.tenant = "tenant/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.complete_query), "__call__") as call: + call.return_value = completion_service.CompleteQueryResponse() + + client.complete_query(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "tenant=tenant/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_complete_query_field_headers_async(): + client = CompletionAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = completion_service.CompleteQueryRequest() + request.tenant = "tenant/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.complete_query), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + completion_service.CompleteQueryResponse() + ) + + await client.complete_query(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "tenant=tenant/value",) in kw["metadata"] + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.CompletionGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.CompletionGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompletionClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.CompletionGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = CompletionClient( + client_options={"scopes": ["1", "2"]}, transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.CompletionGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + client = CompletionClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.CompletionGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.CompletionGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = CompletionClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.CompletionGrpcTransport,) + + +def test_completion_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.CompletionTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_completion_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.talent_v4.services.completion.transports.CompletionTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.CompletionTransport( + credentials=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ("complete_query",) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_completion_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.talent_v4.services.completion.transports.CompletionTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompletionTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_completion_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4.services.completion.transports.CompletionTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompletionTransport() + adc.assert_called_once() + + +def test_completion_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + CompletionClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id=None, + ) + + +def test_completion_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.CompletionGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_completion_host_no_port(): + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions(api_endpoint="jobs.googleapis.com"), + ) + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_completion_host_with_port(): + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="jobs.googleapis.com:8000" + ), + ) + assert client._transport._host == "jobs.googleapis.com:8000" + + +def test_completion_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.CompletionGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +def test_completion_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.CompletionGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +@pytest.mark.parametrize( + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], +) +def test_completion_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], +) +def test_completion_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.CompletionTransport, "_prep_wrapped_messages" + ) as prep: + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.CompletionTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = CompletionClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4/test_event_service.py b/tests/unit/gapic/talent_v4/test_event_service.py new file mode 100644 index 00000000..dd38cf40 --- /dev/null +++ b/tests/unit/gapic/talent_v4/test_event_service.py @@ -0,0 +1,958 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.talent_v4.services.event_service import EventServiceAsyncClient +from google.cloud.talent_v4.services.event_service import EventServiceClient +from google.cloud.talent_v4.services.event_service import transports +from google.cloud.talent_v4.types import event +from google.cloud.talent_v4.types import event_service +from google.oauth2 import service_account +from google.protobuf import timestamp_pb2 as timestamp # type: ignore + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert EventServiceClient._get_default_mtls_endpoint(None) is None + assert ( + EventServiceClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint + ) + assert ( + EventServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + EventServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + EventServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert EventServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + + +@pytest.mark.parametrize("client_class", [EventServiceClient, EventServiceAsyncClient]) +def test_event_service_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_event_service_client_get_transport_class(): + transport = EventServiceClient.get_transport_class() + assert transport == transports.EventServiceGrpcTransport + + transport = EventServiceClient.get_transport_class("grpc") + assert transport == transports.EventServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + EventServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(EventServiceClient) +) +@mock.patch.object( + EventServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(EventServiceAsyncClient), +) +def test_event_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(EventServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(EventServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc", "true"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc", "false"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + EventServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(EventServiceClient) +) +@mock.patch.object( + EventServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(EventServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_event_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_event_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_event_service_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_event_service_client_client_options_from_dict(): + with mock.patch( + "google.cloud.talent_v4.services.event_service.transports.EventServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = EventServiceClient(client_options={"api_endpoint": "squid.clam.whelk"}) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_create_client_event( + transport: str = "grpc", request_type=event_service.CreateClientEventRequest +): + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.create_client_event), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = event.ClientEvent( + request_id="request_id_value", + event_id="event_id_value", + event_notes="event_notes_value", + job_event=event.JobEvent(type_=event.JobEvent.JobEventType.IMPRESSION), + ) + + response = client.create_client_event(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == event_service.CreateClientEventRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, event.ClientEvent) + + assert response.request_id == "request_id_value" + + assert response.event_id == "event_id_value" + + assert response.event_notes == "event_notes_value" + + +def test_create_client_event_from_dict(): + test_create_client_event(request_type=dict) + + +@pytest.mark.asyncio +async def test_create_client_event_async(transport: str = "grpc_asyncio"): + client = EventServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = event_service.CreateClientEventRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_client_event), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + event.ClientEvent( + request_id="request_id_value", + event_id="event_id_value", + event_notes="event_notes_value", + ) + ) + + response = await client.create_client_event(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, event.ClientEvent) + + assert response.request_id == "request_id_value" + + assert response.event_id == "event_id_value" + + assert response.event_notes == "event_notes_value" + + +def test_create_client_event_field_headers(): + client = EventServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = event_service.CreateClientEventRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.create_client_event), "__call__" + ) as call: + call.return_value = event.ClientEvent() + + client.create_client_event(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_client_event_field_headers_async(): + client = EventServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = event_service.CreateClientEventRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_client_event), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(event.ClientEvent()) + + await client.create_client_event(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_create_client_event_flattened(): + client = EventServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.create_client_event), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = event.ClientEvent() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_client_event( + parent="parent_value", + client_event=event.ClientEvent(request_id="request_id_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].client_event == event.ClientEvent(request_id="request_id_value") + + +def test_create_client_event_flattened_error(): + client = EventServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_client_event( + event_service.CreateClientEventRequest(), + parent="parent_value", + client_event=event.ClientEvent(request_id="request_id_value"), + ) + + +@pytest.mark.asyncio +async def test_create_client_event_flattened_async(): + client = EventServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_client_event), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = event.ClientEvent() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(event.ClientEvent()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_client_event( + parent="parent_value", + client_event=event.ClientEvent(request_id="request_id_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].client_event == event.ClientEvent(request_id="request_id_value") + + +@pytest.mark.asyncio +async def test_create_client_event_flattened_error_async(): + client = EventServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_client_event( + event_service.CreateClientEventRequest(), + parent="parent_value", + client_event=event.ClientEvent(request_id="request_id_value"), + ) + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.EventServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.EventServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EventServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.EventServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = EventServiceClient( + client_options={"scopes": ["1", "2"]}, transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.EventServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + client = EventServiceClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.EventServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.EventServiceGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = EventServiceClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.EventServiceGrpcTransport,) + + +def test_event_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.EventServiceTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_event_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.talent_v4.services.event_service.transports.EventServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.EventServiceTransport( + credentials=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ("create_client_event",) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_event_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.talent_v4.services.event_service.transports.EventServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.EventServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_event_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4.services.event_service.transports.EventServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.EventServiceTransport() + adc.assert_called_once() + + +def test_event_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + EventServiceClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id=None, + ) + + +def test_event_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.EventServiceGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_event_service_host_no_port(): + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions(api_endpoint="jobs.googleapis.com"), + ) + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_event_service_host_with_port(): + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="jobs.googleapis.com:8000" + ), + ) + assert client._transport._host == "jobs.googleapis.com:8000" + + +def test_event_service_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.EventServiceGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +def test_event_service_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.EventServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +@pytest.mark.parametrize( + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], +) +def test_event_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], +) +def test_event_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.EventServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.EventServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = EventServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4/test_job_service.py b/tests/unit/gapic/talent_v4/test_job_service.py new file mode 100644 index 00000000..e8bf3b66 --- /dev/null +++ b/tests/unit/gapic/talent_v4/test_job_service.py @@ -0,0 +1,3087 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import future +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.api_core import operation_async # type: ignore +from google.api_core import operations_v1 +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.talent_v4.services.job_service import JobServiceAsyncClient +from google.cloud.talent_v4.services.job_service import JobServiceClient +from google.cloud.talent_v4.services.job_service import pagers +from google.cloud.talent_v4.services.job_service import transports +from google.cloud.talent_v4.types import common +from google.cloud.talent_v4.types import filters +from google.cloud.talent_v4.types import histogram +from google.cloud.talent_v4.types import job +from google.cloud.talent_v4.types import job as gct_job +from google.cloud.talent_v4.types import job_service +from google.longrunning import operations_pb2 +from google.oauth2 import service_account +from google.protobuf import duration_pb2 as duration # type: ignore +from google.protobuf import field_mask_pb2 as field_mask # type: ignore +from google.protobuf import timestamp_pb2 as timestamp # type: ignore +from google.protobuf import wrappers_pb2 as wrappers # type: ignore +from google.type import latlng_pb2 as latlng # type: ignore +from google.type import money_pb2 as money # type: ignore +from google.type import postal_address_pb2 as gt_postal_address # type: ignore +from google.type import timeofday_pb2 as timeofday # type: ignore + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert JobServiceClient._get_default_mtls_endpoint(None) is None + assert ( + JobServiceClient._get_default_mtls_endpoint(api_endpoint) == api_mtls_endpoint + ) + assert ( + JobServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + JobServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + JobServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert JobServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + + +@pytest.mark.parametrize("client_class", [JobServiceClient, JobServiceAsyncClient]) +def test_job_service_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_job_service_client_get_transport_class(): + transport = JobServiceClient.get_transport_class() + assert transport == transports.JobServiceGrpcTransport + + transport = JobServiceClient.get_transport_class("grpc") + assert transport == transports.JobServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + JobServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(JobServiceClient) +) +@mock.patch.object( + JobServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(JobServiceAsyncClient), +) +def test_job_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(JobServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(JobServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc", "true"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc", "false"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + JobServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(JobServiceClient) +) +@mock.patch.object( + JobServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(JobServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_job_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_job_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_job_service_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_job_service_client_client_options_from_dict(): + with mock.patch( + "google.cloud.talent_v4.services.job_service.transports.JobServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = JobServiceClient(client_options={"api_endpoint": "squid.clam.whelk"}) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_create_job(transport: str = "grpc", request_type=job_service.CreateJobRequest): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + + response = client.create_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.CreateJobRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_create_job_from_dict(): + test_create_job(request_type=dict) + + +@pytest.mark.asyncio +async def test_create_job_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.CreateJobRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + ) + + response = await client.create_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_create_job_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.CreateJobRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_job), "__call__") as call: + call.return_value = gct_job.Job() + + client.create_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_job_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.CreateJobRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_job), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_job.Job()) + + await client.create_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_create_job_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_job( + parent="parent_value", job=gct_job.Job(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].job == gct_job.Job(name="name_value") + + +def test_create_job_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_job( + job_service.CreateJobRequest(), + parent="parent_value", + job=gct_job.Job(name="name_value"), + ) + + +@pytest.mark.asyncio +async def test_create_job_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_job.Job()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_job( + parent="parent_value", job=gct_job.Job(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].job == gct_job.Job(name="name_value") + + +@pytest.mark.asyncio +async def test_create_job_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_job( + job_service.CreateJobRequest(), + parent="parent_value", + job=gct_job.Job(name="name_value"), + ) + + +def test_batch_create_jobs( + transport: str = "grpc", request_type=job_service.BatchCreateJobsRequest +): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_create_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/spam") + + response = client.batch_create_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.BatchCreateJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_create_jobs_from_dict(): + test_batch_create_jobs(request_type=dict) + + +@pytest.mark.asyncio +async def test_batch_create_jobs_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.BatchCreateJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_create_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + + response = await client.batch_create_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_create_jobs_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchCreateJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_create_jobs), "__call__" + ) as call: + call.return_value = operations_pb2.Operation(name="operations/op") + + client.batch_create_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_batch_create_jobs_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchCreateJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_create_jobs), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/op") + ) + + await client.batch_create_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_batch_create_jobs_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_create_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.batch_create_jobs( + parent="parent_value", jobs=[job.Job(name="name_value")], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].jobs == [job.Job(name="name_value")] + + +def test_batch_create_jobs_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.batch_create_jobs( + job_service.BatchCreateJobsRequest(), + parent="parent_value", + jobs=[job.Job(name="name_value")], + ) + + +@pytest.mark.asyncio +async def test_batch_create_jobs_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_create_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.batch_create_jobs( + parent="parent_value", jobs=[job.Job(name="name_value")], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].jobs == [job.Job(name="name_value")] + + +@pytest.mark.asyncio +async def test_batch_create_jobs_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.batch_create_jobs( + job_service.BatchCreateJobsRequest(), + parent="parent_value", + jobs=[job.Job(name="name_value")], + ) + + +def test_get_job(transport: str = "grpc", request_type=job_service.GetJobRequest): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + + response = client.get_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.GetJobRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_get_job_from_dict(): + test_get_job(request_type=dict) + + +@pytest.mark.asyncio +async def test_get_job_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.GetJobRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._client._transport.get_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + ) + + response = await client.get_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_get_job_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.GetJobRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_job), "__call__") as call: + call.return_value = job.Job() + + client.get_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_job_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.GetJobRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._client._transport.get_job), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(job.Job()) + + await client.get_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_get_job_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job.Job() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_job(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_get_job_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_job( + job_service.GetJobRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_job_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._client._transport.get_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job.Job() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(job.Job()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_job(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_get_job_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.get_job( + job_service.GetJobRequest(), name="name_value", + ) + + +def test_update_job(transport: str = "grpc", request_type=job_service.UpdateJobRequest): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + + response = client.update_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.UpdateJobRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_update_job_from_dict(): + test_update_job(request_type=dict) + + +@pytest.mark.asyncio +async def test_update_job_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.UpdateJobRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_job.Job( + name="name_value", + company="company_value", + requisition_id="requisition_id_value", + title="title_value", + description="description_value", + addresses=["addresses_value"], + job_benefits=[common.JobBenefit.CHILD_CARE], + degree_types=[common.DegreeType.PRIMARY_EDUCATION], + department="department_value", + employment_types=[common.EmploymentType.FULL_TIME], + incentives="incentives_value", + language_code="language_code_value", + job_level=common.JobLevel.ENTRY_LEVEL, + promotion_value=1635, + qualifications="qualifications_value", + responsibilities="responsibilities_value", + posting_region=common.PostingRegion.ADMINISTRATIVE_AREA, + visibility=common.Visibility.ACCOUNT_ONLY, + company_display_name="company_display_name_value", + ) + ) + + response = await client.update_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_job.Job) + + assert response.name == "name_value" + + assert response.company == "company_value" + + assert response.requisition_id == "requisition_id_value" + + assert response.title == "title_value" + + assert response.description == "description_value" + + assert response.addresses == ["addresses_value"] + + assert response.job_benefits == [common.JobBenefit.CHILD_CARE] + + assert response.degree_types == [common.DegreeType.PRIMARY_EDUCATION] + + assert response.department == "department_value" + + assert response.employment_types == [common.EmploymentType.FULL_TIME] + + assert response.incentives == "incentives_value" + + assert response.language_code == "language_code_value" + + assert response.job_level == common.JobLevel.ENTRY_LEVEL + + assert response.promotion_value == 1635 + + assert response.qualifications == "qualifications_value" + + assert response.responsibilities == "responsibilities_value" + + assert response.posting_region == common.PostingRegion.ADMINISTRATIVE_AREA + + assert response.visibility == common.Visibility.ACCOUNT_ONLY + + assert response.company_display_name == "company_display_name_value" + + +def test_update_job_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.UpdateJobRequest() + request.job.name = "job.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_job), "__call__") as call: + call.return_value = gct_job.Job() + + client.update_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "job.name=job.name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_update_job_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.UpdateJobRequest() + request.job.name = "job.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_job), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_job.Job()) + + await client.update_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "job.name=job.name/value",) in kw["metadata"] + + +def test_update_job_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_job( + job=gct_job.Job(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].job == gct_job.Job(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +def test_update_job_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_job( + job_service.UpdateJobRequest(), + job=gct_job.Job(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_job_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_job.Job() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_job.Job()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_job( + job=gct_job.Job(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].job == gct_job.Job(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +@pytest.mark.asyncio +async def test_update_job_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.update_job( + job_service.UpdateJobRequest(), + job=gct_job.Job(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +def test_batch_update_jobs( + transport: str = "grpc", request_type=job_service.BatchUpdateJobsRequest +): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_update_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/spam") + + response = client.batch_update_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.BatchUpdateJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_update_jobs_from_dict(): + test_batch_update_jobs(request_type=dict) + + +@pytest.mark.asyncio +async def test_batch_update_jobs_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.BatchUpdateJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_update_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + + response = await client.batch_update_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_update_jobs_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchUpdateJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_update_jobs), "__call__" + ) as call: + call.return_value = operations_pb2.Operation(name="operations/op") + + client.batch_update_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_batch_update_jobs_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchUpdateJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_update_jobs), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/op") + ) + + await client.batch_update_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_batch_update_jobs_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_update_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.batch_update_jobs( + parent="parent_value", jobs=[job.Job(name="name_value")], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].jobs == [job.Job(name="name_value")] + + +def test_batch_update_jobs_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.batch_update_jobs( + job_service.BatchUpdateJobsRequest(), + parent="parent_value", + jobs=[job.Job(name="name_value")], + ) + + +@pytest.mark.asyncio +async def test_batch_update_jobs_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_update_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.batch_update_jobs( + parent="parent_value", jobs=[job.Job(name="name_value")], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].jobs == [job.Job(name="name_value")] + + +@pytest.mark.asyncio +async def test_batch_update_jobs_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.batch_update_jobs( + job_service.BatchUpdateJobsRequest(), + parent="parent_value", + jobs=[job.Job(name="name_value")], + ) + + +def test_delete_job(transport: str = "grpc", request_type=job_service.DeleteJobRequest): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + response = client.delete_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.DeleteJobRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_job_from_dict(): + test_delete_job(request_type=dict) + + +@pytest.mark.asyncio +async def test_delete_job_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.DeleteJobRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + response = await client.delete_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_job_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.DeleteJobRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_job), "__call__") as call: + call.return_value = None + + client.delete_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_delete_job_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.DeleteJobRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_job), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + await client.delete_job(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_delete_job_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_job), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.delete_job(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_delete_job_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_job( + job_service.DeleteJobRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_delete_job_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_job), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.delete_job(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_delete_job_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.delete_job( + job_service.DeleteJobRequest(), name="name_value", + ) + + +def test_batch_delete_jobs( + transport: str = "grpc", request_type=job_service.BatchDeleteJobsRequest +): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_delete_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/spam") + + response = client.batch_delete_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.BatchDeleteJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_delete_jobs_from_dict(): + test_batch_delete_jobs(request_type=dict) + + +@pytest.mark.asyncio +async def test_batch_delete_jobs_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.BatchDeleteJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_delete_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + + response = await client.batch_delete_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, future.Future) + + +def test_batch_delete_jobs_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchDeleteJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_delete_jobs), "__call__" + ) as call: + call.return_value = operations_pb2.Operation(name="operations/op") + + client.batch_delete_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_batch_delete_jobs_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.BatchDeleteJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_delete_jobs), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/op") + ) + + await client.batch_delete_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_batch_delete_jobs_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.batch_delete_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.batch_delete_jobs( + parent="parent_value", names=["names_value"], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].names == ["names_value"] + + +def test_batch_delete_jobs_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.batch_delete_jobs( + job_service.BatchDeleteJobsRequest(), + parent="parent_value", + names=["names_value"], + ) + + +@pytest.mark.asyncio +async def test_batch_delete_jobs_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.batch_delete_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = operations_pb2.Operation(name="operations/op") + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + operations_pb2.Operation(name="operations/spam") + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.batch_delete_jobs( + parent="parent_value", names=["names_value"], + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].names == ["names_value"] + + +@pytest.mark.asyncio +async def test_batch_delete_jobs_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.batch_delete_jobs( + job_service.BatchDeleteJobsRequest(), + parent="parent_value", + names=["names_value"], + ) + + +def test_list_jobs(transport: str = "grpc", request_type=job_service.ListJobsRequest): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_jobs), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job_service.ListJobsResponse( + next_page_token="next_page_token_value", + ) + + response = client.list_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.ListJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListJobsPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_jobs_from_dict(): + test_list_jobs(request_type=dict) + + +@pytest.mark.asyncio +async def test_list_jobs_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.ListJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.ListJobsResponse(next_page_token="next_page_token_value",) + ) + + response = await client.list_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListJobsAsyncPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_jobs_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.ListJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_jobs), "__call__") as call: + call.return_value = job_service.ListJobsResponse() + + client.list_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_jobs_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.ListJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_jobs), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.ListJobsResponse() + ) + + await client.list_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_list_jobs_flattened(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_jobs), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job_service.ListJobsResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_jobs( + parent="parent_value", filter="filter_value", + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].filter == "filter_value" + + +def test_list_jobs_flattened_error(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_jobs( + job_service.ListJobsRequest(), parent="parent_value", filter="filter_value", + ) + + +@pytest.mark.asyncio +async def test_list_jobs_flattened_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = job_service.ListJobsResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.ListJobsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_jobs(parent="parent_value", filter="filter_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].filter == "filter_value" + + +@pytest.mark.asyncio +async def test_list_jobs_flattened_error_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_jobs( + job_service.ListJobsRequest(), parent="parent_value", filter="filter_value", + ) + + +def test_list_jobs_pager(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_jobs), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + job_service.ListJobsResponse( + jobs=[job.Job(), job.Job(), job.Job(),], next_page_token="abc", + ), + job_service.ListJobsResponse(jobs=[], next_page_token="def",), + job_service.ListJobsResponse(jobs=[job.Job(),], next_page_token="ghi",), + job_service.ListJobsResponse(jobs=[job.Job(), job.Job(),],), + RuntimeError, + ) + + metadata = () + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_jobs(request={}) + + assert pager._metadata == metadata + + results = [i for i in pager] + assert len(results) == 6 + assert all(isinstance(i, job.Job) for i in results) + + +def test_list_jobs_pages(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_jobs), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + job_service.ListJobsResponse( + jobs=[job.Job(), job.Job(), job.Job(),], next_page_token="abc", + ), + job_service.ListJobsResponse(jobs=[], next_page_token="def",), + job_service.ListJobsResponse(jobs=[job.Job(),], next_page_token="ghi",), + job_service.ListJobsResponse(jobs=[job.Job(), job.Job(),],), + RuntimeError, + ) + pages = list(client.list_jobs(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_jobs_async_pager(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_jobs), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + job_service.ListJobsResponse( + jobs=[job.Job(), job.Job(), job.Job(),], next_page_token="abc", + ), + job_service.ListJobsResponse(jobs=[], next_page_token="def",), + job_service.ListJobsResponse(jobs=[job.Job(),], next_page_token="ghi",), + job_service.ListJobsResponse(jobs=[job.Job(), job.Job(),],), + RuntimeError, + ) + async_pager = await client.list_jobs(request={},) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, job.Job) for i in responses) + + +@pytest.mark.asyncio +async def test_list_jobs_async_pages(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_jobs), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + job_service.ListJobsResponse( + jobs=[job.Job(), job.Job(), job.Job(),], next_page_token="abc", + ), + job_service.ListJobsResponse(jobs=[], next_page_token="def",), + job_service.ListJobsResponse(jobs=[job.Job(),], next_page_token="ghi",), + job_service.ListJobsResponse(jobs=[job.Job(), job.Job(),],), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_jobs(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_search_jobs( + transport: str = "grpc", request_type=job_service.SearchJobsRequest +): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.search_jobs), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = job_service.SearchJobsResponse( + next_page_token="next_page_token_value", + total_size=1086, + broadened_query_jobs_count=2766, + ) + + response = client.search_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.SearchJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, job_service.SearchJobsResponse) + + assert response.next_page_token == "next_page_token_value" + + assert response.total_size == 1086 + + assert response.broadened_query_jobs_count == 2766 + + +def test_search_jobs_from_dict(): + test_search_jobs(request_type=dict) + + +@pytest.mark.asyncio +async def test_search_jobs_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.SearchJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.search_jobs), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.SearchJobsResponse( + next_page_token="next_page_token_value", + total_size=1086, + broadened_query_jobs_count=2766, + ) + ) + + response = await client.search_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, job_service.SearchJobsResponse) + + assert response.next_page_token == "next_page_token_value" + + assert response.total_size == 1086 + + assert response.broadened_query_jobs_count == 2766 + + +def test_search_jobs_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.SearchJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.search_jobs), "__call__") as call: + call.return_value = job_service.SearchJobsResponse() + + client.search_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_search_jobs_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.SearchJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.search_jobs), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.SearchJobsResponse() + ) + + await client.search_jobs(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_search_jobs_for_alert( + transport: str = "grpc", request_type=job_service.SearchJobsRequest +): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.search_jobs_for_alert), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = job_service.SearchJobsResponse( + next_page_token="next_page_token_value", + total_size=1086, + broadened_query_jobs_count=2766, + ) + + response = client.search_jobs_for_alert(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == job_service.SearchJobsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, job_service.SearchJobsResponse) + + assert response.next_page_token == "next_page_token_value" + + assert response.total_size == 1086 + + assert response.broadened_query_jobs_count == 2766 + + +def test_search_jobs_for_alert_from_dict(): + test_search_jobs_for_alert(request_type=dict) + + +@pytest.mark.asyncio +async def test_search_jobs_for_alert_async(transport: str = "grpc_asyncio"): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = job_service.SearchJobsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.search_jobs_for_alert), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.SearchJobsResponse( + next_page_token="next_page_token_value", + total_size=1086, + broadened_query_jobs_count=2766, + ) + ) + + response = await client.search_jobs_for_alert(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, job_service.SearchJobsResponse) + + assert response.next_page_token == "next_page_token_value" + + assert response.total_size == 1086 + + assert response.broadened_query_jobs_count == 2766 + + +def test_search_jobs_for_alert_field_headers(): + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.SearchJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._transport.search_jobs_for_alert), "__call__" + ) as call: + call.return_value = job_service.SearchJobsResponse() + + client.search_jobs_for_alert(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_search_jobs_for_alert_field_headers_async(): + client = JobServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = job_service.SearchJobsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.search_jobs_for_alert), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + job_service.SearchJobsResponse() + ) + + await client.search_jobs_for_alert(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.JobServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.JobServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = JobServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.JobServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = JobServiceClient( + client_options={"scopes": ["1", "2"]}, transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.JobServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + client = JobServiceClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.JobServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.JobServiceGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.JobServiceGrpcTransport,) + + +def test_job_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.JobServiceTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_job_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.talent_v4.services.job_service.transports.JobServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.JobServiceTransport( + credentials=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_job", + "batch_create_jobs", + "get_job", + "update_job", + "batch_update_jobs", + "delete_job", + "batch_delete_jobs", + "list_jobs", + "search_jobs", + "search_jobs_for_alert", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + # Additionally, the LRO client (a property) should + # also raise NotImplementedError + with pytest.raises(NotImplementedError): + transport.operations_client + + +def test_job_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.talent_v4.services.job_service.transports.JobServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.JobServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_job_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4.services.job_service.transports.JobServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.JobServiceTransport() + adc.assert_called_once() + + +def test_job_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + JobServiceClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id=None, + ) + + +def test_job_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.JobServiceGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_job_service_host_no_port(): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions(api_endpoint="jobs.googleapis.com"), + ) + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_job_service_host_with_port(): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="jobs.googleapis.com:8000" + ), + ) + assert client._transport._host == "jobs.googleapis.com:8000" + + +def test_job_service_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.JobServiceGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +def test_job_service_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.JobServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +@pytest.mark.parametrize( + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], +) +def test_job_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], +) +def test_job_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_job_service_grpc_lro_client(): + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), transport="grpc", + ) + transport = client._transport + + # Ensure that we have a api-core operations client. + assert isinstance(transport.operations_client, operations_v1.OperationsClient,) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + +def test_job_service_grpc_lro_async_client(): + client = JobServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport="grpc_asyncio", + ) + transport = client._client._transport + + # Ensure that we have a api-core operations client. + assert isinstance(transport.operations_client, operations_v1.OperationsAsyncClient,) + + # Ensure that subsequent calls to the property send the exact same object. + assert transport.operations_client is transport.operations_client + + +def test_job_path(): + project = "squid" + tenant = "clam" + job = "whelk" + + expected = "projects/{project}/tenants/{tenant}/jobs/{job}".format( + project=project, tenant=tenant, job=job, + ) + actual = JobServiceClient.job_path(project, tenant, job) + assert expected == actual + + +def test_parse_job_path(): + expected = { + "project": "octopus", + "tenant": "oyster", + "job": "nudibranch", + } + path = JobServiceClient.job_path(**expected) + + # Check that the path construction is reversible. + actual = JobServiceClient.parse_job_path(path) + assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.JobServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.JobServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = JobServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4/test_tenant_service.py b/tests/unit/gapic/talent_v4/test_tenant_service.py new file mode 100644 index 00000000..7e7b35b1 --- /dev/null +++ b/tests/unit/gapic/talent_v4/test_tenant_service.py @@ -0,0 +1,1882 @@ +# -*- coding: utf-8 -*- + +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +import os +import mock + +import grpc +from grpc.experimental import aio +import math +import pytest +from proto.marshal.rules.dates import DurationRule, TimestampRule + +from google import auth +from google.api_core import client_options +from google.api_core import exceptions +from google.api_core import gapic_v1 +from google.api_core import grpc_helpers +from google.api_core import grpc_helpers_async +from google.auth import credentials +from google.auth.exceptions import MutualTLSChannelError +from google.cloud.talent_v4.services.tenant_service import TenantServiceAsyncClient +from google.cloud.talent_v4.services.tenant_service import TenantServiceClient +from google.cloud.talent_v4.services.tenant_service import pagers +from google.cloud.talent_v4.services.tenant_service import transports +from google.cloud.talent_v4.types import tenant +from google.cloud.talent_v4.types import tenant as gct_tenant +from google.cloud.talent_v4.types import tenant_service +from google.oauth2 import service_account +from google.protobuf import field_mask_pb2 as field_mask # type: ignore + + +def client_cert_source_callback(): + return b"cert bytes", b"key bytes" + + +# If default endpoint is localhost, then default mtls endpoint will be the same. +# This method modifies the default endpoint so the client can produce a different +# mtls endpoint for endpoint testing purposes. +def modify_default_endpoint(client): + return ( + "foo.googleapis.com" + if ("localhost" in client.DEFAULT_ENDPOINT) + else client.DEFAULT_ENDPOINT + ) + + +def test__get_default_mtls_endpoint(): + api_endpoint = "example.googleapis.com" + api_mtls_endpoint = "example.mtls.googleapis.com" + sandbox_endpoint = "example.sandbox.googleapis.com" + sandbox_mtls_endpoint = "example.mtls.sandbox.googleapis.com" + non_googleapi = "api.example.com" + + assert TenantServiceClient._get_default_mtls_endpoint(None) is None + assert ( + TenantServiceClient._get_default_mtls_endpoint(api_endpoint) + == api_mtls_endpoint + ) + assert ( + TenantServiceClient._get_default_mtls_endpoint(api_mtls_endpoint) + == api_mtls_endpoint + ) + assert ( + TenantServiceClient._get_default_mtls_endpoint(sandbox_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + TenantServiceClient._get_default_mtls_endpoint(sandbox_mtls_endpoint) + == sandbox_mtls_endpoint + ) + assert ( + TenantServiceClient._get_default_mtls_endpoint(non_googleapi) == non_googleapi + ) + + +@pytest.mark.parametrize( + "client_class", [TenantServiceClient, TenantServiceAsyncClient] +) +def test_tenant_service_client_from_service_account_file(client_class): + creds = credentials.AnonymousCredentials() + with mock.patch.object( + service_account.Credentials, "from_service_account_file" + ) as factory: + factory.return_value = creds + client = client_class.from_service_account_file("dummy/file/path.json") + assert client._transport._credentials == creds + + client = client_class.from_service_account_json("dummy/file/path.json") + assert client._transport._credentials == creds + + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_tenant_service_client_get_transport_class(): + transport = TenantServiceClient.get_transport_class() + assert transport == transports.TenantServiceGrpcTransport + + transport = TenantServiceClient.get_transport_class("grpc") + assert transport == transports.TenantServiceGrpcTransport + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +@mock.patch.object( + TenantServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceClient), +) +@mock.patch.object( + TenantServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceAsyncClient), +) +def test_tenant_service_client_client_options( + client_class, transport_class, transport_name +): + # Check that if channel is provided we won't create a new one. + with mock.patch.object(TenantServiceClient, "get_transport_class") as gtc: + transport = transport_class(credentials=credentials.AnonymousCredentials()) + client = client_class(transport=transport) + gtc.assert_not_called() + + # Check that if channel is provided via str we will create a new one. + with mock.patch.object(TenantServiceClient, "get_transport_class") as gtc: + client = client_class(transport=transport_name) + gtc.assert_called() + + # Check the case api_endpoint is provided. + options = client_options.ClientOptions(api_endpoint="squid.clam.whelk") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "never". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is + # "always". + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_MTLS_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc", "true"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc", "false"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TenantServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceClient), +) +@mock.patch.object( + TenantServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_tenant_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + options = client_options.ClientOptions( + client_cert_source=client_cert_source_callback + ) + with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() + with mock.patch( + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds + ): + patched.return_value = None + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_tenant_service_client_client_options_scopes( + client_class, transport_class, transport_name +): + # Check the case scopes are provided. + options = client_options.ClientOptions(scopes=["1", "2"],) + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=["1", "2"], + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name", + [ + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + ), + ], +) +def test_tenant_service_client_client_options_credentials_file( + client_class, transport_class, transport_name +): + # Check the case credentials file is provided. + options = client_options.ClientOptions(credentials_file="credentials.json") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file="credentials.json", + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_tenant_service_client_client_options_from_dict(): + with mock.patch( + "google.cloud.talent_v4.services.tenant_service.transports.TenantServiceGrpcTransport.__init__" + ) as grpc_transport: + grpc_transport.return_value = None + client = TenantServiceClient( + client_options={"api_endpoint": "squid.clam.whelk"} + ) + grpc_transport.assert_called_once_with( + credentials=None, + credentials_file=None, + host="squid.clam.whelk", + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +def test_create_tenant( + transport: str = "grpc", request_type=tenant_service.CreateTenantRequest +): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant( + name="name_value", external_id="external_id_value", + ) + + response = client.create_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == tenant_service.CreateTenantRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_create_tenant_from_dict(): + test_create_tenant(request_type=dict) + + +@pytest.mark.asyncio +async def test_create_tenant_async(transport: str = "grpc_asyncio"): + client = TenantServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = tenant_service.CreateTenantRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_tenant.Tenant(name="name_value", external_id="external_id_value",) + ) + + response = await client.create_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_create_tenant_field_headers(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.CreateTenantRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_tenant), "__call__") as call: + call.return_value = gct_tenant.Tenant() + + client.create_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_create_tenant_field_headers_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.CreateTenantRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_tenant), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_tenant.Tenant()) + + await client.create_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_create_tenant_flattened(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.create_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.create_tenant( + parent="parent_value", tenant=gct_tenant.Tenant(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].tenant == gct_tenant.Tenant(name="name_value") + + +def test_create_tenant_flattened_error(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.create_tenant( + tenant_service.CreateTenantRequest(), + parent="parent_value", + tenant=gct_tenant.Tenant(name="name_value"), + ) + + +@pytest.mark.asyncio +async def test_create_tenant_flattened_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.create_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_tenant.Tenant()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.create_tenant( + parent="parent_value", tenant=gct_tenant.Tenant(name="name_value"), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + assert args[0].tenant == gct_tenant.Tenant(name="name_value") + + +@pytest.mark.asyncio +async def test_create_tenant_flattened_error_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.create_tenant( + tenant_service.CreateTenantRequest(), + parent="parent_value", + tenant=gct_tenant.Tenant(name="name_value"), + ) + + +def test_get_tenant( + transport: str = "grpc", request_type=tenant_service.GetTenantRequest +): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = tenant.Tenant( + name="name_value", external_id="external_id_value", + ) + + response = client.get_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == tenant_service.GetTenantRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_get_tenant_from_dict(): + test_get_tenant(request_type=dict) + + +@pytest.mark.asyncio +async def test_get_tenant_async(transport: str = "grpc_asyncio"): + client = TenantServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = tenant_service.GetTenantRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + tenant.Tenant(name="name_value", external_id="external_id_value",) + ) + + response = await client.get_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_get_tenant_field_headers(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.GetTenantRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_tenant), "__call__") as call: + call.return_value = tenant.Tenant() + + client.get_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_get_tenant_field_headers_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.GetTenantRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_tenant), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(tenant.Tenant()) + + await client.get_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_get_tenant_flattened(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.get_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = tenant.Tenant() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.get_tenant(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_get_tenant_flattened_error(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.get_tenant( + tenant_service.GetTenantRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_get_tenant_flattened_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.get_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = tenant.Tenant() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(tenant.Tenant()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.get_tenant(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_get_tenant_flattened_error_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.get_tenant( + tenant_service.GetTenantRequest(), name="name_value", + ) + + +def test_update_tenant( + transport: str = "grpc", request_type=tenant_service.UpdateTenantRequest +): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant( + name="name_value", external_id="external_id_value", + ) + + response = client.update_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == tenant_service.UpdateTenantRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_update_tenant_from_dict(): + test_update_tenant(request_type=dict) + + +@pytest.mark.asyncio +async def test_update_tenant_async(transport: str = "grpc_asyncio"): + client = TenantServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = tenant_service.UpdateTenantRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + gct_tenant.Tenant(name="name_value", external_id="external_id_value",) + ) + + response = await client.update_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, gct_tenant.Tenant) + + assert response.name == "name_value" + + assert response.external_id == "external_id_value" + + +def test_update_tenant_field_headers(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.UpdateTenantRequest() + request.tenant.name = "tenant.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_tenant), "__call__") as call: + call.return_value = gct_tenant.Tenant() + + client.update_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "tenant.name=tenant.name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_update_tenant_field_headers_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.UpdateTenantRequest() + request.tenant.name = "tenant.name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_tenant), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_tenant.Tenant()) + + await client.update_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "tenant.name=tenant.name/value",) in kw["metadata"] + + +def test_update_tenant_flattened(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.update_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.update_tenant( + tenant=gct_tenant.Tenant(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].tenant == gct_tenant.Tenant(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +def test_update_tenant_flattened_error(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.update_tenant( + tenant_service.UpdateTenantRequest(), + tenant=gct_tenant.Tenant(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +@pytest.mark.asyncio +async def test_update_tenant_flattened_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.update_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = gct_tenant.Tenant() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(gct_tenant.Tenant()) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.update_tenant( + tenant=gct_tenant.Tenant(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].tenant == gct_tenant.Tenant(name="name_value") + + assert args[0].update_mask == field_mask.FieldMask(paths=["paths_value"]) + + +@pytest.mark.asyncio +async def test_update_tenant_flattened_error_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.update_tenant( + tenant_service.UpdateTenantRequest(), + tenant=gct_tenant.Tenant(name="name_value"), + update_mask=field_mask.FieldMask(paths=["paths_value"]), + ) + + +def test_delete_tenant( + transport: str = "grpc", request_type=tenant_service.DeleteTenantRequest +): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + response = client.delete_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == tenant_service.DeleteTenantRequest() + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_tenant_from_dict(): + test_delete_tenant(request_type=dict) + + +@pytest.mark.asyncio +async def test_delete_tenant_async(transport: str = "grpc_asyncio"): + client = TenantServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = tenant_service.DeleteTenantRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + response = await client.delete_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert response is None + + +def test_delete_tenant_field_headers(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.DeleteTenantRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_tenant), "__call__") as call: + call.return_value = None + + client.delete_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_delete_tenant_field_headers_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.DeleteTenantRequest() + request.name = "name/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_tenant), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + + await client.delete_tenant(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "name=name/value",) in kw["metadata"] + + +def test_delete_tenant_flattened(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.delete_tenant), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = None + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.delete_tenant(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +def test_delete_tenant_flattened_error(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.delete_tenant( + tenant_service.DeleteTenantRequest(), name="name_value", + ) + + +@pytest.mark.asyncio +async def test_delete_tenant_flattened_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.delete_tenant), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = None + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall(None) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.delete_tenant(name="name_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].name == "name_value" + + +@pytest.mark.asyncio +async def test_delete_tenant_flattened_error_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.delete_tenant( + tenant_service.DeleteTenantRequest(), name="name_value", + ) + + +def test_list_tenants( + transport: str = "grpc", request_type=tenant_service.ListTenantsRequest +): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_tenants), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = tenant_service.ListTenantsResponse( + next_page_token="next_page_token_value", + ) + + response = client.list_tenants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0] == tenant_service.ListTenantsRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTenantsPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_tenants_from_dict(): + test_list_tenants(request_type=dict) + + +@pytest.mark.asyncio +async def test_list_tenants_async(transport: str = "grpc_asyncio"): + client = TenantServiceAsyncClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = tenant_service.ListTenantsRequest() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_tenants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + tenant_service.ListTenantsResponse(next_page_token="next_page_token_value",) + ) + + response = await client.list_tenants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0] == request + + # Establish that the response is the type that we expect. + assert isinstance(response, pagers.ListTenantsAsyncPager) + + assert response.next_page_token == "next_page_token_value" + + +def test_list_tenants_field_headers(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.ListTenantsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_tenants), "__call__") as call: + call.return_value = tenant_service.ListTenantsResponse() + + client.list_tenants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_list_tenants_field_headers_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = tenant_service.ListTenantsRequest() + request.parent = "parent/value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_tenants), "__call__" + ) as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + tenant_service.ListTenantsResponse() + ) + + await client.list_tenants(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ("x-goog-request-params", "parent=parent/value",) in kw["metadata"] + + +def test_list_tenants_flattened(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_tenants), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = tenant_service.ListTenantsResponse() + + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + client.list_tenants(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +def test_list_tenants_flattened_error(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + client.list_tenants( + tenant_service.ListTenantsRequest(), parent="parent_value", + ) + + +@pytest.mark.asyncio +async def test_list_tenants_flattened_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_tenants), "__call__" + ) as call: + # Designate an appropriate return value for the call. + call.return_value = tenant_service.ListTenantsResponse() + + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + tenant_service.ListTenantsResponse() + ) + # Call the method with a truthy value for each flattened field, + # using the keyword arguments to the method. + response = await client.list_tenants(parent="parent_value",) + + # Establish that the underlying call was made with the expected + # request object values. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + + assert args[0].parent == "parent_value" + + +@pytest.mark.asyncio +async def test_list_tenants_flattened_error_async(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials(),) + + # Attempting to call a method with both a request object and flattened + # fields is an error. + with pytest.raises(ValueError): + await client.list_tenants( + tenant_service.ListTenantsRequest(), parent="parent_value", + ) + + +def test_list_tenants_pager(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_tenants), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(), tenant.Tenant(),], + next_page_token="abc", + ), + tenant_service.ListTenantsResponse(tenants=[], next_page_token="def",), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(),], next_page_token="ghi", + ), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(),], + ), + RuntimeError, + ) + + metadata = () + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", ""),)), + ) + pager = client.list_tenants(request={}) + + assert pager._metadata == metadata + + results = [i for i in pager] + assert len(results) == 6 + assert all(isinstance(i, tenant.Tenant) for i in results) + + +def test_list_tenants_pages(): + client = TenantServiceClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client._transport.list_tenants), "__call__") as call: + # Set the response to a series of pages. + call.side_effect = ( + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(), tenant.Tenant(),], + next_page_token="abc", + ), + tenant_service.ListTenantsResponse(tenants=[], next_page_token="def",), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(),], next_page_token="ghi", + ), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(),], + ), + RuntimeError, + ) + pages = list(client.list_tenants(request={}).pages) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +@pytest.mark.asyncio +async def test_list_tenants_async_pager(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_tenants), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(), tenant.Tenant(),], + next_page_token="abc", + ), + tenant_service.ListTenantsResponse(tenants=[], next_page_token="def",), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(),], next_page_token="ghi", + ), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(),], + ), + RuntimeError, + ) + async_pager = await client.list_tenants(request={},) + assert async_pager.next_page_token == "abc" + responses = [] + async for response in async_pager: + responses.append(response) + + assert len(responses) == 6 + assert all(isinstance(i, tenant.Tenant) for i in responses) + + +@pytest.mark.asyncio +async def test_list_tenants_async_pages(): + client = TenantServiceAsyncClient(credentials=credentials.AnonymousCredentials,) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object( + type(client._client._transport.list_tenants), + "__call__", + new_callable=mock.AsyncMock, + ) as call: + # Set the response to a series of pages. + call.side_effect = ( + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(), tenant.Tenant(),], + next_page_token="abc", + ), + tenant_service.ListTenantsResponse(tenants=[], next_page_token="def",), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(),], next_page_token="ghi", + ), + tenant_service.ListTenantsResponse( + tenants=[tenant.Tenant(), tenant.Tenant(),], + ), + RuntimeError, + ) + pages = [] + async for page_ in (await client.list_tenants(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token + + +def test_credentials_transport_error(): + # It is an error to provide credentials and a transport instance. + transport = transports.TenantServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), transport=transport, + ) + + # It is an error to provide a credentials file and a transport instance. + transport = transports.TenantServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TenantServiceClient( + client_options={"credentials_file": "credentials.json"}, + transport=transport, + ) + + # It is an error to provide scopes and a transport instance. + transport = transports.TenantServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + with pytest.raises(ValueError): + client = TenantServiceClient( + client_options={"scopes": ["1", "2"]}, transport=transport, + ) + + +def test_transport_instance(): + # A client may be instantiated with a custom transport instance. + transport = transports.TenantServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + client = TenantServiceClient(transport=transport) + assert client._transport is transport + + +def test_transport_get_channel(): + # A client may be instantiated with a custom transport instance. + transport = transports.TenantServiceGrpcTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + transport = transports.TenantServiceGrpcAsyncIOTransport( + credentials=credentials.AnonymousCredentials(), + ) + channel = transport.grpc_channel + assert channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + +def test_transport_grpc_default(): + # A client should use the gRPC transport by default. + client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) + assert isinstance(client._transport, transports.TenantServiceGrpcTransport,) + + +def test_tenant_service_base_transport_error(): + # Passing both a credentials object and credentials_file should raise an error + with pytest.raises(exceptions.DuplicateCredentialArgs): + transport = transports.TenantServiceTransport( + credentials=credentials.AnonymousCredentials(), + credentials_file="credentials.json", + ) + + +def test_tenant_service_base_transport(): + # Instantiate the base transport. + with mock.patch( + "google.cloud.talent_v4.services.tenant_service.transports.TenantServiceTransport.__init__" + ) as Transport: + Transport.return_value = None + transport = transports.TenantServiceTransport( + credentials=credentials.AnonymousCredentials(), + ) + + # Every method on the transport should just blindly + # raise NotImplementedError. + methods = ( + "create_tenant", + "get_tenant", + "update_tenant", + "delete_tenant", + "list_tenants", + ) + for method in methods: + with pytest.raises(NotImplementedError): + getattr(transport, method)(request=object()) + + +def test_tenant_service_base_transport_with_credentials_file(): + # Instantiate the base transport with a credentials file + with mock.patch.object( + auth, "load_credentials_from_file" + ) as load_creds, mock.patch( + "google.cloud.talent_v4.services.tenant_service.transports.TenantServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + load_creds.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TenantServiceTransport( + credentials_file="credentials.json", quota_project_id="octopus", + ) + load_creds.assert_called_once_with( + "credentials.json", + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_tenant_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4.services.tenant_service.transports.TenantServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TenantServiceTransport() + adc.assert_called_once() + + +def test_tenant_service_auth_adc(): + # If no credentials are provided, we should use ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + TenantServiceClient() + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id=None, + ) + + +def test_tenant_service_transport_auth_adc(): + # If credentials and host are not provided, the transport class should use + # ADC credentials. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transports.TenantServiceGrpcTransport( + host="squid.clam.whelk", quota_project_id="octopus" + ) + adc.assert_called_once_with( + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + quota_project_id="octopus", + ) + + +def test_tenant_service_host_no_port(): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions(api_endpoint="jobs.googleapis.com"), + ) + assert client._transport._host == "jobs.googleapis.com:443" + + +def test_tenant_service_host_with_port(): + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), + client_options=client_options.ClientOptions( + api_endpoint="jobs.googleapis.com:8000" + ), + ) + assert client._transport._host == "jobs.googleapis.com:8000" + + +def test_tenant_service_grpc_transport_channel(): + channel = grpc.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.TenantServiceGrpcTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +def test_tenant_service_grpc_asyncio_transport_channel(): + channel = aio.insecure_channel("http://localhost/") + + # Check that channel is used if provided. + transport = transports.TenantServiceGrpcAsyncIOTransport( + host="squid.clam.whelk", channel=channel, + ) + assert transport.grpc_channel == channel + assert transport._host == "squid.clam.whelk:443" + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], +) +def test_tenant_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +@pytest.mark.parametrize( + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], +) +def test_tenant_service_transport_channel_mtls_with_adc(transport_class): + mock_ssl_cred = mock.Mock() + with mock.patch.multiple( + "google.auth.transport.grpc.SslCredentials", + __init__=mock.Mock(return_value=None), + ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), + ): + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel + + +def test_tenant_path(): + project = "squid" + tenant = "clam" + + expected = "projects/{project}/tenants/{tenant}".format( + project=project, tenant=tenant, + ) + actual = TenantServiceClient.tenant_path(project, tenant) + assert expected == actual + + +def test_parse_tenant_path(): + expected = { + "project": "whelk", + "tenant": "octopus", + } + path = TenantServiceClient.tenant_path(**expected) + + # Check that the path construction is reversible. + actual = TenantServiceClient.parse_tenant_path(path) + assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.TenantServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.TenantServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = TenantServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_application_service.py b/tests/unit/gapic/talent_v4beta1/test_application_service.py index defe6406..904d4543 100644 --- a/tests/unit/gapic/talent_v4beta1/test_application_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_application_service.py @@ -167,14 +167,14 @@ def test_application_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -183,14 +183,14 @@ def test_application_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -199,90 +199,185 @@ def test_application_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + ( + ApplicationServiceClient, + transports.ApplicationServiceGrpcTransport, + "grpc", + "true", + ), + ( + ApplicationServiceAsyncClient, + transports.ApplicationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + ( + ApplicationServiceClient, + transports.ApplicationServiceGrpcTransport, + "grpc", + "false", + ), + ( + ApplicationServiceAsyncClient, + transports.ApplicationServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + ApplicationServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ApplicationServiceClient), +) +@mock.patch.object( + ApplicationServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ApplicationServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_application_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -309,9 +404,9 @@ def test_application_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -339,9 +434,9 @@ def test_application_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -358,9 +453,9 @@ def test_application_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1626,8 +1721,8 @@ def test_list_applications_pages(): RuntimeError, ) pages = list(client.list_applications(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1707,10 +1802,10 @@ async def test_list_applications_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_applications(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_applications(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_credentials_transport_error(): @@ -1767,6 +1862,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.ApplicationServiceGrpcTransport, + transports.ApplicationServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = ApplicationServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -1828,6 +1938,17 @@ def test_application_service_base_transport_with_credentials_file(): ) +def test_application_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.application_service.transports.ApplicationServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.ApplicationServiceTransport() + adc.assert_called_once() + + def test_application_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -1880,191 +2001,116 @@ def test_application_service_host_with_port(): def test_application_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.ApplicationServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_application_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.ApplicationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_application_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.ApplicationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_application_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.ApplicationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.ApplicationServiceGrpcTransport, + transports.ApplicationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_application_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint +def test_application_service_transport_channel_mtls_with_client_cert_source( + transport_class, ): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.ApplicationServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.ApplicationServiceGrpcTransport, + transports.ApplicationServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_application_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_application_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.ApplicationServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_application_path(): @@ -2094,3 +2140,24 @@ def test_parse_application_path(): # Check that the path construction is reversible. actual = ApplicationServiceClient.parse_application_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.ApplicationServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = ApplicationServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.ApplicationServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = ApplicationServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_company_service.py b/tests/unit/gapic/talent_v4beta1/test_company_service.py index 29ac1a52..317b328f 100644 --- a/tests/unit/gapic/talent_v4beta1/test_company_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_company_service.py @@ -163,14 +163,14 @@ def test_company_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -179,14 +179,14 @@ def test_company_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -195,90 +195,175 @@ def test_company_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc", "true"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (CompanyServiceClient, transports.CompanyServiceGrpcTransport, "grpc", "false"), + ( + CompanyServiceAsyncClient, + transports.CompanyServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + CompanyServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceClient), +) +@mock.patch.object( + CompanyServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompanyServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_company_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -305,9 +390,9 @@ def test_company_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -335,9 +420,9 @@ def test_company_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -354,9 +439,9 @@ def test_company_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1596,8 +1681,8 @@ def test_list_companies_pages(): RuntimeError, ) pages = list(client.list_companies(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1661,10 +1746,10 @@ async def test_list_companies_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_companies(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_companies(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_credentials_transport_error(): @@ -1721,6 +1806,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = CompanyServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -1782,6 +1882,17 @@ def test_company_service_base_transport_with_credentials_file(): ) +def test_company_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.company_service.transports.CompanyServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompanyServiceTransport() + adc.assert_called_once() + + def test_company_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -1834,191 +1945,116 @@ def test_company_service_host_with_port(): def test_company_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.CompanyServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_company_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.CompanyServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_company_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.CompanyServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_company_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.CompanyServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_company_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint +def test_company_service_transport_channel_mtls_with_client_cert_source( + transport_class, ): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.CompanyServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.CompanyServiceGrpcTransport, + transports.CompanyServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_company_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_company_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.CompanyServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_company_path(): @@ -2044,3 +2080,24 @@ def test_parse_company_path(): # Check that the path construction is reversible. actual = CompanyServiceClient.parse_company_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.CompanyServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = CompanyServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.CompanyServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = CompanyServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_completion.py b/tests/unit/gapic/talent_v4beta1/test_completion.py index 5e65aa06..eda690c7 100644 --- a/tests/unit/gapic/talent_v4beta1/test_completion.py +++ b/tests/unit/gapic/talent_v4beta1/test_completion.py @@ -27,6 +27,7 @@ from google import auth from google.api_core import client_options from google.api_core import exceptions +from google.api_core import gapic_v1 from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async from google.auth import credentials @@ -147,14 +148,14 @@ def test_completion_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -163,14 +164,14 @@ def test_completion_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -179,90 +180,173 @@ def test_completion_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (CompletionClient, transports.CompletionGrpcTransport, "grpc", "true"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (CompletionClient, transports.CompletionGrpcTransport, "grpc", "false"), + ( + CompletionAsyncClient, + transports.CompletionGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + CompletionClient, "DEFAULT_ENDPOINT", modify_default_endpoint(CompletionClient) +) +@mock.patch.object( + CompletionAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(CompletionAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_completion_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -289,9 +373,9 @@ def test_completion_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -319,9 +403,9 @@ def test_completion_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -336,9 +420,9 @@ def test_completion_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -512,6 +596,18 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = CompletionClient(credentials=credentials.AnonymousCredentials(),) @@ -567,6 +663,17 @@ def test_completion_base_transport_with_credentials_file(): ) +def test_completion_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.completion.transports.CompletionTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.CompletionTransport() + adc.assert_called_once() + + def test_completion_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -619,188 +726,126 @@ def test_completion_host_with_port(): def test_completion_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.CompletionGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_completion_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.CompletionGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_completion_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - transport = transports.CompletionGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_completion_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.CompletionGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +@pytest.mark.parametrize( + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], +) +def test_completion_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.CompletionGrpcTransport, transports.CompletionGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_completion_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_completion_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.CompletionGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_completion_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.CompletionGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.CompletionTransport, "_prep_wrapped_messages" + ) as prep: + client = CompletionClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.CompletionTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = CompletionClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, ) - assert transport.grpc_channel == mock_grpc_channel + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_event_service.py b/tests/unit/gapic/talent_v4beta1/test_event_service.py index fea20890..fb617f2d 100644 --- a/tests/unit/gapic/talent_v4beta1/test_event_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_event_service.py @@ -27,6 +27,7 @@ from google import auth from google.api_core import client_options from google.api_core import exceptions +from google.api_core import gapic_v1 from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async from google.auth import credentials @@ -148,14 +149,14 @@ def test_event_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -164,14 +165,14 @@ def test_event_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -180,90 +181,173 @@ def test_event_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc", "true"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (EventServiceClient, transports.EventServiceGrpcTransport, "grpc", "false"), + ( + EventServiceAsyncClient, + transports.EventServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + EventServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(EventServiceClient) +) +@mock.patch.object( + EventServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(EventServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_event_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -290,9 +374,9 @@ def test_event_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -320,9 +404,9 @@ def test_event_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -337,9 +421,9 @@ def test_event_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -363,7 +447,7 @@ def test_create_client_event( request_id="request_id_value", event_id="event_id_value", event_notes="event_notes_value", - job_event=event.JobEvent(type=event.JobEvent.JobEventType.IMPRESSION), + job_event=event.JobEvent(type_=event.JobEvent.JobEventType.IMPRESSION), ) response = client.create_client_event(request) @@ -619,6 +703,18 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = EventServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -674,6 +770,17 @@ def test_event_service_base_transport_with_credentials_file(): ) +def test_event_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.event_service.transports.EventServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.EventServiceTransport() + adc.assert_called_once() + + def test_event_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -726,188 +833,126 @@ def test_event_service_host_with_port(): def test_event_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.EventServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_event_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.EventServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_event_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - transport = transports.EventServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_event_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.EventServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +@pytest.mark.parametrize( + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], +) +def test_event_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.EventServiceGrpcTransport, transports.EventServiceGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_event_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_event_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.EventServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel -@pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] -) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_event_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.EventServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.EventServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = EventServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.EventServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = EventServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, ) - assert transport.grpc_channel == mock_grpc_channel + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_job_service.py b/tests/unit/gapic/talent_v4beta1/test_job_service.py index b4446d66..9dae40d1 100644 --- a/tests/unit/gapic/talent_v4beta1/test_job_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_job_service.py @@ -31,7 +31,7 @@ from google.api_core import gapic_v1 from google.api_core import grpc_helpers from google.api_core import grpc_helpers_async -from google.api_core import operation_async +from google.api_core import operation_async # type: ignore from google.api_core import operations_v1 from google.auth import credentials from google.auth.exceptions import MutualTLSChannelError @@ -165,14 +165,14 @@ def test_job_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -181,14 +181,14 @@ def test_job_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -197,90 +197,173 @@ def test_job_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc", "true"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (JobServiceClient, transports.JobServiceGrpcTransport, "grpc", "false"), + ( + JobServiceAsyncClient, + transports.JobServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + JobServiceClient, "DEFAULT_ENDPOINT", modify_default_endpoint(JobServiceClient) +) +@mock.patch.object( + JobServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(JobServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_job_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -307,9 +390,9 @@ def test_job_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -337,9 +420,9 @@ def test_job_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -354,9 +437,9 @@ def test_job_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -2271,8 +2354,8 @@ def test_list_jobs_pages(): RuntimeError, ) pages = list(client.list_jobs(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -2326,10 +2409,10 @@ async def test_list_jobs_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_jobs(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_jobs(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_search_jobs( @@ -2547,8 +2630,8 @@ def test_search_jobs_pages(): RuntimeError, ) pages = list(client.search_jobs(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -2630,10 +2713,10 @@ async def test_search_jobs_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.search_jobs(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.search_jobs(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_search_jobs_for_alert( @@ -2859,8 +2942,8 @@ def test_search_jobs_for_alert_pages(): RuntimeError, ) pages = list(client.search_jobs_for_alert(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -2942,10 +3025,10 @@ async def test_search_jobs_for_alert_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.search_jobs_for_alert(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.search_jobs_for_alert(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_credentials_transport_error(): @@ -3002,6 +3085,18 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = JobServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -3073,6 +3168,17 @@ def test_job_service_base_transport_with_credentials_file(): ) +def test_job_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.job_service.transports.JobServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.JobServiceTransport() + adc.assert_called_once() + + def test_job_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -3125,191 +3231,108 @@ def test_job_service_host_with_port(): def test_job_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.JobServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_job_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.JobServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_job_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.JobServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_job_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.JobServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_job_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.JobServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_job_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [transports.JobServiceGrpcTransport, transports.JobServiceGrpcAsyncIOTransport], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_job_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_job_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.JobServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_job_service_grpc_lro_client(): @@ -3361,3 +3384,24 @@ def test_parse_job_path(): # Check that the path construction is reversible. actual = JobServiceClient.parse_job_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.JobServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = JobServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.JobServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = JobServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_profile_service.py b/tests/unit/gapic/talent_v4beta1/test_profile_service.py index f95669da..53e3dbba 100644 --- a/tests/unit/gapic/talent_v4beta1/test_profile_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_profile_service.py @@ -169,14 +169,14 @@ def test_profile_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -185,14 +185,14 @@ def test_profile_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -201,90 +201,175 @@ def test_profile_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (ProfileServiceClient, transports.ProfileServiceGrpcTransport, "grpc", "true"), + ( + ProfileServiceAsyncClient, + transports.ProfileServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (ProfileServiceClient, transports.ProfileServiceGrpcTransport, "grpc", "false"), + ( + ProfileServiceAsyncClient, + transports.ProfileServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + ProfileServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProfileServiceClient), +) +@mock.patch.object( + ProfileServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(ProfileServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_profile_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -311,9 +396,9 @@ def test_profile_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -341,9 +426,9 @@ def test_profile_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -360,9 +445,9 @@ def test_profile_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -614,8 +699,8 @@ def test_list_profiles_pages(): RuntimeError, ) pages = list(client.list_profiles(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -679,10 +764,10 @@ async def test_list_profiles_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_profiles(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_profiles(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_create_profile( @@ -1806,8 +1891,8 @@ def test_search_profiles_pages(): RuntimeError, ) pages = list(client.search_profiles(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1891,10 +1976,10 @@ async def test_search_profiles_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.search_profiles(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.search_profiles(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_credentials_transport_error(): @@ -1951,6 +2036,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.ProfileServiceGrpcTransport, + transports.ProfileServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = ProfileServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -2013,6 +2113,17 @@ def test_profile_service_base_transport_with_credentials_file(): ) +def test_profile_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.profile_service.transports.ProfileServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.ProfileServiceTransport() + adc.assert_called_once() + + def test_profile_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -2065,191 +2176,116 @@ def test_profile_service_host_with_port(): def test_profile_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.ProfileServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_profile_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.ProfileServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_profile_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.ProfileServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_profile_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.ProfileServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.ProfileServiceGrpcTransport, + transports.ProfileServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_profile_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint +def test_profile_service_transport_channel_mtls_with_client_cert_source( + transport_class, ): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.ProfileServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.ProfileServiceGrpcTransport, + transports.ProfileServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_profile_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_profile_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.ProfileServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_profile_path(): @@ -2275,3 +2311,24 @@ def test_parse_profile_path(): # Check that the path construction is reversible. actual = ProfileServiceClient.parse_profile_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.ProfileServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = ProfileServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.ProfileServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = ProfileServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) diff --git a/tests/unit/gapic/talent_v4beta1/test_tenant_service.py b/tests/unit/gapic/talent_v4beta1/test_tenant_service.py index cfbd0058..ecf6be6c 100644 --- a/tests/unit/gapic/talent_v4beta1/test_tenant_service.py +++ b/tests/unit/gapic/talent_v4beta1/test_tenant_service.py @@ -158,14 +158,14 @@ def test_tenant_service_client_client_options( credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "never". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "never"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -174,14 +174,14 @@ def test_tenant_service_client_client_options( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS is + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT is # "always". - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "always"}): + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}): with mock.patch.object(transport_class, "__init__") as patched: patched.return_value = None client = client_class() @@ -190,90 +190,175 @@ def test_tenant_service_client_client_options( credentials_file=None, host=client.DEFAULT_MTLS_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS_ENDPOINT has + # unsupported value. + with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "Unsupported"}): + with pytest.raises(MutualTLSChannelError): + client = client_class() + + # Check the case GOOGLE_API_USE_CLIENT_CERTIFICATE has unsupported value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "Unsupported"} + ): + with pytest.raises(ValueError): + client = client_class() + + # Check the case quota_project_id is provided + options = client_options.ClientOptions(quota_project_id="octopus") + with mock.patch.object(transport_class, "__init__") as patched: + patched.return_value = None + client = client_class(client_options=options) + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id="octopus", + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + +@pytest.mark.parametrize( + "client_class,transport_class,transport_name,use_client_cert_env", + [ + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc", "true"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "true", + ), + (TenantServiceClient, transports.TenantServiceGrpcTransport, "grpc", "false"), + ( + TenantServiceAsyncClient, + transports.TenantServiceGrpcAsyncIOTransport, + "grpc_asyncio", + "false", + ), + ], +) +@mock.patch.object( + TenantServiceClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceClient), +) +@mock.patch.object( + TenantServiceAsyncClient, + "DEFAULT_ENDPOINT", + modify_default_endpoint(TenantServiceAsyncClient), +) +@mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) +def test_tenant_service_client_mtls_env_auto( + client_class, transport_class, transport_name, use_client_cert_env +): + # This tests the endpoint autoswitch behavior. Endpoint is autoswitched to the default + # mtls endpoint, if GOOGLE_API_USE_CLIENT_CERTIFICATE is "true" and client cert exists. + + # Check the case client_cert_source is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): options = client_options.ClientOptions( client_cert_source=client_cert_source_callback ) with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=client_cert_source_callback, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", and default_client_cert_source is provided. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): - with mock.patch.object(transport_class, "__init__") as patched: + ssl_channel_creds = mock.Mock() with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=True, + "grpc.ssl_channel_credentials", return_value=ssl_channel_creds ): patched.return_value = None - client = client_class() + client = client_class(client_options=options) + + if use_client_cert_env == "false": + expected_ssl_channel_creds = None + expected_host = client.DEFAULT_ENDPOINT + else: + expected_ssl_channel_creds = ssl_channel_creds + expected_host = client.DEFAULT_MTLS_ENDPOINT + patched.assert_called_once_with( credentials=None, credentials_file=None, - host=client.DEFAULT_MTLS_ENDPOINT, + host=expected_host, scopes=None, - api_mtls_endpoint=client.DEFAULT_MTLS_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=expected_ssl_channel_creds, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) - # Check the case api_endpoint is not provided, GOOGLE_API_USE_MTLS is - # "auto", but client_cert_source and default_client_cert_source are None. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "auto"}): + # Check the case ADC client cert is provided. Whether client cert is used depends on + # GOOGLE_API_USE_CLIENT_CERTIFICATE value. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): with mock.patch.object(transport_class, "__init__") as patched: with mock.patch( - "google.auth.transport.mtls.has_default_client_cert_source", - return_value=False, + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None ): - patched.return_value = None - client = client_class() - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id=None, - ) - - # Check the case api_endpoint is not provided and GOOGLE_API_USE_MTLS has - # unsupported value. - with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS": "Unsupported"}): - with pytest.raises(MutualTLSChannelError): - client = client_class() - - # Check the case quota_project_id is provided - options = client_options.ClientOptions(quota_project_id="octopus") - with mock.patch.object(transport_class, "__init__") as patched: - patched.return_value = None - client = client_class(client_options=options) - patched.assert_called_once_with( - credentials=None, - credentials_file=None, - host=client.DEFAULT_ENDPOINT, - scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, - quota_project_id="octopus", - ) + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.ssl_credentials", + new_callable=mock.PropertyMock, + ) as ssl_credentials_mock: + if use_client_cert_env == "false": + is_mtls_mock.return_value = False + ssl_credentials_mock.return_value = None + expected_host = client.DEFAULT_ENDPOINT + expected_ssl_channel_creds = None + else: + is_mtls_mock.return_value = True + ssl_credentials_mock.return_value = mock.Mock() + expected_host = client.DEFAULT_MTLS_ENDPOINT + expected_ssl_channel_creds = ( + ssl_credentials_mock.return_value + ) + + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=expected_host, + scopes=None, + ssl_channel_credentials=expected_ssl_channel_creds, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) + + # Check the case client_cert_source and ADC client cert are not provided. + with mock.patch.dict( + os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": use_client_cert_env} + ): + with mock.patch.object(transport_class, "__init__") as patched: + with mock.patch( + "google.auth.transport.grpc.SslCredentials.__init__", return_value=None + ): + with mock.patch( + "google.auth.transport.grpc.SslCredentials.is_mtls", + new_callable=mock.PropertyMock, + ) as is_mtls_mock: + is_mtls_mock.return_value = False + patched.return_value = None + client = client_class() + patched.assert_called_once_with( + credentials=None, + credentials_file=None, + host=client.DEFAULT_ENDPOINT, + scopes=None, + ssl_channel_credentials=None, + quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, + ) @pytest.mark.parametrize( @@ -300,9 +385,9 @@ def test_tenant_service_client_client_options_scopes( credentials_file=None, host=client.DEFAULT_ENDPOINT, scopes=["1", "2"], - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -330,9 +415,9 @@ def test_tenant_service_client_client_options_credentials_file( credentials_file="credentials.json", host=client.DEFAULT_ENDPOINT, scopes=None, - api_mtls_endpoint=client.DEFAULT_ENDPOINT, - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -349,9 +434,9 @@ def test_tenant_service_client_client_options_from_dict(): credentials_file=None, host="squid.clam.whelk", scopes=None, - api_mtls_endpoint="squid.clam.whelk", - client_cert_source=None, + ssl_channel_credentials=None, quota_project_id=None, + client_info=transports.base.DEFAULT_CLIENT_INFO, ) @@ -1441,8 +1526,8 @@ def test_list_tenants_pages(): RuntimeError, ) pages = list(client.list_tenants(request={}).pages) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token @pytest.mark.asyncio @@ -1506,10 +1591,10 @@ async def test_list_tenants_async_pages(): RuntimeError, ) pages = [] - async for page in (await client.list_tenants(request={})).pages: - pages.append(page) - for page, token in zip(pages, ["abc", "def", "ghi", ""]): - assert page.raw_page.next_page_token == token + async for page_ in (await client.list_tenants(request={})).pages: + pages.append(page_) + for page_, token in zip(pages, ["abc", "def", "ghi", ""]): + assert page_.raw_page.next_page_token == token def test_credentials_transport_error(): @@ -1566,6 +1651,21 @@ def test_transport_get_channel(): assert channel +@pytest.mark.parametrize( + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], +) +def test_transport_adc(transport_class): + # Test default credentials are used if not provided. + with mock.patch.object(auth, "default") as adc: + adc.return_value = (credentials.AnonymousCredentials(), None) + transport_class() + adc.assert_called_once() + + def test_transport_grpc_default(): # A client should use the gRPC transport by default. client = TenantServiceClient(credentials=credentials.AnonymousCredentials(),) @@ -1627,6 +1727,17 @@ def test_tenant_service_base_transport_with_credentials_file(): ) +def test_tenant_service_base_transport_with_adc(): + # Test the default credentials are used if credentials and credentials_file are None. + with mock.patch.object(auth, "default") as adc, mock.patch( + "google.cloud.talent_v4beta1.services.tenant_service.transports.TenantServiceTransport._prep_wrapped_messages" + ) as Transport: + Transport.return_value = None + adc.return_value = (credentials.AnonymousCredentials(), None) + transport = transports.TenantServiceTransport() + adc.assert_called_once() + + def test_tenant_service_auth_adc(): # If no credentials are provided, we should use ADC credentials. with mock.patch.object(auth, "default") as adc: @@ -1679,191 +1790,114 @@ def test_tenant_service_host_with_port(): def test_tenant_service_grpc_transport_channel(): channel = grpc.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TenantServiceGrpcTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called def test_tenant_service_grpc_asyncio_transport_channel(): channel = aio.insecure_channel("http://localhost/") - # Check that if channel is provided, mtls endpoint and client_cert_source - # won't be used. - callback = mock.MagicMock() + # Check that channel is used if provided. transport = transports.TenantServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - channel=channel, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=callback, + host="squid.clam.whelk", channel=channel, ) assert transport.grpc_channel == channel assert transport._host == "squid.clam.whelk:443" - assert not callback.called - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_tenant_service_grpc_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TenantServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel - - -@mock.patch("grpc.ssl_channel_credentials", autospec=True) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_tenant_service_grpc_asyncio_transport_channel_mtls_with_client_cert_source( - grpc_create_channel, grpc_ssl_channel_cred -): - # Check that if channel is None, but api_mtls_endpoint and client_cert_source - # are provided, then a mTLS channel will be created. - mock_cred = mock.Mock() - - mock_ssl_cred = mock.Mock() - grpc_ssl_channel_cred.return_value = mock_ssl_cred - - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - transport = transports.TenantServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint="mtls.squid.clam.whelk", - client_cert_source=client_cert_source_callback, - ) - grpc_ssl_channel_cred.assert_called_once_with( - certificate_chain=b"cert bytes", private_key=b"key bytes" - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers.create_channel", autospec=True) -def test_tenant_service_grpc_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. - mock_ssl_cred = mock.Mock() - with mock.patch.multiple( - "google.auth.transport.grpc.SslCredentials", - __init__=mock.Mock(return_value=None), - ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), - ): - mock_cred = mock.Mock() - transport = transports.TenantServiceGrpcTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel +def test_tenant_service_transport_channel_mtls_with_client_cert_source(transport_class): + with mock.patch( + "grpc.ssl_channel_credentials", autospec=True + ) as grpc_ssl_channel_cred: + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_ssl_cred = mock.Mock() + grpc_ssl_channel_cred.return_value = mock_ssl_cred + + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + + cred = credentials.AnonymousCredentials() + with pytest.warns(DeprecationWarning): + with mock.patch.object(auth, "default") as adc: + adc.return_value = (cred, None) + transport = transport_class( + host="squid.clam.whelk", + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=client_cert_source_callback, + ) + adc.assert_called_once() + + grpc_ssl_channel_cred.assert_called_once_with( + certificate_chain=b"cert bytes", private_key=b"key bytes" + ) + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel @pytest.mark.parametrize( - "api_mtls_endpoint", ["mtls.squid.clam.whelk", "mtls.squid.clam.whelk:443"] + "transport_class", + [ + transports.TenantServiceGrpcTransport, + transports.TenantServiceGrpcAsyncIOTransport, + ], ) -@mock.patch("google.api_core.grpc_helpers_async.create_channel", autospec=True) -def test_tenant_service_grpc_asyncio_transport_channel_mtls_with_adc( - grpc_create_channel, api_mtls_endpoint -): - # Check that if channel and client_cert_source are None, but api_mtls_endpoint - # is provided, then a mTLS channel will be created with SSL ADC. - mock_grpc_channel = mock.Mock() - grpc_create_channel.return_value = mock_grpc_channel - - # Mock google.auth.transport.grpc.SslCredentials class. +def test_tenant_service_transport_channel_mtls_with_adc(transport_class): mock_ssl_cred = mock.Mock() with mock.patch.multiple( "google.auth.transport.grpc.SslCredentials", __init__=mock.Mock(return_value=None), ssl_credentials=mock.PropertyMock(return_value=mock_ssl_cred), ): - mock_cred = mock.Mock() - transport = transports.TenantServiceGrpcAsyncIOTransport( - host="squid.clam.whelk", - credentials=mock_cred, - api_mtls_endpoint=api_mtls_endpoint, - client_cert_source=None, - ) - grpc_create_channel.assert_called_once_with( - "mtls.squid.clam.whelk:443", - credentials=mock_cred, - credentials_file=None, - scopes=( - "https://www.googleapis.com/auth/cloud-platform", - "https://www.googleapis.com/auth/jobs", - ), - ssl_credentials=mock_ssl_cred, - quota_project_id=None, - ) - assert transport.grpc_channel == mock_grpc_channel + with mock.patch.object( + transport_class, "create_channel", autospec=True + ) as grpc_create_channel: + mock_grpc_channel = mock.Mock() + grpc_create_channel.return_value = mock_grpc_channel + mock_cred = mock.Mock() + + with pytest.warns(DeprecationWarning): + transport = transport_class( + host="squid.clam.whelk", + credentials=mock_cred, + api_mtls_endpoint="mtls.squid.clam.whelk", + client_cert_source=None, + ) + + grpc_create_channel.assert_called_once_with( + "mtls.squid.clam.whelk:443", + credentials=mock_cred, + credentials_file=None, + scopes=( + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/jobs", + ), + ssl_credentials=mock_ssl_cred, + quota_project_id=None, + ) + assert transport.grpc_channel == mock_grpc_channel def test_tenant_path(): @@ -1887,3 +1921,24 @@ def test_parse_tenant_path(): # Check that the path construction is reversible. actual = TenantServiceClient.parse_tenant_path(path) assert expected == actual + + +def test_client_withDEFAULT_CLIENT_INFO(): + client_info = gapic_v1.client_info.ClientInfo() + + with mock.patch.object( + transports.TenantServiceTransport, "_prep_wrapped_messages" + ) as prep: + client = TenantServiceClient( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info) + + with mock.patch.object( + transports.TenantServiceTransport, "_prep_wrapped_messages" + ) as prep: + transport_class = TenantServiceClient.get_transport_class() + transport = transport_class( + credentials=credentials.AnonymousCredentials(), client_info=client_info, + ) + prep.assert_called_once_with(client_info)