Skip to content

Commit a65c13a

Browse files
committed
Implement HSD-wrappers to manipulate nested content
1 parent 0d97116 commit a65c13a

File tree

9 files changed

+815
-22
lines changed

9 files changed

+815
-22
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
*~
22
.idea
3+
.env
4+
.vscode
35
*.pyc
46
dist
57
build

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
# a list of builtin themes.
5757
#
5858
# html_theme = 'alabaster'
59-
html_theme = 'sphinx_rtd_theme'
59+
html_theme = 'sphinx_book_theme'
6060

6161
# Add any paths that contain custom static files (such as style sheets) here,
6262
# relative to this directory. They are copied after the builtin static files,

docs/introduction.rst

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ or into the user space issueing ::
3636
Quick tutorial
3737
==============
3838

39+
The basics
40+
----------
41+
3942
A typical, self-explaining input written in HSD looks like ::
4043

4144
driver {
@@ -117,3 +120,81 @@ Python ::
117120
and then stored again in HSD format ::
118121

119122
hsd.dump(hsdinput, "test2.hsd")
123+
124+
125+
126+
Accesing nested data structures via wrappers
127+
--------------------------------------------
128+
129+
The hsd module contains lightweight wrappers (``HsdDict``, ``HsdList`` and
130+
``HsdValue``), which offer convenient access to entries in nested data
131+
structures. With the help of these wrappers, nested nodes and values can be
132+
directly accessed using paths. When accessing nested content via wrappers, the
133+
resulting objects will be wrappers themself, wrapping the appropriate parts of
134+
the data structure (and inheriting certain properties of the original wrapper).
135+
136+
For example, reading and wrapping the example above::
137+
138+
import hsd
139+
hsdinp = hsd.wrap(hsd.load("test.hsd"))
140+
141+
creates an ``HsdDict`` wrapper instance (``hsdinp``), which can be used to query
142+
encapsulated information in the structure::
143+
144+
# Reading out the value directly (100)
145+
maxsteps = hsdinp["driver", "conjugate_gradients", "max_steps"].value
146+
147+
# Storing wrapper (HsdValue) instance and reading out value and the attribute
148+
temp = hsdinp["hamiltonian / dftb / filling / fermi / temperature"]
149+
temp_value = temp.value
150+
temp_unit = temp.attrib
151+
152+
# Getting a default value, if a given path does not exists:
153+
pot = hsdinp.get_item("hamiltonian / dftb / bias", default=hsd.HsdValue(100, attrib="V"))
154+
155+
# Setting a value for given path by creating missing parents
156+
hsdinp.set_item("analysis / calculate_forces", True, parents=True)
157+
158+
As demonstrated above, paths can be specified as tuples or as slash (``/``) joined strings.
159+
160+
The wrappers also support case-insensitive access. Let's have a look at a
161+
mixed-case example file ``test2.hsd``::
162+
163+
Driver {
164+
ConjugateGradients {
165+
MovedAtoms = 1 2 "7:19"
166+
MaxSteps = 100
167+
}
168+
169+
We now make copy of the data structure before wrapping it, and make sure that
170+
all keys are converted to lower case, but the original names are saved as
171+
HSD-attributes::
172+
173+
hsdinp = hsd.copy(hsd.load("test2.hsd"), lower_names=True, save_names=True)
174+
175+
This way, paths passed to the Hsd-wrapper are treated in a case-insensitive
176+
way::
177+
178+
maxsteps = hsdinp["driver", "CONJUGATEGRADIENTS", "MAXSTEPS"].value
179+
180+
When adding new items, the access is and remains case in-sensitive, but the
181+
actual form of the name of the new node will be saved. The code snippet::
182+
183+
hsdinp["driver", "conjugategradients", "MaxForce"] = hsd.HsdValue(1e-4, attrib="au")
184+
maxforceval = hsdinp["driver", "conjugategradients", "maxforce"]
185+
print(f"{maxforceval.value} {maxforceval.attrib}")
186+
print(hsd.dump_string(hsdinp.value, use_hsd_attribs=True))
187+
188+
will result in ::
189+
190+
0.0001 au
191+
Driver {
192+
ConjugateGradients {
193+
MovedAtoms = 1 2 "7:19"
194+
MaxSteps = 100
195+
MaxForce [au] = 0.0001
196+
}
197+
}
198+
199+
where the case-convention for ``MaxForce`` is identical to the one used when the
200+
item was created.

src/hsd/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@
77
"""
88
Toolbox for reading, writing and manipulating HSD-data.
99
"""
10-
from hsd.common import HSD_ATTRIB_LINE, HSD_ATTRIB_EQUAL, HSD_ATTRIB_SUFFIX,\
11-
HSD_ATTRIB_NAME, HsdError
12-
from hsd.dict import HsdDictBuilder, HsdDictWalker
10+
from hsd.common import HSD_ATTRIB_LINE, HSD_ATTRIB_EQUAL, HSD_ATTRIB_NAME, HsdError
11+
from hsd.dict import ATTRIB_KEY_SUFFIX, HSD_ATTRIB_KEY_SUFFIX, HsdDictBuilder, HsdDictWalker
1312
from hsd.eventhandler import HsdEventHandler, HsdEventPrinter
1413
from hsd.formatter import HsdFormatter
1514
from hsd.io import load, load_string, dump, dump_string
1615
from hsd.parser import HsdParser
16+
from hsd.wrappers import HsdDict, HsdList, HsdValue, copy, wrap
1717

1818
__version__ = '0.1'

src/hsd/common.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ def unquote(txt):
2828
# Name for default attribute (when attribute name is not specified)
2929
DEFAULT_ATTRIBUTE = "unit"
3030

31-
# Suffix to mark attribute
32-
ATTRIB_SUFFIX = ".attrib"
33-
34-
# Suffix to mark hsd processing attributes
35-
HSD_ATTRIB_SUFFIX = ".hsdattrib"
36-
3731
# HSD attribute containing the original tag name
3832
HSD_ATTRIB_NAME = "name"
3933

src/hsd/dict.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,17 @@
99
"""
1010
import re
1111
from typing import List, Tuple, Union
12-
from hsd.common import HSD_ATTRIB_NAME, np, ATTRIB_SUFFIX, HSD_ATTRIB_SUFFIX, HsdError,\
13-
QUOTING_CHARS, SPECIAL_CHARS
12+
from hsd.common import HSD_ATTRIB_NAME, np, HsdError, QUOTING_CHARS, SPECIAL_CHARS
1413
from hsd.eventhandler import HsdEventHandler, HsdEventPrinter
1514

15+
16+
# Dictionary key suffix to mark attribute
17+
ATTRIB_KEY_SUFFIX = ".attrib"
18+
19+
# Dictionary keysuffix to mark hsd processing attributes
20+
HSD_ATTRIB_KEY_SUFFIX = ".hsdattrib"
21+
22+
1623
_ItemType = Union[float, complex, int, bool, str]
1724

1825
_DataType = Union[_ItemType, List[_ItemType]]
@@ -130,26 +137,26 @@ def close_tag(self, tagname):
130137
parentblock[key] = [{None: prevcont}, self._curblock]
131138

132139
if attrib and prevcont is None:
133-
parentblock[key + ATTRIB_SUFFIX] = attrib
140+
parentblock[key + ATTRIB_KEY_SUFFIX] = attrib
134141
elif prevcont is not None:
135-
prevattrib = parentblock.get(key + ATTRIB_SUFFIX)
142+
prevattrib = parentblock.get(key + ATTRIB_KEY_SUFFIX)
136143
if isinstance(prevattrib, list):
137144
prevattrib.append(attrib)
138145
else:
139-
parentblock[key + ATTRIB_SUFFIX] = [prevattrib, attrib]
146+
parentblock[key + ATTRIB_KEY_SUFFIX] = [prevattrib, attrib]
140147

141148
if self._include_hsd_attribs:
142149
if self._lower_tag_names:
143150
hsdattrib = {} if hsdattrib is None else hsdattrib
144151
hsdattrib[HSD_ATTRIB_NAME] = tagname
145152
if prevcont is None:
146-
parentblock[key + HSD_ATTRIB_SUFFIX] = hsdattrib
153+
parentblock[key + HSD_ATTRIB_KEY_SUFFIX] = hsdattrib
147154
else:
148-
prevhsdattrib = parentblock.get(key + HSD_ATTRIB_SUFFIX)
155+
prevhsdattrib = parentblock.get(key + HSD_ATTRIB_KEY_SUFFIX)
149156
if isinstance(prevhsdattrib, list):
150157
prevhsdattrib.append(hsdattrib)
151158
else:
152-
parentblock[key + HSD_ATTRIB_SUFFIX] = [prevhsdattrib, hsdattrib]
159+
parentblock[key + HSD_ATTRIB_KEY_SUFFIX] = [prevhsdattrib, hsdattrib]
153160
self._curblock = parentblock
154161
self._data = None
155162

@@ -219,11 +226,11 @@ def walk(self, dictobj):
219226

220227
for key, value in dictobj.items():
221228

222-
if key.endswith(ATTRIB_SUFFIX) or key.endswith(HSD_ATTRIB_SUFFIX):
229+
if key.endswith(ATTRIB_KEY_SUFFIX) or key.endswith(HSD_ATTRIB_KEY_SUFFIX):
223230
continue
224231

225-
hsdattrib = dictobj.get(key + HSD_ATTRIB_SUFFIX)
226-
attrib = dictobj.get(key + ATTRIB_SUFFIX)
232+
hsdattrib = dictobj.get(key + HSD_ATTRIB_KEY_SUFFIX)
233+
attrib = dictobj.get(key + ATTRIB_KEY_SUFFIX)
227234

228235
if isinstance(value, dict):
229236

src/hsd/io.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"""
77
Provides functionality to dump Python structures to HSD
88
"""
9+
from collections.abc import Mapping
910
import io
1011
from typing import Union, TextIO
1112
from hsd.dict import HsdDictWalker, HsdDictBuilder
@@ -155,7 +156,7 @@ def dump(data: dict, hsdfile: Union[TextIO, str],
155156
156157
See :func:`hsd.load_string` for an example.
157158
"""
158-
if not isinstance(data, dict):
159+
if not isinstance(data, Mapping):
159160
msg = "Invalid object type"
160161
raise TypeError(msg)
161162
if isinstance(hsdfile, str):

0 commit comments

Comments
 (0)