Skip to content

Commit 936f14e

Browse files
committed
Pass command line arguments as dictionary instead of structure.
1 parent 67b6c3e commit 936f14e

File tree

4 files changed

+124
-37
lines changed

4 files changed

+124
-37
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ to add the `--user` flag.
5252

5353
## Running
5454

55+
### Standalone runs
56+
5557
To run your models under Cassandra you will first need to prepare a
5658
configuration file. Configuration files are written in the INI
5759
format. In this format, the file is divided into sections, the names
@@ -78,3 +80,38 @@ Depending on how your cluster is set up, you might have to include
7880
additional flags for `mpirun`, such as a hostfile giving the names of
7981
the hosts you will be running on. An example job script for systems
8082
using the Slurm resource manager is included in `extras/mptest.zsh`.
83+
84+
### Running from another python program
85+
86+
It isn't strictly necessary to run `cassandra_main.py` as a standalone
87+
script. You could instead run from another python program, or from a
88+
Jupyter notebook. Besides giving access to the interactive features
89+
of notebooks, running this way could allow you to import components
90+
(see "Adding New Models" below) that are stored in modules outside the
91+
Cassandra package. To do this, you will need to import
92+
`cassandra_main` as a module. If you want to add a new component, you
93+
will also need the `add_new_component` function from
94+
`cassandra.compfactory`.
95+
96+
Once you've imported the necessary modules, continue by adding your
97+
custom components, if any. Then, you will need to create the
98+
structure used as the argument to the `main()` function. In the
99+
standalone version, this structure is created by the `argparse` module
100+
from the command line arguments.
101+
102+
```python
103+
from cassandra.cassandra_main import main
104+
## If including a component from an external module, add these lines
105+
## too. (Make sure mycomp.py is in your python path!)
106+
from cassandra.compfactory import add_new_component
107+
import mycomp.py
108+
109+
```
110+
111+
112+
113+
## Adding New Models
114+
115+
Cassandra's interface to models is provided by objects called
116+
_components_. To add a model to the system, you have to create a
117+
component to run the model. Components

cassandra/cassandra_main.py

Lines changed: 50 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
import os
1919

2020

21-
def bootstrap_sp(argvals):
21+
def bootstrap_sp(args):
2222
"""
23-
Bootstrap the multithreaded (single processing) version of the
24-
calculation.
23+
Bootstrap the multithreaded (single processing) version of the calculation.
2524
26-
:param argvals: Command line arguments parsed by argparse.
25+
:param args: Dictionary of command line arguments parsed by argparse.
2726
:return: (component-list, capability-table)
2827
"""
2928

@@ -32,17 +31,17 @@ def bootstrap_sp(argvals):
3231
from cassandra.compfactory import create_component
3332

3433
# Configure logger
35-
if argvals.logdir is None:
36-
logging.basicConfig(stream=sys.stdout, level=argvals.loglvl)
34+
if args['logdir'] is None:
35+
logging.basicConfig(stream=sys.stdout, level=args['loglvl'])
3736
logging.info(f'This is Cassandra version {__version__}.')
3837
else:
39-
os.makedirs(argvals.logdir, exist_ok=True)
40-
logging.basicConfig(filename=f'{argvals.logdir}/cassandra.log',
41-
level=argvals.loglvl, filemode='w')
38+
os.makedirs(args['logdir'], exist_ok=True)
39+
logging.basicConfig(filename=f"{args['logdir']}/cassandra.log",
40+
level=args['loglvl'], filemode='w')
4241
# Write to screen the location of the logging output
43-
print(f'This is Cassandra version {__version__}. Output will be logged to {argvals.logdir}/cassandra.log')
42+
print(f"This is Cassandra version {__version__}. Output will be logged to {args['logdir']}/cassandra.log")
4443

45-
cfgfile_name = argvals.ctlfile
44+
cfgfile_name = args['ctlfile']
4645

4746
# initialize the structures that will receive the data we are
4847
# parsing from the file
@@ -68,24 +67,51 @@ def bootstrap_sp(argvals):
6867
# end of bootstrap_sp
6968

