diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8f496ad..627d951 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -11,14 +11,29 @@ We pledge to maintain a welcoming and inclusive community. Please read our [Code of Conduct][code-of-conduct] before participating. ## How Can I Contribute? -Clone the repository and install dependencies: + +First, clone the repository: + ``` git clone git@github.com:bufbuild/protovalidate-python.git cd protovalidate-python -make install -pipenv shell ``` +Then, make any changes you'd like. We use a Makefile to test and lint our code, +so you'll need a few non-Python tools: + +* GNU Make (to use the Makefile): part of the `build-essential` package on + Debian-derived Linux distributions (including Ubuntu), and part of + `xcode-select --install` on Macs. +* Go (for the conformance test runner): often available in your system package + manager (`apt`, `dnf`, `brew`, etc.), but most reliable when [installed + directly from upstream](https://go.dev/doc/install). + +With Go and GNU Make installed, you can verify that your changes pass tests and +lint checks by running `make`. If your Python 3 interpreter isn't available as +`python3`, try `PYTHON=python make`. For a list of other useful commands, run +`make help`. + ### Reporting Bugs Bugs are tracked as GitHub issues. If you discover a problem diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b0dd3b8..565c24d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -22,17 +22,17 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 1 - - name: Set up Python ${{ matrix.python-version }} + - name: Install Go + uses: actions/setup-go@v4 + - name: Install Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - - name: Check generated - run: make checkgenerate - name: Execute tests run: make test - - name: Check mypy - run: mypy protovalidate - - name: Check ruff - uses: chartboost/ruff-action@v1 - with: - src: "./protovalidate" + - name: Lint + run: make lint + - name: Format + run: make format + - name: Check generated + run: make checkgenerate diff --git a/.github/workflows/conformance.yaml b/.github/workflows/conformance.yaml index b6586a1..8ef9b69 100644 --- a/.github/workflows/conformance.yaml +++ b/.github/workflows/conformance.yaml @@ -22,7 +22,9 @@ jobs: uses: actions/checkout@v3 with: fetch-depth: 1 - - name: Set up Python ${{ matrix.python-version }} + - name: Install Go + uses: actions/setup-go@v4 + - name: Install Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/Makefile b/Makefile index 75677e6..26c0f65 100644 --- a/Makefile +++ b/Makefile @@ -12,14 +12,20 @@ LICENSE_IGNORE := LICENSE_HEADER_VERSION := 59c69fa4ddbd56c887cb178a03257cd3908ce518 # Set to use a different compiler. For example, `GO=go1.18rc1 make test`. GO ?= go -ARGS ?= --strict --expected_failures=nonconforming.yaml +# Set to use a different Python interpreter. For example, `PYTHON=python make test`. +PYTHON ?= python3 +CONFORMANCE_ARGS ?= --strict --expected_failures=nonconforming.yaml +LICENSE_HEADER := $(BIN)/license-header \ + --license-type apache \ + --copyright-holder "Buf Technologies, Inc." \ + --year-range "$(COPYRIGHT_YEARS)" .PHONY: help help: ## Describe useful make targets @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-15s %s\n", $$1, $$2}' .PHONY: all -all: test conformance +all: test conformance lint ## Run all tests and lint (default) .PHONY: clean clean: ## Delete intermediate build artifacts @@ -27,42 +33,37 @@ clean: ## Delete intermediate build artifacts git clean -Xdf .PHONY: generate -generate: generate-proto generate-license ## Regenerate code and license headers - -.PHONY: generate-license -generate-license: $(BIN)/license-header format-python ## Format code and regenerate license headers - $(BIN)/license-header \ - --license-type apache \ - --copyright-holder "Buf Technologies, Inc." \ - --year-range "$(COPYRIGHT_YEARS)" $(LICENSE_IGNORE) - -.PHONY: generate-proto -generate-proto: $(BIN)/buf ## Regenerate code from proto files +generate: $(BIN)/buf $(BIN)/license-header ## Regenerate code and license headers rm -rf gen $(BIN)/buf generate buf.build/bufbuild/protovalidate $(BIN)/buf generate buf.build/bufbuild/protovalidate-testing + $(LICENSE_HEADER) --ignore __init__.py -.PHONY: format ## Format all code -format: generate-license - -.PHONY: format-python -format-python: install ## Format all code according to isort and black - python3 -m isort protovalidate tests - python3 -m black protovalidate tests +.PHONY: format +format: install $(BIN)/license-header ## Format code + $(LICENSE_HEADER) + pipenv run black protovalidate tests + pipenv run ruff --fix protovalidate tests .PHONY: test -test: generate install ## Run all unit tests +test: $(BIN)/protovalidate-conformance generate install ## Run unit tests pipenv run pytest .PHONY: conformance -conformance: $(BIN)/protovalidate-conformance install - $(BIN)/protovalidate-conformance $(ARGS) pipenv -- run python3 -m tests.conformance.runner +conformance: $(BIN)/protovalidate-conformance generate install ## Run conformance tests + $(BIN)/protovalidate-conformance $(CONFORMANCE_ARGS) pipenv -- run $(PYTHON) -m tests.conformance.runner + +.PHONY: lint +lint: install ## Lint code + pipenv run black --check --diff protovalidate tests + pipenv run mypy protovalidate + pipenv run ruff protovalidate tests + pipenv verify .PHONY: install -install: - python3 -m pip install --upgrade pip - pip install pipenv ruff mypy types-protobuf black isort - pipenv --python python3 install +install: ## Install dependencies + $(PYTHON) -m pip install --upgrade pip pipenv + pipenv --python $(PYTHON) sync --dev .PHONY: checkgenerate checkgenerate: generate diff --git a/Pipfile b/Pipfile index 0f988cf..de36392 100644 --- a/Pipfile +++ b/Pipfile @@ -6,10 +6,13 @@ name = "pypi" [packages] cel-python = "*" protobuf = "*" -pytest = "*" -validate-email = "*" [dev-packages] +pytest = "*" +mypy = "*" +ruff = "*" +types-protobuf = "*" +black = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index 8ce22e7..2d3a31a 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "b3a40595ed674476cb0172e403a1095a20dc91700d287f71913ec878538f33f1" + "sha256": "88b036cd2aa1e619c58537ce9fb4e89bfadbb0d4c5b549e96094bc11e6e91a5a" }, "pipfile-spec": 6, "requires": { @@ -42,84 +42,84 @@ }, "charset-normalizer": { "hashes": [ - "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6", - "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1", - "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e", - "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373", - "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62", - "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230", - "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be", - "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c", - "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0", - "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448", - "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f", - "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649", - "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d", - "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0", - "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706", - "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a", - "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59", - "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23", - "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5", - "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb", - "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e", - "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e", - "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c", - "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28", - "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d", - "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41", - "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974", - "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce", - "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f", - "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1", - "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d", - "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8", - "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017", - "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31", - "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7", - "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8", - "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e", - "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14", - "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd", - "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d", - "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795", - "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b", - "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b", - "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b", - "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203", - "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f", - "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19", - "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1", - "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a", - "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac", - "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9", - "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0", - "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137", - "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f", - "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6", - "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5", - "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909", - "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f", - "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0", - "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324", - "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755", - "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb", - "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854", - "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c", - "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60", - "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84", - "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0", - "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b", - "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1", - "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531", - "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1", - "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11", - "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326", - "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df", - "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab" + "sha256:04e57ab9fbf9607b77f7d057974694b4f6b142da9ed4a199859d9d4d5c63fe96", + "sha256:09393e1b2a9461950b1c9a45d5fd251dc7c6f228acab64da1c9c0165d9c7765c", + "sha256:0b87549028f680ca955556e3bd57013ab47474c3124dc069faa0b6545b6c9710", + "sha256:1000fba1057b92a65daec275aec30586c3de2401ccdcd41f8a5c1e2c87078706", + "sha256:1249cbbf3d3b04902ff081ffbb33ce3377fa6e4c7356f759f3cd076cc138d020", + "sha256:1920d4ff15ce893210c1f0c0e9d19bfbecb7983c76b33f046c13a8ffbd570252", + "sha256:193cbc708ea3aca45e7221ae58f0fd63f933753a9bfb498a3b474878f12caaad", + "sha256:1a100c6d595a7f316f1b6f01d20815d916e75ff98c27a01ae817439ea7726329", + "sha256:1f30b48dd7fa1474554b0b0f3fdfdd4c13b5c737a3c6284d3cdc424ec0ffff3a", + "sha256:203f0c8871d5a7987be20c72442488a0b8cfd0f43b7973771640fc593f56321f", + "sha256:246de67b99b6851627d945db38147d1b209a899311b1305dd84916f2b88526c6", + "sha256:2dee8e57f052ef5353cf608e0b4c871aee320dd1b87d351c28764fc0ca55f9f4", + "sha256:2efb1bd13885392adfda4614c33d3b68dee4921fd0ac1d3988f8cbb7d589e72a", + "sha256:2f4ac36d8e2b4cc1aa71df3dd84ff8efbe3bfb97ac41242fbcfc053c67434f46", + "sha256:3170c9399da12c9dc66366e9d14da8bf7147e1e9d9ea566067bbce7bb74bd9c2", + "sha256:3b1613dd5aee995ec6d4c69f00378bbd07614702a315a2cf6c1d21461fe17c23", + "sha256:3bb3d25a8e6c0aedd251753a79ae98a093c7e7b471faa3aa9a93a81431987ace", + "sha256:3bb7fda7260735efe66d5107fb7e6af6a7c04c7fce9b2514e04b7a74b06bf5dd", + "sha256:41b25eaa7d15909cf3ac4c96088c1f266a9a93ec44f87f1d13d4a0e86c81b982", + "sha256:45de3f87179c1823e6d9e32156fb14c1927fcc9aba21433f088fdfb555b77c10", + "sha256:46fb8c61d794b78ec7134a715a3e564aafc8f6b5e338417cb19fe9f57a5a9bf2", + "sha256:48021783bdf96e3d6de03a6e39a1171ed5bd7e8bb93fc84cc649d11490f87cea", + "sha256:4957669ef390f0e6719db3613ab3a7631e68424604a7b448f079bee145da6e09", + "sha256:5e86d77b090dbddbe78867a0275cb4df08ea195e660f1f7f13435a4649e954e5", + "sha256:6339d047dab2780cc6220f46306628e04d9750f02f983ddb37439ca47ced7149", + "sha256:681eb3d7e02e3c3655d1b16059fbfb605ac464c834a0c629048a30fad2b27489", + "sha256:6c409c0deba34f147f77efaa67b8e4bb83d2f11c8806405f76397ae5b8c0d1c9", + "sha256:7095f6fbfaa55defb6b733cfeb14efaae7a29f0b59d8cf213be4e7ca0b857b80", + "sha256:70c610f6cbe4b9fce272c407dd9d07e33e6bf7b4aa1b7ffb6f6ded8e634e3592", + "sha256:72814c01533f51d68702802d74f77ea026b5ec52793c791e2da806a3844a46c3", + "sha256:7a4826ad2bd6b07ca615c74ab91f32f6c96d08f6fcc3902ceeedaec8cdc3bcd6", + "sha256:7c70087bfee18a42b4040bb9ec1ca15a08242cf5867c58726530bdf3945672ed", + "sha256:855eafa5d5a2034b4621c74925d89c5efef61418570e5ef9b37717d9c796419c", + "sha256:8700f06d0ce6f128de3ccdbc1acaea1ee264d2caa9ca05daaf492fde7c2a7200", + "sha256:89f1b185a01fe560bc8ae5f619e924407efca2191b56ce749ec84982fc59a32a", + "sha256:8b2c760cfc7042b27ebdb4a43a4453bd829a5742503599144d54a032c5dc7e9e", + "sha256:8c2f5e83493748286002f9369f3e6607c565a6a90425a3a1fef5ae32a36d749d", + "sha256:8e098148dd37b4ce3baca71fb394c81dc5d9c7728c95df695d2dca218edf40e6", + "sha256:94aea8eff76ee6d1cdacb07dd2123a68283cb5569e0250feab1240058f53b623", + "sha256:95eb302ff792e12aba9a8b8f8474ab229a83c103d74a750ec0bd1c1eea32e669", + "sha256:9bd9b3b31adcb054116447ea22caa61a285d92e94d710aa5ec97992ff5eb7cf3", + "sha256:9e608aafdb55eb9f255034709e20d5a83b6d60c054df0802fa9c9883d0a937aa", + "sha256:a103b3a7069b62f5d4890ae1b8f0597618f628b286b03d4bc9195230b154bfa9", + "sha256:a386ebe437176aab38c041de1260cd3ea459c6ce5263594399880bbc398225b2", + "sha256:a38856a971c602f98472050165cea2cdc97709240373041b69030be15047691f", + "sha256:a401b4598e5d3f4a9a811f3daf42ee2291790c7f9d74b18d75d6e21dda98a1a1", + "sha256:a7647ebdfb9682b7bb97e2a5e7cb6ae735b1c25008a70b906aecca294ee96cf4", + "sha256:aaf63899c94de41fe3cf934601b0f7ccb6b428c6e4eeb80da72c58eab077b19a", + "sha256:b0dac0ff919ba34d4df1b6131f59ce95b08b9065233446be7e459f95554c0dc8", + "sha256:baacc6aee0b2ef6f3d308e197b5d7a81c0e70b06beae1f1fcacffdbd124fe0e3", + "sha256:bf420121d4c8dce6b889f0e8e4ec0ca34b7f40186203f06a946fa0276ba54029", + "sha256:c04a46716adde8d927adb9457bbe39cf473e1e2c2f5d0a16ceb837e5d841ad4f", + "sha256:c0b21078a4b56965e2b12f247467b234734491897e99c1d51cee628da9786959", + "sha256:c1c76a1743432b4b60ab3358c937a3fe1341c828ae6194108a94c69028247f22", + "sha256:c4983bf937209c57240cff65906b18bb35e64ae872da6a0db937d7b4af845dd7", + "sha256:c4fb39a81950ec280984b3a44f5bd12819953dc5fa3a7e6fa7a80db5ee853952", + "sha256:c57921cda3a80d0f2b8aec7e25c8aa14479ea92b5b51b6876d975d925a2ea346", + "sha256:c8063cf17b19661471ecbdb3df1c84f24ad2e389e326ccaf89e3fb2484d8dd7e", + "sha256:ccd16eb18a849fd8dcb23e23380e2f0a354e8daa0c984b8a732d9cfaba3a776d", + "sha256:cd6dbe0238f7743d0efe563ab46294f54f9bc8f4b9bcf57c3c666cc5bc9d1299", + "sha256:d62e51710986674142526ab9f78663ca2b0726066ae26b78b22e0f5e571238dd", + "sha256:db901e2ac34c931d73054d9797383d0f8009991e723dab15109740a63e7f902a", + "sha256:e03b8895a6990c9ab2cdcd0f2fe44088ca1c65ae592b8f795c3294af00a461c3", + "sha256:e1c8a2f4c69e08e89632defbfabec2feb8a8d99edc9f89ce33c4b9e36ab63037", + "sha256:e4b749b9cc6ee664a3300bb3a273c1ca8068c46be705b6c31cf5d276f8628a94", + "sha256:e6a5bf2cba5ae1bb80b154ed68a3cfa2fa00fde979a7f50d6598d3e17d9ac20c", + "sha256:e857a2232ba53ae940d3456f7533ce6ca98b81917d47adc3c7fd55dad8fab858", + "sha256:ee4006268ed33370957f55bf2e6f4d263eaf4dc3cfc473d1d90baff6ed36ce4a", + "sha256:eef9df1eefada2c09a5e7a40991b9fc6ac6ef20b1372abd48d2794a316dc0449", + "sha256:f058f6963fd82eb143c692cecdc89e075fa0828db2e5b291070485390b2f1c9c", + "sha256:f25c229a6ba38a35ae6e25ca1264621cc25d4d38dca2942a7fce0b67a4efe918", + "sha256:f2a1d0fd4242bd8643ce6f98927cf9c04540af6efa92323e9d3124f57727bfc1", + "sha256:f7560358a6811e52e9c4d142d497f1a6e10103d3a6881f18d04dbce3729c0e2c", + "sha256:f779d3ad205f108d14e99bb3859aa7dd8e9c68874617c72354d7ecaec2a054ac", + "sha256:f87f746ee241d30d6ed93969de31e5ffd09a2961a051e60ae6bddde9ec3583aa" ], - "markers": "python_version >= '3.7'", - "version": "==3.1.0" + "markers": "python_full_version >= '3.7.0'", + "version": "==3.2.0" }, "idna": { "hashes": [ @@ -129,14 +129,6 @@ "markers": "python_version >= '3.5'", "version": "==3.4" }, - "iniconfig": { - "hashes": [ - "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", - "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, "jmespath": { "hashes": [ "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", @@ -152,48 +144,24 @@ ], "version": "==0.12.0" }, - "packaging": { - "hashes": [ - "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", - "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" - ], - "markers": "python_version >= '3.7'", - "version": "==23.1" - }, - "pluggy": { - "hashes": [ - "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", - "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" - ], - "markers": "python_version >= '3.7'", - "version": "==1.2.0" - }, "protobuf": { "hashes": [ - "sha256:0149053336a466e3e0b040e54d0b615fc71de86da66791c592cc3c8d18150bf8", - "sha256:08fe19d267608d438aa37019236db02b306e33f6b9902c3163838b8e75970223", - "sha256:29660574cd769f2324a57fb78127cda59327eb6664381ecfe1c69731b83e8288", - "sha256:2991f5e7690dab569f8f81702e6700e7364cc3b5e572725098215d3da5ccc6ac", - "sha256:3b01a5274ac920feb75d0b372d901524f7e3ad39c63b1a2d55043f3887afe0c1", - "sha256:3bcbeb2bf4bb61fe960dd6e005801a23a43578200ea8ceb726d1f6bd0e562ba1", - "sha256:447b9786ac8e50ae72cae7a2eec5c5df6a9dbf9aa6f908f1b8bda6032644ea62", - "sha256:514b6bbd54a41ca50c86dd5ad6488afe9505901b3557c5e0f7823a0cf67106fb", - "sha256:5cb9e41188737f321f4fce9a4337bf40a5414b8d03227e1d9fbc59bc3a216e35", - "sha256:7a92beb30600332a52cdadbedb40d33fd7c8a0d7f549c440347bc606fb3fe34b", - "sha256:84ea0bd90c2fdd70ddd9f3d3fc0197cc24ecec1345856c2b5ba70e4d99815359", - "sha256:aca6e86a08c5c5962f55eac9b5bd6fce6ed98645d77e8bfc2b952ecd4a8e4f6a", - "sha256:cc14358a8742c4e06b1bfe4be1afbdf5c9f6bd094dff3e14edb78a1513893ff5" - ], - "index": "pypi", - "version": "==4.23.3" - }, - "pytest": { - "hashes": [ - "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362", - "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3" + "sha256:0a5759f5696895de8cc913f084e27fd4125e8fb0914bb729a17816a33819f474", + "sha256:351cc90f7d10839c480aeb9b870a211e322bf05f6ab3f55fcb2f51331f80a7d2", + "sha256:5fea3c64d41ea5ecf5697b83e41d09b9589e6f20b677ab3c48e5f242d9b7897b", + "sha256:6dd9b9940e3f17077e820b75851126615ee38643c2c5332aa7a359988820c720", + "sha256:7b19b6266d92ca6a2a87effa88ecc4af73ebc5cfde194dc737cf8ef23a9a3b12", + "sha256:8547bf44fe8cec3c69e3042f5c4fb3e36eb2a7a013bb0a44c018fc1e427aafbd", + "sha256:9053df6df8e5a76c84339ee4a9f5a2661ceee4a0dab019e8663c50ba324208b0", + "sha256:c3e0939433c40796ca4cfc0fac08af50b00eb66a40bbbc5dee711998fb0bbc1e", + "sha256:ccd9430c0719dce806b93f89c91de7977304729e55377f872a92465d548329a9", + "sha256:e1c915778d8ced71e26fcf43c0866d7499891bca14c4368448a82edc61fdbc70", + "sha256:e9d0be5bf34b275b9f87ba7407796556abeeba635455d036c7351f7c183ef8ff", + "sha256:effeac51ab79332d44fba74660d40ae79985901ac21bca408f8dc335a81aa597", + "sha256:fee88269a090ada09ca63551bf2f573eb2424035bcf2cb1b121895b01a46594a" ], "index": "pypi", - "version": "==7.3.1" + "version": "==4.23.4" }, "python-dateutil": { "hashes": [ @@ -272,14 +240,174 @@ ], "markers": "python_version >= '3.7'", "version": "==2.0.3" + } + }, + "develop": { + "black": { + "hashes": [ + "sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5", + "sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915", + "sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326", + "sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940", + "sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b", + "sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30", + "sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c", + "sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c", + "sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab", + "sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27", + "sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2", + "sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961", + "sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9", + "sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb", + "sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70", + "sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331", + "sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2", + "sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266", + "sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d", + "sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6", + "sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b", + "sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925", + "sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8", + "sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4", + "sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3" + ], + "index": "pypi", + "version": "==23.3.0" + }, + "click": { + "hashes": [ + "sha256:2739815aaa5d2c986a88f1e9230c55e17f0caad3d958a5e13ad0797c166db9e3", + "sha256:b97d0c74955da062a7d4ef92fadb583806a585b2ea81958a81bd72726cbb8e37" + ], + "markers": "python_version >= '3.7'", + "version": "==8.1.4" + }, + "iniconfig": { + "hashes": [ + "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", + "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, - "validate-email": { + "mypy": { "hashes": [ - "sha256:784719dc5f780be319cdd185dc85dd93afebdb6ebb943811bc4c7c5f9c72aeaf" + "sha256:01fd2e9f85622d981fd9063bfaef1aed6e336eaacca00892cd2d82801ab7c042", + "sha256:0dde1d180cd84f0624c5dcaaa89c89775550a675aff96b5848de78fb11adabcd", + "sha256:141dedfdbfe8a04142881ff30ce6e6653c9685b354876b12e4fe6c78598b45e2", + "sha256:16f0db5b641ba159eff72cff08edc3875f2b62b2fa2bc24f68c1e7a4e8232d01", + "sha256:190b6bab0302cec4e9e6767d3eb66085aef2a1cc98fe04936d8a42ed2ba77bb7", + "sha256:2460a58faeea905aeb1b9b36f5065f2dc9a9c6e4c992a6499a2360c6c74ceca3", + "sha256:34a9239d5b3502c17f07fd7c0b2ae6b7dd7d7f6af35fbb5072c6208e76295816", + "sha256:43b592511672017f5b1a483527fd2684347fdffc041c9ef53428c8dc530f79a3", + "sha256:43d24f6437925ce50139a310a64b2ab048cb2d3694c84c71c3f2a1626d8101dc", + "sha256:45d32cec14e7b97af848bddd97d85ea4f0db4d5a149ed9676caa4eb2f7402bb4", + "sha256:470c969bb3f9a9efcedbadcd19a74ffb34a25f8e6b0e02dae7c0e71f8372f97b", + "sha256:566e72b0cd6598503e48ea610e0052d1b8168e60a46e0bfd34b3acf2d57f96a8", + "sha256:5703097c4936bbb9e9bce41478c8d08edd2865e177dc4c52be759f81ee4dd26c", + "sha256:7549fbf655e5825d787bbc9ecf6028731973f78088fbca3a1f4145c39ef09462", + "sha256:8207b7105829eca6f3d774f64a904190bb2231de91b8b186d21ffd98005f14a7", + "sha256:8c4d8e89aa7de683e2056a581ce63c46a0c41e31bd2b6d34144e2c80f5ea53dc", + "sha256:98324ec3ecf12296e6422939e54763faedbfcc502ea4a4c38502082711867258", + "sha256:9bbcd9ab8ea1f2e1c8031c21445b511442cc45c89951e49bbf852cbb70755b1b", + "sha256:9d40652cc4fe33871ad3338581dca3297ff5f2213d0df345bcfbde5162abf0c9", + "sha256:a2746d69a8196698146a3dbe29104f9eb6a2a4d8a27878d92169a6c0b74435b6", + "sha256:ae704dcfaa180ff7c4cfbad23e74321a2b774f92ca77fd94ce1049175a21c97f", + "sha256:bfdca17c36ae01a21274a3c387a63aa1aafe72bff976522886869ef131b937f1", + "sha256:c482e1246726616088532b5e964e39765b6d1520791348e6c9dc3af25b233828", + "sha256:ca637024ca67ab24a7fd6f65d280572c3794665eaf5edcc7e90a866544076878", + "sha256:e02d700ec8d9b1859790c0475df4e4092c7bf3272a4fd2c9f33d87fac4427b8f", + "sha256:e5952d2d18b79f7dc25e62e014fe5a23eb1a3d2bc66318df8988a01b1a037c5b" ], "index": "pypi", - "version": "==1.3" + "version": "==1.4.1" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, + "packaging": { + "hashes": [ + "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61", + "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f" + ], + "markers": "python_version >= '3.7'", + "version": "==23.1" + }, + "pathspec": { + "hashes": [ + "sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687", + "sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293" + ], + "markers": "python_version >= '3.7'", + "version": "==0.11.1" + }, + "platformdirs": { + "hashes": [ + "sha256:cec7b889196b9144d088e4c57d9ceef7374f6c39694ad1577a0aab50d27ea28c", + "sha256:f87ca4fcff7d2b0f81c6a748a77973d7af0f4d526f98f308477c3c436c74d528" + ], + "markers": "python_version >= '3.7'", + "version": "==3.8.1" + }, + "pluggy": { + "hashes": [ + "sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849", + "sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3" + ], + "markers": "python_version >= '3.7'", + "version": "==1.2.0" + }, + "pytest": { + "hashes": [ + "sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32", + "sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a" + ], + "index": "pypi", + "version": "==7.4.0" + }, + "ruff": { + "hashes": [ + "sha256:14a7b2f00f149c5a295f188a643ac25226ff8a4d08f7a62b1d4b0a1dc9f9b85c", + "sha256:2d4444c60f2e705c14cd802b55cd2b561d25bf4311702c463a002392d3116b22", + "sha256:2dab13cdedbf3af6d4427c07f47143746b6b95d9e4a254ac369a0edb9280a0d2", + "sha256:323b674c98078be9aaded5b8b51c0d9c424486566fb6ec18439b496ce79e5998", + "sha256:3250b24333ef419b7a232080d9724ccc4d2da1dbbe4ce85c4caa2290d83200f8", + "sha256:3a43fbe026ca1a2a8c45aa0d600a0116bec4dfa6f8bf0c3b871ecda51ef2b5dd", + "sha256:3e60605e07482183ba1c1b7237eca827bd6cbd3535fe8a4ede28cbe2a323cb97", + "sha256:468bfb0a7567443cec3d03cf408d6f562b52f30c3c29df19927f1e0e13a40cd7", + "sha256:479864a3ccd8a6a20a37a6e7577bdc2406868ee80b1e65605478ad3b8eb2ba0b", + "sha256:6fe81732f788894a00f6ade1fe69e996cc9e485b7c35b0f53fb00284397284b2", + "sha256:734165ea8feb81b0d53e3bf523adc2413fdb76f1264cde99555161dd5a725522", + "sha256:74e4b206cb24f2e98a615f87dbe0bde18105217cbcc8eb785bb05a644855ba50", + "sha256:7baa97c3d7186e5ed4d5d4f6834d759a27e56cf7d5874b98c507335f0ad5aadb", + "sha256:88d0f2afb2e0c26ac1120e7061ddda2a566196ec4007bd66d558f13b374b9efc", + "sha256:a9879f59f763cc5628aa01c31ad256a0f4dc61a29355c7315b83c2a5aac932b5", + "sha256:f32ec416c24542ca2f9cc8c8b65b84560530d338aaf247a4a78e74b99cd476b4", + "sha256:f612e0a14b3d145d90eb6ead990064e22f6f27281d847237560b4e10bf2251f3" + ], + "index": "pypi", + "version": "==0.0.277" + }, + "types-protobuf": { + "hashes": [ + "sha256:7bd5ea122a057b11a82b785d9de464932a1e9175fe977a4128adef11d7f35547", + "sha256:c926104f69ea62103846681b35b690d8d100ecf86c6cdda16c850a1313a272e4" + ], + "index": "pypi", + "version": "==4.23.0.1" + }, + "typing-extensions": { + "hashes": [ + "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36", + "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2" + ], + "markers": "python_version >= '3.7'", + "version": "==4.7.1" } - }, - "develop": {} + } } diff --git a/protovalidate/internal/__init__.py b/protovalidate/internal/__init__.py index b71b700..29a76bb 100644 --- a/protovalidate/internal/__init__.py +++ b/protovalidate/internal/__init__.py @@ -11,4 +11,3 @@ # 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/protovalidate/internal/constraints.py b/protovalidate/internal/constraints.py index f25f18f..157fd82 100644 --- a/protovalidate/internal/constraints.py +++ b/protovalidate/internal/constraints.py @@ -16,12 +16,11 @@ import typing import celpy # type: ignore -from buf.validate import expression_pb2 # type: ignore -from buf.validate import validate_pb2 # type: ignore -from buf.validate.priv import private_pb2 # type: ignore from celpy import celtypes # type: ignore from google.protobuf import any_pb2, descriptor, message +from buf.validate import expression_pb2, validate_pb2 # type: ignore +from buf.validate.priv import private_pb2 # type: ignore from protovalidate.internal import string_format @@ -45,7 +44,7 @@ def make_timestamp(msg: message.Message) -> celtypes.TimestampType: def unwrap(msg: message.Message) -> celtypes.Value: - return _FieldToCel(msg, msg.DESCRIPTOR.fields_by_name["value"]) + return _field_to_cel(msg, msg.DESCRIPTOR.fields_by_name["value"]) _MSG_TYPE_URL_TO_CTOR = { @@ -63,7 +62,7 @@ def unwrap(msg: message.Message) -> celtypes.Value: } -def _MsgToCel(msg: message.Message) -> dict[str, celtypes.Value]: +def _msg_to_cel(msg: message.Message) -> dict[str, celtypes.Value]: ctor = _MSG_TYPE_URL_TO_CTOR.get(msg.DESCRIPTOR.full_name) if ctor is not None: return ctor(msg) @@ -72,12 +71,12 @@ def _MsgToCel(msg: message.Message) -> dict[str, celtypes.Value]: for field in msg.DESCRIPTOR.fields: if field.containing_oneof is not None and not msg.HasField(field.name): continue - result[field.name] = _FieldToCel(msg, field) + result[field.name] = _field_to_cel(msg, field) return result _TYPE_TO_CTOR = { - descriptor.FieldDescriptor.TYPE_MESSAGE: _MsgToCel, + descriptor.FieldDescriptor.TYPE_MESSAGE: _msg_to_cel, descriptor.FieldDescriptor.TYPE_ENUM: celtypes.IntType, descriptor.FieldDescriptor.TYPE_BOOL: celtypes.BoolType, descriptor.FieldDescriptor.TYPE_BYTES: celtypes.BytesType, @@ -97,26 +96,23 @@ def _MsgToCel(msg: message.Message) -> dict[str, celtypes.Value]: } -def _ScalarFieldValueToCel( - val: typing.Any, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _scalar_field_value_to_cel(val: typing.Any, field: descriptor.FieldDescriptor) -> celtypes.Value: ctor = _TYPE_TO_CTOR.get(field.type) if ctor is None: - raise CompilationError("unknown field type") + msg = "unknown field type" + raise CompilationError(msg) return ctor(val) -def _FieldValueToCel( - val: typing.Any, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _field_value_to_cel(val: typing.Any, field: descriptor.FieldDescriptor) -> celtypes.Value: if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: if field.message_type is not None and field.message_type.GetOptions().map_entry: - return _MapFieldValueToCel(val, field) - return _RepeatedFieldValueToCel(val, field) - return _ScalarFieldValueToCel(val, field) + return _map_field_value_to_cel(val, field) + return _repeated_field_value_to_cel(val, field) + return _scalar_field_value_to_cel(val, field) -def _IsEmptyField(msg: message.Message, field: descriptor.FieldDescriptor) -> bool: +def _is_empty_field(msg: message.Message, field: descriptor.FieldDescriptor) -> bool: if field.containing_oneof is not None and not msg.HasField(field.name): return True if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: @@ -155,60 +151,49 @@ def _IsEmptyField(msg: message.Message, field: descriptor.FieldDescriptor) -> bo return getattr(msg, field.name) == 0 if field.type == descriptor.FieldDescriptor.TYPE_ENUM: return getattr(msg, field.name) == 0 - raise ValueError("unknown field type") + exception_msg = "unknown field type" + raise ValueError(exception_msg) -def _RepeatedFieldToCel( - msg: message.Message, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _repeated_field_to_cel(msg: message.Message, field: descriptor.FieldDescriptor) -> celtypes.Value: if field.message_type is not None and field.message_type.GetOptions().map_entry: - return _MapFieldToCel(msg, field) - return _RepeatedFieldValueToCel(getattr(msg, field.name), field) + return _map_field_to_cel(msg, field) + return _repeated_field_value_to_cel(getattr(msg, field.name), field) -def _RepeatedFieldValueToCel( - val: typing.Any, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _repeated_field_value_to_cel(val: typing.Any, field: descriptor.FieldDescriptor) -> celtypes.Value: result = celtypes.ListType() for item in val: - result.append(_ScalarFieldValueToCel(item, field)) + result.append(_scalar_field_value_to_cel(item, field)) return result -def _MapFieldValueToCel( - map: typing.Any, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _map_field_value_to_cel(mapping: typing.Any, field: descriptor.FieldDescriptor) -> celtypes.Value: result = celtypes.MapType() key_field = field.message_type.fields[0] val_field = field.message_type.fields[1] - for key, val in map.items(): - result[_FieldValueToCel(key, key_field)] = _FieldValueToCel(val, val_field) + for key, val in mapping.items(): + result[_field_value_to_cel(key, key_field)] = _field_value_to_cel(val, val_field) return result -def _MapFieldToCel( - msg: message.Message, field: descriptor.FieldDescriptor -) -> celtypes.Value: - return _MapFieldValueToCel(getattr(msg, field.name), field) +def _map_field_to_cel(msg: message.Message, field: descriptor.FieldDescriptor) -> celtypes.Value: + return _map_field_value_to_cel(getattr(msg, field.name), field) -def _FieldToCel( - msg: message.Message, field: descriptor.FieldDescriptor -) -> celtypes.Value: +def _field_to_cel(msg: message.Message, field: descriptor.FieldDescriptor) -> celtypes.Value: if field.label == descriptor.FieldDescriptor.LABEL_REPEATED: - return _RepeatedFieldToCel(msg, field) + return _repeated_field_to_cel(msg, field) elif field.message_type is not None and not msg.HasField(field.name): return None else: - return _ScalarFieldValueToCel(getattr(msg, field.name), field) + return _scalar_field_value_to_cel(getattr(msg, field.name), field) class ConstraintContext: """The state associated with a single constraint evaluation.""" - def __init__( - self, fail_fast: bool = False, violations: expression_pb2.Violations = None - ): + def __init__(self, fail_fast: bool = False, violations: expression_pb2.Violations = None): # noqa: FBT001, FBT002 self._fail_fast = fail_fast if violations is None: violations = expression_pb2.Violations() @@ -255,7 +240,7 @@ def sub_context(self): class ConstraintRules: """The constrains associated with a single 'rules' message.""" - def validate(self, ctx: ConstraintContext, message: message.Message): + def validate(self, ctx: ConstraintContext, message: message.Message): # noqa: ARG002 """Validate the message against the rules in this constraint.""" ctx.add("", "unimplemented", "Unimplemented") @@ -263,23 +248,17 @@ def validate(self, ctx: ConstraintContext, message: message.Message): class CelConstraintRules(ConstraintRules): """A constraint that has rules written in CEL.""" - _runners: list[ - typing.Tuple[celpy.Runner, expression_pb2.Constraint | private_pb2.Constraint] - ] + _runners: list[typing.Tuple[celpy.Runner, expression_pb2.Constraint | private_pb2.Constraint]] _rules_cel: celtypes.Value = None def __init__(self, rules: message.Message | None): self._runners = [] if rules is not None: - self._rules_cel = _MsgToCel(rules) + self._rules_cel = _msg_to_cel(rules) - def _validate_cel( - self, ctx: ConstraintContext, field_name: str, activation: dict[str, typing.Any] - ): + def _validate_cel(self, ctx: ConstraintContext, field_name: str, activation: dict[str, typing.Any]): activation["rules"] = self._rules_cel - activation["now"] = celtypes.TimestampType( - datetime.datetime.now(tz=datetime.timezone.utc) - ) + activation["now"] = celtypes.TimestampType(datetime.datetime.now(tz=datetime.timezone.utc)) for runner, constraint in self._runners: result = runner.evaluate(activation) if isinstance(result, celtypes.BoolType): @@ -306,19 +285,15 @@ class MessageConstraintRules(CelConstraintRules): """Message-level rules.""" def validate(self, ctx: ConstraintContext, message: message.Message): - self._validate_cel(ctx, "", {"this": _MsgToCel(message)}) + self._validate_cel(ctx, "", {"this": _msg_to_cel(message)}) -def check_field_type( - field: descriptor.FieldDescriptor, expected: int, wrapper_name: str | None = None -): +def check_field_type(field: descriptor.FieldDescriptor, expected: int, wrapper_name: str | None = None): if field.type != expected and ( - field.type != descriptor.FieldDescriptor.TYPE_MESSAGE - or field.message_type.full_name != wrapper_name + field.type != descriptor.FieldDescriptor.TYPE_MESSAGE or field.message_type.full_name != wrapper_name ): - raise CompilationError( - f"field {field.name} has type {field.type} but expected {expected}" - ) + msg = f"field {field.name} has type {field.type} but expected {expected}" + raise CompilationError(msg) class FieldConstraintRules(CelConstraintRules): @@ -332,29 +307,29 @@ def __init__( env: celpy.Environment, funcs: dict[str, celpy.CELFunction], field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.FieldConstraints, + field_level: validate_pb2.FieldConstraints, ): - type_case = fieldLvl.WhichOneof("type") - super().__init__(None if type_case is None else getattr(fieldLvl, type_case)) + type_case = field_level.WhichOneof("type") + super().__init__(None if type_case is None else getattr(field_level, type_case)) self._field = field - if fieldLvl.ignore_empty: + if field_level.ignore_empty: self._ignore_empty = True - if fieldLvl.required: + if field_level.required: self._required = True - type_case = fieldLvl.WhichOneof("type") + type_case = field_level.WhichOneof("type") if type_case is not None: - rules = getattr(fieldLvl, type_case) + rules = getattr(field_level, type_case) # For each set field in the message, look for the private constraint # extension. for field, _ in rules.ListFields(): if private_pb2.field in field.GetOptions().Extensions: for cel in field.GetOptions().Extensions[private_pb2.field].cel: self.add_rule(env, funcs, cel) - for cel in fieldLvl.cel: + for cel in field_level.cel: self.add_rule(env, funcs, cel) def validate(self, ctx: ConstraintContext, message: message.Message): - if _IsEmptyField(message, self._field): + if _is_empty_field(message, self._field): if self._required: ctx.add( self._field.name, @@ -373,15 +348,11 @@ def validate(self, ctx: ConstraintContext, message: message.Message): return val = getattr(message, self._field.name) self._validate_value(ctx, self._field.name, val) - self._validate_cel( - ctx, self._field.name, {"this": _FieldValueToCel(val, self._field)} - ) + self._validate_cel(ctx, self._field.name, {"this": _field_value_to_cel(val, self._field)}) def validate_item(self, ctx: ConstraintContext, field_path: str, val: typing.Any): self._validate_value(ctx, field_path, val) - self._validate_cel( - ctx, field_path, {"this": _ScalarFieldValueToCel(val, self._field)} - ) + self._validate_cel(ctx, field_path, {"this": _scalar_field_value_to_cel(val, self._field)}) def _validate_value(self, ctx: ConstraintContext, field_path: str, val: typing.Any): pass @@ -390,25 +361,23 @@ def _validate_value(self, ctx: ConstraintContext, field_path: str, val: typing.A class AnyConstraintRules(FieldConstraintRules): """Rules for an Any field.""" - _in: typing.List[str] = [] - _not_in: typing.List[str] = [] + _in: typing.List[str] = [] # noqa: RUF012 + _not_in: typing.List[str] = [] # noqa: RUF012 def __init__( self, env: celpy.Environment, funcs: dict[str, celpy.CELFunction], field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.FieldConstraints, - ): - super().__init__(env, funcs, field, fieldLvl) - if getattr(fieldLvl.any, "in"): - self._in = getattr(fieldLvl.any, "in") - if fieldLvl.any.not_in: - self._not_in = fieldLvl.any.not_in - - def _validate_value( - self, ctx: ConstraintContext, field_path: str, value: any_pb2.Any + field_level: validate_pb2.FieldConstraints, ): + super().__init__(env, funcs, field, field_level) + if getattr(field_level.any, "in"): + self._in = getattr(field_level.any, "in") + if field_level.any.not_in: + self._not_in = field_level.any.not_in + + def _validate_value(self, ctx: ConstraintContext, field_path: str, value: any_pb2.Any): if len(self._in) > 0: if value.type_url not in self._in: ctx.add( @@ -434,10 +403,10 @@ def __init__( env: celpy.Environment, funcs: dict[str, celpy.CELFunction], field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.FieldConstraints, + field_level: validate_pb2.FieldConstraints, ): - super().__init__(env, funcs, field, fieldLvl) - if fieldLvl.enum.defined_only: + super().__init__(env, funcs, field, field_level) + if field_level.enum.defined_only: self._defined_only = True def validate(self, ctx: ConstraintContext, message: message.Message): @@ -464,10 +433,10 @@ def __init__( env: celpy.Environment, funcs: dict[str, celpy.CELFunction], field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.FieldConstraints, + field_level: validate_pb2.FieldConstraints, item_rules: FieldConstraintRules | None, ): - super().__init__(env, funcs, field, fieldLvl) + super().__init__(env, funcs, field, field_level) if item_rules is not None: self._item_rules = item_rules @@ -498,11 +467,11 @@ def __init__( env: celpy.Environment, funcs: dict[str, celpy.CELFunction], field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.FieldConstraints, + field_level: validate_pb2.FieldConstraints, key_rules: FieldConstraintRules | None, value_rules: FieldConstraintRules | None, ): - super().__init__(env, funcs, field, fieldLvl) + super().__init__(env, funcs, field, field_level) if key_rules is not None: self._key_rules = key_rules if value_rules is not None: @@ -513,12 +482,12 @@ def validate(self, ctx: ConstraintContext, message: message.Message): if ctx.done: return value = getattr(message, self._field.name) - for key, value in value.items(): - key_field_path = make_key_path(self._field.name, key) + for k, v in value.items(): + key_field_path = make_key_path(self._field.name, k) if self._key_rules is not None: - self._key_rules.validate_item(ctx, key_field_path, key) + self._key_rules.validate_item(ctx, key_field_path, k) if self._value_rules is not None: - self._value_rules.validate_item(ctx, key_field_path, value) + self._value_rules.validate_item(ctx, key_field_path, v) class OneofConstraintRules(ConstraintRules): @@ -526,9 +495,7 @@ class OneofConstraintRules(ConstraintRules): required = True - def __init__( - self, oneof: descriptor.OneofDescriptor, rules: validate_pb2.OneofConstraints - ): + def __init__(self, oneof: descriptor.OneofDescriptor, rules: validate_pb2.OneofConstraints): self._oneof = oneof if not rules.required: self.required = False @@ -567,9 +534,7 @@ def get(self, descriptor: descriptor.Descriptor) -> list[ConstraintRules]: raise result return result - def _new_message_constraint( - self, rules: validate_pb2.message - ) -> MessageConstraintRules: + def _new_message_constraint(self, rules: validate_pb2.message) -> MessageConstraintRules: result = MessageConstraintRules(rules) for cel in rules.cel: result.add_rule(self._env, self._funcs, cel) @@ -578,31 +543,29 @@ def _new_message_constraint( def _new_scalar_field_constraint( self, field: descriptor.FieldDescriptor, - fieldLvl: validate_pb2.field, + field_level: validate_pb2.field, ): - if fieldLvl.skipped: + if field_level.skipped: return None - type_case = fieldLvl.WhichOneof("type") + type_case = field_level.WhichOneof("type") if type_case is None: - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "duration": check_field_type(field, 0, "google.protobuf.Duration") - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "timestamp": check_field_type(field, 0, "google.protobuf.Timestamp") - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "enum": check_field_type(field, descriptor.FieldDescriptor.TYPE_ENUM) - result = EnumConstraintRules(self._env, self._funcs, field, fieldLvl) + result = EnumConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "bool": - check_field_type( - field, descriptor.FieldDescriptor.TYPE_BOOL, "google.protobuf.BoolValue" - ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + check_field_type(field, descriptor.FieldDescriptor.TYPE_BOOL, "google.protobuf.BoolValue") + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "bytes": check_field_type( @@ -610,15 +573,15 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_BYTES, "google.protobuf.BytesValue", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "fixed32": check_field_type(field, descriptor.FieldDescriptor.TYPE_FIXED32) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "fixed64": check_field_type(field, descriptor.FieldDescriptor.TYPE_FIXED64) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "float": check_field_type( @@ -626,7 +589,7 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_FLOAT, "google.protobuf.FloatValue", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "double": check_field_type( @@ -634,7 +597,7 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_DOUBLE, "google.protobuf.DoubleValue", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "int32": check_field_type( @@ -642,7 +605,7 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_INT32, "google.protobuf.Int32Value", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "int64": check_field_type( @@ -650,23 +613,23 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_INT64, "google.protobuf.Int64Value", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "sfixed32": check_field_type(field, descriptor.FieldDescriptor.TYPE_SFIXED32) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "sfixed64": check_field_type(field, descriptor.FieldDescriptor.TYPE_SFIXED64) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "sint32": check_field_type(field, descriptor.FieldDescriptor.TYPE_SINT32) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "sint64": check_field_type(field, descriptor.FieldDescriptor.TYPE_SINT64) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "uint32": check_field_type( @@ -674,7 +637,7 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_UINT32, "google.protobuf.UInt32Value", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "uint64": check_field_type( @@ -682,7 +645,7 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_UINT64, "google.protobuf.UInt64Value", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "string": check_field_type( @@ -690,13 +653,11 @@ def _new_scalar_field_constraint( descriptor.FieldDescriptor.TYPE_STRING, "google.protobuf.StringValue", ) - result = FieldConstraintRules(self._env, self._funcs, field, fieldLvl) + result = FieldConstraintRules(self._env, self._funcs, field, field_level) return result elif type_case == "any": - check_field_type( - field, descriptor.FieldDescriptor.TYPE_MESSAGE, "google.protobuf.Any" - ) - result = AnyConstraintRules(self._env, self._funcs, field, fieldLvl) + check_field_type(field, descriptor.FieldDescriptor.TYPE_MESSAGE, "google.protobuf.Any") + result = AnyConstraintRules(self._env, self._funcs, field, field_level) return result def _new_field_constraint( @@ -714,12 +675,8 @@ def _new_field_constraint( value_rules = None if rules.map.HasField("values"): value_field = field.message_type.fields_by_name["value"] - value_rules = self._new_scalar_field_constraint( - value_field, rules.map.values - ) - return MapConstraintRules( - self._env, self._funcs, field, rules, key_rules, value_rules - ) + value_rules = self._new_scalar_field_constraint(value_field, rules.map.values) + return MapConstraintRules(self._env, self._funcs, field, rules, key_rules, value_rules) item_rule = None if rules.repeated.HasField("items"): item_rule = self._new_scalar_field_constraint(field, rules.repeated.items) @@ -729,26 +686,24 @@ def _new_constraints(self, desc: descriptor.Descriptor) -> list[ConstraintRules] result: list[ConstraintRules] = [] constraint: ConstraintRules | None = None if validate_pb2.message in desc.GetOptions().Extensions: - msgLvl = desc.GetOptions().Extensions[validate_pb2.message] - if msgLvl.disabled: + message_level = desc.GetOptions().Extensions[validate_pb2.message] + if message_level.disabled: return [] - if constraint := self._new_message_constraint(msgLvl): + if constraint := self._new_message_constraint(message_level): result.append(constraint) for oneof in desc.oneofs: if validate_pb2.oneof in oneof.GetOptions().Extensions: - if constraint := OneofConstraintRules( - oneof, oneof.GetOptions().Extensions[validate_pb2.oneof] - ): + if constraint := OneofConstraintRules(oneof, oneof.GetOptions().Extensions[validate_pb2.oneof]): result.append(constraint) for field in desc.fields: if validate_pb2.field in field.GetOptions().Extensions: - fieldLvl = field.GetOptions().Extensions[validate_pb2.field] - if fieldLvl.skipped: + field_level = field.GetOptions().Extensions[validate_pb2.field] + if field_level.skipped: continue - result.append(self._new_field_constraint(field, fieldLvl)) - if fieldLvl.repeated.items.skipped: + result.append(self._new_field_constraint(field, field_level)) + if field_level.repeated.items.skipped: continue if field.message_type is None: continue @@ -806,12 +761,12 @@ def validate(self, ctx: ConstraintContext, message: message.Message): constraints = self._factory.get(self._value_field.message_type) if constraints is None: return - for key, val in val.items(): + for k, v in val.items(): sub_ctx = ctx.sub_context() for constraint in constraints: - constraint.validate(sub_ctx, val) + constraint.validate(sub_ctx, v) if sub_ctx.has_errors(): - sub_ctx.add_path_prefix(f"{self._field.name}[{key}]") + sub_ctx.add_path_prefix(f"{self._field.name}[{k}]") ctx.add_errors(sub_ctx) @@ -831,10 +786,10 @@ def validate(self, ctx: ConstraintContext, message: message.Message): constraints = self._factory.get(self._field.message_type) if constraints is None: return - for idx, val in enumerate(val): + for idx, item in enumerate(val): sub_ctx = ctx.sub_context() for constraint in constraints: - constraint.validate(sub_ctx, val) + constraint.validate(sub_ctx, item) if sub_ctx.has_errors(): sub_ctx.add_path_prefix(f"{self._field.name}[{idx}]") ctx.add_errors(sub_ctx) diff --git a/protovalidate/internal/extra_func.py b/protovalidate/internal/extra_func.py index 2f16636..9902c6c 100644 --- a/protovalidate/internal/extra_func.py +++ b/protovalidate/internal/extra_func.py @@ -21,7 +21,7 @@ from protovalidate.internal import string_format -def _validateHostName(host): +def _validate_hostname(host): if not host: return False if len(host) > 253: @@ -38,17 +38,12 @@ def _validateHostName(host): if part[0] == "-" or part[-1] == "-": return False for r in part: - if ( - (r < "A" or r > "Z") - and (r < "a" or r > "z") - and (r < "0" or r > "9") - and r != "-" - ): + if (r < "A" or r > "Z") and (r < "a" or r > "z") and (r < "0" or r > "9") and r != "-": return False return True -def _validateEmail(addr): +def validate_email(addr): if "<" in addr and ">" in addr: addr = addr.split("<")[1].split(">")[0] @@ -60,12 +55,13 @@ def _validateEmail(addr): return False if len(parts[0]) > 64: return False - return _validateHostName(parts[1]) + return _validate_hostname(parts[1]) def is_ip(val: celtypes.Value, version: celtypes.Value | None = None) -> celpy.Result: if not isinstance(val, (celtypes.BytesType, celtypes.StringType)): - raise celpy.EvalError("invalid argument, expected string or bytes") + msg = "invalid argument, expected string or bytes" + raise celpy.EvalError(msg) try: if version is None: ip_address(val) @@ -74,7 +70,8 @@ def is_ip(val: celtypes.Value, version: celtypes.Value | None = None) -> celpy.R elif version == 6: IPv6Address(val) else: - raise celpy.EvalError("invalid argument, expected 4 or 6") + msg = "invalid argument, expected 4 or 6" + raise celpy.EvalError(msg) return celtypes.BoolType(True) except ValueError: return celtypes.BoolType(False) @@ -82,8 +79,9 @@ def is_ip(val: celtypes.Value, version: celtypes.Value | None = None) -> celpy.R def is_email(string: celtypes.Value) -> celpy.Result: if not isinstance(string, celtypes.StringType): - raise celpy.EvalError("invalid argument, expected string") - return celtypes.BoolType(_validateEmail(string)) + msg = "invalid argument, expected string" + raise celpy.EvalError(msg) + return celtypes.BoolType(validate_email(string)) def is_uri(string: celtypes.Value) -> celpy.Result: @@ -102,13 +100,15 @@ def is_uri_ref(string: celtypes.Value) -> celpy.Result: def is_hostname(string: celtypes.Value) -> celpy.Result: if not isinstance(string, celtypes.StringType): - raise celpy.EvalError("invalid argument, expected string") - return celtypes.BoolType(_validateHostName(string)) + msg = "invalid argument, expected string" + raise celpy.EvalError(msg) + return celtypes.BoolType(_validate_hostname(string)) def unique(val: celtypes.Value) -> celpy.Result: if not isinstance(val, celtypes.ListType): - raise celpy.EvalError("invalid argument, expected list") + msg = "invalid argument, expected list" + raise celpy.EvalError(msg) return celtypes.BoolType(len(val) == len(set(val))) diff --git a/protovalidate/internal/string_format.py b/protovalidate/internal/string_format.py index 42c4fe9..bed78f5 100644 --- a/protovalidate/internal/string_format.py +++ b/protovalidate/internal/string_format.py @@ -40,15 +40,11 @@ class StringFormat: def __init__(self, locale: str): self.locale = locale - def format(self, fmt: celtypes.Value, args: celtypes.Value) -> celpy.Result: + def format(self, fmt: celtypes.Value, args: celtypes.Value) -> celpy.Result: # noqa: A003 if not isinstance(fmt, celtypes.StringType): - return celpy.native_to_cel( - celpy.new_error("format() requires a string as the first argument") - ) + return celpy.native_to_cel(celpy.new_error("format() requires a string as the first argument")) if not isinstance(args, celtypes.ListType): - return celpy.native_to_cel( - celpy.new_error("format() requires a list as the second argument") - ) + return celpy.native_to_cel(celpy.new_error("format() requires a list as the second argument")) # printf style formatting i = 0 j = 0 @@ -64,9 +60,7 @@ def format(self, fmt: celtypes.Value, args: celtypes.Value) -> celpy.Result: i += 2 continue if j >= len(args): - return celpy.CELEvalError( - "format() not enough arguments for format string" - ) + return celpy.CELEvalError("format() not enough arguments for format string") arg = args[j] j += 1 i += 1 @@ -98,9 +92,7 @@ def format(self, fmt: celtypes.Value, args: celtypes.Value) -> celpy.Result: elif fmt[i] == "b": result += self.format_bin(arg) else: - return celpy.CELEvalError( - "format() unknown format specifier: " + fmt[i] - ) + return celpy.CELEvalError("format() unknown format specifier: " + fmt[i]) i += 1 if j < len(args): return celpy.CELEvalError("format() too many arguments for format string") @@ -132,9 +124,7 @@ def format_hex(self, arg: celtypes.Value) -> celpy.Result: return celtypes.StringType(arg.hex()) if isinstance(arg, celtypes.StringType): return celtypes.StringType(arg.encode("utf-8").hex()) - return celpy.CELEvalError( - "format_hex() requires an integer, string, or binary argument" - ) + return celpy.CELEvalError("format_hex() requires an integer, string, or binary argument") def format_oct(self, arg: celtypes.Value) -> celpy.Result: if isinstance(arg, celtypes.IntType): @@ -179,5 +169,5 @@ def format_list(self, arg: celtypes.ListType) -> celpy.Result: _default_format = StringFormat("en_US") -format = _default_format.format +format = _default_format.format # noqa: A001 format_value = _default_format.format_value diff --git a/protovalidate/validator.py b/protovalidate/validator.py index 2349a9a..d4eca96 100644 --- a/protovalidate/validator.py +++ b/protovalidate/validator.py @@ -12,9 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -from buf.validate import expression_pb2 # type: ignore from google.protobuf import message +from buf.validate import expression_pb2 # type: ignore from protovalidate.internal import constraints as _constraints from protovalidate.internal import extra_func @@ -39,7 +39,7 @@ def __init__(self): def validate( self, message: message.Message, - fail_fast: bool = False, + fail_fast: bool = False, # noqa: FBT001, FBT002 result: expression_pb2.Violations = None, ) -> expression_pb2.Violations: """ diff --git a/pyproject.toml b/pyproject.toml index 93d1727..c2b327c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,9 +4,10 @@ build-backend = "hatchling.build" [project] name = "protovalidate" -description = "Protocol Buffer Validation for python" +description = "Protocol Buffer Validation for Python" readme = "README.md" license = { file = "LICENSE" } +keywords = ["validate", "protobuf", "protocol buffer"] requires-python = ">=3.7" classifiers = [ "Programming Language :: Python :: 3", @@ -15,9 +16,69 @@ classifiers = [ ] dynamic = ["version"] dependencies = ["protobuf", "cel-python"] + +[project.urls] +Homepage = "https://github.com/bufbuild/protovalidate-python" +Source = "https://github.com/bufbuild/protovalidate-python" +Issues = "https://github.com/bufbuild/protovalidate-python/issues" + [tool.hatch.version] source = "vcs" -[project.urls] -"Homepage" = "https://github.com/bufbuild/protovalidate-python" -"Bug Tracker" = "https://github.com/bufbuild/protovalidate-python/issues" +[tool.black] +target-version = ["py37"] +line-length = 120 + +[tool.ruff] +target-version = "py37" +line-length = 120 +select = [ + "A", + "ARG", + "B", + "C", + "DTZ", + "E", + "EM", + "F", + "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "T", + "TID", + "UP", + "W", + "YTT", +] +ignore = [ + # Allow boolean positional values in function calls, like `dict.get(..., True)`. + "FBT003", + # Ignore complexity + "C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915", + # Ignore magic values - in this library, most are obvious in context. + "PLR2004", +] +unfixable = [ + # Don't autofix unused imports. + "F401", +] + +[tool.ruff.isort] +known-first-party = ["protovalidate", "buf"] + +[tool.ruff.flake8-tidy-imports] +ban-relative-imports = "all" + +[tool.ruff.per-file-ignores] +# Tests can use magic values, assertions, and relative imports. +"tests/**/*" = ["PLR2004", "S101", "TID252"] + diff --git a/tests/__init__.py b/tests/__init__.py index b71b700..29a76bb 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,4 +11,3 @@ # 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/tests/conformance/__init__.py b/tests/conformance/__init__.py index b71b700..29a76bb 100644 --- a/tests/conformance/__init__.py +++ b/tests/conformance/__init__.py @@ -11,4 +11,3 @@ # 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/tests/conformance/runner.py b/tests/conformance/runner.py index 6ca48be..2a6163e 100644 --- a/tests/conformance/runner.py +++ b/tests/conformance/runner.py @@ -16,38 +16,36 @@ import typing import celpy # type: ignore +from google.protobuf import any_pb2, descriptor, descriptor_pool, message_factory + +import protovalidate # TODO(afuller): Use dynamic descriptor pool based on the FileDescriptorSet # in the TestConformanceRequest, once the Python protobuf library no longer # segfaults when using a dynamic descriptor pool. from buf.validate.conformance.cases import ( - bool_pb2, - bytes_pb2, - enums_pb2, - filename_with_dash_pb2, - kitchen_sink_pb2, - maps_pb2, - messages_pb2, - numbers_pb2, - oneofs_pb2, - repeated_pb2, - strings_pb2, - wkt_any_pb2, - wkt_duration_pb2, - wkt_nested_pb2, - wkt_timestamp_pb2, - wkt_wrappers_pb2, + bool_pb2, # noqa: F401 + bytes_pb2, # noqa: F401 + enums_pb2, # noqa: F401 + filename_with_dash_pb2, # noqa: F401 + kitchen_sink_pb2, # noqa: F401 + maps_pb2, # noqa: F401 + messages_pb2, # noqa: F401 + numbers_pb2, # noqa: F401 + oneofs_pb2, # noqa: F401 + repeated_pb2, # noqa: F401 + strings_pb2, # noqa: F401 + wkt_any_pb2, # noqa: F401 + wkt_duration_pb2, # noqa: F401 + wkt_nested_pb2, # noqa: F401 + wkt_timestamp_pb2, # noqa: F401 + wkt_wrappers_pb2, # noqa: F401 ) -from buf.validate.conformance.cases.custom_constraints import custom_constraints_pb2 +from buf.validate.conformance.cases.custom_constraints import custom_constraints_pb2 # noqa: F401 from buf.validate.conformance.harness import harness_pb2 -from google.protobuf import any_pb2, descriptor, descriptor_pool, message_factory - -import protovalidate -def run_test_case( - tc: typing.Any, result: harness_pb2.TestResult | None = None -) -> harness_pb2.TestResult: +def run_test_case(tc: typing.Any, result: harness_pb2.TestResult | None = None) -> harness_pb2.TestResult: if result is None: result = harness_pb2.TestResult() # Run the validator diff --git a/tests/runner_test.py b/tests/runner_test.py index 9b190d9..588a1e9 100644 --- a/tests/runner_test.py +++ b/tests/runner_test.py @@ -12,29 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +from google.protobuf import descriptor_pool -from buf.validate.conformance.cases import oneofs_pb2 +from buf.validate.conformance.cases import oneofs_pb2 # noqa: F401 from buf.validate.conformance.harness import results_pb2 -from google.protobuf import descriptor_pool, message_factory - -import protovalidate from tests.conformance import runner -class RunnerTest(unittest.TestCase): - def test_oneof(self): - results = results_pb2.ResultSet() - # load the results from oneof.binproto - with open("tests/oneof.binproto", "rb") as f: - results.ParseFromString(f.read()) - for suite in results.suites: - pool = descriptor_pool.Default() - # for fd in suite.fdset.file: - # pool.Add(fd) - for result in suite.cases: - actual = runner.run_any_test_case(pool, result.input) - - -if __name__ == "__main__": - unittest.main() +def test_oneof(): + results = results_pb2.ResultSet() + # load the results from oneof.binproto + with open("tests/oneof.binproto", "rb") as f: + results.ParseFromString(f.read()) + for suite in results.suites: + pool = descriptor_pool.Default() + for result in suite.cases: + runner.run_any_test_case(pool, result.input) diff --git a/tests/validate_test.py b/tests/validate_test.py index d3f7ce0..9ab89d6 100644 --- a/tests/validate_test.py +++ b/tests/validate_test.py @@ -12,53 +12,42 @@ # See the License for the specific language governing permissions and # limitations under the License. -import unittest +import protovalidate +from buf.validate.conformance.cases import maps_pb2, numbers_pb2, oneofs_pb2, repeated_pb2, wkt_timestamp_pb2 -from buf.validate.conformance.cases import ( - maps_pb2, - numbers_pb2, - oneofs_pb2, - repeated_pb2, - wkt_timestamp_pb2, -) -import protovalidate +def test_sfixed64(): + msg = numbers_pb2.SFixed64ExLTGT(val=11) + violations = protovalidate.validate(msg) + assert len(violations.violations) == 0 -# Test basic validation -class TestValidate(unittest.TestCase): - def test_SFixed64ExLTGT(self): - msg = numbers_pb2.SFixed64ExLTGT(val=11) - violations = protovalidate.validate(msg) - self.assertEqual(len(violations.violations), 0) +def test_oneofs(): + msg1 = oneofs_pb2.Oneof() + msg1.y = 123 + violations = protovalidate.validate(msg1) + assert len(violations.violations) == 0 - def test_Oneofs(self): - msg1 = oneofs_pb2.Oneof() - msg1.y = 123 - violations = protovalidate.validate(msg1) - self.assertEqual(len(violations.violations), 0) + msg2 = oneofs_pb2.Oneof() + msg2.z.val = True + violations = protovalidate.validate(msg2) + assert len(violations.violations) == 0 - msg2 = oneofs_pb2.Oneof() - msg2.z.val = True - violations = protovalidate.validate(msg2) - self.assertEqual(len(violations.violations), 0) - def test_Repeated(self): - msg = repeated_pb2.RepeatedEmbedSkip() - msg.val.add(val=-1) - violations = protovalidate.validate(msg) - self.assertEqual(len(violations.violations), 0) +def test_repeated(): + msg = repeated_pb2.RepeatedEmbedSkip() + msg.val.add(val=-1) + violations = protovalidate.validate(msg) + assert len(violations.violations) == 0 - def test_Maps(self): - msg = maps_pb2.MapMinMax() - violations = protovalidate.validate(msg) - self.assertEqual(len(violations.violations), 1) - def test_Timestamp(self): - msg = wkt_timestamp_pb2.TimestampGTNow() - violations = protovalidate.validate(msg) - self.assertEqual(len(violations.violations), 0) +def test_maps(): + msg = maps_pb2.MapMinMax() + violations = protovalidate.validate(msg) + assert len(violations.violations) == 1 -if __name__ == "__main__": - unittest.main() +def test_timestamp(): + msg = wkt_timestamp_pb2.TimestampGTNow() + violations = protovalidate.validate(msg) + assert len(violations.violations) == 0