Package: pybrm
pybrmA Python C wrapper for Oracle BRM.pybrmrequires >= Python 3.6 and is tested with Python 3.6.5 and 3.7.1, on BRM 7.5 (GCC 4.4.7) and BRM 12 (GCC 4.8.5). It has been tested on both 32bit and 64bit.InstallationTo install and compilepybrm, yourPIN_HOMEenvironment variable must point to the BRM home directory.Install it withpiplike this:pip install pybrmThe installation will determine if you are on 32bit or 64bit Python and attempt to install correctly.UsageImports:from pybrm import Client
from datetime import datetimeOpen up a client CM connection:c = Client()Create an empty flist:f = c.flist()Set some scalar data on the flist:f['PIN_FLD_POID'] = '/account'  # type /account, id -1, revision 0
f['PIN_FLD_STATUS'] = 1
f['PIN_FLD_CREATED_T'] = datetime.now()  # you can also set it to an integerOr if you preferflist.FIELDnotation:f.PIN_FLD_POID = '/account'  # type /account, id -1, revision 0
f.PIN_FLD_STATUS = 1
f.PIN_FLD_CREATED_T = datetime.now()  # you can also set it to an integerCreate a substructure in one shot, the preferred way:f['PIN_FLD_INHERITED_INFO'] = {
    'PIN_FLD_POID': ('/service', 1234), # type service, id 1234, revision 0
    'PIN_FLD_STATUS': 2,
}Or if you preferflist.FIELDnotation:f.PIN_FLD_INHERITED_INFO = {}
f.PIN_FLD_INHERITED_INFO.PIN_FLD_POID = ('/service', 1234) # type service, id 1234, revision 0
f.PIN_FLD_INHERITED_INFO.PIN_FLD_STATUS = 2Print the flist:>>> print(f)
0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
0 PIN_FLD_STATUS         ENUM [0] 1
0 PIN_FLD_CREATED_T    TSTAMP [0] (1582600707) Mon Feb 24 19:18:27 2020
0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
1     PIN_FLD_STATUS         ENUM [0] 2You can also build up a substruct it up piece by piece:f['PIN_FLD_EVENT'] = {}  # Creates an empty substruct
f['PIN_FLD_EVENT']['PIN_FLD_POID'] = ('/service', 1234, 1)  # type service, id 1234, revision 1

# You can pull a subsctruct out into its own variable if you like
substruct = f['PIN_FLD_EVENT']
substruct['PIN_FLD_STATUS'] = 3which is equivalent to:f.PIN_FLD_EVENT = {}
f.PIN_FLD_EVENT.PIN_FLD_POID = ('/service', 1234, 1)  # type service, id 1234, revision 1

# You can pull a subsctruct out into its own variable if you like
substruct = f.PIN_FLD_EVENT
substruct.PIN_FLD_STATUS = 3Let's print it again:>>> print(f)
# number of field entries allocated 20, used 5
0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
0 PIN_FLD_STATUS         ENUM [0] 1
0 PIN_FLD_CREATED_T    TSTAMP [0] (1582600707) Mon Feb 24 19:18:27 2020
0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
1     PIN_FLD_STATUS         ENUM [0] 2
0 PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 1
1     PIN_FLD_STATUS         ENUM [0] 3Create an array in one shot, the preferred way with dict syntax{}. The keys are the elem_ids, and the values are flists. In this example, we will creates two flists on elem_id 4 and 16:f['PIN_FLD_ARGS'] = {
    4: {'PIN_FLD_STATUS': 4},
    16: {'PIN_FLD_STATUS': 5},
}Let's print it again:>>> print(f)
# number of field entries allocated 20, used 9
0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
0 PIN_FLD_STATUS         ENUM [0] 1
0 PIN_FLD_CREATED_T    TSTAMP [0] (1582600707) Mon Feb 24 19:18:27 2020
0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
1     PIN_FLD_STATUS         ENUM [0] 2
0 PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 1
1     PIN_FLD_STATUS         ENUM [0] 3
0 PIN_FLD_ARGS          ARRAY [4] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 4
0 PIN_FLD_ARGS          ARRAY [16] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 5You can instead use list syntax[], and not specify the elem_ids. This will automatically start the elem_id off at 0 and increase it by one for each flist.f['PIN_FLD_RESULTS'] = [
    {'PIN_FLD_STATUS': 6},  # sets at elem_id 0
    {'PIN_FLD_STATUS': 7},  # sets at elem_id 1 
]orf.PIN_FLD_RESULTS = [{}, {}]
f.PIN_FLD_RESULTS[0].PIN_FLD_STATUS = 6
f.PIN_FLD_RESULTS[1].PIN_FLD_STATUS = 7Let's print it again:>>> print(f)
# number of field entries allocated 20, used 9
0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
0 PIN_FLD_STATUS         ENUM [0] 1
0 PIN_FLD_CREATED_T    TSTAMP [0] (1582600707) Mon Feb 24 19:18:27 2020
0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
1     PIN_FLD_STATUS         ENUM [0] 2
0 PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 1
1     PIN_FLD_STATUS         ENUM [0] 3
0 PIN_FLD_ARGS          ARRAY [4] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 4
0 PIN_FLD_ARGS          ARRAY [16] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 5
0 PIN_FLD_RESULTS       ARRAY [0] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 6
0 PIN_FLD_RESULTS       ARRAY [1] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 7You can also build up an array piece by piece:f['PIN_FLD_VALUES'] = {}  #  Creates an empty array
f['PIN_FLD_VALUES'][0] = {'PIN_FLD_STATUS': 8}  # Adds an flist on the 0th elem_id of this array

