{"id":41591429,"url":"https://github.com/messagebird/python-grpc-argument-validator","last_synced_at":"2026-01-24T09:31:37.594Z","repository":{"id":36964574,"uuid":"359869794","full_name":"messagebird/python-grpc-argument-validator","owner":"messagebird","description":null,"archived":false,"fork":false,"pushed_at":"2022-06-20T07:12:07.000Z","size":130,"stargazers_count":7,"open_issues_count":1,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-09-04T09:22:10.209Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/messagebird.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-04-20T15:45:05.000Z","updated_at":"2024-04-02T12:18:20.000Z","dependencies_parsed_at":"2022-08-18T02:00:14.551Z","dependency_job_id":null,"html_url":"https://github.com/messagebird/python-grpc-argument-validator","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/messagebird/python-grpc-argument-validator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/messagebird%2Fpython-grpc-argument-validator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/messagebird%2Fpython-grpc-argument-validator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/messagebird%2Fpython-grpc-argument-validator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/messagebird%2Fpython-grpc-argument-validator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/messagebird","download_url":"https://codeload.github.com/messagebird/python-grpc-argument-validator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/messagebird%2Fpython-grpc-argument-validator/sbom","scorecard":{"id":637353,"data":{"date":"2025-08-11","repo":{"name":"github.com/messagebird/python-grpc-argument-validator","commit":"1616f657335057fc8d85cdcb77539d5ba6719c35"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Code-Review","score":9,"reason":"Found 10/11 approved changesets -- score normalized to 9","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/release.yml:1","Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:48: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:65: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:78: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:79: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:87: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/release.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/release.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/release.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/tests.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:32: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:49: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:52: update your workflow using https://app.stepsecurity.io/secureworkflow/messagebird/python-grpc-argument-validator/tests.yml/master?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:35","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:55","Warn: pipCommand not pinned by hash: .github/workflows/release.yml:84","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:58","Warn: pipCommand not pinned by hash: .github/workflows/tests.yml:38","Info:   0 out of  14 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   4 third-party GitHubAction dependencies pinned","Info:   0 out of   5 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: BSD 3-Clause \"New\" or \"Revised\" License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"16 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2024-230 / GHSA-248v-346w-9cwc","Warn: Project is vulnerable to: PYSEC-2022-42986 / GHSA-43fp-rhv2-5gv8","Warn: Project is vulnerable to: PYSEC-2023-135 / GHSA-xqr8-7jwr-rhp7","Warn: Project is vulnerable to: GHSA-496j-2rq6-j6cc","Warn: Project is vulnerable to: PYSEC-2024-60 / GHSA-jjg7-2v4v-x38h","Warn: Project is vulnerable to: PYSEC-2022-260 / GHSA-v973-fxgf-6xhp","Warn: Project is vulnerable to: GHSA-8gq9-2x98-w8hf","Warn: Project is vulnerable to: GHSA-8qvm-5x2c-j2w7","Warn: Project is vulnerable to: GHSA-9hjg-9r4m-mvj7","Warn: Project is vulnerable to: GHSA-9wx4-h78v-vm56","Warn: Project is vulnerable to: PYSEC-2023-74 / GHSA-j8r2-6x86-q33q","Warn: Project is vulnerable to: GHSA-34jh-p97f-mpxf","Warn: Project is vulnerable to: PYSEC-2023-212 / GHSA-g4mx-q9vg-27p4","Warn: Project is vulnerable to: GHSA-pq67-6m6q-mj2v","Warn: Project is vulnerable to: PYSEC-2023-192 / GHSA-v845-jxx5-vc9f","Warn: Project is vulnerable to: GHSA-jfmj-5v4g-7637"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T09:35:43.613Z","repository_id":36964574,"created_at":"2025-08-21T09:35:43.613Z","updated_at":"2025-08-21T09:35:43.613Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28723233,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-24T08:27:05.734Z","status":"ssl_error","status_checked_at":"2026-01-24T08:27:01.197Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-24T09:31:37.505Z","updated_at":"2026-01-24T09:31:37.577Z","avatar_url":"https://github.com/messagebird.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![codecov](https://codecov.io/gh/messagebird/python-grpc-argument-validator/branch/master/graph/badge.svg)](https://codecov.io/gh/messagebird/python-grpc-argument-validator)\n[![PyPI](https://img.shields.io/pypi/v/grpc-argument-validator.svg?color=blue)](https://pypi.org/project/python-grpc-argument-validator/)\n[![License](https://img.shields.io/github/license/messagebird/python-grpc-argument-validator)](https://opensource.org/licenses/BSD-3-Clause)\n[![Docs](https://img.shields.io/static/v1?label=Docs\u0026message=Github%20Pages\u0026color=blue)](https://messagebird.github.io/python-grpc-argument-validator/grpc_argument_validator/)\n[![tests](https://github.com/messagebird/python-grpc-argument-validator/workflows/Tests/badge.svg)](https://github.com/messagebird/python-grpc-argument-validator/actions?workflow=tests)\n\n\n# gRPC argument validator\ngRPC argument validator is a library that provides decorators to automatically validate arguments in requests to rpc methods.\n\n\u003c!-- GETTING STARTED --\u003e\n## Getting Started\n\nThis is an example of how you may give instructions on setting up your project locally.\nTo get a local copy up and running follow these simple example steps.\n\n### Installation\n\n#### From PyPI\n```sh\npip install grpc-argument-validator\n```\n\n#### From source\n\n- Install [`poetry`](https://python-poetry.org/docs/)\n\n- Clone repo\n\n```sh\ngit clone https://github.com/messagebird/python-grpc-argument-validator.git\n```\n\n- Install packages\n\n```sh\ncd python-grpc-argument-validator \u0026\u0026 poetry install\n```\n\n- Run the tests\n\n```sh\ncd src/tests\npoetry run python -m unittest\n```\n\n\n\n\u003c!-- USAGE EXAMPLES --\u003e\n## Quick Example\n```python\nfrom google.protobuf.descriptor import FieldDescriptor\nfrom grpc_argument_validator import validate_args\nfrom grpc_argument_validator import AbstractArgumentValidator, ValidationResult, ValidationContext\n\nclass PathValidator(AbstractArgumentValidator):\n\n    def check(self, name: str, value: Path, field_descriptor: FieldDescriptor, validation_context: ValidationContext) -\u003e ValidationResult:\n        if len(value.points) \u003e 5:\n            return ValidationResult(valid=True)\n        return ValidationResult(False, f\"path for '{name}' should be at least five points long\")\n\nclass RouteService(RouteCheckerServicer):\n    @validate_args(\n        non_empty=[\"tags\", \"tags[]\", \"path.points\"],\n        validators={\"path\": PathValidator()},\n    )\n    def Create(self, request: Route, context: grpc.ServicerContext):\n        return BoolValue(value=True)\n```\n\n\u003c!-- DOCUMENTATION --\u003e\n## Documentation\n\nWe host the full API reference on [GitHub pages](https://messagebird.github.io/python-grpc-argument-validator/grpc_argument_validator/).\n\n### Argument field syntax\nTo specify which argument field should be validated, `grpc-argument-validator` expects strings that match the field names\nas defined in the protobufs. To access nested fields, use a dot (`.`).\n\nConsider the following protobuf definition:\n```protobuf\nsyntax = \"proto3\";\n\npackage routeguide;\n\nimport \"google/protobuf/empty.proto\";\nimport \"google/protobuf/wrappers.proto\";\n\nmessage Point {\n  int32 x = 1;\n  int32 y = 2;\n  google.protobuf.StringValue name = 3;\n}\n\nmessage Rectangle {\n  Point lo = 1;\n  Point hi = 2;\n}\n\nmessage Area {\n  Rectangle rectangle = 1;\n  google.protobuf.StringValue message = 2;\n  google.protobuf.BytesValue uuid = 3;\n}\n\nmessage Path {\n  repeated Point points = 1;\n}\n\nenum Planet {\n  PLANET_INVALID = 0;\n  PLANET_EARTH = 1;\n  PLANET_MARS = 2;\n}\n\nmessage PlanetValue {\n  Planet value = 1;\n}\n\nmessage Route {\n  Path path = 1;\n  google.protobuf.StringValue name = 2;\n  PlanetValue planet = 3;\n  repeated string tags = 4;\n}\n\nservice RouteService {\n  rpc CreateRoute(Route) returns (google.protobuf.Empty);\n  rpc CreateArea(Area) returns (google.protobuf.Empty);\n}\n```\n- If you want to validate the field `planet` in a `Route` proto, simply specify `\"planet\"` or equivalently `\".planet\"`.\n- If you want to validate the `value` field within the `name` field of a `Route` proto, use `\"name.value\"` or\nequivalently `\".name.value\"`.\n- If you want to apply a check to each element of a `repeated` field, append `[]` to the name of the field.\n- If you want to apply a check to the 'root proto' (i.e. the request itself), use `\".\"` as the field path.\n\nTo clarify this, let's say that we know that both `planet` and `name.value` should have non-default values. We can then\ndecorate a method in our gRPC server as follows:\n\n```python\nimport grpc\nfrom google.protobuf.empty_pb2 import Empty\nfrom grpc_argument_validator import validate_args\nfrom tests.route_guide_protos.route_guide_pb2 import Route\nfrom tests.route_guide_protos.route_guide_pb2_grpc import RouteServiceServicer\n\n\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(non_empty=[\"planet\", \"name.value\"])\n    def CreateRoute(self, request: Route, context: grpc.ServicerContext):\n        return Empty()\n```\n\nCalling the service with a default value for either `planet` or `name.value` will yield an `INVALID_ARGUMENT` status code\nwith further details on which fields violate the validation.\n\n### Validators\nThere are two kinds of validators you might consider:\n\n- There are predefined validators which we will cover shortly\n- Another option is to define your own validators\n\nIn the examples below, we have used exactly one validator + field path per `validate_args` decorator for clarity.\nFortunately, our API allows you to use multiple validators and fields!\n\n#### 'Has' validator\nThe simplest of all predefined validators is the 'has' validator which simply checks whether a `HasField` evaluates to\n`True`. This of course works in combination with nested fields.\n\nIn the example below, calling the `Create` endpoint without setting `Route.name` would result in an `INVALID_ARGUMENT`\nstatus.\n```python\n\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(has=[\"name\"])\n    def CreateRoute(self, request: Route, context: grpc.ServicerContext):\n        return Empty()\n```\nRun this on a local machine and make a request with an invalid argument:\n```python\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateRoute(Route(tags=[\"tag\"]))\n    except grpc.RpcError as e:\n        if isinstance(e, grpc.Call):\n            print(e.details())\n```\nThe following will be printed:\n`must have 'name'`\n\n#### UUID validator\nAnother common use-case is the validation of UUIDs. You can enlist the fields that should be UUIDs (represented as\n16 bytes) with the `uuids` argument:\n```python\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(uuids=[\"uuid.value\"])\n    def CreateArea(self, request: Area, context: grpc.ServicerContext):\n        return Empty()\n```\nThe client side might violate the UUID requirement as follows:\n```python\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateArea(Area(uuid=BytesValue(value=\"not a uuid\".encode())))\n    except grpc.RpcError as e:\n        if isinstance(e, grpc.Call):\n            print(e.details())\n```\nThis will print `'uuid.value' must be a valid UUID`.\n\n#### Non-default validator\nFor fields that should have a non-default value, such as\n[enums](https://developers.google.com/protocol-buffers/docs/style#enums), we have provided the `non_default` argument:\n```python\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(non_default=[\"planet.value\"])\n    def CreateRoute(self, request: Route, context: grpc.ServicerContext):\n        return Empty()\n```\nThe client side may violate this as follows:\n```python\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateRoute(Route(planet=PlanetValue()))\n    except grpc.RpcError as e:\n        if isinstance(e, grpc.Call):\n            print(e.details())\n```\nWhich will print `'planet.value' must have non-default value`.\n\n#### Non-empty validator\nWe provide a 'non-'empty validator which can be used to ensure that a `repeated` field has more than zero elements.\n```python\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(non_empty=[\"tags\"])\n    def CreateRoute(self, request: Route, context: grpc.ServicerContext):\n        return Empty()\n```\nWhich can be violated as follows:\n\n```python\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateRoute(Route(tags=[]))\n    except grpc.RpcError as e:\n        if isinstance(e, grpc.Call):\n            print(e.details())\n```\nWhich will print `'tags' must be non-empty`.\n\n#### Regexp validator\nFinally, we have the regexp validator that can be used to check whether a string field matches a regular expression.\n```python\nclass RouteServiceImpl(RouteServiceServicer):\n    @validate_args(validators={\"message.value\": RegexpValidator(pattern=r\"\\d+\")})\n    def CreateArea(self, request: Area, context: grpc.ServicerContext):\n        return Empty()\n```\n\n```python\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateArea(Area(message=StringValue(value=\"hello world\")))\n    except grpc.RpcError as e:\n        if isinstance(e, grpc.Call):\n            print(e.details())\n```\nWhich will print `'message.value' must match regexp pattern: \\d+`.\n\n#### Custom validators\nYou can also write custom validators to flexibily handle your use-case. You need to derive a class from\n`AbstractArgumentValidator` and implement its `check` method. The example below shows how to implement a simple\nvalidator for checking that a path has 5 points. You can provide such custom validators through a `dict` that\nmaps a field path to a validator:\n```python\nfrom grpc_argument_validator import AbstractArgumentValidator\nfrom grpc_argument_validator import ValidationContext\nfrom grpc_argument_validator import ValidationResult\nfrom google.protobuf.descriptor import FieldDescriptor\n\nfrom examples.route_guide_pb2 import Path\n\nclass PathValidator(AbstractArgumentValidator):\n    def check(\n        self, name: str, value: Path, field_descriptor: FieldDescriptor, validation_context: ValidationContext\n    ) -\u003e ValidationResult:\n        if len(value.points) \u003e 5:\n            return ValidationResult(valid=True)\n        return ValidationResult(False, f\"path for '{name}' should be at least five points long\")\n\n\nclass RouteServiceImpl(RouteServiceServicer):\n\n    @validate_args(validators={\"path\": PathValidator()})\n    def CreateRoute(self, request: Route, context: grpc.ServicerContext):\n        return Empty()\n```\n\n### Optional vs. required validators\nFor each of the built-in validators (except for the `has` validator), `validate_args` has not one but two keyword\narguments. One of those is prepended with `optional_`. This means that apart from `uuid`, `non_default` and\n`non_empty` we also have `optional_uuid`, `optional_non_default` and `optional_non_empty`. The behavior is slightly\ndifferent: for any of the `optional_*` validators, it is OK if the field is not contained by the incoming request.\nSometimes fields are simply optional, and you only want to validate them _if_ they are present.\n\nSince it is also common that fields are _not optional_, we also provide the required validators (without `optional_*`)\nfor which [`HasField`](https://googleapis.dev/python/protobuf/latest/google/protobuf/message.html#google.protobuf.message.Message.HasField)\nmust evaluate to `True` for that field and all preceding fields in the protos hierarchy.\n\nThe custom validator counterparts are `validators` and `optional_validators`. Each takes a `dict` with a mapping of\nfield paths to validators. These can be used for validators that might be preconfigured such as the `RegexpValidator`\nor for customer validators.\n\n### Streaming requests\nYou can also use the validators for streaming requests. Since streaming requests might not all look the same in a\nsingle stream (e.g. the first request might have metadata describing the remainder of the stream), we provide a\nstreaming request index in a `ValidationContext` that is passed to an `AbstractArgumentValidator`.\n\nHere's an example of how that could be used:\n```python\nclass StreamingPathValidator(AbstractArgumentValidator):\n    def __init__(self, first_number_of_points: int, second_number_of_points: int):\n        self._first_number_of_points = first_number_of_points\n        self._second_number_of_points = second_number_of_points\n\n    def check(\n        self, name: str, value: Any, field_descriptor: FieldDescriptor, validation_context: ValidationContext\n    ) -\u003e ValidationResult:\n        if not validation_context.is_streaming:\n            return ValidationResult(False, \"request must be a streaming request\")\n\n        if validation_context.streaming_message_index == 0:\n            if len(value.points) != self._first_number_of_points:\n                return ValidationResult(False, f\"first path should have {self._first_number_of_points} points\")\n\n        if validation_context.streaming_message_index == 1:\n            if len(value.points) != self._second_number_of_points:\n                return ValidationResult(False, f\"second path should have {self._second_number_of_points} points\")\n\n        return ValidationResult(True)\n```\n\n### Enabling rich error details\nTo enable [richer error responses](https://cloud.google.com/apis/design/errors#error_model) where each violation is\ncontained in a\n[`BadRequest` proto](https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto), you can use\n```python\nfrom grpc_argument_validator import ArgumentValidatorConfig\n\nArgumentValidatorConfig.set_rich_grpc_errors(enabled=True)\n```\n\nNow, your client-side can parse the error details as follows:\n```python\ndef extract_error_details(err):\n    status_proto = status_pb2.Status()\n\n    for metadatum in err.trailing_metadata():\n        if isinstance(metadatum, _Metadatum):\n            if metadatum.key == \"grpc-status-details-bin\":\n                status_proto.MergeFromString(metadatum.value)\n\n    unpacked = [_unpack_error_detail(det) for det in status_proto.details]\n    return unpacked\n\ndef _unpack_error_detail(grpc_detail):\n    val = error_details_pb2.BadRequest()\n    grpc_detail.Unpack(val)\n    return val\n\nwith grpc.insecure_channel(\"127.0.0.1:50051\") as c:\n    route_client = RouteServiceStub(channel=c)\n    try:\n        route_client.CreateArea(Area(message=StringValue(value=\"hello world\")))\n    except grpc.RpcError as e:\n        error_details = extract_error_details(e)\n        print(error_details)\n```\n\n\u003c!-- CONTRIBUTING --\u003e\n## Contributing\n\nContributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.\n\n1. Fork the Project\n2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)\n3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)\n4. Push to the Branch (`git push origin feature/AmazingFeature`)\n5. Open a Pull Request\n\n### Generating HTML Documentation\nGenerate the docs by running:\n```sh\npdoc --html -o docs src/grpc_argument_validator\n```\n\n\n\u003c!-- LICENSE --\u003e\n## License\n\nDistributed under The BSD 3-Clause License. Copyright (c) 2021, MessageBird\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmessagebird%2Fpython-grpc-argument-validator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmessagebird%2Fpython-grpc-argument-validator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmessagebird%2Fpython-grpc-argument-validator/lists"}