ADCT Python
Python client API implementation for publishing ADCT data
Loading...
Searching...
No Matches
Public Member Functions | Static Public Member Functions | Static Public Attributes | Protected Member Functions | List of all members
adctk.builder.Builder Class Reference

ADC Toolkit message and subsection construction class. More...

Inheritance diagram for adctk.builder.Builder:
Inheritance graph
[legend]

Public Member Functions

 __init__ (self)
 
adc_types.Field get_value (self, str path_full)
 get the existing named nested field in the builder.
 
 get_value_string (self, str path)
 get the existing named nested scalar string value.
 
 get_value_int64 (self, str path)
 get the existing named nested scalar int64-equivalent value.
 
 get_value_uint64 (self, str path)
 get the existing named nested scalar uint64-equivalent value.
 
 __json__ (self)
 
 add_header_section (self, str application_name)
 Create or replace the 'header' section using application_name.
 
 add_host_section (self, osinfo=False, ramsize=False, env=False, cpu=False, gpu=False, numa=False, all_hs=False)
 Add the 'host' section, with optional subgroups of properties.
 
 add_app_data_section (self, 'Builder' app_data)
 Create the "app_data" section with data defined by the application writer.
 
 add_model_data_section (self, 'Builder' model_data)
 populate application run-time physics (re)configuration or result to "model_data" section.
 
 add_code_section (self, str tag, 'Builder' version, 'Builder|None' code_details=None)
 Add the 'code' section, with user-specified version subsection and optional code_details section describing things which do not normally vary with invocation.
 
 add_code_configuration_section (self, 'Builder' build_details)
 
 add_exit_data_section (self, 'int|str' return_code, str status, 'Builder|None' status_details=None)
 populate "exit_data" section with return code and status string & user provided details.
 
 add_memory_usage_section (self)
 Populate "memory_usage" section with current host /proc/meminfo data in the style of free(1).
 
'Builder|None' get_section (self, str name)
 get the existing named section.
 
'list[str]' get_section_names (self)
 Get the names of sections.
 
 add_mpi_section (self, name, communicator, bool rank=False, bool size=False, bool mpi_name=False, bool hostlist=False, bool rank_host=False, bool ver=False, bool lib_ver=False, bool mpiall=False, bool local=False)
 Unimplemented: python with mpi is not yet supported.
 
 add_gitlab_ci_section (self)
 Add gitlab_ci environment variable dictionary.
 
 add_workflow_section (self, str|None wfid=None, str|None parent=None, list[str]|str|None children=None)
 add data from adc_wfid_ environment variables.
 
 add_workflow_children (self, collections.abc.Iterable[str] child_uuids)
 Add list of child uuids to "adc_workflow" section after add_workflow_section has been called.
 
 add_slurm_section (self, collections.abc.Iterable[str]|None output_vars=None)
 Add slurm output environment variable dictionary elements.
 
 add_section (self, str name, 'Builder|collections.abc.Mapping[str,...]' section)
 Add or replace a user-defined, named section.
 
