From b23b906a332fe8161dc46f164f513904712124d0 Mon Sep 17 00:00:00 2001
From: bruno <97033386+bruno-at-orange@users.noreply.github.com>
Date: Mon, 11 May 2026 15:10:45 +0200
Subject: [PATCH 1/8] Add Python examples for KNI usage
Add Python implementations demonstrating single-table and multi-table
recoding with the Khiops Native Interface (KNI).
New files:
- python/KNI.py: Complete ctypes wrapper for KhiopsNativeInterface library
with automatic library discovery (KNI_HOME, system paths)
- python/KNIRecodeFile.py: Single-table recoding example
- python/KNIRecodeMTFiles.py: Multi-table recoding example with support for
secondary tables and external tables
Features:
- Cross-platform support (Windows, Linux, macOS)
- Flexible library loading with multiple search strategies
- Complete API coverage including multi-table operations
- Command-line interface with argparse for multi-table example
- Error handling with descriptive messages
Requirements: Python 3.6+ and KNI shared library installed
---
README.md | 92 ++++++--
python/KNI.py | 430 +++++++++++++++++++++++++++++++++++++
python/KNIRecodeFile.py | 168 +++++++++++++++
python/KNIRecodeMTFiles.py | 332 ++++++++++++++++++++++++++++
4 files changed, 1010 insertions(+), 12 deletions(-)
create mode 100755 python/KNI.py
create mode 100755 python/KNIRecodeFile.py
create mode 100755 python/KNIRecodeMTFiles.py
diff --git a/README.md b/README.md
index c9d8bc2..fe9b5eb 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,11 @@
-# Khiops Native Interface v11.0.0
+# Khiops Native Interface v11.0.1-a.5
This project provides all the basics to use the Khiops Native Interface (KNI): installation and examples.
-The purpose of KNI is to allow a deeper integration of Khiops in information systems, by mean of the C programming language, using a shared library (`.dll` in Windows, `.so` in Linux). This relates specially to the problem of model deployment, which otherwise requires the use of input and output data files when using directly the Khiops tool in batch mode. See Khiops Guide for an introduction to dictionary files, dictionaries, database files and deployment.
+The purpose of KNI is to allow a deeper integration of Khiops in information systems, by means of the C programming language, using a shared library (`.dll` in Windows, `.so` in Linux). This relates especially to the problem of model deployment, which otherwise requires the use of input and output data files when using directly the Khiops tool in batch mode. See Khiops Guide for an introduction to dictionary files, dictionaries, database files and deployment.
-The Khiops deployment API is thus made public through a shared library. Therefore, a Khiops model can be deployed directly from any programming language, such as C, C++, Java, Python, Matlab, etc. This enables real time model deployment without the overhead of temporary data files or launching executables. This is critical for certain applications, such as marketing or targeted advertising on the web..
+The Khiops deployment API is thus made public through a shared library. Therefore, a Khiops model can be deployed directly from any programming language, such as C, C++, Java, Python, Matlab, etc. This enables real-time model deployment without the overhead of temporary data files or launching executables. This is critical for certain applications, such as marketing or targeted advertising on the web.
All KNI functions are C functions for easy use with other programming languages. They return a positive or zero value in case of success, and a negative error code in case of failure.
@@ -14,15 +14,32 @@ See [KhiopsNativeInterface.h](include/KhiopsNativeInterface.h) for a detailed de
> [!CAUTION]
> The functions are not reentrant (thread-safe): the library can be used simultaneously by several executables, but not simultaneously by several threads in the same executable.
+## Table of Contents
+
+- [KNI installation](#kni-installation)
+ - [Windows](#windows)
+ - [Linux](#linux)
+- [Application examples](#application-examples)
+- [Example with C](#example-with-c)
+ - [Building the examples](#building-the-examples)
+ - [Launch](#launch)
+- [Example with Java](#example-with-java)
+ - [Building the examples](#building-the-examples-1)
+ - [Launch](#launch-1)
+- [Example with Python](#example-with-python)
+ - [Requirements](#requirements)
+ - [Scripts](#scripts)
+ - [Launch](#launch-2)
+
# KNI installation
## Windows
-Download [KNI-11.0.0.zip](https://github.com/KhiopsML/khiops/releases/tag/11.0.0/KNI-11.0.0.zip) and extract it to your machine. Set the environment variable `KNI_HOME` to the extracted directory. This variable is used in the following examples.
+Download [KNI-11.0.1-a.5.zip](https://github.com/KhiopsML/khiops/releases/tag/11.0.1/KNI-11.0.1-a.5.zip) and extract it to your machine. Set the environment variable `KNI_HOME` to the extracted directory. This variable is used in the following examples.
## Linux
-On Linux, go to the [release page](https://github.com/KhiopsML/khiops/releases/tag/11.0.0/) and download the KNI package. The name of the package begins with **kni** and ends with the **code name** of the OS. The code name is in the release file of the distribution (here, it is "jammy"):
+On Linux, go to the [release page](https://github.com/KhiopsML/khiops/releases/tag/11.0.1/) and download the KNI package. The name of the package begins with **kni** and ends with the **code name** of the OS. The code name is in the release file of the distribution (here, it is "jammy"):
```bash
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.4 LTS"
@@ -46,7 +63,7 @@ Download the package according to the code name of your OS and install it with `
Application examples are available in this repository. The main branch corresponds to the latest version of KNI. To explore older versions, switch between branches, which are named after their respective versions.
-Both examples in C and Java produce a sample binary `KNIRecodeFile`. It recodes an input file to an output file, using a Khiops dictionary from a dictionary file.
+Examples in C, Java, and Python demonstrate how to use KNI. The main example, `KNIRecodeFile`, recodes an input file to an output file using a Khiops dictionary from a dictionary file.
```bash
KNIRecodeFile
\n\n
This package provides Python bindings for the Khiops Native Interface (KNI),\nenabling direct deployment of Khiops models from Python without temporary files.
Args:\n dictionary_file_name: Path to the dictionary file (str or bytes)\n dictionary_name: Name of the dictionary to use (str or bytes)\n header_line: Header line with field names (str or bytes)\n field_separator: Character used to separate fields (str or bytes, default: tab)
\n\n
Returns:\n Stream handle (positive integer)
\n\n
Raises:\n KNIError: If opening stream fails\n TypeError: If arguments have invalid types
Recode an input record using the stream's dictionary.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n input_record: Input record string or bytes\n max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
\n\n
Returns:\n Recoded output string
\n\n
Raises:\n KNIError: If recoding fails\n TypeError: If arguments have invalid types
Set the header line of a secondary table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n header_line: Header line with field names (str or bytes)
\n\n
Raises:\n KNIError: If setting secondary header fails\n TypeError: If arguments have invalid types
Set the name of a data file for an external table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_root: Root dictionary of the external table (str or bytes)\n data_path: Data path for secondary external tables (str or bytes, empty for root)\n data_table_file_name: Path to the external table data file (str or bytes)
\n\n
Raises:\n KNIError: If setting external table fails\n TypeError: If arguments have invalid types
Set a secondary input record for multi-table recoding.
\n\n
All secondary records must be set before recoding the primary record.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n input_record: Secondary input record string or bytes
\n\n
Raises:\n KNIError: If setting secondary input record fails\n TypeError: If arguments have invalid types
29classKNI:
+ 30"""Python wrapper for Khiops Native Interface using ctypes."""
+ 31
+ 32# Error codes
+ 33KNI_OK=0
+ 34KNI_ErrorRunningFunction=-1
+ 35KNI_ErrorDictionaryFileName=-2
+ 36KNI_ErrorDictionaryMissingFile=-3
+ 37KNI_ErrorDictionaryFileFormat=-4
+ 38KNI_ErrorDictionaryName=-5
+ 39KNI_ErrorMissingDictionary=-6
+ 40KNI_ErrorTooManyStreams=-7
+ 41KNI_ErrorStreamHeaderLine=-8
+ 42KNI_ErrorFieldSeparator=-9
+ 43KNI_ErrorStreamHandle=-10
+ 44KNI_ErrorStreamOpened=-11
+ 45KNI_ErrorStreamNotOpened=-12
+ 46KNI_ErrorStreamInputRecord=-13
+ 47KNI_ErrorStreamInputRead=-14
+ 48KNI_ErrorStreamOutputRecord=-15
+ 49KNI_ErrorMissingSecondaryHeader=-16
+ 50KNI_ErrorMissingExternalTable=-17
+ 51KNI_ErrorDataRoot=-18
+ 52KNI_ErrorDataPath=-19
+ 53KNI_ErrorDataTableFile=-20
+ 54KNI_ErrorLoadDataTable=-21
+ 55KNI_ErrorMemoryOverflow=-22
+ 56KNI_ErrorStreamOpening=-23
+ 57KNI_ErrorStreamOpeningNotFinished=-24
+ 58KNI_ErrorLogFile=-25
+ 59
+ 60# Constants
+ 61KNI_MaxStreamNumber=512
+ 62KNI_DefaultMaxStreamMemory=100
+ 63KNI_MaxPathNameLength=1024
+ 64KNI_MaxDictionaryNameLength=128
+ 65KNI_MaxRecordLength=8*1024*1024# 8 MB
+ 66
+ 67def__init__(self,library_path:str|bytes|Path|None=None):
+ 68"""
+ 69 Initialize the KNI wrapper.
+ 70
+ 71 Args:
+ 72 library_path: Optional path to the KNI shared library (str, bytes, or
+ 73 pathlib.Path). If None, attempts to locate it automatically.
+ 74 """
+ 75self._lib=self._load_library(library_path)
+ 76self._setup_function_signatures()
+ 77
+ 78def_load_library(self,library_path:str|bytes|Path|None):
+ 79"""Load the KNI shared library."""
+ 80iflibrary_path:
+ 81returnctypes.CDLL(os.fsdecode(library_path))
+ 82
+ 83# Try to locate library automatically
+ 84system=platform.system()
+ 85ifsystem=="Windows":
+ 86lib_name="KhiopsNativeInterface.dll"
+ 87elifsystem=="Linux":
+ 88lib_name="libKhiopsNativeInterface.so"
+ 89elifsystem=="Darwin":# macOS
+ 90lib_name="libKhiopsNativeInterface.dylib"
+ 91else:
+ 92raiseRuntimeError(f"Unsupported platform: {system}")
+ 93
+ 94# When installed via pip, the shared library is placed in the Python
+ 95# scripts directory (bin/ on Unix, Scripts/ on Windows)
+ 96scripts_dir=sysconfig.get_path("scripts")
+ 97ifscripts_dir:
+ 98lib_path=Path(scripts_dir)/lib_name
+ 99iflib_path.exists():
+100returnctypes.CDLL(str(lib_path))
+101
+102raiseRuntimeError(
+103f"Could not find {lib_name}. Reinstall the khiops-kni package."
+104)
+105
+106def_setup_function_signatures(self):
+107"""Setup function signatures for KNI library functions."""
+108# KNIGetVersion
+109self._lib.KNIGetVersion.argtypes=[]
+110self._lib.KNIGetVersion.restype=ctypes.c_int
+111
+112# KNIGetFullVersion
+113self._lib.KNIGetFullVersion.argtypes=[]
+114self._lib.KNIGetFullVersion.restype=ctypes.c_char_p115
-116# KNIGetFullVersion
-117self._lib.KNIGetFullVersion.argtypes=[]
-118self._lib.KNIGetFullVersion.restype=ctypes.c_char_p
+116# KNISetLogFileName
+117self._lib.KNISetLogFileName.argtypes=[ctypes.c_char_p]
+118self._lib.KNISetLogFileName.restype=ctypes.c_int119
-120# KNISetLogFileName
-121self._lib.KNISetLogFileName.argtypes=[ctypes.c_char_p]
-122self._lib.KNISetLogFileName.restype=ctypes.c_int
-123
-124# KNIOpenStream
-125self._lib.KNIOpenStream.argtypes=[
-126ctypes.c_char_p,# sDictionaryFileName
-127ctypes.c_char_p,# sDictionaryName
-128ctypes.c_char_p,# sStreamHeaderLine
-129ctypes.c_char,# cFieldSeparator
-130]
-131self._lib.KNIOpenStream.restype=ctypes.c_int
+120# KNIOpenStream
+121self._lib.KNIOpenStream.argtypes=[
+122ctypes.c_char_p,# sDictionaryFileName
+123ctypes.c_char_p,# sDictionaryName
+124ctypes.c_char_p,# sStreamHeaderLine
+125ctypes.c_char,# cFieldSeparator
+126]
+127self._lib.KNIOpenStream.restype=ctypes.c_int
+128
+129# KNICloseStream
+130self._lib.KNICloseStream.argtypes=[ctypes.c_int]
+131self._lib.KNICloseStream.restype=ctypes.c_int132
-133# KNICloseStream
-134self._lib.KNICloseStream.argtypes=[ctypes.c_int]
-135self._lib.KNICloseStream.restype=ctypes.c_int
-136
-137# KNIRecodeStreamRecord
-138self._lib.KNIRecodeStreamRecord.argtypes=[
-139ctypes.c_int,# hStream
-140ctypes.c_char_p,# sInputRecord
-141ctypes.c_char_p,# sOutputRecord
-142ctypes.c_int,# nOutputMaxLength
-143]
-144self._lib.KNIRecodeStreamRecord.restype=ctypes.c_int
-145
-146# Multi-table functions
-147# KNISetSecondaryHeaderLine
-148self._lib.KNISetSecondaryHeaderLine.argtypes=[
-149ctypes.c_int,# hStream
-150ctypes.c_char_p,# sDataPath
-151ctypes.c_char_p,# sStreamSecondaryHeaderLine
-152]
-153self._lib.KNISetSecondaryHeaderLine.restype=ctypes.c_int
-154
-155# KNISetExternalTable
-156self._lib.KNISetExternalTable.argtypes=[
-157ctypes.c_int,# hStream
-158ctypes.c_char_p,# sDataRoot
-159ctypes.c_char_p,# sDataPath
-160ctypes.c_char_p,# sDataTableFileName
-161]
-162self._lib.KNISetExternalTable.restype=ctypes.c_int
+133# KNIRecodeStreamRecord
+134self._lib.KNIRecodeStreamRecord.argtypes=[
+135ctypes.c_int,# hStream
+136ctypes.c_char_p,# sInputRecord
+137ctypes.c_char_p,# sOutputRecord
+138ctypes.c_int,# nOutputMaxLength
+139]
+140self._lib.KNIRecodeStreamRecord.restype=ctypes.c_int
+141
+142# Multi-table functions
+143# KNISetSecondaryHeaderLine
+144self._lib.KNISetSecondaryHeaderLine.argtypes=[
+145ctypes.c_int,# hStream
+146ctypes.c_char_p,# sDataPath
+147ctypes.c_char_p,# sStreamSecondaryHeaderLine
+148]
+149self._lib.KNISetSecondaryHeaderLine.restype=ctypes.c_int
+150
+151# KNISetExternalTable
+152self._lib.KNISetExternalTable.argtypes=[
+153ctypes.c_int,# hStream
+154ctypes.c_char_p,# sDataRoot
+155ctypes.c_char_p,# sDataPath
+156ctypes.c_char_p,# sDataTableFileName
+157]
+158self._lib.KNISetExternalTable.restype=ctypes.c_int
+159
+160# KNIFinishOpeningStream
+161self._lib.KNIFinishOpeningStream.argtypes=[ctypes.c_int]
+162self._lib.KNIFinishOpeningStream.restype=ctypes.c_int163
-164# KNIFinishOpeningStream
-165self._lib.KNIFinishOpeningStream.argtypes=[ctypes.c_int]
-166self._lib.KNIFinishOpeningStream.restype=ctypes.c_int
-167
-168# KNISetSecondaryInputRecord
-169self._lib.KNISetSecondaryInputRecord.argtypes=[
-170ctypes.c_int,# hStream
-171ctypes.c_char_p,# sDataPath
-172ctypes.c_char_p,# sStreamSecondaryInputRecord
-173]
-174self._lib.KNISetSecondaryInputRecord.restype=ctypes.c_int
-175
-176# Advanced parameters
-177# KNIGetStreamMaxMemory
-178self._lib.KNIGetStreamMaxMemory.argtypes=[]
-179self._lib.KNIGetStreamMaxMemory.restype=ctypes.c_int
+164# KNISetSecondaryInputRecord
+165self._lib.KNISetSecondaryInputRecord.argtypes=[
+166ctypes.c_int,# hStream
+167ctypes.c_char_p,# sDataPath
+168ctypes.c_char_p,# sStreamSecondaryInputRecord
+169]
+170self._lib.KNISetSecondaryInputRecord.restype=ctypes.c_int
+171
+172# Advanced parameters
+173# KNIGetStreamMaxMemory
+174self._lib.KNIGetStreamMaxMemory.argtypes=[]
+175self._lib.KNIGetStreamMaxMemory.restype=ctypes.c_int
+176
+177# KNISetStreamMaxMemory
+178self._lib.KNISetStreamMaxMemory.argtypes=[ctypes.c_int]
+179self._lib.KNISetStreamMaxMemory.restype=ctypes.c_int180
-181# KNISetStreamMaxMemory
-182self._lib.KNISetStreamMaxMemory.argtypes=[ctypes.c_int]
-183self._lib.KNISetStreamMaxMemory.restype=ctypes.c_int
-184
-185defget_version(self):
-186"""Get KNI version as integer (10*major + minor)."""
-187returnself._lib.KNIGetVersion()
-188
-189defget_full_version(self):
-190"""Get KNI full version string."""
-191returnself._lib.KNIGetFullVersion().decode("utf-8")
-192
-193defset_log_file_name(self,log_file_name):
-194"""
-195 Set the log file name for error messages.
-196
-197 Args:
-198 log_file_name: Path to log file (str or bytes, empty string for no logging)
+181@staticmethod
+182def_to_bytes(value:str|bytes,param_name:str)->bytes:
+183"""Encode a str or bytes parameter to UTF-8 bytes."""
+184ifisinstance(value,str):
+185returnvalue.encode("utf-8")
+186ifisinstance(value,bytes):
+187returnvalue
+188raiseTypeError(
+189f"{param_name} must be str or bytes, not {type(value).__name__}"
+190)
+191
+192defget_version(self)->int:
+193"""Get KNI version as integer (10*major + minor)."""
+194returnself._lib.KNIGetVersion()
+195
+196defget_full_version(self)->str:
+197"""Get KNI full version string."""
+198returnself._lib.KNIGetFullVersion().decode("utf-8")199
-200 Raises:
-201 KNIError: If setting log file fails
-202 TypeError: If log_file_name is not str or bytes
-203 """
-204ifisinstance(log_file_name,str):
-205log_file_name_bytes=log_file_name.encode("utf-8")
-206elifisinstance(log_file_name,bytes):
-207log_file_name_bytes=log_file_name
-208else:
-209raiseTypeError(
-210f"log_file_name must be str or bytes, not {type(log_file_name).__name__}"
-211)
+200defset_log_file_name(self,log_file_name:str|bytes)->None:
+201"""
+202 Set the log file name for error messages.
+203
+204 Args:
+205 log_file_name: Path to log file (str or bytes, empty string for no logging)
+206
+207 Raises:
+208 KNIError: If setting log file fails
+209 TypeError: If log_file_name is not str or bytes
+210 """
+211log_file_name_bytes=self._to_bytes(log_file_name,"log_file_name")212ret_code=self._lib.KNISetLogFileName(log_file_name_bytes)213ifret_code!=self.KNI_OK:214raiseKNIError(
@@ -432,376 +431,316 @@
217)218219defopen_stream(
-220self,dictionary_file_name,dictionary_name,header_line,field_separator="\t"
-221):
-222"""
-223 Open a KNI stream for recoding.
-224
-225 Args:
-226 dictionary_file_name: Path to the dictionary file (str or bytes)
-227 dictionary_name: Name of the dictionary to use (str or bytes)
-228 header_line: Header line with field names (str or bytes)
-229 field_separator: Character used to separate fields (str or bytes, default: tab)
-230
-231 Returns:
-232 Stream handle (positive integer)
-233
-234 Raises:
-235 KNIError: If opening stream fails
-236 TypeError: If arguments have invalid types
-237 """
-238# Type checking and conversion
-239ifisinstance(dictionary_file_name,str):
-240dictionary_file_name_bytes=dictionary_file_name.encode("utf-8")
-241elifisinstance(dictionary_file_name,bytes):
-242dictionary_file_name_bytes=dictionary_file_name
-243else:
-244raiseTypeError(
-245f"dictionary_file_name must be str or bytes, not {type(dictionary_file_name).__name__}"
-246)
-247ifisinstance(dictionary_name,str):
-248dictionary_name_bytes=dictionary_name.encode("utf-8")
-249elifisinstance(dictionary_name,bytes):
-250dictionary_name_bytes=dictionary_name
-251else:
-252raiseTypeError(
-253f"dictionary_name must be str or bytes, not {type(dictionary_name).__name__}"
-254)
-255ifisinstance(header_line,str):
-256header_line_bytes=header_line.encode("utf-8")
-257elifisinstance(header_line,bytes):
-258header_line_bytes=header_line
-259else:
-260raiseTypeError(
-261f"header_line must be str or bytes, not {type(header_line).__name__}"
-262)
-263
-264# Convert field_separator to a single byte
-265ifisinstance(field_separator,str):
-266field_separator_byte=field_separator.encode("utf-8")[0]
-267elifisinstance(field_separator,bytes):
-268field_separator_byte=field_separator[0]
-269else:
-270raiseTypeError(
-271f"field_separator must be str or bytes, not {type(field_separator).__name__}"
-272)
-273
-274stream_handle=self._lib.KNIOpenStream(
-275dictionary_file_name_bytes,
-276dictionary_name_bytes,
-277header_line_bytes,
-278field_separator_byte,
-279)
-280ifstream_handle<0:
-281raiseKNIError(
-282f"Failed to open stream: {self.get_error_message(stream_handle)}",
-283stream_handle,
-284)
-285returnstream_handle
-286
-287defclose_stream(self,stream_handle):
-288"""
-289 Close a KNI stream.
-290
-291 Args:
-292 stream_handle: Handle returned by open_stream
-293
-294 Raises:
-295 KNIError: If closing stream fails
-296 TypeError: If stream_handle is not int
-297 """
-298ifnotisinstance(stream_handle,int):
-299raiseTypeError(
-300f"stream_handle must be int, not {type(stream_handle).__name__}"
-301)
-302ret_code=self._lib.KNICloseStream(stream_handle)
-303ifret_code!=self.KNI_OK:
-304raiseKNIError(
-305f"Failed to close stream: {self.get_error_message(ret_code)}",
-306ret_code,
-307)
-308
-309defrecode_stream_record(self,stream_handle,input_record,max_output_length=None):
-310"""
-311 Recode an input record using the stream's dictionary.
-312
-313 Args:
-314 stream_handle: Handle returned by open_stream
-315 input_record: Input record string or bytes
-316 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
-317
-318 Returns:
-319 Recoded output string
-320
-321 Raises:
-322 KNIError: If recoding fails
-323 TypeError: If arguments have invalid types
-324 """
-325ifnotisinstance(stream_handle,int):
-326raiseTypeError(
-327f"stream_handle must be int, not {type(stream_handle).__name__}"
-328)
-329ifisinstance(input_record,str):
-330input_record_bytes=input_record.encode("utf-8")
-331elifisinstance(input_record,bytes):
-332input_record_bytes=input_record
-333else:
-334raiseTypeError(
-335f"input_record must be str or bytes, not {type(input_record).__name__}"
-336)
-337ifmax_output_lengthisNone:
-338max_output_length=self.KNI_MaxRecordLength
-339elifnotisinstance(max_output_length,int):
-340raiseTypeError(
-341f"max_output_length must be int or None, not {type(max_output_length).__name__}"
-342)
+220self,
+221dictionary_file_path:str|bytes|Path,
+222dictionary_name:str|bytes,
+223header_line:str|bytes,
+224field_separator:str|bytes="\t",
+225)->int:
+226"""
+227 Open a KNI stream for recoding.
+228
+229 Args:
+230 dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
+231 dictionary_name: Name of the dictionary to use (str or bytes)
+232 header_line: Header line with field names (str or bytes)
+233 field_separator: Character used to separate fields (str or bytes, default: tab)
+234
+235 Returns:
+236 Stream handle (positive integer)
+237
+238 Raises:
+239 KNIError: If opening stream fails
+240 TypeError: If arguments have invalid types
+241 """
+242# Type checking and conversion
+243ifisinstance(dictionary_file_path,Path):
+244dictionary_file_path=str(dictionary_file_path)
+245dictionary_file_path_bytes=self._to_bytes(
+246dictionary_file_path,"dictionary_file_path"
+247)
+248dictionary_name_bytes=self._to_bytes(dictionary_name,"dictionary_name")
+249header_line_bytes=self._to_bytes(header_line,"header_line")
+250field_separator_bytes=self._to_bytes(field_separator,"field_separator")
+251iflen(field_separator_bytes)==0:
+252raiseValueError("field_separator must not be empty")
+253field_separator_byte=field_separator_bytes[0]
+254
+255stream_handle=self._lib.KNIOpenStream(
+256dictionary_file_path_bytes,
+257dictionary_name_bytes,
+258header_line_bytes,
+259field_separator_byte,
+260)
+261ifstream_handle<0:
+262raiseKNIError(
+263f"Failed to open stream: {self.get_error_message(stream_handle)}",
+264stream_handle,
+265)
+266returnstream_handle
+267
+268defclose_stream(self,stream_handle:int)->None:
+269"""
+270 Close a KNI stream.
+271
+272 Args:
+273 stream_handle: Handle returned by open_stream
+274
+275 Raises:
+276 KNIError: If closing stream fails
+277 TypeError: If stream_handle is not int
+278 """
+279ifnotisinstance(stream_handle,int):
+280raiseTypeError(
+281f"stream_handle must be int, not {type(stream_handle).__name__}"
+282)
+283ret_code=self._lib.KNICloseStream(stream_handle)
+284ifret_code!=self.KNI_OK:
+285raiseKNIError(
+286f"Failed to close stream: {self.get_error_message(ret_code)}",
+287ret_code,
+288)
+289
+290defrecode_stream_record(
+291self,
+292stream_handle:int,
+293input_record:str|bytes,
+294max_output_length:int|None=None,
+295)->str:
+296"""
+297 Recode an input record using the stream's dictionary.
+298
+299 Args:
+300 stream_handle: Handle returned by open_stream
+301 input_record: Input record string or bytes
+302 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
+303
+304 Returns:
+305 Recoded output string
+306
+307 Raises:
+308 KNIError: If recoding fails
+309 TypeError: If arguments have invalid types
+310 """
+311ifnotisinstance(stream_handle,int):
+312raiseTypeError(
+313f"stream_handle must be int, not {type(stream_handle).__name__}"
+314)
+315input_record_bytes=self._to_bytes(input_record,"input_record")
+316ifmax_output_lengthisNone:
+317max_output_length=self.KNI_MaxRecordLength
+318elifnotisinstance(max_output_length,int):
+319raiseTypeError(
+320f"max_output_length must be int or None, not {type(max_output_length).__name__}"
+321)
+322
+323output_buffer=ctypes.create_string_buffer(max_output_length)
+324ret_code=self._lib.KNIRecodeStreamRecord(
+325stream_handle,
+326input_record_bytes,
+327output_buffer,
+328max_output_length,
+329)
+330
+331ifret_code!=self.KNI_OK:
+332raiseKNIError(
+333f"Failed to recode record: {self.get_error_message(ret_code)}",
+334ret_code,
+335)
+336returnoutput_buffer.value.decode("utf-8")
+337
+338defset_secondary_header_line(
+339self,stream_handle:int,data_path:str|bytes,header_line:str|bytes
+340)->None:
+341"""
+342 Set the header line of a secondary table (multi-table only).343
-344output_buffer=ctypes.create_string_buffer(max_output_length)
-345ret_code=self._lib.KNIRecodeStreamRecord(
-346stream_handle,
-347input_record_bytes,
-348output_buffer,
-349max_output_length,
-350)
-351
-352ifret_code!=self.KNI_OK:
-353raiseKNIError(
-354f"Failed to recode record: {self.get_error_message(ret_code)}",
-355ret_code,
+344 Args:
+345 stream_handle: Handle returned by open_stream
+346 data_path: Data path identifying the secondary table (str or bytes)
+347 header_line: Header line with field names (str or bytes)
+348
+349 Raises:
+350 KNIError: If setting secondary header fails
+351 TypeError: If arguments have invalid types
+352 """
+353ifnotisinstance(stream_handle,int):
+354raiseTypeError(
+355f"stream_handle must be int, not {type(stream_handle).__name__}"356)
-357returnoutput_buffer.value.decode("utf-8")
-358
-359defset_secondary_header_line(self,stream_handle,data_path,header_line):
-360"""
-361 Set the header line of a secondary table (multi-table only).
-362
-363 Args:
-364 stream_handle: Handle returned by open_stream
-365 data_path: Data path identifying the secondary table (str or bytes)
-366 header_line: Header line with field names (str or bytes)
+357data_path_bytes=self._to_bytes(data_path,"data_path")
+358header_line_bytes=self._to_bytes(header_line,"header_line")
+359ret_code=self._lib.KNISetSecondaryHeaderLine(
+360stream_handle,data_path_bytes,header_line_bytes
+361)
+362ifret_code!=self.KNI_OK:
+363raiseKNIError(
+364f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
+365ret_code,
+366)367
-368 Raises:
-369 KNIError: If setting secondary header fails
-370 TypeError: If arguments have invalid types
-371 """
-372ifnotisinstance(stream_handle,int):
-373raiseTypeError(
-374f"stream_handle must be int, not {type(stream_handle).__name__}"
-375)
-376ifisinstance(data_path,str):
-377data_path_bytes=data_path.encode("utf-8")
-378elifisinstance(data_path,bytes):
-379data_path_bytes=data_path
-380else:
-381raiseTypeError(
-382f"data_path must be str or bytes, not {type(data_path).__name__}"
-383)
-384ifisinstance(header_line,str):
-385header_line_bytes=header_line.encode("utf-8")
-386elifisinstance(header_line,bytes):
-387header_line_bytes=header_line
-388else:
+368defset_external_table(
+369self,
+370stream_handle:int,
+371data_root:str|bytes,
+372data_path:str|bytes,
+373data_table_file_name:str|bytes,
+374)->None:
+375"""
+376 Set the name of a data file for an external table (multi-table only).
+377
+378 Args:
+379 stream_handle: Handle returned by open_stream
+380 data_root: Root dictionary of the external table (str or bytes)
+381 data_path: Data path for secondary external tables (str or bytes, empty for root)
+382 data_table_file_name: Path to the external table data file (str or bytes)
+383
+384 Raises:
+385 KNIError: If setting external table fails
+386 TypeError: If arguments have invalid types
+387 """
+388ifnotisinstance(stream_handle,int):389raiseTypeError(
-390f"header_line must be str or bytes, not {type(header_line).__name__}"
+390f"stream_handle must be int, not {type(stream_handle).__name__}"391)
-392ret_code=self._lib.KNISetSecondaryHeaderLine(
-393stream_handle,data_path_bytes,header_line_bytes
-394)
-395ifret_code!=self.KNI_OK:
-396raiseKNIError(
-397f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
-398ret_code,
-399)
-400
-401defset_external_table(
-402self,stream_handle,data_root,data_path,data_table_file_name
-403):
-404"""
-405 Set the name of a data file for an external table (multi-table only).
-406
-407 Args:
-408 stream_handle: Handle returned by open_stream
-409 data_root: Root dictionary of the external table (str or bytes)
-410 data_path: Data path for secondary external tables (str or bytes, empty for root)
-411 data_table_file_name: Path to the external table data file (str or bytes)
+392data_root_bytes=self._to_bytes(data_root,"data_root")
+393data_path_bytes=self._to_bytes(data_path,"data_path")
+394data_table_file_name_bytes=self._to_bytes(
+395data_table_file_name,"data_table_file_name"
+396)
+397ret_code=self._lib.KNISetExternalTable(
+398stream_handle,
+399data_root_bytes,
+400data_path_bytes,
+401data_table_file_name_bytes,
+402)
+403ifret_code!=self.KNI_OK:
+404raiseKNIError(
+405f"Failed to set external table: {self.get_error_message(ret_code)}",
+406ret_code,
+407)
+408
+409deffinish_opening_stream(self,stream_handle:int)->None:
+410"""
+411 Finish opening a stream (multi-table only).412
-413 Raises:
-414 KNIError: If setting external table fails
-415 TypeError: If arguments have invalid types
-416 """
-417ifnotisinstance(stream_handle,int):
-418raiseTypeError(
-419f"stream_handle must be int, not {type(stream_handle).__name__}"
-420)
-421ifisinstance(data_root,str):
-422data_root_bytes=data_root.encode("utf-8")
-423elifisinstance(data_root,bytes):
-424data_root_bytes=data_root
-425else:
-426raiseTypeError(
-427f"data_root must be str or bytes, not {type(data_root).__name__}"
-428)
-429ifisinstance(data_path,str):
-430data_path_bytes=data_path.encode("utf-8")
-431elifisinstance(data_path,bytes):
-432data_path_bytes=data_path
-433else:
-434raiseTypeError(
-435f"data_path must be str or bytes, not {type(data_path).__name__}"
-436)
-437ifisinstance(data_table_file_name,str):
-438data_table_file_name_bytes=data_table_file_name.encode("utf-8")
-439elifisinstance(data_table_file_name,bytes):
-440data_table_file_name_bytes=data_table_file_name
-441else:
-442raiseTypeError(
-443f"data_table_file_name must be str or bytes, not {type(data_table_file_name).__name__}"
-444)
-445ret_code=self._lib.KNISetExternalTable(
-446stream_handle,
-447data_root_bytes,
-448data_path_bytes,
-449data_table_file_name_bytes,
-450)
-451ifret_code!=self.KNI_OK:
-452raiseKNIError(
-453f"Failed to set external table: {self.get_error_message(ret_code)}",
-454ret_code,
-455)
-456
-457deffinish_opening_stream(self,stream_handle):
-458"""
-459 Finish opening a stream (multi-table only).
-460
-461 Must be called after all secondary headers and external tables are set.
-462
-463 Args:
-464 stream_handle: Handle returned by open_stream
-465
-466 Raises:
-467 KNIError: If finishing opening stream fails
-468 TypeError: If stream_handle is not int
-469 """
-470ifnotisinstance(stream_handle,int):
-471raiseTypeError(
-472f"stream_handle must be int, not {type(stream_handle).__name__}"
-473)
-474ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
-475ifret_code!=self.KNI_OK:
-476raiseKNIError(
-477f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
-478ret_code,
-479)
+413 Must be called after all secondary headers and external tables are set.
+414
+415 Args:
+416 stream_handle: Handle returned by open_stream
+417
+418 Raises:
+419 KNIError: If finishing opening stream fails
+420 TypeError: If stream_handle is not int
+421 """
+422ifnotisinstance(stream_handle,int):
+423raiseTypeError(
+424f"stream_handle must be int, not {type(stream_handle).__name__}"
+425)
+426ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
+427ifret_code!=self.KNI_OK:
+428raiseKNIError(
+429f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
+430ret_code,
+431)
+432
+433defset_secondary_input_record(
+434self,stream_handle:int,data_path:str|bytes,input_record:str|bytes
+435)->None:
+436"""
+437 Set a secondary input record for multi-table recoding.
+438
+439 All secondary records must be set before recoding the primary record.
+440
+441 Args:
+442 stream_handle: Handle returned by open_stream
+443 data_path: Data path identifying the secondary table (str or bytes)
+444 input_record: Secondary input record string or bytes
+445
+446 Raises:
+447 KNIError: If setting secondary input record fails
+448 TypeError: If arguments have invalid types
+449 """
+450ifnotisinstance(stream_handle,int):
+451raiseTypeError(
+452f"stream_handle must be int, not {type(stream_handle).__name__}"
+453)
+454data_path_bytes=self._to_bytes(data_path,"data_path")
+455input_record_bytes=self._to_bytes(input_record,"input_record")
+456ret_code=self._lib.KNISetSecondaryInputRecord(
+457stream_handle,data_path_bytes,input_record_bytes
+458)
+459ifret_code!=self.KNI_OK:
+460raiseKNIError(
+461f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
+462ret_code,
+463)
+464
+465defget_stream_max_memory(self)->int:
+466"""
+467 Get the maximum amount of memory (in MB) for stream opening.
+468
+469 Returns:
+470 Maximum memory in MB
+471 """
+472returnself._lib.KNIGetStreamMaxMemory()
+473
+474defset_stream_max_memory(self,max_mb:int)->int:
+475"""
+476 Set the maximum amount of memory (in MB) for stream opening.
+477
+478 Args:
+479 max_mb: Maximum memory in MB480
-481defset_secondary_input_record(self,stream_handle,data_path,input_record):
-482"""
-483 Set a secondary input record for multi-table recoding.
-484
-485 All secondary records must be set before recoding the primary record.
-486
-487 Args:
-488 stream_handle: Handle returned by open_stream
-489 data_path: Data path identifying the secondary table (str or bytes)
-490 input_record: Secondary input record string or bytes
-491
-492 Raises:
-493 KNIError: If setting secondary input record fails
-494 TypeError: If arguments have invalid types
-495 """
-496ifnotisinstance(stream_handle,int):
-497raiseTypeError(
-498f"stream_handle must be int, not {type(stream_handle).__name__}"
-499)
-500ifisinstance(data_path,str):
-501data_path_bytes=data_path.encode("utf-8")
-502elifisinstance(data_path,bytes):
-503data_path_bytes=data_path
-504else:
-505raiseTypeError(
-506f"data_path must be str or bytes, not {type(data_path).__name__}"
-507)
-508ifisinstance(input_record,str):
-509input_record_bytes=input_record.encode("utf-8")
-510elifisinstance(input_record,bytes):
-511input_record_bytes=input_record
-512else:
-513raiseTypeError(
-514f"input_record must be str or bytes, not {type(input_record).__name__}"
-515)
-516ret_code=self._lib.KNISetSecondaryInputRecord(
-517stream_handle,data_path_bytes,input_record_bytes
-518)
-519ifret_code!=self.KNI_OK:
-520raiseKNIError(
-521f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
-522ret_code,
-523)
-524
-525defget_stream_max_memory(self):
-526"""
-527 Get the maximum amount of memory (in MB) for stream opening.
-528
-529 Returns:
-530 Maximum memory in MB
-531 """
-532returnself._lib.KNIGetStreamMaxMemory()
-533
-534defset_stream_max_memory(self,max_mb):
-535"""
-536 Set the maximum amount of memory (in MB) for stream opening.
-537
-538 Args:
-539 max_mb: Maximum memory in MB
-540
-541 Returns:
-542 Accepted value (bounded by system limits)
-543 """
-544ifnotisinstance(max_mb,int):
-545raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
-546returnself._lib.KNISetStreamMaxMemory(max_mb)
-547
-548@staticmethod
-549defget_error_message(error_code):
-550"""
-551 Get a human-readable error message for an error code.
-552
-553 Args:
-554 error_code: KNI error code
-555
-556 Returns:
-557 Error message string
-558 """
-559ifnotisinstance(error_code,int):
-560raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
-561error_messages={
-562KNI.KNI_OK:"Success",
-563KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
-564KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
-565KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
-566KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
-567KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
-568KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
-569KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
-570KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
-571KNI.KNI_ErrorFieldSeparator:"Bad field separator",
-572KNI.KNI_ErrorStreamHandle:"Bad stream handle",
-573KNI.KNI_ErrorStreamOpened:"Stream already opened",
-574KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
-575KNI.KNI_ErrorStreamInputRecord:"Bad input record",
-576KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
-577KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
-578KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
-579KNI.KNI_ErrorMissingExternalTable:"Missing external table",
-580KNI.KNI_ErrorDataRoot:"Bad data root",
-581KNI.KNI_ErrorDataPath:"Bad data path",
-582KNI.KNI_ErrorDataTableFile:"Bad data table file",
-583KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
-584KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
-585KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
-586KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
-587KNI.KNI_ErrorLogFile:"Bad error file",
-588}
-589returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
+481 Returns:
+482 Accepted value (bounded by system limits)
+483 """
+484ifnotisinstance(max_mb,int):
+485raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
+486returnself._lib.KNISetStreamMaxMemory(max_mb)
+487
+488@staticmethod
+489defget_error_message(error_code:int)->str:
+490"""
+491 Get a human-readable error message for an error code.
+492
+493 Args:
+494 error_code: KNI error code
+495
+496 Returns:
+497 Error message string
+498 """
+499ifnotisinstance(error_code,int):
+500raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
+501error_messages={
+502KNI.KNI_OK:"Success",
+503KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
+504KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
+505KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
+506KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
+507KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
+508KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
+509KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
+510KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
+511KNI.KNI_ErrorFieldSeparator:"Bad field separator",
+512KNI.KNI_ErrorStreamHandle:"Bad stream handle",
+513KNI.KNI_ErrorStreamOpened:"Stream already opened",
+514KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
+515KNI.KNI_ErrorStreamInputRecord:"Bad input record",
+516KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
+517KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
+518KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
+519KNI.KNI_ErrorMissingExternalTable:"Missing external table",
+520KNI.KNI_ErrorDataRoot:"Bad data root",
+521KNI.KNI_ErrorDataPath:"Bad data path",
+522KNI.KNI_ErrorDataTableFile:"Bad data table file",
+523KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
+524KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
+525KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
+526KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
+527KNI.KNI_ErrorLogFile:"Bad error file",
+528}
+529returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
66def__init__(self,library_path=None):
-67"""
-68 Initialize the KNI wrapper.
-69
-70 Args:
-71 library_path: Optional path to the KNI shared library.
-72 If None, attempts to locate it automatically.
-73 """
-74self._lib=self._load_library(library_path)
-75self._setup_functions()
+
67def__init__(self,library_path:str|bytes|Path|None=None):
+68"""
+69 Initialize the KNI wrapper.
+70
+71 Args:
+72 library_path: Optional path to the KNI shared library (str, bytes, or
+73 pathlib.Path). If None, attempts to locate it automatically.
+74 """
+75self._lib=self._load_library(library_path)
+76self._setup_function_signatures()
Initialize the KNI wrapper.
Args:
- library_path: Optional path to the KNI shared library.
- If None, attempts to locate it automatically.
+ library_path: Optional path to the KNI shared library (str, bytes, or
+ pathlib.Path). If None, attempts to locate it automatically.
193defset_log_file_name(self,log_file_name):
-194"""
-195 Set the log file name for error messages.
-196
-197 Args:
-198 log_file_name: Path to log file (str or bytes, empty string for no logging)
-199
-200 Raises:
-201 KNIError: If setting log file fails
-202 TypeError: If log_file_name is not str or bytes
-203 """
-204ifisinstance(log_file_name,str):
-205log_file_name_bytes=log_file_name.encode("utf-8")
-206elifisinstance(log_file_name,bytes):
-207log_file_name_bytes=log_file_name
-208else:
-209raiseTypeError(
-210f"log_file_name must be str or bytes, not {type(log_file_name).__name__}"
-211)
+
200defset_log_file_name(self,log_file_name:str|bytes)->None:
+201"""
+202 Set the log file name for error messages.
+203
+204 Args:
+205 log_file_name: Path to log file (str or bytes, empty string for no logging)
+206
+207 Raises:
+208 KNIError: If setting log file fails
+209 TypeError: If log_file_name is not str or bytes
+210 """
+211log_file_name_bytes=self._to_bytes(log_file_name,"log_file_name")212ret_code=self._lib.KNISetLogFileName(log_file_name_bytes)213ifret_code!=self.KNI_OK:214raiseKNIError(
@@ -1313,86 +1245,67 @@
219defopen_stream(
-220self,dictionary_file_name,dictionary_name,header_line,field_separator="\t"
-221):
-222"""
-223 Open a KNI stream for recoding.
-224
-225 Args:
-226 dictionary_file_name: Path to the dictionary file (str or bytes)
-227 dictionary_name: Name of the dictionary to use (str or bytes)
-228 header_line: Header line with field names (str or bytes)
-229 field_separator: Character used to separate fields (str or bytes, default: tab)
-230
-231 Returns:
-232 Stream handle (positive integer)
-233
-234 Raises:
-235 KNIError: If opening stream fails
-236 TypeError: If arguments have invalid types
-237 """
-238# Type checking and conversion
-239ifisinstance(dictionary_file_name,str):
-240dictionary_file_name_bytes=dictionary_file_name.encode("utf-8")
-241elifisinstance(dictionary_file_name,bytes):
-242dictionary_file_name_bytes=dictionary_file_name
-243else:
-244raiseTypeError(
-245f"dictionary_file_name must be str or bytes, not {type(dictionary_file_name).__name__}"
-246)
-247ifisinstance(dictionary_name,str):
-248dictionary_name_bytes=dictionary_name.encode("utf-8")
-249elifisinstance(dictionary_name,bytes):
-250dictionary_name_bytes=dictionary_name
-251else:
-252raiseTypeError(
-253f"dictionary_name must be str or bytes, not {type(dictionary_name).__name__}"
-254)
-255ifisinstance(header_line,str):
-256header_line_bytes=header_line.encode("utf-8")
-257elifisinstance(header_line,bytes):
-258header_line_bytes=header_line
-259else:
-260raiseTypeError(
-261f"header_line must be str or bytes, not {type(header_line).__name__}"
-262)
-263
-264# Convert field_separator to a single byte
-265ifisinstance(field_separator,str):
-266field_separator_byte=field_separator.encode("utf-8")[0]
-267elifisinstance(field_separator,bytes):
-268field_separator_byte=field_separator[0]
-269else:
-270raiseTypeError(
-271f"field_separator must be str or bytes, not {type(field_separator).__name__}"
-272)
-273
-274stream_handle=self._lib.KNIOpenStream(
-275dictionary_file_name_bytes,
-276dictionary_name_bytes,
-277header_line_bytes,
-278field_separator_byte,
-279)
-280ifstream_handle<0:
-281raiseKNIError(
-282f"Failed to open stream: {self.get_error_message(stream_handle)}",
-283stream_handle,
-284)
-285returnstream_handle
+220self,
+221dictionary_file_path:str|bytes|Path,
+222dictionary_name:str|bytes,
+223header_line:str|bytes,
+224field_separator:str|bytes="\t",
+225)->int:
+226"""
+227 Open a KNI stream for recoding.
+228
+229 Args:
+230 dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
+231 dictionary_name: Name of the dictionary to use (str or bytes)
+232 header_line: Header line with field names (str or bytes)
+233 field_separator: Character used to separate fields (str or bytes, default: tab)
+234
+235 Returns:
+236 Stream handle (positive integer)
+237
+238 Raises:
+239 KNIError: If opening stream fails
+240 TypeError: If arguments have invalid types
+241 """
+242# Type checking and conversion
+243ifisinstance(dictionary_file_path,Path):
+244dictionary_file_path=str(dictionary_file_path)
+245dictionary_file_path_bytes=self._to_bytes(
+246dictionary_file_path,"dictionary_file_path"
+247)
+248dictionary_name_bytes=self._to_bytes(dictionary_name,"dictionary_name")
+249header_line_bytes=self._to_bytes(header_line,"header_line")
+250field_separator_bytes=self._to_bytes(field_separator,"field_separator")
+251iflen(field_separator_bytes)==0:
+252raiseValueError("field_separator must not be empty")
+253field_separator_byte=field_separator_bytes[0]
+254
+255stream_handle=self._lib.KNIOpenStream(
+256dictionary_file_path_bytes,
+257dictionary_name_bytes,
+258header_line_bytes,
+259field_separator_byte,
+260)
+261ifstream_handle<0:
+262raiseKNIError(
+263f"Failed to open stream: {self.get_error_message(stream_handle)}",
+264stream_handle,
+265)
+266returnstream_handle
Open a KNI stream for recoding.
Args:
- dictionary_file_name: Path to the dictionary file (str or bytes)
+ dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
dictionary_name: Name of the dictionary to use (str or bytes)
header_line: Header line with field names (str or bytes)
field_separator: Character used to separate fields (str or bytes, default: tab)
287defclose_stream(self,stream_handle):
-288"""
-289 Close a KNI stream.
-290
-291 Args:
-292 stream_handle: Handle returned by open_stream
-293
-294 Raises:
-295 KNIError: If closing stream fails
-296 TypeError: If stream_handle is not int
-297 """
-298ifnotisinstance(stream_handle,int):
-299raiseTypeError(
-300f"stream_handle must be int, not {type(stream_handle).__name__}"
-301)
-302ret_code=self._lib.KNICloseStream(stream_handle)
-303ifret_code!=self.KNI_OK:
-304raiseKNIError(
-305f"Failed to close stream: {self.get_error_message(ret_code)}",
-306ret_code,
-307)
+
268defclose_stream(self,stream_handle:int)->None:
+269"""
+270 Close a KNI stream.
+271
+272 Args:
+273 stream_handle: Handle returned by open_stream
+274
+275 Raises:
+276 KNIError: If closing stream fails
+277 TypeError: If stream_handle is not int
+278 """
+279ifnotisinstance(stream_handle,int):
+280raiseTypeError(
+281f"stream_handle must be int, not {type(stream_handle).__name__}"
+282)
+283ret_code=self._lib.KNICloseStream(stream_handle)
+284ifret_code!=self.KNI_OK:
+285raiseKNIError(
+286f"Failed to close stream: {self.get_error_message(ret_code)}",
+287ret_code,
+288)
309defrecode_stream_record(self,stream_handle,input_record,max_output_length=None):
-310"""
-311 Recode an input record using the stream's dictionary.
-312
-313 Args:
-314 stream_handle: Handle returned by open_stream
-315 input_record: Input record string or bytes
-316 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
-317
-318 Returns:
-319 Recoded output string
-320
-321 Raises:
-322 KNIError: If recoding fails
-323 TypeError: If arguments have invalid types
-324 """
-325ifnotisinstance(stream_handle,int):
-326raiseTypeError(
-327f"stream_handle must be int, not {type(stream_handle).__name__}"
-328)
-329ifisinstance(input_record,str):
-330input_record_bytes=input_record.encode("utf-8")
-331elifisinstance(input_record,bytes):
-332input_record_bytes=input_record
-333else:
-334raiseTypeError(
-335f"input_record must be str or bytes, not {type(input_record).__name__}"
-336)
-337ifmax_output_lengthisNone:
-338max_output_length=self.KNI_MaxRecordLength
-339elifnotisinstance(max_output_length,int):
-340raiseTypeError(
-341f"max_output_length must be int or None, not {type(max_output_length).__name__}"
-342)
-343
-344output_buffer=ctypes.create_string_buffer(max_output_length)
-345ret_code=self._lib.KNIRecodeStreamRecord(
-346stream_handle,
-347input_record_bytes,
-348output_buffer,
-349max_output_length,
-350)
-351
-352ifret_code!=self.KNI_OK:
-353raiseKNIError(
-354f"Failed to recode record: {self.get_error_message(ret_code)}",
-355ret_code,
-356)
-357returnoutput_buffer.value.decode("utf-8")
+
290defrecode_stream_record(
+291self,
+292stream_handle:int,
+293input_record:str|bytes,
+294max_output_length:int|None=None,
+295)->str:
+296"""
+297 Recode an input record using the stream's dictionary.
+298
+299 Args:
+300 stream_handle: Handle returned by open_stream
+301 input_record: Input record string or bytes
+302 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
+303
+304 Returns:
+305 Recoded output string
+306
+307 Raises:
+308 KNIError: If recoding fails
+309 TypeError: If arguments have invalid types
+310 """
+311ifnotisinstance(stream_handle,int):
+312raiseTypeError(
+313f"stream_handle must be int, not {type(stream_handle).__name__}"
+314)
+315input_record_bytes=self._to_bytes(input_record,"input_record")
+316ifmax_output_lengthisNone:
+317max_output_length=self.KNI_MaxRecordLength
+318elifnotisinstance(max_output_length,int):
+319raiseTypeError(
+320f"max_output_length must be int or None, not {type(max_output_length).__name__}"
+321)
+322
+323output_buffer=ctypes.create_string_buffer(max_output_length)
+324ret_code=self._lib.KNIRecodeStreamRecord(
+325stream_handle,
+326input_record_bytes,
+327output_buffer,
+328max_output_length,
+329)
+330
+331ifret_code!=self.KNI_OK:
+332raiseKNIError(
+333f"Failed to recode record: {self.get_error_message(ret_code)}",
+334ret_code,
+335)
+336returnoutput_buffer.value.decode("utf-8")
359defset_secondary_header_line(self,stream_handle,data_path,header_line):
-360"""
-361 Set the header line of a secondary table (multi-table only).
-362
-363 Args:
-364 stream_handle: Handle returned by open_stream
-365 data_path: Data path identifying the secondary table (str or bytes)
-366 header_line: Header line with field names (str or bytes)
-367
-368 Raises:
-369 KNIError: If setting secondary header fails
-370 TypeError: If arguments have invalid types
-371 """
-372ifnotisinstance(stream_handle,int):
-373raiseTypeError(
-374f"stream_handle must be int, not {type(stream_handle).__name__}"
-375)
-376ifisinstance(data_path,str):
-377data_path_bytes=data_path.encode("utf-8")
-378elifisinstance(data_path,bytes):
-379data_path_bytes=data_path
-380else:
-381raiseTypeError(
-382f"data_path must be str or bytes, not {type(data_path).__name__}"
-383)
-384ifisinstance(header_line,str):
-385header_line_bytes=header_line.encode("utf-8")
-386elifisinstance(header_line,bytes):
-387header_line_bytes=header_line
-388else:
-389raiseTypeError(
-390f"header_line must be str or bytes, not {type(header_line).__name__}"
-391)
-392ret_code=self._lib.KNISetSecondaryHeaderLine(
-393stream_handle,data_path_bytes,header_line_bytes
-394)
-395ifret_code!=self.KNI_OK:
-396raiseKNIError(
-397f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
-398ret_code,
-399)
+
338defset_secondary_header_line(
+339self,stream_handle:int,data_path:str|bytes,header_line:str|bytes
+340)->None:
+341"""
+342 Set the header line of a secondary table (multi-table only).
+343
+344 Args:
+345 stream_handle: Handle returned by open_stream
+346 data_path: Data path identifying the secondary table (str or bytes)
+347 header_line: Header line with field names (str or bytes)
+348
+349 Raises:
+350 KNIError: If setting secondary header fails
+351 TypeError: If arguments have invalid types
+352 """
+353ifnotisinstance(stream_handle,int):
+354raiseTypeError(
+355f"stream_handle must be int, not {type(stream_handle).__name__}"
+356)
+357data_path_bytes=self._to_bytes(data_path,"data_path")
+358header_line_bytes=self._to_bytes(header_line,"header_line")
+359ret_code=self._lib.KNISetSecondaryHeaderLine(
+360stream_handle,data_path_bytes,header_line_bytes
+361)
+362ifret_code!=self.KNI_OK:
+363raiseKNIError(
+364f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
+365ret_code,
+366)
401defset_external_table(
-402self,stream_handle,data_root,data_path,data_table_file_name
-403):
-404"""
-405 Set the name of a data file for an external table (multi-table only).
-406
-407 Args:
-408 stream_handle: Handle returned by open_stream
-409 data_root: Root dictionary of the external table (str or bytes)
-410 data_path: Data path for secondary external tables (str or bytes, empty for root)
-411 data_table_file_name: Path to the external table data file (str or bytes)
-412
-413 Raises:
-414 KNIError: If setting external table fails
-415 TypeError: If arguments have invalid types
-416 """
-417ifnotisinstance(stream_handle,int):
-418raiseTypeError(
-419f"stream_handle must be int, not {type(stream_handle).__name__}"
-420)
-421ifisinstance(data_root,str):
-422data_root_bytes=data_root.encode("utf-8")
-423elifisinstance(data_root,bytes):
-424data_root_bytes=data_root
-425else:
-426raiseTypeError(
-427f"data_root must be str or bytes, not {type(data_root).__name__}"
-428)
-429ifisinstance(data_path,str):
-430data_path_bytes=data_path.encode("utf-8")
-431elifisinstance(data_path,bytes):
-432data_path_bytes=data_path
-433else:
-434raiseTypeError(
-435f"data_path must be str or bytes, not {type(data_path).__name__}"
-436)
-437ifisinstance(data_table_file_name,str):
-438data_table_file_name_bytes=data_table_file_name.encode("utf-8")
-439elifisinstance(data_table_file_name,bytes):
-440data_table_file_name_bytes=data_table_file_name
-441else:
-442raiseTypeError(
-443f"data_table_file_name must be str or bytes, not {type(data_table_file_name).__name__}"
-444)
-445ret_code=self._lib.KNISetExternalTable(
-446stream_handle,
-447data_root_bytes,
-448data_path_bytes,
-449data_table_file_name_bytes,
-450)
-451ifret_code!=self.KNI_OK:
-452raiseKNIError(
-453f"Failed to set external table: {self.get_error_message(ret_code)}",
-454ret_code,
-455)
+
368defset_external_table(
+369self,
+370stream_handle:int,
+371data_root:str|bytes,
+372data_path:str|bytes,
+373data_table_file_name:str|bytes,
+374)->None:
+375"""
+376 Set the name of a data file for an external table (multi-table only).
+377
+378 Args:
+379 stream_handle: Handle returned by open_stream
+380 data_root: Root dictionary of the external table (str or bytes)
+381 data_path: Data path for secondary external tables (str or bytes, empty for root)
+382 data_table_file_name: Path to the external table data file (str or bytes)
+383
+384 Raises:
+385 KNIError: If setting external table fails
+386 TypeError: If arguments have invalid types
+387 """
+388ifnotisinstance(stream_handle,int):
+389raiseTypeError(
+390f"stream_handle must be int, not {type(stream_handle).__name__}"
+391)
+392data_root_bytes=self._to_bytes(data_root,"data_root")
+393data_path_bytes=self._to_bytes(data_path,"data_path")
+394data_table_file_name_bytes=self._to_bytes(
+395data_table_file_name,"data_table_file_name"
+396)
+397ret_code=self._lib.KNISetExternalTable(
+398stream_handle,
+399data_root_bytes,
+400data_path_bytes,
+401data_table_file_name_bytes,
+402)
+403ifret_code!=self.KNI_OK:
+404raiseKNIError(
+405f"Failed to set external table: {self.get_error_message(ret_code)}",
+406ret_code,
+407)
457deffinish_opening_stream(self,stream_handle):
-458"""
-459 Finish opening a stream (multi-table only).
-460
-461 Must be called after all secondary headers and external tables are set.
-462
-463 Args:
-464 stream_handle: Handle returned by open_stream
-465
-466 Raises:
-467 KNIError: If finishing opening stream fails
-468 TypeError: If stream_handle is not int
-469 """
-470ifnotisinstance(stream_handle,int):
-471raiseTypeError(
-472f"stream_handle must be int, not {type(stream_handle).__name__}"
-473)
-474ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
-475ifret_code!=self.KNI_OK:
-476raiseKNIError(
-477f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
-478ret_code,
-479)
+
409deffinish_opening_stream(self,stream_handle:int)->None:
+410"""
+411 Finish opening a stream (multi-table only).
+412
+413 Must be called after all secondary headers and external tables are set.
+414
+415 Args:
+416 stream_handle: Handle returned by open_stream
+417
+418 Raises:
+419 KNIError: If finishing opening stream fails
+420 TypeError: If stream_handle is not int
+421 """
+422ifnotisinstance(stream_handle,int):
+423raiseTypeError(
+424f"stream_handle must be int, not {type(stream_handle).__name__}"
+425)
+426ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
+427ifret_code!=self.KNI_OK:
+428raiseKNIError(
+429f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
+430ret_code,
+431)
481defset_secondary_input_record(self,stream_handle,data_path,input_record):
-482"""
-483 Set a secondary input record for multi-table recoding.
-484
-485 All secondary records must be set before recoding the primary record.
-486
-487 Args:
-488 stream_handle: Handle returned by open_stream
-489 data_path: Data path identifying the secondary table (str or bytes)
-490 input_record: Secondary input record string or bytes
-491
-492 Raises:
-493 KNIError: If setting secondary input record fails
-494 TypeError: If arguments have invalid types
-495 """
-496ifnotisinstance(stream_handle,int):
-497raiseTypeError(
-498f"stream_handle must be int, not {type(stream_handle).__name__}"
-499)
-500ifisinstance(data_path,str):
-501data_path_bytes=data_path.encode("utf-8")
-502elifisinstance(data_path,bytes):
-503data_path_bytes=data_path
-504else:
-505raiseTypeError(
-506f"data_path must be str or bytes, not {type(data_path).__name__}"
-507)
-508ifisinstance(input_record,str):
-509input_record_bytes=input_record.encode("utf-8")
-510elifisinstance(input_record,bytes):
-511input_record_bytes=input_record
-512else:
-513raiseTypeError(
-514f"input_record must be str or bytes, not {type(input_record).__name__}"
-515)
-516ret_code=self._lib.KNISetSecondaryInputRecord(
-517stream_handle,data_path_bytes,input_record_bytes
-518)
-519ifret_code!=self.KNI_OK:
-520raiseKNIError(
-521f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
-522ret_code,
-523)
+
433defset_secondary_input_record(
+434self,stream_handle:int,data_path:str|bytes,input_record:str|bytes
+435)->None:
+436"""
+437 Set a secondary input record for multi-table recoding.
+438
+439 All secondary records must be set before recoding the primary record.
+440
+441 Args:
+442 stream_handle: Handle returned by open_stream
+443 data_path: Data path identifying the secondary table (str or bytes)
+444 input_record: Secondary input record string or bytes
+445
+446 Raises:
+447 KNIError: If setting secondary input record fails
+448 TypeError: If arguments have invalid types
+449 """
+450ifnotisinstance(stream_handle,int):
+451raiseTypeError(
+452f"stream_handle must be int, not {type(stream_handle).__name__}"
+453)
+454data_path_bytes=self._to_bytes(data_path,"data_path")
+455input_record_bytes=self._to_bytes(input_record,"input_record")
+456ret_code=self._lib.KNISetSecondaryInputRecord(
+457stream_handle,data_path_bytes,input_record_bytes
+458)
+459ifret_code!=self.KNI_OK:
+460raiseKNIError(
+461f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
+462ret_code,
+463)
525defget_stream_max_memory(self):
-526"""
-527 Get the maximum amount of memory (in MB) for stream opening.
-528
-529 Returns:
-530 Maximum memory in MB
-531 """
-532returnself._lib.KNIGetStreamMaxMemory()
+
465defget_stream_max_memory(self)->int:
+466"""
+467 Get the maximum amount of memory (in MB) for stream opening.
+468
+469 Returns:
+470 Maximum memory in MB
+471 """
+472returnself._lib.KNIGetStreamMaxMemory()
534defset_stream_max_memory(self,max_mb):
-535"""
-536 Set the maximum amount of memory (in MB) for stream opening.
-537
-538 Args:
-539 max_mb: Maximum memory in MB
-540
-541 Returns:
-542 Accepted value (bounded by system limits)
-543 """
-544ifnotisinstance(max_mb,int):
-545raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
-546returnself._lib.KNISetStreamMaxMemory(max_mb)
+
474defset_stream_max_memory(self,max_mb:int)->int:
+475"""
+476 Set the maximum amount of memory (in MB) for stream opening.
+477
+478 Args:
+479 max_mb: Maximum memory in MB
+480
+481 Returns:
+482 Accepted value (bounded by system limits)
+483 """
+484ifnotisinstance(max_mb,int):
+485raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
+486returnself._lib.KNISetStreamMaxMemory(max_mb)
548@staticmethod
-549defget_error_message(error_code):
-550"""
-551 Get a human-readable error message for an error code.
-552
-553 Args:
-554 error_code: KNI error code
-555
-556 Returns:
-557 Error message string
-558 """
-559ifnotisinstance(error_code,int):
-560raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
-561error_messages={
-562KNI.KNI_OK:"Success",
-563KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
-564KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
-565KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
-566KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
-567KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
-568KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
-569KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
-570KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
-571KNI.KNI_ErrorFieldSeparator:"Bad field separator",
-572KNI.KNI_ErrorStreamHandle:"Bad stream handle",
-573KNI.KNI_ErrorStreamOpened:"Stream already opened",
-574KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
-575KNI.KNI_ErrorStreamInputRecord:"Bad input record",
-576KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
-577KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
-578KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
-579KNI.KNI_ErrorMissingExternalTable:"Missing external table",
-580KNI.KNI_ErrorDataRoot:"Bad data root",
-581KNI.KNI_ErrorDataPath:"Bad data path",
-582KNI.KNI_ErrorDataTableFile:"Bad data table file",
-583KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
-584KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
-585KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
-586KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
-587KNI.KNI_ErrorLogFile:"Bad error file",
-588}
-589returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
+
488@staticmethod
+489defget_error_message(error_code:int)->str:
+490"""
+491 Get a human-readable error message for an error code.
+492
+493 Args:
+494 error_code: KNI error code
+495
+496 Returns:
+497 Error message string
+498 """
+499ifnotisinstance(error_code,int):
+500raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
+501error_messages={
+502KNI.KNI_OK:"Success",
+503KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
+504KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
+505KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
+506KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
+507KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
+508KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
+509KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
+510KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
+511KNI.KNI_ErrorFieldSeparator:"Bad field separator",
+512KNI.KNI_ErrorStreamHandle:"Bad stream handle",
+513KNI.KNI_ErrorStreamOpened:"Stream already opened",
+514KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
+515KNI.KNI_ErrorStreamInputRecord:"Bad input record",
+516KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
+517KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
+518KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
+519KNI.KNI_ErrorMissingExternalTable:"Missing external table",
+520KNI.KNI_ErrorDataRoot:"Bad data root",
+521KNI.KNI_ErrorDataPath:"Bad data path",
+522KNI.KNI_ErrorDataTableFile:"Bad data table file",
+523KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
+524KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
+525KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
+526KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
+527KNI.KNI_ErrorLogFile:"Bad error file",
+528}
+529returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
diff --git a/python/docs/search.js b/python/docs/search.js
index 6b2cf79..f3f8e1d 100644
--- a/python/docs/search.js
+++ b/python/docs/search.js
@@ -1,6 +1,6 @@
window.pdocSearch = (function(){
/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oKhiops Native Interface (KNI) Python Package\n\n
This package provides Python bindings for the Khiops Native Interface (KNI),\nenabling direct deployment of Khiops models from Python without temporary files.
Args:\n dictionary_file_name: Path to the dictionary file (str or bytes)\n dictionary_name: Name of the dictionary to use (str or bytes)\n header_line: Header line with field names (str or bytes)\n field_separator: Character used to separate fields (str or bytes, default: tab)
\n\n
Returns:\n Stream handle (positive integer)
\n\n
Raises:\n KNIError: If opening stream fails\n TypeError: If arguments have invalid types
Recode an input record using the stream's dictionary.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n input_record: Input record string or bytes\n max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
\n\n
Returns:\n Recoded output string
\n\n
Raises:\n KNIError: If recoding fails\n TypeError: If arguments have invalid types
Set the header line of a secondary table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n header_line: Header line with field names (str or bytes)
\n\n
Raises:\n KNIError: If setting secondary header fails\n TypeError: If arguments have invalid types
Set the name of a data file for an external table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_root: Root dictionary of the external table (str or bytes)\n data_path: Data path for secondary external tables (str or bytes, empty for root)\n data_table_file_name: Path to the external table data file (str or bytes)
\n\n
Raises:\n KNIError: If setting external table fails\n TypeError: If arguments have invalid types
Set a secondary input record for multi-table recoding.
\n\n
All secondary records must be set before recoding the primary record.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n input_record: Secondary input record string or bytes
\n\n
Raises:\n KNIError: If setting secondary input record fails\n TypeError: If arguments have invalid types
This package provides Python bindings for the Khiops Native Interface (KNI),\nenabling direct deployment of Khiops models from Python without temporary files.
Args:\n dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)\n dictionary_name: Name of the dictionary to use (str or bytes)\n header_line: Header line with field names (str or bytes)\n field_separator: Character used to separate fields (str or bytes, default: tab)
\n\n
Returns:\n Stream handle (positive integer)
\n\n
Raises:\n KNIError: If opening stream fails\n TypeError: If arguments have invalid types
Recode an input record using the stream's dictionary.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n input_record: Input record string or bytes\n max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
\n\n
Returns:\n Recoded output string
\n\n
Raises:\n KNIError: If recoding fails\n TypeError: If arguments have invalid types
Set the header line of a secondary table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n header_line: Header line with field names (str or bytes)
\n\n
Raises:\n KNIError: If setting secondary header fails\n TypeError: If arguments have invalid types
Set the name of a data file for an external table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_root: Root dictionary of the external table (str or bytes)\n data_path: Data path for secondary external tables (str or bytes, empty for root)\n data_table_file_name: Path to the external table data file (str or bytes)
\n\n
Raises:\n KNIError: If setting external table fails\n TypeError: If arguments have invalid types
Set a secondary input record for multi-table recoding.
\n\n
All secondary records must be set before recoding the primary record.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n input_record: Secondary input record string or bytes
\n\n
Raises:\n KNIError: If setting secondary input record fails\n TypeError: If arguments have invalid types
This package provides Python bindings for the Khiops Native Interface (KNI),
-enabling direct deployment of Khiops models from Python without temporary files.
-
-
-
-
-
-
-
1# Copyright (c) 2023-2026 Orange. All rights reserved.
- 2# This software is distributed under the BSD 3-Clause-clear License, the text of which is available
- 3# at https://spdx.org/licenses/BSD-3-Clause-Clear.html or see the "LICENSE" file for more details.
- 4
- 5"""
- 6Khiops Native Interface (KNI) Python Package
- 7
- 8This package provides Python bindings for the Khiops Native Interface (KNI),
- 9enabling direct deployment of Khiops models from Python without temporary files.
-10"""
-11
-12from.kniimportKNI,KNIError
-13
-14__all__=["KNI","KNIError"]
-15
-16# Get version from package metadata (set by scikit-build-core during wheel building)
-17try:
-18fromimportlib.metadataimportversion
-19
-20__version__=version("khiops-kni")
-21exceptException:
-22# Fallback for development installations
-23__version__="0.0.0.dev0"
-
-
-
-
-
-
-
-
- class
- KNI:
-
-
-
-
-
-
29classKNI:
- 30"""Python wrapper for Khiops Native Interface using ctypes."""
- 31
- 32# Error codes
- 33KNI_OK=0
- 34KNI_ErrorRunningFunction=-1
- 35KNI_ErrorDictionaryFileName=-2
- 36KNI_ErrorDictionaryMissingFile=-3
- 37KNI_ErrorDictionaryFileFormat=-4
- 38KNI_ErrorDictionaryName=-5
- 39KNI_ErrorMissingDictionary=-6
- 40KNI_ErrorTooManyStreams=-7
- 41KNI_ErrorStreamHeaderLine=-8
- 42KNI_ErrorFieldSeparator=-9
- 43KNI_ErrorStreamHandle=-10
- 44KNI_ErrorStreamOpened=-11
- 45KNI_ErrorStreamNotOpened=-12
- 46KNI_ErrorStreamInputRecord=-13
- 47KNI_ErrorStreamInputRead=-14
- 48KNI_ErrorStreamOutputRecord=-15
- 49KNI_ErrorMissingSecondaryHeader=-16
- 50KNI_ErrorMissingExternalTable=-17
- 51KNI_ErrorDataRoot=-18
- 52KNI_ErrorDataPath=-19
- 53KNI_ErrorDataTableFile=-20
- 54KNI_ErrorLoadDataTable=-21
- 55KNI_ErrorMemoryOverflow=-22
- 56KNI_ErrorStreamOpening=-23
- 57KNI_ErrorStreamOpeningNotFinished=-24
- 58KNI_ErrorLogFile=-25
- 59
- 60# Constants
- 61KNI_MaxStreamNumber=512
- 62KNI_DefaultMaxStreamMemory=100
- 63KNI_MaxPathNameLength=1024
- 64KNI_MaxDictionaryNameLength=128
- 65KNI_MaxRecordLength=8*1024*1024# 8 MB
- 66
- 67def__init__(self,library_path:str|bytes|Path|None=None):
- 68"""
- 69 Initialize the KNI wrapper.
- 70
- 71 Args:
- 72 library_path: Optional path to the KNI shared library (str, bytes, or
- 73 pathlib.Path). If None, attempts to locate it automatically.
- 74 """
- 75self._lib=self._load_library(library_path)
- 76self._setup_function_signatures()
- 77
- 78def_load_library(self,library_path:str|bytes|Path|None):
- 79"""Load the KNI shared library."""
- 80iflibrary_path:
- 81returnctypes.CDLL(os.fsdecode(library_path))
- 82
- 83# Try to locate library automatically
- 84system=platform.system()
- 85ifsystem=="Windows":
- 86lib_name="KhiopsNativeInterface.dll"
- 87elifsystem=="Linux":
- 88lib_name="libKhiopsNativeInterface.so"
- 89elifsystem=="Darwin":# macOS
- 90lib_name="libKhiopsNativeInterface.dylib"
- 91else:
- 92raiseRuntimeError(f"Unsupported platform: {system}")
- 93
- 94# When installed via pip, the shared library is placed in the Python
- 95# scripts directory (bin/ on Unix, Scripts/ on Windows)
- 96scripts_dir=sysconfig.get_path("scripts")
- 97ifscripts_dir:
- 98lib_path=Path(scripts_dir)/lib_name
- 99iflib_path.exists():
-100returnctypes.CDLL(str(lib_path))
-101
-102raiseRuntimeError(
-103f"Could not find {lib_name}. Reinstall the khiops-kni package."
-104)
-105
-106def_setup_function_signatures(self):
-107"""Setup function signatures for KNI library functions."""
-108# KNIGetVersion
-109self._lib.KNIGetVersion.argtypes=[]
-110self._lib.KNIGetVersion.restype=ctypes.c_int
-111
-112# KNIGetFullVersion
-113self._lib.KNIGetFullVersion.argtypes=[]
-114self._lib.KNIGetFullVersion.restype=ctypes.c_char_p
-115
-116# KNISetLogFileName
-117self._lib.KNISetLogFileName.argtypes=[ctypes.c_char_p]
-118self._lib.KNISetLogFileName.restype=ctypes.c_int
-119
-120# KNIOpenStream
-121self._lib.KNIOpenStream.argtypes=[
-122ctypes.c_char_p,# sDictionaryFileName
-123ctypes.c_char_p,# sDictionaryName
-124ctypes.c_char_p,# sStreamHeaderLine
-125ctypes.c_char,# cFieldSeparator
-126]
-127self._lib.KNIOpenStream.restype=ctypes.c_int
-128
-129# KNICloseStream
-130self._lib.KNICloseStream.argtypes=[ctypes.c_int]
-131self._lib.KNICloseStream.restype=ctypes.c_int
-132
-133# KNIRecodeStreamRecord
-134self._lib.KNIRecodeStreamRecord.argtypes=[
-135ctypes.c_int,# hStream
-136ctypes.c_char_p,# sInputRecord
-137ctypes.c_char_p,# sOutputRecord
-138ctypes.c_int,# nOutputMaxLength
-139]
-140self._lib.KNIRecodeStreamRecord.restype=ctypes.c_int
-141
-142# Multi-table functions
-143# KNISetSecondaryHeaderLine
-144self._lib.KNISetSecondaryHeaderLine.argtypes=[
-145ctypes.c_int,# hStream
-146ctypes.c_char_p,# sDataPath
-147ctypes.c_char_p,# sStreamSecondaryHeaderLine
-148]
-149self._lib.KNISetSecondaryHeaderLine.restype=ctypes.c_int
-150
-151# KNISetExternalTable
-152self._lib.KNISetExternalTable.argtypes=[
-153ctypes.c_int,# hStream
-154ctypes.c_char_p,# sDataRoot
-155ctypes.c_char_p,# sDataPath
-156ctypes.c_char_p,# sDataTableFileName
-157]
-158self._lib.KNISetExternalTable.restype=ctypes.c_int
-159
-160# KNIFinishOpeningStream
-161self._lib.KNIFinishOpeningStream.argtypes=[ctypes.c_int]
-162self._lib.KNIFinishOpeningStream.restype=ctypes.c_int
-163
-164# KNISetSecondaryInputRecord
-165self._lib.KNISetSecondaryInputRecord.argtypes=[
-166ctypes.c_int,# hStream
-167ctypes.c_char_p,# sDataPath
-168ctypes.c_char_p,# sStreamSecondaryInputRecord
-169]
-170self._lib.KNISetSecondaryInputRecord.restype=ctypes.c_int
-171
-172# Advanced parameters
-173# KNIGetStreamMaxMemory
-174self._lib.KNIGetStreamMaxMemory.argtypes=[]
-175self._lib.KNIGetStreamMaxMemory.restype=ctypes.c_int
-176
-177# KNISetStreamMaxMemory
-178self._lib.KNISetStreamMaxMemory.argtypes=[ctypes.c_int]
-179self._lib.KNISetStreamMaxMemory.restype=ctypes.c_int
-180
-181@staticmethod
-182def_to_bytes(value:str|bytes,param_name:str)->bytes:
-183"""Encode a str or bytes parameter to UTF-8 bytes."""
-184ifisinstance(value,str):
-185returnvalue.encode("utf-8")
-186ifisinstance(value,bytes):
-187returnvalue
-188raiseTypeError(
-189f"{param_name} must be str or bytes, not {type(value).__name__}"
-190)
-191
-192defget_version(self)->int:
-193"""Get KNI version as integer (10*major + minor)."""
-194returnself._lib.KNIGetVersion()
-195
-196defget_full_version(self)->str:
-197"""Get KNI full version string."""
-198returnself._lib.KNIGetFullVersion().decode("utf-8")
-199
-200defset_log_file_name(self,log_file_name:str|bytes)->None:
-201"""
-202 Set the log file name for error messages.
-203
-204 Args:
-205 log_file_name: Path to log file (str or bytes, empty string for no logging)
-206
-207 Raises:
-208 KNIError: If setting log file fails
-209 TypeError: If log_file_name is not str or bytes
-210 """
-211log_file_name_bytes=self._to_bytes(log_file_name,"log_file_name")
-212ret_code=self._lib.KNISetLogFileName(log_file_name_bytes)
-213ifret_code!=self.KNI_OK:
-214raiseKNIError(
-215f"Failed to set log file: {self.get_error_message(ret_code)}",
-216ret_code,
-217)
-218
-219defopen_stream(
-220self,
-221dictionary_file_path:str|bytes|Path,
-222dictionary_name:str|bytes,
-223header_line:str|bytes,
-224field_separator:str|bytes="\t",
-225)->int:
-226"""
-227 Open a KNI stream for recoding.
-228
-229 Args:
-230 dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
-231 dictionary_name: Name of the dictionary to use (str or bytes)
-232 header_line: Header line with field names (str or bytes)
-233 field_separator: Character used to separate fields (str or bytes, default: tab)
-234
-235 Returns:
-236 Stream handle (positive integer)
-237
-238 Raises:
-239 KNIError: If opening stream fails
-240 TypeError: If arguments have invalid types
-241 """
-242# Type checking and conversion
-243ifisinstance(dictionary_file_path,Path):
-244dictionary_file_path=str(dictionary_file_path)
-245dictionary_file_path_bytes=self._to_bytes(
-246dictionary_file_path,"dictionary_file_path"
-247)
-248dictionary_name_bytes=self._to_bytes(dictionary_name,"dictionary_name")
-249header_line_bytes=self._to_bytes(header_line,"header_line")
-250field_separator_bytes=self._to_bytes(field_separator,"field_separator")
-251iflen(field_separator_bytes)==0:
-252raiseValueError("field_separator must not be empty")
-253field_separator_byte=field_separator_bytes[0]
-254
-255stream_handle=self._lib.KNIOpenStream(
-256dictionary_file_path_bytes,
-257dictionary_name_bytes,
-258header_line_bytes,
-259field_separator_byte,
-260)
-261ifstream_handle<0:
-262raiseKNIError(
-263f"Failed to open stream: {self.get_error_message(stream_handle)}",
-264stream_handle,
-265)
-266returnstream_handle
-267
-268defclose_stream(self,stream_handle:int)->None:
-269"""
-270 Close a KNI stream.
-271
-272 Args:
-273 stream_handle: Handle returned by open_stream
-274
-275 Raises:
-276 KNIError: If closing stream fails
-277 TypeError: If stream_handle is not int
-278 """
-279ifnotisinstance(stream_handle,int):
-280raiseTypeError(
-281f"stream_handle must be int, not {type(stream_handle).__name__}"
-282)
-283ret_code=self._lib.KNICloseStream(stream_handle)
-284ifret_code!=self.KNI_OK:
-285raiseKNIError(
-286f"Failed to close stream: {self.get_error_message(ret_code)}",
-287ret_code,
-288)
-289
-290defrecode_stream_record(
-291self,
-292stream_handle:int,
-293input_record:str|bytes,
-294max_output_length:int|None=None,
-295)->str:
-296"""
-297 Recode an input record using the stream's dictionary.
-298
-299 Args:
-300 stream_handle: Handle returned by open_stream
-301 input_record: Input record string or bytes
-302 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
-303
-304 Returns:
-305 Recoded output string
-306
-307 Raises:
-308 KNIError: If recoding fails
-309 TypeError: If arguments have invalid types
-310 """
-311ifnotisinstance(stream_handle,int):
-312raiseTypeError(
-313f"stream_handle must be int, not {type(stream_handle).__name__}"
-314)
-315input_record_bytes=self._to_bytes(input_record,"input_record")
-316ifmax_output_lengthisNone:
-317max_output_length=self.KNI_MaxRecordLength
-318elifnotisinstance(max_output_length,int):
-319raiseTypeError(
-320f"max_output_length must be int or None, not {type(max_output_length).__name__}"
-321)
-322
-323output_buffer=ctypes.create_string_buffer(max_output_length)
-324ret_code=self._lib.KNIRecodeStreamRecord(
-325stream_handle,
-326input_record_bytes,
-327output_buffer,
-328max_output_length,
-329)
-330
-331ifret_code!=self.KNI_OK:
-332raiseKNIError(
-333f"Failed to recode record: {self.get_error_message(ret_code)}",
-334ret_code,
-335)
-336returnoutput_buffer.value.decode("utf-8")
-337
-338defset_secondary_header_line(
-339self,stream_handle:int,data_path:str|bytes,header_line:str|bytes
-340)->None:
-341"""
-342 Set the header line of a secondary table (multi-table only).
-343
-344 Args:
-345 stream_handle: Handle returned by open_stream
-346 data_path: Data path identifying the secondary table (str or bytes)
-347 header_line: Header line with field names (str or bytes)
-348
-349 Raises:
-350 KNIError: If setting secondary header fails
-351 TypeError: If arguments have invalid types
-352 """
-353ifnotisinstance(stream_handle,int):
-354raiseTypeError(
-355f"stream_handle must be int, not {type(stream_handle).__name__}"
-356)
-357data_path_bytes=self._to_bytes(data_path,"data_path")
-358header_line_bytes=self._to_bytes(header_line,"header_line")
-359ret_code=self._lib.KNISetSecondaryHeaderLine(
-360stream_handle,data_path_bytes,header_line_bytes
-361)
-362ifret_code!=self.KNI_OK:
-363raiseKNIError(
-364f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
-365ret_code,
-366)
-367
-368defset_external_table(
-369self,
-370stream_handle:int,
-371data_root:str|bytes,
-372data_path:str|bytes,
-373data_table_file_name:str|bytes,
-374)->None:
-375"""
-376 Set the name of a data file for an external table (multi-table only).
-377
-378 Args:
-379 stream_handle: Handle returned by open_stream
-380 data_root: Root dictionary of the external table (str or bytes)
-381 data_path: Data path for secondary external tables (str or bytes, empty for root)
-382 data_table_file_name: Path to the external table data file (str or bytes)
-383
-384 Raises:
-385 KNIError: If setting external table fails
-386 TypeError: If arguments have invalid types
-387 """
-388ifnotisinstance(stream_handle,int):
-389raiseTypeError(
-390f"stream_handle must be int, not {type(stream_handle).__name__}"
-391)
-392data_root_bytes=self._to_bytes(data_root,"data_root")
-393data_path_bytes=self._to_bytes(data_path,"data_path")
-394data_table_file_name_bytes=self._to_bytes(
-395data_table_file_name,"data_table_file_name"
-396)
-397ret_code=self._lib.KNISetExternalTable(
-398stream_handle,
-399data_root_bytes,
-400data_path_bytes,
-401data_table_file_name_bytes,
-402)
-403ifret_code!=self.KNI_OK:
-404raiseKNIError(
-405f"Failed to set external table: {self.get_error_message(ret_code)}",
-406ret_code,
-407)
-408
-409deffinish_opening_stream(self,stream_handle:int)->None:
-410"""
-411 Finish opening a stream (multi-table only).
-412
-413 Must be called after all secondary headers and external tables are set.
-414
-415 Args:
-416 stream_handle: Handle returned by open_stream
-417
-418 Raises:
-419 KNIError: If finishing opening stream fails
-420 TypeError: If stream_handle is not int
-421 """
-422ifnotisinstance(stream_handle,int):
-423raiseTypeError(
-424f"stream_handle must be int, not {type(stream_handle).__name__}"
-425)
-426ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
-427ifret_code!=self.KNI_OK:
-428raiseKNIError(
-429f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
-430ret_code,
-431)
-432
-433defset_secondary_input_record(
-434self,stream_handle:int,data_path:str|bytes,input_record:str|bytes
-435)->None:
-436"""
-437 Set a secondary input record for multi-table recoding.
-438
-439 All secondary records must be set before recoding the primary record.
-440
-441 Args:
-442 stream_handle: Handle returned by open_stream
-443 data_path: Data path identifying the secondary table (str or bytes)
-444 input_record: Secondary input record string or bytes
-445
-446 Raises:
-447 KNIError: If setting secondary input record fails
-448 TypeError: If arguments have invalid types
-449 """
-450ifnotisinstance(stream_handle,int):
-451raiseTypeError(
-452f"stream_handle must be int, not {type(stream_handle).__name__}"
-453)
-454data_path_bytes=self._to_bytes(data_path,"data_path")
-455input_record_bytes=self._to_bytes(input_record,"input_record")
-456ret_code=self._lib.KNISetSecondaryInputRecord(
-457stream_handle,data_path_bytes,input_record_bytes
-458)
-459ifret_code!=self.KNI_OK:
-460raiseKNIError(
-461f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
-462ret_code,
-463)
-464
-465defget_stream_max_memory(self)->int:
-466"""
-467 Get the maximum amount of memory (in MB) for stream opening.
-468
-469 Returns:
-470 Maximum memory in MB
-471 """
-472returnself._lib.KNIGetStreamMaxMemory()
-473
-474defset_stream_max_memory(self,max_mb:int)->int:
-475"""
-476 Set the maximum amount of memory (in MB) for stream opening.
-477
-478 Args:
-479 max_mb: Maximum memory in MB
-480
-481 Returns:
-482 Accepted value (bounded by system limits)
-483 """
-484ifnotisinstance(max_mb,int):
-485raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
-486returnself._lib.KNISetStreamMaxMemory(max_mb)
-487
-488@staticmethod
-489defget_error_message(error_code:int)->str:
-490"""
-491 Get a human-readable error message for an error code.
-492
-493 Args:
-494 error_code: KNI error code
-495
-496 Returns:
-497 Error message string
-498 """
-499ifnotisinstance(error_code,int):
-500raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
-501error_messages={
-502KNI.KNI_OK:"Success",
-503KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
-504KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
-505KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
-506KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
-507KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
-508KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
-509KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
-510KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
-511KNI.KNI_ErrorFieldSeparator:"Bad field separator",
-512KNI.KNI_ErrorStreamHandle:"Bad stream handle",
-513KNI.KNI_ErrorStreamOpened:"Stream already opened",
-514KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
-515KNI.KNI_ErrorStreamInputRecord:"Bad input record",
-516KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
-517KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
-518KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
-519KNI.KNI_ErrorMissingExternalTable:"Missing external table",
-520KNI.KNI_ErrorDataRoot:"Bad data root",
-521KNI.KNI_ErrorDataPath:"Bad data path",
-522KNI.KNI_ErrorDataTableFile:"Bad data table file",
-523KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
-524KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
-525KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
-526KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
-527KNI.KNI_ErrorLogFile:"Bad error file",
-528}
-529returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
-
-
-
-
Python wrapper for Khiops Native Interface using ctypes.
200defset_log_file_name(self,log_file_name:str|bytes)->None:
-201"""
-202 Set the log file name for error messages.
-203
-204 Args:
-205 log_file_name: Path to log file (str or bytes, empty string for no logging)
-206
-207 Raises:
-208 KNIError: If setting log file fails
-209 TypeError: If log_file_name is not str or bytes
-210 """
-211log_file_name_bytes=self._to_bytes(log_file_name,"log_file_name")
-212ret_code=self._lib.KNISetLogFileName(log_file_name_bytes)
-213ifret_code!=self.KNI_OK:
-214raiseKNIError(
-215f"Failed to set log file: {self.get_error_message(ret_code)}",
-216ret_code,
-217)
-
-
-
-
Set the log file name for error messages.
-
-
Args:
- log_file_name: Path to log file (str or bytes, empty string for no logging)
-
-
Raises:
- KNIError: If setting log file fails
- TypeError: If log_file_name is not str or bytes
219defopen_stream(
-220self,
-221dictionary_file_path:str|bytes|Path,
-222dictionary_name:str|bytes,
-223header_line:str|bytes,
-224field_separator:str|bytes="\t",
-225)->int:
-226"""
-227 Open a KNI stream for recoding.
-228
-229 Args:
-230 dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
-231 dictionary_name: Name of the dictionary to use (str or bytes)
-232 header_line: Header line with field names (str or bytes)
-233 field_separator: Character used to separate fields (str or bytes, default: tab)
-234
-235 Returns:
-236 Stream handle (positive integer)
-237
-238 Raises:
-239 KNIError: If opening stream fails
-240 TypeError: If arguments have invalid types
-241 """
-242# Type checking and conversion
-243ifisinstance(dictionary_file_path,Path):
-244dictionary_file_path=str(dictionary_file_path)
-245dictionary_file_path_bytes=self._to_bytes(
-246dictionary_file_path,"dictionary_file_path"
-247)
-248dictionary_name_bytes=self._to_bytes(dictionary_name,"dictionary_name")
-249header_line_bytes=self._to_bytes(header_line,"header_line")
-250field_separator_bytes=self._to_bytes(field_separator,"field_separator")
-251iflen(field_separator_bytes)==0:
-252raiseValueError("field_separator must not be empty")
-253field_separator_byte=field_separator_bytes[0]
-254
-255stream_handle=self._lib.KNIOpenStream(
-256dictionary_file_path_bytes,
-257dictionary_name_bytes,
-258header_line_bytes,
-259field_separator_byte,
-260)
-261ifstream_handle<0:
-262raiseKNIError(
-263f"Failed to open stream: {self.get_error_message(stream_handle)}",
-264stream_handle,
-265)
-266returnstream_handle
-
-
-
-
Open a KNI stream for recoding.
-
-
Args:
- dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)
- dictionary_name: Name of the dictionary to use (str or bytes)
- header_line: Header line with field names (str or bytes)
- field_separator: Character used to separate fields (str or bytes, default: tab)
-
-
Returns:
- Stream handle (positive integer)
-
-
Raises:
- KNIError: If opening stream fails
- TypeError: If arguments have invalid types
268defclose_stream(self,stream_handle:int)->None:
-269"""
-270 Close a KNI stream.
-271
-272 Args:
-273 stream_handle: Handle returned by open_stream
-274
-275 Raises:
-276 KNIError: If closing stream fails
-277 TypeError: If stream_handle is not int
-278 """
-279ifnotisinstance(stream_handle,int):
-280raiseTypeError(
-281f"stream_handle must be int, not {type(stream_handle).__name__}"
-282)
-283ret_code=self._lib.KNICloseStream(stream_handle)
-284ifret_code!=self.KNI_OK:
-285raiseKNIError(
-286f"Failed to close stream: {self.get_error_message(ret_code)}",
-287ret_code,
-288)
-
-
-
-
Close a KNI stream.
-
-
Args:
- stream_handle: Handle returned by open_stream
-
-
Raises:
- KNIError: If closing stream fails
- TypeError: If stream_handle is not int
290defrecode_stream_record(
-291self,
-292stream_handle:int,
-293input_record:str|bytes,
-294max_output_length:int|None=None,
-295)->str:
-296"""
-297 Recode an input record using the stream's dictionary.
-298
-299 Args:
-300 stream_handle: Handle returned by open_stream
-301 input_record: Input record string or bytes
-302 max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
-303
-304 Returns:
-305 Recoded output string
-306
-307 Raises:
-308 KNIError: If recoding fails
-309 TypeError: If arguments have invalid types
-310 """
-311ifnotisinstance(stream_handle,int):
-312raiseTypeError(
-313f"stream_handle must be int, not {type(stream_handle).__name__}"
-314)
-315input_record_bytes=self._to_bytes(input_record,"input_record")
-316ifmax_output_lengthisNone:
-317max_output_length=self.KNI_MaxRecordLength
-318elifnotisinstance(max_output_length,int):
-319raiseTypeError(
-320f"max_output_length must be int or None, not {type(max_output_length).__name__}"
-321)
-322
-323output_buffer=ctypes.create_string_buffer(max_output_length)
-324ret_code=self._lib.KNIRecodeStreamRecord(
-325stream_handle,
-326input_record_bytes,
-327output_buffer,
-328max_output_length,
-329)
-330
-331ifret_code!=self.KNI_OK:
-332raiseKNIError(
-333f"Failed to recode record: {self.get_error_message(ret_code)}",
-334ret_code,
-335)
-336returnoutput_buffer.value.decode("utf-8")
-
-
-
-
Recode an input record using the stream's dictionary.
-
-
Args:
- stream_handle: Handle returned by open_stream
- input_record: Input record string or bytes
- max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
-
-
Returns:
- Recoded output string
-
-
Raises:
- KNIError: If recoding fails
- TypeError: If arguments have invalid types
338defset_secondary_header_line(
-339self,stream_handle:int,data_path:str|bytes,header_line:str|bytes
-340)->None:
-341"""
-342 Set the header line of a secondary table (multi-table only).
-343
-344 Args:
-345 stream_handle: Handle returned by open_stream
-346 data_path: Data path identifying the secondary table (str or bytes)
-347 header_line: Header line with field names (str or bytes)
-348
-349 Raises:
-350 KNIError: If setting secondary header fails
-351 TypeError: If arguments have invalid types
-352 """
-353ifnotisinstance(stream_handle,int):
-354raiseTypeError(
-355f"stream_handle must be int, not {type(stream_handle).__name__}"
-356)
-357data_path_bytes=self._to_bytes(data_path,"data_path")
-358header_line_bytes=self._to_bytes(header_line,"header_line")
-359ret_code=self._lib.KNISetSecondaryHeaderLine(
-360stream_handle,data_path_bytes,header_line_bytes
-361)
-362ifret_code!=self.KNI_OK:
-363raiseKNIError(
-364f"Failed to set secondary header line: {self.get_error_message(ret_code)}",
-365ret_code,
-366)
-
-
-
-
Set the header line of a secondary table (multi-table only).
-
-
Args:
- stream_handle: Handle returned by open_stream
- data_path: Data path identifying the secondary table (str or bytes)
- header_line: Header line with field names (str or bytes)
-
-
Raises:
- KNIError: If setting secondary header fails
- TypeError: If arguments have invalid types
368defset_external_table(
-369self,
-370stream_handle:int,
-371data_root:str|bytes,
-372data_path:str|bytes,
-373data_table_file_name:str|bytes,
-374)->None:
-375"""
-376 Set the name of a data file for an external table (multi-table only).
-377
-378 Args:
-379 stream_handle: Handle returned by open_stream
-380 data_root: Root dictionary of the external table (str or bytes)
-381 data_path: Data path for secondary external tables (str or bytes, empty for root)
-382 data_table_file_name: Path to the external table data file (str or bytes)
-383
-384 Raises:
-385 KNIError: If setting external table fails
-386 TypeError: If arguments have invalid types
-387 """
-388ifnotisinstance(stream_handle,int):
-389raiseTypeError(
-390f"stream_handle must be int, not {type(stream_handle).__name__}"
-391)
-392data_root_bytes=self._to_bytes(data_root,"data_root")
-393data_path_bytes=self._to_bytes(data_path,"data_path")
-394data_table_file_name_bytes=self._to_bytes(
-395data_table_file_name,"data_table_file_name"
-396)
-397ret_code=self._lib.KNISetExternalTable(
-398stream_handle,
-399data_root_bytes,
-400data_path_bytes,
-401data_table_file_name_bytes,
-402)
-403ifret_code!=self.KNI_OK:
-404raiseKNIError(
-405f"Failed to set external table: {self.get_error_message(ret_code)}",
-406ret_code,
-407)
-
-
-
-
Set the name of a data file for an external table (multi-table only).
-
-
Args:
- stream_handle: Handle returned by open_stream
- data_root: Root dictionary of the external table (str or bytes)
- data_path: Data path for secondary external tables (str or bytes, empty for root)
- data_table_file_name: Path to the external table data file (str or bytes)
-
-
Raises:
- KNIError: If setting external table fails
- TypeError: If arguments have invalid types
409deffinish_opening_stream(self,stream_handle:int)->None:
-410"""
-411 Finish opening a stream (multi-table only).
-412
-413 Must be called after all secondary headers and external tables are set.
-414
-415 Args:
-416 stream_handle: Handle returned by open_stream
-417
-418 Raises:
-419 KNIError: If finishing opening stream fails
-420 TypeError: If stream_handle is not int
-421 """
-422ifnotisinstance(stream_handle,int):
-423raiseTypeError(
-424f"stream_handle must be int, not {type(stream_handle).__name__}"
-425)
-426ret_code=self._lib.KNIFinishOpeningStream(stream_handle)
-427ifret_code!=self.KNI_OK:
-428raiseKNIError(
-429f"Failed to finish opening stream: {self.get_error_message(ret_code)}",
-430ret_code,
-431)
-
-
-
-
Finish opening a stream (multi-table only).
-
-
Must be called after all secondary headers and external tables are set.
-
-
Args:
- stream_handle: Handle returned by open_stream
-
-
Raises:
- KNIError: If finishing opening stream fails
- TypeError: If stream_handle is not int
433defset_secondary_input_record(
-434self,stream_handle:int,data_path:str|bytes,input_record:str|bytes
-435)->None:
-436"""
-437 Set a secondary input record for multi-table recoding.
-438
-439 All secondary records must be set before recoding the primary record.
-440
-441 Args:
-442 stream_handle: Handle returned by open_stream
-443 data_path: Data path identifying the secondary table (str or bytes)
-444 input_record: Secondary input record string or bytes
-445
-446 Raises:
-447 KNIError: If setting secondary input record fails
-448 TypeError: If arguments have invalid types
-449 """
-450ifnotisinstance(stream_handle,int):
-451raiseTypeError(
-452f"stream_handle must be int, not {type(stream_handle).__name__}"
-453)
-454data_path_bytes=self._to_bytes(data_path,"data_path")
-455input_record_bytes=self._to_bytes(input_record,"input_record")
-456ret_code=self._lib.KNISetSecondaryInputRecord(
-457stream_handle,data_path_bytes,input_record_bytes
-458)
-459ifret_code!=self.KNI_OK:
-460raiseKNIError(
-461f"Failed to set secondary input record: {self.get_error_message(ret_code)}",
-462ret_code,
-463)
-
-
-
-
Set a secondary input record for multi-table recoding.
-
-
All secondary records must be set before recoding the primary record.
-
-
Args:
- stream_handle: Handle returned by open_stream
- data_path: Data path identifying the secondary table (str or bytes)
- input_record: Secondary input record string or bytes
-
-
Raises:
- KNIError: If setting secondary input record fails
- TypeError: If arguments have invalid types
465defget_stream_max_memory(self)->int:
-466"""
-467 Get the maximum amount of memory (in MB) for stream opening.
-468
-469 Returns:
-470 Maximum memory in MB
-471 """
-472returnself._lib.KNIGetStreamMaxMemory()
-
-
-
-
Get the maximum amount of memory (in MB) for stream opening.
474defset_stream_max_memory(self,max_mb:int)->int:
-475"""
-476 Set the maximum amount of memory (in MB) for stream opening.
-477
-478 Args:
-479 max_mb: Maximum memory in MB
-480
-481 Returns:
-482 Accepted value (bounded by system limits)
-483 """
-484ifnotisinstance(max_mb,int):
-485raiseTypeError(f"max_mb must be int, not {type(max_mb).__name__}")
-486returnself._lib.KNISetStreamMaxMemory(max_mb)
-
-
-
-
Set the maximum amount of memory (in MB) for stream opening.
-
-
Args:
- max_mb: Maximum memory in MB
-
-
Returns:
- Accepted value (bounded by system limits)
488@staticmethod
-489defget_error_message(error_code:int)->str:
-490"""
-491 Get a human-readable error message for an error code.
-492
-493 Args:
-494 error_code: KNI error code
-495
-496 Returns:
-497 Error message string
-498 """
-499ifnotisinstance(error_code,int):
-500raiseTypeError(f"error_code must be int, not {type(error_code).__name__}")
-501error_messages={
-502KNI.KNI_OK:"Success",
-503KNI.KNI_ErrorRunningFunction:"Another KNI function is currently running",
-504KNI.KNI_ErrorDictionaryFileName:"Bad dictionary file name",
-505KNI.KNI_ErrorDictionaryMissingFile:"Dictionary file does not exist",
-506KNI.KNI_ErrorDictionaryFileFormat:"Bad dictionary format",
-507KNI.KNI_ErrorDictionaryName:"Bad dictionary name",
-508KNI.KNI_ErrorMissingDictionary:"Dictionary not found in dictionary file",
-509KNI.KNI_ErrorTooManyStreams:"Too many streams opened",
-510KNI.KNI_ErrorStreamHeaderLine:"Bad stream header line",
-511KNI.KNI_ErrorFieldSeparator:"Bad field separator",
-512KNI.KNI_ErrorStreamHandle:"Bad stream handle",
-513KNI.KNI_ErrorStreamOpened:"Stream already opened",
-514KNI.KNI_ErrorStreamNotOpened:"Stream not opened",
-515KNI.KNI_ErrorStreamInputRecord:"Bad input record",
-516KNI.KNI_ErrorStreamInputRead:"Problem reading input record",
-517KNI.KNI_ErrorStreamOutputRecord:"Output record too long",
-518KNI.KNI_ErrorMissingSecondaryHeader:"Missing secondary table header",
-519KNI.KNI_ErrorMissingExternalTable:"Missing external table",
-520KNI.KNI_ErrorDataRoot:"Bad data root",
-521KNI.KNI_ErrorDataPath:"Bad data path",
-522KNI.KNI_ErrorDataTableFile:"Bad data table file",
-523KNI.KNI_ErrorLoadDataTable:"Problem loading external data tables",
-524KNI.KNI_ErrorMemoryOverflow:"Memory overflow",
-525KNI.KNI_ErrorStreamOpening:"Stream could not be opened",
-526KNI.KNI_ErrorStreamOpeningNotFinished:"Multi-table stream opening not finished",
-527KNI.KNI_ErrorLogFile:"Bad error file",
-528}
-529returnerror_messages.get(error_code,f"Unknown error code: {error_code}")
-
-
-
-
Get a human-readable error message for an error code.
-
-
-
-
\ No newline at end of file
diff --git a/python/docs/kni.md b/python/docs/kni.md
new file mode 100644
index 0000000..f9458c4
--- /dev/null
+++ b/python/docs/kni.md
@@ -0,0 +1,339 @@
+# Table of Contents
+
+* [kni](#kni)
+ * [KNIError](#kni.KNIError)
+ * [KNI](#kni.KNI)
+ * [KNI\_MaxRecordLength](#kni.KNI.KNI_MaxRecordLength)
+ * [\_\_init\_\_](#kni.KNI.__init__)
+ * [get\_version](#kni.KNI.get_version)
+ * [get\_full\_version](#kni.KNI.get_full_version)
+ * [set\_log\_file\_name](#kni.KNI.set_log_file_name)
+ * [open\_stream](#kni.KNI.open_stream)
+ * [close\_stream](#kni.KNI.close_stream)
+ * [recode\_stream\_record](#kni.KNI.recode_stream_record)
+ * [set\_secondary\_header\_line](#kni.KNI.set_secondary_header_line)
+ * [set\_external\_table](#kni.KNI.set_external_table)
+ * [finish\_opening\_stream](#kni.KNI.finish_opening_stream)
+ * [set\_secondary\_input\_record](#kni.KNI.set_secondary_input_record)
+ * [get\_stream\_max\_memory](#kni.KNI.get_stream_max_memory)
+ * [set\_stream\_max\_memory](#kni.KNI.set_stream_max_memory)
+ * [get\_error\_message](#kni.KNI.get_error_message)
+
+
+
+# kni
+
+Khiops Native Interface (KNI) Python wrapper using ctypes.
+
+This module provides a Python interface to the Khiops Native Interface (KNI) C library,
+allowing direct deployment of Khiops models without temporary files.
+
+
+
+## KNIError Objects
+
+```python
+class KNIError(Exception)
+```
+
+Exception raised for KNI errors.
+
+
+
+## KNI Objects
+
+```python
+class KNI()
+```
+
+Python wrapper for Khiops Native Interface using ctypes.
+
+
+
+#### KNI\_MaxRecordLength
+
+8 MB
+
+
+
+#### \_\_init\_\_
+
+```python
+def __init__(library_path: str | bytes | Path | None = None)
+```
+
+Initialize the KNI wrapper.
+
+**Arguments**:
+
+- `library_path` - Optional path to the KNI shared library (str, bytes, or
+ pathlib.Path). If None, attempts to locate it automatically.
+
+
+
+#### get\_version
+
+```python
+def get_version() -> int
+```
+
+Get KNI version as integer (10*major + minor).
+
+
+
+#### get\_full\_version
+
+```python
+def get_full_version() -> str
+```
+
+Get KNI full version string.
+
+
+
+#### set\_log\_file\_name
+
+```python
+def set_log_file_name(log_file_name: str | bytes) -> None
+```
+
+Set the log file name for error messages.
+
+**Arguments**:
+
+- `log_file_name` - Path to log file (str or bytes, empty string for no logging)
+
+
+**Raises**:
+
+- `KNIError` - If setting log file fails
+- `TypeError` - If log_file_name is not str or bytes
+
+
+
+#### open\_stream
+
+```python
+def open_stream(dictionary_file_path: str | bytes | Path,
+ dictionary_name: str | bytes,
+ header_line: str | bytes,
+ field_separator: str | bytes = "\t") -> int
+```
+
+Open a KNI stream for recoding.
+
+**Arguments**:
+
+- `dictionary_file_path` - Path to the dictionary file (str, bytes, or pathlib.Path)
+- `dictionary_name` - Name of the dictionary to use (str or bytes)
+- `header_line` - Header line with field names (str or bytes)
+- `field_separator` - Character used to separate fields (str or bytes, default: tab)
+
+
+**Returns**:
+
+ Stream handle (positive integer)
+
+
+**Raises**:
+
+- `KNIError` - If opening stream fails
+- `TypeError` - If arguments have invalid types
+
+
+
+#### close\_stream
+
+```python
+def close_stream(stream_handle: int) -> None
+```
+
+Close a KNI stream.
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream
+
+
+**Raises**:
+
+- `KNIError` - If closing stream fails
+- `TypeError` - If stream_handle is not int
+
+
+
+#### recode\_stream\_record
+
+```python
+def recode_stream_record(stream_handle: int,
+ input_record: str | bytes,
+ max_output_length: int | None = None) -> str
+```
+
+Recode an input record using the stream's dictionary.
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream (int)
+- `input_record` - Input record (str or bytes)
+- `max_output_length` - Maximum output buffer size (int, default: KNI_MaxRecordLength)
+
+
+**Returns**:
+
+ Recoded output string
+
+
+**Raises**:
+
+- `KNIError` - If recoding fails
+- `TypeError` - If arguments have invalid types
+
+
+
+#### set\_secondary\_header\_line
+
+```python
+def set_secondary_header_line(stream_handle: int, data_path: str | bytes,
+ header_line: str | bytes) -> None
+```
+
+Set the header line of a secondary table (multi-table only).
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream
+- `data_path` - Data path identifying the secondary table (str or bytes)
+- `header_line` - Header line with field names (str or bytes)
+
+
+**Raises**:
+
+- `KNIError` - If setting secondary header fails
+- `TypeError` - If arguments have invalid types
+
+
+
+#### set\_external\_table
+
+```python
+def set_external_table(stream_handle: int, data_root: str | bytes,
+ data_path: str | bytes,
+ data_table_file_name: str | bytes) -> None
+```
+
+Set the name of a data file for an external table (multi-table only).
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream
+- `data_root` - Root dictionary of the external table (str or bytes)
+- `data_path` - Data path for secondary external tables (str or bytes, empty for root)
+- `data_table_file_name` - Path to the external table data file (str or bytes)
+
+
+**Raises**:
+
+- `KNIError` - If setting external table fails
+- `TypeError` - If arguments have invalid types
+
+
+
+#### finish\_opening\_stream
+
+```python
+def finish_opening_stream(stream_handle: int) -> None
+```
+
+Finish opening a stream (multi-table only).
+
+Must be called after all secondary headers and external tables are set.
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream
+
+
+**Raises**:
+
+- `KNIError` - If finishing opening stream fails
+- `TypeError` - If stream_handle is not int
+
+
+
+#### set\_secondary\_input\_record
+
+```python
+def set_secondary_input_record(stream_handle: int, data_path: str | bytes,
+ input_record: str | bytes) -> None
+```
+
+Set a secondary input record for multi-table recoding.
+
+All secondary records must be set before recoding the primary record.
+
+**Arguments**:
+
+- `stream_handle` - Handle returned by open_stream
+- `data_path` - Data path identifying the secondary table (str or bytes)
+- `input_record` - Secondary input record (str or bytes)
+
+
+**Raises**:
+
+- `KNIError` - If setting secondary input record fails
+- `TypeError` - If arguments have invalid types
+
+
+
+#### get\_stream\_max\_memory
+
+```python
+def get_stream_max_memory() -> int
+```
+
+Get the maximum amount of memory (in MB) for stream opening.
+
+**Returns**:
+
+ Maximum memory in MB
+
+
+
+#### set\_stream\_max\_memory
+
+```python
+def set_stream_max_memory(max_mb: int) -> int
+```
+
+Set the maximum amount of memory (in MB) for stream opening.
+
+**Arguments**:
+
+- `max_mb` - Maximum memory in MB
+
+
+**Returns**:
+
+ Accepted value (bounded by system limits)
+
+
+
+#### get\_error\_message
+
+```python
+@staticmethod
+def get_error_message(error_code: int) -> str
+```
+
+Get a human-readable error message for an error code.
+
+**Arguments**:
+
+- `error_code` - KNI error code
+
+
+**Returns**:
+
+ Error message string
+
diff --git a/python/docs/search.js b/python/docs/search.js
deleted file mode 100644
index f3f8e1d..0000000
--- a/python/docs/search.js
+++ /dev/null
@@ -1,46 +0,0 @@
-window.pdocSearch = (function(){
-/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oKhiops Native Interface (KNI) Python Package\n\n
This package provides Python bindings for the Khiops Native Interface (KNI),\nenabling direct deployment of Khiops models from Python without temporary files.
Args:\n dictionary_file_path: Path to the dictionary file (str, bytes, or pathlib.Path)\n dictionary_name: Name of the dictionary to use (str or bytes)\n header_line: Header line with field names (str or bytes)\n field_separator: Character used to separate fields (str or bytes, default: tab)
\n\n
Returns:\n Stream handle (positive integer)
\n\n
Raises:\n KNIError: If opening stream fails\n TypeError: If arguments have invalid types
Recode an input record using the stream's dictionary.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n input_record: Input record string or bytes\n max_output_length: Maximum output buffer size (default: KNI_MaxRecordLength)
\n\n
Returns:\n Recoded output string
\n\n
Raises:\n KNIError: If recoding fails\n TypeError: If arguments have invalid types
Set the header line of a secondary table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n header_line: Header line with field names (str or bytes)
\n\n
Raises:\n KNIError: If setting secondary header fails\n TypeError: If arguments have invalid types
Set the name of a data file for an external table (multi-table only).
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_root: Root dictionary of the external table (str or bytes)\n data_path: Data path for secondary external tables (str or bytes, empty for root)\n data_table_file_name: Path to the external table data file (str or bytes)
\n\n
Raises:\n KNIError: If setting external table fails\n TypeError: If arguments have invalid types
Set a secondary input record for multi-table recoding.
\n\n
All secondary records must be set before recoding the primary record.
\n\n
Args:\n stream_handle: Handle returned by open_stream\n data_path: Data path identifying the secondary table (str or bytes)\n input_record: Secondary input record string or bytes
\n\n
Raises:\n KNIError: If setting secondary input record fails\n TypeError: If arguments have invalid types