7069

71-
def main(argvals):
70+
def main(args):
71+
"""
72+
Cassandra main entry function.
73+
74+
:param args: Dictionary of command line arguments parsed by argparse.
75+
76+
This function starts up the Cassandra framework, selecting either the
77+
single-processing or multi-processing mode, as required. It's set up to be
78+
run from the script block at the end of the module, but it could be called
79+
from another program if desired. To do that you will need to set up a
80+
dictionary simulating the command-line parameters from the stand-alone
81+
version. The parameters that need to be supplied are:
82+
ctlfile : Name of the configuration ("control") file.
83+
mp : Flag indicating whether we are running in MP mode
84+
logdir : Directory for log files. In SP mode this can be None, in
85+
which case log outputs go to stdout
86+
verbose : Flag indicating whether to produce debugging output.
87+
quiet : Flag indicating whether to suppress output except for warnings
88+
and error messages
89+
90+
Keep in mind that this function will throw an exception if any of the
91+
components fail (whether by exception or by returning a failure code). It's
92+
up to whatever code is calling it to handle any cleanup associated with the
93+
failure. This is especially important if you are running in MP mode, as
94+
failing to do this cleanup can hang the entire calculation.
95+
96+
"""
97+
7298

7399
# Set the appropriate logging level
74100
# NB: You MUST NOT call any logging functions until either bootstrap_mp
75101
# or bootstrap_sp has been called.
76-
if argvals.verbose:
77-
argvals.loglvl = logging.DEBUG
78-
elif argvals.quiet:
79-
argvals.loglvl = logging.WARNING
102+
if args['verbose']:
103+
args['loglvl'] = logging.DEBUG
104+
elif args['quiet']:
105+
args['loglvl'] = logging.WARNING
80106
else:
81-
argvals.loglvl = logging.INFO
107+
args['loglvl'] = logging.INFO
82108

83-
if argvals.mp:
109+
if args['mp']:
84110
# See notes in mp.py about side effects of importing that module.
85111
from cassandra.mp import bootstrap_mp, finalize
86-
(component_list, cap_table) = bootstrap_mp(argvals)
112+
(component_list, cap_table) = bootstrap_mp(args)
87113
else:
88-
(component_list, cap_table) = bootstrap_sp(argvals)
114+
(component_list, cap_table) = bootstrap_sp(args)
89115

90116
# We will look up "general" in the cap_table and process any
91117
# global parameters here, but in the current version we don't
@@ -99,7 +125,7 @@ def main(argvals):
99125

100126
# Wait for all component threads to complete before printing end
101127
# message.
102-
if argvals.mp:
128+
if args['mp']:
103129
# The RAB thread will always be first in the thread list
104130
component_threads = threads[1:]
105131
else:
@@ -114,7 +140,7 @@ def main(argvals):
114140
# if the RAB is present, it is always the first in the list.
115141
nfail = 0
116142

117-
if argvals.mp:
143+
if args['mp']:
118144
reg_comps = component_list[1:]
119145
rab_comp = component_list[0]
120146
else:
@@ -140,7 +166,7 @@ def main(argvals):
140166

141167
# If this is a multiprocessing calculation, then we need to
142168
# perform the finalization procedure
143-
if argvals.mp:
169+
if args['mp']:
144170
finalize(component_list[0], threads[0])
145171

146172
logging.info("\nFIN.")
@@ -163,7 +189,7 @@ def main(argvals):
163189

164190
try:
165191
# status is the number of component failures.
166-
status = main(argvals)
192+
status = main(vars(argvals))
167193
except Exception as err:
168194
if argvals.mp:
169195
from mpi4py import MPI

cassandra/compfactory.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,24 @@ def create_component(compname, cap_tbl):
3636
raise RuntimeError(f'Unknown component type {compname}')
3737