# You can pull an array out into its own variable if you like
array = f['PIN_FLD_VALUES']
array[1] = {}  # Create empty flist on the 1st elem_id of this array
array[1]['PIN_FLD_STATUS'] = 9  # set one field on the flist we just createdalternatively:f.PIN_FLD_VALUES = {}  #  Creates an empty array
f.PIN_FLD_VALUES[0] = {'PIN_FLD_STATUS': 8}  # Adds an flist on the 0th elem_id of this array

# You can pull an array out into its own variable if you like
array = f.PIN_FLD_VALUES
array[1] = {}  # Create empty flist on the 1st elem_id of this array
array[1].PIN_FLD_STATUS = 9  # set one field on the flist we just createdLet's print it again:>>> print(f)
# number of field entries allocated 20, used 11
0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
0 PIN_FLD_STATUS         ENUM [0] 1
0 PIN_FLD_CREATED_T    TSTAMP [0] (1582600707) Mon Feb 24 19:18:27 2020
0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
1     PIN_FLD_STATUS         ENUM [0] 2
0 PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 2
1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 1
1     PIN_FLD_STATUS         ENUM [0] 3
0 PIN_FLD_ARGS          ARRAY [4] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 4
0 PIN_FLD_ARGS          ARRAY [16] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 5
0 PIN_FLD_RESULTS       ARRAY [0] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 6
0 PIN_FLD_RESULTS       ARRAY [1] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 7
0 PIN_FLD_VALUES        ARRAY [0] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 8
0 PIN_FLD_VALUES        ARRAY [1] allocated 20, used 1
1     PIN_FLD_STATUS         ENUM [0] 9However, it's better to just create it all with a dict. This will keep your code clean:f = c.flist({
    'PIN_FLD_POID': '/account',
    'PIN_FLD_STATUS': 1,
    'PIN_FLD_CREATED_T': datetime.now(),
    'PIN_FLD_INHERITED_INFO': {
        'PIN_FLD_POID': ('/service', 1234),
        'PIN_FLD_STATUS': 2,
    },
    'PIN_FLD_EVENT': {
        'PIN_FLD_POID': ('/service', 1234, 1),
        'PIN_FLD_STATUS': 3,
    },
    'PIN_FLD_ARGS': {
        4: {'PIN_FLD_STATUS': 4},
        16: {'PIN_FLD_STATUS': 5},
    },
    'PIN_FLD_RESULTS': [
        {'PIN_FLD_STATUS': 6},
        {'PIN_FLD_STATUS': 7},
    ],
    'PIN_FLD_VALUES': {
        0: {'PIN_FLD_STATUS': 8},
        1: {'PIN_FLD_STATUS': 9},
    },
})Call an opcode via PCM_OP with no flags:output = f('PCM_OP_TEST_LOOPBACK')  # or f.opcode('PCM_OP_TEST_LOOPBACK')Call an opcode via PCM_OP with one flag:output = f('PCM_OP_TEST_LOOPBACK', flags='SRCH_DISTINCT')Call an opcode via PCM_OP with multiple flags:output = f('PCM_OP_TEST_LOOPBACK', flags=('SRCH_DISTINCT', 'PCM_OPFLG_REV_CHECK'))You can also use thef.opcodefunction. One benefit is you can ctrl+f to find all opcode calls.output = f.opcode('PCM_OP_TEST_LOOPBACK', flags='SRCH_DISTINCT')To use PCM_OPREF, which passes the reference of the flist to the opcode, instead of a copy like PCM_OP:output = f('PCM_OP_TEST_LOOPBACK', reference=True)  # or f.opcode('PCM_OP_TEST_LOOPBACK', reference=True)Get some data off the flist:status = f['PIN_FLD_STATUS']
assert status == 1

