Metadata-Version: 2.4
Name: drupal-jsonapi-params
Version: 0.1.0
Summary: Query string builder for Drupal's JSON:API — Python port of drupal-jsonapi-params (JS)
Project-URL: Homepage, https://github.com/VincenzoGambino/drupal-jsonapi-params-python
Project-URL: Repository, https://github.com/VincenzoGambino/drupal-jsonapi-params-python
Project-URL: Issues, https://github.com/VincenzoGambino/drupal-jsonapi-params-python/issues
Author: Vincenzo Gambino
License: ISC
License-File: LICENSE
License-File: NOTICE
Keywords: drupal,drupal-jsonapi,json-api,jsonapi,query-builder,query-string
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: ISC License (ISCL)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: twine>=5; extra == 'dev'
Description-Content-Type: text/markdown

# drupal-jsonapi-params-python

Python port of [drupal-jsonapi-params](https://github.com/d34dman/drupal-jsonapi-params) — a query string builder for Drupal's JSON:API. Pure Python, no dependencies, Python 3.9+.

**Scope:** query string generation only. No HTTP, no auth, no response parsing.

## Installation

```sh
pip install drupal-jsonapi-params
```

Or for development:

```sh
git clone https://github.com/VincenzoGambino/drupal-jsonapi-params-python.git
cd drupal-jsonapi-params-python
pip install -e ".[dev]"
```

## Quick start

```python
from drupal_jsonapi_params import DrupalJsonApiParams, FilterOperator

api = DrupalJsonApiParams()
(
    api
    .add_group("publish_status", "OR", "parent_group")
    .add_group("parent_group", "AND")
    .add_filter("status", "1")
    .add_filter("status", "2", "<>", "publish_status")
    .add_page_limit(5)
    .add_page_offset(20)
    .add_fields("node--article", ["field_a.id", "field_b.uid"])
    .add_include(["field_a.id", "field_b.uid"])
    .add_sort("id", "DESC")
    .add_sort("uid")
)

# URL-encoded (default)
print(api.get_query_string())

# Unencoded (useful for debugging / reading)
print(api.get_query_string(encode=False))
```

## JS → Python API reference

| JavaScript | Python |
|---|---|
| `new DrupalJsonApiParams()` | `DrupalJsonApiParams()` |
| `addFilter(path, value, op, group, key)` | `add_filter(path, value, operator, member_of, key)` |
| `addInclude(fields)` | `add_include(fields)` |
| `addFields(type, fields)` | `add_fields(resource_type, fields)` |
| `addSort(path, direction)` | `add_sort(path, direction)` |
| `addPageLimit(n)` | `add_page_limit(n)` |
| `addPageOffset(n)` | `add_page_offset(n)` |
| `addGroup(name, conjunction, memberOf)` | `add_group(name, conjunction, member_of)` |
| `addCustomParam({key: val})` | `add_custom_param({"key": val})` |
| `getQueryString({encode: false})` | `get_query_string(encode=False)` |
| `getQueryString({addQueryPrefix: true})` | `get_query_string(add_query_prefix=True)` |
| `getQueryObject()` | `get_query_object()` |
| `clear()` | `clear()` |
| `initialize(input)` | `initialize(input)` |
| `initializeWithQueryString(s)` | `initialize_with_query_string(s)` |
| `initializeWithQueryObject(obj)` | `initialize_with_query_object(obj)` |
| `setQsOption({...})` | `set_output_options(encode=..., add_query_prefix=...)` |
| `getQsOption()` | `get_output_options()` |
| `Operators.equal` | `FilterOperator.EQUAL` |

## Side-by-side examples

### Simple equality filter

**JavaScript**
```js
const api = new DrupalJsonApiParams();
api.addFilter('status', '1');
api.getQueryString({ encode: false });
// → filter[status]=1
```

**Python**
```python
api = DrupalJsonApiParams()
api.add_filter("status", "1")
api.get_query_string(encode=False)
# → filter[status]=1
```

### IN filter

**JavaScript**
```js
api.addFilter('uid.name', ['admin', 'john'], 'IN');
```

**Python**
```python
api.add_filter("uid.name", ["admin", "john"], "IN")
# or with enum:
api.add_filter("uid.name", ["admin", "john"], FilterOperator.IN)
```

### BETWEEN filter

**JavaScript**
```js
api.addFilter('changed', ['0', '123456789'], 'BETWEEN');
```

**Python**
```python
api.add_filter("changed", ["0", "123456789"], "BETWEEN")
```

### IS NULL filter

**JavaScript**
```js
api.addFilter('status', null, 'IS NULL');
```

**Python**
```python
api.add_filter("status", None, "IS NULL")
```

### Grouped filters

**JavaScript**
```js
api
  .addGroup('and-group', 'AND')
  .addFilter('uid.name', 'admin', '=', 'and-group')
  .addFilter('status', '1', '=', 'and-group');
```

**Python**
```python
(
    api
    .add_group("and-group", "AND")
    .add_filter("uid.name", "admin", "=", "and-group")
    .add_filter("status", "1", "=", "and-group")
)
```

### Round-trip

**JavaScript**
```js
const qs = api.getQueryString();
api.clear();
api.initializeWithQueryString(qs);
```

**Python**
```python
qs = api.get_query_string()
api.clear()
api.initialize_with_query_string(qs)
```

### Custom parameters

**JavaScript**
```js
api.addCustomParam({ foo: 'bar' })
   .addCustomParam({ foo: { bar: 'baz' } });
```

**Python**
```python
api.add_custom_param({"foo": "bar"}).add_custom_param({"foo": {"bar": "baz"}})
```

## Output options (qs compatibility)

The JS library delegates serialization to the [`qs`](https://github.com/ljharb/qs)
library and exposes its full options via `getQueryString()` / `setQsOption()`.

This Python port supports only the two options that are relevant for Drupal JSON:API
usage:

| JS (`qs` option) | Python | Default |
|---|---|---|
| `encode: false` | `get_query_string(encode=False)` | `True` |
| `addQueryPrefix: true` | `get_query_string(add_query_prefix=True)` | `False` |

Other `qs` options (e.g. `arrayFormat`, `delimiter`, `sort`, `charset`) are **not
supported**. The bracket-notation format used by Drupal's JSON:API is always applied.

You can also persist options across calls:

```python
api.set_output_options(encode=False, add_query_prefix=True)
api.get_query_string()  # uses stored options
```

## FilterOperator enum

```python
from drupal_jsonapi_params import FilterOperator

FilterOperator.EQUAL          # "="
FilterOperator.NOT_EQUAL      # "<>"
FilterOperator.GREATER_THAN   # ">"
FilterOperator.LESS_THAN      # "<"
FilterOperator.IN             # "IN"
FilterOperator.NOT_IN         # "NOT IN"
FilterOperator.BETWEEN        # "BETWEEN"
FilterOperator.NOT_BETWEEN    # "NOT BETWEEN"
FilterOperator.IS_NULL        # "IS NULL"
FilterOperator.IS_NOT_NULL    # "IS NOT NULL"
FilterOperator.CONTAINS       # "CONTAINS"
FilterOperator.STARTS_WITH    # "STARTS_WITH"
FilterOperator.ENDS_WITH      # "ENDS_WITH"
```

Because `FilterOperator` extends `str`, you can pass either the enum member or its
string value interchangeably:

```python
api.add_filter("title", "Foo", FilterOperator.CONTAINS)
api.add_filter("title", "Foo", "CONTAINS")  # equivalent
```

> **Note:** `FilterOperator.NOT_EQUAL` uses `"<>"`, Drupal's canonical not-equal
> operator. The JS library's test suite uses `"!="`, but Drupal's JSON:API may
> reject it — you can pass either string directly if needed.
