2121
2222#include < algorithm>
2323#include < limits>
24+ #include < map>
2425#include < sstream>
2526#include < string>
2627#include < utility>
@@ -49,9 +50,11 @@ Status InvalidConversion(PyObject* obj, const std::string& expected_types,
4950 return Status::OK ();
5051}
5152
52- class ScalarVisitor {
53+ class TypeInferrer {
54+ // A type inference visitor for Python values
55+
5356 public:
54- ScalarVisitor ()
57+ TypeInferrer ()
5558 : total_count_(0 ),
5659 none_count_ (0 ),
5760 bool_count_(0 ),
@@ -62,14 +65,46 @@ class ScalarVisitor {
6265 binary_count_(0 ),
6366 unicode_count_(0 ),
6467 decimal_count_(0 ),
68+ list_count_(0 ),
69+ struct_count_(0 ),
6570 max_decimal_metadata_(std::numeric_limits<int32_t >::min(),
6671 std::numeric_limits<int32_t>::min()),
6772 decimal_type_() {
68- PyAcquireGIL lock;
6973 Status status = internal::ImportDecimalType (&decimal_type_);
7074 DCHECK_OK (status);
7175 }
7276
77+ // Infer value type from a sequence of values
78+ Status VisitSequence (PyObject* obj) {
79+ // Loop through a sequence
80+ if (PyArray_Check (obj)) {
81+ Py_ssize_t size = PySequence_Size (obj);
82+ OwnedRef value_ref;
83+
84+ for (Py_ssize_t i = 0 ; i < size; ++i) {
85+ auto array = reinterpret_cast <PyArrayObject*>(obj);
86+ auto ptr = reinterpret_cast <const char *>(PyArray_GETPTR1 (array, i));
87+
88+ value_ref.reset (PyArray_GETITEM (array, ptr));
89+ RETURN_IF_PYERROR ();
90+ RETURN_NOT_OK (Visit (value_ref.obj ()));
91+ }
92+ } else if (PySequence_Check (obj)) {
93+ OwnedRef seq_ref (PySequence_Fast (obj, " Object is not a sequence or iterable" ));
94+ RETURN_IF_PYERROR ();
95+ PyObject* seq = seq_ref.obj ();
96+
97+ Py_ssize_t size = PySequence_Fast_GET_SIZE (seq);
98+ for (Py_ssize_t i = 0 ; i < size; ++i) {
99+ PyObject* value = PySequence_Fast_GET_ITEM (seq, i);
100+ RETURN_NOT_OK (Visit (value));
101+ }
102+ } else {
103+ return Status::TypeError (" Object is not a sequence or iterable" );
104+ }
105+ return Status::OK ();
106+ }
107+
73108 Status Visit (PyObject* obj) {
74109 ++total_count_;
75110 if (obj == Py_None || internal::PyFloat_IsNaN (obj)) {
@@ -103,6 +138,10 @@ class ScalarVisitor {
103138 ss << type->ToString ();
104139 return Status::Invalid (ss.str ());
105140 }
141+ } else if (PyList_Check (obj) || PyArray_Check (obj)) {
142+ return VisitList (obj);
143+ } else if (PyDict_Check (obj)) {
144+ return VisitDict (obj);
106145 } else if (PyObject_IsInstance (obj, decimal_type_.obj ())) {
107146 RETURN_NOT_OK (max_decimal_metadata_.Update (obj));
108147 ++decimal_count_;
@@ -118,14 +157,36 @@ class ScalarVisitor {
118157 return Status::OK ();
119158 }
120159
121- std::shared_ptr<DataType> GetType () {
160+ Status Validate () const {
161+ if (list_count_ > 0 ) {
162+ if (list_count_ + none_count_ != total_count_) {
163+ return Status::Invalid (" cannot mix list and non-list, non-null values" );
164+ }
165+ RETURN_NOT_OK (list_inferrer_->Validate ());
166+ } else if (struct_count_ > 0 ) {
167+ if (struct_count_ + none_count_ != total_count_) {
168+ return Status::Invalid (" cannot mix struct and non-struct, non-null values" );
169+ }
170+ for (const auto & it : struct_inferrers_) {
171+ RETURN_NOT_OK (it.second .Validate ());
172+ }
173+ }
174+ return Status::OK ();
175+ }
176+
177+ std::shared_ptr<DataType> GetType () const {
122178 // TODO(wesm): handling mixed-type cases
123- if (decimal_count_) {
179+ if (list_count_) {
180+ auto value_type = list_inferrer_->GetType ();
181+ DCHECK (value_type != nullptr );
182+ return list (value_type);
183+ } else if (struct_count_) {
184+ return GetStructType ();
185+ } else if (decimal_count_) {
124186 return decimal (max_decimal_metadata_.precision (), max_decimal_metadata_.scale ());
125187 } else if (float_count_) {
126188 return float64 ();
127189 } else if (int_count_) {
128- // TODO(wesm): tighter type later
129190 return int64 ();
130191 } else if (date_count_) {
131192 return date64 ();
@@ -144,6 +205,53 @@ class ScalarVisitor {
144205
145206 int64_t total_count () const { return total_count_; }
146207
208+ protected:
209+ Status VisitList (PyObject* obj) {
210+ if (!list_inferrer_) {
211+ list_inferrer_.reset (new TypeInferrer);
212+ }
213+ ++list_count_;
214+ return list_inferrer_->VisitSequence (obj);
215+ }
216+
217+ Status VisitDict (PyObject* obj) {
218+ PyObject* key_obj;
219+ PyObject* value_obj;
220+ Py_ssize_t pos = 0 ;
221+
222+ while (PyDict_Next (obj, &pos, &key_obj, &value_obj)) {
223+ std::string key;
224+ if (PyUnicode_Check (key_obj)) {
225+ RETURN_NOT_OK (internal::PyUnicode_AsStdString (key_obj, &key));
226+ } else if (PyBytes_Check (key_obj)) {
227+ key = internal::PyBytes_AsStdString (key_obj);
228+ } else {
229+ std::stringstream ss;
230+ ss << " Expected dict key of type str or bytes, got '" << Py_TYPE (key_obj)->tp_name
231+ << " '" ;
232+ return Status::TypeError (ss.str ());
233+ }
234+ // Get or create visitor for this key
235+ auto it = struct_inferrers_.find (key);
236+ if (it == struct_inferrers_.end ()) {
237+ it = struct_inferrers_.insert (std::make_pair (key, TypeInferrer ())).first ;
238+ }
239+ TypeInferrer* visitor = &it->second ;
240+ RETURN_NOT_OK (visitor->Visit (value_obj));
241+ }
242+ ++struct_count_;
243+ return Status::OK ();
244+ }
245+
246+ std::shared_ptr<DataType> GetStructType () const {
247+ std::vector<std::shared_ptr<Field>> fields;
248+ for (const auto & it : struct_inferrers_) {
249+ const auto struct_field = field (it.first , it.second .GetType ());
250+ fields.emplace_back (struct_field);
251+ }
252+ return struct_ (fields);
253+ }
254+
147255 private:
148256 int64_t total_count_;
149257 int64_t none_count_;
@@ -155,6 +263,10 @@ class ScalarVisitor {
155263 int64_t binary_count_;
156264 int64_t unicode_count_;
157265 int64_t decimal_count_;
266+ int64_t list_count_;
267+ std::unique_ptr<TypeInferrer> list_inferrer_;
268+ int64_t struct_count_;
269+ std::map<std::string, TypeInferrer> struct_inferrers_;
158270
159271 internal::DecimalMetadata max_decimal_metadata_;
160272
@@ -163,116 +275,6 @@ class ScalarVisitor {
163275 OwnedRefNoGIL decimal_type_;
164276};
165277
166- static constexpr int MAX_NESTING_LEVELS = 32 ;
167-
168- // SeqVisitor is used to infer the type.
169- class SeqVisitor {
170- public:
171- SeqVisitor () : max_nesting_level_(0 ), max_observed_level_(0 ), nesting_histogram_() {
172- std::fill (nesting_histogram_, nesting_histogram_ + MAX_NESTING_LEVELS , 0 );
173- }
174-
175- // co-recursive with VisitElem
176- Status Visit (PyObject* obj, int level = 0 ) {
177- max_nesting_level_ = std::max (max_nesting_level_, level);
178-
179- // Loop through a sequence
180- if (!PySequence_Check (obj))
181- return Status::TypeError (" Object is not a sequence or iterable" );
182-
183- Py_ssize_t size = PySequence_Size (obj);
184- for (int64_t i = 0 ; i < size; ++i) {
185- OwnedRef ref;
186- if (PyArray_Check (obj)) {
187- auto array = reinterpret_cast <PyArrayObject*>(obj);
188- auto ptr = reinterpret_cast <const char *>(PyArray_GETPTR1 (array, i));
189-
190- ref.reset (PyArray_GETITEM (array, ptr));
191- RETURN_IF_PYERROR ();
192-
193- RETURN_NOT_OK (VisitElem (ref, level));
194- } else {
195- ref.reset (PySequence_GetItem (obj, i));
196- RETURN_IF_PYERROR ();
197- RETURN_NOT_OK (VisitElem (ref, level));
198- }
199- }
200- return Status::OK ();
201- }
202-
203- std::shared_ptr<DataType> GetType () {
204- // If all the non-list inputs were null (or there were no inputs)
205- std::shared_ptr<DataType> result;
206- if (scalars_.total_count () == 0 ) {
207- // Lists of Lists of NULL
208- result = null ();
209- } else {
210- // Lists of Lists of [X]
211- result = scalars_.GetType ();
212- }
213- for (int i = 0 ; i < max_nesting_level_; ++i) {
214- result = std::make_shared<ListType>(result);
215- }
216- return result;
217- }
218-
219- Status Validate () const {
220- if (scalars_.total_count () > 0 ) {
221- if (num_nesting_levels () > 1 ) {
222- return Status::Invalid (" Mixed nesting levels not supported" );
223- // If the nesting goes deeper than the deepest scalar
224- } else if (max_observed_level_ < max_nesting_level_) {
225- return Status::Invalid (" Mixed nesting levels not supported" );
226- }
227- }
228- return Status::OK ();
229- }
230-
231- // Returns the number of nesting levels which have scalar elements.
232- int num_nesting_levels () const {
233- int result = 0 ;
234- for (int i = 0 ; i < MAX_NESTING_LEVELS ; ++i) {
235- if (nesting_histogram_[i] > 0 ) {
236- ++result;
237- }
238- }
239- return result;
240- }
241-
242- private:
243- ScalarVisitor scalars_;
244-
245- // Track observed
246- // Deapest nesting level (irregardless of scalars)
247- int max_nesting_level_;
248- int max_observed_level_;
249-
250- // Number of scalar elements at each nesting level.
251- // (TOOD: We really only need to know if a scalar is present, not the count).
252- int nesting_histogram_[MAX_NESTING_LEVELS ];
253-
254- // Visits a specific element (inner part of the loop).
255- Status VisitElem (const OwnedRef& item_ref, int level) {
256- DCHECK_NE (item_ref.obj (), NULLPTR );
257- if (PyList_Check (item_ref.obj ()) || PyArray_Check (item_ref.obj ())) {
258- RETURN_NOT_OK (Visit (item_ref.obj (), level + 1 ));
259- } else if (PyDict_Check (item_ref.obj ())) {
260- return Status::NotImplemented (" No type inference for dicts" );
261- } else {
262- // We permit nulls at any level of nesting, but they aren't treated like
263- // other scalar values as far as the checking for mixed nesting structure
264- if (item_ref.obj () != Py_None) {
265- ++nesting_histogram_[level];
266- }
267- if (level > max_observed_level_) {
268- max_observed_level_ = level;
269- }
270- return scalars_.Visit (item_ref.obj ());
271- }
272- return Status::OK ();
273- }
274- };
275-
276278// Convert *obj* to a sequence if necessary
277279// Fill *size* to its length. If >= 0 on entry, *size* is an upper size
278280// bound that may lead to truncation.
@@ -319,11 +321,11 @@ Status ConvertToSequenceAndInferSize(PyObject* obj, PyObject** seq, int64_t* siz
319321// Non-exhaustive type inference
320322Status InferArrowType (PyObject* obj, std::shared_ptr<DataType>* out_type) {
321323 PyDateTime_IMPORT;
322- SeqVisitor seq_visitor ;
323- RETURN_NOT_OK (seq_visitor. Visit (obj));
324- RETURN_NOT_OK (seq_visitor .Validate ());
324+ TypeInferrer inferrer ;
325+ RETURN_NOT_OK (inferrer. VisitSequence (obj));
326+ RETURN_NOT_OK (inferrer .Validate ());
325327
326- *out_type = seq_visitor .GetType ();
328+ *out_type = inferrer .GetType ();
327329 if (*out_type == nullptr ) {
328330 return Status::TypeError (" Unable to determine data type" );
329331 }
0 commit comments