poid = f.PIN_FLD_POID
assert poid.type == '/account'
assert poid.id == -1
assert poid.revision == 0
print(poid.database)  # this is picked up dynamically, but is probably 1

assert f['PIN_FLD_INHERITED_INFO']['PIN_FLD_POID'].type == '/service'
assert len(f['PIN_FLD_VALUES']) == 2
assert f.PIN_FLD_VALUES[0].PIN_FLD_STATUS == 8Get the length:assert len(f) == 8
assert f.count() == 8  # same as len
assert f.count(recursive=True) == 18  # counts items in arrays/substructs

assert len(f['PIN_FLD_INHERITED_INFO']) == 2  # count of a substruct
assert f['PIN_FLD_INHERITED_INFO'].count() == 2 # same as len


assert len(f.PIN_FLD_VALUES) == 2  # count of an array
assert f.PIN_FLD_VALUES.count() == 2  # same as lenGet all the field names:>>> print(list(f.keys())
['PIN_FLD_POID', 'PIN_FLD_STATUS', 'PIN_FLD_CREATED_T', 'PIN_FLD_INHERITED_INFO', 'PIN_FLD_EVENT', 'PIN_FLD_ARGS', 'PIN_FLD_RESULTS', 'PIN_FLD_VALUES']Check if a field exists on our flist:>>> 'PIN_FLD_POID' in f
True
>>> 'PIN_FLD_EVENT' in f
True
>>> 'PIN_FLD_VALUES' in f
True
>>> 'PIN_FLD_QUANTITY' in f
FalseGet an flist at a particular elem_id from an array:child = f['PIN_FLD_VALUES'][0]
child = f.PIN_FLD_VALUES[1]Get any flist from an array (PIN_ELEMID_ANY):child = f['PIN_FLD_VALUES']['*']
# This is equivalent
child = f.PIN_FLD_VALUES['PIN_ELEMID_ANY']
# This is also equivalent
child = f['PIN_FLD_VALUES'][-1]Delete a field:assert 'PIN_FLD_STATUS' in f
del f['PIN_FLD_STATUS']
assert 'PIN_FLD_STATUS' not in f

assert 'PIN_FLD_VALUES' in f
del f.PIN_FLD_VALUES
assert 'PIN_FLD_VALUES' not in fSearching Shortcuts for PCM_OP_SEARCHYou can create your own flists and callPCM_OP_SEARCHyourself, however theclient.search()make searching convenient with syntactic sugar:output = c.search(
    template=' select X from /foo where F1 = V1 and F2 = V2',
    search_flags='SRCH_DISTINCT',
    args={'PIN_FLD_LOGIN': 'bar', 'PIN_FLD_USAGE_TYPE': 'foo'},
)This would build the following search flist and execute it, returning the results:# number of field entries allocated 20, used 6
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 256
0 PIN_FLD_TEMPLATE        STR [0] " select X from /foo where F1 = V1 and F2 = V2"
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_LOGIN           STR [0] "bar"
0 PIN_FLD_ARGS          ARRAY [2] allocated 20, used 1
1     PIN_FLD_USAGE_TYPE      STR [0] "foo"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 0, used 0For theargsparameter, you may specify it with a dict, or a list of tuples. These two are the same:output = c.search(
    template=' select X from /foo where F1 = V1 and F2 = V2',
    args={'PIN_FLD_LOGIN': 'bar', 'PIN_FLD_USAGE_TYPE': 'foo'},
)

output = c.search(
    template=' select X from /foo where F1 = V1 and F2 = V2',
    args=[('PIN_FLD_LOGIN', 'bar'), ('PIN_FLD_USAGE_TYPE', 'foo')],
)Using a dict in args is just syntactic sugar to save a few keystrokes.
Sometimes you cannot use the dict, for example if you have a template likeselect C from /foo where F1 IN (V1, V2).
A dict will not work because a dict can only have unique keys.output = c.search(
    template=' select X from /foo where F1 IN (V1, V2) ',
    args=[
        ('PIN_FLD_USAGE_TYPE', 'FOO'),
        ('PIN_FLD_USAGE_TYPE', 'BAR')
    ]
)This would build the following search flist:# number of field entries allocated 20, used 6
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 0
0 PIN_FLD_TEMPLATE        STR [0] " select C from /foo where F1 IN (V1, V2) "
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_USAGE_TYPE      STR [0] "FOO"
0 PIN_FLD_ARGS          ARRAY [2] allocated 20, used 1
1     PIN_FLD_USAGE_TYPE      STR [0] "BAR"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 0, used 0You can use nested substructs and arrays in theargsparam like the following:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_EVENT': {'PIN_FLD_USAGE_TYPE': 'bar'}}
)This would create the following search flist:# number of field entries allocated 20, used 5
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 0
0 PIN_FLD_TEMPLATE        STR [0] " select X from /foo where F1 = V1 "
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 1
2         PIN_FLD_USAGE_TYPE      STR [0] "bar"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 0, used 0You may customize the results of the search by using theresultsargument. By default, it will return all all the fields.This will return only the three fields specified inresults:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_USAGE_TYPE', 'PIN_FLD_EVENT_TYPE', 'PIN_FLD_SERVICE_TYPE'}
)Here is the search flist that would be created:# number of field entries allocated 20, used 5
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 0
0 PIN_FLD_TEMPLATE        STR [0] " select X from /foo where F1 = V1 "
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_LOGIN           STR [0] "bar"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 20, used 3
1     PIN_FLD_EVENT_TYPE      STR [0] NULL str ptr
1     PIN_FLD_USAGE_TYPE      STR [0] NULL str ptr
1     PIN_FLD_SERVICE_TYPE    STR [0] NULL str ptrThese three are identical:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_USAGE_TYPE', 'PIN_FLD_EVENT_TYPE', 'PIN_FLD_SERVICE_TYPE'}
)

output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_USAGE_TYPE': None, 'PIN_FLD_EVENT_TYPE': None, 'PIN_FLD_SERVICE_TYPE': None}
)

output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results=['PIN_FLD_USAGE_TYPE', 'PIN_FLD_EVENT_TYPE', 'PIN_FLD_SERVICE_TYPE']
)The following will return all the fields in the substructure:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_INHERITED_INFO'}
)This will return only the subset of fields in the substructure:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_INHERITED_INFO': {
        'PIN_FLD_USAGE_TYPE', 'PIN_FLD_EVENT_TYPE'
    }}
)Here is the search flist that would be created:# number of field entries allocated 20, used 5
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 0
0 PIN_FLD_TEMPLATE        STR [0] " select X from /foo where F1 = V1 "
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_LOGIN           STR [0] "bar"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 20, used 1
1     PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
2         PIN_FLD_EVENT_TYPE      STR [0] NULL str ptr
2         PIN_FLD_USAGE_TYPE      STR [0] NULL str ptrYou can do the same with arrays, for examplePIN_FLD_FIELD.output = c.search(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
    results={'PIN_FLD_FIELD': {
        'PIN_FLD_USAGE_TYPE', 'PIN_FLD_EVENT_TYPE'
    }}
)Here is the search flist that would be created:# number of field entries allocated 20, used 5
0 PIN_FLD_POID           POID [0] 0.0.0.1 /search -1 0
0 PIN_FLD_FLAGS           INT [0] 0
0 PIN_FLD_TEMPLATE        STR [0] " select X from /foo where F1 = V1 "
0 PIN_FLD_ARGS          ARRAY [1] allocated 20, used 1
1     PIN_FLD_LOGIN           STR [0] "bar"
0 PIN_FLD_RESULTS       ARRAY [*] allocated 20, used 1
1     PIN_FLD_FIELD         ARRAY [*] allocated 20, used 2
2         PIN_FLD_EVENT_TYPE      STR [0] NULL str ptr
2         PIN_FLD_USAGE_TYPE      STR [0] NULL str ptrYou can specify the search flags inPIN_FLD_FLAGSvia thesearch_flagsparameter:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    search_flags='SRCH_DISTINCT',
    args={'PIN_FLD_LOGIN': 'bar'},
)You can pass in more than one search flag in a sequence:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    search_flags=('SRCH_DISTINCT', 'SRCH_EXACT'),
    args={'PIN_FLD_LOGIN': 'bar'},
)You can specify the opcode flags via theopcode_flagsparameter:output = c.search(
    template=' select X from /foo where F1 = V1 ',
    search_flags=('SRCH_DISTINCT', 'SRCH_EXACT'),
    args={'PIN_FLD_LOGIN': 'bar'},
    opcode_flags='PCM_OPFLG_CACHEABLE',
)In fact, if you want to examine the search flist, and not execute it, you can usec.search_build_flist():search =c.search_build_flist(
    template=' select X from /foo where F1 = V1 ',
    args={'PIN_FLD_LOGIN': 'bar'},
)
print(search)Substructures and ArraysOne important thing to note about substructures is that when you add one flist to another flist, it is ALWAYS copied.c = Client()
f = c.flist({'PIN_FLD_STATUS': 1}) 
f2 = c.flist()

# COPY f onto f2
f2['PIN_FLD_INHERITED_INFO'] = f

# Change f, which does not change anything on f2
f['PIN_FLD_STATUS'] = 2
# The copy on f2['PIN_FLD_INHERITED_INFO'] was not changed!
assert f2['PIN_FLD_INHERITED_INFO']['PIN_FLD_STATUS'] == 1The same is true for arrays:c = Client()
f = c.flist({'PIN_FLD_STATUS': 1})
f2 = c.flist()

# COPY f2 onto f's PIN_FLD_VALUES array
f2['PIN_FLD_VALUES'] = {0: f}

# Change f, which does not change anything on f2
f['PIN_FLD_STATUS'] = 2
# The copy on f['PIN_FLD_VALUES'][0] was not changed!
assert f2['PIN_FLD_VALUES'][0]['PIN_FLD_STATUS'] == 1Both the FList and the BRMArray classes implement the Python dictionary protocol. What this means is that they both have a key that maps to a value.
In the case of an flist, the key is the field, and the value is the value of the flist at that field.
In the case of a BRMArray, the key is the elem_id, and the value is the flist at that elem_id.Do not try to iterate over a BRMArray like a Python list:f = c.flist({
    'PIN_FLD_VALUES': [
        {'PIN_FLD_STATUS': 2},
        {'PIN_FLD_STATUS': 4},
    ]
})

# Wrong, probably not what you want:
>>> for v in f['PIN_FLD_VALUES']:
>>>     v
0
1Doing it that way will return the keys, or elem_ids of the BRMArray, which in this case are0and1.Instead, you have to use the.values()function, like you would on a dict in python:>>> for v in f['PIN_FLD_VALUES'].values():
>>>     v
{'PIN_FLD_STATUS': 2}
{'PIN_FLD_STATUS': 4}Likewise, to get the keys and the values, use.items():>>> for k, v in f['PIN_FLD_VALUES'].items():
>>>     k, v
(0, {'PIN_FLD_STATUS': 2})
(1, {'PIN_FLD_STATUS': 4})Empty Array BehaviorThere is some behavior with empty arrays that is important to keep in mind.In the following example, does anything happen to the C flist?c = client()
f = c.flist()
f['PIN_FLD_RESULTS'] = {}No, nothing has happened. We have not added any flists at all. However in Python we can access this array:ar = f['PIN_FLD_RESULTS']Most importantly to keep in mind, we can check ifPIN_FLD_RESULTSexists on our flist:if 'PIN_FLD_RESULTS' in f:
    print('yes it exists')However, in fact there is nothing on the C flist at this point, until you add your first flist:ar[0] = {'PIN_FLD_POID': '/foo'}It's worth repeating: the BRMArray class is not implemented as a list, but rather as a dictionary.
Why was this decision made? An array in brm is sparse array where each value is an flist.
You can put an flist at index 2 of an array, and then place another flist on the 9th position, leaving gaps in between.
Due to this sparse nature, it is better to represent it as a dict of keys (elem_ids) to values (flists)Loggingpybrmintegrates with the normalpinloglogging that the BRM C API does.import logging
import pybrm
from pybrm import Client, BRMHandler

# These three are the defaults
pybrm.pin_err_set_level(pybrm.PIN_ERR_LEVEL_ERROR)
pybrm.pin_err_set_program('pybrm')
pybrm.pin_err_set_logfile('default.pinlog')

logging.basicConfig(level=pybrm.brm_to_python_log_level(pybrm.PIN_ERR_LEVEL_ERROR))
logger = logging.getLogger(__name__)
logger.addHandler(BRMHandler())

logger.error('This is visible')
logger.debug('This is not visible')Note how we are passing through two loggers here, the regular python logger as well as the BRM logger.Therefore we have to sync the python log level with the BRM log level,
which is whatpybrm.brm_to_python_log_leveldoes.Later on in your program, you can change the logging parameters like the following:pybrm.pin_err_set_level(pybrm.PIN_ERR_LEVEL_DEBUG)
logger.setLevel(pybrm.brm_to_python_log_level(pybrm.PIN_ERR_LEVEL_DEBUG))
pybrm.pin_err_set_program('new_program_name')
pybrm.pin_err_set_logfile('new.pinlog')TransactionsYou can use transactions like the following:c = Client()
t = c.transaction('/poid')
try:
    f = c.flist()
    f['PIN_FLD_POID'] = '/foo'
    f('PCM_OP_TEST_LOOPBACK')
except Exception:
    t.rollback()
else:
    t.commit()Better yet, use the context manager:c = Client()
with c.transaction('/poid') as t:
    f = c.flist()
    f['PIN_FLD_POID'] = '/foo'
    f('PCM_OP_TEST_LOOPBACK')
    t.commit()Using the context manager will result in a rollback if a) an exception is raised or b) no commit was issued.For example, this will rollback, sincecommitwas never executed:c = Client()
with c.transaction('/poid') as t:
    f = c.flist()
    f['PIN_FLD_POID'] = '/foo'
    f('PCM_OP_TEST_LOOPBACK')
    # Rollback occurs now, since commit was not invoked.Multi Threadingpybrmis thread safe, as long as eachClient(), and all the flists owned by a client, are accessed by a single thread.
Just as with the BRM C API, it is not thread safe to have multiple threads access a singleClient()or touching flists with shared error buffers.For example, if you needed to split up some work and run them in multiple threads, each thread must have its ownClient():import threading

def work(data):
    # do NOT pass a parent flist to a thread; rather serialize it with .asdict()
    with Client() as c:
        flist = c.flist(data)
        out = flist('PCM_OP_TEST_LOOPBACK')
        print(out)

def main():
    with Client() as c:
        flists = [
            c.flist({'PIN_FLD_POID': ('/account', i)})  # account with id = i
            for i in range(10)
        ]
        # do NOT pass parent flists to a thread; serialize it with .asdict() first
        threads = [
            threading.Thread(target=work, args=(flist.asdict(),))  # serialize the flist to a dict
            for flist in flists
        ]
        for thread in threads:
            thread.start()
        for thread in threads:
            thread.join()One important fact to keep in mind is that the flists created from a client belong to that client.You should not pass flists created in one thread to another thread. Instead, you should serialize them to a dictionary and back:c = Client()
c2 = Client()
f = c.flist()
f['PIN_FLD_POID'] = '/account'
data = f.asdict()  # serialize from flist to dict
f2 = c2.flist(data)  # deserialize from dict to flistMiscellaneousTo get thepin_virtual_time:import pybrm
print(pybrm.pin_virtual_time())If you want to setpin_virtual_time, you can create a function like the following:import subprocess
from datetime import datetime

def set_pin_virtual_time(time, mode='2'):
    mode = str(mode)
    assert mode in ('0', '1', '2')
    if isinstance(time, datetime):
        time = time.strftime('%m%d%H%M%Y.%S')
    args = ["pin_virtual_time", "-m", mode]
    if mode != "0":
        args.append(time)
    subprocess.run(args)Please note that this should not ever be run in prod, which is why it is not built into the library.You can also create an flist from a BRM formatted flist string:f = c.flist('''
    # number of field entries allocated 20, used 8
    0 PIN_FLD_POID           POID [0] 0.0.0.1 /account -1 0
    0 PIN_FLD_STATUS         ENUM [0] 1
    0 PIN_FLD_CREATED_T    TSTAMP [0] (1582071028) Tue Feb 18 16:10:28 2020
    0 PIN_FLD_INHERITED_INFO SUBSTRUCT [0] allocated 20, used 2
    1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 0
    1     PIN_FLD_STATUS         ENUM [0] 2
    0 PIN_FLD_EVENT        SUBSTRUCT [0] allocated 20, used 2
    1     PIN_FLD_POID           POID [0] 0.0.0.1 /service 1234 1
    1     PIN_FLD_STATUS         ENUM [0] 3
    0 PIN_FLD_ARGS          ARRAY [4] allocated 20, used 1
    1     PIN_FLD_STATUS         ENUM [0] 4
    0 PIN_FLD_ARGS          ARRAY [16] allocated 20, used 1
    1     PIN_FLD_STATUS         ENUM [0] 5
    0 PIN_FLD_VALUES        ARRAY [0] allocated 20, used 1
    1     PIN_FLD_STATUS         ENUM [0] 6
    0 PIN_FLD_VALUES        ARRAY [1] allocated 20, used 1
    1     PIN_FLD_STATUS         ENUM [0] 7
''')Additionally, you can create serialize an flist to XML, and create an flist via XML:xml = f.xml()
f2 = c.flist(xml)Likewise you can serialize an flist to JSON, and create an flist from JSONj = f.json()
f2 = c.flist(j)Python3 InstallationIt is easiest to install Miniconda, which contains precompiled Python binaries for both 32 and 64bit.32 bit (Python 3.7):wget https://repo.anaconda.com/miniconda/Miniconda3-4.5.12-Linux-x86.sh64 bit (Python 3.7):wget https://repo.anaconda.com/miniconda/Miniconda3-4.7.12.1-Linux-x86_64.shTo install it, chose a path you want it to be installed on, like/path/to/miniconda3sh Miniconda3-4.5.12-Linux-x86.sh -b -p /path/to/miniconda3
rm Miniconda3-4.5.12-Linux-x86.shYou can alternatively build Python from source which will take up less space than Miniconda.Run the testsThe tests require a valid CM up and running. It only callsPCM_OP_TEST_LOOPBACK.cd into the tests directory, and edit pin.conf. Change this line to point to the correct CM:- nap cm_ptr ip ${HOSTNAME} ${BRM_CM_PORT}Additionally, cd into thepin_conf_testdirectory, and do the same for the pin.conf there.After this, run the tests from inside thetests/directory like:python3 tests.pypin.confApin.conffile has to be created in the same directory you run Python fromAt a minimum it needs these lines:- nap cm_ptr ip ${HOSTNAME} ${BRM_CM_PORT}
- nap login_type 1
- nap login_name root.0.0.0.1
- nap login_pw   password
- - userid 0.0.0.1 /service/pcm_client 1You should also include this:- - pin_virtual_time ${PIN_HOME}/lib/pin_virtual_time_file
 - - ops_fields_extension_file ${PIN_HOME}/include/custom_ops_dat.datwherecustom_ops_dat.datpoints to your custom opcode file.Just like with normal BRM C code, you need this file in order to get access to custom fields and opcodes.Calling C from PythonCheck out the examples/call_c_from_python/ for an example of calling C from Python using ctypes.Calling Python from CCheck out the examples/call_python_from_c/ for an example of calling Python from C.