Skip to content

Commit f97008d

Browse files
(capi) - Reusability, cosmetics and performance improvements
1 parent 43cf84d commit f97008d

26 files changed

+1476
-2376
lines changed

Makefile

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pull_rayforce_from_github:
2727
patch_rayforce_makefile:
2828
@echo "🔧 Patching Makefile for Python support..."
2929
@echo '\n# Build Python module' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
30-
@echo 'PY_OBJECTS = core/rayforce_c.o core/raypy_conversion.o core/raypy_constructors.o core/raypy_readers.o core/raypy_operations.o core/raypy_queries.o core/raypy_io.o' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
30+
@echo 'PY_OBJECTS = core/rayforce_c.o core/raypy_init_from_py.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' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
3131
@echo 'PY_APP_OBJECTS = app/term.o' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
3232
@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
3333
@echo 'python: LDFLAGS = $(RELEASE_LDFLAGS)' >> $(EXEC_DIR)/tmp/rayforce-c/Makefile
@@ -53,12 +53,14 @@ clean:
5353
rayforce_binaries:
5454
@cp rayforce/capi/rayforce_c.c tmp/rayforce-c/core/rayforce_c.c
5555
@cp rayforce/capi/rayforce_c.h tmp/rayforce-c/core/rayforce_c.h
56-
@cp rayforce/capi/raypy_conversion.c tmp/rayforce-c/core/raypy_conversion.c
57-
@cp rayforce/capi/raypy_constructors.c tmp/rayforce-c/core/raypy_constructors.c
58-
@cp rayforce/capi/raypy_readers.c tmp/rayforce-c/core/raypy_readers.c
59-
@cp rayforce/capi/raypy_operations.c tmp/rayforce-c/core/raypy_operations.c
56+
@cp rayforce/capi/raypy_init_from_py.c tmp/rayforce-c/core/raypy_init_from_py.c
57+
@cp rayforce/capi/raypy_read_from_rf.c tmp/rayforce-c/core/raypy_read_from_rf.c
6058
@cp rayforce/capi/raypy_queries.c tmp/rayforce-c/core/raypy_queries.c
6159
@cp rayforce/capi/raypy_io.c tmp/rayforce-c/core/raypy_io.c
60+
@cp rayforce/capi/raypy_binary.c tmp/rayforce-c/core/raypy_binary.c
61+
@cp rayforce/capi/raypy_dynlib.c tmp/rayforce-c/core/raypy_dynlib.c
62+
@cp rayforce/capi/raypy_eval.c tmp/rayforce-c/core/raypy_eval.c
63+
@cp rayforce/capi/raypy_iter.c tmp/rayforce-c/core/raypy_iter.c
6264
@cd tmp/rayforce-c && $(MAKE) python
6365
@cd tmp/rayforce-c && $(MAKE) release
6466
@cd tmp/rayforce-c/ext/raykx && $(MAKE) release
@@ -81,6 +83,7 @@ lint:
8183
python3 -m ruff format tests/ rayforce/
8284
python3 -m ruff check rayforce/ --fix
8385
python3 -m mypy rayforce/
86+
clang-format -i rayforce/capi/*
8487

8588
ipython:
8689
ipython -i -c "from rayforce import *"

rayforce/_rayforce_c.pyi

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from collections.abc import Sequence
12
from typing import Any
23

34
TYPE_LIST: int
@@ -49,10 +50,10 @@ def init_date(value: Any) -> RayObject: ...
4950
def init_time(value: Any) -> RayObject: ...
5051
def init_timestamp(value: Any) -> RayObject: ...
5152
def init_guid(value: Any) -> RayObject: ...
52-
def init_list() -> RayObject: ...
53+
def init_list(item: list[Any]) -> RayObject: ...
5354
def init_table(columns: RayObject, values: RayObject) -> RayObject: ...
5455
def init_dict(keys: RayObject, values: RayObject) -> RayObject: ...
55-
def init_vector(type_code: int, length: int) -> RayObject: ...
56+
def init_vector(type_code: int, length_or_items: int | Sequence[Any]) -> RayObject: ...
5657
def read_i16(obj: RayObject) -> int: ...
5758
def read_i32(obj: RayObject) -> int: ...
5859
def read_i64(obj: RayObject) -> int: ...
@@ -76,20 +77,17 @@ def at_idx(iterable: RayObject, idx: int) -> RayObject: ...
7677
def insert_obj(iterable: RayObject, idx: int, ptr: RayObject) -> None: ...
7778
def push_obj(iterable: RayObject, ptr: RayObject) -> None: ...
7879
def set_obj(obj: RayObject, idx: RayObject, value: RayObject) -> None: ...
79-
def fill_vector(obj: RayObject, fill: list[Any]) -> None: ...
80-
def fill_list(obj: RayObject, fill: list[Any]) -> None: ...
8180
def get_obj_length(obj: RayObject) -> int: ...
8281
def eval_str(obj: RayObject) -> RayObject: ...
8382
def get_error_obj(error_obj: RayObject) -> RayObject: ...
8483
def binary_set(name: RayObject, obj: RayObject) -> None: ...
85-
def env_get_internal_function_by_name(name: str) -> RayObject: ...
86-
def env_get_internal_name_by_function(obj: RayObject) -> str: ...
84+
def env_get_internal_fn_by_name(name: str) -> RayObject: ...
85+
def env_get_internal_name_by_fn(obj: RayObject) -> str: ...
8786
def eval_obj(obj: RayObject) -> RayObject: ...
8887
def loadfn_from_file(filename: str, fn_name: str, args_count: int) -> RayObject: ...
8988
def quote(obj: RayObject) -> RayObject: ...
9089
def rc_obj(obj: RayObject) -> int: ...
9190
def set_obj_attrs(obj: RayObject, attr: int) -> None: ...
92-
def select(query: RayObject) -> RayObject: ...
9391
def update(query: RayObject) -> RayObject: ...
9492
def insert(table: RayObject, data: RayObject) -> RayObject: ...
9593
def upsert(table: RayObject, keys: RayObject, data: RayObject) -> RayObject: ...

rayforce/capi/rayforce_c.c

Lines changed: 38 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ int check_main_thread(void) {
1010
return 0;
1111
}
1212

13-
unsigned long current_thread_id;
14-
current_thread_id = (unsigned long)PyThread_get_thread_ident();
15-
16-
if (current_thread_id != g_main_thread_id) {
13+
if ((unsigned long)PyThread_get_thread_ident() != g_main_thread_id) {
1714
PyErr_Format(PyExc_RuntimeError,
1815
"Rayforce runtime can not be called from other threads than "
1916
"the one where it was initialized from");
@@ -23,58 +20,58 @@ int check_main_thread(void) {
2320
return 1;
2421
}
2522

26-
// Initialize runtime (FFI function)
23+
// GENERIC UTILS
2724
PyObject *raypy_init_runtime(PyObject *self, PyObject *args) {
2825
(void)self;
29-
(void)args; // Suppress unused parameter warning
26+
(void)args;
3027

3128
if (g_runtime != NULL) {
32-
// Runtime already initialized
33-
Py_RETURN_NONE;
29+
PyErr_SetString(PyExc_RuntimeError, "Runtime already initialized");
30+
return NULL;
3431
}
3532

36-
char *argv[] = {"raypy", "-r", "0", NULL};
37-
g_runtime = runtime_create(3, argv);
38-
if (g_runtime == NULL) {
33+
char *argv[] = {"py", "-r", "0", NULL};
34+
if (runtime_create(3, argv) == NULL) {
3935
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize Rayforce");
4036
return NULL;
4137
}
4238

43-
if (g_main_thread_id == 0)
44-
g_main_thread_id = (unsigned long)PyThread_get_thread_ident();
45-
39+
g_main_thread_id = (unsigned long)PyThread_get_thread_ident();
4640
Py_RETURN_NONE;
4741
}
42+
PyObject *raypy_wrap_ray_object(obj_p ray_obj) {
43+
if (ray_obj == NULL) {
44+
PyErr_SetString(PyExc_RuntimeError, "Rayforce object can't be null");
45+
return NULL;
46+
}
47+
48+
RayObject *result = (RayObject *)RayObjectType.tp_alloc(&RayObjectType, 0);
49+
if (result != NULL)
50+
result->obj = ray_obj;
51+
return (PyObject *)result;
52+
}
53+
// --
54+
55+
static void RayObject_dealloc(RayObject *self) {
56+
if (self->obj != NULL && self->obj != NULL_OBJ)
57+
drop_obj(self->obj);
58+
Py_TYPE(self)->tp_free((PyObject *)self);
59+
}
4860

4961
PyTypeObject RayObjectType = {
5062
PyVarObject_HEAD_INIT(NULL, 0).tp_name = "_rayforce_c.RayObject",
5163
.tp_basicsize = sizeof(RayObject),
5264
.tp_itemsize = 0,
53-
.tp_dealloc = NULL, // Will be set below
65+
.tp_dealloc = (destructor)RayObject_dealloc,
5466
.tp_flags = Py_TPFLAGS_DEFAULT,
5567
.tp_doc = "RayObject objects",
56-
.tp_methods = NULL, // Will be set below
68+
.tp_methods = NULL,
5769
.tp_new = PyType_GenericNew,
5870
};
5971

60-
static void RayObject_dealloc(RayObject *self) {
61-
if (self->obj != NULL && self->obj != NULL_OBJ)
62-
drop_obj(self->obj);
63-
Py_TYPE(self)->tp_free((PyObject *)self);
64-
}
65-
6672
PyMODINIT_FUNC PyInit__rayforce_c(void);
6773

68-
static struct PyModuleDef rayforce_module = {
69-
PyModuleDef_HEAD_INIT,
70-
.m_name = "_rayforce_c",
71-
.m_doc = "Python C API bus to Rayforce",
72-
.m_size = -1,
73-
.m_methods = NULL, // Will be set in PyInit__rayforce_c
74-
};
75-
76-
static PyMethodDef module_methods[] = {
77-
// Constructors
74+
static PyMethodDef rayforce_methods[] = {
7875
{"init_i16", raypy_init_i16, METH_VARARGS, "Create a new i16 object"},
7976
{"init_i32", raypy_init_i32, METH_VARARGS, "Create a new i32 object"},
8077
{"init_i64", raypy_init_i64, METH_VARARGS, "Create a new i64 object"},
@@ -99,8 +96,6 @@ static PyMethodDef module_methods[] = {
9996
"Create a new dictionary object"},
10097
{"init_vector", raypy_init_vector, METH_VARARGS,
10198
"Create a new vector object"},
102-
103-
// Readers
10499
{"read_i16", raypy_read_i16, METH_VARARGS, "Read i16 value from object"},
105100
{"read_i32", raypy_read_i32, METH_VARARGS, "Read i32 value from object"},
106101
{"read_i64", raypy_read_i64, METH_VARARGS, "Read i64 value from object"},
@@ -117,40 +112,26 @@ static PyMethodDef module_methods[] = {
117112
{"read_timestamp", raypy_read_timestamp, METH_VARARGS,
118113
"Read timestamp value from object"},
119114
{"read_guid", raypy_read_guid, METH_VARARGS, "Read GUID value from object"},
120-
121-
// Table operations
122115
{"table_keys", raypy_table_keys, METH_VARARGS, "Get table keys"},
123116
{"table_values", raypy_table_values, METH_VARARGS, "Get table values"},
124117
{"repr_table", raypy_repr_table, METH_VARARGS, "Format table"},
125-
126-
// Dictionary operations
127118
{"dict_keys", raypy_dict_keys, METH_VARARGS, "Get dictionary keys"},
128119
{"dict_values", raypy_dict_values, METH_VARARGS, "Get dictionary values"},
129120
{"dict_get", raypy_dict_get, METH_VARARGS, "Get value from dictionary"},
130-
131-
// Vector operations
132121
{"at_idx", raypy_at_idx, METH_VARARGS, "Get element at index"},
133122
{"insert_obj", raypy_insert_obj, METH_VARARGS, "Insert object at index"},
134123
{"push_obj", raypy_push_obj, METH_VARARGS,
135124
"Push object to the end of iterable"},
136125
{"set_obj", raypy_set_obj, METH_VARARGS, "Set object at index"},
137-
{"fill_vector", raypy_fill_vector, METH_VARARGS,
138-
"Fill vector from Python list (bulk operation)"},
139-
{"fill_list", raypy_fill_list, METH_VARARGS,
140-
"Fill list from Python list (bulk operation)"},
141-
142-
// Misc operations
143126
{"get_obj_length", raypy_get_obj_length, METH_VARARGS, "Get object length"},
144127
{"eval_str", raypy_eval_str, METH_VARARGS, "Evaluate string expression"},
145128
{"get_error_obj", raypy_get_error_obj, METH_VARARGS, "Get error object"},
146129
{"binary_set", raypy_binary_set, METH_VARARGS,
147130
"Set value to symbol or file"},
148-
{"env_get_internal_function_by_name",
149-
raypy_env_get_internal_function_by_name, METH_VARARGS,
150-
"Get internal function by name"},
151-
{"env_get_internal_name_by_function",
152-
raypy_env_get_internal_name_by_function, METH_VARARGS,
153-
"Get internal function name"},
131+
{"env_get_internal_fn_by_name", raypy_env_get_internal_fn_by_name,
132+
METH_VARARGS, "Get internal function by name"},
133+
{"env_get_internal_name_by_fn", raypy_env_get_internal_name_by_fn,
134+
METH_VARARGS, "Get internal function name"},
154135
{"eval_obj", raypy_eval_obj, METH_VARARGS, "Evaluate object"},
155136
{"loadfn_from_file", raypy_loadfn, METH_VARARGS,
156137
"Load function from shared library"},
@@ -159,35 +140,30 @@ static PyMethodDef module_methods[] = {
159140
{"set_obj_attrs", raypy_set_obj_attrs, METH_VARARGS,
160141
"Set object attributes"},
161142
{"get_obj_type", raypy_get_obj_type, METH_VARARGS, "Get object type"},
162-
163-
// Database operations
164-
{"select", raypy_select, METH_VARARGS, "Perform SELECT query"},
165143
{"update", raypy_update, METH_VARARGS, "Perform UPDATE query"},
166144
{"insert", raypy_insert, METH_VARARGS, "Perform INSERT query"},
167145
{"upsert", raypy_upsert, METH_VARARGS, "Perform UPSERT query"},
168-
169-
// IO operations
170146
{"hopen", raypy_hopen, METH_VARARGS, "Open file or socket handle"},
171147
{"hclose", raypy_hclose, METH_VARARGS, "Close file or socket handle"},
172148
{"write", raypy_write, METH_VARARGS, "Write data to file or socket"},
173-
174149
{"init_runtime", raypy_init_runtime, METH_VARARGS,
175150
"Initialize Rayforce runtime"},
176151

177152
{NULL, NULL, 0, NULL}};
178153

154+
static struct PyModuleDef rayforce_module = {
155+
PyModuleDef_HEAD_INIT, .m_name = "_rayforce_c",
156+
.m_doc = "Python C API bus to Rayforce", .m_size = -1,
157+
.m_methods = rayforce_methods};
158+
179159
static RayObject *g_null_obj = NULL;
180160

181161
PyMODINIT_FUNC PyInit__rayforce_c(void) {
182162
PyObject *m;
183163

184-
// Initialize RayObjectType
185-
RayObjectType.tp_dealloc = (destructor)RayObject_dealloc;
186-
187164
if (PyType_Ready(&RayObjectType) < 0)
188165
return NULL;
189166

190-
rayforce_module.m_methods = module_methods;
191167
m = PyModule_Create(&rayforce_module);
192168
if (m == NULL)
193169
return NULL;

rayforce/capi/rayforce_c.h

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ typedef struct {
5151
PyObject_HEAD obj_p obj;
5252
} RayObject;
5353

54-
// Runtime
5554
extern void *g_runtime;
5655

5756
int check_main_thread(void);
@@ -62,7 +61,12 @@ int check_main_thread(void);
6261
return NULL; \
6362
} while (0)
6463

65-
// Conversion functions (Python -> Rayforce)
64+
#if defined(__GNUC__) || defined(__clang__)
65+
#define UNUSED_SELF_PARAM __attribute__((unused))
66+
#else
67+
#define UNUSED_SELF_PARAM
68+
#endif
69+
6670
obj_p raypy_init_i16_from_py(PyObject *item);
6771
obj_p raypy_init_i32_from_py(PyObject *item);
6872
obj_p raypy_init_i64_from_py(PyObject *item);
@@ -71,14 +75,29 @@ obj_p raypy_init_c8_from_py(PyObject *item);
7175
obj_p raypy_init_b8_from_py(PyObject *item);
7276
obj_p raypy_init_u8_from_py(PyObject *item);
7377
obj_p raypy_init_symbol_from_py(PyObject *item);
78+
obj_p raypy_init_string_from_py(PyObject *item);
79+
obj_p raypy_init_list_from_py(PyObject *item);
7480
obj_p raypy_init_guid_from_py(PyObject *item);
7581
obj_p raypy_init_date_from_py(PyObject *item);
7682
obj_p raypy_init_time_from_py(PyObject *item);
7783
obj_p raypy_init_timestamp_from_py(PyObject *item);
7884
obj_p raypy_init_dict_from_py(PyObject *item);
7985
obj_p raypy_init_list_from_py(PyObject *item);
8086

81-
// Constructors
87+
// Temporal utility functions
88+
int is_leap_year(int year);
89+
long days_since_epoch(int year, int month, int day);
90+
int parse_iso_date(const char *str, Py_ssize_t len, int *year, int *month,
91+
int *day);
92+
int parse_iso_time(const char *str, Py_ssize_t len, int *hour, int *minute,
93+
int *second, int *microsecond);
94+
int parse_iso_timestamp(const char *str, Py_ssize_t len, int *year, int *month,
95+
int *day, int *hour, int *minute, int *second,
96+
int *microsecond, int *tz_offset_hours,
97+
int *tz_offset_minutes);
98+
99+
PyObject *raypy_wrap_ray_object(obj_p ray_obj);
100+
82101
PyObject *raypy_init_i16(PyObject *self, PyObject *args);
83102
PyObject *raypy_init_i32(PyObject *self, PyObject *args);
84103
PyObject *raypy_init_i64(PyObject *self, PyObject *args);
@@ -96,8 +115,6 @@ PyObject *raypy_init_list(PyObject *self, PyObject *args);
96115
PyObject *raypy_init_table(PyObject *self, PyObject *args);
97116
PyObject *raypy_init_dict(PyObject *self, PyObject *args);
98117
PyObject *raypy_init_vector(PyObject *self, PyObject *args);
99-
100-
// Readers
101118
PyObject *raypy_read_i16(PyObject *self, PyObject *args);
102119
PyObject *raypy_read_i32(PyObject *self, PyObject *args);
103120
PyObject *raypy_read_i64(PyObject *self, PyObject *args);
@@ -111,52 +128,33 @@ PyObject *raypy_read_date(PyObject *self, PyObject *args);
111128
PyObject *raypy_read_time(PyObject *self, PyObject *args);
112129
PyObject *raypy_read_timestamp(PyObject *self, PyObject *args);
113130
PyObject *raypy_read_guid(PyObject *self, PyObject *args);
114-
115-
// Type introspection
116131
PyObject *raypy_get_obj_type(PyObject *self, PyObject *args);
117-
118-
// Table operations
119132
PyObject *raypy_table_keys(PyObject *self, PyObject *args);
120133
PyObject *raypy_table_values(PyObject *self, PyObject *args);
121134
PyObject *raypy_repr_table(PyObject *self, PyObject *args);
122-
123-
// Dictionary operations
124135
PyObject *raypy_dict_keys(PyObject *self, PyObject *args);
125136
PyObject *raypy_dict_values(PyObject *self, PyObject *args);
126137
PyObject *raypy_dict_get(PyObject *self, PyObject *args);
127-
128-
// Vector operations
129138
PyObject *raypy_at_idx(PyObject *self, PyObject *args);
130139
PyObject *raypy_insert_obj(PyObject *self, PyObject *args);
131140
PyObject *raypy_push_obj(PyObject *self, PyObject *args);
132141
PyObject *raypy_set_obj(PyObject *self, PyObject *args);
133-
PyObject *raypy_fill_vector(PyObject *self, PyObject *args);
134-
PyObject *raypy_fill_list(PyObject *self, PyObject *args);
135-
136-
// Misc operations
137142
PyObject *raypy_get_obj_length(PyObject *self, PyObject *args);
138143
PyObject *raypy_eval_str(PyObject *self, PyObject *args);
139144
PyObject *raypy_get_error_obj(PyObject *self, PyObject *args);
140145
PyObject *raypy_binary_set(PyObject *self, PyObject *args);
141-
PyObject *raypy_env_get_internal_function_by_name(PyObject *self,
142-
PyObject *args);
143-
PyObject *raypy_env_get_internal_name_by_function(PyObject *self,
144-
PyObject *args);
146+
PyObject *raypy_env_get_internal_fn_by_name(PyObject *self, PyObject *args);
147+
PyObject *raypy_env_get_internal_name_by_fn(PyObject *self, PyObject *args);
145148
PyObject *raypy_eval_obj(PyObject *self, PyObject *args);
146149
PyObject *raypy_loadfn(PyObject *self, PyObject *args);
147150
PyObject *raypy_quote(PyObject *self, PyObject *args);
148151
PyObject *raypy_rc(PyObject *self, PyObject *args);
149152
PyObject *raypy_set_obj_attrs(PyObject *self, PyObject *args);
150-
151-
// Database operations
152-
PyObject *raypy_select(PyObject *self, PyObject *args);
153153
PyObject *raypy_update(PyObject *self, PyObject *args);
154154
PyObject *raypy_insert(PyObject *self, PyObject *args);
155155
PyObject *raypy_upsert(PyObject *self, PyObject *args);
156-
157-
// IO operations
158156
PyObject *raypy_hopen(PyObject *self, PyObject *args);
159157
PyObject *raypy_hclose(PyObject *self, PyObject *args);
160158
PyObject *raypy_write(PyObject *self, PyObject *args);
161159

162-
#endif // RAYFORCE_C_H
160+
#endif

0 commit comments

Comments
 (0)