Skip to content

Changelog

All notable changes to Rayforce-Py will be documented in this file.

You can also subscribe for release notifications by joining our Zulip!

2.3.0

Improvements

  • Rebuilt against Rayforce C core v2.8.0, up from the 2.5.x line 2.2.1 shipped with. The core is now pinned by tag — RAYFORCE_REF (default v2.8.0) in the Makefile and scripts/prepare_build.sh, next to the existing RAYFORCE_Q_REF — so a wheel is built from a known core rather than from whatever master holds on release day. Engine highlights since 2.5.x: a grouped select that matches no row keeps every aggregate and literal projection column; every sort path keeps equal keys in source order, and pivot emits rows and columns in first-seen order; grouped var / stddev are computed numerically stably and pearson_corr is null when a side has no variance; count (distinct …) accepts a symbol-vector by:; the key map a keyed upsert builds is kept on the table and maintained across in-place writes, and a new in-place delete keeps it too; integer partitions of a parted table are ordered by value; .ipc.open takes an options dict with a compress threshold and loopback links are never compressed; plus a broad grouped-aggregate performance sweep. See the core release notes for v2.6.0 through v2.8.0.
  • TCPServer runs on the core's poll-based IPC server. The legacy blocking server API the extension used was removed from the core in v2.8.0; the server now owns a dedicated core poll with an IPC listener registered on it and drives it in bounded passes. The Python API and its blocking, interruptible listen() loop are unchanged.

Notes

  • Empty symbols are nulls. Since core v2.6.0 the engine recognises in-band nulls at construction and treats the empty symbol and the empty string as their types' nulls. Reading such an element out of a Symbol Vector (v[i], to_python()) or a Table row now yields Null where it used to yield Symbol(""). Atoms are unaffected: Symbol("").value is still "", and String("") still round-trips as "".

2.2.1