str serialize (self)
 get json string of the builder.
 
 add (self, str name, any value, value_type=None)
 add named item and its datatype information to the builder.
 
 add_path (self, name, str path)
 Add string file path.
 
 add_json_string (self, str name, str js)
 Add stringified json.
 
 add_yaml_string (self, str name, str yaml)
 Add stringified yaml.
 
 add_xml_string (self, str name, str xml)
 Add stringified xml.
 
 add_number_string (self, str name, decimal.Decimal dec)
 Add stringified number of arbitrary precision.
 
 add_epoch (self, name, int epoch)
 add unix epoch seconds (gettimeofday)
 
 add_mime (self, str name, str mime_type, str encoding, str file_name, str data)
 add a string encoded object (e.g.
 
 add_array_typed_string (self, name, adc_types.ScalarType st, collections.abc.Iterable[str] strings)
 Add string group tagged with scalar type.
 

Static Public Member Functions

'Builder' BasicBuilder (str app_name)
 DEPRECATED.
 

Static Public Attributes

 API_VERSION = adc_version.AdcVersion()._dict()
 

Protected Member Functions

 _set_attr (self, name, value)
 
 _get_attr (self, name)
 
 _find_path (self, obj, path)
 
dict _flatten (self, depth=0)
 Get a dictionary tree-copy constructed from keys and sections.
 
adc_types.KeyType _kind (self, str name)
 

Detailed Description

ADC Toolkit message and subsection construction class.

Use of the Builder ensures that adctk messages:

Constructor & Destructor Documentation

◆ __init__()

adctk.builder.Builder.__init__ (   self)

Reimplemented in adctk.builder.Memory, adctk.builder.Host, and adctk.builder.Header.

231 def __init__(self):
232 self._sections: dict[Builder] = {}
233 # dict of key/values defined by add directly on this builder
234 self._d: dict[dict] = {}
235

Member Function Documentation

◆ __json__()

adctk.builder.Builder.__json__ (   self)
438 def __json__(self):
439 print("CALLED __json__?")
440 return self._flatten()
441

◆ _find_path()

adctk.builder.Builder._find_path (   self,
  obj,
  path 
)
protected
look up value in nested string-named structures using /-delimited path
246 def _find_path(self, obj, path):
247 """look up value in nested string-named structures using /-delimited path"""
248 parts = path.strip("/").split("/")
249
250 for p in parts:
251 if p in obj:
252 obj = obj[p]
253 else:
254 return None
255 return obj
256

◆ _flatten()

dict adctk.builder.Builder._flatten (   self,
  depth = 0 
)
protected

Get a dictionary tree-copy constructed from keys and sections.

Changes made to the returned dictionary are not reflected in the Builder instance.

442 def _flatten(self, depth=0) -> dict:
443 """! @brief Get a dictionary tree-copy constructed from keys and sections.
444
445 Changes made to the returned dictionary are not reflected in
446 the Builder instance.
447 """
448 d = copy.deepcopy(self._d)
449 for name,section in self._sections.items():
450 d[name] = section._flatten(depth=depth+1)
451 return d
452

◆ _get_attr()

adctk.builder.Builder._get_attr (   self,
  name 
)
protected
241 def _get_attr(self, name):
242 if name in self._d:
243 return self._d[name]
244 return None
245

◆ _kind()

adc_types.KeyType adctk.builder.Builder._kind (   self,
str  name 
)
protected
 return the type that name resolves to in this builder.
1142 def _kind(self, name: str) -> adc_types.KeyType:
1143 """ return the type that name resolves to in this builder."""
1144 if name in self._sections:
1145
1146 return adc_types.KeyType.k_section
1147 if name in self._d:
1148
1149 return adc_types.KeyType.k_value
1150
1151 return adc_types.KeyType.k_none
1152

◆ _set_attr()

adctk.builder.Builder._set_attr (   self,
  name,
  value 
)
protected
238 def _set_attr(self, name, value):
239 self._d[name] = value
240

◆ add()

adctk.builder.Builder.add (   self,
str  name,
any  value,
  value_type = None 
)

add named item and its datatype information to the builder.

Supported value types are

  • built-ins: int, float, complex, bool, str
  • standard: decimal, numpy.ndarray (as 1-d list and dimensions if originally multidimensional)
  • character types: Python often wraps non-python applications; where the developer knows a python string represents a single character, the optional value_type parameter can be passed to disambiguate. Supported value_type options are (signed)"char",(unsigned)"char8", (signed)"char16",(signed)"char32"
  • lower-precision types: Python often wraps non-python applications; where the developer knows a python number represents something smaller than 64-bit, the optional value_type parameter can be passed to disambiguate. Supported numeric value_type options are int8, int16,int32, uint8, uint16, uint32, f32. (signed)"char16",(signed)"char32"
  • any value not explicitly supported which can be converted to a json string will be.

Any value not explicitly supported which cannot be converted to a json string will be silently ignored.

Notes:

  • int is represented as type int64 if possible and number_str if too big.
853 def add(self, name: str, value: any, value_type=None):
854 """! @brief add named item and its datatype information to the builder.
855
856 Supported value types are
857 - built-ins: int, float, complex, bool, str
858 - standard: decimal, numpy.ndarray (as 1-d list and dimensions if
859 originally multidimensional)
860 - character types: Python often wraps non-python applications; where
861 the developer knows a python string represents a single character,
862 the optional value_type parameter can be passed to disambiguate.
863 Supported value_type options are (signed)"char",(unsigned)"char8",
864 (signed)"char16",(signed)"char32"
865 - lower-precision types: Python often wraps non-python applications; where
866 the developer knows a python number represents something smaller than
867 64-bit, the optional value_type parameter can be passed to disambiguate.
868 Supported numeric value_type options are int8, int16,int32, uint8,
869 uint16, uint32, f32.
870 (signed)"char16",(signed)"char32"
871 - any value not explicitly supported which can be converted to a
872 json string will be.
873
874 Any value not explicitly supported which cannot be converted to
875 a json string will be silently ignored.
876
877 Notes:
878 - int is represented as type int64 if possible and number_str if too big.
879
880 """
881 if name is None or value is None:
882 raise ValueError("add: name and value cannot be None")
883 t = type(value)
884
885 if isinstance(t, Builder):
886 self.add_section(name, value)
887 return
888 if isinstance(value, bool):
889 self._set_attr(name,
890 { "type": adc_types.scalar_to_json(adc_types.type_to_scalar(t)),
891 "value": value })
892 return
893 if isinstance(value, float):
894 if not value_type:
895
896 self._set_attr(name,
897 { "type": adc_types.scalar_to_json(adc_types.type_to_scalar(t, value)),
898 "value": value })
899 return
900 if value_type in ["f32", "f64"]:
901 self._set_attr(name,
902 { "type": adc_types.scalar_to_json(adc_types.scalar_from_json(value_type)),
903 "value": value })
904 return
905 raise ValueError("add: value_type option for float value must be f32 or f64")
906 if isinstance(value, str):
907 if not value_type:
908
909 self._set_attr(name,
910 { "type": adc_types.scalar_to_json(adc_types.type_to_scalar(t)),
911 "value": value })
912 return
913 if value_type in ["char", "char16", "char32", "char8"]:
914
915 self._set_attr(name,
916 { "type": adc_types.scalar_to_json(adc_types.scalar_from_json(value_type)),
917 "value": value })
918 return
919 raise ValueError("add: value_type option for str value must be one of: char, char16, char32, char8")
920 if t == complex:
921 self._set_attr(name,
922 { "type": adc_types.scalar_to_json(adc_types.type_to_scalar(t, value)),
923 "value": [value.real, value.imag] })
924 return
925 if t == int:
926 if not value_type:
927 jtname = adc_types.scalar_to_json(adc_types.type_to_scalar(int, value))
928 if adc_types.fits_int64(value):
929 self._set_attr(name, { "type": jtname, "value": value })
930 else:
931 self._set_attr(name, { "type": jtname, "value": str(value) })
932 return
933 if value_type in ["int16", "int32", "int8", "uint8", "uint16", "uint32", "int64"]:
934
935 self._set_attr(name,
936 { "type": adc_types.scalar_to_json(adc_types.scalar_from_json(value_type)),
937 "value": value })
938 return
939 raise ValueError("add: value_type option for int value must be one of: int16, int32, int8, uint8, uint16, uint32, int64")
940 if isinstance(value, decimal.Decimal):
941 jtname = adc_types.scalar_to_json(adc_types.type_to_scalar(t))
942 self._set_attr(name, { "type": jtname, "value": str(value) })
943 return
944 if type(value).__module__ == numpy.__name__:
945 st = adc_types.scalar_from_numpy(value.dtype)
946 if st == adc_types.ScalarType.cp_none:
947 raise ValueError(f"add: unknown scalar type for {value.dtype}")
948 jtype = adc_types.scalar_to_json(st)
949 if isinstance(value, numpy.ndarray):
950 if value.flags['F_CONTIGUOUS']:
951 rowcol = 'F'
952 else:
953 rowcol = 'C'
954 shape = list( value.shape )
955 list_value = adc_types.get_list_format(value, st)
956 self._set_attr(name, {"type": "array_"+jtype,
957 "value": list_value, "container_type": "ndarray", "order": rowcol,
958 "shape": shape})
959 return
960 match value:
961 case numpy.float64() | numpy.float32() | numpy.float16():
962 self._set_attr(name, {"type": jtype, "value": \
963 float(numpy.format_float_scientific(value, unique=True)) , "BUG":"TRUE"} )
964 case numpy.int64() | numpy.int32() | numpy.int16() | numpy.int8() | \
965 numpy.uint32() | numpy.uint16() | numpy.uint8() |numpy.bool():
966 self._set_attr(name, {"type": jtype, "value": int(value) } )
967 case numpy.bool():
968 self._set_attr(name, {"type": jtype, "value": bool(value) } )
969 case numpy.str_() | numpy.bytes_():
970 self._set_attr(name, {"type": jtype, "value": value.tostring() } )
971 # fixme numpy str_, bytes_ conversion may need work.
972 case numpy.uint64():
973 self._set_attr(name, {"type": jtype, "value": value.tostring() })
974 case numpy.complex64() | numpy.complex128():
975 self._set_attr(name, {"type": jtype, "value": [ \
976 float(numpy.format_float_scientific(value.real, unique=True)), \
977 float(numpy.format_float_scientific(value.imag, unique=True))] })
978 case numpy.complex192() | numpy.complex256():
979 self._set_attr(name, {"type": jtype, "value": [ \
980 numpy.format_float_scientific(value.real, unique=True), \
981 numpy.format_float_scientific(value.imag, unique=True)] } )
982 case _:
983 msg = f"add: item {name} contains unserializable numpy type." + \
984 f"({type(value)})" + \
985 " consider add() on a type conversion to work around this."
986 print(dir(type(value).__module__ ))
987 print(dir( numpy.__name__))
988 print(dir(value))
989 raise ValueError(msg)
990 return # from all matched setattr
991 if isinstance(value, list|collections.abc.Set|tuple):
992 depth = 0
993 maxdepth=[0]
994 it = adc_types.get_common_scalar(value, depth, maxdepth)
995 pname = value.__class__.__name__
996 if it != adc_types.ScalarType.cp_none:
997 list_value = adc_types.get_list_format(value, it)
998 if maxdepth[0] == 1:
999 cname = "vector"
1000 pname = value.__class__.__name__
1001 jt = "array_" + adc_types.scalar_to_json(it)
1002 self._set_attr(name, {"type": jt,
1003 "value": list_value, "container_type": cname, "python": pname})
1004 return
1005 # this isn't documented in json spec; fixme-spec
1006 cname = "nested_list"
1007 major = "row" # fixme: use in setattr call? need test cases
1008 self._set_attr(name, {"type": adc_types.scalar_to_json(it),
1009 "value": list_value, "container_type": cname, "major": major})
1010 return
1011 # if we had a 'warn-untyped-items' mode, it would warn here.
1012 try:
1013 tmp = json.dumps(value)
1014 self._set_attr(name,
1015 {"type": adc_types.scalar_to_json(adc_types.ScalarType.cp_json),
1016 "value": tmp, "python": pname})
1017 return
1018 except TypeError as orig_exception:
1019 msg = f"add: item {name} contains unserializable types." + \
1020 " Use add() on individual elements to work around this."
1021 raise ValueError(msg) from orig_exception
1022 elif isinstance(value, array.array):
1023 it = adc_types.typecode_to_scalar(value)
1024 if it != adc_types.ScalarType.cp_none:
1025 list_value = value.tolist()
1026 if it == adc_types.ScalarType.cp_number_str:
1027 list_value = [str(v) for v in list_value]
1028 cname = "vector"
1029 pname = f"array.array('{value.typecode}')"
1030 jt = "array_" + adc_types.scalar_to_json(it)
1031 self._set_attr(name, {"type": jt,
1032 "value": list_value, "container_type": cname, "python": pname})
1033 return
1034 msg = f"add: array.array {name} contains unserializable types. how?!"
1035 raise ValueError(msg)
1036 else:
1037 # if we had a 'warn-untyped-items' mode, it would warn here.
1038
1039 pname = value.__class__.__name__
1040 try:
1041 tmp = json.dumps(value)
1042 self._set_attr(name,
1043 {"type": adc_types.scalar_to_json(
1044 adc_types.ScalarType.cp_json),
1045 "value": value, "python": pname})
1046 return
1047 except TypeError as orig_exception:
1048 msg = f"add: item {name} contains unserializable types." + \
1049 " Use add() on individual elements to work around this."
1050 raise ValueError(msg) from orig_exception
1051

◆ add_app_data_section()

adctk.builder.Builder.add_app_data_section (   self,
'Builder'  app_data 
)

Create the "app_data" section with data defined by the application writer.

It is recommended that any relationship identifiers to previous jobs or higher level environments goes in app_data, unless supported by add_workflow_section, add_slurm_section or other normalized sections.

Example: app_data = Factory.get_builder(); app_data.add("saw_id", getenv("SAW_WORKFLOW_ID"); builder.add_app_data_section(app_data)

480 def add_app_data_section(self, app_data: 'Builder'):
481 """! Create the "app_data" section with data defined by the application writer.
482
483 It is recommended that any relationship identifiers to previous jobs or higher
484 level environments goes in app_data, unless supported by add_workflow_section,
485 add_slurm_section or other normalized sections.
486
487 Example:
488 app_data = Factory.get_builder();
489 app_data.add("saw_id", getenv("SAW_WORKFLOW_ID");
490 builder.add_app_data_section(app_data)
491 """
492
493 if isinstance(app_data, Builder):
494 self.add_section("app_data", app_data)
495 else:
496 raise TypeError("argument app_data must be of type adctk.Builder")
497

◆ add_array_typed_string()

adctk.builder.Builder.add_array_typed_string (   self,
  name,
adc_types.ScalarType  st,
collections.abc.Iterable[str]  strings 
)

Add string group tagged with scalar type.

Parameters
stthe ScalarType
stringsa one-dimensional string group
namethe field name.
Exceptions
ValueErrorif strings cannot be converted.
1123 strings: collections.abc.Iterable[str]):
1124 """! Add string group tagged with scalar type.
1125
1126 @param st the ScalarType
1127 @param strings a one-dimensional string group
1128 @param name the field name.
1129 @exception ValueError if strings cannot be converted.
1130 """
1131 jtname = adc_types.scalar_to_json(adc_types.type_to_scalar(st))
1132 maxdepth = [0]
1133 jt = adc_types.get_common_scalar(strings, 0, maxdepth)
1134 if not adc_types.is_string_type(jt):
1135 raise ValueError("add_array_typed_string: strings argument must be strings only")
1136 if maxdepth[0] > 1:
1137 raise ValueError("add_array_typed_string: strings argument must be one-dimensional")
1138 data = adc_types.get_list_format(strings)
1139 self._set_attr(name, {"type": "array_"+jtname, "value": data,
1140 "container_type": "vector", "python": strings.__class__.__name__})
1141

◆ add_code_configuration_section()

adctk.builder.Builder.add_code_configuration_section (   self,
'Builder'  build_details 
)
 @brief Populate build/install "configuration" information such as options enabled

    @param build_details option list.
543 def add_code_configuration_section(self, build_details: 'Builder'):
544 """ @brief Populate build/install "configuration" information such as options enabled
545
546 @param build_details option list.
547 """
548 if build_details:
549 if isinstance(build_details, Builder):
550 self.add_section("code_configuration", build_details)
551 else:
552 raise TypeError("argument build_details must be from type adctk.Builder")
553 else:
554 raise TypeError("argument build_details must not be None")
555

◆ add_code_section()

adctk.builder.Builder.add_code_section (   self,
str  tag,
'Builder'  version,
'Builder|None'   code_details = None 
)

Add the 'code' section, with user-specified version subsection and optional code_details section describing things which do not normally vary with invocation.

Automatic contents include tag, base program, full path, loaded modules and shared libraries. Shared libraries detected may vary with invocation if LD_LIBRARY_PATH or related environment settings change.

509 code_details: 'Builder|None' = None):
510 """! Add the 'code' section, with user-specified version subsection and
511 optional code_details section describing things which do not normally
512 vary with invocation.
513
514 Automatic contents include tag, base program, full path, loaded modules
515 and shared libraries.
516 Shared libraries detected may vary with invocation if LD_LIBRARY_PATH
517 or related environment settings change.
518 """
519 fullpath = sys.executable
520 basename = os.path.basename(fullpath)
521 libs = get_libs()
522 mods = get_modules()
523 if code_details:
524 if isinstance(code_details, Builder):
525 d = code_details._flatten()
526 c = { "name": tag,
527 "program": basename,
528 "path": fullpath,
529 "version": version._flatten(),
530 "libs": libs,
531 "modules": mods,
532 "details": d }
533 else:
534 raise TypeError("argument status_details must be from type adctk.Builder")
535 else:
536 c = { "name": tag,
537 "program": basename,
538 "path": fullpath,
539 "version": version._flatten(),
540 "libs": libs}
541 self.add_section("code", c)
542