3838
return _available_components[compname](cap_tbl)
39+
40+
def add_new_component(compname, classobj):
41+
"""Add a new type of component to the list of available components.
42+
43+
:param compname: Name by which the new component will be known in
44+
configuration files.
45+
:param classobj: The class object for the class implementing the
46+
component. The class MUST be a subclass of ComponentBase.
47+
48+
Calling this function will add the requested component to the available
49+
components table, allowing it to be parsed from config files and instantiated
50+
by the system. The class passed in as class object must be a subclass of
51+
ComponentBase. The class inheritance isn't checked, so passing some random class
52+
might appear to work, or it might work in actuality, but nothing is guaranteed.
53+
54+
"""
55+
56+
global _available_components
57+
58+
_available_components[compname] = classobj
59+

cassandra/mp.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,41 @@
3636
import os
3737

3838

39-
def bootstrap_mp(argvals):
39+
def bootstrap_mp(args):
4040
"""Bootstrap the multiprocessing system.
4141
42+
:param args: Dictionary of arguments parsed from the command line.
43+
4244
1. Parse configuration files
4345
2. Distribute configuration information to peers
4446
3. Create RABs
4547
4. Create components
4648
5. Add remote capabilities to capability table
47-
6. Return component list (including RAB). The main loop will be able to
48-
start these components in the usual way.
49+
6. Return component list (including the RAB first in the list). The main loop
50+
will be able to start these components in the usual way.
51+
4952
"""
5053

5154
world = MPI.COMM_WORLD
5255
rank = world.Get_rank()
5356

54-
if argvals.logdir is None:
57+
if args['logdir'] is None:
5558
logdir = 'logs'
5659
else:
57-
logdir = argvals.logdir
60+
logdir = args['logdir']
5861

5962
os.makedirs(logdir, exist_ok=True)
6063

61-
logging.basicConfig(filename=f'{logdir}/cassandra-{rank}.log', level=argvals.loglvl,
64+
logging.basicConfig(filename=f'{logdir}/cassandra-{rank}.log', level=args['loglvl'],
6265
filemode='w')
6366

6467
if rank == SUPERVISOR_RANK:
6568
# Print the location of the logs so that it will appear in the
6669
# output file of batch jobs.
6770
print(f'\nThis is Cassandra version {__version__}. Log output will be written to {logdir}.')
68-
my_assignment = distribute_assignments_supervisor(argvals)
71+
my_assignment = distribute_assignments_supervisor(args)
6972
else:
70-
my_assignment = distribute_assignments_worker(argvals)
73+
my_assignment = distribute_assignments_worker(args)
7174

7275
# my_assignment will be a dictionary of configuration sections assigned to
7376
# this process. We need to create and initialize the components assigned to
@@ -104,10 +107,10 @@ def bootstrap_mp(argvals):
104107
return (comps, cap_tbl)
105108

106109

107-
def distribute_assignments_worker(argvals):
110+
def distribute_assignments_worker(args):
108111
"""Prepare to receive component assignments
109112
110-
:param argvals: Arguments structure parsed by argparse. Not currently used,
113+
:param args: Dictionary of arguments parsed by argparse. Not currently used,
111114
but included in case we eventually want to have command line
112115
arguments that affect the way assignments are handled.
113116
:return: Dictionary of component definitions for components assigned to this
@@ -125,10 +128,10 @@ def distribute_assignments_worker(argvals):
125128
return assignments
126129

127130

128-
def distribute_assignments_supervisor(argvals):
131+
def distribute_assignments_supervisor(args):
129132
"""Parse config file and distribute component assignments
130133
131-
:param argvals: Arguments structure parsed by argparse.
134+
:param args: Dictionary of arguments parsed by argparse.
132135
:return: Dictionary of component definitions for components assigned to this
133136
process (i.e., the supervisor).
134137
@@ -150,7 +153,7 @@ def distribute_assignments_supervisor(argvals):
150153

151154
from configobj import ConfigObj
152155

153-
config = ConfigObj(argvals.ctlfile)
156+
config = ConfigObj(args['ctlfile'])
154157

155158
# Get list of section names
156159
section_names = list(config.keys())

0 commit comments

Comments
 (0)