Bug fixes

  • Table.drop() no longer reorders the surviving columns. The columns left behind now keep the order they had before the drop. (#19)
  • SQL SELECT DISTINCT now deduplicates instead of returning every matching row. (#20)
  • SQL WHERE clauses accept a literal on the left of a comparison. WHERE 100 < price now compiles the same as WHERE price > 100; previously the literal-on-left form produced a broken query. (#23)
  • Unsigned integer columns no longer wrap to negative values. The PyArrow plugin was sign-flipping uint16 / uint32 / uint64 values, and the Pandas and Polars converters had the same gap. All three now widen an unsigned column to the next signed width, so the values survive the conversion. uint64 has no lossless target and still maps to I64. (#18, #22)
  • Raw vector readers reject nulls and atoms rather than reading past the object header. (#21)

Notes

  • Out-of-range date strings now raise instead of rolling over. Rebuilding against the current Rayforce C core picks up bounds validation in the DATE string cast, so Date("2025-02-30"), Date("2023-02-29") and Date("2025-13-01") raise rather than silently normalizing into the following month. Valid dates, including leap days such as Date("2024-02-29"), are unaffected.

2.2.0

New Features

  • Q connection authentication and connect timeout. KDBEngine now accepts user, password, and timeout_ms arguments. Credentials are sent during the Q login handshake, and timeout_ms bounds the connect attempt. Both default to off, so existing code is unaffected.

Improvements

  • Q client rebuilt on the shared rayforce-q core. The Q wire-format implementation moved out of the Python extension into the standalone, language-neutral rayforce-q C library (pinned to v2.0.0), shared across Rayforce language bindings. The blocking network round-trip now releases the GIL, so other Python threads keep running while a query is in flight, and KDB+ dictionaries decode natively.

2.1.1

New Features

  • Rebuilt against the latest Rayforce C core, picking up a batch of engine fixes and improvements. Highlights: new partitioned-store helpers (.db.parted.fill, .db.parted.tables) and journal .log.purge; an .ipc.open connect timeout plus fixes for localhost / lazy-result IPC hangs; and a broad reliability sweep (silent-failure hardening, durable column-store format versioning, and a worker-heap teardown crash fix).

2026-06-24 | 🔗 PyPI | 🔗 GitHub

2.1.0

New Features

  • Restored WebSocket support. The WSClient / WSServer classes and the rayforce.network.websocket module are back (they were removed in 2.0.0). The server is an async Python implementation, built on the websockets library, that evaluates Rayforce queries in-process. Install with the websocket extra (pip install rayforce-py[websocket]). See the WebSocket guide.
  • Added the PyArrow plugin. from_arrow() converts a pyarrow.Table into a Rayforce Table, mirroring the Polars / Pandas plugins. A column of strings maps to String by default, or to Symbol via the opt-in strings_as_symbols=True flag. Requires the parquet extra (pip install rayforce-py[parquet]).

Bug fixes

  • Fixed mis-scaled Parquet timestamps. load_parquet() now reads microsecond- and millisecond-precision timestamp columns at the correct precision; previously non-nanosecond units were mis-scaled. Parquet reading now shares the PyArrow plugin's conversion path.

2026-06-19 | 🔗 PyPI | 🔗 GitHub

2.0.2

New Features

  • Added the ungroup verb. Table.ungroup() flattens nested LIST columns into row form — each list cell expands to one row, with atom columns replicated per element (the inverse of a by-grouping). Also available as a deferred step on select queries: t.select(...).by(...).ungroup().execute().

Bug fixes

  • Fixed a bug when the Timestamp type didn't account for TZ offset when using from_csv handler

2026-06-18 | 🔗 PyPI | 🔗 GitHub

2.0.1

Bug Fixes

Fix Type error when attempting to insert/upsert into an empty table.

2026-06-17 | 🔗 PyPI | 🔗 GitHub

2.0.0

First release against the Rayforce v2 C core. This is a major release with breaking changes to the type system, the network layer, and the plugin surface. The 1.0.x line continues to be maintained against the v1 core.

Breaking Changes

  • Built against the Rayforce v2 C core. The bundled engine, build pipeline (make app), and binary protocol all target v2. Code written against the v1 core may need updates — see the items below.
  • Removed the C8 / Char type. v2 has no standalone character type; a single character is now a length-1 String. C8 is no longer importable from rayforce.
  • Removed WebSocket support. The WSClient / WSServer classes and the entire rayforce.network.websocket module have been removed. Use TCP IPC (TCPClient / TCPServer) for networked queries.
  • Renamed the KDB+ plugin. rayforce.plugins.raykx is now rayforce.plugins.kdb; import KDBEngine from the new path.
  • Renumbered scalar type codes to match the v2 core. Atoms use the negative of these values (see the Data Types overview):
Type v1 code v2 code
Symbol 6 12
F64 10 7
Date 7 8
Time 8 9
Timestamp 9 10
String 13
F32 6

New Features

  • Added the F32 (32-bit floating-point) type. Arithmetic on F32 promotes to F64.
  • Added the RayforceLimitError exception class (core EC_LIMIT).

Notes

  • String is now a first-class type (no longer modeled as a vector of C8).

2026-06-12 | 🔗 PyPI | 🔗 GitHub

1.0.0

  • Project has came out of beta. Stable release

2026-04-20 | 🔗 PyPI | 🔗 GitHub

0.6.3

Bug Fixes

  • Integer null sentinel handling: I16, I32, and I64 scalars now correctly recognize null sentinels (0Nh, 0Ni, 0Nj) and return None from to_python(). This fixes null comparisons in operations where unmatched rows returned raw sentinel values (e.g., -9223372036854775808) instead of None.

2026-04-15 | 🔗 PyPI | 🔗 GitHub

0.6.2

Bug Fixes

  • Now library passes correct contiguous numpy arrays within from_numpy function
  • F64 vectors can now be used in median calculations

2026-03-15 | 🔗 PyPI | 🔗 GitHub

0.6.1

New Features

  • Fancy indexing for Tables: Table.__getitem__ now supports multiple indexing modes beyond column access:

    • Expression filter: table[Column("age") > 35] — filter rows by condition. Supports & (and) and | (or) for combining expressions.
    • Integer row access: table[0], table[-1] — access a single row by index, returns a Dict.
    • Slicing: table[1:3], table[:5], table[-2:] — row slicing backed by the C-level TAKE operation.
    • Index list: table[[0, 2, 5]] — select specific rows by position.
  • Vector.from_numpy() auto-widening: Unsupported numpy dtypes are now automatically widened to the nearest supported type: float32/float16F64, int8I16, uint16I32, uint32I64.

  • Vector.from_numpy() bytes and UUID support: Byte string arrays (dtype='S') are automatically decoded to Symbol vectors. Object arrays of uuid.UUID values are detected and converted to GUID vectors.

  • NaT preservation: NaT (Not-a-Time) values in numpy datetime64 and timedelta64 arrays now survive round-trips through Vector.from_numpy() and Vector.to_numpy().

Bug Fixes

  • Table.to_numpy() with Timestamp columns: Fixed DTypePromotionError when calling to_numpy() on tables containing a mix of incompatible column types (e.g., integers, strings, and timestamps). Mixed-type tables now gracefully fall back to object dtype.

  • Filtering F64 by distinct - fixed

  • Vector.__getitem__ for U8 vectors: Fixed U8 vector elements being returned as B8(True/False) instead of U8(value). Both types are 1-byte, causing the C-level at_idx to misinterpret the type.

  • Vector.from_numpy() with explicit ray_type for temporal arrays: Fixed ValueError: cannot include dtype 'M' in a buffer when passing ray_type=Timestamp, ray_type=Date, or ray_type=Time with datetime64/timedelta64 arrays.

2026-03-03 | 🔗 PyPI | 🔗 GitHub

0.6.0

New Features

  • Vector.from_numpy() classmethod: Create vectors from NumPy arrays via bulk memory copy. Supports numeric types (int16, int32, int64, float64, uint8, bool), string arrays, and temporal types (datetime64Timestamp/Date, timedelta64Time). Handles epoch adjustment between NumPy (1970-01-01) and Rayforce (2000-01-01) automatically.

  • Vector.to_numpy() method: Export vector data to a NumPy array via bulk memory copy.

  • Vector.to_list() method: Export vector data to a Python list via bulk memory copy from the underlying C buffer.

  • Table.from_dict() classmethod: Create tables from a dictionary of NumPy arrays, Python lists, or Vectors.

  • Table.to_dict() method: Export table data to a Python dictionary of lists via bulk memory copy.

  • Table.to_numpy() method: Export table data to a 2D NumPy array via bulk memory copy.

  • select("*", col=value) fix: Using "*" with computed columns in select() now correctly preserves all existing columns

Dependencies

  • NumPy (>=2.0.0) is now a required dependency.

2026-02-21 | 🔗 PyPI | 🔗 GitHub

0.5.11

New Features

  • Timestamp.shift_tz() method: Shift a Timestamp value by a timezone offset. Accepts any datetime.tzinfo (including datetime.timezone and zoneinfo.ZoneInfo).

  • Column.shift_tz() for queries: Shift an entire column of timestamps by a timezone offset within select() expressions.

2026-02-19 | 🔗 PyPI | 🔗 GitHub

0.5.10

Bug Fixes

  • Various bugfixes to the database operations

2026-02-11 | 🔗 PyPI | 🔗 GitHub

0.5.9

Bug Fixes

  • Fix boolean operator is_ performed over lists and vectors within table aggregations

2026-02-11 | 🔗 PyPI | 🔗 GitHub

0.5.8

Bug Fixes

  • Fix type conversion error during upsert operation

2026-02-10 | 🔗 PyPI | 🔗 GitHub

0.5.7

Bug Fixes

  • Fix boolean operator is_ performed over lists within table aggregations

2026-02-10 | 🔗 PyPI | 🔗 GitHub

0.5.6

Bug Fixes

  • Fix sorting for symbol vectors larger than 32 elements

2026-01-29 | 🔗 PyPI | 🔗 GitHub

0.5.5

Breaking Changes

  • SQLQuery API changed: SQLQuery now accepts a Table object instead of a table name string. Update calls from SQLQuery("table_name", query) to SQLQuery(Table("table_name"), query).

Bug Fixes

  • Division operators fixed: Fixed __truediv__ (/) and __floordiv__ (//) being incorrectly swapped for scalars and vectors.
  • Sorting MAPCOMMON columns: Fixed issue when tables containing MAPCOMMON columns were sorted incorrectly.

2026-01-28 | 🔗 PyPI | 🔗 GitHub

0.5.4

New Features

  • SQL over IPC: Send SQL queries to remote Rayforce servers via TCP or WebSocket using the new SQLQuery class. Documentation

2026-01-17 | 🔗 PyPI | 🔗 GitHub

0.5.3

New Features

  • SQL Query Support: Query tables using familiar SQL syntax with the new Table.sql() method. Supports SELECT, UPDATE, INSERT, and UPSERT (via ON CONFLICT) statements. Requires optional sqlglot dependency. See SQL documentation for details.

  • Vector type inference: Vector now automatically infers the element type from the first item when ray_type is not specified.

2026-01-17 | 🔗 PyPI | 🔗 GitHub

0.5.2

New Features

  • Vector operations: Added operations for Vector types including arithmetic (+, -, *, /), comparison (<, <=, >, >=, eq, ne), logical (and_, or_, not_), aggregation (sum, min, max, average), element access (first, last, take, at), set operations (union, sect, except_), search (find, within, filter), sort (asc, desc, iasc, rank, reverse, negate), and functional (map).

  • Dict operations: Added key/value access (key, value) and sort operations (asc, desc) for Dict types.

  • Scalar operations: Added arithmetic, comparison, and math operations (ceil, floor, round) for numeric scalar types (I16, I32, I64, F64).

2026-01-16 | 🔗 PyPI | 🔗 GitHub

0.5.1

New Features

  • pivot() method: Reshape data from long to wide format. Supports multiple index columns and aggregation functions (sum, count, avg, min, max, first, last). See Pivot documentation for details.

2026-01-16 | 🔗 PyPI | 🔗 GitHub

0.5.0

Breaking Changes

  • slice() renamed to take(): The slice(start_idx, tail) method has been renamed to take(n, offset=0) with a cleaner signature.

  • match_by_first renamed to key_columns: In upsert(), the parameter match_by_first has been renamed to key_columns for clarity.

  • Table.from_parted now accepts name instead of symfile argument.

  • Sorting API changed: xasc() and xdesc() methods have been replaced with order_by(*cols, desc=False) which returns a query that must be executed. This allows chaining with other query operations. See Order By documentation.

New Features

  • Bracket notation access: Access columns using table["col"] for a single column or table[["col1", "col2"]] for multiple columns.

  • shape(): method returns (rows, cols) to see table shape

  • len() support: Get row count with len(table).

  • head() and tail() methods: Get first/last n rows with table.head(5) or table.tail(5).

  • describe() method: Get summary statistics (count, mean, min, max) for numeric columns.

  • dtypes property: Get column types as a dictionary with table.dtypes.

  • drop() method: Remove columns with table.drop("col1", "col2"). See Transform documentation.

  • rename() method: Rename columns with table.rename({"old": "new"}). See Transform documentation.

  • cast() method: Change column types with table.cast("col", I64). See Transform documentation.

  • Chainable order_by(): Sort results as part of query chain: table.select(...).where(...).order_by("col").execute().

Bug fixes

  • Fix issue when applying a boolean vector filter for a table.

2026-01-15 | 🔗 PyPI | 🔗 GitHub

0.4.3

New Features

  • Apache Parquet Support (Beta): Added load_parquet() function to read Parquet files directly into Rayforce Tables. Uses PyArrow for efficient zero-copy data access where possible. See Parquet documentation for details.

2026-01-14 | 🔗 PyPI | 🔗 GitHub

0.4.2

Bug fixes

  • Fix CI/CD Makefile patch for manylinux container CPU flags

2026-01-14 | 🔗 PyPI | 🔗 GitHub

0.4.0

Breaking Changes

  • Network module restructuring: IPC classes (IPCClient, IPCConnection, IPCServer) have been removed from the main package exports and replaced with TCP classes (TCPClient, TCPServer). The IPC functionality has been reorganized into the rayforce.network module.

  • Error class renamed: RayforceIPCError has been removed and replaced with RayforceTCPError for network-related errors.

  • Table initialization API changed: Table.from_dict() and Table.from_name() methods are removed. Use Table(dict/str) constructor directly instead.

New Features

  • Added WebSocket support: New WSClient and WSServer classes in the rayforce.network.websocket module for WebSocket-based communication.
  • Added TCP client/server: New TCPClient and TCPServer classes in rayforce.network.tcp module exported from the main package.
  • Added asof_join() method to Table class for as-of joins (time-based joins). See Joins documentation for details.
  • Added ipcsave() method to Table and query objects (SelectQuery, UpdateQuery, InsertQuery, UpsertQuery, LeftJoin, InnerJoin, AsofJoin, WindowJoin) for saving query results in IPC connections.

2026-01-08 | 🔗 PyPI | 🔗 GitHub

0.3.1

  • Improve client-side IPC by allowing sending pythonic queries into the IPC connection https://py.rayforcedb.com/content/documentation/IPC.html#sending-query-objects

2026-01-04 | 🔗 PyPI | 🔗 GitHub

0.3.0

  • Add server-side IPC - initializing a Rayforce server via Python runtime
  • Improve IPC Client-side interface

2026-01-03 | 🔗 PyPI | 🔗 GitHub

0.2.2

  • Better C API error handling
  • Performance improvements for initializing RF types from python collections (List, Vector, Table)

2026-01-02 | 🔗 PyPI | 🔗 GitHub

0.2.1

  • C API speedup improvements
  • Add thread-safety block to disallow calling runtime from multiple threads

2025-12-28 | 🔗 PyPI | 🔗 GitHub

0.2.0

  • Enhanced error handling
  • Core speedup improvements

2025-12-28 | 🔗 PyPI | 🔗 GitHub

0.1.5

  • Added slice() method to Table for extracting subsets of rows (renamed to take() in 0.4.4)

2025-12-24 | 🔗 PyPI | 🔗 GitHub