Skip to content

Commit df3da6e

Browse files
Rayforce v2: squash rayforce2 onto master (1.0.0)
Squash of the rayforce2 line (52 commits since the 0.6.3 divergence) rebased onto master. Highlights: - Rebuilt against the Rayforce v2 C core (build pipeline, ARC refcount model, native IPC, per-vocabulary symfiles). - Type system: removed C8/Char (now a length-1 String), added F32; scalar type codes renumbered; String is a first-class type. - Network: removed WebSocket; KDB+ plugin moved raykx -> kdb. - Symbol quoted/literal (ATTR_QUOTED) handling; temporal/GUID raw-int/bytes constructors; pivot aggfunc default; TCPClient/TCPServer top-level exports. - Full v1->v2 documentation migration with execution-verified examples. Test suite: 2275 passed, 2 skipped, 2 xfailed.
1 parent 0fe7238 commit df3da6e

178 files changed

Lines changed: 8687 additions & 4736 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build-wheels.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ name: Build and publish wheels
33
on:
44
push:
55
tags:
6+
# Stable: X.Y.Z
67
- '[0-9]+.[0-9]+.[0-9]+'
8+
# Pre-release: 2.0.0a1, 2.0.0b1
9+
- '[0-9]+.[0-9]+.[0-9]+a[0-9]+'
10+
- '[0-9]+.[0-9]+.[0-9]+b[0-9]+'
711

812
jobs:
913
build_wheels:
@@ -20,7 +24,6 @@ jobs:
2024

2125
runs-on: ${{ matrix.os }}
2226
env:
23-
RAYFORCE_GITHUB: "https://github.com/RayforceDB/rayforce.git"
2427
MACOSX_DEPLOYMENT_TARGET: "11.0"
2528

2629
steps:

.github/workflows/coverage.yml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ name: Coverage
22

33
on:
44
push:
5-
branches: [master]
5+
branches: [master, rayforce2]
66

77
jobs:
88
coverage:
99
name: Coverage
1010
runs-on: ubuntu-latest
1111

12-
env:
13-
RAYFORCE_GITHUB: "https://github.com/RayforceDB/rayforce.git"
14-
1512
steps:
1613
- name: Checkout repository
1714
uses: actions/checkout@v4

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ coverage.xml
1616

1717
# Binaries
1818
rayforce/bin/
19-
rayforce/rayforce/
2019
rayforce.so
2120
_rayforce_c.so
2221
_rayforce_c.dylib
@@ -47,3 +46,4 @@ tables/
4746
*.parquet
4847
.claude/
4948
db/
49+
CLAUDE.md

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ repos:
3636
hooks:
3737
- id: pytest
3838
name: pytest
39-
entry: pytest
39+
entry: python3 -u -m pytest
4040
language: system
4141
types: [python]
4242
pass_filenames: false

Makefile

Lines changed: 34 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,80 @@
11
UNAME_S := $(shell uname -s)
22

