Upgrading to OpenDSSDirect.py v0.9+#

If you are using older OpenDSSDirect.py versions, some specifics changed in v0.9 are important to note. For general changes of the DSS engine, please see the changelog at dss-extensions/dss_capi

Basic usage of ODD.py can continue as in previous versions, and should mostly keep working out-of-the-box if you do not intend to use multiple DSS instances. There are also a couple of deprecation warnings introduced to signal some changes to the users.

  • Instead of using import opendssdirect as odd, we now recommend from opendssdirect import dss as odd. This will import a class instance, which exposes more features and is safer. For example, the main class (OpenDSSDirect) exposes the OpenDSSDirect.Text.Commands as the call operator – you can use odd('redirect some_script.dss'), even with multi-line strings.

  • The classes also handle a common mistake of new users, which is using OpenDSSDirect.py as DSS-Python or the COM API, accidentally replacing the functions/methods with the intended values instead. That is, something like from opendssdirect import dss as odd; odd.Text.Command = 'clear' now results in an error.

  • The old Iterator class is deprecated since all iterable classes should now support the native iterator protocol in Python. That is [load.kW() for load in odd.Loads] now works. The general behavior from OpenDSS still applies (a single object of a certain type must be active, etc.).

  • opendssdirect.run_command is also deprecated due to the lack of direct error handling. We noticed this was resulting in error being ignored by many users, especially new users.

  • Multiple DSS engines are now supported, including multi-threading in Python. This means that some old usage patterns are not recommended anymore, since the code moved from plain modules to classes in order to allow this kind of feature.

from opendssdirect import dss as odd_default

# When using multiple contexts, it's better avoid changing the 
# working directory of the process
odd_default.Basic.AllowChangeDir(False)

odd1 = odd_default.NewContext()
odd2 = odd_default.NewContext()

odd1('new circuit.circuit1')
odd2('new circuit.circuit2')

assert odd1.Circuit.Name() == 'circuit1'
assert odd2.Circuit.Name() == 'circuit2'
  • The package now has the option of returning NumPy arrays (like DSS-Python) instead of plain lists. As of v0.9, this is not enabled by default, but it could be in future versions (feedback is welcome). To enable it globally, you can set the environment variable OPENDSSDIRECT_PY_USE_NUMPY=1. To enable it on a per-instance base, you can pass prefer_lists=False to the constructor. For example, from one of the tests:

from opendssdirect.OpenDSSDirect import OpenDSSDirect
from numpy import ndarray

# NOTE: this constructors ALWAYS binds to the default DSS engine.
odd_np = OpenDSSDirect(prefer_lists=False)
# Use it normally
odd_np(f"Redirect '{PATH_TO_DSS}'")
assert isinstance(odd_np.Circuit.AllBusMagPu(), ndarray)

odd_lst = OpenDSSDirect(prefer_lists=True)
# Same global instance, we can just reuse the result
assert isinstance(odd_lst.Circuit.AllBusMagPu(), list)
  • Due to changes in the AltDSS/DSS C-API engine, the capitalization of the DSS properties was changed. OpenDSS is case insensitive, but a lot of third-party code assumes a certain capitalization. This is not recommended since OpenDSS already changed the capitalization in the past. Code that relied on the capitalization before should stop working, but we added a way to toggle the capitalization style through Settings.SetPropertyNameStyle, as in the snippet below. We’d recommend using this but updating any related code to not depend on the capitalization at all. Note that there is also DSSPropertyNameStyle.Lowercase which may simplify some comparisons.

from opendssdirect import dss as odd, enums as dss_enums
odd.Settings.SetPropertyNameStyle(dss_enums.DSSPropertyNameStyle.Legacy)

Architecture of version 0.9#

Previous versions of OpenDSSDirect.py only exposed the default OpenDSS engine instance, as that was the how the original OpenDSS worked. With just a single instance, modules used to fit nicely to map the interfaces:

flowchart LR A["AltDSS/DSS C-API\n(libdss_capi)"] ---|CFFI| B["DSS-Python\n(dss package)"] B --- C["OpenDSSDirect.py\n(opendssdirect package)"] C --- D[opendssdirect.ActiveCircuit mod.] C --- E[opendssdirect.Basic mod.] C --- F[opendssdirect.Bus mod.] C --- G[...] C --- H[opendssdirect.XYCurve mod.] C --- I[opendssdirect.YMatrix mod.] C --- J[opendssdirect.ZIP mod.]

To allow multiple instances and other desirable features like pythonic iteration, indexing, len(), etc., a new OpenDSSDirect class was introduced, and the original modules were replaced with instances of dedicated classes:

flowchart LR A["AltDSS/DSS C-API\n(libdss_capi)"] ---|CFFI| B["DSS-Python\n(dss package)"] B --- C1["OpenDSSDirect.py\n(opendssdirect package)"] C1 --- C["opendssdirect.dss\n(instance of OpenDSSDirect class)"] C --- D[ActiveCircuit attr.] C --- E[Basic attr.] C --- F[Bus attr.] C --- G[...] C --- H[XYCurve attr.] C --- I[YMatrix attr.] C --- J[ZIP attr.]

Finally, to provide better backwards compatibility, in case users imported separate modules directly, the methods bound to the default DSS engine instance/context are exposed in the original module organization.

flowchart LR A["AltDSS/DSS C-API\n(libdss_capi)"] ---|CFFI| B["DSS-Python\n(dss package)"] B --- C1["OpenDSSDirect.py\n(opendssdirect package)"] C1 --- C["opendssdirect.dss\n(instance of OpenDSSDirect class)"] C --- D[ActiveCircuit attr.] C --- E[Basic attr.] C --- F[Bus attr.] C --- G[...] C --- H[XYCurve attr.] C --- I[YMatrix attr.] C --- J[ZIP attr.] D -.- D2[opendssdirect.ActiveCircuit mod.] E -.- E2[opendssdirect.Basic mod.] F -.- F2[opendssdirect.Bus mod.] G -.- G2[...] H -.- H2[opendssdirect.XYCurve mod.] I -.- I2[opendssdirect.YMatrix mod.] J -.- J2[opendssdirect.ZIP mod.]

In the end, importing the default instance is enough to provide the new features:

from opendssdirect import dss

In the long term, the modules could be removed if the community does indeed migrate to the import above. Typically, replacing import opendssdirect as dss with from opendssdirect import dss is enough.

The design mirrors the basics of what was done in DSS-Python 0.12 (July 2022) when support for DSSContexts (multiple engines) was introduced, with the caveat that DSS-Python already used a class-based approach. After a few releases, the design seems fit and stable to port over to ODD.py. Besides the extras, especially opendssdirect.utils, ODD.py is effectively equal to DSS-Python using a different convention, that is, instead of using Python properties like DSS-Python (done to achieve drop-in compatibility with the official COM implementation), OpenDSSDirect.py uses function calls for setters and getters.

For backwards compatibility, and to respect users expectations, OpenDSSDirect.py doesn’t allow/recommend using the constructor of the main interface class directly. Instead, NewContext must be used.

Using OpenDSSDirect.py with DSS-Python and AltDSS-Python#

Two new functions were introduced to make it easier to map from one module to the other:

This is especially useful now that altdss exposes most of the classic API in a more pythonic approach, batches and more.

Note

See also DSS-Python and AltDSS-Python.

A multi-threading example#

An example of how to use multiple DSS instances and multi-threading, directly in Python, is available at Multiple DSS engines and multithreading. This would be recommended for most uses instead of the internal/classic Parallel interface.