◆ add_epoch()

adctk.builder.Builder.add_epoch (   self,
  name,
int  epoch 
)

add unix epoch seconds (gettimeofday)

1097 def add_epoch(self, name, epoch: int):
1098 """! add unix epoch seconds (gettimeofday)"""
1099 self._set_attr(name, { "type": adc_types.scalar_to_json(
1100 adc_types.ScalarType.cp_epoch),
1101 "value": epoch })
1102

◆ add_exit_data_section()

adctk.builder.Builder.add_exit_data_section (   self,
'int|str'  return_code,
str  status,
'Builder|None'   status_details = None 
)

populate "exit_data" section with return code and status string & user provided details.

Parameters
return_codenumeric value of exit status
statusone-line summary of the execution
status_detailsany final or summary results of interest to application users or developers.
557 status: str, status_details: 'Builder|None' = None):
558 """! @brief populate "exit_data" section with return code and status string
559 & user provided details.
560
561 @param return_code numeric value of exit status
562 @param status one-line summary of the execution
563 @param status_details any final or summary results of interest to application
564 users or developers.
565 """
566 if status_details:
567 if isinstance(status_details, Builder):
568 d = status_details._flatten()
569 ed = { "return_code": str(return_code), "status": status, "details": d }
570 else:
571 raise TypeError("argument status_details must be from type adctk.Builder")
572 else:
573 ed = { "return_code": str(return_code), "status": status }
574 self.add_section("exit_data", ed)
575

