Skip to content

Conversation

@hoijnet
Copy link
Collaborator

@hoijnet hoijnet commented Jan 7, 2026

This PR aligns the Python client's RDFList predicates and WOQL.localize functionality with the JavaScript client implementation, ensuring consistent variable scoping behavior across both clients.

Background

The JavaScript client has comprehensive support for RDF List operations through a WOQLLibrary class with rdflist_* methods, and a localize method for variable scoping. The Python client was missing these features, creating an inconsistency between the two client libraries.

Changes

New Classes and Functions

VarsUnique - Generates unique variable names to prevent collisions in library functions:

from terminusdb_client.woqlquery import VarsUnique

v = VarsUnique('x', 'y', 'z')
# v.x.name = 'x_1', v.y.name = 'y_2', v.z.name = 'z_3'

WOQLQuery.localize() - Creates localized variable scopes to prevent local variables from leaking to outer scope:

(localized, v) = WOQLQuery().localize({
    'consSubject': 'v:list_head',  # Outer parameter (bound via eq)
    'valueVar': 'v:first_value',   # Outer parameter
    'temp_cell': None,             # Local-only variable (hidden by select)
})

query = localized(
    WOQLQuery().woql_and(
        WOQLQuery().triple(v.consSubject, 'rdf:type', 'rdf:List'),
        WOQLQuery().triple(v.temp_cell, 'rdf:rest', 'rdf:nil'),
        WOQLQuery().triple(v.temp_cell, 'rdf:first', v.valueVar)
    )
)

WOQLQuery.lib() - Returns WOQLLibrary instance for RDFList operations:

query = WOQLQuery().lib().rdflist_peek('v:list_head', 'v:first_value')

WOQLLibrary RDFList Operations

All RDFList predicates from the JavaScript client are now available in Python:

Method Description
rdflist_list(cons_subject, list_var) Collect all elements into array
rdflist_peek(cons_subject, value_var) Get first element
rdflist_last(cons_subject, value_var) Get last element
rdflist_nth0(cons_subject, index, value_var) Get element at 0-indexed position
rdflist_nth1(cons_subject, index, value_var) Get element at 1-indexed position
rdflist_member(cons_subject, value) Traverse list yielding each element
rdflist_length(cons_subject, length_var) Get list length
rdflist_pop(cons_subject, value_var) Pop first element in-place
rdflist_push(cons_subject, value) Push to front in-place
rdflist_append(cons_subject, value, new_cell) Append to end
rdflist_clear(cons_subject, new_list_var) Delete all cells, return rdf:nil
rdflist_empty(list_var) Create empty list (rdf:nil)
rdflist_is_empty(cons_subject) Check if list equals rdf:nil
rdflist_slice(cons_subject, start, end, result_var) Extract range as array
rdflist_insert(cons_subject, position, value) Insert at position
rdflist_drop(cons_subject, position) Remove at position
rdflist_swap(cons_subject, pos_a, pos_b) Swap elements at positions

Variable Scoping

The localize method implements the same pattern as JavaScript:

  1. Parameters with non-None values are bound from outer scope via eq() clauses placed outside the select("") wrapper
  2. Parameters with None values are local-only variables, hidden by select("") with empty variable list
  3. Unique variable names are generated via VarsUnique to prevent collisions

This ensures that:

  • Local variables don't leak to outer query scope
  • Outer parameters remain visible in query results
  • Multiple library calls don't have variable name collisions

Files Modified

  • terminusdb_client/woqlquery/woql_query.py - Added VarsUnique class, localize method, lib method, and WOQLLibrary class
  • terminusdb_client/woqlquery/__init__.py - Updated exports

Files Added

  • terminusdb_client/tests/test_woql_localize.py - Unit tests for localize and VarsUnique (13 tests)
  • terminusdb_client/tests/test_woql_rdflist.py - Unit tests for RDFList operations (30 tests)
  • docs/PR_RDFLIST_LOCALIZE_ALIGNMENT.md - This documentation

Tests

All 43 new unit tests pass:

  • 13 tests for localize and VarsUnique functionality
  • 30 tests for RDFList operations and variable scoping

Tests verify:

  • JSON structure of generated queries
  • Variable scoping behavior
  • Unique variable name generation
  • Error handling for invalid parameters

Compatibility

The implementation aligns with the JavaScript client's woqlLibrary.js and woqlQuery.js:

  • Same method names and signatures
  • Same variable scoping pattern using select("") and eq() bindings
  • Same unique variable naming convention with counter suffix

Usage Example

from terminusdb_client import Client
from terminusdb_client.woqlquery import WOQLQuery

client = Client("http://localhost:6363")
client.connect(db="mydb")

# Get length of an rdf:List
query = WOQLQuery().woql_and(
    WOQLQuery().triple('doc:mylist', 'tasks', 'v:list_head'),
    WOQLQuery().lib().rdflist_length('v:list_head', 'v:count')
)
result = client.query(query)
print(f"List has {result['bindings'][0]['count']} elements")

# Get all list elements
query = WOQLQuery().woql_and(
    WOQLQuery().triple('doc:mylist', 'tasks', 'v:list_head'),
    WOQLQuery().lib().rdflist_member('v:list_head', 'v:item')
)
result = client.query(query)
for binding in result['bindings']:
    print(f"Item: {binding['item']}")

@hoijnet hoijnet merged commit 874c76a into main Jan 7, 2026
12 checks passed
@hoijnet hoijnet deleted the align-rdflist-predicates branch January 7, 2026 16:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants