24#include "pybind11/pybind11.h"
25#include "pybind11/stl.h"
29#include "ndarray/pybind11.h"
40using namespace pybind11::literals;
46using cpputils::python::WrapperCollection;
50using PySchema = py::class_<Schema>;
52using PySubSchema = py::class_<SubSchema>;
55using PyFieldBase = py::class_<FieldBase<T>>;
58using PyKeyBase = py::class_<KeyBase<T>>;
61using PyField = py::class_<Field<T>, FieldBase<T>>;
64using PyKey = py::class_<Key<T>, KeyBase<T>, FieldBase<T>>;
67using PySchemaItem = py::class_<SchemaItem<T>>;
72void declareFieldBaseSpecializations(PyFieldBase<T> &cls) {
73 cls.def(py::init<>());
77void declareFieldBaseSpecializations(PyFieldBase<Array<T>> &cls) {
78 cls.def(py::init<int>(),
"size"_a = 0);
79 cls.def(
"getSize", &FieldBase<Array<T>>::getSize);
80 cls.def(
"isVariableLength", &FieldBase<Array<T>>::isVariableLength);
83void declareFieldBaseSpecializations(PyFieldBase<std::string> &cls) {
84 cls.def(py::init<int>(),
"size"_a = -1);
85 cls.def(
"getSize", &FieldBase<std::string>::getSize);
91void declareFieldSpecializations(PyField<T> &cls) {
93 [](Field<T>
const &self) {
95 return py::make_tuple(self.getName(), self.getDoc(), self.getUnits());
98 int const NPARAMS = 3;
99 if (t.size() != NPARAMS) {
101 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
105 return Field<T>(t[0].cast<std::string>(), t[1].cast<std::string>(), t[2].cast<std::string>());
111void _sequenceFieldSpecializations(PyField<T> &cls) {
113 [](Field<T>
const &self) {
115 return py::make_tuple(self.getName(), self.getDoc(), self.getUnits(), self.getSize());
118 int const NPARAMS = 4;
119 if (t.size() != NPARAMS) {
121 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
125 return Field<T>(t[0].cast<std::string>(), t[1].cast<std::string>(), t[2].cast<std::string>(),
131void declareFieldSpecializations(PyField<Array<T>> &cls) {
132 _sequenceFieldSpecializations(cls);
135void declareFieldSpecializations(PyField<std::string> &cls) { _sequenceFieldSpecializations(cls); }
140void declareKeyBaseSpecializations(PyKeyBase<T> &) {}
143void declareKeyBaseSpecializations(PyKeyBase<Array<T>> &cls) {
144 cls.def(
"__getitem__", [](Key<Array<T>>
const &self, py::object
const &index) -> py::object {
145 if (py::isinstance<py::slice>(index)) {
146 py::slice slice(index);
147 py::size_t start = 0, stop = 0,
step = 0,
length = 0;
148 bool valid = slice.compute(self.getSize(), &start, &stop, &
step, &length);
149 if (!valid)
throw py::error_already_set();
151 throw LSST_EXCEPT(pex::exceptions::InvalidParameterError,
152 "Step for array Key indexing must be 1.");
154 return py::cast(self.slice(start, stop));
156 return py::cast(self[py::cast<int>(index)]);
159 cls.def(
"slice", &KeyBase<Array<T>>::slice);
165void declareKeyAccessors(PyKey<T> &cls) {
166 cls.def(
"get", [](Key<T>
const &self, BaseRecord &record) {
return record.get(self); });
167 cls.def(
"set", [](Key<T>
const &self, BaseRecord &record,
typename Key<T>::Value const &value) {
168 record.set(self, value);
173void declareKeyAccessors(PyKey<Array<U>> &cls) {
174 auto getter = [](Key<Array<U>>
const &self, BaseRecord &record) -> ndarray::Array<U, 1, 1> {
177 auto setter = [](Key<Array<U>>
const &self, BaseRecord &record, py::object
const &
value) {
178 if (self.getSize() == 0) {
181 record.set(self, py::cast<ndarray::Array<U, 1, 1>>(value));
186 auto v = py::cast<ndarray::Array<U const, 1, 0>>(
value);
187 ndarray::ArrayRef<U, 1, 1>
ref = record[self];
188 if (v.size() !=
ref.size()) {
190 pex::exceptions::LengthError,
191 (boost::format(
"Array sizes do not agree: %s != %s") % v.size() %
ref.size()).str());
197 cls.def(
"get", getter);
198 cls.def(
"set", setter);
202void declareKeySpecializations(PyKey<T> &cls) {
203 declareKeyAccessors(cls);
204 cls.def_property_readonly(
"subfields", [](py::object
const &) {
return py::none(); });
205 cls.def_property_readonly(
"subkeys", [](py::object
const &) {
return py::none(); });
207 [](Key<T>
const &self) {
209 return py::make_tuple(self.getOffset());
212 int const NPARAMS = 1;
213 if (t.size() != NPARAMS) {
215 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
219 return detail::Access::makeKey<T>(t[0].cast<int>());
223void declareKeySpecializations(PyKey<Flag> &cls) {
224 declareKeyAccessors(cls);
225 cls.def_property_readonly(
"subfields", [](py::object
const &) {
return py::none(); });
226 cls.def_property_readonly(
"subkeys", [](py::object
const &) {
return py::none(); });
227 cls.def(
"getBit", &Key<Flag>::getBit);
229 [](Key<Flag>
const &self) {
231 return py::make_tuple(self.getOffset(), self.getBit());
234 int const NPARAMS = 2;
235 if (t.size() != NPARAMS) {
237 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
246void declareKeySpecializations(PyKey<Array<T>> &cls) {
247 declareKeyAccessors(cls);
248 cls.def_property_readonly(
"subfields", [](Key<Array<T>>
const &self) -> py::object {
251 result.append(py::cast(i));
255 cls.def_property_readonly(
"subkeys", [](Key<Array<T>>
const &self) -> py::object {
258 result.append(py::cast(self[i]));
263 [](Key<Array<T>>
const &self) {
265 return py::make_tuple(self.getOffset(), self.getElementCount());
268 int const NPARAMS = 2;
269 if (t.size() != NPARAMS) {
271 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
275 return detail::Access::makeKeyArray<T>(t[0].cast<int>(), t[1].cast<int>());
279void declareKeySpecializations(PyKey<std::string> &cls) {
280 declareKeyAccessors(cls);
281 cls.def_property_readonly(
"subfields", [](py::object
const &) {
return py::none(); });
282 cls.def_property_readonly(
"subkeys", [](py::object
const &) {
return py::none(); });
284 [](Key<std::string>
const &self) {
286 return py::make_tuple(self.getOffset(), self.getElementCount());
289 int const NPARAMS = 2;
290 if (t.size() != NPARAMS) {
292 os <<
"Invalid number of parameters (" << t.size() <<
") when unpickling; expected "
302void declareSchemaType(WrapperCollection &wrappers) {
304 py::str pySuffix(suffix);
306 py::object astropyUnit = py::module::import(
"astropy.units").attr(
"Unit");
309 wrappers.wrapType(PyFieldBase<T>(wrappers.module, (
"FieldBase" + suffix).c_str()),
310 [](
auto &mod,
auto &cls) {
311 cls.def_static(
"getTypeString", &FieldBase<T>::getTypeString);
312 declareFieldBaseSpecializations(cls);
316 wrappers.wrapType(PyKeyBase<T>(wrappers.module, (
"KeyBase" + suffix).c_str()), [](
auto &mod,
auto &cls) {
317 declareKeyBaseSpecializations(cls);
321 wrappers.wrapType(PyField<T>(wrappers.module, (
"Field" + suffix).c_str()), [pySuffix, astropyUnit](
322 auto &mod,
auto &cls) {
323 declareFieldSpecializations(cls);
325 mod.attr(
"_Field")[pySuffix] = cls;
327 cls.def(py::init([astropyUnit](
328 std::string const &name, std::string const &doc, py::str const &units,
329 py::object const &size, py::str const &parse_strict) {
330 astropyUnit(units,
"parse_strict"_a = parse_strict);
331 std::string u = py::cast<std::string>(units);
332 if (size.is(py::none())) {
333 return new Field<T>(name, doc, u);
335 int s = py::cast<int>(size);
336 return new Field<T>(name, doc, u, s);
339 "name"_a,
"doc"_a =
"",
"units"_a =
"",
"size"_a = py::none(),
"parse_strict"_a =
"raise");
340 cls.def(
"_addTo", [](Field<T>
const &self, Schema &schema,
bool doReplace) -> Key<T> {
341 return schema.addField(self, doReplace);
343 cls.def(
"getName", &Field<T>::getName);
344 cls.def(
"getDoc", &Field<T>::getDoc);
345 cls.def(
"getUnits", &Field<T>::getUnits);
346 cls.def(
"copyRenamed", &Field<T>::copyRenamed);
352 wrappers.wrapType(PyKey<T>(wrappers.module, (
"Key" + suffix).c_str()), [pySuffix](
auto &mod,
auto &cls) {
353 mod.attr(
"_Key")[pySuffix] = cls;
354 cls.def(py::init<>());
355 cls.def(
"__eq__", [](Key<T>
const &self, Key<T>
const &other) ->
bool {
return self == other; },
358 cls.def(
"__ne__", [](Key<T>
const &self, Key<T>
const &other) ->
bool {
return self != other; },
369 cls.def(
"_findIn", [](Key<T>
const &self, Schema
const &schema) {
return schema.find(self); });
370 cls.def(
"_addMappingTo", [](Key<T>
const &self, SchemaMapper &
mapper, Field<T>
const &field,
372 cls.def(
"_addMappingTo", [](Key<T>
const &self, SchemaMapper &
mapper,
std::string const &name,
374 cls.def(
"_addMappingTo", [](Key<T>
const &self, SchemaMapper &
mapper, py::object
const &,
376 declareKeySpecializations(cls);
380 wrappers.wrapType(PySchemaItem<T>(wrappers.module, (
"SchemaItem" + suffix).c_str()),
381 [pySuffix](
auto &mod,
auto &cls) {
382 mod.attr(
"_SchemaItem")[pySuffix] = cls;
385 cls.def(
"getKey", [](SchemaItem<T>
const &self) {
return self.key; });
386 cls.def(
"getField", [](SchemaItem<T>
const &self) {
return self.field; });
387 cls.def(
"__getitem__", [](py::object
const &self,
int index) -> py::object {
389 return self.attr(
"key");
390 }
else if (index == 1) {
391 return self.attr(
"field");
395 throw py::index_error(
"Index to SchemaItem must be 0 or 1.");
397 cls.def(
"__len__", [](py::object
const &self) ->
int {
return 2; });
399 [](py::object
const &self) -> py::str {
return py::str(py::tuple(self)); });
400 cls.def(
"__repr__", [](py::object
const &self) -> py::str {
401 return py::str(
"SchemaItem(key={0.key}, field={0.field})").format(self);
404 [](SchemaItem<T>
const &self) {
406 return py::make_tuple(self.key, self.field);
409 int const NPARAMS = 2;
410 if (t.size() != NPARAMS) {
412 os <<
"Invalid number of parameters (" << t.size()
413 <<
") when unpickling; expected " << NPARAMS;
416 return SchemaItem<T>(t[0].cast<Key<T>>(), t[1].cast<Field<T>>());
423struct MakePythonSchemaItem {
424 template <
typename T>
425 void operator()(SchemaItem<T>
const &item) {
432void declareSchema(WrapperCollection &wrappers) {
433 wrappers.wrapType(PySchema(wrappers.module,
"Schema"), [](
auto &mod,
auto &cls) {
436 cls.attr(
"EQUAL_KEYS") = py::cast(int(Schema::EQUAL_KEYS));
437 cls.attr(
"EQUAL_NAMES") = py::cast(int(Schema::EQUAL_NAMES));
438 cls.attr(
"EQUAL_DOCS") = py::cast(int(Schema::EQUAL_DOCS));
439 cls.attr(
"EQUAL_UNITS") = py::cast(int(Schema::EQUAL_UNITS));
440 cls.attr(
"EQUAL_FIELDS") = py::cast(int(Schema::EQUAL_FIELDS));
441 cls.attr(
"EQUAL_ALIASES") = py::cast(int(Schema::EQUAL_ALIASES));
442 cls.attr(
"IDENTICAL") = py::cast(int(Schema::IDENTICAL));
444 cls.attr(
"VERSION") = py::cast(int(Schema::VERSION));
446 cls.def(py::init<>());
447 cls.def(py::init<Schema const &>());
448 cls.def(
"__getitem__", [](Schema &self, std::string const &name) { return self[name]; });
449 cls.def(
"__eq__", [](Schema
const &self, Schema
const &other) {
return self == other; },
451 cls.def(
"__ne__", [](Schema
const &self, Schema
const &other) {
return self != other; },
457 cls.def(
"find", [](py::object
const &self, py::object
const &key) -> py::object {
459 if (py::isinstance<py::str>(key) || py::isinstance<py::bytes>(key)) {
460 Schema
const &s = py::cast<Schema const &>(self);
462 MakePythonSchemaItem func;
463 s.findAndApply(name, func);
466 return key.attr(
"_findIn")(self);
467 }
catch (pex::exceptions::NotFoundError &err) {
469 PyErr_SetString(PyExc_KeyError, err.what());
470 throw py::error_already_set();
477 cls.def(
"forEach", [](Schema &self, py::object &obj) { self.forEach(obj); });
479 cls.def(
"contains", (
int (Schema::*)(Schema const &,
int) const) & Schema::
contains,
"other"_a,
480 "flags"_a =
int(Schema::EQUAL_KEYS));
481 cls.def(
"__contains__", [](py::object
const &self, py::object
const &key) {
483 self.attr(
"find")(key);
484 }
catch (py::error_already_set &err) {
493 cls.def_static(
"readFits", (Schema(*)(fits::MemFileManager &,
int)) &
Schema::readFits,
"manager"_a,
497 (
std::string(Schema::*)(
std::string const &,
std::string const &) const) & Schema::join,
503 "a"_a,
"b"_a,
"c"_a);
506 std::string const &) const) &
508 "a"_a,
"b"_a,
"c"_a,
"d"_a);
514void declareSubSchema(WrapperCollection &wrappers) {
515 wrappers.wrapType(PySubSchema(wrappers.module,
"SubSchema"), [](
auto &mod,
auto &cls) {
516 cls.def(
"getNames", &SubSchema::getNames,
"topOnly"_a = false);
517 cls.def(
"getPrefix", &SubSchema::getPrefix);
518 cls.def(
"asKey", [](SubSchema const &self) -> py::object {
519 MakePythonSchemaItem func;
521 return func.result.attr(
"key");
523 cls.def(
"asField", [](SubSchema
const &self) -> py::object {
524 MakePythonSchemaItem func;
526 return func.result.attr(
"field");
528 cls.def(
"find", [](SubSchema
const &self,
std::string const &name) -> py::object {
529 MakePythonSchemaItem func;
530 self.findAndApply(name, func);
533 cls.def(
"__getitem__", [](SubSchema &self,
std::string const &name) {
return self[
name]; });
543 auto &
mod = wrappers.module;
544 mod.attr(
"_Field") = py::dict();
545 mod.attr(
"_Key") = py::dict();
546 mod.attr(
"_SchemaItem") = py::dict();
table::Key< std::string > name
#define LSST_EXCEPT(type,...)
Create an exception with a given type.
Tag types used to declare specialized field types.
std::size_t getOffset() const noexcept
Return the offset (in bytes) of this field within a record.
bool isValid() const noexcept
Return true if the key was initialized to valid offset.
std::size_t getFieldCount() const
The total number of fields.
void disconnectAliases()
Sever the connection between this schema and any others with which it shares aliases.
void setAliasMap(std::shared_ptr< AliasMap > aliases)
Set the alias map.
std::shared_ptr< AliasMap > getAliasMap() const
Return the map of aliases.
std::set< std::string > getNames(bool topOnly=false) const
Return a set of field names in the schema.
static Schema readFits(std::string const &filename, int hdu=fits::DEFAULT_HDU)
Construct from reading a FITS file.
std::size_t getRecordSize() const
Return the raw size of a record in bytes.
std::size_t getNonFlagFieldCount() const
The number of non-Flag fields.
int compare(Schema const &other, int flags=EQUAL_KEYS) const
Do a detailed equality comparison of two schemas.
@ EQUAL_KEYS
Keys have the same types offsets, and sizes.
std::size_t getFlagFieldCount() const
The number of Flag fields.
Key< T > addMapping(Key< T > const &inputKey, bool doReplace=false)
Add a new field to the output Schema that is a copy of a field in the input Schema.
static Key< T > makeKey(std::size_t offset)
static Key< std::string > makeKeyString(std::size_t offset, std::size_t size)
A helper class for subdividing pybind11 module across multiple translation units (i....
const int DEFAULT_HDU
Specify that the default HDU should be read.
void wrapSchema(WrapperCollection &wrappers)
void addHash(PyClass &cls)
Add __hash__ method implemented by std::hash.
void addOutputOp(PyClass &cls, std::string const &method)
Add __str__ or __repr__ method implemented by operator<<.
static std::string getTypeString()
Return a string description of the field type.
T Value
the type returned by BaseRecord::get