arraykit 1.11.0__tar.gz → 1.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arraykit-1.11.0/arraykit.egg-info → arraykit-1.12.0}/PKG-INFO +7 -1
- {arraykit-1.11.0 → arraykit-1.12.0}/README.rst +6 -0
- arraykit-1.12.0/VERSION +2 -0
- {arraykit-1.11.0 → arraykit-1.12.0/arraykit.egg-info}/PKG-INFO +7 -1
- {arraykit-1.11.0 → arraykit-1.12.0}/arraykit.egg-info/SOURCES.txt +2 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/__init__.py +2 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/__init__.pyi +4 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/_arraykit.c +8 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/methods.c +288 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/methods.h +6 -0
- arraykit-1.12.0/test/test_map_object.py +153 -0
- arraykit-1.12.0/test/test_prepare_iter_for_array.py +190 -0
- arraykit-1.11.0/VERSION +0 -2
- {arraykit-1.11.0 → arraykit-1.12.0}/LICENSE.txt +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/MANIFEST.in +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/arraykit.egg-info/dependency_links.txt +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/arraykit.egg-info/requires.txt +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/arraykit.egg-info/top_level.txt +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/pyproject.toml +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/setup.cfg +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/setup.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/array_go.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/array_go.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/array_to_tuple.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/array_to_tuple.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/auto_map.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/auto_map.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/block_index.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/block_index.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/delimited_to_arrays.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/delimited_to_arrays.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/py.typed +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/tri_map.c +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/tri_map.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/src/utilities.h +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_array_go.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_astype_array.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_auto_map.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_auto_map_property.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_block_index.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_delimited_to_arrays.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_delimited_to_arrays_integration.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_delimited_to_arrays_property.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_factorize.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_fill_directional.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_group_ordering.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_group_reduce.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_nonzero_1d.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_nonzero_1d_property.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_objectable.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_pyi.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_split_after_count.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_transition_slices_from_group.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_tri_map.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_type_discovery.py +0 -0
- {arraykit-1.11.0 → arraykit-1.12.0}/test/test_util.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraykit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Array utilities for StaticFrame
|
|
5
5
|
Author: Christopher Ariza, Brandt Bucher, Charles Burkland
|
|
6
6
|
License: MIT
|
|
@@ -64,6 +64,12 @@ ArrayKit requires the following:
|
|
|
64
64
|
What is New in ArrayKit
|
|
65
65
|
-------------------------
|
|
66
66
|
|
|
67
|
+
1.12.0
|
|
68
|
+
............
|
|
69
|
+
|
|
70
|
+
Added ``map_object()`` and ``prepare_iter_for_array()``.
|
|
71
|
+
|
|
72
|
+
|
|
67
73
|
1.11.0
|
|
68
74
|
............
|
|
69
75
|
|
arraykit-1.12.0/VERSION
ADDED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: arraykit
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.12.0
|
|
4
4
|
Summary: Array utilities for StaticFrame
|
|
5
5
|
Author: Christopher Ariza, Brandt Bucher, Charles Burkland
|
|
6
6
|
License: MIT
|
|
@@ -64,6 +64,12 @@ ArrayKit requires the following:
|
|
|
64
64
|
What is New in ArrayKit
|
|
65
65
|
-------------------------
|
|
66
66
|
|
|
67
|
+
1.12.0
|
|
68
|
+
............
|
|
69
|
+
|
|
70
|
+
Added ``map_object()`` and ``prepare_iter_for_array()``.
|
|
71
|
+
|
|
72
|
+
|
|
67
73
|
1.11.0
|
|
68
74
|
............
|
|
69
75
|
|
|
@@ -40,9 +40,11 @@ test/test_factorize.py
|
|
|
40
40
|
test/test_fill_directional.py
|
|
41
41
|
test/test_group_ordering.py
|
|
42
42
|
test/test_group_reduce.py
|
|
43
|
+
test/test_map_object.py
|
|
43
44
|
test/test_nonzero_1d.py
|
|
44
45
|
test/test_nonzero_1d_property.py
|
|
45
46
|
test/test_objectable.py
|
|
47
|
+
test/test_prepare_iter_for_array.py
|
|
46
48
|
test/test_pyi.py
|
|
47
49
|
test/test_split_after_count.py
|
|
48
50
|
test/test_transition_slices_from_group.py
|
|
@@ -28,6 +28,8 @@ from ._arraykit import write_array_to_file as write_array_to_file
|
|
|
28
28
|
from ._arraykit import factorize as factorize
|
|
29
29
|
from ._arraykit import group_ordering as group_ordering
|
|
30
30
|
from ._arraykit import group_reduce as group_reduce
|
|
31
|
+
from ._arraykit import map_object as map_object
|
|
32
|
+
from ._arraykit import prepare_iter_for_array as prepare_iter_for_array
|
|
31
33
|
from ._arraykit import fill_directional as fill_directional
|
|
32
34
|
from ._arraykit import count_iteration as count_iteration
|
|
33
35
|
from ._arraykit import first_true_1d as first_true_1d
|
|
@@ -236,6 +236,10 @@ def group_ordering(
|
|
|
236
236
|
def group_reduce(
|
|
237
237
|
codes: np.ndarray, size: int, values: np.ndarray, op: str
|
|
238
238
|
) -> np.ndarray: ...
|
|
239
|
+
def map_object(array: np.ndarray, func: tp.Callable[[tp.Any], tp.Any]) -> np.ndarray: ...
|
|
240
|
+
def prepare_iter_for_array(
|
|
241
|
+
values: tp.Iterable[tp.Any], copy: bool = ...
|
|
242
|
+
) -> tp.Tuple[tp.Optional[type], bool, tp.Sequence[tp.Any]]: ...
|
|
239
243
|
def fill_directional(
|
|
240
244
|
array: np.ndarray,
|
|
241
245
|
target: np.ndarray,
|
|
@@ -82,6 +82,14 @@ static PyMethodDef arraykit_methods[] = {
|
|
|
82
82
|
(PyCFunction)group_reduce,
|
|
83
83
|
METH_VARARGS | METH_KEYWORDS,
|
|
84
84
|
NULL},
|
|
85
|
+
{"map_object",
|
|
86
|
+
(PyCFunction)map_object,
|
|
87
|
+
METH_VARARGS | METH_KEYWORDS,
|
|
88
|
+
NULL},
|
|
89
|
+
{"prepare_iter_for_array",
|
|
90
|
+
(PyCFunction)prepare_iter_for_array,
|
|
91
|
+
METH_VARARGS | METH_KEYWORDS,
|
|
92
|
+
NULL},
|
|
85
93
|
{"fill_directional",
|
|
86
94
|
(PyCFunction)fill_directional,
|
|
87
95
|
METH_VARARGS | METH_KEYWORDS,
|
|
@@ -1403,6 +1403,294 @@ group_reduce(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
|
|
|
1403
1403
|
return out_arr;
|
|
1404
1404
|
}
|
|
1405
1405
|
|
|
1406
|
+
//------------------------------------------------------------------------------
|
|
1407
|
+
// int magnitude beyond which a Python int is no longer losslessly coercible to float;
|
|
1408
|
+
// mirrors static_frame.core.util.INT_MAX_COERCIBLE_TO_FLOAT
|
|
1409
|
+
#define AK_INT_MAX_COERCIBLE_TO_FLOAT 1000000000000000LL
|
|
1410
|
+
|
|
1411
|
+
// running state of the prepare_iter_for_array dtype inference over a series of values
|
|
1412
|
+
typedef struct AK_InferState {
|
|
1413
|
+
int has_str;
|
|
1414
|
+
int has_non_str;
|
|
1415
|
+
int has_inexact;
|
|
1416
|
+
int has_big_int;
|
|
1417
|
+
int has_tuple; // a sized object was seen (tuple/list/array/...)
|
|
1418
|
+
int needs_object; // the resolved dtype must be object rather than auto-detected
|
|
1419
|
+
} AK_InferState;
|
|
1420
|
+
|
|
1421
|
+
// Classify one value per static_frame.core.util.prepare_iter_for_array's rules, updating
|
|
1422
|
+
// `s`. Once `s->needs_object` is set the caller can stop inspecting. `enum_type` is
|
|
1423
|
+
// enum.Enum (may be NULL to skip the Enum check).
|
|
1424
|
+
static inline void
|
|
1425
|
+
AK_infer_value(PyObject *v, PyObject *enum_type, AK_InferState *s)
|
|
1426
|
+
{
|
|
1427
|
+
PyTypeObject *vt = Py_TYPE(v);
|
|
1428
|
+
// exact str/bytes (Python or numpy scalar) -> string; subclasses fall through
|
|
1429
|
+
if (PyUnicode_CheckExact(v) || PyBytes_CheckExact(v)
|
|
1430
|
+
|| PyArray_IsScalar(v, Unicode) || PyArray_IsScalar(v, String)) {
|
|
1431
|
+
s->has_str = 1;
|
|
1432
|
+
}
|
|
1433
|
+
// a sized object (tuple, list, array, SF container, str subclass) -> object
|
|
1434
|
+
else if ((vt->tp_as_sequence && vt->tp_as_sequence->sq_length)
|
|
1435
|
+
|| (vt->tp_as_mapping && vt->tp_as_mapping->mp_length)) {
|
|
1436
|
+
s->has_tuple = 1;
|
|
1437
|
+
s->needs_object = 1;
|
|
1438
|
+
}
|
|
1439
|
+
else {
|
|
1440
|
+
s->has_non_str = 1;
|
|
1441
|
+
if (vt == &PyFloat_Type || vt == &PyComplex_Type) {
|
|
1442
|
+
s->has_inexact = 1;
|
|
1443
|
+
}
|
|
1444
|
+
else if (vt == &PyLong_Type) {
|
|
1445
|
+
int overflow = 0;
|
|
1446
|
+
long long lv = PyLong_AsLongLongAndOverflow(v, &overflow);
|
|
1447
|
+
if (overflow || llabs(lv) > AK_INT_MAX_COERCIBLE_TO_FLOAT) {
|
|
1448
|
+
s->has_big_int = 1;
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else if (PyArray_IsScalar(v, Generic)) {
|
|
1452
|
+
; // any other numpy scalar: non-str, no inexact/big-int, not an Enum
|
|
1453
|
+
}
|
|
1454
|
+
else if (enum_type != NULL && PyObject_IsInstance(v, enum_type) == 1) {
|
|
1455
|
+
s->needs_object = 1;
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
if ((s->has_str && s->has_non_str) || (s->has_big_int && s->has_inexact)) {
|
|
1459
|
+
s->needs_object = 1;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
// Import enum.Enum for the Enum inference case; returns a new reference or NULL
|
|
1464
|
+
// (clearing the error), in which case the Enum check is skipped.
|
|
1465
|
+
static PyObject *
|
|
1466
|
+
AK_import_enum(void)
|
|
1467
|
+
{
|
|
1468
|
+
PyObject *enum_type = NULL;
|
|
1469
|
+
PyObject *enum_mod = PyImport_ImportModule("enum");
|
|
1470
|
+
if (enum_mod != NULL) {
|
|
1471
|
+
enum_type = PyObject_GetAttrString(enum_mod, "Enum");
|
|
1472
|
+
Py_DECREF(enum_mod);
|
|
1473
|
+
}
|
|
1474
|
+
if (enum_type == NULL) {
|
|
1475
|
+
PyErr_Clear();
|
|
1476
|
+
}
|
|
1477
|
+
return enum_type;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
static char *map_object_kwarg_names[] = {
|
|
1481
|
+
"array",
|
|
1482
|
+
"func",
|
|
1483
|
+
NULL
|
|
1484
|
+
};
|
|
1485
|
+
|
|
1486
|
+
// Apply a Python callable to each element of a 1D array (elements boxed as numpy scalars,
|
|
1487
|
+
// matching NumPy/Series iteration) and return a new 1D array, inferring the result dtype
|
|
1488
|
+
// with the same rules as static_frame.core.util.prepare_iter_for_array: the result is an
|
|
1489
|
+
// object array when the applied values mix strings and non-strings, include a sized object
|
|
1490
|
+
// (tuple/list/array), an Enum, or mix a large Python int with a Python float/complex;
|
|
1491
|
+
// otherwise NumPy auto-detects the dtype (e.g. str -> '<U', float -> float64). This fuses
|
|
1492
|
+
// the per-element apply, the type inspection, and the array build into one C pass.
|
|
1493
|
+
PyObject *
|
|
1494
|
+
map_object(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
|
|
1495
|
+
{
|
|
1496
|
+
PyArrayObject *array = NULL;
|
|
1497
|
+
PyObject *func = NULL;
|
|
1498
|
+
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
|
1499
|
+
"O!O:map_object",
|
|
1500
|
+
map_object_kwarg_names,
|
|
1501
|
+
&PyArray_Type, &array,
|
|
1502
|
+
&func
|
|
1503
|
+
)) {
|
|
1504
|
+
return NULL;
|
|
1505
|
+
}
|
|
1506
|
+
if (PyArray_NDIM(array) != 1) {
|
|
1507
|
+
PyErr_SetString(PyExc_ValueError, "array must be 1-dimensional");
|
|
1508
|
+
return NULL;
|
|
1509
|
+
}
|
|
1510
|
+
if (!PyCallable_Check(func)) {
|
|
1511
|
+
PyErr_SetString(PyExc_TypeError, "func must be callable");
|
|
1512
|
+
return NULL;
|
|
1513
|
+
}
|
|
1514
|
+
npy_intp n = PyArray_SIZE(array);
|
|
1515
|
+
int is_object = PyArray_TYPE(array) == NPY_OBJECT;
|
|
1516
|
+
|
|
1517
|
+
PyObject *values = PyList_New(n); // collected results; owns references
|
|
1518
|
+
if (values == NULL) {
|
|
1519
|
+
return NULL;
|
|
1520
|
+
}
|
|
1521
|
+
// enum.Enum for the rare Enum-result case; on failure proceed without the check
|
|
1522
|
+
PyObject *enum_type = AK_import_enum();
|
|
1523
|
+
AK_InferState state = {0, 0, 0, 0, 0, 0};
|
|
1524
|
+
|
|
1525
|
+
// 1D: hoist the base pointer and element stride and walk a running pointer, rather
|
|
1526
|
+
// than recomputing PyArray_GETPTR1 each iteration. For a contiguous array the stride
|
|
1527
|
+
// is the itemsize (direct indexing into the flat buffer); a strided slice still works.
|
|
1528
|
+
char *p = (char*)PyArray_DATA(array);
|
|
1529
|
+
npy_intp stride = PyArray_STRIDES(array)[0];
|
|
1530
|
+
|
|
1531
|
+
for (npy_intp i = 0; i < n; i++, p += stride) {
|
|
1532
|
+
PyObject *elem;
|
|
1533
|
+
if (is_object) {
|
|
1534
|
+
elem = *(PyObject**)p;
|
|
1535
|
+
Py_INCREF(elem);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
elem = PyArray_ToScalar(p, array);
|
|
1539
|
+
if (elem == NULL) {
|
|
1540
|
+
goto fail;
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
PyObject *r = PyObject_CallOneArg(func, elem);
|
|
1544
|
+
Py_DECREF(elem);
|
|
1545
|
+
if (r == NULL) {
|
|
1546
|
+
goto fail;
|
|
1547
|
+
}
|
|
1548
|
+
PyList_SET_ITEM(values, i, r); // steals reference to r
|
|
1549
|
+
if (!state.needs_object) {
|
|
1550
|
+
AK_infer_value(r, enum_type, &state);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
Py_XDECREF(enum_type);
|
|
1554
|
+
|
|
1555
|
+
PyObject *result;
|
|
1556
|
+
if (state.needs_object) {
|
|
1557
|
+
// build an object array of the collected values
|
|
1558
|
+
result = PyArray_FROM_OTF(values, NPY_OBJECT, NPY_ARRAY_C_CONTIGUOUS);
|
|
1559
|
+
}
|
|
1560
|
+
else {
|
|
1561
|
+
// let NumPy auto-detect the dtype from the values (str -> '<U', float -> f8, ...)
|
|
1562
|
+
result = PyArray_FromAny(values, NULL, 1, 1, NPY_ARRAY_C_CONTIGUOUS, NULL);
|
|
1563
|
+
}
|
|
1564
|
+
Py_DECREF(values);
|
|
1565
|
+
if (result == NULL) {
|
|
1566
|
+
return NULL;
|
|
1567
|
+
}
|
|
1568
|
+
PyArray_CLEARFLAGS((PyArrayObject*)result, NPY_ARRAY_WRITEABLE);
|
|
1569
|
+
return result;
|
|
1570
|
+
|
|
1571
|
+
fail:
|
|
1572
|
+
Py_XDECREF(enum_type);
|
|
1573
|
+
Py_DECREF(values);
|
|
1574
|
+
return NULL;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
static char *prepare_iter_for_array_kwarg_names[] = {
|
|
1578
|
+
"values",
|
|
1579
|
+
"copy",
|
|
1580
|
+
NULL
|
|
1581
|
+
};
|
|
1582
|
+
|
|
1583
|
+
// Infer a dtype specifier for the elements of an iterable, matching
|
|
1584
|
+
// static_frame.core.util.prepare_iter_for_array: return ``(resolved, has_tuple, values)``
|
|
1585
|
+
// where `resolved` is None (let NumPy auto-detect) or the ``object`` type, `has_tuple`
|
|
1586
|
+
// marks that a sized object was seen, and `values` is a newly materialized list when
|
|
1587
|
+
// `copy` is true (e.g. a generator/dict/set) else the original iterable. The caller
|
|
1588
|
+
// decides `copy` (via is_gen_copy_values), keeping the SF-specific type policy out of C.
|
|
1589
|
+
PyObject *
|
|
1590
|
+
prepare_iter_for_array(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs)
|
|
1591
|
+
{
|
|
1592
|
+
PyObject *values = NULL;
|
|
1593
|
+
int copy = 0;
|
|
1594
|
+
if (!PyArg_ParseTupleAndKeywords(args, kwargs,
|
|
1595
|
+
"O|p:prepare_iter_for_array",
|
|
1596
|
+
prepare_iter_for_array_kwarg_names,
|
|
1597
|
+
&values,
|
|
1598
|
+
©
|
|
1599
|
+
)) {
|
|
1600
|
+
return NULL;
|
|
1601
|
+
}
|
|
1602
|
+
PyObject *enum_type = AK_import_enum();
|
|
1603
|
+
AK_InferState state = {0, 0, 0, 0, 0, 0};
|
|
1604
|
+
PyObject *values_out = NULL; // new list when copy, else a new ref to the original
|
|
1605
|
+
|
|
1606
|
+
if (copy) {
|
|
1607
|
+
// materialize into a list while inspecting (generator/dict/set input). Pre-size
|
|
1608
|
+
// the list from a length hint when one is available (sets, dicts, sized
|
|
1609
|
+
// iterators) to avoid repeated reallocation; fall back to append growth for a
|
|
1610
|
+
// bare generator (hint 0), and guard against an inexact hint over/under-shooting.
|
|
1611
|
+
Py_ssize_t hint = PyObject_LengthHint(values, 0);
|
|
1612
|
+
if (hint < 0) {
|
|
1613
|
+
goto fail;
|
|
1614
|
+
}
|
|
1615
|
+
values_out = PyList_New(hint);
|
|
1616
|
+
if (values_out == NULL) {
|
|
1617
|
+
goto fail;
|
|
1618
|
+
}
|
|
1619
|
+
PyObject *iter = PyObject_GetIter(values);
|
|
1620
|
+
if (iter == NULL) {
|
|
1621
|
+
goto fail;
|
|
1622
|
+
}
|
|
1623
|
+
Py_ssize_t i = 0;
|
|
1624
|
+
PyObject *item;
|
|
1625
|
+
while ((item = PyIter_Next(iter)) != NULL) {
|
|
1626
|
+
if (!state.needs_object) {
|
|
1627
|
+
AK_infer_value(item, enum_type, &state);
|
|
1628
|
+
}
|
|
1629
|
+
if (i < hint) {
|
|
1630
|
+
PyList_SET_ITEM(values_out, i, item); // steals reference
|
|
1631
|
+
}
|
|
1632
|
+
else { // hint underestimated the length
|
|
1633
|
+
int rc = PyList_Append(values_out, item);
|
|
1634
|
+
Py_DECREF(item);
|
|
1635
|
+
if (rc != 0) {
|
|
1636
|
+
Py_DECREF(iter);
|
|
1637
|
+
goto fail;
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
i++;
|
|
1641
|
+
}
|
|
1642
|
+
Py_DECREF(iter);
|
|
1643
|
+
if (PyErr_Occurred()) {
|
|
1644
|
+
goto fail;
|
|
1645
|
+
}
|
|
1646
|
+
if (i < hint) { // hint overestimated: drop the trailing (NULL) slots
|
|
1647
|
+
Py_SET_SIZE(values_out, i);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
// inspect only; direct indexing for list/tuple, else the iterator protocol
|
|
1652
|
+
Py_INCREF(values);
|
|
1653
|
+
values_out = values;
|
|
1654
|
+
if (PyList_CheckExact(values) || PyTuple_CheckExact(values)) {
|
|
1655
|
+
Py_ssize_t sz = PySequence_Fast_GET_SIZE(values);
|
|
1656
|
+
for (Py_ssize_t i = 0; i < sz && !state.needs_object; i++) {
|
|
1657
|
+
AK_infer_value(PySequence_Fast_GET_ITEM(values, i), enum_type, &state);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
else {
|
|
1661
|
+
PyObject *iter = PyObject_GetIter(values);
|
|
1662
|
+
if (iter == NULL) {
|
|
1663
|
+
goto fail;
|
|
1664
|
+
}
|
|
1665
|
+
PyObject *item;
|
|
1666
|
+
while (!state.needs_object && (item = PyIter_Next(iter)) != NULL) {
|
|
1667
|
+
AK_infer_value(item, enum_type, &state);
|
|
1668
|
+
Py_DECREF(item);
|
|
1669
|
+
}
|
|
1670
|
+
Py_DECREF(iter);
|
|
1671
|
+
if (PyErr_Occurred()) {
|
|
1672
|
+
goto fail;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
Py_XDECREF(enum_type);
|
|
1677
|
+
|
|
1678
|
+
PyObject *resolved = state.needs_object
|
|
1679
|
+
? (PyObject*)&PyBaseObject_Type // the ``object`` builtin -> object dtype
|
|
1680
|
+
: Py_None;
|
|
1681
|
+
PyObject *has_tuple = state.has_tuple ? Py_True : Py_False;
|
|
1682
|
+
PyObject *result = PyTuple_Pack(3, resolved, has_tuple, values_out);
|
|
1683
|
+
Py_DECREF(values_out);
|
|
1684
|
+
return result;
|
|
1685
|
+
|
|
1686
|
+
fail:
|
|
1687
|
+
Py_XDECREF(enum_type);
|
|
1688
|
+
Py_XDECREF(values_out);
|
|
1689
|
+
return NULL;
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
//------------------------------------------------------------------------------
|
|
1693
|
+
|
|
1406
1694
|
// Fill one strided lane in place: walk positions in the fill direction, carrying
|
|
1407
1695
|
// the most recent non-target value into each target position (subject to `limit`
|
|
1408
1696
|
// consecutive fills per run). `elem_base`/`elem_stride` address elements in bytes;
|
|
@@ -75,6 +75,12 @@ group_ordering(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
|
75
75
|
PyObject *
|
|
76
76
|
group_reduce(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
77
77
|
|
|
78
|
+
PyObject *
|
|
79
|
+
map_object(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
80
|
+
|
|
81
|
+
PyObject *
|
|
82
|
+
prepare_iter_for_array(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
83
|
+
|
|
78
84
|
PyObject *
|
|
79
85
|
fill_directional(PyObject *Py_UNUSED(m), PyObject *args, PyObject *kwargs);
|
|
80
86
|
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from arraykit import map_object
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Color(Enum):
|
|
9
|
+
R = 1
|
|
10
|
+
G = 2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# reference: prepare_iter_for_array's inference + build, as static_frame applies it
|
|
14
|
+
_INEXACT = (float, complex, np.inexact)
|
|
15
|
+
_BIG = 1_000_000_000_000_000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _reference(arr, func):
|
|
19
|
+
vals = [func(v) for v in arr]
|
|
20
|
+
resolved = None
|
|
21
|
+
has_str = has_non = has_inx = has_big = False
|
|
22
|
+
for v in vals:
|
|
23
|
+
vt = v.__class__
|
|
24
|
+
if vt is str or vt is np.str_ or vt is bytes or vt is np.bytes_:
|
|
25
|
+
has_str = True
|
|
26
|
+
elif hasattr(v, '__len__') or isinstance(v, Enum):
|
|
27
|
+
resolved = object
|
|
28
|
+
break
|
|
29
|
+
else:
|
|
30
|
+
has_non = True
|
|
31
|
+
if vt in _INEXACT:
|
|
32
|
+
has_inx = True
|
|
33
|
+
elif vt is int and abs(v) > _BIG:
|
|
34
|
+
has_big = True
|
|
35
|
+
if (has_str and has_non) or (has_big and has_inx):
|
|
36
|
+
resolved = object
|
|
37
|
+
break
|
|
38
|
+
return np.array(vals) if resolved is None else np.array(vals, dtype=object)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestUnit(unittest.TestCase):
|
|
42
|
+
def _check(self, arr, func):
|
|
43
|
+
post = map_object(arr, func)
|
|
44
|
+
exp = _reference(arr, func)
|
|
45
|
+
self.assertEqual(post.dtype, exp.dtype, (arr.dtype, exp.dtype))
|
|
46
|
+
self.assertTrue(np.array_equal(post, exp))
|
|
47
|
+
self.assertFalse(post.flags.writeable)
|
|
48
|
+
return post
|
|
49
|
+
|
|
50
|
+
def test_map_object_str_from_float(self) -> None:
|
|
51
|
+
post = self._check(np.array([1.5, 2.25, 3.0]), lambda x: str(x))
|
|
52
|
+
self.assertEqual(post.dtype.kind, 'U')
|
|
53
|
+
|
|
54
|
+
def test_map_object_str_from_bool(self) -> None:
|
|
55
|
+
post = self._check(np.array([True, False, True]), lambda x: str(x))
|
|
56
|
+
self.assertEqual(post.tolist(), ['True', 'False', 'True'])
|
|
57
|
+
self.assertEqual(post.dtype.kind, 'U')
|
|
58
|
+
|
|
59
|
+
def test_map_object_str_from_int(self) -> None:
|
|
60
|
+
self._check(np.array([1, 2, 3], dtype=np.int64), lambda x: str(x))
|
|
61
|
+
|
|
62
|
+
def test_map_object_native_float(self) -> None:
|
|
63
|
+
post = self._check(np.array([1.5, 2.5]), lambda x: float(x) * 2)
|
|
64
|
+
self.assertEqual(post.dtype, np.dtype(np.float64))
|
|
65
|
+
|
|
66
|
+
def test_map_object_native_int(self) -> None:
|
|
67
|
+
# python-int results auto-detect to the platform default int (int32 on Windows)
|
|
68
|
+
post = self._check(np.array([1, 2, 3]), lambda x: int(x) + 1)
|
|
69
|
+
self.assertEqual(post.dtype, np.dtype(np.int_))
|
|
70
|
+
|
|
71
|
+
def test_map_object_tuple_result(self) -> None:
|
|
72
|
+
post = self._check(np.array([1, 2]), lambda x: (int(x), int(x)))
|
|
73
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
74
|
+
|
|
75
|
+
def test_map_object_list_result(self) -> None:
|
|
76
|
+
post = self._check(np.array([1, 2]), lambda x: [int(x)])
|
|
77
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
78
|
+
|
|
79
|
+
def test_map_object_mixed_str_nonstr(self) -> None:
|
|
80
|
+
post = self._check(np.array([1, 2, 3]), lambda x: str(x) if x > 1 else int(x))
|
|
81
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
82
|
+
|
|
83
|
+
def test_map_object_python_float(self) -> None:
|
|
84
|
+
self._check(np.array([1, 2, 3]), lambda x: 1.5)
|
|
85
|
+
|
|
86
|
+
def test_map_object_bigint_and_inexact(self) -> None:
|
|
87
|
+
# a large python int mixed with a python float -> object
|
|
88
|
+
post = self._check(np.array([1, 2]), lambda x: 10**18 if x == 1 else 1.5)
|
|
89
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
90
|
+
|
|
91
|
+
def test_map_object_bigint_only(self) -> None:
|
|
92
|
+
# big ints alone (no inexact) do not force object
|
|
93
|
+
post = self._check(np.array([1, 2]), lambda x: 10**18)
|
|
94
|
+
self.assertNotEqual(post.dtype, np.dtype(object))
|
|
95
|
+
|
|
96
|
+
def test_map_object_enum_result(self) -> None:
|
|
97
|
+
post = self._check(np.array([1, 2]), lambda x: Color.R)
|
|
98
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
99
|
+
|
|
100
|
+
def test_map_object_object_input(self) -> None:
|
|
101
|
+
arr = np.array(['a', 'bb', 'ccc'], dtype=object)
|
|
102
|
+
post = self._check(arr, lambda x: len(x))
|
|
103
|
+
self.assertEqual(post.tolist(), [1, 2, 3])
|
|
104
|
+
|
|
105
|
+
def test_map_object_receives_numpy_scalar(self) -> None:
|
|
106
|
+
# elements are boxed as numpy scalars, matching Series/array iteration
|
|
107
|
+
seen = []
|
|
108
|
+
map_object(np.array([1.5, 2.5]), lambda x: seen.append(type(x)) or x)
|
|
109
|
+
self.assertTrue(all(t is np.float64 for t in seen))
|
|
110
|
+
|
|
111
|
+
def test_map_object_str_subclass_is_object(self) -> None:
|
|
112
|
+
# a str subclass is not an exact str -> sized object -> object array
|
|
113
|
+
class S(str):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
post = map_object(np.array([1, 2]), lambda x: S(str(x)))
|
|
117
|
+
self.assertEqual(post.dtype, np.dtype(object))
|
|
118
|
+
|
|
119
|
+
def test_map_object_empty(self) -> None:
|
|
120
|
+
post = self._check(np.array([], dtype=np.float64), lambda x: str(x))
|
|
121
|
+
self.assertEqual(len(post), 0)
|
|
122
|
+
|
|
123
|
+
def test_map_object_strided_non_contiguous(self) -> None:
|
|
124
|
+
# a strided slice (non-contiguous) must be walked correctly by the running pointer
|
|
125
|
+
base = np.array([1.0, 99.0, 2.0, 99.0, 3.0])
|
|
126
|
+
strided = base[::2]
|
|
127
|
+
self.assertFalse(strided.flags['C_CONTIGUOUS'])
|
|
128
|
+
post = self._check(strided, lambda x: str(x))
|
|
129
|
+
self.assertEqual(post.tolist(), ['1.0', '2.0', '3.0'])
|
|
130
|
+
|
|
131
|
+
def test_map_object_strided_object(self) -> None:
|
|
132
|
+
arr = np.array(['a', 'X', 'bb', 'X', 'ccc'], dtype=object)[::2]
|
|
133
|
+
post = self._check(arr, lambda x: len(x))
|
|
134
|
+
self.assertEqual(post.tolist(), [1, 2, 3])
|
|
135
|
+
|
|
136
|
+
def test_map_object_propagates_exception(self) -> None:
|
|
137
|
+
def bad(x):
|
|
138
|
+
raise ValueError('boom')
|
|
139
|
+
|
|
140
|
+
with self.assertRaises(ValueError):
|
|
141
|
+
map_object(np.array([1, 2]), bad)
|
|
142
|
+
|
|
143
|
+
def test_map_object_errors(self) -> None:
|
|
144
|
+
with self.assertRaises(ValueError): # 2d
|
|
145
|
+
map_object(np.array([[1, 2]]), lambda x: x)
|
|
146
|
+
with self.assertRaises(TypeError): # not callable
|
|
147
|
+
map_object(np.array([1, 2]), 3)
|
|
148
|
+
with self.assertRaises(TypeError): # not an array
|
|
149
|
+
map_object([1, 2], lambda x: x)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == '__main__':
|
|
153
|
+
unittest.main()
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
from enum import Enum
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
from arraykit import prepare_iter_for_array
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Color(Enum):
|
|
9
|
+
R = 1
|
|
10
|
+
G = 2
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class _Hinted:
|
|
14
|
+
"""An iterable exposing a (possibly inexact) __length_hint__."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, items, hint):
|
|
17
|
+
self._items = list(items)
|
|
18
|
+
self._hint = hint
|
|
19
|
+
|
|
20
|
+
def __iter__(self):
|
|
21
|
+
return iter(self._items)
|
|
22
|
+
|
|
23
|
+
def __length_hint__(self):
|
|
24
|
+
return self._hint
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# faithful reference: static_frame.core.util.prepare_iter_for_array, given a precomputed
|
|
28
|
+
# copy flag (SF's is_gen_copy_values decision lives in the SF wrapper)
|
|
29
|
+
_INEXACT = (float, complex, np.inexact)
|
|
30
|
+
_BIG = 1_000_000_000_000_000
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _reference(values, copy):
|
|
34
|
+
if copy:
|
|
35
|
+
vpost = []
|
|
36
|
+
resolved = None
|
|
37
|
+
has_tuple = False
|
|
38
|
+
has_str = has_non = has_inx = has_big = False
|
|
39
|
+
it = iter(values)
|
|
40
|
+
for v in it:
|
|
41
|
+
if copy:
|
|
42
|
+
vpost.append(v)
|
|
43
|
+
vt = v.__class__
|
|
44
|
+
if vt is str or vt is np.str_ or vt is bytes or vt is np.bytes_:
|
|
45
|
+
has_str = True
|
|
46
|
+
elif hasattr(v, '__len__'):
|
|
47
|
+
has_tuple = True
|
|
48
|
+
resolved = object
|
|
49
|
+
break
|
|
50
|
+
elif isinstance(v, Enum):
|
|
51
|
+
resolved = object
|
|
52
|
+
break
|
|
53
|
+
else:
|
|
54
|
+
has_non = True
|
|
55
|
+
if vt in _INEXACT:
|
|
56
|
+
has_inx = True
|
|
57
|
+
elif vt is int and abs(v) > _BIG:
|
|
58
|
+
has_big = True
|
|
59
|
+
if (has_str and has_non) or (has_big and has_inx):
|
|
60
|
+
resolved = object
|
|
61
|
+
break
|
|
62
|
+
if copy:
|
|
63
|
+
vpost.extend(it)
|
|
64
|
+
return resolved, has_tuple, vpost
|
|
65
|
+
return resolved, has_tuple, values
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class TestUnit(unittest.TestCase):
|
|
69
|
+
def _check(self, make, copy):
|
|
70
|
+
# make() returns a fresh iterable so the two runs are independent
|
|
71
|
+
r_ak = prepare_iter_for_array(make(), copy)
|
|
72
|
+
r_ref = _reference(make(), copy)
|
|
73
|
+
self.assertIs(r_ak[0], r_ref[0]) # None or the object type, by identity
|
|
74
|
+
self.assertEqual(r_ak[1], r_ref[1]) # has_tuple
|
|
75
|
+
self.assertEqual(list(r_ak[2]), list(r_ref[2])) # values
|
|
76
|
+
return r_ak
|
|
77
|
+
|
|
78
|
+
def test_list_str(self) -> None:
|
|
79
|
+
r = self._check(lambda: ['a', 'b', 'c'], False)
|
|
80
|
+
self.assertIsNone(r[0])
|
|
81
|
+
|
|
82
|
+
def test_list_float(self) -> None:
|
|
83
|
+
self._check(lambda: [1.0, 2.0, 3.0], False)
|
|
84
|
+
|
|
85
|
+
def test_tuple_int(self) -> None:
|
|
86
|
+
self._check(lambda: (1, 2, 3), False)
|
|
87
|
+
|
|
88
|
+
def test_mixed_str_nonstr(self) -> None:
|
|
89
|
+
r = self._check(lambda: [1, 'a', 2.0], False)
|
|
90
|
+
self.assertIs(r[0], object)
|
|
91
|
+
|
|
92
|
+
def test_sized_object_has_tuple(self) -> None:
|
|
93
|
+
r = self._check(lambda: [1, (2, 3)], False)
|
|
94
|
+
self.assertIs(r[0], object)
|
|
95
|
+
self.assertTrue(r[1]) # has_tuple
|
|
96
|
+
|
|
97
|
+
def test_enum(self) -> None:
|
|
98
|
+
r = self._check(lambda: [Color.R, Color.G], False)
|
|
99
|
+
self.assertIs(r[0], object)
|
|
100
|
+
self.assertFalse(r[1]) # not has_tuple
|
|
101
|
+
|
|
102
|
+
def test_bigint_and_inexact(self) -> None:
|
|
103
|
+
r = self._check(lambda: [10**18, 1.5], False)
|
|
104
|
+
self.assertIs(r[0], object)
|
|
105
|
+
|
|
106
|
+
def test_bigint_only(self) -> None:
|
|
107
|
+
r = self._check(lambda: [10**18, 2], False)
|
|
108
|
+
self.assertIsNone(r[0])
|
|
109
|
+
|
|
110
|
+
def test_numpy_float_scalars(self) -> None:
|
|
111
|
+
r = self._check(lambda: [np.float64(1.5), np.float64(2.5)], False)
|
|
112
|
+
self.assertIsNone(r[0])
|
|
113
|
+
|
|
114
|
+
def test_bytes(self) -> None:
|
|
115
|
+
self._check(lambda: [b'x', b'y'], False)
|
|
116
|
+
|
|
117
|
+
def test_empty_list(self) -> None:
|
|
118
|
+
r = self._check(list, False)
|
|
119
|
+
self.assertIsNone(r[0])
|
|
120
|
+
|
|
121
|
+
def test_generator_copy(self) -> None:
|
|
122
|
+
# a generator is materialized when copy=True; the returned list is the values
|
|
123
|
+
r = self._check(lambda: (str(i) for i in range(4)), True)
|
|
124
|
+
self.assertEqual(list(r[2]), ['0', '1', '2', '3'])
|
|
125
|
+
self.assertIsInstance(r[2], list)
|
|
126
|
+
|
|
127
|
+
def test_generator_copy_mixed(self) -> None:
|
|
128
|
+
r = self._check(lambda: (i if i < 2 else str(i) for i in range(4)), True)
|
|
129
|
+
self.assertIs(r[0], object)
|
|
130
|
+
self.assertEqual(list(r[2]), [0, 1, '2', '3'])
|
|
131
|
+
|
|
132
|
+
def test_generator_empty_copy(self) -> None:
|
|
133
|
+
self._check(lambda: (x for x in []), True)
|
|
134
|
+
|
|
135
|
+
def test_set_copy(self) -> None:
|
|
136
|
+
# a set is materialized (order-independent check of contents); it has __len__,
|
|
137
|
+
# so the list is pre-sized
|
|
138
|
+
r = prepare_iter_for_array({1, 2, 3}, True)
|
|
139
|
+
self.assertIsNone(r[0])
|
|
140
|
+
self.assertEqual(sorted(r[2]), [1, 2, 3])
|
|
141
|
+
|
|
142
|
+
def test_dict_copy(self) -> None:
|
|
143
|
+
r = prepare_iter_for_array({'a': 1, 'b': 2}, True)
|
|
144
|
+
self.assertEqual(sorted(r[2]), ['a', 'b'])
|
|
145
|
+
|
|
146
|
+
def test_length_hint_exact(self) -> None:
|
|
147
|
+
r = self._check(lambda: _Hinted([1, 2, 3], 3), True)
|
|
148
|
+
self.assertEqual(list(r[2]), [1, 2, 3])
|
|
149
|
+
|
|
150
|
+
def test_length_hint_overestimate(self) -> None:
|
|
151
|
+
# a hint larger than the actual length -> trailing slots dropped
|
|
152
|
+
r = prepare_iter_for_array(_Hinted([1, 2, 3], 10), True)
|
|
153
|
+
self.assertEqual(list(r[2]), [1, 2, 3])
|
|
154
|
+
self.assertEqual(len(r[2]), 3)
|
|
155
|
+
|
|
156
|
+
def test_length_hint_underestimate(self) -> None:
|
|
157
|
+
# a hint smaller than the actual length -> remaining items appended
|
|
158
|
+
r = prepare_iter_for_array(_Hinted([1, 2, 3, 4, 5], 1), True)
|
|
159
|
+
self.assertEqual(list(r[2]), [1, 2, 3, 4, 5])
|
|
160
|
+
|
|
161
|
+
def test_length_hint_inference_preserved(self) -> None:
|
|
162
|
+
# inference still resolves object through the pre-sized path
|
|
163
|
+
r = prepare_iter_for_array(_Hinted([1, (2,)], 5), True)
|
|
164
|
+
self.assertIs(r[0], object)
|
|
165
|
+
self.assertTrue(r[1])
|
|
166
|
+
self.assertEqual(list(r[2]), [1, (2,)])
|
|
167
|
+
|
|
168
|
+
def test_no_copy_returns_original(self) -> None:
|
|
169
|
+
src = [1, 2, 3]
|
|
170
|
+
r = prepare_iter_for_array(src, False)
|
|
171
|
+
self.assertIs(r[2], src) # original object, not a copy
|
|
172
|
+
|
|
173
|
+
def test_early_stop_does_not_over_iterate(self) -> None:
|
|
174
|
+
# once object is resolved, inspection stops; a later error-raising element in a
|
|
175
|
+
# non-copy list is never inspected
|
|
176
|
+
r = prepare_iter_for_array([1, (2,), object()], False)
|
|
177
|
+
self.assertIs(r[0], object)
|
|
178
|
+
self.assertTrue(r[1])
|
|
179
|
+
|
|
180
|
+
def test_generator_copy_propagates_exception(self) -> None:
|
|
181
|
+
def gen():
|
|
182
|
+
yield 1
|
|
183
|
+
raise ValueError('boom')
|
|
184
|
+
|
|
185
|
+
with self.assertRaises(ValueError):
|
|
186
|
+
prepare_iter_for_array(gen(), True)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == '__main__':
|
|
190
|
+
unittest.main()
|
arraykit-1.11.0/VERSION
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|