All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
fclmodule.cxx
Go to the documentation of this file.
1 //----------------------------------------------------------------------
2 //
3 // Name: fclmodule.cxx
4 //
5 // Purpose: A python extension module that wraps the c++ fcl
6 // api. The main purpose of this module is to provide the
7 // capability to read and parse a fcl file and return the
8 // resulting parameter set in the form of a python dictionary.
9 //
10 // This module compiles to a shared library (fclmodule.so or fcl.so).
11 // When this shared library is on the python path, it can be
12 // imported as a python module (import fcl).
13 //
14 // Created: 4-Apr-2017 H. Greenlee
15 //
16 // FCL module interface.
17 //
18 // Module members.
19 //
20 // 1. Function make_pset(fclfile)
21 //
22 // The argument is the name of a fcl file (can be anywhere on $FHICL_FILE_PATH).
23 // The function reads the fcl file and returns the expanded parameter set
24 // in the form of a python dictionary.
25 //
26 // 2. Function pretty(pset)
27 //
28 // Generate a prettified string corresponding to a parameter set dictionary.
29 // String is valid fcl code.
30 //
31 // Example:
32 //
33 // #! /usr/bin/env python
34 // import fcl
35 // pset = fcl.make_pset('myfile.fcl')
36 // print(fcl.pretty(pset))
37 //
38 // Notes on python 2 and python 3 compatibility.
39 //
40 // 1. This single source file with its corresponding CMakeLists.txt file
41 // is compatible with being built using either python 2.7 or python 3.x.
42 //
43 // 2. The keys and string values in the python dictionary generated by
44 // function make_pset will contain whatever kind of python string is
45 // the default for the version of python (namely, bytes for python 2,
46 // unicode for python 3).
47 //
48 //
49 //----------------------------------------------------------------------
50 
51 #define PY_SSIZE_T_CLEAN
52 #include "Python.h"
53 #include <iostream>
54 #include <iomanip>
55 #include <sstream>
56 #include "cetlib/search_path.h"
57 #include "cetlib_except/exception.h"
58 
59 // Walk interface now public.
60 
61 #include "fhiclcpp/ParameterSet.h"
62 #include "fhiclcpp/ParameterSetWalker.h"
63 
64 #include "fhiclcpp/ParameterSetRegistry.h"
65 #include "fhiclcpp/make_ParameterSet.h"
66 
67 // Local python string compatibility functions.
68 
69 namespace {
70 
71  // Convert python byte string or unicode string to c++ string.
72 
73  std::string python_to_cxx_str(PyObject* obj) {
74  if(PyBytes_Check(obj))
75  return std::string(PyBytes_AsString(obj));
76  else if(PyUnicode_Check(obj)) {
77  PyObject* bytes = PyUnicode_AsUTF8String(obj);
78  return std::string(PyBytes_AsString(bytes));
79  }
80  else
81  throw cet::exception("fclmodule") << "String has wrong type." << std::endl;
82  }
83 
84  // Count number of characters in python byte string or unicode string.
85 
86  Py_ssize_t python_string_len(PyObject* obj) {
87  if(PyBytes_Check(obj))
88  return PyBytes_Size(obj);
89  else if(PyUnicode_Check(obj)) {
90  PyObject* bytes = PyUnicode_AsUTF8String(obj);
91  return PyBytes_Size(bytes);
92  }
93  else
94  throw cet::exception("fclmodule") << "String has wrong type." << std::endl;
95  }
96 
97  // Convert std::string to default python string.
98  // Python 2 - byte string.
99  // Python 3 - unicode string.
100 
101  PyObject* cxx_str_to_python(const std::string& s) {
102  PyObject* result = 0;
103 #if PY_MAJOR_VERSION >= 3
104  result = PyUnicode_FromString(s.c_str());
105 #else
106  result = PyBytes_FromString(s.c_str());
107 #endif
108  return result;
109  }
110 
111 }
112 
113 // Define a parameter set walker class
114 
115 class PythonDictConverter : public fhicl::ParameterSetWalker
116 {
117 public:
118 
119  using key_t = std::string;
120  using any_t = std::any;
121 
122  // Public methods.
123 
125  PyObject* result() const; // Result is python dictionary.
126 
127 private:
128 
129  // Base class overrides.
130 
131  void enter_table (key_t const& key, any_t const& any);
132  void enter_sequence (key_t const& key, any_t const& any);
133  void atom (key_t const& key, any_t const& any);
134  void exit_table (key_t const& key, any_t const& any);
135  void exit_sequence (key_t const& key, any_t const& any);
136 
137  // Local private methods.
138 
139  void add_object(key_t const& key, PyObject* pyobj); // Add object to current parent.
140 
141  // Data members.
142 
143  // Result stack.
144  // _stack[0] is the entire parameter set (a python dictionary).
145  // _stack.back() is the current parent container that is being filled
146  // (a python dictionary or list).
147 
148  std::vector<PyObject*> _stack;
149 };
150 
152 //
153 // Purpose: Constructor.
154 //
155 {
156  // Initialize result stack with an empty python dictionary.
157  // This dictionary will represent the parameter set.
158 
159  _stack.emplace_back(PyDict_New());
160 }
161 
163 //
164 // Purpose: Return result. When this method is called, the result stack
165 // should contain exactly one object, and this object should be a
166 // python dictionary.
167 //
168 // Returns: Python dictionary.
169 //
170 {
171  // Do consistency checks.
172 
173  if(_stack.size() != 1)
174  throw cet::exception("fclmodule") << "Result stack has wrong size: "
175  << _stack.size() << std::endl;
176  if(!PyDict_Check(_stack[0]))
177  throw cet::exception("fclmodule") << "Result stack has wrong type." << std::endl;
178 
179  // Everything OK.
180 
181  return _stack[0];
182 }
183 
184 void PythonDictConverter::enter_table(key_t const& key, any_t const& any)
185 //
186 // Purpose: Convert table.
187 //
188 // Arguments: key - Key of this object.
189 // any - Object
190 //
191 {
192  // Make a new empty python dictionary for this table.
193 
194  PyObject* dict = PyDict_New();
195 
196  // Insert this dictionary into the current parent container.
197 
198  add_object(key, dict);
199 
200  // Make the newly created python dictionary the current parent container.
201 
202  _stack.emplace_back(dict);
203 }
204 
205 void PythonDictConverter::enter_sequence(key_t const& key, any_t const& any)
206 //
207 // Purpose: Convert sequence.
208 //
209 // Arguments: key - Key of this object.
210 // any - Object
211 //
212 {
213  // Make a new empty python list for this sequence.
214 
215  PyObject* seq = PyList_New(0);
216 
217  // Insert the list into the current parent container.
218 
219  add_object(key, seq);
220 
221  // Make the newly created python dictionary the current parent container.
222 
223  _stack.emplace_back(seq);
224 }
225 
226 void PythonDictConverter::atom(key_t const& key, any_t const& any)
227 //
228 // Purpose: Convert atom.
229 //
230 // Arguments: key - Key of this object.
231 // any - Object
232 //
233 {
234  PyObject* pyval = 0;
235 
236  // Extract atom as string.
237 
238  const std::string& atom = std::any_cast<const std::string&>(any);
239 
240  // Get lower case version of argument string.
241 
242  std::string lcatom(atom);
243  std::transform(lcatom.begin(), lcatom.end(), lcatom.begin(),
244  [](unsigned char c){return std::tolower(c);});
245 
246  // Check for boolean.
247 
248  if(lcatom == std::string("true") || lcatom == std::string("\"true\"")) {
249  pyval = Py_True;
250  Py_INCREF(pyval);
251  }
252  else if(lcatom == std::string("false") || lcatom == std::string("\"false\"")) {
253  pyval = Py_False;
254  Py_INCREF(pyval);
255  }
256 
257  // Check for quoted string.
258 
259  size_t n = atom.size();
260  if(pyval == 0 && n >= 2 && atom[0] == '"' && atom[n-1] == '"') {
261  std::string s = atom.substr(1, n-2);
262  pyval = cxx_str_to_python(s);
263  }
264 
265  // Check for int.
266 
267  if(pyval == 0) {
268  std::istringstream iss(atom);
269  long i;
270  iss >> std::noskipws >> i;
271  if(iss.eof() and !iss.fail())
272  pyval = PyLong_FromLong(i);
273  }
274 
275  // Check for float.
276 
277  if(pyval == 0) {
278  std::istringstream iss(atom);
279  double x;
280  iss >> std::noskipws >> x;
281  if(iss.eof() and !iss.fail())
282  pyval = PyFloat_FromDouble(x);
283  }
284 
285  // Last resort store a copy of the original string (unquoted string).
286 
287  if(pyval == 0)
288  pyval = cxx_str_to_python(atom);
289 
290  // Done converting atom to python.
291  // Add python object to parent container.
292 
293  add_object(key, pyval);
294 }
295 
296 void PythonDictConverter::exit_table(key_t const& key, any_t const& any)
297 //
298 // Purpose: Close parent table.
299 //
300 {
301  // Do consistency checks.
302 
303  if(_stack.size() < 2)
304  throw cet::exception("fclmodule") << "Result stack has wrong size: "
305  << _stack.size() << std::endl;
306  if(!PyDict_Check(_stack.back()))
307  throw cet::exception("fclmodule") << "Result stack has wrong type." << std::endl;
308 
309  // Pop the current parent (this table) off the result stack.
310 
311  _stack.pop_back();
312 }
313 
314 void PythonDictConverter::exit_sequence(key_t const& key, any_t const& any)
315 //
316 // Purpose: Close current sequence.
317 //
318 // Arguments: key - Key of this object.
319 // any - Object
320 //
321 {
322  // Do consistency checks.
323 
324  if(_stack.size() < 2)
325  throw cet::exception("fclmodule") << "Result stack has wrong size: "
326  << _stack.size() << std::endl;
327  if(!PyList_Check(_stack.back()))
328  throw cet::exception("fclmodule") << "Result stack has wrong type." << std::endl;
329 
330  // Pop the current parent (this sequence) off the result stack.
331 
332  _stack.pop_back();
333 }
334 
336 //
337 // Purpose: Add object to the current parent container. The parent object
338 // can be either a python dictionary or a python list. The key
339 // argument is only used if the parent is a dictionary.
340 //
341 // Arguments: key - Key of object in parent.
342 // pyobj - Python object.
343 //
344 {
345  // Get the current parent object.
346 
347  if(_stack.size() == 0)
348  throw cet::exception("fclmodule") << "No parent object." << std::endl;
349  PyObject* parent = _stack.back();
350 
351  if(PyDict_Check(parent)) {
352 
353  // Insert object into dicionary.
354 
355  PyObject* keyobj = cxx_str_to_python(key);
356  PyDict_SetItem(parent, keyobj, pyobj);
357  Py_DECREF(pyobj);
358  }
359  else if(PyList_Check(parent)) {
360 
361  // Append object to list.
362 
363  PyList_Append(parent, pyobj);
364  Py_DECREF(pyobj);
365  }
366  else {
367 
368  // Oops.
369 
370  throw cet::exception("fclmodule") << "Parent object is not dictionary or list." << std::endl;
371  }
372 }
373 
374 static std::string format(PyObject* obj, unsigned int pos, unsigned int indent,
375  unsigned int maxlen, unsigned int depth)
376 //
377 // Purpose: Convert a python object to a prettified string. The resulting string
378 // is suppsed to be valid fcl code.
379 //
380 // Arguments: obj - Object to be formatted.
381 // pos - Current character position (number of characters printed
382 // since the last newline).
383 // indent - Indentation level (spaces) for multiline formatting.
384 // maxlen - Maximum line length before breaking.
385 // depth - Recursion depth.
386 //
387 // Returns: c++ string.
388 //
389 // Usage:
390 //
391 // This function is designed to call itself recursively in order to descend
392 // into structured objects like dictionaries and sequences.
393 //
394 // Dictionaries and sequences may be printed in either single-line or multiline
395 // format, depending on the complexity of the contained objects, and the indent
396 // and maxlen parameters.
397 //
398 {
399  // Result string stream.
400 
401  std::ostringstream ss;
402 
403  if(PyBytes_Check(obj)) {
404 
405  // String objects, add double quotes, but don't do any other formatting.
406 
407  ss << "\"" << PyBytes_AsString(obj) << "\"";
408  }
409 
410  else if(PyUnicode_Check(obj)) {
411 
412  // Unicode objects, convert to byte string and add double quotes.
413 
414  PyObject* bytes = PyUnicode_AsUTF8String(obj);
415  ss << "\"" << PyBytes_AsString(bytes) << "\"";
416  }
417 
418  else if(PyDict_Check(obj)) {
419 
420  // Always print dictionary objects in multiline format, one key per line.
421 
422  // Get list of keys. Keys are assumed to be strings.
423 
424  PyObject* keys = PyDict_Keys(obj);
425 
426  // Make a first pass over the list of keys to determine the maximum length key.
427 
428  int n = PyList_Size(keys);
429  int keymaxlen = 0;
430  for(int i=0; i<n; ++i) {
431  PyObject* key = PyList_GetItem(keys, i);
432  int keylen = python_string_len(key);
433  if(keylen > keymaxlen)
434  keymaxlen = keylen;
435  }
436 
437  // Print enclosing braces, but not for outermost table (i.e. the whole parameter set).
438 
439  bool outer = (pos == 0 && indent == 0);
440  if(!outer && n != 0)
441  ss << "{\n";
442 
443  // Second pass, loop over keys and values and convert them to strings.
444 
445  for(int i=0; i<n; ++i) {
446  PyObject* key = PyList_GetItem(keys, i);
447  PyObject* value = PyDict_GetItem(obj, key);
448  std::string ks = python_to_cxx_str(key);
449  ss << std::setw(indent) << ""
450  << std::setw(keymaxlen) << std::left << ks << " : "
451  << format(value, indent + keymaxlen + 3, indent+2, maxlen, depth+1)
452  << '\n';
453  }
454  if(n == 0)
455  ss << "{}";
456  else if(!outer)
457  ss << std::setw(indent-1) << std::right << '}';
458 
459  Py_DECREF(keys);
460 
461  }
462 
463  else if(PyList_Check(obj) || PyTuple_Check(obj)) {
464 
465  // Sequence printing handled here.
466  // Break lines only when position exceeds maxlen.
467 
468  char open_seq = 0;
469  char close_seq = 0;
470  int n = 0;
471  if(PyList_Check(obj)) {
472  open_seq = '[';
473  close_seq = ']';
474  n = PyList_Size(obj);
475  }
476  else {
477  open_seq = '(';
478  close_seq = ')';
479  n = PyTuple_Size(obj);
480  }
481 
482  // Loop over elements of this sequence.
483 
484  std::string sep(1, open_seq);
485  unsigned int break_indent = pos+1;
486  for(int i=0; i<n; ++i) {
487  ss << sep;
488  pos += sep.size();
489  PyObject* ele = PySequence_GetItem(obj, i);
490 
491  // Get the formatted string representation of this object.
492 
493  std::string f = format(ele, pos, break_indent, maxlen, depth+1);
494 
495  // Get the number of characters before the first newline.
496 
497  std::string::size_type fs = f.size();
498  std::string::size_type n1 = std::min(f.find('\n'), fs);
499 
500  // Decide if we want to break the line before printing this element.
501  // Never break at the first element of a sequence.
502  // Force a break (except at first element) if this is a structured element.
503  // If we do break here, reformat this element with the updated position.
504 
505  bool force_break = PyList_Check(ele) || PyTuple_Check(ele) || PyDict_Check(ele);
506  if(i > 0 && (force_break || pos + n1 > maxlen)) {
507  ss << '\n' << std::setw(break_indent) << "";
508  pos = break_indent;
509  f = format(ele, pos, break_indent, maxlen, depth+1);
510  fs = f.size();
511  }
512 
513  // Print this element
514 
515  ss << f;
516 
517  // Update the current character position, taking into account
518  // whether the string we just printed contains a newline.
519 
520  std::string::size_type n2 = f.find_last_of('\n');
521  if(n2 >= fs)
522  pos += fs;
523  else
524  pos = fs - n2 - 1;
525 
526  sep = std::string(", ");
527  Py_DECREF(ele);
528  }
529 
530  // Close sequence.
531 
532  if(n == 0)
533  ss << open_seq;
534  ss << close_seq;
535  }
536 
537  else {
538 
539  // Last resort, use python's string representation.
540 
541  PyObject* pystr = PyObject_Str(obj);
542  std::string s = python_to_cxx_str(pystr);
543 
544  // Print booleans in lower case instead of python default case.
545 
546  if(s == std::string("True"))
547  s = "true";
548  else if(s == std::string("False"))
549  s = "false";
550  ss << s;
551  }
552 
553  // Done.
554 
555  return ss.str();
556 }
557 
559 //
560 // Purpose: Public module function to read fcl file and return a python dictionary.
561 //
562 // Arguments: self - Not used, because this is not a member function.
563 // args - Argument tuple. A single string representing the
564 // name of a fcl file.
565 // Returned value: A python dictionary.
566 //
567 {
568  // Extract argument as string.
569 
570  const char* fclname;
571  if(!PyArg_ParseTuple(args, "s", &fclname))
572  return 0;
573  std::string fclstr(fclname);
574 
575  // Make parameter set.
576 
577  PyObject* result = 0;
578  try {
579  std::string pathvar("FHICL_FILE_PATH");
580  cet::filepath_lookup maker(pathvar);
581  auto pset = fhicl::ParameterSet::make(fclstr, maker);
582  PythonDictConverter converter;
583  pset.walk(converter);
584  result = converter.result();
585  }
586  catch(cet::exception& e) {
587  PyErr_SetString(PyExc_IOError, e.what());
588  result = 0;
589  }
590 
591  // Done.
592 
593  return result;
594 }
595 
597 //
598 // Purpose: Public module function to convert a python fcl dictionary to a
599 // prettified string.
600 //
601 // Arguments: self - Not used, because this is not a member function.
602 // args - Argument tuple. This function expects a single
603 // python object of any type, but typically a dictionary.
604 //
605 // Returned value: A python string or none.
606 //
607 {
608  // Result.
609 
610  PyObject* result = 0;
611 
612  // Extract argument.
613 
614  int n = PySequence_Length(args);
615  if(n == 0) {
616 
617  // No arguments, return none.
618 
619  result = Py_None;
620  Py_INCREF(result);
621  }
622  else {
623 
624  // Otherwise, extract the first element.
625 
626  PyObject* obj = PySequence_GetItem(args, 0);
627  std::string s = format(obj, 0, 0, 80, 0);
628  result = cxx_str_to_python(s);
629  }
630 
631  // Done.
632 
633  return result;
634 }
635 
636 // Module method table.
637 
638 static struct PyMethodDef fclmodule_methods[] = {
639  {"make_pset", make_pset, METH_VARARGS, "Convert fcl ParameterSet to python dictionary"},
640  {"pretty", pretty, METH_VARARGS, "Convert dictionary parameter set to fcl text"},
641  {0, 0, 0, 0}
642 };
643 
644 // Module initialization.
645 
646 #if PY_MAJOR_VERSION >= 3
647 
648 static struct PyModuleDef fclmodule = {
649  PyModuleDef_HEAD_INIT,
650  "fcl", // Module name.
651  0, // Module documentation.
652  -1, // Module state size (this module has no state).
653  fclmodule_methods // Method table.
654 };
655 
656 PyMODINIT_FUNC
657 PyInit_fcl(void)
658 {
659  return PyModule_Create(&fclmodule);
660 }
661 
662 #else
663 
664 extern "C" {
665  void initfcl()
666  {
667  Py_InitModule("fcl", fclmodule_methods);
668  }
669 }
670 
671 #endif
void atom(key_t const &key, any_t const &any)
Definition: fclmodule.cxx:226
static constexpr Sample_t transform(Sample_t sample)
_object PyObject
Definition: PyUtils.h:4
process_name opflash particleana ie x
std::string key_t
Definition: fclmodule.cxx:119
void exit_table(key_t const &key, any_t const &any)
Definition: fclmodule.cxx:296
void exit_sequence(key_t const &key, any_t const &any)
Definition: fclmodule.cxx:314
static std::string format(PyObject *obj, unsigned int pos, unsigned int indent, unsigned int maxlen, unsigned int depth)
Definition: fclmodule.cxx:374
walls no right
Definition: selectors.fcl:105
std::vector< PyObject * > _stack
Definition: fclmodule.cxx:148
static PyObject * make_pset(PyObject *self, PyObject *args)
Definition: fclmodule.cxx:558
void initfcl()
Definition: fclmodule.cxx:665
return match has_match and(match.match_pdg==11 or match.match_pdg==-11)
walls no left
Definition: selectors.fcl:105
void enter_table(key_t const &key, any_t const &any)
Definition: fclmodule.cxx:184
PyObject * result() const
Definition: fclmodule.cxx:162
static PyObject * pretty(PyObject *self, PyObject *args)
Definition: fclmodule.cxx:596
then echo File list $list not found else cat $list while read file do echo $file sed s
Definition: file_to_url.sh:60
do i e
static struct PyMethodDef fclmodule_methods[]
Definition: fclmodule.cxx:638
temporary value
byte bytes
Alias for common language habits.
Definition: datasize.h:101
void enter_sequence(key_t const &key, any_t const &any)
Definition: fclmodule.cxx:205
void add_object(key_t const &key, PyObject *pyobj)
Definition: fclmodule.cxx:335