◆ add_gitlab_ci_section()

adctk.builder.Builder.add_gitlab_ci_section (   self)

Add gitlab_ci environment variable dictionary.

The section added is named "gitlab_ci". The variables collected from env() are:

  • ci_runner_id
  • ci_runner_version
  • ci_project_id
  • ci_project_name
  • ci_server_fqdn
  • ci_server_version
  • ci_job_id
  • ci_job_started_at
  • ci_pipeline_id
  • ci_pipeline_source
  • ci_commit_sha
  • gitlab_user_login

Where the values are strings from the corresponding env() values.

642 def add_gitlab_ci_section(self):
643 """! @brief Add gitlab_ci environment variable dictionary.
644
645 The section added is named "gitlab_ci".
646 The variables collected from env() are:
647 - ci_runner_id
648 - ci_runner_version
649 - ci_project_id
650 - ci_project_name
651 - ci_server_fqdn
652 - ci_server_version
653 - ci_job_id
654 - ci_job_started_at
655 - ci_pipeline_id
656 - ci_pipeline_source
657 - ci_commit_sha
658 - gitlab_user_login
659
660 Where the values are strings from the corresponding env() values.
661 """
662 gitlab_ci_names = [
663 "CI_RUNNER_ID",
664 "CI_RUNNER_VERSION",
665 "CI_PROJECT_ID",
666 "CI_PROJECT_NAME",
667 "CI_SERVER_FQDN",
668 "CI_SERVER_VERSION",
669 "CI_JOB_ID",
670 "CI_JOB_STARTED_AT",
671 "CI_PIPELINE_ID",
672 "CI_PIPELINE_SOURCE",
673 "CI_COMMIT_SHA",
674 "GITLAB_USER_LOGIN"
675 ]
676 b = Builder()
677 for i in gitlab_ci_names:
678 v = os.getenv(i)
679 if v:
680 b._set_attr(i.lower(), v)
681 else:
682 b._set_attr(i.lower(), "")
683 self.add_section("gitlab_ci", b)
684

◆ add_header_section()

adctk.builder.Builder.add_header_section (   self,
str  application_name 
)

Create or replace the 'header' section using application_name.

To reuse a Builder object for a new message, call add_header_section on it again before sending it to publish again.

