From 8d6876e743c836a70f358a6fa2566028a022abba Mon Sep 17 00:00:00 2001 From: Francisc Balint <7178547+franciscbalint@users.noreply.github.com> Date: Sat, 24 Oct 2026 01:52:55 +0400 Subject: [PATCH] docs: bring every doc in line with the code after the API parity work README / documentation.md - Security: where the key travels per API (query/body, Bearer header, Warmup API-Key header), https enforced on every base URL, redaction, key checks. - Errors: ZBClientException vs ZBApiException, status_code, unknown enum values. - Region note: file-based APIs always use bulkapi.zerobounce.net. - Email finder: first_name optional, use_post; stream=True on the other downloads. - Other hosts: BULK_ROOT_URL (S3/SFTP) and the https requirement. - guess_format example crashed (str + response object). - Development: install with [test] and the pinned ruff, lint and pytest commands, the live suite's real command; the old `python -m tests` output (contradictory counts) is gone. AGENTS.md: all offline suites, pinned ruff, every optional parameter, key placement per API, the https guard and regional warning, the redaction pattern. CONTRIBUTING.md: branch from and PR against master (not main), Conventional Commits, CHANGELOG entry, docs-only endpoints rule. SECURITY.md: current guidance (https enforced, Python 3.10+, query-string logs, separate Warmup key). CHANGELOG.md: the missing 2.2.4 section (CI-only release) and compare link. Dockerfile: runs every offline suite, not just zero_bounce_test_case.py. tests/__main__.py: runs every offline suite and no longer includes the live API suite, which called the real API whenever ZEROBOUNCE_API_KEY was set. pyproject.toml: keywords and Documentation / Changelog / Issues URLs. ZeroBounce class docstring: what it covers, and ZeroBounceWarmup for Warmup. Every Python example in README.md and documentation.md runs against the offline fakes without error. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 13 +++-- CHANGELOG.md | 5 ++ CONTRIBUTING.md | 16 +++--- Dockerfile | 4 +- README.md | 81 +++++++++++++----------------- SECURITY.md | 8 +-- documentation.md | 13 ++++- pyproject.toml | 5 +- src/zerobouncesdk/zerobouncesdk.py | 4 +- tests/__main__.py | 26 ++++++++-- 10 files changed, 104 insertions(+), 71 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 846ed3f..a9f20ac 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,7 +12,7 @@ Guidance for AI coding agents working in this repository. - `src/zerobouncesdk/zb_warmup.py`: `ZeroBounceWarmup`, the Warmup API client. It has its own host and key, authenticates with an `API-Key` header, and reuses the error helpers in `zerobouncesdk.py`. Keep Warmup methods there, not on `ZeroBounce`. - `src/zerobouncesdk/zb_*.py`: response models, enums, and options. - `tests/zero_bounce_test_case.py`: scripted unit tests. They patch `zerobouncesdk.zerobouncesdk.requests`. -- `tests/test_offline_edges.py` and `tests/test_warmup_offline.py`: more offline suites on the same principle (error bodies, redaction, every optional field; the Warmup API). +- `tests/test_offline_edges.py`, `tests/test_warmup_offline.py`, `tests/test_security_offline.py` and `tests/test_streaming_and_regions_offline.py`: more offline suites on the same principle (error bodies and every optional field; the Warmup API; where keys travel and that they never reach an exception; streamed downloads and the regional-host warning). - `tests/test_offline_api.py`: offline suite. It patches `requests.sessions.Session.send`, so the real `requests` client builds the URL and body and the fake answers without the network. - `tests/zero_bounce_integration_test.py`: live API tests. `pytest` does not collect this file. Run it with `python -m unittest tests.zero_bounce_integration_test` when `ZEROBOUNCE_API_KEY` is set. - `pyproject.toml`: the only version field, `version = "x.y.z"`. `setup.py` calls `setup()` and reads that file. @@ -21,10 +21,10 @@ Guidance for AI coding agents working in this repository. ```bash pip install -e ".[test]" -pip install ruff +pip install ruff==0.17.2 # the version CI pins ruff check src tests # undefined names and syntax errors only python -m compileall -q src tests -python -m pytest # unit tests and tests/test_offline_api.py +python -m pytest # every offline suite under tests/ ``` `ruff check` plus `compileall` is the lint step. There is no typechecker. The Ruff selection in `pyproject.toml` is `E9`, `F63`, `F7`, and `F82`. Do not widen it to a style ruleset that fails the existing tree. @@ -36,13 +36,16 @@ CI (`.github/workflows/sdk_ci.yml`) runs `ruff check`, `compileall`, then `pytho - **Only endpoints documented at https://www.zerobounce.net/docs/.** Do not add a method for an endpoint that the public docs do not describe, even if it exists on the server. Integration pages count: the S3 bulk API is documented at https://www.zerobounce.net/docs/api-integrations/amazon-s3-bulk-api. - **One `ZeroBounce` per API key.** The class is not a singleton. Do not reuse one instance across users or requests. The offline concurrency test constructs a separate client per key. - **Column indexes are 1-based, and the SDK does not add 1.** Pass the API's index. `0` is a real value and is sent. `None` on an optional column (such as `first_name_column`) omits the field. `send_file`'s email column is required and is sent as given, including `0`. `domain_column` and the list-evaluator email column are required: `None` is rejected, `0` is sent. The bulk email finder also needs `first_name_column` or `full_name_column`. -- **Optional parameters are omitted when unset.** `timeout`, `activity_data`, `verify_plus`, and `allow_phase_2` are not sent until the caller sets them. Explicit `False` is sent as `false`. `ip_address=None` is omitted; `""` is sent. -- **Booleans on form and multipart bodies must be the strings `true` and `false`.** A Python `bool` passed to `requests` becomes `True` or `False`, and the API will not treat that as a boolean. `_form_data` converts them. JSON bodies (`validate_batch`) keep JSON booleans. +- **Optional parameters are omitted when unset.** `timeout`, `activity_data`, `verify_plus`, `domain_info`, `greylist_processing`, `callback_url`, and `allow_phase_2` are not sent until the caller sets them. Explicit `False` is sent as `false`. `ip_address=None` is omitted; `""` is sent. +- **Booleans on form and multipart bodies must be the strings `true` and `false`.** A Python `bool` passed to `requests` becomes `True` or `False`, and the API will not treat that as a boolean. `_form_data` converts them. JSON bodies (`validate_batch`, the Inbox Placement Tester, the Warmup API) keep JSON booleans. - **`allow_phase_2` is validation `send_file` only.** Scoring `send_file` and the S3 / SFTP jobs do not send it; those jobs send only their documented `file_name`, `has_header_row`, `remove_duplicates`, and `return_url`. `activity_data` on get-file is validation `get_file` only. - **`validate_batch` uses `api.zerobounce.net`**, not `bulkapi.zerobounce.net`. It follows `base_url`. - **Failures raise.** `ZBClientException` means the arguments were rejected and no request was sent. `ZBApiException` means the API failed. The message must be short: no raw HTML, no megabyte body, no `JSONDecodeError` / `Expecting value` text, and no `Error: Error:` prefix. An account-level `validate_batch` error (`email_address` `all`) raises. A per-address error stays on the result's `errors` list. `get_credits` with an invalid key returns `Credits` `-1`; that endpoint does not send an `error` field. - **Downloads are buffered unless `stream=True`.** The streamed path sniffs the first 64 KB for an error body, writes to a `.part` temporary file next to `download_path`, and moves it into place only when complete. Keep both paths' error checks identical (`_raise_if_download_error`). - **A file download is not JSON.** `get_file` saves a non-empty CSV body, including `text/csv` and `text/plain`. An empty body, an HTML page (status 200 or 403), and a JSON error are `ZBApiException`. Do not treat every `text/plain` body as an error. +- **Where the key travels is fixed by each API's docs.** The validation API: `api_key` in the query (GET) or body (POST, uploads). The Inbox Placement Tester: `Authorization: Bearer` (`_request_json(..., bearer=True)`). The Warmup API: `API-Key` header, in `ZeroBounceWarmup` only, never mixed with `ZeroBounce`. +- **Every request URL must be https.** `_checked_url` (main client) refuses anything else before sending, including the reassignable `*_BASE_URL` attributes; it also warns once when an EU or US `base_url` meets `bulkapi.zerobounce.net`, because no regional bulk host is documented. +- **Keys never reach an exception.** `_check_api_key` rejects surrounding whitespace and control characters (the HTTP library would quote such a header value unredacted). A `RequestException` is redacted with `_redact` and raised after the `except` block, so the original is not its `__context__`; keep that pattern for any new request path. Caller IDs placed in a URL path go through `_path_segment`. - **Do not follow redirects.** Every `requests` call passes `allow_redirects=False`. A 3xx response is a `ZBApiException`. A redirect must not be fetched, because the `Location` could be another host and the next request can carry the API key. - **Do not commit `.env`.** It is gitignored and may hold a real key. Tests must not call the live API. - Match the surrounding style. Do not reformat unrelated code. diff --git a/CHANGELOG.md b/CHANGELOG.md index eea0b43..72277cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,6 +64,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Error messages no longer include the raw HTML access-denied page. The live `get_file` page is about 1.4 MB and contains the API key. - Redirects are not followed, so a response cannot send the client, or the API key, to another host. +## [2.2.4] - 2026-08-14 + +No changes to the package; CI action updates only. + ## [2.2.3] - 2026-06-18 ### Fixed @@ -152,6 +156,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release: `get_credits`, `get_api_usage`, `get_activity`, `validate`, `validate_batch`, bulk and AI-scoring file methods, and `guess_format`. [Unreleased]: https://github.com/zerobounce/zero-bounce-python-sdk-setup/compare/v2.2.4...HEAD +[2.2.4]: https://github.com/zerobounce/zero-bounce-python-sdk-setup/compare/v2.2.3...v2.2.4 [2.2.3]: https://github.com/zerobounce/zero-bounce-python-sdk-setup/compare/v2.2.2...v2.2.3 [2.2.2]: https://github.com/zerobounce/zero-bounce-python-sdk-setup/compare/v2.2.0...v2.2.2 [2.2.0]: https://github.com/zerobounce/zero-bounce-python-sdk-setup/compare/v2.1.10...v2.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index df23a33..fd9f893 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,7 +8,7 @@ By participating in this project, you agree to uphold our [Code of Conduct](CODE ## Getting Started -See the [README](README.md) for prerequisites, setup, and how to run tests. +See the [README](README.md) for setup, linting and tests (`ruff check src tests`, `python -m compileall -q src tests`, `python -m pytest`). [AGENTS.md](AGENTS.md) describes the layout and the conventions changes must follow. ## How to Contribute @@ -28,20 +28,22 @@ Open an [issue](https://github.com/zerobounce/zero-bounce-python-sdk-setup/issue ### Submitting Changes -1. **Fork** the repository and create a branch from `main`: +1. **Fork** the repository and create a branch from `master`: ```bash git checkout -b feature/your-feature-name ``` -2. **Make your changes** and add or update tests where relevant. +2. **Make your changes** and add or update tests where relevant. Tests must not call the live API. Only add methods for endpoints documented at https://www.zerobounce.net/docs/. -3. **Run the test suite** (see README) before submitting. +3. **Add a CHANGELOG entry** under `## [Unreleased]` for anything users will notice. -4. **Commit** with a clear message (e.g. `Add X`, `Fix Y`). +4. **Run the lint and test commands** (see README) before submitting. -5. **Push** your branch and open a **Pull Request** against `main`. +5. **Commit** with a [Conventional Commits](https://www.conventionalcommits.org/) message (e.g. `feat: add X`, `fix: Y`, `docs: Z`). -6. In the PR description, briefly explain what changed and why. Link any related issues. +6. **Push** your branch and open a **Pull Request** against `master`. + +7. In the PR description, briefly explain what changed and why. Link any related issues. Maintainers will review and may request changes. Once approved, your PR can be merged. diff --git a/Dockerfile b/Dockerfile index f4c932b..18a713c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,9 +9,9 @@ RUN pip install --no-cache-dir -e ".[test]" COPY tests/ tests/ -# Unit tests only (no API key); use CMD so it can be overridden +# Every offline suite (no API key, no network); use CMD so it can be overridden USER 65532:65532 -CMD ["python", "-m", "pytest", "tests/zero_bounce_test_case.py", "-v", \ +CMD ["python", "-m", "pytest", "-v", \ "--cov=zerobouncesdk", "--cov-report=term-missing", "--cov-fail-under=50"] diff --git a/README.md b/README.md index a0b80a5..d25a3e7 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,9 @@ More information about ZeroBounce you can find in the [official documentation](h ## Security - Keep API keys on a trusted server. Do not embed them in mobile apps or browser JavaScript that untrusted users can inspect. -- Custom API base URLs (when supported) must use `https://`. Do not pass end-user-controlled hosts into those settings. -- Request URLs include `api_key` as a query parameter (ZeroBounce API contract). Do not log full request URLs or enable payload debug logging in production. +- Where the key travels, as each API documents it: the validation API takes an `api_key` parameter (in the query string for `GET` requests; in the body with `use_post=True` and for file uploads); the Inbox Placement Tester takes an `Authorization: Bearer` header; the Warmup API takes a separate Warmup key in an `API-Key` header, used only by `ZeroBounceWarmup`. Query strings can end up in proxy and server logs, so do not log full request URLs, and prefer `use_post=True` where a method offers it. +- Every base URL, including the per-API `*_BASE_URL` attributes, must be `https://`: the client refuses to send a request anywhere else. Do not pass end-user-controlled hosts into those settings. +- The client never follows redirects and masks the key in every exception it raises, network errors included. It rejects a key with surrounding whitespace or control characters, such as one read from a file with its trailing newline. ## INSTALLATION ```bash @@ -56,6 +57,10 @@ zero_bounce = ZeroBounce("", base_url="https://custom-api.example. - `ZBApiUrl.API_USA_URL` - USA region API (https://api-us.zerobounce.net/v2/) - `ZBApiUrl.API_EU_URL` - EU region API (https://api-eu.zerobounce.net/v2/) +The region applies to the single-request API. File-based APIs always use `bulkapi.zerobounce.net` (see [Other hosts](#other-hosts)). + +**Errors:** a method raises `ZBClientException` when it rejects an argument before sending anything, and `ZBApiException` when the API returns an error; both inherit from `ZBException`. `ZBApiException.status_code` holds the HTTP status when it is known (for example 429 from the Warmup API). An unknown status, sub-status or confidence value in a response becomes `None` with a `UserWarning` instead of an error. + ## Examples Then you can use any of the SDK methods, for example: @@ -103,6 +108,8 @@ except ZBException as e: * ##### Find the correct email format when you provide a name and email domain or company name +Pass exactly one of `domain` or `company_name`. The name fields are optional, and `use_post=True` sends the request as a form-encoded `POST`. + ```python from zerobouncesdk import ZeroBounce, ZBException @@ -374,7 +381,7 @@ file_id="" # The returned file ID when calling local_download_path = "./dwnld_file.csv" # The path where the file will be downloaded try: - response = zero_bounce.scoring_get_file(file_id, local_download_path) + response = zero_bounce.scoring_get_file(file_id, local_download_path) # stream=True for large files print("ZeroBounce get_file response: " + str(response)) # Optional third argument: ZBGetFileOptions with download_type only (activity_data is not used for scoring getfile) # response = zero_bounce.scoring_get_file(file_id, local_download_path, opts) @@ -445,7 +452,7 @@ try: "./people.csv", domain_column=3, first_name_column=1, last_name_column=2, has_header_row=True ) print(zero_bounce.email_finder_file_status(sent.file_id)) - zero_bounce.email_finder_get_file(sent.file_id, "./people_results.csv") + zero_bounce.email_finder_get_file(sent.file_id, "./people_results.csv") # stream=True for large files zero_bounce.email_finder_delete_file(sent.file_id) # only when the status is Complete sent = zero_bounce.send_domain_search_file("./domains.csv", domain_column=1, has_header_row=True) @@ -575,8 +582,11 @@ zero_bounce.EMAIL_FINDER_BASE_URL = "https://bulkapi.zerobounce.net/email-finder zero_bounce.DOMAIN_SEARCH_BASE_URL = "https://bulkapi.zerobounce.net/domain-search" zero_bounce.LIST_EVALUATOR_BASE_URL = "https://bulkapi.zerobounce.net/v2/listevaluator" zero_bounce.INBOX_PLACEMENT_BASE_URL = "https://bulkapi.zerobounce.net/api/ipt" +zero_bounce.BULK_ROOT_URL = "https://bulkapi.zerobounce.net" # S3 and SFTP/FTP ``` +Each of these must be an `https://` URL; the client refuses to send the key anywhere else. + * ##### (Deprecated) Identify the correct email format when you provide a name and email domain > **⚠️ Deprecated:** The `guess_format` method is deprecated and will be removed in future versions. Use `find_email_format` or `find_domain` instead (see examples above). @@ -593,7 +603,7 @@ last_name = "Doe" # The last name of the person whose email format is being sear try: response = zero_bounce.guess_format(domain, first_name, middle_name, last_name) - print("ZeroBounce guess format response: " + response) + print("ZeroBounce guess format response: " + str(response)) except ZBException as e: print("ZeroBounce guess format error: " + str(e)) ``` @@ -602,62 +612,39 @@ except ZBException as e: ### Local setup ```bash -python -m venv venv # python 3.12+ +python -m venv venv # Python 3.10+ recommended source venv/bin/activate -pip install -e . +pip install -e ".[test]" +pip install ruff==0.17.2 ``` -### Run tests with Docker -From the **`sdk-docs/`** folder in the SDKs monorepo: - +### Lint and test ```bash -cd sdk-docs -docker compose build python -docker compose run --rm python +ruff check src tests +python -m compileall -q src tests +python -m pytest ``` -Or build and run this SDK’s image from this directory: +`python -m pytest` runs every offline suite: the scripted unit tests and the `tests/test_*.py` suites, which answer through a fake transport and never call the API. CI runs the same commands, with coverage. -```bash -docker build -t zb-python-sdk . -docker run --rm zb-python-sdk -``` +### Live API tests +`tests/zero_bounce_integration_test.py` calls the real API and is not collected by `pytest`. Run it with a valid key: -### Run tests (local) ```bash -python -m tests -v - -# output: -python -m tests -v -test_api_regions (tests.zero_bounce_integration_test.ZeroBounceIntegrationTestCase.test_api_regions) -Test that different API regions work. ... skipped 'ZEROBOUNCE_API_KEY environment variable not set' -test_error_handling_invalid_key (tests.zero_bounce_integration_test.ZeroBounceIntegrationTestCase.test_error_handling_invalid_key) -Test error handling with invalid API key. ... skipped 'ZEROBOUNCE_API_KEY environment variable not set' -test_find_domain_with_domain (tests.zero_bounce_integration_test.ZeroBounceIntegrationTestCase.test_find_domain_with_domain) -Test find_domain with domain parameter. ... skipped 'ZEROBOUNCE_API_KEY environment variable not set' - ----------------------------------------------------------------------- -Ran 4 tests in 0.015s - -OK (skipped=10) # integration tests are skipped if no api key is present +export ZEROBOUNCE_API_KEY= +python -m unittest tests.zero_bounce_integration_test -v ``` -```bash -# to run integration tests export api key into the environment (valid api key required) -export ZEROBOUNCE_API_KEY= && python -m tests -v - -# output: -test_api_regions (tests.zero_bounce_integration_test.ZeroBounceIntegrationTestCase.test_api_regions) -Test that different API regions work. ... ok -test_error_handling_invalid_key (tests.zero_bounce_integration_test.ZeroBounceIntegrationTestCase.test_error_handling_invalid_key) -Test error handling with invalid API key. ... ok - ----------------------------------------------------------------------- -Ran 4 tests in 1.1s +Without `ZEROBOUNCE_API_KEY` its tests are skipped. It covers the original single-request methods; the newer APIs are tested offline only. -OK +### Run tests with Docker +```bash +docker build -t zb-python-sdk . +docker run --rm zb-python-sdk ``` +From the **`sdk-docs/`** folder in the SDKs monorepo, `docker compose build python` and `docker compose run --rm python` do the same. + ## Publish 1. Bump `version` in `pyproject.toml`, commit, tag (`vX.Y.Z`), push tag. diff --git a/SECURITY.md b/SECURITY.md index e343e59..78b8a5f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -14,8 +14,10 @@ We focus on the current release line for fixes. Using the latest release is reco ## Tips for Using This SDK -* Don't commit API keys or `.env` files—use environment variables or a secrets manager. -* Keep dependencies up to date and upgrade when new versions are released. -* The client uses HTTPS by default; avoid overriding to non-HTTPS in production. +* Don't commit API keys or `.env` files—use environment variables or a secrets manager. The Warmup API uses its own key; keep it separate from the validation API key. +* Keep the SDK and its dependencies up to date. Use Python 3.10 or newer: older versions cannot install the current `requests` and `urllib3` security releases. +* The client only sends requests to `https://` URLs and never follows redirects. Do not point a base URL at a host you don't control. +* The validation API puts the key in the query string of `GET` requests, and query strings can reach proxy and server logs. Don't log full request URLs; use `use_post=True` where a method offers it. +* Exceptions raised by the SDK mask the key, but avoid logging request objects or enabling HTTP debug logging (for example `http.client` debuglevel), which would print the key. Thanks for helping keep things secure. diff --git a/documentation.md b/documentation.md index 8362dfb..cfa4325 100644 --- a/documentation.md +++ b/documentation.md @@ -43,6 +43,10 @@ zero_bounce = ZeroBounce("", base_url="https://custom-api.example. - `ZBApiUrl.API_USA_URL` - USA region API (https://api-us.zerobounce.net/v2/) - `ZBApiUrl.API_EU_URL` - EU region API (https://api-eu.zerobounce.net/v2/) +The region applies to the single-request API. File-based APIs always use `bulkapi.zerobounce.net` (see [Other hosts](#other-hosts)). + +**Errors:** a method raises `ZBClientException` when it rejects an argument before sending anything, and `ZBApiException` when the API returns an error; both inherit from `ZBException`. `ZBApiException.status_code` holds the HTTP status when it is known (for example 429 from the Warmup API). An unknown status, sub-status or confidence value in a response becomes `None` with a `UserWarning` instead of an error. + #### Examples Then you can use any of the SDK methods, for example: @@ -90,6 +94,8 @@ except ZBException as e: * ####### Find the correct email format when you provide a name and email domain or company name +Pass exactly one of `domain` or `company_name`. The name fields are optional, and `use_post=True` sends the request as a form-encoded `POST`. + ```python from zerobouncesdk import ZeroBounce, ZBException @@ -357,7 +363,7 @@ file_id="" # The returned file ID when calling local_download_path = "./dwnld_file.csv" # The path where the file will be downloaded try: - response = zero_bounce.scoring_get_file(file_id, local_download_path) + response = zero_bounce.scoring_get_file(file_id, local_download_path) # stream=True for large files print("ZeroBounce get_file response: " + str(response)) # Optional third argument: ZBGetFileOptions with download_type only (activity_data is not used for scoring getfile) # response = zero_bounce.scoring_get_file(file_id, local_download_path, opts) @@ -428,7 +434,7 @@ try: "./people.csv", domain_column=3, first_name_column=1, last_name_column=2, has_header_row=True ) print(zero_bounce.email_finder_file_status(sent.file_id)) - zero_bounce.email_finder_get_file(sent.file_id, "./people_results.csv") + zero_bounce.email_finder_get_file(sent.file_id, "./people_results.csv") # stream=True for large files zero_bounce.email_finder_delete_file(sent.file_id) # only when the status is Complete sent = zero_bounce.send_domain_search_file("./domains.csv", domain_column=1, has_header_row=True) @@ -558,4 +564,7 @@ zero_bounce.EMAIL_FINDER_BASE_URL = "https://bulkapi.zerobounce.net/email-finder zero_bounce.DOMAIN_SEARCH_BASE_URL = "https://bulkapi.zerobounce.net/domain-search" zero_bounce.LIST_EVALUATOR_BASE_URL = "https://bulkapi.zerobounce.net/v2/listevaluator" zero_bounce.INBOX_PLACEMENT_BASE_URL = "https://bulkapi.zerobounce.net/api/ipt" +zero_bounce.BULK_ROOT_URL = "https://bulkapi.zerobounce.net" # S3 and SFTP/FTP ``` + +Each of these must be an `https://` URL; the client refuses to send the key anywhere else. diff --git a/pyproject.toml b/pyproject.toml index fb757c1..900b3a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -keywords = ["zero", "bounce", "sdk"] +keywords = ["zerobounce", "email validation", "email verification", "email finder", "deliverability", "sdk"] # Floors are the oldest releases without known advisories (OSV) on each Python version. dependencies = [ "requests >= 2.33.0; python_version >= '3.10'", @@ -36,6 +36,9 @@ test = [ [project.urls] Homepage = "https://github.com/zerobounce/zero-bounce-python-sdk-setup" +Documentation = "https://www.zerobounce.net/docs/" +Changelog = "https://github.com/zerobounce/zero-bounce-python-sdk-setup/blob/master/CHANGELOG.md" +Issues = "https://github.com/zerobounce/zero-bounce-python-sdk-setup/issues" [tool.ruff.lint] select = ["E9", "F63", "F7", "F82"] diff --git a/src/zerobouncesdk/zerobouncesdk.py b/src/zerobouncesdk/zerobouncesdk.py index a75b757..38c3fbd 100644 --- a/src/zerobouncesdk/zerobouncesdk.py +++ b/src/zerobouncesdk/zerobouncesdk.py @@ -184,7 +184,9 @@ def _response_text(response) -> str: class ZeroBounce: - """The ZeroBounce main class. All the requests are implemented here.""" + """Client for the ZeroBounce API: validation, scoring, email finder and domain search, bulk and S3/SFTP + files, the list evaluator, filters and the Inbox Placement Tester. The Warmup API has its own client, + ``ZeroBounceWarmup``, with its own key.""" BULK_BASE_URL = "https://bulkapi.zerobounce.net/v2" BULK_ROOT_URL = "https://bulkapi.zerobounce.net" diff --git a/tests/__main__.py b/tests/__main__.py index 72e7a10..1a0f0a3 100644 --- a/tests/__main__.py +++ b/tests/__main__.py @@ -1,8 +1,28 @@ +"""``python -m tests``: the offline suites through unittest, the same ones ``python -m pytest`` runs. + +The live API suite is left out on purpose: it would call the real API whenever ZEROBOUNCE_API_KEY is set. +Run it explicitly with ``python -m unittest tests.zero_bounce_integration_test``. +""" + import unittest -from .test_offline_api import OfflineApiTest -from .zero_bounce_test_case import ZeroBounceTestCase -from .zero_bounce_integration_test import ZeroBounceIntegrationTestCase +from .test_offline_api import OfflineApiTest # noqa: F401 +from .test_offline_edges import ( # noqa: F401 + DocumentedVariantsTest, + ErrorBodyTest, + OptionalFieldsTest, + ResponseModelTest, + TransportFailureTest, +) +from .test_security_offline import ( # noqa: F401 + KeyFormatTest, + NoContextLeakTest, + TransportPlacementTest, + WarmupBodyRedactionTest, +) +from .test_streaming_and_regions_offline import RegionalBaseUrlTest, StreamedDownloadTest # noqa: F401 +from .test_warmup_offline import WarmupTest # noqa: F401 +from .zero_bounce_test_case import ZeroBounceTestCase # noqa: F401 if __name__ == "__main__": unittest.main()