0
# Type System
1
2
Comprehensive type system with DataType constants for all supported data types, type checking utilities, and type coercion functions. The type system enables optional type safety, validation, and proper handling of Python data types within rule expressions.
3
4
## Capabilities
5
6
### DataType Constants
7
8
The DataType class provides constants for all supported data types with type checking and compatibility methods.
9
10
```python { .api }
11
class DataType:
12
"""Collection of constants representing supported data types."""
13
14
# Scalar Types
15
BOOLEAN: DataType # bool values
16
BYTES: DataType # bytes objects
17
DATETIME: DataType # datetime.datetime objects
18
FLOAT: DataType # decimal.Decimal, float, int (non-bool)
19
NULL: DataType # None values
20
STRING: DataType # str values
21
TIMEDELTA: DataType # datetime.timedelta objects
22
23
# Compound Types
24
ARRAY: DataType # tuple, list, range objects
25
MAPPING: DataType # dict, OrderedDict objects
26
SET: DataType # set objects
27
28
# Special Types
29
FUNCTION: DataType # callable objects
30
UNDEFINED: DataType # undefined/unknown types
31
```
32
33
**Usage Example:**
34
35
```python
36
import rule_engine
37
38
# Using type constants for validation
39
print(rule_engine.DataType.STRING.name) # "STRING"
40
print(rule_engine.DataType.FLOAT.python_type) # <class 'decimal.Decimal'>
41
42
# Check if a type is scalar or compound
43
print(rule_engine.DataType.STRING.is_scalar) # True
44
print(rule_engine.DataType.ARRAY.is_compound) # True
45
print(rule_engine.DataType.MAPPING.is_iterable) # True
46
```
47
48
### Type Resolution from Names
49
50
Get DataType constants from their string names.
51
52
```python { .api }
53
@classmethod
54
def from_name(cls, name: str) -> DataType:
55
"""
56
Get the data type from its name.
57
58
Args:
59
name (str): The name of the data type to retrieve
60
61
Returns:
62
DataType: The corresponding data type constant
63
64
Raises:
65
TypeError: If name is not a string
66
ValueError: If name doesn't map to a valid data type
67
"""
68
```
69
70
**Usage Example:**
71
72
```python
73
import rule_engine
74
75
# Get types by name
76
string_type = rule_engine.DataType.from_name('STRING')
77
float_type = rule_engine.DataType.from_name('FLOAT')
78
array_type = rule_engine.DataType.from_name('ARRAY')
79
80
print(string_type == rule_engine.DataType.STRING) # True
81
82
# Error handling
83
try:
84
invalid_type = rule_engine.DataType.from_name('INVALID')
85
except ValueError as e:
86
print(f"Invalid type name: {e}")
87
```
88
89
### Type Resolution from Python Types
90
91
Get DataType constants from Python types and type hints.
92
93
```python { .api }
94
@classmethod
95
def from_type(cls, python_type) -> DataType:
96
"""
97
Get the data type constant for a Python type or type hint.
98
99
Args:
100
python_type: Native Python type or type hint
101
102
Returns:
103
DataType: The corresponding data type constant
104
105
Raises:
106
TypeError: If argument is not a type or type hint
107
ValueError: If type cannot be mapped to a supported data type
108
"""
109
```
110
111
**Usage Example:**
112
113
```python
114
import rule_engine
115
from typing import List, Dict
116
import datetime
117
118
# Basic Python types
119
print(rule_engine.DataType.from_type(str)) # DataType.STRING
120
print(rule_engine.DataType.from_type(int)) # DataType.FLOAT
121
print(rule_engine.DataType.from_type(bool)) # DataType.BOOLEAN
122
print(rule_engine.DataType.from_type(list)) # DataType.ARRAY
123
print(rule_engine.DataType.from_type(dict)) # DataType.MAPPING
124
125
# Datetime types
126
print(rule_engine.DataType.from_type(datetime.datetime)) # DataType.DATETIME
127
print(rule_engine.DataType.from_type(datetime.timedelta)) # DataType.TIMEDELTA
128
129
# Type hints (Python 3.6+)
130
list_type = rule_engine.DataType.from_type(List[str])
131
print(list_type.name) # "ARRAY"
132
if hasattr(list_type, 'value_type'):
133
print(list_type.value_type.name) # "STRING"
134
135
dict_type = rule_engine.DataType.from_type(Dict[str, int])
136
print(dict_type.name) # "MAPPING"
137
```
138
139
### Type Resolution from Values
140
141
Get DataType constants from Python values with automatic type detection.
142
143
```python { .api }
144
@classmethod
145
def from_value(cls, python_value) -> DataType:
146
"""
147
Get the data type constant for a Python value.
148
149
Args:
150
python_value: Native Python value to analyze
151
152
Returns:
153
DataType: The corresponding data type constant
154
155
Raises:
156
TypeError: If value cannot be mapped to a supported data type
157
"""
158
```
159
160
**Usage Example:**
161
162
```python
163
import rule_engine
164
import datetime
165
from collections import OrderedDict
166
167
# Scalar values
168
print(rule_engine.DataType.from_value("hello")) # DataType.STRING
169
print(rule_engine.DataType.from_value(42)) # DataType.FLOAT
170
print(rule_engine.DataType.from_value(True)) # DataType.BOOLEAN
171
print(rule_engine.DataType.from_value(None)) # DataType.NULL
172
print(rule_engine.DataType.from_value(b"bytes")) # DataType.BYTES
173
174
# Datetime values
175
now = datetime.datetime.now()
176
delta = datetime.timedelta(days=1)
177
print(rule_engine.DataType.from_value(now)) # DataType.DATETIME
178
print(rule_engine.DataType.from_value(delta)) # DataType.TIMEDELTA
179
180
# Collections
181
print(rule_engine.DataType.from_value([1, 2, 3])) # DataType.ARRAY
182
print(rule_engine.DataType.from_value({'a': 1, 'b': 2})) # DataType.MAPPING
183
print(rule_engine.DataType.from_value({1, 2, 3})) # DataType.SET
184
185
# Typed collections
186
array_type = rule_engine.DataType.from_value([1, 2, 3])
187
print(array_type.value_type.name) # "FLOAT" (all integers)
188
189
mixed_array = rule_engine.DataType.from_value([1, "hello", None])
190
print(mixed_array.value_type.name) # "UNDEFINED" (mixed types)
191
```
192
193
### Type Compatibility Checking
194
195
Check if two data types are compatible without conversion.
196
197
```python { .api }
198
@classmethod
199
def is_compatible(cls, dt1: DataType, dt2: DataType) -> bool:
200
"""
201
Check if two data types are compatible without conversion.
202
203
Args:
204
dt1: First data type to compare
205
dt2: Second data type to compare
206
207
Returns:
208
bool: True if types are compatible
209
210
Raises:
211
TypeError: If arguments are not data type definitions
212
"""
213
```
214
215
**Usage Example:**
216
217
```python
218
import rule_engine
219
220
# Scalar type compatibility
221
string_type = rule_engine.DataType.STRING
222
float_type = rule_engine.DataType.FLOAT
223
undefined_type = rule_engine.DataType.UNDEFINED
224
225
print(rule_engine.DataType.is_compatible(string_type, string_type)) # True
226
print(rule_engine.DataType.is_compatible(string_type, float_type)) # False
227
print(rule_engine.DataType.is_compatible(string_type, undefined_type)) # True (undefined is always compatible)
228
229
# Compound type compatibility
230
array_str = rule_engine.DataType.ARRAY(rule_engine.DataType.STRING)
231
array_float = rule_engine.DataType.ARRAY(rule_engine.DataType.FLOAT)
232
array_undefined = rule_engine.DataType.ARRAY(rule_engine.DataType.UNDEFINED)
233
234
print(rule_engine.DataType.is_compatible(array_str, array_str)) # True
235
print(rule_engine.DataType.is_compatible(array_str, array_float)) # False
236
print(rule_engine.DataType.is_compatible(array_str, array_undefined)) # True
237
```
238
239
### Type Definition Checking
240
241
Check if a value is a data type definition.
242
243
```python { .api }
244
@classmethod
245
def is_definition(cls, value) -> bool:
246
"""
247
Check if a value is a data type definition.
248
249
Args:
250
value: The value to check
251
252
Returns:
253
bool: True if value is a data type definition
254
"""
255
```
256
257
**Usage Example:**
258
259
```python
260
import rule_engine
261
262
# Check if values are type definitions
263
print(rule_engine.DataType.is_definition(rule_engine.DataType.STRING)) # True
264
print(rule_engine.DataType.is_definition("STRING")) # False
265
print(rule_engine.DataType.is_definition(42)) # False
266
267
# Useful for validation
268
def validate_type_resolver(type_map):
269
for symbol, dtype in type_map.items():
270
if not rule_engine.DataType.is_definition(dtype):
271
raise ValueError(f"Invalid type definition for {symbol}: {dtype}")
272
```
273
274
## Type Utility Functions
275
276
### Value Coercion
277
278
Convert Python values to rule engine compatible types.
279
280
```python { .api }
281
def coerce_value(value, verify_type: bool = True):
282
"""
283
Convert a Python value to a rule engine compatible type.
284
285
Args:
286
value: The value to convert
287
verify_type (bool): Whether to verify the converted type
288
289
Returns:
290
The converted value
291
292
Raises:
293
TypeError: If verify_type=True and type is incompatible
294
"""
295
```
296
297
**Usage Example:**
298
299
```python
300
import rule_engine
301
import datetime
302
from collections import OrderedDict
303
304
# Automatic coercion
305
coerced_list = rule_engine.coerce_value([1, 2, 3]) # Converts to tuple
306
print(type(coerced_list)) # <class 'tuple'>
307
308
coerced_date = rule_engine.coerce_value(datetime.date(2023, 1, 1)) # Converts to datetime
309
print(type(coerced_date)) # <class 'datetime.datetime'>
310
311
coerced_float = rule_engine.coerce_value(42) # Converts to Decimal
312
print(type(coerced_float)) # <class 'decimal.Decimal'>
313
314
# Nested structures
315
nested_data = {
316
'numbers': [1, 2, 3],
317
'info': {'name': 'test'}
318
}
319
coerced_nested = rule_engine.coerce_value(nested_data)
320
print(type(coerced_nested)) # <class 'collections.OrderedDict'>
321
```
322
323
### Numeric Type Checking
324
325
Utility functions for checking numeric properties of values.
326
327
```python { .api }
328
def is_numeric(value) -> bool:
329
"""Check if value is numeric (int, float, Decimal, but not bool)."""
330
331
def is_real_number(value) -> bool:
332
"""Check if value is a real number (finite, not NaN)."""
333
334
def is_integer_number(value) -> bool:
335
"""Check if value is an integer number (whole number)."""
336
337
def is_natural_number(value) -> bool:
338
"""Check if value is a natural number (non-negative integer)."""
339
```
340
341
**Usage Example:**
342
343
```python
344
import rule_engine
345
import decimal
346
import math
347
348
# Numeric checks
349
print(rule_engine.is_numeric(42)) # True
350
print(rule_engine.is_numeric(3.14)) # True
351
print(rule_engine.is_numeric(True)) # False (bool excluded)
352
print(rule_engine.is_numeric("42")) # False
353
354
# Real number checks
355
print(rule_engine.is_real_number(42)) # True
356
print(rule_engine.is_real_number(float('inf'))) # False
357
print(rule_engine.is_real_number(float('nan'))) # False
358
359
# Integer checks
360
print(rule_engine.is_integer_number(42)) # True
361
print(rule_engine.is_integer_number(3.0)) # True (whole number)
362
print(rule_engine.is_integer_number(3.14)) # False
363
364
# Natural number checks
365
print(rule_engine.is_natural_number(42)) # True
366
print(rule_engine.is_natural_number(0)) # True
367
print(rule_engine.is_natural_number(-5)) # False
368
```
369
370
### Iterable Member Type Analysis
371
372
Analyze the types of members in iterable collections.
373
374
```python { .api }
375
def iterable_member_value_type(python_value) -> DataType:
376
"""
377
Get the data type of iterable members if consistent.
378
379
Args:
380
python_value: Iterable to analyze
381
382
Returns:
383
DataType: The member type (UNDEFINED if mixed types)
384
"""
385
```
386
387
**Usage Example:**
388
389
```python
390
import rule_engine
391
392
# Homogeneous collections
393
numbers = [1, 2, 3, 4]
394
member_type = rule_engine.iterable_member_value_type(numbers)
395
print(member_type.name) # "FLOAT"
396
397
strings = ["a", "b", "c"]
398
member_type = rule_engine.iterable_member_value_type(strings)
399
print(member_type.name) # "STRING"
400
401
# Heterogeneous collections
402
mixed = [1, "hello", True]
403
member_type = rule_engine.iterable_member_value_type(mixed)
404
print(member_type.name) # "UNDEFINED"
405
406
# Nullable collections (None values allowed)
407
nullable = [1, 2, None, 3]
408
member_type = rule_engine.iterable_member_value_type(nullable)
409
print(member_type.name) # "FLOAT" (None is treated specially)
410
```
411
412
## Compound Type Definitions
413
414
### Array Types
415
416
Define array types with specific member types.
417
418
```python
419
import rule_engine
420
421
# Create typed array definitions
422
string_array = rule_engine.DataType.ARRAY(rule_engine.DataType.STRING)
423
number_array = rule_engine.DataType.ARRAY(rule_engine.DataType.FLOAT)
424
425
print(string_array.value_type.name) # "STRING"
426
print(number_array.value_type_nullable) # True (allows None values)
427
428
# Non-nullable arrays
429
strict_array = rule_engine.DataType.ARRAY(
430
rule_engine.DataType.STRING,
431
value_type_nullable=False
432
)
433
```
434
435
### Mapping Types
436
437
Define mapping types with specific key and value types.
438
439
```python
440
import rule_engine
441
442
# String keys, any values
443
string_mapping = rule_engine.DataType.MAPPING(rule_engine.DataType.STRING)
444
445
# Specific key and value types
446
typed_mapping = rule_engine.DataType.MAPPING(
447
rule_engine.DataType.STRING, # key type
448
rule_engine.DataType.FLOAT, # value type
449
value_type_nullable=False # values cannot be None
450
)
451
452
print(typed_mapping.key_type.name) # "STRING"
453
print(typed_mapping.value_type.name) # "FLOAT"
454
```
455
456
### Function Types
457
458
Define function types with return types and argument specifications.
459
460
```python
461
import rule_engine
462
463
# Basic function type
464
basic_func = rule_engine.DataType.FUNCTION(
465
"calculate",
466
return_type=rule_engine.DataType.FLOAT
467
)
468
469
# Function with argument types
470
typed_func = rule_engine.DataType.FUNCTION(
471
"process",
472
return_type=rule_engine.DataType.STRING,
473
argument_types=(rule_engine.DataType.STRING, rule_engine.DataType.FLOAT),
474
minimum_arguments=1 # Second argument is optional
475
)
476
477
print(typed_func.return_type.name) # "STRING"
478
print(len(typed_func.argument_types)) # 2
479
```
480
481
## Type System Integration
482
483
Using the type system with contexts and rules for comprehensive type safety:
484
485
```python
486
import rule_engine
487
488
# Define a complete type schema
489
type_schema = {
490
'user_id': rule_engine.DataType.FLOAT,
491
'name': rule_engine.DataType.STRING,
492
'active': rule_engine.DataType.BOOLEAN,
493
'tags': rule_engine.DataType.ARRAY(rule_engine.DataType.STRING),
494
'metadata': rule_engine.DataType.MAPPING(
495
rule_engine.DataType.STRING,
496
rule_engine.DataType.STRING
497
),
498
'created_at': rule_engine.DataType.DATETIME
499
}
500
501
# Create type resolver and context
502
type_resolver = rule_engine.type_resolver_from_dict(type_schema)
503
context = rule_engine.Context(type_resolver=type_resolver)
504
505
# Type-safe rule creation
506
rule = rule_engine.Rule(
507
'active and len(tags) > 0 and name =~ "Admin.*"',
508
context=context
509
)
510
511
# Validation happens at rule creation time
512
try:
513
invalid_rule = rule_engine.Rule('name + user_id', context=context) # Type error
514
except rule_engine.EvaluationError as e:
515
print(f"Type validation failed: {e.message}")
516
```