453 def add_header_section(self, application_name: str):
454 """! @brief Create or replace the 'header' section using application_name.
455
456 To reuse a Builder object for a new message, call add_header_section on it again
457 before sending it to publish again."""
458 header = Header(application_name)
459 self.add_section("header", header)
460

◆ add_host_section()

adctk.builder.Builder.add_host_section (   self,
  osinfo = False,
  ramsize = False,
  env = False,
  cpu = False,
  gpu = False,
  numa = False,
  all_hs = False 
)

Add the 'host' section, with optional subgroups of properties.

builder.add_host_section(os=True, ramsize=True, env=True, cpu=True, gpu=True, numa=True) is equivalent to builder.add_host_section(all_hs=True)

464 env=False, cpu=False, gpu=False, numa=False, all_hs=False):
465 """! Add the 'host' section, with optional subgroups of properties.
466
467 builder.add_host_section(os=True, ramsize=True, env=True, cpu=True, gpu=True, numa=True)
468 is equivalent to builder.add_host_section(all_hs=True)
469 """
470 if all_hs:
471 osinfo=True
472 ramsize=True
473 env=True
474 cpu=True
475 gpu=True
476 numa=True
477 host = Host(osinfo, ramsize, env, cpu, gpu, numa)
478 self.add_section("host", host)
479

◆ add_json_string()

adctk.builder.Builder.add_json_string (   self,
str  name,
str   js 
)

Add stringified json.

1061 def add_json_string(self, name: str, js: str ):
1062 """! Add stringified json"""
1063 if js is None or isinstance(js, str):
1064 self._set_attr(name,
1065 { "type": adc_types.scalar_to_json(
1066 adc_types.ScalarType.cp_json_str),
1067 "value": js })
1068 else:
1069 raise ValueError("add_json_string: json must be a string")
1070

◆ add_memory_usage_section()

adctk.builder.Builder.add_memory_usage_section (   self)

Populate "memory_usage" section with current host /proc/meminfo data in the style of free(1).

Values included are: mem_total mem_used mem_free mem_shared mem_buffers mem_cache mem_available swap_total swap_used swap_free

576 def add_memory_usage_section(self):
577 """! @brief Populate "memory_usage" section with current host /proc/meminfo data
578 in the style of free(1).
579
580 Values included are:
581 mem_total mem_used mem_free mem_shared
582 mem_buffers mem_cache mem_available
583 swap_total swap_used swap_free
584 """
585 memory = Memory()
586 self.add_section("memory_usage", memory)
587

◆ add_mime()

adctk.builder.Builder.add_mime (   self,
str  name,
str  mime_type,
str  encoding,
str  file_name,
str  data 
)

add a string encoded object (e.g.

image)

Parameters
namethe name in the message of this field
mime_typee.g. "image/png"
encodinge.g. "base64"
file_namee.g. "origin.png"
datacharacter array of the encoded object.
1103 def add_mime(self, name:str, mime_type:str, encoding:str, file_name:str, data: str):
1104 """! @brief add a string encoded object (e.g. image)
1105
1106 @param name the name in the message of this field
1107 @param mime_type e.g. "image/png"
1108 @param encoding e.g. "base64"
1109 @param file_name e.g. "origin.png"
1110 @param data character array of the encoded object.
1111 """
1112 if not name or not mime_type or not encoding or not file_name or not data:
1113 return
1114 self._set_attr(name, { "type": adc_types.scalar_to_json(
1115 adc_types.ScalarType.cp_mime),
1116 "mimetype": mime_type,
1117 "encoding": encoding,
1118 "filename": file_name,
1119 "value": data })
1120
1121

◆ add_model_data_section()

adctk.builder.Builder.add_model_data_section (   self,
'Builder'  model_data 
)

populate application run-time physics (re)configuration or result to "model_data" section.

For example initial or changes in mesh or particle decomp go here.

498 def add_model_data_section(self, model_data: 'Builder'):
499 """! @brief populate application run-time physics (re)configuration or
500 result to "model_data" section.
501 For example initial or changes in mesh or particle decomp go here.
502 """
503 if isinstance(model_data, Builder):
504 self.add_section("model_data", model_data)
505 else:
506 raise TypeError("argument model_data must be from type adctk.Builder")
507

◆ add_mpi_section()

adctk.builder.Builder.add_mpi_section (   self,
  name,
  communicator,
bool   rank = False,
bool   size = False,
bool   mpi_name = False,
bool   hostlist = False,
bool   rank_host = False,
bool   ver = False,
bool   lib_ver = False,
bool   mpiall = False,
bool   local = False 
)

Unimplemented: python with mpi is not yet supported.

608 mpiall:bool = False, local:bool = False):
609 """! Unimplemented: python with mpi is not yet supported."""
610 if mpiall:
611 local = True
612 hostlist = True
613 rank_host = True
614 if local:
615 rank = True
616 size = True
617 mpi_name = True
618 ver = True
619 lib_ver = True
620 b = Builder()
621 # fixme: shall we assume add_mpi_section items from mpi4py communicator?
622 # check that communicator is the type from mpi4py.
623 # default to 1-rank assumptions
624 if rank:
625 b._set_attr("mpi_rank", 0)
626 if size:
627 b._set_attr("mpi_size", 1)
628 if mpi_name:
629 b._set_attr("mpi_name", "unimplemented")
630 if ver:
631 # major.minor
632 b._set_attr("mpi_version", "unimplemented")
633 if lib_ver:
634 # vmajor.vminor.vrelease...
635 b._set_attr("mpi_library_version", "unimplemented")
636 if rank_host:
637 b._set_attr("mpi_rank_host", [])
638 if hostlist:
639 b._set_attr("mpi_hostlist", [])
640 self.add_section("mpi_comm_" + name, b)
641

◆ add_number_string()

adctk.builder.Builder.add_number_string (   self,
str  name,
decimal.Decimal   dec 
)

Add stringified number of arbitrary precision.

1090 def add_number_string(self, name: str, dec: decimal.Decimal ):
1091 """! Add stringified number of arbitrary precision"""
1092 decstr = str(dec)
1093 self._set_attr(name,
1094 {"type": adc_types.scalar_to_json(adc_types.ScalarType.cp_number_str),
1095 "value": decstr })
1096

◆ add_path()

