Skip to content

Commit

Permalink
release 9.101.22008
Browse files Browse the repository at this point in the history
  • Loading branch information
klahnakoski committed Jan 8, 2022
2 parents 78eb207 + 5bae90b commit 8812e1b
Show file tree
Hide file tree
Showing 18 changed files with 222 additions and 165 deletions.
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
[![PyPI Latest Release](https://img.shields.io/pypi/v/mo-dots.svg)](https://pypi.org/project/mo-dots/)
[![Build Status](https://app.travis-ci.com/klahnakoski/mo-dots.svg?branch=master)](https://travis-ci.com/github/klahnakoski/mo-dots)
[![Coverage Status](https://coveralls.io/repos/github/klahnakoski/mo-dots/badge.svg?branch=dev)](https://coveralls.io/github/klahnakoski/mo-dots?branch=dev)
[![Downloads](https://pepy.tech/badge/mo-dots)](https://pepy.tech/project/mo-dots)

#### Changes in version 9.x.x

Escaping a literal dot (`.`) is no longer (`\\.`) rather double-dot ('..')

#### Changes in version 5.x.x

The `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input.


## Overview

Expand All @@ -14,11 +23,7 @@ This library defines a `Data` class that can serve as a replacement for `dict`,

*See the [full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs) for all the features of `mo-dots`*

### Changes in version 5.x.x

The `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input.

### Easy Definition
### Create instances

Define `Data` using named parameters, just like you would a `dict`

Expand Down
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ True</pre>
3. Accessing missing properties does not change the data; unlike `defaultdict`
4. Remove an attribute by assigning `None` (eg `a.b = None`)
5. Dot-separated path access: `a["b.c"] == a.b.c`.
* Refer to literal dot (`.`) by escaping with a backslash (`\\.`). Eg `to_data(a)['c\\.b'] == from_data(a)['c.b']`
* Use bell (`\b`) to refer to literal (`\\.`), which allows backslash to be last character in a key (`a['c\bd'] == a['c\\']['d']`)
* Use double-dot (`..`) to refer to literal dot (`.`) - Eg `to_data(a)['c..b'] == from_data(a)['c.b']`
* Alternativly, use bell (`\b`) to refer to literal dot (`.`) - this allows dots to be the last character in a key (`a['c\b.d'] == a['c.']['d']`)
6. you can set paths to values, missing dicts along the path are created:<pre>
&gt;&gt;&gt; a = to_data({})
a == {}
Expand Down
75 changes: 23 additions & 52 deletions mo_dots/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

from __future__ import absolute_import, division, unicode_literals

import re
import sys

from mo_future import (
binary_type,
generator_types,
is_binary,
is_text,
text,
OrderedDict,
none_type,
Expand Down Expand Up @@ -96,16 +96,24 @@ def is_missing(t):
else:
return t == None


def exists(value):
return not is_missing(value)


ESCAPE_DOTS1 = re.compile(r"(^\.|\.$)") # DOTS AT START/END
ESCAPE_DOTS2 = re.compile(r"(?<!^)\.(?!$)") # INTERNAL DOTS
ILLEGAL_DOTS = re.compile(r"[^.]\.(?:\.\.)+") # ODD DOTS ARE NOT ALLOWED
SPLIT_DOTS = re.compile(r"(?<!\.)\.(?!\.)") # SINGLE DOTS
UNESCAPE_DOTS = re.compile(r"\x08|(?:\.\.)") # ENCODED DOTS


def literal_field(field):
"""
RETURN SAME WITH DOTS (`.`) ESCAPED
"""
try:
return field.replace(".", "\\.")
return ESCAPE_DOTS2.sub("..", ESCAPE_DOTS1.sub("\b", field))
except Exception as e:
get_logger().error("bad literal", e)

Expand All @@ -120,9 +128,7 @@ def unliteral_field(field):
:param field: THE STRING TO DE-literal IZE
:return: SIMPLER STRING
"""
if len(split_field(field)) > 1:
get_logger().error("Bad call! Dude!")
return field.replace("\\.", ".")
return UNESCAPE_DOTS.sub(".", field)


def tail_field(field):
Expand All @@ -133,14 +139,8 @@ def tail_field(field):
if field == "." or field == None:
return ".", "."
elif "." in field:
if "\\." in field:
path = field.replace("\\.", "\a").replace("\b", "\\.").split(".", 1)
if len(path) == 1:
return path[0].replace("\a", "."), "."
else:
return tuple(k.replace("\a", ".") for k in path)
else:
return field.split(".", 1)
path = split_field(field)
return path[0], join_field(path[1:])
else:
return field, "."

Expand All @@ -149,19 +149,17 @@ def split_field(field):
"""
RETURN field AS ARRAY OF DOT-SEPARATED FIELDS
"""
if field == "." or field == None:
return []
elif is_text(field) and ("." in field or "\b" in field):
if ILLEGAL_DOTS.search(field):
get_logger().error("Odd number of dots is not allowed")
try:
if field.startswith(".."):
remainder = field.lstrip(".")
back = len(field) - len(remainder) - 1
return [-1] * back + [
k.replace("\a", ".") for k in remainder.replace("\\.", "\a").replace("\b", "\\.").split(".")
]
return [-1] * back + [UNESCAPE_DOTS.sub(".", k) for k in SPLIT_DOTS.split(remainder) if k]
else:
return [k.replace("\a", ".") for k in field.replace("\\.", "\a").replace("\b", "\\.").split(".")]
else:
return [field]
return [UNESCAPE_DOTS.sub(".", k) for k in SPLIT_DOTS.split(field) if k]
except Exception as cause:
return []


def join_field(path):
Expand All @@ -178,18 +176,11 @@ def join_field(path):
for i, step in enumerate(path):
if step != -1:
parents = "." + ("." * i)
return parents + ".".join([
f.replace(".", "\a") for f in path[i:] if f != None
]).replace("\\.", "\b").replace("\a", "\\.")
return parents + ".".join(literal_field(f) for f in path if f != None)
return "." + ("." * len(path))
output = ".".join(f.replace(".", "\a") for f in path if f != None).replace("\\.", "\b").replace("\a", "\\.")
output = ".".join(literal_field(f) for f in path if f != None)
return output if output else "."

# potent = [f for f in path if f != "."]
# if not potent:
# return "."
# return ".".join([f.replace(".", "\\.") for f in potent])


def concat_field(prefix, suffix):
if suffix.startswith(".."):
Expand Down Expand Up @@ -336,26 +327,6 @@ def _all_default(d, default, seen=None):
_all_default(existing_value, default_value, seen)


def _get_dict_default(obj, key):
"""
obj MUST BE A DICT
key IS EXPECTED TO BE LITERAL (NO ESCAPING)
TRY BOTH ATTRIBUTE AND ITEM ACCESS, OR RETURN Null
"""
try:
return obj[key]
except Exception as f:
pass

try:
if float(key) == round(float(key), 0):
return obj[int(key)]
except Exception as f:
pass

return NullType(obj, key)


def _getdefault(obj, key):
"""
obj ANY OBJECT
Expand Down Expand Up @@ -623,7 +594,7 @@ def _leaves_to_data(value):
key = key.decode("utf8")

d = output
if key.find(".") == -1:
if "." not in key:
if value is None:
d.pop(key, None)
else:
Expand Down
10 changes: 6 additions & 4 deletions mo_dots/datas.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ def __init__(self, *args, **kwargs):
CONSTRUCT DATA WITH GIVEN PROPERTY VALUES
"""
if args:
raise Exception("only keywords are allowed, not "+args[0].__class__.__name__)
raise Exception(
"only keywords are allowed, not " + args[0].__class__.__name__
)
_set(self, SLOT, kwargs)

def __bool__(self):
Expand Down Expand Up @@ -144,7 +146,7 @@ def __setitem__(self, key, value):
try:
d = _get(self, SLOT)
value = from_data(value)
if key.find(".") == -1:
if "." not in key:
if value is None:
d.pop(key, None)
else:
Expand Down Expand Up @@ -370,7 +372,7 @@ def __deepcopy__(self, memo):
return to_data(deepcopy(d, memo))

def __delitem__(self, key):
if key.find(".") == -1:
if "." not in key:
d = _get(self, SLOT)
d.pop(key, None)
return
Expand Down Expand Up @@ -439,7 +441,7 @@ def _split_field(field):
"""
SIMPLE SPLIT, NO CHECKS
"""
return [k.replace("\a", ".") for k in field.replace("\\.", "\a").replace("\b", "\\.").split(".")]
return [k.replace("\b", ".") for k in field.replace("..", "\b").split(".")]


def _str(value, depth):
Expand Down
2 changes: 1 addition & 1 deletion mo_dots/lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class FlatList(object):
ENCAPSULATES FLAT SLICES ([::]) FOR USE IN WINDOW FUNCTIONS
https://github.com/klahnakoski/mo-dots/tree/dev/docs#flatlist-is-flat
"""
__slots__ = [SLOT]

__slots__ = [SLOT]

def __init__(self, vals=None):
""" USE THE vals, NOT A COPY """
Expand Down
3 changes: 2 additions & 1 deletion mo_dots/nones.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class NullType(object):
Null INSTANCES WILL TRACK THEIR OWN DEREFERENCE PATH SO
ASSIGNMENT CAN BE DONE
"""

__slots__ = [SLOT, KEY]

def __init__(self, obj=None, key=None):
Expand Down Expand Up @@ -290,7 +291,7 @@ def _split_field(field):
if field == ".":
return []
else:
return [k.replace("\a", ".") for k in field.replace("\\.", "\a".replace("\b", "\\.")).split(".")]
return [k.replace("\b", ".") for k in field.replace("..", "\b").split(".")]


def _setdefault(obj, key, value):
Expand Down
1 change: 1 addition & 0 deletions mo_dots/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class DataObject(Mapping):
"""
TREAT AN OBJECT LIKE DATA
"""

__slots__ = [SLOT]

def __init__(self, obj):
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@
include_package_data=True,
install_requires=["mo-future==6.2.21303","mo-imports==7.3.21313"],
license='MPL 2.0',
long_description='\n# More Dots!\n\n[![PyPI Latest Release](https://img.shields.io/pypi/v/mo-dots.svg)](https://pypi.org/project/mo-dots/)\n[![Build Status](https://app.travis-ci.com/klahnakoski/mo-dots.svg?branch=master)](https://travis-ci.com/github/klahnakoski/mo-dots)\n [![Coverage Status](https://coveralls.io/repos/github/klahnakoski/mo-dots/badge.svg?branch=dev)](https://coveralls.io/github/klahnakoski/mo-dots?branch=dev)\n\n\n## Overview\n\nThis library defines a `Data` class that can serve as a replacement for `dict`, with additional features. \n\n >>> from mo_dots import to_data, Data\n\n*See the [full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs) for all the features of `mo-dots`*\n\n### Changes in version 5.x.x\n\nThe `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input. \n \n### Easy Definition\n\nDefine `Data` using named parameters, just like you would a `dict`\n\n >>> Data(b=42, c="hello world")\n Data({\'b\': 42, \'c\': \'hello world\'})\n\nYou can also wrap existing `dict`s so they can be used like `Data`\n\n >>> to_data({\'b\': 42, \'c\': \'hello world\'})\n Data({\'b\': 42, \'c\': \'hello world\'})\n\n### Dot Access\n\nAccess properties with attribute dots: `a.b == a["b"]`. You have probably seen this before.\n\n### Path Access\n\nAccess properties by dot-delimited path.\n\n\t>>> a = to_data({"b": {"c": 42}})\n\t>>> a["b.c"] == 42\n\tTrue\n\n### Safe Access\n\nIf a property does not exist then return `Null` rather than raising an error.\n\n\t>>> a = Data()\n\ta == {}\n\t>>> a.b == None\n\tTrue\n\t>>> a.b.c == None\n\tTrue\n\t>>> a[None] == None\n\tTrue\n\n### Path assignment\n\nNo need to make intermediate `dicts`\n\n >>> a = Data()\n a == {}\n >>> a["b.c"] = 42 # same as a.b.c = 42\n a == {"b": {"c": 42}}\n\n### Path accumulation\n\nUse `+=` to add to a property; default zero (`0`)\n\n >>> a = Data()\n a == {}\n >>> a.b.c += 1\n a == {"b": {"c": 1}}\n >>> a.b.c += 42\n a == {"b": {"c": 43}}\n\nUse `+=` with a list ([]) to append to a list; default empty list (`[]`)\n\n >>> a = Data()\n a == {}\n >>> a.b.c += [1]\n a == {"b": {"c": [1]}}\n >>> a.b.c += [42]\n a == {"b": {"c": [1, 42]}}\n\n## Serializing to JSON\n\nThe standard Python JSON library does not recognize `Data` as serializable. You may overcome this by providing `default=from_data`; which converts the data structures in this module into Python primitives of the same. \n\n from mo_dots import from_data, to_data\n \n s = to_data({"a": ["b", 1]})\n result = json.dumps(s, default=from_data) \n\nAlternatively, you may consider [mo-json](https://pypi.org/project/mo-json/) which has a function `value2json` that converts a larger number of data structures into JSON.\n\n\n## Summary\n\nThis library is the basis for a data transformation algebra: We want a succinct way of transforming data in Python. We want operations on data to result in yet more data. We do not want data operations to raise exceptions. This library is solves Python\'s lack of consistency (lack of closure) under the dot (`.`) and slice `[::]` operators when operating on data objects. \n\n[Full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs)\n',
long_description='\n# More Dots!\n\n[![PyPI Latest Release](https://img.shields.io/pypi/v/mo-dots.svg)](https://pypi.org/project/mo-dots/)\n[![Build Status](https://app.travis-ci.com/klahnakoski/mo-dots.svg?branch=master)](https://travis-ci.com/github/klahnakoski/mo-dots)\n [![Coverage Status](https://coveralls.io/repos/github/klahnakoski/mo-dots/badge.svg?branch=dev)](https://coveralls.io/github/klahnakoski/mo-dots?branch=dev)\n[![Downloads](https://pepy.tech/badge/mo-dots)](https://pepy.tech/project/mo-dots)\n\n#### Changes in version 9.x.x\n\nEscaping a literal dot (`.`) is no longer (`\\\\.`) rather double-dot (\'..\')\n\n#### Changes in version 5.x.x\n\nThe `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input. \n \n\n## Overview\n\nThis library defines a `Data` class that can serve as a replacement for `dict`, with additional features. \n\n >>> from mo_dots import to_data, Data\n\n*See the [full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs) for all the features of `mo-dots`*\n\n### Create instances\n\nDefine `Data` using named parameters, just like you would a `dict`\n\n >>> Data(b=42, c="hello world")\n Data({\'b\': 42, \'c\': \'hello world\'})\n\nYou can also wrap existing `dict`s so they can be used like `Data`\n\n >>> to_data({\'b\': 42, \'c\': \'hello world\'})\n Data({\'b\': 42, \'c\': \'hello world\'})\n\n### Dot Access\n\nAccess properties with attribute dots: `a.b == a["b"]`. You have probably seen this before.\n\n### Path Access\n\nAccess properties by dot-delimited path.\n\n\t>>> a = to_data({"b": {"c": 42}})\n\t>>> a["b.c"] == 42\n\tTrue\n\n### Safe Access\n\nIf a property does not exist then return `Null` rather than raising an error.\n\n\t>>> a = Data()\n\ta == {}\n\t>>> a.b == None\n\tTrue\n\t>>> a.b.c == None\n\tTrue\n\t>>> a[None] == None\n\tTrue\n\n### Path assignment\n\nNo need to make intermediate `dicts`\n\n >>> a = Data()\n a == {}\n >>> a["b.c"] = 42 # same as a.b.c = 42\n a == {"b": {"c": 42}}\n\n### Path accumulation\n\nUse `+=` to add to a property; default zero (`0`)\n\n >>> a = Data()\n a == {}\n >>> a.b.c += 1\n a == {"b": {"c": 1}}\n >>> a.b.c += 42\n a == {"b": {"c": 43}}\n\nUse `+=` with a list ([]) to append to a list; default empty list (`[]`)\n\n >>> a = Data()\n a == {}\n >>> a.b.c += [1]\n a == {"b": {"c": [1]}}\n >>> a.b.c += [42]\n a == {"b": {"c": [1, 42]}}\n\n## Serializing to JSON\n\nThe standard Python JSON library does not recognize `Data` as serializable. You may overcome this by providing `default=from_data`; which converts the data structures in this module into Python primitives of the same. \n\n from mo_dots import from_data, to_data\n \n s = to_data({"a": ["b", 1]})\n result = json.dumps(s, default=from_data) \n\nAlternatively, you may consider [mo-json](https://pypi.org/project/mo-json/) which has a function `value2json` that converts a larger number of data structures into JSON.\n\n\n## Summary\n\nThis library is the basis for a data transformation algebra: We want a succinct way of transforming data in Python. We want operations on data to result in yet more data. We do not want data operations to raise exceptions. This library is solves Python\'s lack of consistency (lack of closure) under the dot (`.`) and slice `[::]` operators when operating on data objects. \n\n[Full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs)\n',
long_description_content_type='text/markdown',
name='mo-dots',
packages=["mo_dots"],
url='https://github.com/klahnakoski/mo-dots',
version='8.20.21357',
version='9.101.22008',
zip_safe=False
)
17 changes: 11 additions & 6 deletions setuptools.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@
"[![PyPI Latest Release](https://img.shields.io/pypi/v/mo-dots.svg)](https://pypi.org/project/mo-dots/)",
"[![Build Status](https://app.travis-ci.com/klahnakoski/mo-dots.svg?branch=master)](https://travis-ci.com/github/klahnakoski/mo-dots)",
" [![Coverage Status](https://coveralls.io/repos/github/klahnakoski/mo-dots/badge.svg?branch=dev)](https://coveralls.io/github/klahnakoski/mo-dots?branch=dev)",
"[![Downloads](https://pepy.tech/badge/mo-dots)](https://pepy.tech/project/mo-dots)",
"",
"#### Changes in version 9.x.x",
"",
"Escaping a literal dot (`.`) is no longer (`\\\\.`) rather double-dot ('..')",
"",
"#### Changes in version 5.x.x",
"",
"The `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input. ",
" ",
"",
"## Overview",
"",
Expand All @@ -35,11 +44,7 @@
"",
"*See the [full documentation](https://github.com/klahnakoski/mo-dots/tree/dev/docs) for all the features of `mo-dots`*",
"",
"### Changes in version 5.x.x",
"",
"The `Data()` constructor only accepts keyword parameters. It no longer accepts a dict, nor does it attempt to clean the input. ",
" ",
"### Easy Definition",
"### Create instances",
"",
"Define `Data` using named parameters, just like you would a `dict`",
"",
Expand Down Expand Up @@ -130,6 +135,6 @@
"name": "mo-dots",
"packages": ["mo_dots"],
"url": "https://github.com/klahnakoski/mo-dots",
"version": "8.20.21357",
"version": "9.101.22008",
"zip_safe": false
}
11 changes: 11 additions & 0 deletions tests/smoke_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# encoding: utf-8
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
#
# Contact: Kyle Lahnakoski (kyle@lahnakoski.com)
#
from mo_dots import Data

d = Data(a=42)
8 changes: 2 additions & 6 deletions tests/speedtest_dot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,18 @@
# Contact: Kyle Lahnakoski (kyle@lahnakoski.com)
#

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import cProfile
import pstats
import random
from collections import Mapping

from mo_dots import Data, to_data, Null, split_field
from mo_future import text
from mo_math.randoms import Random
from mo_testing.fuzzytestcase import FuzzyTestCase
from mo_threads import profiles
from mo_times import Timer

from mo_dots import Data, to_data, Null


class SpeedTestDot(FuzzyTestCase):
def test_simple_access(self):
Expand Down

0 comments on commit 8812e1b

Please sign in to comment.