update httpx requirement from >=0.25,<0.28 to >=0.25,<0.29#120
Open
vlouvet wants to merge 31 commits into
Open
update httpx requirement from >=0.25,<0.28 to >=0.25,<0.29#120vlouvet wants to merge 31 commits into
vlouvet wants to merge 31 commits into
Conversation
add REST API example in Programmatic use section
* updated settings to add test specific parameters * updated httpx package pinning to allow newer versions * fixed pyodbc package name to pass poetry check
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #120 +/- ##
==========================================
+ Coverage 35.76% 37.02% +1.26%
==========================================
Files 25 25
Lines 1071 1083 +12
==========================================
+ Hits 383 401 +18
+ Misses 688 682 -6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
* move wsdl version to a config property
Contributor
Author
|
@iloveitaly please review |
iloveitaly
reviewed
Oct 17, 2025
iloveitaly
reviewed
Oct 17, 2025
iloveitaly
reviewed
Oct 17, 2025
iloveitaly
requested changes
Oct 17, 2025
…oap api client instead.
Contributor
Author
|
@iloveitaly The recommended / commented changes have been resolved at this time. please let me know if there is anything else you have questions on. |
…DL version. change unscoped for upgrade to httpx
…vante#45) (#4) `WebServiceCall` checked `isinstance(response, zeep.xsd.ComplexType)`, but `ComplexType` is the schema-definition class — runtime SOAP responses are `zeep.xsd.CompoundValue` instances. The check was always False, so status validation and `extract` were silently skipped on every call. The decorator was also a synchronous wrapper around async SOAP methods, so even with the right isinstance check it would have processed the coroutine object rather than the awaited response. Switch the isinstance check to `CompoundValue`, and detect coroutine functions at decoration time so async methods are awaited inside the wrapper. Add regression tests covering the success/error/extract/default paths plus the sync code path. Closes jacobsvante#45 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…svante#72) (#5) Adds substantive unit tests for `NetSuiteSoapApi`: - WSDL URL construction for sandbox vs production accounts, custom versions, and explicit `wsdl_url` overrides - `AsyncNetSuiteTransport._fix_address` munges the default WSDL host to the account-specific subdomain - Cache injection (default `SqliteCache` vs custom `InMemoryCache`) - `TokenPassport` signature determinism and message format, and that `passport.make` raises for username/password auth - Argument shaping for every public SOAP method (`get`, `getList`, `getAll`, `add`, `update`, `upsert`, `search`, `searchMoreWithId`, `getItemAvailability`) by mocking `request` and the type factories - `request` merges `generate_passport()` output with `additionalHeaders` - `to_builtin` delegates to `zeep.helpers.serialize_object` - `with_timeout` proxies to `transport.settings(timeout=...)` - `_ensure_required_dependencies` raises a `RuntimeError` mentioning the `soap_api` extra when zeep is missing Closes jacobsvante#72 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…acobsvante#42) (#6) NetSuite's SuiteQL REST API caps a single response at limit=1000 and a single query at 100,000 rows. Users had to wire up the `next` link themselves — the issue was that `suiteql()` always re-encoded `limit` and `offset` in `params`, which would clash with the offset embedded in the absolute `next` URL. Add `NetSuiteRestApi.suiteql_paginated`: an async generator that runs the initial query, then follows `links[rel=next]` (POSTing the same body to the absolute URL) until `hasMore` is False or no next link is present. The original `suiteql` docstring now explains both the ORDER-BY zero-row quirk (jacobsvante#29) and the >100k partitioning workaround. README/docs also gain a pagination section. Closes jacobsvante#42 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e#29) (#7) NetSuite's SuiteQL REST endpoint has a known quirk where a query with `ORDER BY` and a small `limit` (the default 10) silently returns zero rows. Users hit this and assume the library is broken — see jacobsvante#29 where `SELECT id, name FROM subsidiary ORDER BY id` returns no items. We can't fix the NetSuite-side bug, but we can stop users from losing hours debugging it: log a warning whenever `ORDER BY` is detected in the query string and `limit < 1000`, pointing them at the workaround (raise the limit, or sort client-side). Detection uses a word-boundary regex so substrings like `order_by_id` don't trigger false positives. Documented in the `suiteql` docstring. Closes jacobsvante#29 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…oder (#8) Adds 100 new tests across 8 files. Coverage gains by module: config.py 59% -> 100% exceptions.py 57% -> 100% json.py 46% -> 94% rest_api_base.py 46% -> 100% restlet.py 70% -> 100% soap_api/decorators 88% -> 100% soap_api/passport 77% -> 100% soap_api/transports 88% -> 100% cli/helpers 0% -> 92% cli/main 0% -> 55% cli/misc 0% -> 100% cli/rest_api 0% -> 51% cli/restlet 0% -> 63% cli/soap_api 0% -> 69% TOTAL 46% -> 75% Also fixes a 5-year-old bug in `netsuite.json._orjson_default`: it called `_get_encoder(obj)` then invoked the result, but `_get_encoder` already returns the *encoded value*, not the encoder. Any payload containing a Decimal, bytes, set, frozenset, Path, or timedelta would crash with `'str' object is not callable` when orjson is installed. The fix removes the redundant call. Surfaced while writing tests for `nsjson.dumps`. CLI tests gate on `pytest.importorskip("pkg_resources")` so they skip cleanly on Python 3.14 / setuptools 81+ (which dropped `pkg_resources`). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(ci): trim workflows for unofficial-fork posture Drop the publishing/distribution machinery the upstream uses to ship to PyPI and gh-pages — this fork is a friendly fork that does not publish a package. What remains is the dev-loop automation: tests, style, types, coverage. - Delete `.github/workflows/cd.yml` (release-please + `poetry publish`). - Delete `.github/workflows/docs.yml` (mkdocs -> gh-pages deploy). The in-repo `docs/` markdown still renders on github.com. - `ci.yml` and `codecov.yml`: collapse upstream's 75-cell matrix (5 Pythons x 5 extras x 3 OSes) to a single combination — Python 3.12 on ubuntu-latest with `--extras all`. Forks don't need the full compatibility surface; one combination is enough to catch regressions on every PR. - Drop the `.tool-versions` / asdf-parse-action plumbing since the matrix no longer reads versions from there. Also reformat the test files added in #8 to satisfy the `style` job (black + isort). Production code is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci(deps): add pytest-asyncio dev-dep so async tests can run The async test files added in #5/#6/#7/#8 (tests/test_rest_api.py, test_soap_api.py, test_rest_api_base.py, test_restlet.py, test_soap_api_decorators*.py, test_soap_api_transports.py) all use `@pytest.mark.asyncio`, but the dev-dependency was never declared in pyproject.toml. The local development environment had pytest-asyncio installed transitively, so `pytest -q` showed 149 passing — masking the gap until CI was actually exercised. This PR (chore/fork-ci-config) is the first one to enable a working CI matrix on the fork, so it's where the failure surfaces. CI runs `poetry install --extras all` from a clean state and then `pytest`, which collects the async tests but has no plugin to drive them, fails collection on all 51 with: Failed: async def functions are not natively supported. You need to install a suitable plugin for your async framework, for example: anyio, pytest-asyncio, ... Add `pytest-asyncio = "~0.23"` to `[tool.poetry.dev-dependencies]` between pytest and pytest-cov. Also pin `asyncio_mode = "strict"` in `[tool.pytest.ini_options]` to match how the existing tests are written (each is explicitly marked with `@pytest.mark.asyncio`) and to silence pytest-asyncio 0.23+'s deprecation warning about leaving the mode unset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This is an unofficial fork that does not publish to PyPI, so the existing "pip install netsuite" instructions never produce this fork's code. Switch every install snippet to the git+https URL form. - README.md: replace upstream's PyPI/CI/codecov badges with vlouvet equivalents, add an "unofficial fork" disclaimer, and rewrite the install section to use `pip install git+https://...`. Drop the upstream Slack/SuiteSync links since they belong to the original maintainer's project, not this fork. - docs/index.md: same install rewrite. Note that the SOAP install line also now flags NetSuite's 2027.1 SOAP deprecation, so users picking the soap_api extra are at least warned at install time. - mkdocs.yml: point repo_name/repo_url at vlouvet/netsuite so the docs theme's edit/source links resolve correctly. - pyproject.toml: keep the original authors as the credit they're owed, add Vicente Louvet III, and point homepage/repository at the fork. Drop the now-incorrect `documentation` URL (no auto-deployed site for this fork; in-repo docs/ is canonical per #9). No production code changes. No tests added — the install instructions aren't exercised by the test suite. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation warning (#11) * feat: add OAuth 2.0 auth (Client Credentials JWT + Auth Code) and SOAP deprecation warning NetSuite has announced SOAP Web Services will be removed in the 2027.1 release. This PR introduces the migration path: OAuth 2.0 against the REST API, plus a runtime DeprecationWarning to surface the sunset. Two flows, both centered on a shared `OAuth2BearerAuth` httpx auth handler that the REST API and Restlet clients use transparently in place of OAuth 1.0a TBA: - **Client Credentials with JWT Bearer assertion** (M2M, RFC 7523). `OAuth2ClientCredentialsAuth` takes a `client_id`, `certificate_id` (the `kid` NetSuite assigns when you upload your public key), and a PEM-encoded private key. The handler signs short-lived JWTs and exchanges them at NetSuite's token endpoint, caching the resulting access token with a 60-second safety margin before refreshing. Default algorithm is PS256; PS/RS/ES at 256/384/512 are accepted. - **Authorization Code Grant** is supported via two helper functions — `build_authorization_url()` and `exchange_authorization_code()` — that callers wire into their own redirect handlers. The resulting token is dropped into `OAuth2AccessTokenAuth` (bring-your-own). We don't refresh BYO tokens automatically; the calling app's auth layer is responsible for that. `Config.auth` accepts the four auth types (TokenAuth, OAuth2ClientCredentialsAuth, OAuth2AccessTokenAuth, UsernamePasswordAuth). `RestApiBase._make_auth` dispatches on type. The OAuth2 handler is cached on the API instance so token re-use works across back-to-back requests. `NetSuiteSoapApi.__init__` now emits a `DeprecationWarning` mentioning the 2027.1 sunset and pointing users at `OAuth2ClientCredentialsAuth`. The pyproject `filterwarnings` filter suppresses it inside the existing test suite (which instantiates the SOAP client 60+ times) so it doesn't clutter the pytest summary; a dedicated test in `tests/test_soap_api_deprecation.py` re-enables warnings explicitly to assert the behavior. 35 new tests across `tests/test_oauth2.py`, `tests/test_oauth2_integration.py`, and `tests/test_soap_api_deprecation.py`. Coverage of the new `netsuite/oauth2.py` is 97%. Overall: 149 tests -> 184 tests, 75% -> 77% coverage. `docs/index.md` gains a new "Authentication" section with worked examples for all three flows (M2M JWT, Authorization Code, legacy TBA) and an explicit callout for the 2027.1 SOAP sunset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(deps): declare joserfc as a direct dependency netsuite/oauth2.py imports `joserfc` directly for JWT signing. Newer authlib (1.7+) pulls joserfc in as a transitive, which is why local test runs work, but the older end of our `authlib = ">=1,<3"` range does not. CI's clean resolver picks one of those older authlibs and fails at import time with: ModuleNotFoundError: No module named 'joserfc' Direct usage means direct dependency. Pin loosely (`>=1,<2`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
For private repos, and for forks where Codecov tokenless uploads can be flaky, the token has to be passed explicitly to `codecov/codecov-action@v5`. The repo's `CODECOV_TOKEN` secret is read at run time and forwarded. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Imports the dependabot bump from upstream PR jacobsvante#109. Black 25 introduces the 2025 stable style (PEP 639 license metadata, trailing-comma normalization on typed function parameters, generic function definition wrapping, etc.) but none of those rules trigger on this codebase — `black --check .` is a no-op after the version bump, so no reformat hunks are bundled into this commit. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Imports the dependabot bump from upstream PR jacobsvante#108. pytest-cov 6 dropped Python 3.8 support; this fork already requires Python >=3.9 in pyproject.toml so the floor is unchanged. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Imports the change from upstream PR jacobsvante#99 (merit-finns). NetSuite explicitly informed integrators that SOAP web service WSDL versions 2017.2 through 2021.1 are unsupported and that integrations on those versions need to upgrade. Until our 2027.1 SOAP sunset arrives, the lib's default WSDL needs to be one NetSuite still serves. The `NetSuiteSoapApi(version=...)` kwarg still lets users pin to a specific version when their account hasn't migrated yet. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…^80.9.0 (#13) Imports the dependabot bump from upstream PR jacobsvante#117. types-setuptools is type stubs only — no runtime impact. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ns (#20) The deprecation warning and OAuth2 module docstring asserted SOAP Web Services 'will be removed in the 2027.1 release.' The NetSuite 2026.1 SuiteTalk release notes make no such hard-date claim: removal is gradual, 2025.2 is the last *planned* SOAP endpoint, and older endpoints degrade to unsupported over subsequent releases (later endpoints only released as needed). Soften the message and docstring to match the official wording and point at the SOAP Removal Plans FAQ. The 'NetSuite has announced that SOAP Web Services' prefix is preserved so the pyproject filterwarnings entry still matches. Update test_soap_api_deprecation to assert 2025.2 instead of the removed 2027.1 date. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The specs/ folder holds local reference material (NetSuite release-note PDFs, scratch notes) that should not be tracked. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add convenience wrappers on NetSuiteRestApi for the four REST operations
introduced in the NetSuite 2026.1 release, mirroring the existing
suiteql/jsonschema/openapi helpers. All were already reachable via the
generic get/post/patch methods; these absorb the operation-specific URL
shapes and custom media-type headers:
- attach() / detach(): POST /record/v1/{t}/{id}/!attach|!detach/{tt}/{tid};
optional contact "role" body; returns None on the 204 response. Internal
and eid: external IDs both work.
- create_form(): POST .../!transform/{tt} with the
"Accept: application/vnd.oracle.resource+json; type=create-form" header,
returning a record prepopulated from a related record.
- select_options(): POST (new instance) or PATCH (existing, via record_id)
with the type=select-options Accept header and a comma-joined "fields"
param; dependent field values ride in the body.
- batch(): async homogeneous add/update/upsert (max 100) with the
"Prefer: respond-async" + type=collection headers. Talks to
_request_impl directly to surface the async job's Location header, which
the JSON-only _request helper discards.
Covered by unit tests asserting method, URL, headers, params and body for
each operation.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The parametrized encoder test hardcoded Path("/tmp/x") -> "/tmp/x". The
encoder serializes Path via str(), which on Windows yields "\tmp\x", so the
test failed on windows-latest CI. Compare against str(Path(...)) so the
expected value matches per-OS (POSIX "/tmp/x" vs Windows "\\tmp\\x").
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) works (#24) build_client_assertion called jwt.encode(header, claims, key) without an algorithms argument. joserfc's default registry only permits its "recommended" algorithms (RS256/ES256/...) and raises UnsupportedAlgorithmError for PS256 -- which is this module's DEFAULT_ALGORITHM and NetSuite's recommended assertion algorithm. So the default machine-to-machine flow failed at signing time; only callers explicitly passing algorithm="RS256" worked (ES384/ES512 were affected too). Whitelist the chosen algorithm in jwt.encode so every entry in SUPPORTED_ALGORITHMS actually signs. Confirmed against a live NetSuite sandbox: a PS256 assertion now signs and the M2M token endpoint accepts it. Every existing assertion test pinned algorithm="RS256", so the default PS256 path was never exercised. Add a regression test that builds an assertion with no algorithm= and asserts it signs as PS256. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NetSuite answers a REST create with 204 No Content and a Location header
pointing at the new record, so the JSON-only _request helper (which returns
None on 204) gives callers no way to learn the assigned ID.
Add create_record(record_type, record_data): it POSTs to
/record/v1/{record_type} via _request_impl directly (the same pattern
batch() uses to reach the Location header), then returns the ID parsed from
that header -- an int for numeric IDs, the raw string for external IDs
(eid:...), or None when no Location is present.
This is a scoped reimplementation of the idea in PR jacobsvante#121. Unlike that PR it
does not mutate the generic _request to special-case every 204 POST (which
would silently change the return type of all POSTs), and the ID parser
ignores query strings/fragments and tolerates a trailing slash rather than
using a bare /([^/]+)$ regex that would capture a querystring.
Tested: collection-endpoint POST + body, numeric id -> int, external id ->
str, query/trailing-slash handling, missing-header -> None, error status
raises.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#26) * test: raise coverage to 91% (CLI handlers, client facade, rest_api methods) The suite sat at 78%, with the largest gaps in code that had no direct tests: the CLI command handlers (cli/rest_api 51%, cli/restlet 63%, cli/main 55%), the NetSuite facade (client.py 68%), and several plain NetSuiteRestApi methods (the HTTP verbs, jsonschema, token_info, openapi). Add focused unit tests that drive each surface without the network: - test_cli_rest_api.py: builds the real argparse parser and invokes each subcommand's func(config, args) with a mocked NetSuite -- covers param assembly, payload/query-file reading, header parsing (incl. repeated and invalid headers), JSON encoding, the openapi-serve path (http.server.test mocked), and the RuntimeError->parser.error path. cli/rest_api 51%->99%. - test_cli_restlet.py: same approach for the restlet handlers. 63%->100%. - test_cli_main.py: drives main() for the per-section help shortcuts and a full async-subcommand run via both --config-environment and an ini file. 55%->95%. - test_client.py: the NetSuite cached_property accessors and option passing. 68%->100%. - test_rest_api.py: direct tests for request/get/post/put/patch/delete, jsonschema, token_info, openapi (with/without record types), batch body parsing, and the _make_url/_make_default_headers helpers. 81%->100%. Net: 78% -> 91% (above the 90% target), 194 -> 235 tests, all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): drop pkg_resources so the CLI imports on modern setuptools netsuite/cli/misc.py did `import pkg_resources` at module top, only to read the package version. setuptools 81+ removed pkg_resources, so on any modern environment `import netsuite.cli.<anything>` raised ModuleNotFoundError (cli/__init__ -> main -> misc) -- breaking the whole CLI, not just `version`. Switch to the stdlib `importlib.metadata.version`, which has been the recommended replacement since Python 3.8. This also unblocks CI coverage: the CLI tests were guarded with `pytest.importorskip("pkg_resources")` and silently skipped wherever pkg_resources was absent (i.e. CI), and the new test_cli_rest_api / test_cli_restlet modules errored at collection. With misc importable, drop those guards so every CLI test actually runs. Verified with pkg_resources uninstalled: 241 passed, 0 skipped, 0 collection errors, coverage 91%. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR would address the httpx package pinning to allow using the latest minor version.
Also update the settings file to make testing easier.
Finally, update the pyodbc package name to resolve a failure of 'poetry check'