adctk.builder.Builder.add_path (   self,
  name,
str   path 
)

Add string file path.

1052 def add_path(self, name, path:str ):
1053 """! Add string file path."""
1054 if path is None or isinstance(path, str):
1055 self._set_attr(name,
1056 { "type": adc_types.scalar_to_json(adc_types.ScalarType.cp_path),
1057 "value": path })
1058 else:
1059 raise ValueError("add_path: path must be a string")
1060

◆ add_section()

adctk.builder.Builder.add_section (   self,
str  name,
'Builder|collections.abc.Mapping[str,...]'  section 
)

Add or replace a user-defined, named section.

Using a string-keyed dictionary instead of a Builder is allowed but is not recommended for numeric or path or markup data because this omits variable type information needed for correct extraction in languages other than python. An exception occurs replacing a key/value pair with a section is attempted.

815 section: 'Builder|collections.abc.Mapping[str,...]'):
816 """! @brief Add or replace a user-defined, named section.
817
818 Using a string-keyed dictionary instead of a Builder is allowed but is
819 not recommended for numeric or path or markup data because
820 this omits variable type information needed for correct extraction in
821 languages other than python.
822 An exception occurs replacing a key/value pair with a section is attempted.
823 """
824 if name in self._d:
825 raise TypeError("attempting to replace a key/value with a section. Not Allowed. {}: {}".format(name, section))
826 if isinstance(section, Builder) and isinstance(name, str):
827 self._sections[name] = section
828 elif isinstance(section, collections.abc.Mapping):
829 b = Builder()
830 for i,j in section.items():
831 if isinstance(i, str):
832 if isinstance(j,Builder):
833 b.add_section(i, j)
834 else:
835 b._set_attr(i,j)
836 self._sections[name] = b
837 else:
838 raise TypeError("arguments name,section must be of types str," +
839 f"adctk.Builder,Mapping not {type(name)},{type(section)}")
840

◆ add_slurm_section()

adctk.builder.Builder.add_slurm_section (   self,
collections.abc.Iterable[str]|None   output_vars = None 
)

Add slurm output environment variable dictionary elements.

The section added is named "slurm".

The variables collected from env() are:

  • cluster: SLURM_CLUSTER_NAME
  • job_id: SLURM_JOB_ID
  • num_nodes: SLURM_JOB_NUM_NODES
  • dependency: SLURM_JOB_DEPENDENCY

Where the values are strings from the corresponding env() values.

781 def add_slurm_section(self, output_vars: collections.abc.Iterable[str]|None = None):
782 """! @brief Add slurm output environment variable dictionary elements.
783
784 The section added is named "slurm".
785
786 The variables collected from env() are:
787 - cluster: SLURM_CLUSTER_NAME
788 - job_id: SLURM_JOB_ID
789 - num_nodes: SLURM_JOB_NUM_NODES
790 - dependency: SLURM_JOB_DEPENDENCY
791
792 Where the values are strings from the corresponding env() values.
793 """
794 b = Builder()
795 for n,e in [
796 ( "cluster", "SLURM_CLUSTER_NAME"),
797 ( "job_id", "SLURM_JOB_ID"),
798 ( "num_nodes", "SLURM_JOB_NUM_NODES"),
799 ( "dependency", "SLURM_JOB_DEPENDENCY") ]:
800 v = os.getenv(e)
801 if v:
802 b._set_attr(n, v)
803 else:
804 b._set_attr(n, "")
805 for e in output_vars:
806 if isinstance(e,str):
807 v = os.getenv(e)
808 if v:
809 b._set_attr(e, v)
810 else:
811 b._set_attr(e, "")
812 self.add_section("slurm", b)
813

◆ add_workflow_children()

adctk.builder.Builder.add_workflow_children (   self,
collections.abc.Iterable[str]  child_uuids 
)

Add list of child uuids to "adc_workflow" section after add_workflow_section has been called.

This call is optional.

This call may be repeated if necessary, incrementally building the child list. wfid_children: [ user defined list of ids ]

Where a workflow can track its immediate children, it may substantially improve downstream workflow analyses if the child items can be captured. The result appears in the resulting json as wfid_children: $ADC_WFID_CHILDREN

749 def add_workflow_children(self, child_uuids: collections.abc.Iterable[str]):
750 """! @brief Add list of child uuids to "adc_workflow" section
751 after add_workflow_section has been called. This call is optional.
752
753 This call may be repeated if necessary, incrementally building the child list.
754 wfid_children: [ user defined list of ids ]
755
756 Where a workflow can track its immediate children, it may substantially
757 improve downstream workflow analyses if the child items can be captured.
758 The result appears in the resulting json as
759 wfid_children: $ADC_WFID_CHILDREN
760 """
761 wsec = self.get_section("adc_workflow")
762 if wsec:
763 cl = wsec._get_attr("wfid_children")
764 if cl:
765 if isinstance(cl, list):
766 for i in child_uuids:
767 if isinstance(i, str) and not i in cl:
768 cl.append(i)
769 wsec._set_attr("wfid_children", cl)
770 else:
771 raise TypeError("adc_workflow.wfid_children found but isn't an array (?)")
772 else:
773 cl = []
774 for i in child_uuids:
775 if isinstance(i, str) and not i in cl:
776 cl.append(i)
777 wsec._set_attr("wfid_children", cl)
778 else:
779 raise APISequenceError("add_workflow_children called before add_workflow")
780

◆ add_workflow_section()

adctk.builder.Builder.add_workflow_section (   self,
str|None   wfid = None,
str|None   parent = None,
list[str]|str|None   children = None 
)

add data from adc_wfid_ environment variables.

The section name is "adc_workflow".

The env variables collected are: wfid: ADC_WFID wfid_parent: $ADC_WFID_PARENT wfid_path: $ADC_WFID_PATH

The suggested format of an adc workflow identifier (wfid) is as: uuid -v1 -F STR run at the appropriate scope. For example, when starting numerous processes with mpi under slurm, in the sbatch script before launching anything else do: export ADC_WFID=$(uuid -v1 -F STR) and then make sure it gets propagated to all the processes via the launch mechamism. This ties all the messages from mpi ranks together in adc.

Where a workflow parent (such as an agent launching multiple slurm jobs) can, export ADC_WFID_PARENT=$(uuid -v1 -F STR) and then make sure this value is propagated to the slurm environments.

