Program Setup
As Python is an interpreted language, configuration of an application can be carried out by editing the actual scripts. In some circumstances however, it is still easier or more practical to rely upon a configuration database or environment variables. When using OSE this is especially the case, as an application can be a mix of C++ and Python code and configuration data may need to be accessible from code written in both languages.
To support this the Python wrappers provide an interface to the configuration database of the OSE C++ class library. The corresponding class in the OSE C++ class library which provides this functionality is the "OTC_Program" class. Not all functionality of this class is mirrored in the Python interface as Python has its own way of doing most of what is provided by this class. Access is however provided to aspects of the configuration database and environment variable database. The functionality for generating unique identifiers is also exposed.
Configuration Database
The configuration database is an in memory database. The database may be populated by calls from within the application, or by loading in a configuration file. The configuration database may also be saved to a file. In essence, the configuration database is not much more than a dictionary mapping names to values.
To initially load the configuration database from a file, the "loadConfig()" function is used. A single configuration item may be explicitly merged into the configuration database using the "mergeConfig()" function. A query can subsequently be made against the configuration database using the "lookupConfig()" function. If no match is found in the configuration database for the item in question, the value "None" is returned.
1 import netsvc
2 import os
3
4 netsvc.loadConfig("database.cfg")
5 netsvc.mergeConfig("PWD", os.getcwd())
6
7 print netsvc.lookupConfig("PWD")
A single configuration item can be removed from the database using the "removeConfig()" function. The configuration database can be completely emptied using the function "removeAllConfig()". The contents of the configuration database can be saved to a file using the "saveConfig()" function.
1 netsvc.removeConfig("PWD")
2 netsvc.saveConfig("database.cfg")
Configuration File
The only real restrictions in regard to naming is that the colon character should not be used anywhere in a name, a name should not being with an exclamation mark and whitespace should not be used at the start or end of a name. The colon character cannot be used as it is used in a configuration file to separate the name from the value. A leading exclamation mark should not be used as it is used to denote a comment.
If these characters are used in a name and the configuration database is saved to a file, the results when that configuration file is read back in will not be the same. The only other special character when used in a configuration file is a back slash, which when used at the end of the line, indicates the following line is part of the same value. Note that the leading whitespace and the whitespace either side of the colon will be ignored when the configuration file is read in.
! comment
single-line-value : value
multi-line-value : value\
value
When reading in a configuration file using "loadConfig()", an exception is raised only if the file doesn't exist or the file couldn't be opened. If there are no errors in the file, the value "None" is returned. If there are errors in the file, a string is returned which contains details of the errors and what action has been taken. By default, the details of the errors are also output via the logging system on the default log channel.
If details of any errors should be output on a specific log channel, an optional second argument can be supplied to the "loadConfig()" function giving the name of the log channel. If the value "None" is supplied in place of the name of the log channel, the details of the errors will not be output via the logger at all. The value "None" could be used if you wish to amend the details of the errors before they are logged.
1 file = "database.cfg"
2 errors = netsvc.loadConfig(file, None)
3 if errors:
4 errors = "Error reading %s\n%s" % (`file`, errors)
5 netsvc.Logger().notify(netsvc.LOG_DEBUG,errors)
Naming Hierarchies
If a naming hierarchy is required, the components of the hierarchy within the name should be separated by using a period.
compiler.preprocessor.debug-level : 0 compiler.parser.debug-level : 1 compiler.code-generator.debug-level : 0 compiler.assembler.debug-level : 0
In general, the purpose of using a naming hierarchy is to associate properties with the same name with different parts of an application, or with different instances of some object. To cater for default values, rather than enumerating all possible objects, a wildcard can be specified in place of a single component in a naming hierarchy. This says to match any component name in this position. Only those items which need to be different then need to be explicitly specified.
compiler.*.debug-level : 0 compiler.parser.debug-level : 1
When a lookup is made against the database, a check is first made for any entry which matches exactly the name of interest. If this name is not present, a search is then made of the entries containing a wildcard. If a match is found, the value associated with the wildcard entry will be returned. If there are multiple wildcard entries which match a lookup against the configuration database, that which has the longest leading exact match will be used.
Environment Variables
In addition to the configuration database, an interface is also provided to the standard operating system environment variables. Python does already provide an interface for this, however the Python interface does have a few quirks which can sometimes make it less than useful.
One problem with the standard Python interface is that when "os.putenv()" is used to set an environment variable, that variable is not then visible using "os.getenv()". This is because "os.getenv()" uses "os.environ", which is a copy of the environment which is populated at startup and any changes to environment variables are not reflected in that copy.
As such, although changes to the environment will be seen by subprocesses, they will not be visible in the same process. This means that an environment variable can't at the same time be used to transfer information to a different part of the application.
To lookup the value of an environment variable the function "lookupEnviron()" is used. If a new environment variable needs to be set, or an existing value changed, the function "mergeEnviron()" is used. Any changes to the environment variables will be visible immediately, but there is no way to get a list of all environment variables which are set. When a lookup is made but no such environment variable exists, the value "None" is returned.
1 netsvc.mergeEnviron("PWD", os.getcwd())
2 print netsvc.lookupEnviron("PWD")
In addition to these functions, the function "expandEnviron()" is provided. This function accepts a string and replaces any reference to an environment variable specified using Bourne shell syntax, with that environment variables actual value. The intent in providing this function is that it can be used in conjunction with the configuration database, allowing configuration items to refer to environment variables.
application.log-files : ${HOME}/logs
Note that the expansion isn't automatic when a lookup is made against the configuration database. The application code will have to explicitly expand the value obtained form the configuration database.
1 value = netsvc.lookupConfig("application.log-files")
2 directory = netsvc.expandEnviron(value)
Unique Identifiers
In many applications, it is often useful to be able to create abstract identifiers to uniquely identify objects or resources. These might be used to identify user sessions in a web based application, specific requests in a distributed messaging system, or even the particular service agent which a request in a distributed messaging system is targeted at.
Such identifiers may only need to be unique within the context of the lifetime of the application, or possibly may need to be globally unique. In the case of the latter, to be rigourous this would normally require an external database to be maintained which tracks what identifiers have been used. In most cases however, it is not necessary to go to that extent and a simplistic means can be used to generate a psuedo unique identifier which is sufficient.
To generate such identifiers the function "uniqueId()" is provided. The function can provide identifiers in either a short or long format. In the short format, the identifier contains components which identify the host on which the process is running, the process id and an incremental counter. In the long format, time values are also included which tie the identifier to an instant in time.
1 id1 = netsvc.uniqueId(netsvc.ID_SHORT_FORMAT)
2 id2 = netsvc.uniqueId(netsvc.ID_LONG_FORMAT)
If you wish to incorporate your own prefix into the identifier, an optional second argument can be supplied to the "uniqueId()" function.
1 id1 = netsvc.uniqueId(netsvc.ID_SHORT_FORMAT, "$SID?")
The short format identifier is suitable for use within the context of a single process. Duplicates would only be encounterd if the incremental count of the number of identifiers exceeded what can be stored within a 32 bit integer value. If this were to occur, the counter would wrap around to zero and conflicts might thus arise if the existing identifier were still active.
The short format identifier could also be used within the context of a constrained distributed application provided that the nature of the application is such that knowlege of what the identifier is associated with is always discarded when the process the identifier is bound to is destroyed. This would be necessary, as the identifier could be reused if the process id was reused at some latter point.
If a better gaurantee of uniqueness over time is required, the long format identifier should be used. In this case, the identifier also records the time at which the first identifier was generated by the process, as well as a time delta as to when that particular identifier was generated. Incorporation of time information avoids problems with the incremental counter overflowing and reuse of the same process id at a latter point in time.
Process Identity
A feature which is useful in distributed applications is a way of identifying specific processes. Such an identifier can be generated by combining the name of the host and the process id into a single string. To facilitate this, the function "processIdentity()" is provided.
1 identity = netsvc.processIdentity()
Threading Support
By default, threading support would be compiled into the OSE C++ class library when being used by the Python wrappers. If there is a need to validate that this is the case then the "threadingEnabled()" function is provided.
1 if netsvc.threadingEnabled():
2 ...
