Convert Data to NestedText

nestedtext.dumps(obj, *, width=0, inline_level=0, sort_keys=False, indent=4, converters=None, map_keys=None, default=None, dialect=None)[source]

Recursively convert object to NestedText string.

Parameters:
  • obj – The object to convert to NestedText.

  • width (int) – Enables inline lists and dictionaries if greater than zero and if resulting line would be less than or equal to given width.

  • inline_level (int) – Recursion depth must be equal to this value or greater to be eligible for inlining.

  • sort_keys (bool or func) –

    Dictionary items are sorted by their key if sort_keys is True. In this case, keys at all level are sorted alphabetically. If sort_keys is False, the natural order of dictionaries is retained.

    If a function is passed in, it is expected to return the sort key. The function is passed two tuples, each consists only of strings. The first contains the mapped key, the original key, and the rendered item. So it takes the form:

    ('<mapped_key>', '<orig_key>', '<mapped_key>: <value>')
    

    The second contains the keys of the parent.

  • indent (int) – The number of spaces to use to represent a single level of indentation. Must be one or greater.

  • converters (dict) –

    A dictionary where the keys are types and the values are converter functions (functions that take an object and return it in a form that can be processed by NestedText). If a value is False, an unsupported type error is raised.

    An object may provide its own converter by defining the __nestedtext_converter__ attribute. It may be False, or it may be a method that converts the object to a supported data type for NestedText. A matching converter specified in the converters argument dominates over this attribute.

  • default (func or “strict”) – The default converter. Use to convert otherwise unrecognized objects to a form that can be processed. If not provided an error will be raised for unsupported data types. Typical values are repr or str. If “strict” is specified then only dictionaries, lists, strings, and those types that have converters are allowed. If default is not specified then a broader collection of value types are supported, including None, bool, int, float, and list- and dict-like objects. In this case Booleans are rendered as “True” and “False” and None is rendered as an empty string. If default is a function, it acts as the default converter. If it raises a TypeError, the value is reported as an unsupported type.

  • map_keys (func or keymap) –

    This argument is used to modify the way keys are rendered. It may be a keymap that was created by load() or loads(), in which case keys are rendered into their original form, before any normalization or de-duplication was performed by the load functions.

    It may also be a function that takes two arguments: the key after any needed conversion has been performed, and the tuple of parent keys. The value returned is used as the key and so must be a string. If no value is returned, the key is not modified.

  • dialect (str) –

    Specifies support for particular variations in NestedText.

    In general you are discouraged from using a dialect as it can result in NestedText documents that are not compliant with the standard.

    The following deviant dialects are supported.

    support inlines:

    If “i” is included in dialect, support for inline lists and dictionaries is dropped. The default is “I”, which enables support for inlines. The main effect of disabling inlines in the dump functions is that empty lists and dictionaries are output using an empty value, which is normally interpreted by NestedText as an empty string.

Returns:

The NestedText content without a trailing newline. NestedText files should end with a newline, but unlike dump(), this function does not output that newline. You will need to explicitly add that newline when writing the output dumps() to a file.

Raises:

NestedTextError – if there is a problem in the input data.

Examples

>>> import nestedtext as nt

>>> data = {
...     "name": "Kristel Templeton",
...     "gender": "female",
...     "age": "74",
... }

>>> try:
...     print(nt.dumps(data))
... except nt.NestedTextError as e:
...     print(str(e))
name: Kristel Templeton
gender: female
age: 74

The NestedText format only supports dictionaries, lists, and strings. By default, dumps is configured to be rather forgiving, so it will render many of the base Python data types, such as None, bool, int, float and list-like types such as tuple and set by converting them to the types supported by the format. This implies that a round trip through dumps and loads could result in the types of values being transformed. You can restrict dumps to only supporting the native types of NestedText by passing default=”strict” to dumps. Doing so means that values that are not dictionaries, lists, or strings generate exceptions.

>>> data = {"key": 42, "value": 3.1415926, "valid": True}

>>> try:
...     print(nt.dumps(data))
... except nt.NestedTextError as e:
...     print(str(e))
key: 42
value: 3.1415926
valid: True

>>> try:
...     print(nt.dumps(data, default="strict"))
... except nt.NestedTextError as e:
...     print(str(e))
key: unsupported type (int).

Alternatively, you can specify a function to default, which is used to convert values to recognized types. It is used if no suitable converter is available. Typical values are str and repr.

>>> class Color:
...     def __init__(self, color):
...         self.color = color
...     def __repr__(self):
...         return f"Color({self.color!r})"
...     def __str__(self):
...         return self.color

>>> data["house"] = Color("red")
>>> print(nt.dumps(data, default=repr))
key: 42
value: 3.1415926
valid: True
house: Color('red')

>>> print(nt.dumps(data, default=str))
key: 42
value: 3.1415926
valid: True
house: red

If Color is consistently used with NestedText, you can include the converter in Color itself.

>>> class Color:
...     def __init__(self, color):
...         self.color = color
...     def __nestedtext_converter__(self):
...         return self.color.title()

>>> data["house"] = Color("red")
>>> print(nt.dumps(data))
key: 42
value: 3.1415926
valid: True
house: Red

You can also specify a dictionary of converters. The dictionary maps the object type to a converter function.

>>> class Info:
...     def __init__(self, **kwargs):
...         self.__dict__ = kwargs

>>> converters = {
...     bool: lambda b: "yes" if b else "no",
...     int: hex,
...     float: lambda f: f"{f:0.3}",
...     Color: lambda c: c.color,
...     Info: lambda i: i.__dict__,
... }

>>> data["attributes"] = Info(readable=True, writable=False)

>>> try:
...    print(nt.dumps(data, converters=converters))
... except nt.NestedTextError as e:
...     print(str(e))
key: 0x2a
value: 3.14
valid: yes
house: red
attributes:
    readable: yes
    writable: no

The above example shows that Color in the converters argument dominates over the __nestedtest__converter__ class.

If the dictionary maps a type to None, then the default behavior is used for that type. If it maps to False, then an exception is raised.

>>> converters = {
...     bool: lambda b: "yes" if b else "no",
...     int: hex,
...     float: False,
...     Color: lambda c: c.color,
...     Info: lambda i: i.__dict__,
... }

>>> try:
...    print(nt.dumps(data, converters=converters))
... except nt.NestedTextError as e:
...     print(str(e))
value: unsupported type (float).

converters need not actually change the type of a value, it may simply transform the value. In the following example, converters is used to transform dictionaries by removing empty dictionary fields. It is also converts dates and physical quantities to strings.

>>> import arrow
>>> from inform import cull
>>> import quantiphy

>>> class Dollars(quantiphy.Quantity):
...     units = "$"
...     form = "fixed"
...     prec = 2
...     strip_zeros = False
...     show_commas = True

>>> converters = {
...     dict: cull,
...     arrow.Arrow: lambda d: d.format("D MMMM YYYY"),
...     quantiphy.Quantity: lambda q: str(q)
... }

>>> transaction = dict(
...     date = arrow.get("2013-05-07T22:19:11.363410-07:00"),
...     description = "Incoming wire from Publisher’s Clearing House",
...     debit = 0,
...     credit = Dollars(12345.67)
... )

>>> print(nt.dumps(transaction, converters=converters))
date: 7 May 2013
description: Incoming wire from Publisher’s Clearing House
credit: $12,345.67

Both default and converters may be used together. converters has priority over the built-in types and default. When a function is specified as default, it is always applied as a last resort.

Use the map_keys argument to format the keys as you wish. For example, you may wish to render the keys at the first level of hierarchy in upper case:

>>> def map_keys(key, parent_keys):
...     if len(parent_keys) == 0:
...         return key.upper()

>>> print(nt.dumps(transaction, converters=converters, map_keys=map_keys))
DATE: 7 May 2013
DESCRIPTION: Incoming wire from Publisher’s Clearing House
CREDIT: $12,345.67

It can also be used map the keys back to their original form when round-tripping a dataset when using key normalization or key de-duplication:

>>> content = """
... Michael Jordan:
...     occupation: basketball player
... Michael Jordan:
...     occupation: actor
... Michael Jordan:
...     occupation: football player
... """

>>> def de_dup(key, state):
...     if key not in state:
...         state[key] = 1
...     state[key] += 1
...     return f"{key}  ⟪#{state[key]}⟫"

>>> keymap = {}
>>> people = nt.loads(content, dict, on_dup=de_dup, keymap=keymap)
>>> print(nt.dumps(people))
Michael Jordan:
    occupation: basketball player
Michael Jordan  ⟪#2⟫:
    occupation: actor
Michael Jordan  ⟪#3⟫:
    occupation: football player

>>> print(nt.dumps(people, map_keys=keymap))
Michael Jordan:
    occupation: basketball player
Michael Jordan:
    occupation: actor
Michael Jordan:
    occupation: football player
nestedtext.dump(obj, dest, **kwargs)[source]

Write the NestedText representation of the given object to the given file.

Parameters:
  • obj – The object to convert to NestedText.

  • dest (str, os.PathLike, io.TextIOBase) –

    The file to write the NestedText content to. The file can be specified either as a path (e.g. a string or a pathlib.Path) or as a text IO instance (e.g. an open file, or 1 for stdout). If a path is given, the will be opened, written, and closed. If an IO object is given, it must have been opened in a mode that allows writing (e.g. open(path, "w")), if applicable. It will be written and not closed.

    The name used for the file is arbitrary but it is tradition to use a .nt suffix. If you also wish to further distinguish the file type by giving the schema, it is recommended that you use two suffixes, with the suffix that specifies the schema given first and .nt given last. For example: flicker.sig.nt.

  • kwargs – See dumps() for optional arguments.

Returns:

The NestedText content with a trailing newline. This differs from dumps(), which does not add the trailing newline.

Raises:
  • NestedTextError – if there is a problem in the input data.

  • OSError – if there is a problem opening the file.

Examples

This example writes to a pointer to an open file.

>>> import nestedtext as nt
>>> from inform import fatal, os_error

>>> data = {
...     "name": "Kristel Templeton",
...     "gender": "female",
...     "age": "74",
... }

>>> try:
...     with open("data.nt", "w", encoding="utf-8") as f:
...         nt.dump(data, f)
... except nt.NestedTextError as e:
...     e.terminate()
... except OSError as e:
...     fatal(os_error(e))

This example writes to a file specified by file name. In general, the file name and extension are arbitrary. However, by convention a ‘.nt’ suffix is generally used for NestedText files.

>>> try:
...     nt.dump(data, "data.nt")
... except nt.NestedTextError as e:
...     e.terminate()
... except OSError as e:
...     fatal(os_error(e))