Where possible (requires coordination at all workflow levels) export ADC_WFID_PATH=(higher_level_wfid_path)/$ADC_WFID the entire task hierarchy identifier can be collected.

The choice of uuid version is up to the user.

686 children: list[str]|str|None =None):
687 """! @brief add data from adc_wfid_ environment variables.
688
689 The section name is "adc_workflow".
690
691 The env variables collected are:
692 wfid: ADC_WFID
693 wfid_parent: $ADC_WFID_PARENT
694 wfid_path: $ADC_WFID_PATH
695
696 The suggested format of an adc workflow identifier (wfid) is as:
697 uuid -v1 -F STR
698 run at the appropriate scope.
699 For example, when starting numerous processes with mpi under slurm,
700 in the sbatch script before launching anything else do:
701 export ADC_WFID=$(uuid -v1 -F STR)
702 and then make sure it gets propagated to all the processes via the launch
703 mechamism. This ties all the messages from mpi ranks together in adc.
704
705 Where a workflow parent (such as an agent launching multiple slurm jobs)
706 can,
707 export ADC_WFID_PARENT=$(uuid -v1 -F STR)
708 and then make sure this value is propagated to the slurm environments.
709
710 Where possible (requires coordination at all workflow levels)
711 export ADC_WFID_PATH=(higher_level_wfid_path)/$ADC_WFID
712 the entire task hierarchy identifier can be collected.
713
714 The choice of uuid version is up to the user.
715 """
716
717 b = Builder()
718 for n,e in [
719 ( "wfid", "ADC_WFID"),
720 ( "wfid_parent", "ADC_WFID_PARENT"),
721 ( "wfid_path", "ADC_WFID_PATH")
722 ]:
723 v = os.getenv(e)
724 if v:
725 b._set_attr(n, v)
726 else:
727 b._set_attr(n, "")
728 cenv = os.getenv("ADC_WFID_CHILDREN")
729 if cenv:
730 echildren = list(filter(None,cenv.split(":")))
731 b._set_attr("wfid_children", echildren)
732 # now override from arguments anything found in the environment
733 if children:
734 if isinstance(children, list):
735 clist = []
736 for i in children:
737 if isinstance(i, str) and len(i):
738 clist.append(i)
739 b._set_attr("wfid_children", echildren)
740 if isinstance(children, str):
741 echildren = list(filter(None, children.split(":")))
742 b._set_attr("wfid_children", echildren)
743 if parent and len(parent):
744 b._set_attr("wfid_parent", parent)
745 if wfid and len(wfid):
746 b._set_attr("wfid", parent)
747 self.add_section("adc_workflow", b)
748

◆ add_xml_string()

adctk.builder.Builder.add_xml_string (   self,
str  name,
str   xml 
)

Add stringified xml.

1081 def add_xml_string(self, name: str, xml: str ):
1082 """! Add stringified xml"""
1083 if xml is None or isinstance(xml, str):
1084 self._set_attr(name,
1085 {"type": adc_types.scalar_to_json(adc_types.ScalarType.cp_xml_str),
1086 "value": xml })
1087 else:
1088 raise ValueError("add_xml_string: xml must be a string")
1089

◆ add_yaml_string()

adctk.builder.Builder.add_yaml_string (   self,
str  name,
str   yaml 
)

Add stringified yaml.

1071 def add_yaml_string(self, name: str, yaml: str ):
1072 """! Add stringified yaml"""
1073 if yaml is None or isinstance(yaml, str):
1074 self._set_attr(name,
1075 { "type": adc_types.scalar_to_json(
1076 adc_types.ScalarType.cp_yaml_str),
1077 "value": yaml })
1078 else:
1079 raise ValueError("add_yaml_string: yaml must be a string")
1080

◆ BasicBuilder()

'Builder' adctk.builder.Builder.BasicBuilder ( str  app_name)
static

DEPRECATED.

1154 def BasicBuilder(app_name: str) -> 'Builder':
1155 """! DEPRECATED"""
1156 # pylint: disable=C0103
1157 b = Builder()
1158 b.add_header_section(app_name)
1159 return b
1160

◆ get_section()

'Builder|None' adctk.builder.Builder.get_section (   self,
str  name 
)

get the existing named section.

Returns
the section, or an empty pointer if it doesn't exist.
588 def get_section(self, name: str) -> 'Builder|None':
589 """! @brief get the existing named section.
590
591 @return the section, or an empty pointer if it doesn't exist.
592 """
593 if name in self._sections:
594 return self._sections[name]
595 return None
596

◆ get_section_names()

'list[str]' adctk.builder.Builder.get_section_names (   self)

Get the names of sections.

Returns
list of names, or an empty list.
597 def get_section_names(self) -> 'list[str]':
598 """! @brief Get the names of sections
599
600 @return list of names, or an empty list.
601 """
602 return self._sections.keys()
603

◆ get_value()

adc_types.Field adctk.builder.Builder.get_value (   self,
str  path_full 
)

get the existing named nested field in the builder.

