Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4d11ee6
Update index.md
vlouvet Dec 22, 2023
22b9d1a
Merge branch 'jacobsvante:main' into main
vlouvet Dec 25, 2023
c4104cd
Merge branch 'jacobsvante:main' into main
vlouvet Sep 5, 2025
5b95dcc
update httpx requirement from >=0.25,<0.28 to >=0.25,<0.29 (#2)
vlouvet Sep 18, 2025
0f63e9f
Move WSDL version to a config parameter (#3)
vlouvet Sep 18, 2025
2712dda
remove change unrelated to httpx upgrade
vlouvet Jan 23, 2026
19fde3d
keep original wsdl version for httpx change
vlouvet Jan 23, 2026
f00e81a
remove change unrelated to httpx upgrade
vlouvet Jan 23, 2026
73bdb7b
remove unused property definition for wsdl, which is defined in the s…
vlouvet Jan 23, 2026
b46d2d1
removed the assertion in soap API testing that checked for a newer WS…
vlouvet Jan 23, 2026
85c800c
removed WSDL definition in conftest file, unscoped for HTTPX upgrade
vlouvet Jan 23, 2026
bb0b92f
fix(soap_api): WebServiceCall decorator was silently bypassed (#45) (#4)
vlouvet May 8, 2026
c6ab7a9
test(soap_api): expand SOAP client coverage from 3 to 29 tests (#72) …
vlouvet May 8, 2026
a207edf
feat(rest_api): add suiteql_paginated helper that follows next link (…
vlouvet May 8, 2026
4b58e7d
feat(rest_api): warn on SuiteQL ORDER BY with small limit (#29) (#7)
vlouvet May 8, 2026
4d7ea66
test: raise coverage from 46% to 75%; fix double-call bug in json enc…
vlouvet May 8, 2026
c99fbb4
chore(ci): trim workflows for unofficial-fork posture (#9)
vlouvet May 8, 2026
fd5a76a
docs: rebrand README/docs to vlouvet fork; install via git+ URL (#10)
vlouvet May 8, 2026
e86f6eb
feat: OAuth 2.0 auth (Client Credentials + Auth Code) and SOAP deprec…
vlouvet May 8, 2026
9557c07
ci(codecov): pass CODECOV_TOKEN to codecov-action (#16)
vlouvet May 8, 2026
3659392
build(deps-dev): update black requirement from ~24 to ~25 (#18)
vlouvet Jun 17, 2026
596f637
build(deps-dev): update pytest-cov requirement from ~5 to ~6 (#14)
vlouvet Jun 17, 2026
9d81932
feat(soap_api): bump default WSDL version 2021.1.0 -> 2024.2.0 (#12)
vlouvet Jun 17, 2026
2f5463e
build(deps-dev): update types-setuptools requirement from ^75.8.2 to …
vlouvet Jun 17, 2026
b97f310
fix(soap): align deprecation wording with official 2026.1 removal pla…
vlouvet Jun 17, 2026
1e12d13
chore(gitignore): ignore local /specs/ scratch folder (#21)
vlouvet Jun 17, 2026
405da70
feat(rest): add 2026.1 REST web services operation helpers (#22)
vlouvet Jun 17, 2026
fd289ca
fix(tests): make json Path encoding test OS-agnostic (#23)
vlouvet Jun 17, 2026
ff4d68a
fix(oauth2): sign with the configured algorithm so PS256 (the default…
vlouvet Jun 17, 2026
87f8f05
feat(rest): add create_record() that returns the new record's ID (#25)
vlouvet Jun 17, 2026
691231c
test: raise coverage to 91% (CLI handlers, client facade, rest_api me…
vlouvet Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions .github/workflows/cd.yml

This file was deleted.

27 changes: 4 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,24 @@ on:

jobs:
unittests:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.13", "3.12", "3.11", "3.10", "3.9"]
extras: ["", all, soap_api, orjson, cli]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ASDF Parse
uses: kota65535/github-asdf-parse-action@v2.0.0
id: versions
- uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
python-version: "3.12"
- uses: abatilo/actions-poetry@v4.0.0
with:
poetry-version: "${{ steps.versions.outputs.poetry }}"
- run: poetry install --extras ${{ matrix.extras }}
if: matrix.extras != ''
- run: poetry install
if: matrix.extras == ''
- run: poetry install --extras all
- run: poetry run pytest -v

style:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ASDF Parse
uses: kota65535/github-asdf-parse-action@v2.0.0
id: versions
- uses: actions/setup-python@v5
with:
python-version: "${{ steps.versions.outputs.python }}"
architecture: x64
python-version: "3.12"
- uses: abatilo/actions-poetry@v4.0.0
with:
poetry-version: "${{ steps.versions.outputs.poetry }}"
- run: poetry install --extras all
- run: poetry run flake8
- run: poetry run mypy --ignore-missing-imports .
Expand Down
9 changes: 2 additions & 7 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: ASDF Parse
uses: kota65535/github-asdf-parse-action@v2.0.0
id: versions
- uses: actions/setup-python@v5
with:
python-version: "${{ steps.versions.outputs.python }}"
architecture: x64
python-version: "3.12"
- uses: abatilo/actions-poetry@v4.0.0
with:
poetry-version: "${{ steps.versions.outputs.poetry }}"
- run: poetry install --extras all
- run: poetry run pytest --cov=netsuite --cov-report=xml --cov-report=term
- uses: codecov/codecov-action@v5
with:
files: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
26 changes: 0 additions & 26 deletions .github/workflows/docs.yml

This file was deleted.

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
/.pytest_cache
/poetry.lock
.DS_Store
/specs/
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
"python.formatting.provider": "black",
"files.exclude": {
"poetry.lock": true
}
},
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
37 changes: 16 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,39 @@
# netsuite

[![Continuous Integration Status](https://github.com/jacobsvante/netsuite/actions/workflows/ci.yml/badge.svg)](https://github.com/jacobsvante/netsuite/actions/workflows/ci.yml)
[![Continuous Delivery Status](https://github.com/jacobsvante/netsuite/actions/workflows/cd.yml/badge.svg)](https://github.com/jacobsvante/netsuite/actions/workflows/cd.yml)
[![Code Coverage](https://img.shields.io/codecov/c/github/jacobsvante/netsuite?color=%2334D058)](https://codecov.io/gh/jacobsvante/netsuite)
[![PyPI version](https://img.shields.io/pypi/v/netsuite.svg)](https://pypi.python.org/pypi/netsuite/)
[![License](https://img.shields.io/pypi/l/netsuite.svg)](https://pypi.python.org/pypi/netsuite/)
[![Python Versions](https://img.shields.io/pypi/pyversions/netsuite.svg)](https://pypi.org/project/netsuite/)
[![PyPI status (alpha/beta/stable)](https://img.shields.io/pypi/status/netsuite.svg)](https://pypi.python.org/pypi/netsuite/)
[![Slack Status](https://netsuite-slackin.fly.dev/badge.svg)](https://netsuite-slackin.fly.dev)
[![Continuous Integration Status](https://github.com/vlouvet/netsuite/actions/workflows/ci.yml/badge.svg)](https://github.com/vlouvet/netsuite/actions/workflows/ci.yml)
[![Code Coverage](https://img.shields.io/codecov/c/github/vlouvet/netsuite?color=%2334D058)](https://codecov.io/gh/vlouvet/netsuite)
[![License](https://img.shields.io/github/license/vlouvet/netsuite.svg)](LICENSE)

Make async requests to NetSuite SuiteTalk SOAP, REST Web Services, and Restlets. [Detailed documentation available here.](https://jacobsvante.github.io/netsuite/)
Make async requests to NetSuite SuiteTalk SOAP, REST Web Services, and Restlets.

# Help & Support
This is an unofficial fork. It is not published to PyPI; install directly from this repository.

Join the [Slack channel](https://netsuite-slackin.fly.dev) for help with NetSuite issues. Please do not post usage questions as issues in GitHub.
## Installation

There are some additional helpful resources for NetSuite development [listed here](https://dashboard.suitesync.io/docs/resources#netsuite).
Default features (REST API + Restlet support):

## Installation
pip install git+https://github.com/vlouvet/netsuite.git

With default features (REST API + Restlet support):
Pin to a specific commit or tag:

pip install netsuite
pip install git+https://github.com/vlouvet/netsuite.git@<commit-sha-or-tag>

With Web Services SOAP API support:
With Web Services SOAP API support (deprecated by NetSuite as of the 2027.1 release — prefer REST + OAuth 2.0 for new integrations):

pip install netsuite[soap_api]
pip install "netsuite[soap_api] @ git+https://github.com/vlouvet/netsuite.git"

With CLI support:

pip install netsuite[cli]
pip install "netsuite[cli] @ git+https://github.com/vlouvet/netsuite.git"

With `orjson` package (faster JSON handling):

pip install netsuite[orjson]
pip install "netsuite[orjson] @ git+https://github.com/vlouvet/netsuite.git"

With all features:

pip install netsuite[all]
pip install "netsuite[all] @ git+https://github.com/vlouvet/netsuite.git"

## Documentation

Is found here: https://jacobsvante.github.io/netsuite/
In-repo documentation lives in [`docs/index.md`](docs/index.md).
131 changes: 124 additions & 7 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,126 @@ hide:

# netsuite python library

Make async requests to NetSuite SuiteTalk SOAP/REST Web Services and Restlets
Make async requests to NetSuite SuiteTalk SOAP/REST Web Services and Restlets.

This is an unofficial fork. It is not published to PyPI; install directly from this repository.

## Installation

With default features (REST API + Restlet support):

pip install netsuite
pip install git+https://github.com/vlouvet/netsuite.git

With Web Services SOAP API support:
With Web Services SOAP API support (deprecated by NetSuite as of the 2027.1 release):

pip install netsuite[soap_api]
pip install "netsuite[soap_api] @ git+https://github.com/vlouvet/netsuite.git"

With CLI support:

pip install netsuite[cli]
pip install "netsuite[cli] @ git+https://github.com/vlouvet/netsuite.git"

With `orjson` package (faster JSON handling):

pip install netsuite[orjson]
pip install "netsuite[orjson] @ git+https://github.com/vlouvet/netsuite.git"

With all features:

pip install netsuite[all]
pip install "netsuite[all] @ git+https://github.com/vlouvet/netsuite.git"


## Authentication

The library supports three authentication methods:

| Auth class | Mechanism | When to use |
|---|---|---|
| `OAuth2ClientCredentialsAuth` | OAuth 2.0 Client Credentials w/ JWT Bearer (M2M) | New server-to-server integrations. **Recommended.** |
| `OAuth2AccessTokenAuth` | Bring-your-own access token | When an upstream auth flow (e.g. Authorization Code in your web app) already has a token. |
| `TokenAuth` | OAuth 1.0a Token-Based Auth (TBA) | Legacy. Still works, but new integrations should use OAuth 2.0. |

> **Note on SOAP.** NetSuite has announced that SOAP Web Services will be removed in the **2027.1 release**. Plan REST + OAuth 2.0 migrations accordingly. Instantiating `NetSuiteSoapApi` emits a `DeprecationWarning` to surface this at runtime.

### OAuth 2.0 — Client Credentials (M2M, JWT Bearer)

Upload a public key/certificate to your NetSuite integration record, save the certificate ID NetSuite gives you, and supply the matching private key:

```python
from netsuite import Config, NetSuite, OAuth2ClientCredentialsAuth

config = Config(
account="123456_SB1",
auth=OAuth2ClientCredentialsAuth(
client_id="<your integration consumer key>",
certificate_id="<the kid NetSuite assigned when you uploaded the cert>",
private_key_pem=open("/path/to/private.pem").read(),
scope=["rest_webservices"], # default; can also include "restlets" or "suite_analytics"
algorithm="PS256", # default; "RS256", "ES256/384/512" also accepted
),
)

ns = NetSuite(config)
result = await ns.rest_api.get("/record/v1/customer/1337")
```

The library transparently mints a JWT, exchanges it at NetSuite's token endpoint, caches the access token until ~1 minute before expiry, and refreshes automatically. No additional code on your side.

### OAuth 2.0 — Authorization Code Grant

Best for apps acting on behalf of a NetSuite user. The library provides helpers for the URL building and token exchange; the redirect dance is yours.

```python
from netsuite.oauth2 import (
build_authorization_url,
exchange_authorization_code,
)

# 1. Send the user here:
auth_url = build_authorization_url(
"123456_SB1",
client_id="<your client id>",
redirect_uri="https://app.example.com/oauth/callback",
scope=["rest_webservices"],
state="<opaque CSRF token you stored in the user's session>",
)

# 2. NetSuite redirects to your callback with `?code=...&state=...`.
# After verifying state, exchange the code:
token = await exchange_authorization_code(
"123456_SB1",
code=request.args["code"],
client_id="<your client id>",
client_secret="<your client secret>", # OR pass certificate_id+private_key_pem
redirect_uri="https://app.example.com/oauth/callback",
)

# 3. Pass the token into the library:
config = Config(
account="123456_SB1",
auth=OAuth2AccessTokenAuth(
access_token=token.access_token,
refresh_token=token.refresh_token,
expires_at=token.expires_at,
),
)
```

`OAuth2AccessTokenAuth` does *not* refresh automatically — your application is responsible for refreshing using the `refresh_token` and constructing a new `Config`.

### Legacy OAuth 1.0a (TBA)

```python
from netsuite import Config, NetSuite, TokenAuth

config = Config(
account="123456_SB1",
auth=TokenAuth(
consumer_key="...", consumer_secret="...",
token_id="...", token_secret="...",
),
)
```

Still fully supported, but consider migrating: NetSuite is steering integrations toward OAuth 2.0.

## Programmatic use - Basic Example

Expand Down Expand Up @@ -122,6 +218,27 @@ async def async_main() -> dict:
asyncio.run(async_main())
```

## Programmatic use - SuiteQL pagination

NetSuite caps a single SuiteQL response at `limit=1000` rows, and a single query overall at 100,000 rows. To stream every page of a result set without manually wiring up the `next` link, use `suiteql_paginated`:

```python
async def fetch_all_transactions():
rows = []
async for page in ns.rest_api.suiteql_paginated(
q="SELECT id, tranid FROM transaction",
limit=1000,
):
rows.extend(page["items"])
return rows
```

Each yielded `page` is the raw NetSuite response dict (with `items`, `count`, `hasMore`, `links`, …). Iteration stops automatically when `hasMore` is False.

To retrieve more than 100,000 rows from a query, partition by a WHERE clause and run multiple paginated queries — for example by `id` ranges or date windows.

> **`ORDER BY` caveat.** NetSuite has a known quirk where a SuiteQL query with `ORDER BY` and a small `limit` (the default 10) can return zero items. Either request a larger page (`limit=1000`) or sort client-side after fetching. See [#29](https://github.com/jacobsvante/netsuite/issues/29).

## Programmatic use - Download Large Files Using SOAP API
When working with large files, you might find that responses are truncated if they exceed 10MB. This limitation stems from the default settings in Zeep. To overcome this, enable the `xml_huge_tree` option in the Zeep client settings.

Expand Down
4 changes: 2 additions & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
site_name: netsuite

repo_name: jacobsvante/netsuite
repo_url: https://github.com/jacobsvante/netsuite
repo_name: vlouvet/netsuite
repo_url: https://github.com/vlouvet/netsuite

theme:
name: material
4 changes: 2 additions & 2 deletions netsuite/cli/misc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import pkg_resources
from importlib.metadata import version as _package_version

__all__ = ()

Expand All @@ -10,4 +10,4 @@ def add_parser(parser, subparser):


def version() -> str:
return pkg_resources.get_distribution("netsuite").version
return _package_version("netsuite")
Loading
Loading