3-
RAYFORCE_GITHUB = https://github.com/RayforceDB/rayforce.git
3+
RAYFORCE_GITHUB ?= https://github.com/RayforceDB/rayforce.git
4+
RAYFORCE_LOCAL_PATH ?=
45
EXEC_DIR = $(shell pwd)
56
LIBNAME = _rayforce.so
7+
PYTHON_VERSION = $(shell python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
8+
PYTHON_INCLUDE = $(shell python3 -c "import sysconfig; print(sysconfig.get_config_var('INCLUDEPY'))")
69

710

811
ifeq ($(UNAME_S),Darwin)
9-
RAYKX_LIB_NAME = libraykx.dylib
10-
RELEASE_LDFLAGS = $(shell python3 -c "import sysconfig; print(sysconfig.get_config_var('LDFLAGS') or '')")
11-
PYTHON_VERSION = $(shell python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
12-
SHARED_COMPILE_FLAGS = -lpython$(PYTHON_VERSION)
12+
RELEASE_LDFLAGS_PY = $(shell python3 -c "import sysconfig; print(sysconfig.get_config_var('LDFLAGS') or '')")
13+
SHARED_COMPILE_FLAGS = -undefined dynamic_lookup
1314
else ifeq ($(UNAME_S),Linux)
14-
RAYKX_LIB_NAME = libraykx.so
15-
RELEASE_LDFLAGS = $$(RELEASE_LDFLAGS)
15+
RELEASE_LDFLAGS_PY =
1616
SHARED_COMPILE_FLAGS =
1717
else
1818
$(error Unsupported platform: $(UNAME_S))
1919
endif
2020

2121
pull_rayforce_from_github:
22-
@rm -rf $(EXEC_DIR)/tmp/rayforce-c && \
23-
echo "⬇️ Cloning rayforce repo from GitHub..."; \
24-
git clone $(RAYFORCE_GITHUB) $(EXEC_DIR)/tmp/rayforce-c && \
25-
cp -r $(EXEC_DIR)/tmp/rayforce-c/core $(EXEC_DIR)/rayforce/rayforce
22+
@rm -rf $(EXEC_DIR)/tmp/rayforce-c
23+
@if [ -n "$(RAYFORCE_LOCAL_PATH)" ]; then \
24+
echo "📂 Copying rayforce from $(RAYFORCE_LOCAL_PATH)..."; \
25+
mkdir -p $(EXEC_DIR)/tmp && \
26+
rsync -a \
27+
--exclude='.git' --exclude='tmp' --exclude='build*' \
28+
--exclude='*.o' --exclude='*.so' --exclude='*.a' --exclude='*.dylib' \
29+
"$(RAYFORCE_LOCAL_PATH)/" "$(EXEC_DIR)/tmp/rayforce-c/"; \
30+
else \
31+
echo "⬇️ Cloning rayforce repo from $(RAYFORCE_GITHUB)..."; \
32+
git clone $(RAYFORCE_GITHUB) $(EXEC_DIR)/tmp/rayforce-c; \
33+
fi
2634

2735
patch_rayforce_makefile:
2836
@echo "🔧 Patching Makefile for Python support..."
29-
@echo '\n# Build Python module' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
30-
@echo 'PY_OBJECTS = core/rayforce_c.o core/raypy_init_from_py.o core/raypy_init_from_buffer.o core/raypy_read_from_rf.o core/raypy_queries.o core/raypy_io.o core/raypy_binary.o core/raypy_dynlib.o core/raypy_eval.o core/raypy_iter.o core/raypy_serde.o' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
31-
@echo 'PY_APP_OBJECTS = app/term.o' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
32-
@echo 'python: CFLAGS = $$(RELEASE_CFLAGS) -I$$(shell python3 -c "import sysconfig; print(sysconfig.get_config_var(\"INCLUDEPY\"))") -Wno-macro-redefined' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
33-
@echo 'python: LDFLAGS = $(RELEASE_LDFLAGS)' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
34-
@echo 'python: $$(CORE_OBJECTS) $$(PY_OBJECTS) $$(PY_APP_OBJECTS)' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
35-
@echo '\t$$(CC) -shared -o $(LIBNAME) $$(CFLAGS) $$(CORE_OBJECTS) $$(PY_OBJECTS) $$(PY_APP_OBJECTS) $$(LIBS) $$(LDFLAGS) $(SHARED_COMPILE_FLAGS)' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
37+
@mkdir -p $(EXEC_DIR)/tmp/rayforce-c/pyext
38+
@printf '\n\n# ---- Python extension target (added by rayforce-py) ----\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
39+
@printf 'PY_SRC = $$(wildcard pyext/*.c)\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
40+
@printf 'PY_OBJ = $$(PY_SRC:.c=.o)\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
41+
@printf 'python: CFLAGS = $$(RELEASE_CFLAGS) -DPY_SSIZE_T_CLEAN -I$(PYTHON_INCLUDE) -Ipyext -Wno-macro-redefined -Wno-unused-variable -Wno-unused-function\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
42+
@printf 'python: LDFLAGS = $$(RELEASE_LDFLAGS) $(RELEASE_LDFLAGS_PY)\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
43+
@printf 'python: $$(LIB_OBJ) $$(PY_OBJ)\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
44+
@printf '\t$$(CC) -shared -o $(LIBNAME) $$(CFLAGS) $$(LIB_OBJ) $$(PY_OBJ) $$(LIBS) $$(LDFLAGS) $(SHARED_COMPILE_FLAGS)\n' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
3645

3746
clean:
3847
@echo "🧹 Cleaning cache and generated files..."
3948
@cd $(EXEC_DIR) && rm -rf \
4049
rayforce/_rayforce_c.so \
4150
rayforce/_rayforce.c*.so \
4251
rayforce/librayforce.* \
43-
rayforce/plugins/libraykx.* \
4452
rayforce/bin \
4553
build/ \
4654
*.egg-info \
4755
dist/ && \
4856
find . -type d -name "__pycache__" -exec rm -rf {} +
4957
@cd $(EXEC_DIR) && rm -rf \
50-
rayforce/rayforce/ \
51-
tmp/ \
58+
tmp/
5259

5360
rayforce_binaries:
54-
@cp rayforce/capi/rayforce_c.c tmp/rayforce-c/core/rayforce_c.c
55-
@cp rayforce/capi/rayforce_c.h tmp/rayforce-c/core/rayforce_c.h
56-
@cp rayforce/capi/raypy_init_from_py.c tmp/rayforce-c/core/raypy_init_from_py.c
57-
@cp rayforce/capi/raypy_init_from_buffer.c tmp/rayforce-c/core/raypy_init_from_buffer.c
58-
@cp rayforce/capi/raypy_read_from_rf.c tmp/rayforce-c/core/raypy_read_from_rf.c
59-
@cp rayforce/capi/raypy_queries.c tmp/rayforce-c/core/raypy_queries.c
60-
@cp rayforce/capi/raypy_io.c tmp/rayforce-c/core/raypy_io.c
61-
@cp rayforce/capi/raypy_binary.c tmp/rayforce-c/core/raypy_binary.c
62-
@cp rayforce/capi/raypy_dynlib.c tmp/rayforce-c/core/raypy_dynlib.c
63-
@cp rayforce/capi/raypy_eval.c tmp/rayforce-c/core/raypy_eval.c
64-
@cp rayforce/capi/raypy_iter.c tmp/rayforce-c/core/raypy_iter.c
65-
@cp rayforce/capi/raypy_serde.c tmp/rayforce-c/core/raypy_serde.c
61+
@mkdir -p tmp/rayforce-c/pyext
62+
@cp rayforce/capi/*.c tmp/rayforce-c/pyext/
63+
@cp rayforce/capi/*.h tmp/rayforce-c/pyext/
6664
@cd tmp/rayforce-c && $(MAKE) python
6765
@cd tmp/rayforce-c && $(MAKE) release
68-
@cd tmp/rayforce-c/ext/raykx && $(MAKE) release
6966
@cp tmp/rayforce-c/$(LIBNAME) rayforce/_rayforce_c.so
70-
@cp tmp/rayforce-c/ext/raykx/$(RAYKX_LIB_NAME) rayforce/plugins/$(RAYKX_LIB_NAME)
7167
@mkdir -p rayforce/bin
7268
@cp tmp/rayforce-c/rayforce rayforce/bin/rayforce
7369
@chmod +x rayforce/bin/rayforce
7470

7571
app: pull_rayforce_from_github patch_rayforce_makefile rayforce_binaries
7672

7773
test:
78-
python3 -m pytest -x -vv tests/
74+
python3 -u -m pytest -x -vv tests/
7975

8076
test-cov:
81-
python3 -m pytest -m "" -x -vv --durations=20 --cov=rayforce --cov-report=term-missing --cov-report=html tests/
77+
python3 -u -m pytest -m "" -x -vv --durations=20 --cov=rayforce --cov-report=term-missing --cov-report=html tests/
8278

8379
lint:
8480
python3 -m ruff format tests/ rayforce/ benchmark/
@@ -90,9 +86,6 @@ lint:
9086
ipython:
9187
ipython -i -c "from rayforce import *"
9288

93-
websocket:
94-
95-
9689
benchmarkdb:
9790
python3 benchmark/run.py $(ARGS)
9891

@@ -106,15 +99,6 @@ citest:
10699
python3 -m ruff check rayforce/
107100
python3 -m ruff check tests/ --select I
108101
python3 -m mypy rayforce/
109-
python3 -m pytest -x -vv tests/
110-
111-
test-linux-versions:
112-
@chmod +x scripts/test_linux_versions.sh
113-
@./scripts/test_linux_versions.sh
114-
115-
test-macos-versions:
116-
@chmod +x scripts/test_macos_versions.sh
117-
@./scripts/test_macos_versions.sh
102+
python3 -u -m pytest -x -vv tests/
118103

119-
test-versions: test-linux-versions test-macos-versions
120104
# }}

benchmark/run.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import format as f
1111
import prepare
1212

13+
import rayforce as r
14+
1315

1416
def run(n_runs=15, n_warmup=5):
1517
"""
@@ -90,7 +92,7 @@ def run(n_runs=15, n_warmup=5):
9092
std_native_rayforce = (
9193
statistics.stdev(native_rayforce_times) if len(native_rayforce_times) > 1 else 0
9294
)
93-
95+
print(f"Rayforce version: {r.version}")
9496
f.print_results(
9597
query_name,
9698
median_rayforce_py,

docs/docs/content/CHANGELOG.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,51 @@ All notable changes to Rayforce-Py will be documented in this file.
55
!!! note ""
66
You can also subscribe for release notifications by joining our [:simple-zulip: Zulip](https://rayforcedb.zulipchat.com/#narrow/channel/549008-Discuss)!
77

8+
## **`2.0.0`**
9+
10+
First release against the **Rayforce v2 C core**. This is a major release with
11+
breaking changes to the type system, the network layer, and the plugin surface.
12+
The `1.0.x` line continues to be maintained against the v1 core.
13+
14+
### Breaking Changes
15+
16+
- **Built against the Rayforce v2 C core.** The bundled engine, build pipeline
17+
(`make app`), and binary protocol all target v2. Code written against the v1
18+
core may need updates — see the items below.
19+
- **Removed the `C8` / `Char` type.** v2 has no standalone character type; a
20+
single character is now a length-1 [`String`](./documentation/data-types/string.md).
21+
`C8` is no longer importable from `rayforce`.
22+
- **Removed WebSocket support.** The `WSClient` / `WSServer` classes and the
23+
entire `rayforce.network.websocket` module have been removed. Use
24+
[TCP IPC](./documentation/IPC.md) (`TCPClient` / `TCPServer`) for networked queries.
25+
- **Renamed the KDB+ plugin.** `rayforce.plugins.raykx` is now
26+
[`rayforce.plugins.kdb`](./documentation/plugins/kdb.md); import `KDBEngine`
27+
from the new path.
28+
- **Renumbered scalar type codes** to match the v2 core. Atoms use the negative
29+
of these values (see the [Data Types overview](./documentation/data-types/overview.md)):
30+
31+
| Type | v1 code | v2 code |
32+
|------|---------|---------|
33+
| `Symbol` | 6 | 12 |
34+
| `F64` | 10 | 7 |
35+
| `Date` | 7 | 8 |
36+
| `Time` | 8 | 9 |
37+
| `Timestamp` | 9 | 10 |
38+
| `String` || 13 |
39+
| `F32` || 6 |
40+
41+
### New Features
42+
43+
- **Added the `F32`** (32-bit floating-point) type. Arithmetic on `F32` promotes
44+
to `F64`.
45+
- **Added the `RayforceLimitError`** exception class (core `EC_LIMIT`).
46+
47+
### Notes
48+
49+
- `String` is now a first-class type (no longer modeled as a vector of `C8`).
50+
51+
2026-06-12 | **[🔗 PyPI](https://pypi.org/project/rayforce-py/2.0.0/)** | **[🔗 GitHub](https://github.com/RayforceDB/rayforce-py/releases/tag/2.0.0)**
52+
853
## **`1.0.0`**
954

1055
- Project has came out of beta. Stable release
@@ -266,7 +311,7 @@ All notable changes to Rayforce-Py will be documented in this file.
266311

267312
### New Features
268313

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

docs/docs/content/documentation/IPC.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ TCP IPC in Rayforce-Py consists of two main components:
99
- **`TCPServer`** - Listens for incoming TCP connections on a specified port
1010
- **`TCPClient`** - Connects to remote Rayforce instances and executes queries
1111

12-
The TCP IPC implementation uses Rayforce's native runtime event loop, ensuring optimal performance and seamless integration with the Rayforce ecosystem.
12+
The TCP IPC implementation uses Rayforce's native IPC protocol, ensuring optimal performance and seamless integration with the Rayforce ecosystem.
1313

1414
---
1515

1616
## :material-server: TCP Server
1717

18-
The `TCPServer` class allows you to create a server that listens for incoming TCP connections. The server runs on Rayforce's native event loop.
18+
The `TCPServer` class allows you to create a server that listens for incoming TCP connections. The server runs a blocking poll loop over Rayforce's native IPC protocol.
1919

2020
### Creating a Server
2121

@@ -26,20 +26,20 @@ To create a TCP server, import `TCPServer` and initialize it with a port:
2626

2727
>>> server = TCPServer(port=5000)
2828
>>> server
29-
TCPServer(port=5000)
29+
TCPServer(port=5000, idle)
3030
```
3131

3232
### Starting the Server
3333

34-
The `listen()` method starts the server and blocks until the event loop exits:
34+
The `listen()` method starts the server and blocks until the server is stopped:
3535

3636
```python
3737
>>> server.listen()
3838
Rayforce IPC Server listening on 5000 (id:123)
3939
```
4040

4141
!!! note "Blocking Operation"
42-
The `listen()` method is **blocking** - it will run until the event loop is stopped (e.g., via KeyboardInterrupt or program termination). The server automatically closes the port when the event loop exits, so no manual cleanup is needed.
42+
The `listen()` method is **blocking** - it will run until the server is stopped (e.g., via KeyboardInterrupt or program termination). The server automatically closes the port when the loop exits, so no manual cleanup is needed.
4343

4444
---
4545

@@ -117,7 +117,7 @@ The `ipcsave()` method allows you to save query results or tables to a remote Ra
117117
>>> query = table.select("id", "name").where(Column("id") > 1)
118118

119119
>>> # Save the query result to a variable on the remote server
120-
>>> TCPClient.execute(query.ipcsave("filtered_results"))
120+
>>> client.execute(query.ipcsave("filtered_results"))
121121
```
122122

123123
!!! note ""

docs/docs/content/documentation/data-types/char.md

Lines changed: 0 additions & 17 deletions
This file was deleted.

docs/docs/content/documentation/data-types/dict.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ The `Dict` type represents a key-value mapping. It's similar to Python dictionar
2323
Dict({'name': 'Alice', 'age': 29, 'active': True, 'score': 95.5, 'cache': {'enabled': True, 'ttl': 3600}})
2424

2525
>>> user_data.keys()
26-
Vector[6]
26+
Vector([Symbol('name'), Symbol('age'), Symbol('active'), Symbol('score'), Symbol('cache')])
2727
>>> [i for i in user_data.keys()]
2828
[
2929
Symbol('name'),
@@ -63,15 +63,3 @@ Vector([Symbol('a'), Symbol('b'), Symbol('c')])
6363
>>> d.value()
6464
List([I64(1), I64(2), I64(3)])
6565
```
66-
67-
### Sort Operations
68-
69-
```python
70-
>>> d = Dict({"c": 3, "a": 1, "b": 2})
71-
72-
>>> d.asc()
73-
Dict({'a': 1, 'b': 2, 'c': 3})
74-
75-
>>> d.desc()
76-
Dict({'c': 3, 'b': 2, 'a': 1})
77-
```

0 commit comments

Comments
 (0)