Parameters
path_fulla simple json path such as /a/b/c which resolves to a value added via one of the add* functions.
Returns
the field description, with kt==k_none if not found. If the value was not set via this interface, k_none may be returned. Leading / is optional in path_full.
257 def get_value(self, path_full: str) -> adc_types.Field:
258 """! @brief get the existing named nested field in the builder.
259
260 @param path_full a simple json path such as /a/b/c which resolves to
261 a value added via one of the add* functions.
262 @return the field description, with kt==k_none if not found.
263 If the value was not set via this interface, k_none may be returned.
264 Leading / is optional in path_full.
265
266 """
267 field = adc_types.Field()
268 path = path_full.strip('/')
269
270 pos = path.find('/')
271 if pos == -1:
272 name = path
273 else:
274 name = path[:pos]
275
276 kt = self._kind(name)
277
278 child = path[pos:]
279 jit = None
280 match kt:
281 case adc_types.KeyType.k_none:
282
284 return field
285 case adc_types.KeyType.k_section:
286
288 return self._sections[name].get_value(child)
289 case adc_types.KeyType.k_value:
290
291 jit = self._d[name]
292 if not jit:
293
294 return field
295 field.kt = kt
296 if isinstance(jit, dict):
297
298 v = None
299 if "value" in jit:
300 v = jit["value"]
301 if "type" in jit and isinstance(jit["type"], str):
302 type_name = jit["type"]
303 else:
304 return field # not defined by api
305 if len(type_name):
306 if "container_type" in jit:
307 c = jit["container_type"]
308 else:
309 c = None
310 st = adc_types.scalar_from_json(type_name)
311 field.st = st
312 field.data = v
313 if c:
314 field.container = c
315
316 return field
317 if isinstance(jit, str):
318 field.st = adc_types.ScalarType.cp_cstr
319 field.data = jit
320
321 return field
322 if isinstance(jit, bool):
323 field.st = adc_types.ScalarType.cp_bool
324 field.data = jit
325
326 return field
327 if isinstance(jit, float):
328 field.st = adc_types.ScalarType.cp_f64
329 field.data = jit
330
331 return field
332 if isinstance(jit, int):
333 if adc_types.fits_int64(jit):
334 field.st = adc_types.ScalarType.cp_int64
335 field.data = jit
336
337 return field
338 if adc_types.fits_uint64(jit):
339 field.st = adc_types.ScalarType.cp_uint64
340 field.data = jit
341
342 return field
343 field.st = adc_types.ScalarType.cp_number_str
344 field.data = jit
345
346 return field
347 if isinstance(jit, decimal.Decimal):
348 field.st = adc_types.type_to_scalar(type(jit), jit)
349 field.data = jit
350
351 return field
352 field.data = jit
353 field.st = adc_types.type_to_scalar(type(jit), jit)
354
355 return field
356

◆ get_value_int64()

adctk.builder.Builder.get_value_int64 (   self,
str  path 
)

get the existing named nested scalar int64-equivalent value.

Parameters
patha simple json path such as /a/b/c which resolves to a value added via one of the add* functions.
Returns
None if the path is not matched or int64_max if it is not int64 compatible.
380 def get_value_int64(self, path: str):
381 """! @brief get the existing named nested scalar int64-equivalent value.
382
383 @param path a simple json path such as /a/b/c which resolves to
384 a value added via one of the add* functions.
385 @return None if the path is not matched or int64_max if it is
386 not int64 compatible.
387 """
388 field = self.get_value(path)
389 if not field or field.kt != adc_types.KeyType.k_value or len(field.data) != 1:
390 return None
391 i = numpy.iinfo(numpy.int64).max
392 match field.st:
393 case adc_types.ScalarType.cp_bool:
394 if field.data:
395 return 1
396 return 0
397 case adc_types.ScalarType.cp_char32 | \
398 adc_types.ScalarType.cp_char16 | \
399 adc_types.ScalarType.cp_char:
400 return ord(field.data)
401 case adc_types.ScalarType.cp_int8 | \
402 adc_types.ScalarType.cp_int16 | \
403 adc_types.ScalarType.cp_int32 | \
404 adc_types.ScalarType.cp_int64:
405 return field.data
406 case _:
407 return i
408

◆ get_value_string()

adctk.builder.Builder.get_value_string (   self,
str  path 
)

get the existing named nested scalar string value.

Parameters
patha simple json path such as /a/b/c which resolves to a value added via one of the add* functions.
Returns
None if the path is not matched or is not a string of some sort.
357 def get_value_string(self, path: str):
358 """! @brief get the existing named nested scalar string value.
359
360 @param path a simple json path such as /a/b/c which resolves to
361 a value added via one of the add* functions.
362 @return None if the path is not matched or is not a string of some sort.
363 """
364 field = self.get_value(path)
365 if (not field or field.kt != adc_types.KeyType.k_value or
366 (field.container and len(field.container))):
367 return None
368 match field.st:
369 case adc_types.ScalarType.cp_cstr | \
370 adc_types.ScalarType.cp_json_str | \
371 adc_types.ScalarType.cp_yaml_str | \
372 adc_types.ScalarType.cp_xml_str | \
373 adc_types.ScalarType.cp_json | \
374 adc_types.ScalarType.cp_path | \
375 adc_types.ScalarType.cp_number_str:
376 return field.data
377 case _:
378 return None
379

◆ get_value_uint64()

adctk.builder.Builder.get_value_uint64 (   self,
str  path 
)

get the existing named nested scalar uint64-equivalent value.

Parameters
patha simple json path such as /a/b/c which resolves to a uint64-equivalent value added via one of the add* functions.
Returns
None if the path is not matched or uint64_max if it is not uint64 compatible.
409 def get_value_uint64(self, path: str):
410 """! @brief get the existing named nested scalar uint64-equivalent value.
411
412 @param path a simple json path such as /a/b/c which resolves to
413 a uint64-equivalent value added via one of the add* functions.
414 @return None if the path is not matched or uint64_max if it is
415 not uint64 compatible.
416 """
417 field = self.get_value(path)
418 if not field or field.kt != adc_types.KeyType.k_value or len(field.data) != 1:
419 return None
420 i = numpy.iinfo(numpy.uint64).max
421 match field.st:
422 case adc_types.ScalarType.cp_bool:
423 if field.data:
424 return 1
425 return 0
426 case adc_types.ScalarType.cp_char32 | \
427 adc_types.ScalarType.cp_char16 | \
428 adc_types.ScalarType.cp_char:
429 return ord(field.data)
430 case adc_types.ScalarType.cp_uint8 | \
431 adc_types.ScalarType.cp_uint16 | \
432 adc_types.ScalarType.cp_uint32 | \
433 adc_types.ScalarType.cp_uint64:
434 return field.data
435 case _:
436 return i
437

◆ serialize()

str adctk.builder.Builder.serialize (   self)

get json string of the builder.

Items within builder must already be json compatible; custom encoders/decoders for json to handle numpy and complex correctly are not provided.

841 def serialize(self) -> str:
842 """! @brief get json string of the builder.
843
844 Items within builder must already be json compatible; custom
845 encoders/decoders for json to handle numpy and complex correctly
846 are not provided.
847 """
848 p = self._flatten()
849 # pprint.pprint(self)
850 return json.dumps(p)
851

Member Data Documentation

◆ API_VERSION

adctk.builder.Builder.API_VERSION = adc_version.AdcVersion()._dict()
static

The documentation for this class was generated from the following file: