├── .coveragerc
├── .gitignore
├── .travis.yml
├── .vscode
├── launch.json
└── settings.json
├── LICENSE
├── MANIFEST.in
├── README.md
├── README.rst
├── README.rst.original
├── VER
├── build.sh
├── objectpath
├── __init__.py
├── core
│ ├── __init__.py
│ ├── interpreter.py
│ └── parser.py
├── shell.py
└── utils
│ ├── __init__.py
│ ├── colorify.py
│ ├── debugger.py
│ ├── json_ext.py
│ └── timeutils.py
├── requirements.txt
├── setup.cfg
├── setup.py
├── shell.py
├── testObjectPath.py
└── tests
├── __init__.py
├── test_ObjectPath.py
└── test_utils.py
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | omit =
3 | */tests*
4 | */shell.py
5 | */debugger.py
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 | .pytest_cache/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 | db.sqlite3
60 |
61 | # Flask stuff:
62 | instance/
63 | .webassets-cache
64 |
65 | # Scrapy stuff:
66 | .scrapy
67 |
68 | # Sphinx documentation
69 | docs/_build/
70 |
71 | # PyBuilder
72 | target/
73 |
74 | # Jupyter Notebook
75 | .ipynb_checkpoints
76 |
77 | # IPython
78 | profile_default/
79 | ipython_config.py
80 |
81 | # pyenv
82 | .python-version
83 |
84 | # celery beat schedule file
85 | celerybeat-schedule
86 |
87 | # SageMath parsed files
88 | *.sage.py
89 |
90 | # Environments
91 | .env
92 | .venv
93 | env/
94 | venv/
95 | ENV/
96 | env.bak/
97 | venv.bak/
98 |
99 | # Spyder project settings
100 | .spyderproject
101 | .spyproject
102 |
103 | # Rope project settings
104 | .ropeproject
105 |
106 | # mkdocs documentation
107 | /site
108 |
109 | # mypy
110 | .mypy_cache/
111 | .dmypy.json
112 | dmypy.json
113 |
114 | # Pyre type checker
115 | .pyre/
116 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - '2.7'
4 | - '3.8'
5 | - pypy
6 | install:
7 | - pip install -r requirements.txt
8 | - pip install coveralls
9 | script:
10 | - nosetests
11 | - coverage run --source=objectpath setup.py test
12 | deploy:
13 | provider: pypi
14 | user: adriankal
15 | password:
16 | secure: bWTP43XxjAIwC6PMmfjt4/dCBenky+zF7empMgzfmkUq5jCoQjCJm4IUdYIdjCKLYEIU1DaOTp7+rqmIZf2d8wWvGAc4+k3vinV9k8WzycpBM+YgnW2knQ5eko93H0lpNOIrat4J0wvc51JjHfj4uqib6SCTaXmBS/kRHmiRkx8=
17 | on:
18 | tags: true
19 | all_branches: true
20 | repo: adriank/ObjectPath
21 | distributions: "sdist bdist_wheel"
22 | after_success:
23 | - coveralls
24 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Python: Current File (Integrated Terminal)",
9 | "type": "python",
10 | "request": "launch",
11 | "program": "/Users/adrian/projects/ObjectPath/shell.py",
12 | "args": ["/Users/adrian/projects/nutrient-db/butter.json"],
13 | "console": "integratedTerminal"
14 | },
15 | {
16 | "name": "Python: Attach",
17 | "type": "python",
18 | "request": "attach",
19 | "port": 5678,
20 | "host": "localhost"
21 | },
22 | {
23 | "name": "Python: Module",
24 | "type": "python",
25 | "request": "launch",
26 | "module": "objectpath",
27 | "console": "integratedTerminal"
28 | },
29 | {
30 | "name": "Python: Django",
31 | "type": "python",
32 | "request": "launch",
33 | "program": "${workspaceFolder}/manage.py",
34 | "console": "integratedTerminal",
35 | "args": [
36 | "runserver",
37 | "--noreload",
38 | "--nothreading"
39 | ],
40 | "django": true
41 | },
42 | {
43 | "name": "Python: Flask",
44 | "type": "python",
45 | "request": "launch",
46 | "module": "flask",
47 | "env": {
48 | "FLASK_APP": "app.py"
49 | },
50 | "args": [
51 | "run",
52 | "--no-debugger",
53 | "--no-reload"
54 | ],
55 | "jinja": true
56 | },
57 | {
58 | "name": "Python: Current File (External Terminal)",
59 | "type": "python",
60 | "request": "launch",
61 | "program": "${file}",
62 | "console": "externalTerminal"
63 | }
64 | ]
65 | }
66 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "python.linting.pylintEnabled": true
3 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Adrian Kalbarczyk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.rst
2 | include VER
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ObjectPath
2 | ==========
3 |
4 | [](https://pypi.python.org/pypi/objectpath/)
5 |
6 | [](https://travis-ci.org/adriank/ObjectPath)
7 | [](https://landscape.io/github/adriank/ObjectPath/master)
8 | [](https://coveralls.io/r/adriank/ObjectPath?branch=master)
9 |
10 | The agile NoSQL query language for semi-structured data
11 | -----------------------------------------------
12 |
13 | **#Python #NoSQL #Javascript #JSON #nested-array-object**
14 |
15 | ObjectPath is a query language similar to XPath or JSONPath, but much more powerful thanks to embedded arithmetic calculations, comparison mechanisms and built-in functions. This makes the language more like SQL in terms of expressiveness, but it works over JSON documents rather than relations. ObjectPath can be considered a full-featured expression language. Besides selector mechanism there is also boolean logic, type system and string concatenation available. On top of that, the language implementations (Python at the moment; Javascript is in beta!) are secure and relatively fast.
16 |
17 | More at [ObjectPath site](https://adriank.github.io/ObjectPath/)
18 |
19 | 
20 |
21 | ObjectPath makes it easy to find data in big nested JSON documents. It borrows the best parts from E4X, JSONPath, XPath and SQL. ObjectPath is to JSON documents what XPath is to XML. Other examples to ilustrate this kind of relationship are:
22 |
23 | | Scope | Language |
24 | |---|---|
25 | | text documents | regular expression |
26 | | XML | XPath |
27 | | HTML | CSS selectors |
28 | | JSON documents | ObjectPath |
29 |
30 | Documentation
31 | -------------
32 |
33 | [ObjectPath Reference](https://adriank.github.io/ObjectPath/reference.html)
34 |
35 | Command line usage
36 | -----
37 |
38 | `````sh
39 | $ sudo pip install objectpath
40 | $ objectpath file.json
41 | `````
42 | or
43 | `````sh
44 | $ git clone https://github.com/adriank/ObjectPath.git
45 | $ cd ObjectPath
46 | $ python shell.py file.json
47 | `````
48 |
49 | Python usage
50 | ----------------
51 |
52 | `````sh
53 | $ sudo pip install objectpath
54 | $ python
55 | >>> from objectpath import *
56 | >>> tree=Tree({"a":1})
57 | >>> tree.execute("$.a")
58 | 1
59 | >>>
60 | `````
61 |
62 | `````sh
63 | $ git clone https://github.com/adriank/ObjectPath.git
64 | $ cd ObjectPath
65 | $ python
66 | >>> from objectpath import *
67 | >>> tree=Tree({"a":1})
68 | >>> tree.execute("$.a")
69 | 1
70 | >>>
71 | `````
72 |
73 | Contributing & bugs
74 | -------------------
75 |
76 | I appreciate all contributions and bugfix requests for ObjectPath, however since I don't code in Python anymore, this library is not maintained as of now. Since I can't fully assure that code contributed by others meets quality standards, I can't accept PRs.
77 |
78 | If you feel you could maintain this code, ping me. I'd be more than happy to transfer this repo to a dedicated ObjectPath organization on GitHub and give the ownership to someone with more time for this project than me.
79 |
80 | License
81 | -------
82 |
83 | **MIT**
84 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ObjectPath
2 | ==========
3 |
4 | |Downloads| |Build Status| |Code Health| |Coverage Status|
5 |
6 | The agile NoSQL query language for semi-structured data
7 | -------------------------------------------------------
8 |
9 | **#Python #NoSQL #Javascript #JSON #nested-array-object**
10 |
11 | ObjectPath is a query language similar to XPath or JSONPath, but much
12 | more powerful thanks to embedded arithmetic calculations, comparison
13 | mechanisms and built-in functions. This makes the language more like SQL
14 | in terms of expressiveness, but it works over JSON documents rather than
15 | relations. ObjectPath can be considered a full-featured expression
16 | language. Besides selector mechanism there is also boolean logic, type
17 | system and string concatenation available. On top of that, the language
18 | implementations (Python at the moment; Javascript is in beta!) are
19 | secure and relatively fast.
20 |
21 | More at `ObjectPath site `__
22 |
23 | .. figure:: http://adriank.github.io/ObjectPath/img/op-colors.png
24 | :alt: ObjectPath img
25 |
26 | ObjectPath img
27 |
28 | ObjectPath makes it easy to find data in big nested JSON documents. It
29 | borrows the best parts from E4X, JSONPath, XPath and SQL. ObjectPath is
30 | to JSON documents what XPath is to XML. Other examples to ilustrate this
31 | kind of relationship are:
32 |
33 | ============== ==================
34 | Scope Language
35 | ============== ==================
36 | text documents regular expression
37 | XML XPath
38 | HTML CSS selectors
39 | JSON documents ObjectPath
40 | ============== ==================
41 |
42 | Documentation
43 | -------------
44 |
45 | `ObjectPath Reference `__
46 |
47 | Command line usage
48 | ------------------
49 |
50 | .. code:: sh
51 |
52 | $ sudo pip install objectpath
53 | $ objectpath file.json
54 |
55 | or
56 |
57 | .. code:: sh
58 |
59 | $ git clone https://github.com/adriank/ObjectPath.git
60 | $ cd ObjectPath
61 | $ python shell.py file.json
62 |
63 | Python usage
64 | ------------
65 |
66 | .. code:: sh
67 |
68 | $ sudo pip install objectpath
69 | $ python
70 | >>> from objectpath import *
71 | >>> tree=Tree({"a":1})
72 | >>> tree.execute("$.a")
73 | 1
74 | >>>
75 |
76 | .. code:: sh
77 |
78 | $ git clone https://github.com/adriank/ObjectPath.git
79 | $ cd ObjectPath
80 | $ python
81 | >>> from objectpath import *
82 | >>> tree=Tree({"a":1})
83 | >>> tree.execute("$.a")
84 | 1
85 | >>>
86 |
87 | Contributing & bugs
88 | -------------------
89 |
90 | I appreciate all contributions and bugfix requests for ObjectPath,
91 | however since I don’t code in Python any more, this library is not
92 | maintained as of now. Since I can’t fully assure that code contributed
93 | by others meets quality standards, I can’t accept PRs.
94 |
95 | If you feel you could maintain this code, ping me. I’d be more than
96 | happy to transfer this repo to a dedicated ObjectPath organization on
97 | GitHub and give the ownership to someone with more time for this project
98 | than me.
99 |
100 | License
101 | -------
102 |
103 | **MIT**
104 |
105 | .. |Downloads| image:: https://img.shields.io/pypi/dm/objectpath.svg
106 | :target: https://pypi.python.org/pypi/objectpath/
107 | .. |Build Status| image:: https://travis-ci.org/adriank/ObjectPath.svg?branch=master
108 | :target: https://travis-ci.org/adriank/ObjectPath
109 | .. |Code Health| image:: https://landscape.io/github/adriank/ObjectPath/master/landscape.png
110 | :target: https://landscape.io/github/adriank/ObjectPath/master
111 | .. |Coverage Status| image:: https://coveralls.io/repos/adriank/ObjectPath/badge.png?branch=master
112 | :target: https://coveralls.io/r/adriank/ObjectPath?branch=master
113 |
--------------------------------------------------------------------------------
/README.rst.original:
--------------------------------------------------------------------------------
1 | ObjectPath
2 | ==========
3 |
4 | |Downloads| |Build Status| |Code Health| |Coverage Status|
5 |
6 | The agile NoSQL query language for semi-structured data
7 | -------------------------------------------------------
8 |
9 | **#Python #NoSQL #Javascript #JSON #nested-array-object**
10 |
11 | ObjectPath is a query language similar to XPath or JSONPath, but much
12 | more powerful thanks to embedded arithmetic calculations, comparison
13 | mechanisms and built-in functions. This makes the language more like SQL
14 | in terms of expressiveness, but it works over JSON documents rather than
15 | relations. ObjectPath can be considered a full-featured expression
16 | language. Besides selector mechanism there is also boolean logic, type
17 | system and string concatenation available. On top of that, the language
18 | implementations (Python at the moment; Javascript is in beta!) are
19 | secure and relatively fast.
20 |
21 | More at `ObjectPath site `__
22 |
23 | .. figure:: http://adriank.github.io/ObjectPath/img/op-colors.png
24 | :alt: ObjectPath img
25 |
26 | ObjectPath img
27 |
28 | ObjectPath makes it easy to find data in big nested JSON documents. It
29 | borrows the best parts from E4X, JSONPath, XPath and SQL. ObjectPath is
30 | to JSON documents what XPath is to XML. Other examples to ilustrate this
31 | kind of relationship are:
32 |
33 | ============== ==================
34 | Scope Language
35 | ============== ==================
36 | text documents regular expression
37 | XML XPath
38 | HTML CSS selectors
39 | JSON documents ObjectPath
40 | ============== ==================
41 |
42 | Documentation
43 | -------------
44 |
45 | `ObjectPath Reference `__
46 |
47 | Command line usage
48 | ------------------
49 |
50 | .. code:: sh
51 |
52 | $ sudo pip install objectpath
53 | $ objectpath file.json
54 |
55 | or
56 |
57 | .. code:: sh
58 |
59 | $ git clone https://github.com/adriank/ObjectPath.git
60 | $ cd ObjectPath
61 | $ python shell.py file.json
62 |
63 | Python usage
64 | ------------
65 |
66 | .. code:: sh
67 |
68 | $ sudo pip install objectpath
69 | $ python
70 | >>> from objectpath import *
71 | >>> tree=Tree({"a":1})
72 | >>> tree.execute("$.a")
73 | 1
74 | >>>
75 |
76 | .. code:: sh
77 |
78 | $ git clone https://github.com/adriank/ObjectPath.git
79 | $ cd ObjectPath
80 | $ python
81 | >>> from objectpath import *
82 | >>> tree=Tree({"a":1})
83 | >>> tree.execute("$.a")
84 | 1
85 | >>>
86 |
87 | Contributing & bugs
88 | -------------------
89 |
90 | I appreciate all contributions and bugfix requests for ObjectPath,
91 | however since I don’t code in Python any more, this library is not
92 | maintained as of now. Since I can’t fully assure that code contributed
93 | by others meets quality standards, I can’t accept PRs.
94 |
95 | If you feel you could maintain this code, ping me. I’d be more than
96 | happy to transfer this repo to a dedicated ObjectPath organization on
97 | GitHub and give the ownership to someone with more time for this project
98 | than me.
99 |
100 | License
101 | -------
102 |
103 | **MIT**
104 |
105 | .. |Downloads| image:: https://img.shields.io/pypi/dm/objectpath.svg
106 | :target: https://pypi.python.org/pypi/objectpath/
107 | .. |Build Status| image:: https://travis-ci.org/adriank/ObjectPath.svg?branch=master
108 | :target: https://travis-ci.org/adriank/ObjectPath
109 | .. |Code Health| image:: https://landscape.io/github/adriank/ObjectPath/master/landscape.png
110 | :target: https://landscape.io/github/adriank/ObjectPath/master
111 | .. |Coverage Status| image:: https://coveralls.io/repos/adriank/ObjectPath/badge.png?branch=master
112 | :target: https://coveralls.io/r/adriank/ObjectPath?branch=master
113 |
--------------------------------------------------------------------------------
/VER:
--------------------------------------------------------------------------------
1 | 0.6.2
2 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | pandoc -f markdown -t rst -o README.rst README.md
3 | mdTable="aaa Scope aaa Language aaa aaa---aaa---aaa aaa text documents aaa regular
4 | expression aaa aaa XML aaa XPath aaa aaa HTML aaa CSS selectors aaa aaa JSON
5 | documents aaa ObjectPath aaa"
6 | rstTable="============== ==================
7 | Scope Language
8 | ============== ==================
9 | text documents regular expression
10 | XML XPath
11 | HTML CSS selectors
12 | JSON documents ObjectPath
13 | ============== =================="
14 | sed -i "s/\\\|/aaa/g" README.rst
15 | perl -0777 -i.original -pe "s/$mdTable/$rstTable/g" README.rst
16 |
17 | python setup.py build
18 | python setup.py sdist bdist_wheel upload
19 |
--------------------------------------------------------------------------------
/objectpath/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from .core.interpreter import *
5 | from .utils import *
6 |
--------------------------------------------------------------------------------
/objectpath/core/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # This file is part of ObjectPath released under MIT license.
5 | # Copyright (C) 2010-2014 Adrian Kalbarczyk
6 |
7 | from types import GeneratorType as generator
8 | from itertools import chain
9 |
10 | SELECTOR_OPS = [
11 | "is", ">", "<", "is not", ">=", "<=", "in", "not in", ":", "and", "or", "matches", "fn"
12 | ]
13 | # it must be list because of further concatenations
14 | NUM_TYPES = [int, float]
15 |
16 | try:
17 | NUM_TYPES += [long]
18 | except NameError:
19 | pass
20 |
21 | STR_TYPES = [str]
22 |
23 | try:
24 | STR_TYPES += [unicode]
25 | except NameError:
26 | pass
27 |
28 | ITER_TYPES = [list, generator, chain]
29 |
30 | try:
31 | ITER_TYPES += [map, filter]
32 | except NameError:
33 | pass
34 |
35 | class ProgrammingError(Exception):
36 | pass
37 |
38 | class ExecutionError(Exception):
39 | pass
40 |
41 | PY_TYPES_MAP = {
42 | "int": "number",
43 | "float": "number",
44 | "str": "string",
45 | "dict": "object",
46 | "list": "array"
47 | }
48 |
--------------------------------------------------------------------------------
/objectpath/core/interpreter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of ObjectPath released under MIT license.
4 | # Copyright (C) 2010-2014 Adrian Kalbarczyk
5 |
6 | import sys, re
7 | from .parser import parse
8 | from objectpath.core import *
9 | import objectpath.utils.colorify as color # pylint: disable=W0614
10 | from objectpath.utils import flatten, filter_dict, timeutils, skip
11 | from objectpath.utils.json_ext import py2JSON
12 | from objectpath.core import ITER_TYPES, generator, chain
13 | from objectpath.utils.debugger import Debugger
14 |
15 | EPSILON = 0.0000000000000001 #this is used in float comparison
16 | EXPR_CACHE = {}
17 | RE_TYPE = type(re.compile(''))
18 | # setting external modules to 0, thus enabling lazy loading. 0 ensures that Pythonic types are never matched.
19 | # this way is efficient because if statement is fast and once loaded these variables are pointing to libraries.
20 | ObjectId = generateID = calendar = escape = escapeDict = unescape = unescapeDict = 0
21 |
22 | class Tree(Debugger):
23 | _REGISTERED_FUNCTIONS = {}
24 |
25 | @classmethod
26 | def register_function(cls, name, func):
27 | """
28 | This method is used to add custom functions not catered for by default
29 | :param str name: The name by which the function will be referred to in the expression
30 | :param callable func: The function
31 | :return:
32 | """
33 | cls._REGISTERED_FUNCTIONS[name] = func
34 |
35 | def __init__(self, obj, cfg=None):
36 | if not cfg:
37 | cfg = {}
38 | self.D = cfg.get("debug", False)
39 | self.setObjectGetter(cfg.get("object_getter", None))
40 | self.setData(obj)
41 | self.current = self.node = None
42 | if self.D: super(Tree, self).__init__()
43 |
44 | def setData(self, obj):
45 | if type(obj) in ITER_TYPES + [dict]:
46 | self.data = obj
47 |
48 | def setObjectGetter(self, object_getter_cb):
49 | if callable(object_getter_cb):
50 | self.object_getter = object_getter_cb
51 | else:
52 |
53 | def default_getter(obj, attr):
54 | try:
55 | return obj.__getattribute__(attr)
56 | except AttributeError:
57 | if self.D:
58 | self.end(color.op(".") + " returning '%s'", color.bold(obj))
59 | return obj
60 |
61 | self.object_getter = default_getter
62 |
63 | def compile(self, expr):
64 | if expr in EXPR_CACHE:
65 | return EXPR_CACHE[expr]
66 | ret = EXPR_CACHE[expr] = parse(expr, self.D)
67 | return ret
68 |
69 | def execute(self, expr):
70 | D = self.D
71 | if D: self.start("Tree.execute")
72 | TYPES = [str, int, float, bool, generator, chain]
73 | try:
74 | TYPES += [long]
75 | except NameError:
76 | pass
77 |
78 | # TODO change to yield?
79 | def exe(node):
80 | """
81 | node[0] - operator name
82 | node[1:] - params
83 | """
84 | types = [
85 | str, timeutils.datetime.time, timeutils.datetime.date,
86 | timeutils.datetime.datetime
87 | ]
88 | try:
89 | types += [unicode]
90 | except:
91 | pass
92 | if D: self.start("executing node %s", color.bold(self.cleanOutput(node)))
93 | type_node = type(node)
94 | if node is None or type_node in TYPES:
95 | return node
96 | elif type_node in types:
97 | return node
98 | elif type_node is list:
99 | return (exe(n) for n in node)
100 | elif type_node is dict:
101 | ret = {}
102 | for i in node.items():
103 | ret[exe(i[0])] = exe(i[1])
104 | return ret
105 | op = node[0]
106 | if op == "or":
107 | if D: self.debug("%s or %s", node[1], node[2])
108 | return exe(node[1]) or exe(node[2])
109 | elif op == "and":
110 | if D: self.debug("%s and %s", node[1], node[2])
111 | return exe(node[1]) and exe(node[2])
112 | elif op == "+":
113 | if len(node) > 2:
114 | fst = exe(node[1])
115 | snd = exe(node[2])
116 | if None in (fst, snd):
117 | return fst or snd
118 | typefst = type(fst)
119 | typesnd = type(snd)
120 | if typefst is dict:
121 | try:
122 | fst.update(snd)
123 | except Exception:
124 | if type(snd) is not dict:
125 | raise ProgrammingError(
126 | "Can't add value of type %s to %s" % (
127 | color.bold(
128 | PY_TYPES_MAP.
129 | get(type(snd).__name__,
130 | type(snd).__name__)
131 | ), color.bold("object")
132 | )
133 | )
134 | return fst
135 | if typefst is list and typesnd is list:
136 | if D: self.debug("both sides are lists, returning '%s'", fst + snd)
137 | return fst + snd
138 | if typefst in ITER_TYPES or typesnd in ITER_TYPES:
139 | if typefst not in ITER_TYPES:
140 | fst = [fst]
141 | elif typesnd not in ITER_TYPES:
142 | snd = [snd]
143 | if D: self.debug("at least one side is a generator and the other is an iterable, returning chain")
144 | return chain(fst, snd)
145 | if typefst in NUM_TYPES:
146 | try:
147 | return fst + snd
148 | except Exception:
149 | return fst + float(snd)
150 | if typefst in STR_TYPES or typesnd in STR_TYPES:
151 | if D: self.info("doing string comparison '%s' is '%s'", fst, snd)
152 | if sys.version_info[0] < 3:
153 | if typefst is unicode:
154 | fst = fst.encode("utf-8")
155 | if typesnd is unicode:
156 | snd = snd.encode("utf-8")
157 | return str(fst) + str(snd)
158 | try:
159 | timeType = timeutils.datetime.time
160 | if typefst is timeType and typesnd is timeType:
161 | return timeutils.addTimes(fst, snd)
162 | except Exception:
163 | pass
164 | if D: self.debug("standard addition, returning '%s'", fst + snd)
165 | return fst + snd
166 | else:
167 | return exe(node[1])
168 | elif op == "-":
169 | if len(node) > 2:
170 | fst = exe(node[1])
171 | snd = exe(node[2])
172 | try:
173 | return fst - snd
174 | except Exception:
175 | typefst = type(fst)
176 | typesnd = type(snd)
177 | timeType = timeutils.datetime.time
178 | if typefst is timeType and typesnd is timeType:
179 | return timeutils.subTimes(fst, snd)
180 | else:
181 | return -exe(node[1])
182 | elif op == "*":
183 | return exe(node[1])*exe(node[2])
184 | elif op == "%":
185 | return exe(node[1]) % exe(node[2])
186 | elif op == "/":
187 | return exe(node[1])/float(exe(node[2]))
188 | elif op == ">":
189 | if D: self.debug("%s > %s, %s", node[1], node[2], node[1] > node[2])
190 | return exe(node[1]) > exe(node[2])
191 | elif op == "<":
192 | return exe(node[1]) < exe(node[2])
193 | elif op == ">=":
194 | return exe(node[1]) >= exe(node[2])
195 | elif op == "<=":
196 | return exe(node[1]) <= exe(node[2])
197 | # TODO this algorithm produces 3 for 1<2<3 and should be true
198 | # elif op in "<=>=":
199 | # fst=exe(node[1])
200 | # snd=exe(node[2])
201 | # if op==">":
202 | # return fst > snd and snd or False
203 | # elif op=="<":
204 | # return fst < snd and snd or False
205 | # elif op==">=":
206 | # return fst >= snd and snd or False
207 | # elif op=="<=":
208 | # return fst <= snd and snd or False
209 | elif op == "not":
210 | fst = exe(node[1])
211 | if D: self.debug("doing not '%s'", fst)
212 | return not fst
213 | elif op == "in":
214 | fst = exe(node[1])
215 | snd = exe(node[2])
216 | if D: self.debug("doing '%s' in '%s'", node[1], node[2])
217 | if type(fst) in ITER_TYPES and type(snd) in ITER_TYPES:
218 | return any(
219 | x in max(fst, snd, key=len) for x in min(fst, snd, key=len)
220 | )
221 | return exe(node[1]) in exe(node[2])
222 | elif op == "not in":
223 | fst = exe(node[1])
224 | snd = exe(node[2])
225 | if D: self.debug("doing '%s' not in '%s'", node[1], node[2])
226 | if type(fst) in ITER_TYPES and type(snd) in ITER_TYPES:
227 | return not any(
228 | x in max(fst, snd, key=len) for x in min(fst, snd, key=len)
229 | )
230 | return exe(node[1]) not in exe(node[2])
231 | elif op in ("is", "is not"):
232 | if D: self.debug("found operator '%s'", op)
233 | # try:
234 | fst = exe(node[1])
235 | # except Exception as e:
236 | # if D: self.debug("NOT ERROR! Can't execute node[1] '%s', error: '%s'. Falling back to orginal value.",node[1],str(e))
237 | # fst=node[1]
238 | # try:
239 | snd = exe(node[2])
240 | # except Exception as e:
241 | # if D: self.debug("NOT ERROR! Can't execute node[2] '%s', error: '%s'. Falling back to orginal value.",node[2],str(e))
242 | # snd=node[2]
243 | if op == "is" and fst == snd:
244 | return True
245 |
246 | # this doesn't work for 3 is not '3'
247 | # if op == "is not" and fst != snd:
248 | # return True
249 | typefst = type(fst)
250 | typesnd = type(snd)
251 | if D: self.debug("type fst: '%s', type snd: '%s'", typefst, typesnd)
252 | if typefst in STR_TYPES:
253 | if D: self.info("doing string comparison '\"%s\" is \"%s\"'", fst, snd)
254 | ret = str(fst) == str(snd)
255 | elif typefst is float or typesnd is float:
256 | if D: self.info("doing float comparison '%s is %s'", fst, snd)
257 | try:
258 | ret = abs(float(fst) - float(snd)) < EPSILON
259 | except:
260 | ret = False
261 | elif typefst is int or typesnd is int:
262 | if D: self.info("doing integer comparison '%s is %s'", fst, snd)
263 | try:
264 | ret = int(fst) == int(snd)
265 | except:
266 | ret = False
267 | elif typefst is list and typesnd is list:
268 | if D: self.info("doing array comparison '%s' is '%s'", fst, snd)
269 | ret = fst == snd
270 | elif typefst is dict and typesnd is dict:
271 | if D: self.info("doing object comparison '%s' is '%s'", fst, snd)
272 | ret = fst == snd
273 | elif fst is None or snd is None:
274 | if fst is None and snd is None:
275 | # this executes only for "is not"
276 | ret = True
277 | else:
278 | ret = (fst or snd) is None
279 | if D: self.info(
280 | "doing None comparison %s is %s = %s", color.bold(fst), color.bold(snd),
281 | color.bold(not not (fst or snd))
282 | )
283 | else:
284 | if D: self.info("can't compare %s and %s. Returning False", self.cleanOutput(fst), self.cleanOutput(snd))
285 | ret = False
286 | # else:
287 | # try:
288 | # global ObjectId
289 | # if not ObjectId:
290 | # from bson.objectid import ObjectId
291 | # if typefst is ObjectId or typesnd is ObjectId:
292 | # if D: self.info("doing MongoDB objectID comparison '%s' is '%s'",fst,snd)
293 | # ret=str(fst)==str(snd)
294 | # else:
295 | # if D: self.info("doing standard comparison '%s' is '%s'",fst,snd)
296 | # ret=fst is snd
297 | # except Exception:
298 | # pass
299 | if op == "is not":
300 | if D: self.info("'is not' found. Returning %s", not ret)
301 | return not ret
302 | else:
303 | if D: self.info("returning %s is %s => %s", color.bold(self.cleanOutput(fst)), color.bold(self.cleanOutput(snd)), color.bold(ret))
304 | return ret
305 | elif op == "re":
306 | return re.compile(exe(node[1]))
307 | elif op == "matches":
308 | fst = exe(node[1])
309 | snd = exe(node[2])
310 | if type(fst) not in STR_TYPES+[RE_TYPE]:
311 | raise Exception("operator " + color.bold("matches") + " expects regexp on the left. Example: 'a.*d' matches 'abcd'")
312 | if type(snd) in ITER_TYPES:
313 | for i in snd:
314 | if not not re.match(fst, i):
315 | return True
316 | return False
317 | else:
318 | # regex matches string
319 | return not not re.match(fst, snd)
320 | # elif op=="(literal)":
321 | # fstLetter=node[1][0]
322 | # if fstLetter is "'":
323 | # return node[1][1:-1]
324 | # elif fstLetter.isdigit:
325 | # return int(node[1])
326 | elif op == "(root)": # this is $
327 | return self.data
328 | # elif op=="(node)":# this is !
329 | # if D: self.debug("returning node %s",self.node)
330 | # return self.node
331 | elif op == "(current)": # this is @
332 | if D: self.debug("returning current node: \n %s", color.bold(self.current))
333 | return self.current
334 | elif op == "name":
335 | return node[1]
336 | elif op == ".":
337 | fst = node[1]
338 | if type(fst) is tuple:
339 | fst = exe(fst)
340 | typefst = type(fst)
341 | if D: self.debug(color.op(".") + " left is '%s'", color.bold(self.cleanOutput(fst)))
342 | # try:
343 | if node[2][0] == "*":
344 | if D:
345 | self.end(
346 | color.op(".") + " returning '%s'",
347 | color.bold(typefst in ITER_TYPES and fst or [fst])
348 | )
349 | return fst # typefst in ITER_TYPES and fst or [fst]
350 | # except:
351 | # pass
352 | snd = exe(node[2])
353 | if D: self.debug(color.op(".") + " right is '%s'", color.bold(snd))
354 | if typefst in ITER_TYPES:
355 | if D: self.debug(
356 | color.op(".") + " filtering %s by %s", color.bold(self.cleanOutput(fst)),
357 | color.bold(snd)
358 | )
359 | if type(snd) in ITER_TYPES:
360 | return filter_dict(fst, list(snd))
361 | else:
362 | # if D: self.debug(list(fst))
363 | return (e[snd] for e in fst if type(e) is dict and snd in e)
364 | try:
365 | if D: self.end(color.op(".") + " returning '%s'", fst.get(snd))
366 | return fst.get(snd)
367 | except Exception:
368 | if isinstance(fst, object):
369 | return self.object_getter(fst, snd)
370 | if D: self.end(color.op(".") + " returning '%s'", color.bold(fst))
371 | return fst
372 | elif op == "..":
373 | fst = flatten(exe(node[1]))
374 | if node[2][0] == "*":
375 | if D: self.debug(color.op("..") + " returning '%s'", color.bold(fst))
376 | return fst
377 | # reduce objects to selected attributes
378 | snd = exe(node[2])
379 | if D: self.debug(
380 | color.op("..") + " finding all %s in %s", color.bold(snd),
381 | color.bold(self.cleanOutput(fst))
382 | )
383 | if type(snd) in ITER_TYPES:
384 | ret = filter_dict(fst, list(snd))
385 | if D: self.debug(color.op("..") + " returning %s", color.bold(ret))
386 | return ret
387 | else:
388 | ret = chain.from_iterable(
389 | type(x) in ITER_TYPES and x or [x]
390 | for x in (e[snd] for e in fst if snd in e)
391 | )
392 | # print list(chain(*(type(x) in ITER_TYPES and x or [x] for x in (e[snd] for e in fst if snd in e))))
393 | if D: self.debug(color.op("..") + " returning %s", color.bold(self.cleanOutput(ret)))
394 | return ret
395 | elif op == "[":
396 | len_node = len(node)
397 | # TODO move it to tree generation phase
398 | if len_node == 1: # empty list
399 | if D: self.debug("returning an empty list")
400 | return []
401 | if len_node == 2: # list - preserved to catch possible event of leaving it as '[' operator
402 | if D: self.debug("doing list mapping")
403 | return [exe(x) for x in node[1]]
404 | if len_node == 3: # selector used []
405 | fst = exe(node[1])
406 | # check against None
407 | if not fst:
408 | return fst
409 | selector = node[2]
410 | if D:
411 | self.debug(
412 | "\n found selector '%s'.\n executing on %s", color.bold(selector),
413 | color.bold(fst)
414 | )
415 | selectorIsTuple = type(selector) is tuple
416 |
417 | if selectorIsTuple and selector[0] == "[":
418 | nodeList = []
419 | nodeList_append = nodeList.append
420 | for i in fst:
421 | if D: self.debug("setting self.current to %s", color.bold(i))
422 | self.current = i
423 | nodeList_append(
424 | exe((selector[0], exe(selector[1]), exe(selector[2])))
425 | )
426 | if D: self.debug(
427 | "returning %s objects: %s", color.bold(len(nodeList)),
428 | color.bold(nodeList)
429 | )
430 | return nodeList
431 |
432 | if selectorIsTuple and selector[0] == "(current)":
433 | if D:
434 | self.warning(
435 | color.bold("$.*[@]") + " is eqivalent to " +
436 | color.bold("$.*") + "!"
437 | )
438 | return fst
439 |
440 | if selectorIsTuple and selector[0] in SELECTOR_OPS:
441 | if D: self.debug(
442 | "found %s operator in selector, %s", color.bold(selector[0]),
443 | color.bold(selector)
444 | )
445 | if type(fst) is dict:
446 | fst = [fst]
447 | # TODO move it to tree building phase
448 | if type(selector[1]) is tuple and selector[1][0] == "name":
449 | selector = (selector[0], selector[1][1], selector[2])
450 | selector0 = selector[0]
451 | selector1 = selector[1]
452 | selector2 = selector[2]
453 |
454 | def exeSelector(fst):
455 | for i in fst:
456 | if D:
457 | self.debug("setting self.current to %s", color.bold(i))
458 | self.debug(" s0: %s\n s1: %s\n s2: %s\n Current: %s", selector0, selector1, selector2, i)
459 | self.current = i
460 | if selector0 == "fn":
461 | yield exe(selector)
462 | # elif type(selector1) in STR_TYPES and False:
463 | # if D: self.debug("found string %s", type(i))
464 | # try:
465 | # if exe((selector0,i[selector1],selector2)):
466 | # yield i
467 | # if D: self.debug("appended")
468 | # if D: self.debug("discarded")
469 | # except Exception as e:
470 | # if D: self.debug("discarded, Exception: %s",color.bold(e))
471 | else:
472 | try:
473 | # TODO optimize an event when @ is not used. exe(selector1) can be cached
474 | if exe((selector0, exe(selector1), exe(selector2))):
475 | yield i
476 | if D: self.debug("appended %s", i)
477 | elif D: self.debug("discarded")
478 | except Exception:
479 | if D: self.debug("discarded")
480 |
481 | # if D and nodeList: self.debug("returning '%s' objects: '%s'", color.bold(len(nodeList)), color.bold(nodeList))
482 | return exeSelector(fst)
483 | self.current = fst
484 | snd = exe(node[2])
485 | typefst = type(fst)
486 | if typefst in [tuple] + ITER_TYPES + STR_TYPES:
487 | typesnd = type(snd)
488 | # nodes[N]
489 | if typesnd in NUM_TYPES or typesnd is str and snd.isdigit():
490 | n = int(snd)
491 | if D:
492 | self.info(
493 | "getting %sth element from '%s'", color.bold(n),
494 | color.bold(fst)
495 | )
496 | if typefst in (generator, chain):
497 | if n > 0:
498 | return skip(fst, n)
499 | elif n == 0:
500 | return next(fst)
501 | else:
502 | fst = list(fst)
503 | else:
504 | try:
505 | return fst[n]
506 | except (IndexError, TypeError):
507 | return None
508 | # $.*['string']==$.string
509 | if type(snd) in STR_TYPES:
510 | return exe((".", fst, snd))
511 | else:
512 | # $.*[@.string] - bad syntax, but allowed
513 | return snd
514 | else:
515 | try:
516 | if D: self.debug("returning %s", color.bold(fst[snd]))
517 | return fst[snd]
518 | except KeyError:
519 | # CHECK - is it ok to do that or should it be ProgrammingError?
520 | if D: self.debug("returning an empty list")
521 | return []
522 | raise ProgrammingError(
523 | "Wrong usage of " + color.bold("[") + " operator"
524 | )
525 | elif op == "fn":
526 | # Built-in functions
527 | fnName = node[1]
528 | args = None
529 | try:
530 | args = [exe(x) for x in node[2:]]
531 | except IndexError:
532 | if D:
533 | self.debug("NOT ERROR: can't map '%s' with '%s'", node[2:], exe)
534 | # arithmetic
535 | if fnName == "sum":
536 | args = args[0]
537 | if type(args) in NUM_TYPES:
538 | return args
539 | return sum((x for x in args if type(x) in NUM_TYPES))
540 | elif fnName == "max":
541 | args = args[0]
542 | if type(args) in NUM_TYPES:
543 | return args
544 | return max((x for x in args if type(x) in NUM_TYPES))
545 | elif fnName == "min":
546 | args = args[0]
547 | if type(args) in NUM_TYPES:
548 | return args
549 | return min((x for x in args if type(x) in NUM_TYPES))
550 | elif fnName == "avg":
551 | args = args[0]
552 | if type(args) in NUM_TYPES:
553 | return args
554 | if type(args) not in ITER_TYPES:
555 | raise Exception("Argument for avg() is not an array")
556 | else:
557 | args = list(args)
558 | try:
559 | return sum(args)/float(len(args))
560 | except TypeError:
561 | args = [x for x in args if type(x) in NUM_TYPES]
562 | self.warning("Some items in array were ommited")
563 | return sum(args)/float(len(args))
564 | elif fnName == "round":
565 | return round(*args)
566 | # casting
567 | elif fnName == "int":
568 | return int(args[0])
569 | elif fnName == "float":
570 | return float(args[0])
571 | elif fnName == "str":
572 | return str(py2JSON(args[0]))
573 | elif fnName in ("list", "array"):
574 | try:
575 | a = args[0]
576 | except IndexError:
577 | return []
578 | targs = type(a)
579 | if targs is timeutils.datetime.datetime:
580 | return timeutils.date2list(a) + timeutils.time2list(a)
581 | if targs is timeutils.datetime.date:
582 | return timeutils.date2list(a)
583 | if targs is timeutils.datetime.time:
584 | return timeutils.time2list(a)
585 | return list(a)
586 | # string
587 | elif fnName == "upper":
588 | return args[0].upper()
589 | elif fnName == "lower":
590 | return args[0].lower()
591 | elif fnName == "capitalize":
592 | return args[0].capitalize()
593 | elif fnName == "title":
594 | return args[0].title()
595 | elif fnName == "split":
596 | return args[0].split(*args[1:])
597 | elif fnName == "slice":
598 | if args and type(args[1]) not in ITER_TYPES:
599 | raise ExecutionError(
600 | "Wrong usage of slice(STRING, ARRAY). Second argument is not an array but %s."
601 | % color.bold(type(args[1]).__name__)
602 | )
603 | try:
604 | pos = list(args[1])
605 | if type(pos[0]) in ITER_TYPES:
606 | if D: self.debug("run slice() for a list of slicers")
607 | return (args[0][x[0]:x[1]] for x in pos)
608 | return args[0][pos[0]:pos[1]]
609 | except IndexError:
610 | if len(args) != 2:
611 | raise ProgrammingError(
612 | "Wrong usage of slice(STRING, ARRAY). Provided %s argument, should be exactly 2."
613 | % len(args)
614 | )
615 | elif fnName == "escape":
616 | global escape, escapeDict
617 | if not escape:
618 | from objectpath.utils import escape, escapeDict
619 | return escape(args[0], escapeDict)
620 | elif fnName == "unescape":
621 | global unescape, unescapeDict
622 | if not unescape:
623 | from objectpath.utils import unescape, unescapeDict
624 | return unescape(args[0], unescapeDict)
625 | elif fnName == "replace":
626 | if sys.version_info[0] < 3 and type(args[0]) is unicode:
627 | args[0] = args[0].encode("utf8")
628 | return str.replace(args[0], args[1], args[2])
629 | # TODO this should be supported by /regex/
630 | # elif fnName=="REsub":
631 | # return re.sub(args[1],args[2],args[0])
632 | elif fnName == "sort":
633 | if len(args) > 1:
634 | key = args[1]
635 | a = {"key": lambda x: x.get(key, 0)}
636 | else:
637 | a = {}
638 | args = args[0]
639 | if D: self.debug("doing sort on '%s'", args)
640 | try:
641 | return sorted(args, **a)
642 | except TypeError:
643 | return args
644 | elif fnName == "reverse":
645 | args = args[0]
646 | try:
647 | args.reverse()
648 | return args
649 | except TypeError:
650 | return args
651 | elif fnName == "unique":
652 | try:
653 | return list(set(args[0]))
654 | except TypeError:
655 | return args[0]
656 | elif fnName == "map":
657 | return chain.from_iterable(map(lambda x: exe(("fn", args[0], x)), args[1]))
658 | elif fnName in ("count", "len"):
659 | args = args[0]
660 | if args in (True, False, None):
661 | return args
662 | if type(args) in ITER_TYPES:
663 | return len(list(args))
664 | return len(args)
665 | elif fnName == "join":
666 | try:
667 | joiner = args[1]
668 | except Exception:
669 | joiner = ""
670 | try:
671 | return joiner.join(args[0])
672 | except TypeError:
673 | try:
674 | return joiner.join(map(str, args[0]))
675 | except Exception:
676 | return args[0]
677 | # time
678 | elif fnName in ("now", "age", "time", "date", "dateTime"):
679 | if fnName == "now":
680 | return timeutils.now()
681 | if fnName == "date":
682 | return timeutils.date(args)
683 | if fnName == "time":
684 | return timeutils.time(args)
685 | if fnName == "dateTime":
686 | return timeutils.dateTime(args)
687 | # TODO move lang to localize() entirely!
688 | if fnName == "age":
689 | a = {}
690 | if len(args) > 1:
691 | a["reference"] = args[1]
692 | if len(args) > 2:
693 | a["lang"] = args[2]
694 | return list(timeutils.age(args[0], **a))
695 | elif fnName == "toMillis":
696 | args = args[0]
697 | if args.utcoffset() is not None:
698 | args = args - args.utcoffset() # pylint: disable=E1103
699 | global calendar
700 | if not calendar:
701 | import calendar
702 | return int(
703 | calendar.timegm(args.timetuple())*1000 + args.microsecond/1000
704 | )
705 | elif fnName == "localize":
706 | if type(args[0]) is timeutils.datetime.datetime:
707 | return timeutils.UTC2local(*args)
708 | # polygons
709 | elif fnName == "area":
710 |
711 | def segments(p):
712 | p = list(map(lambda x: x[0:2], p))
713 | return zip(p, p[1:] + [p[0]])
714 |
715 | return 0.5*abs(
716 | sum(x0*y1 - x1*y0 for ((x0, y0), (x1, y1)) in segments(args[0]))
717 | )
718 | # misc
719 | elif fnName == "keys":
720 | try:
721 | return list(args[0].keys())
722 | except AttributeError:
723 | raise ExecutionError(
724 | "Argument is not " + color.bold("object") +
725 | " but %s in keys()" % color.bold(type(args[0]).__name__)
726 | )
727 | elif fnName == "values":
728 | try:
729 | return list(args[0].values())
730 | except AttributeError:
731 | raise ExecutionError(
732 | "Argument is not " + color.bold("object") +
733 | " but %s in values()" % color.bold(type(args[0]).__name__)
734 | )
735 | elif fnName == "type":
736 | ret = type(args[0])
737 | if ret in ITER_TYPES:
738 | return "array"
739 | if ret is dict:
740 | return "object"
741 | return ret.__name__
742 | elif fnName in self._REGISTERED_FUNCTIONS:
743 | return self._REGISTERED_FUNCTIONS[fnName](*args)
744 | else:
745 | raise ProgrammingError(
746 | "Function " + color.bold(fnName) + " does not exist."
747 | )
748 | else:
749 | return node
750 |
751 | D = self.D
752 | if type(expr) in STR_TYPES:
753 | tree = self.compile(expr)
754 | elif type(expr) not in (tuple, list, dict):
755 | return expr
756 | ret = exe(tree)
757 | if D: self.end("Tree.execute with: %s", color.bold(self.cleanOutput(ret)))
758 | return ret
759 |
760 | def __str__(self):
761 | return "TreeObject()"
762 |
763 | def __repr__(self):
764 | return self.__str__()
765 |
--------------------------------------------------------------------------------
/objectpath/core/parser.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | # This file is part of ObjectPath released under MIT license.
4 | # Copyright (C) 2010-2014 Adrian Kalbarczyk
5 |
6 | # Code from http://effbot.org/zone/simple-top-down-parsing.htm was used in this file.
7 | # Licence of the code is public domain.
8 | # Relicenced to MIT by Adrian Kalbarczyk and:
9 | # - specialized to work with ObjectPath,
10 | # - optimized
11 |
12 | import sys
13 |
14 | if sys.version_info[0] >= 3:
15 | from io import StringIO
16 | else:
17 | from cStringIO import StringIO
18 |
19 | from objectpath.core import SELECTOR_OPS, NUM_TYPES
20 |
21 | symbol_table = {}
22 | token = nextToken = None
23 | # TODO optimization ('-',1) -> -1
24 | # TODO optimization operators should be numbers
25 |
26 | TRUE = ["true", "t"]
27 | FALSE = ["false", "f"]
28 | NONE = ["none", "null", "n", "nil"]
29 |
30 | class symbol_base(object):
31 | id = None
32 | value = None
33 | fst = snd = third = None
34 |
35 | def nud(self):
36 | raise SyntaxError("Syntax error (%r)." % self.id)
37 |
38 | def led(self):
39 | raise SyntaxError("Unknown operator (%r)." % self.id)
40 |
41 | def getTree(self):
42 | if self.id == "(name)":
43 | val = self.value.lower()
44 | if val in TRUE:
45 | return True
46 | elif val in FALSE:
47 | return False
48 | elif val in NONE:
49 | return None
50 | return (self.id[1:-1], self.value)
51 | elif self.id == "(number)":
52 | return self.value
53 | elif self.id == "(literal)":
54 | fstLetter = self.value[0]
55 | if fstLetter in ["'", "\""]:
56 | return self.value[1:-1]
57 | # elif fstLetter.isdigit():
58 | # try:
59 | # return int(self.value)
60 | # except:
61 | # return float(self.value)
62 | else:
63 | if self.value == "True":
64 | return True
65 | elif self.value == "False":
66 | return False
67 | elif self.value == "None":
68 | return None
69 | ret = [self.id]
70 | ret_append = ret.append
71 | L = (dict, tuple, list)
72 | for i in filter(None, [self.fst, self.snd, self.third]):
73 | if type(i) is str:
74 | ret_append(i)
75 | elif type(i) in L:
76 | t = []
77 | t_append = t.append
78 | if self.id == "{":
79 | ret = {}
80 | for j in list(self.fst.items()):
81 | ret[j[0].getTree()] = j[1].getTree()
82 | return ret
83 | for j in i:
84 | try:
85 | t_append(j.getTree())
86 | except Exception:
87 | t_append(j)
88 | if self.id in ("[", ".", ".."):
89 | ret.append(t)
90 | else:
91 | ret.extend(t)
92 | # ret_append(t)
93 | # return (self.id,ret[1:])
94 | else:
95 | if type(self.fst.value) in NUM_TYPES and self.snd is None:
96 | if self.id == "-":
97 | return -self.fst.value
98 | if self.id == "+":
99 | return self.fst.value
100 | ret_append(i.getTree())
101 | if self.id == "{":
102 | return {}
103 | # if self.id == "[" and self.fst == []:
104 | # return []
105 | if self.id == "(":
106 | # this will produce ("fn","fnName",arg1,arg2,...argN)
107 | # try:
108 | return tuple(["fn", ret[1][1]] + ret[2:])
109 | # except:
110 | # pass
111 | return tuple(ret)
112 |
113 | def __repr__(self):
114 | if self.id == "(name)" or self.id == "(literal)":
115 | return "(%s:%s)" % (self.id[1:-1], self.value)
116 | out = [self.id, self.fst, self.snd, self.third]
117 | # out=list(map(str, filter(None, out)))
118 | return "(" + " ".join(out) + ")"
119 |
120 | def symbol(ID, bp=0):
121 | try:
122 | s = symbol_table[ID]
123 | except KeyError:
124 |
125 | class s(symbol_base):
126 | pass
127 |
128 | s.__name__ = "symbol-" + ID # for debugging
129 | s.id = ID
130 | s.value = None
131 | s.lbp = bp
132 | symbol_table[ID] = s
133 | else:
134 | s.lbp = max(bp, s.lbp)
135 | return s
136 |
137 | # helpers
138 |
139 | def infix(ID, bp):
140 | def led(self, left):
141 | self.fst = left
142 | self.snd = expression(bp)
143 | return self
144 |
145 | symbol(ID, bp).led = led
146 |
147 | def infix_r(ID, bp):
148 | def led(self, left):
149 | self.fst = left
150 | self.snd = expression(bp - 1)
151 | return self
152 |
153 | symbol(ID, bp).led = led
154 |
155 | def prefix(ID, bp):
156 | def nud(self):
157 | self.fst = expression(bp)
158 | return self
159 |
160 | symbol(ID).nud = nud
161 |
162 | def advance(ID=None):
163 | global token
164 | if ID and token.id != ID:
165 | raise SyntaxError("Expected %r, got %s" % (ID, token.id))
166 | token = nextToken()
167 |
168 | def method(s):
169 | # decorator
170 | assert issubclass(s, symbol_base)
171 |
172 | def bind(fn):
173 | setattr(s, fn.__name__, fn)
174 |
175 | return bind
176 |
177 | infix_r("or", 30)
178 | infix_r("and", 40)
179 | prefix("not", 50)
180 | infix("in", 60)
181 | infix("not", 60) # not in
182 | infix("is", 60)
183 | infix("matches", 60)
184 | infix("<", 60)
185 | infix("<=", 60)
186 | infix(">", 60)
187 | infix(">=", 60)
188 | # infix(" ", 60); infix("!=", 60); infix("==", 60)
189 | # infix("&", 90)
190 | # infix("<<", 100); infix(">>", 100)
191 | infix("+", 110)
192 | infix("-", 110)
193 | infix("*", 120)
194 | infix("/", 120)
195 | infix("//", 120)
196 | infix("%", 120)
197 | prefix("-", 130)
198 | prefix("+", 130)
199 | #prefix("~", 130)
200 | # infix_r("**", 140)
201 | symbol(".", 150)
202 | symbol("[", 150)
203 | symbol("{", 150)
204 | symbol("(", 150)
205 | # additional behavior
206 | symbol("(name)").nud = lambda self: self
207 | symbol("(literal)").nud = lambda self: self
208 | symbol("(number)").nud = lambda self: self
209 | symbol("(end)")
210 | symbol(")")
211 |
212 | # REGEX
213 | infix("|", 0)
214 | infix("^", 0)
215 | infix("?", 0)
216 | infix("\\", 0)
217 |
218 | symbol("@")
219 |
220 | @method(symbol("@"))
221 | def nud(self): # pylint: disable=E0102
222 | self.id = "(current)"
223 | return self
224 |
225 | symbol("!")
226 |
227 | @method(symbol("!"))
228 | def nud(self): # pylint: disable=E0102
229 | self.id = "(node)"
230 | return self
231 |
232 | # RegEx
233 | @method(symbol("/"))
234 | def nud(self): # pylint: disable=E0102
235 | self.id = "re"
236 | regex = []
237 | if token.id != "/":
238 | self_fst_append = regex.append
239 | while 1:
240 | if token.id == "/":
241 | break
242 | if token.id in ["(name)", "(number)"]:
243 | self_fst_append(str(token.value))
244 | else:
245 | self_fst_append(token.id)
246 | advance()
247 | self.fst = "".join(regex).replace("\\", "\\\\")
248 | advance("/")
249 | return self
250 |
251 | @method(symbol("("))
252 | def nud(self): # pylint: disable=E0102,W0613
253 | expr = expression()
254 | advance(")")
255 | return expr
256 |
257 | symbol(",")
258 |
259 | @method(symbol("."))
260 | def led(self, left): # pylint: disable=E0102
261 | attr = False
262 | if token.id == ".":
263 | self.id = ".."
264 | advance()
265 | if token.id == "@":
266 | attr = True
267 | advance()
268 | if token.id == "(":
269 | advance()
270 | self.fst = left
271 | self.snd = []
272 | if token.id != ")":
273 | self_snd_append = self.snd.append
274 | while 1:
275 | self_snd_append(expression())
276 | if token.id != ",":
277 | break
278 | advance(",")
279 | advance(")")
280 | return self
281 | if token.id not in ["(name)", "*", "(literal)", "("]:
282 | raise SyntaxError("Expected an attribute name.")
283 | self.fst = left
284 | if attr:
285 | token.value = "@" + token.value
286 | self.snd = token
287 | advance()
288 | return self
289 |
290 | # handling namespaces; e.g $.a.b.c or $ss.a.b.c
291 | # default storage is the request namespace
292 | symbol("$")
293 |
294 | @method(symbol("$"))
295 | def nud(self): # pylint: disable=E0102
296 | global token # pylint: disable=W0602
297 | self.id = "(root)"
298 | if token.id == ".":
299 | self.fst = "rs"
300 | else:
301 | self.fst = token.value
302 | advance()
303 | return self
304 |
305 | symbol("]")
306 |
307 | @method(symbol("["))
308 | def led(self, left): # pylint: disable=E0102
309 | self.fst = left
310 | self.snd = expression()
311 | advance("]")
312 | return self
313 |
314 | symbol(",")
315 |
316 | # this is for built-in functions
317 | @method(symbol("("))
318 | def led(self, left): # pylint: disable=E0102
319 | # self.id="fn"
320 | self.fst = left
321 | self.snd = []
322 | if token.id != ")":
323 | self_snd_append = self.snd.append
324 | while 1:
325 | self_snd_append(expression())
326 | if token.id != ",":
327 | break
328 | advance(",")
329 | advance(")")
330 | return self
331 |
332 | symbol(":")
333 | symbol("=")
334 |
335 | # constants
336 |
337 | def constant(ID):
338 | @method(symbol(ID))
339 | def nud(self): # pylint: disable=W0612
340 | self.id = "(literal)"
341 | self.value = ID
342 | return self
343 |
344 | constant("None")
345 | constant("True")
346 | constant("False")
347 |
348 | # multitoken operators
349 |
350 | @method(symbol("not"))
351 | def led(self, left): # pylint: disable=E0102
352 | if token.id != "in":
353 | raise SyntaxError("Invalid syntax")
354 | advance()
355 | self.id = "not in"
356 | self.fst = left
357 | self.snd = expression(60)
358 | return self
359 |
360 | @method(symbol("is"))
361 | def led(self, left): # pylint: disable=E0102
362 | if token.id == "not":
363 | advance()
364 | self.id = "is not"
365 | self.fst = left
366 | self.snd = expression(60)
367 | return self
368 |
369 | symbol("]")
370 |
371 | @method(symbol("["))
372 | def nud(self): # pylint: disable=E0102
373 | self.fst = []
374 | if token.id != "]":
375 | while 1:
376 | if token.id == "]":
377 | break
378 | self.fst.append(expression())
379 | if token.id not in SELECTOR_OPS + [","]:
380 | break
381 | advance(",")
382 | advance("]")
383 | return self
384 |
385 | symbol("}")
386 |
387 | @method(symbol("{"))
388 | def nud(self): # pylint: disable=E0102
389 | self.fst = {}
390 | if token.id != "}":
391 | while 1:
392 | if token.id == "}":
393 | break
394 | key = expression()
395 | advance(":")
396 | self.fst[key] = expression()
397 | if token.id != ",":
398 | break
399 | advance(",")
400 | advance("}")
401 | return self
402 |
403 | import tokenize as tokenizer
404 | type_map = {
405 | tokenizer.NUMBER: "(number)",
406 | tokenizer.STRING: "(literal)",
407 | tokenizer.OP: "(operator)",
408 | tokenizer.NAME: "(name)",
409 | tokenizer.ERRORTOKEN:
410 | "(operator)" #'$' is recognized in python tokenizer as error token!
411 | }
412 |
413 | # python tokenizer
414 | def tokenize_python(program):
415 | if sys.version_info[0] < 3:
416 | tokens = tokenizer.generate_tokens(StringIO(program).next)
417 | else:
418 | tokens = tokenizer.generate_tokens(StringIO(program).__next__)
419 | for t in tokens:
420 | # print type_map[t[0]], t[1]
421 | try:
422 | # change this to output python values in correct type
423 | yield type_map[t[0]], t[1]
424 | except KeyError:
425 | if t[0] in [tokenizer.NL, tokenizer.COMMENT, tokenizer.NEWLINE]:
426 | continue
427 | if t[0] == tokenizer.ENDMARKER:
428 | break
429 | else:
430 | raise SyntaxError("Syntax error")
431 | yield "(end)", "(end)"
432 |
433 | def tokenize(program):
434 | if isinstance(program, list):
435 | source = program
436 | else:
437 | source = tokenize_python(program)
438 | for ID, value in source:
439 | if ID == "(literal)":
440 | symbol = symbol_table[ID]
441 | s = symbol()
442 | s.value = value
443 | elif ID == "(number)":
444 | symbol = symbol_table[ID]
445 | s = symbol()
446 | try:
447 | s.value = int(value)
448 | except Exception:
449 | s.value = float(value)
450 | elif value == " ":
451 | continue
452 | else:
453 | # name or operator
454 | symbol = symbol_table.get(value)
455 | if symbol:
456 | s = symbol()
457 | elif ID == "(name)":
458 | symbol = symbol_table[ID]
459 | s = symbol()
460 | s.value = value
461 | else:
462 | raise SyntaxError("Unknown operator '%s', '%s'" % (ID, value))
463 | yield s
464 |
465 | # parser engine
466 | def expression(rbp=0):
467 | global token
468 | t = token
469 | token = nextToken()
470 | left = t.nud()
471 | while rbp < token.lbp:
472 | t = token
473 | token = nextToken()
474 | left = t.led(left)
475 | return left
476 |
477 | def parse(expr, D=False):
478 | if sys.version_info[0] < 3 and type(expr) is unicode:
479 | expr = expr.encode("utf8")
480 | if type(expr) is not str:
481 | return expr
482 | expr = expr.strip()
483 | global token, nextToken
484 | if sys.version_info[0] >= 3:
485 | nextToken = tokenize(expr).__next__
486 | else:
487 | nextToken = tokenize(expr).next
488 | token = nextToken()
489 | r = expression().getTree()
490 | if D:
491 | print("PARSE STAGE")
492 | print(r)
493 | return r
494 |
--------------------------------------------------------------------------------
/objectpath/shell.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # This file is part of ObjectPath released under MIT license.
5 | # Copyright (C) 2010-2014 Adrian Kalbarczyk
6 |
7 | import argparse
8 | import os
9 | import sys
10 | try:
11 | import readline
12 | # this is to prevent various tools from deleting import readline
13 | ___x = readline.__doc__
14 | except ImportError:
15 | import pyreadline as readline
16 |
17 | from objectpath import Tree, ITER_TYPES
18 | from objectpath.utils.colorify import * # pylint: disable=W0614
19 | from objectpath.utils import json_ext as json
20 | from objectpath.utils.json_ext import printJSON
21 |
22 | try:
23 | import pytz
24 | except ImportError:
25 | if os.isatty(sys.stdin.fileno()) and sys.stdout.isatty():
26 | print("WARNING! pytz is not installed. Localized times are not supported.")
27 |
28 | def main():
29 | parser = argparse.ArgumentParser(description='Command line options')
30 | parser.add_argument(
31 | '-u', '--url', dest='URL', help='URL containing JSON document.'
32 | )
33 | # parser.add_argument('-xml', dest='xml', help='[EXPERIMENTAL] Expect XML input.',action='store_true')
34 | parser.add_argument(
35 | '-d',
36 | '--debug',
37 | dest='debug',
38 | help='Debbuging on/off.',
39 | action='store_true'
40 | )
41 | parser.add_argument(
42 | '-p',
43 | '--profile',
44 | dest='profile',
45 | help='Profiling on/off.',
46 | action='store_true'
47 | )
48 | parser.add_argument(
49 | '-e',
50 | '--expr',
51 | dest='expr',
52 | help='Expression/query to execute on file, print on stdout and exit.'
53 | )
54 | parser.add_argument('file', metavar='FILE', nargs="?", help='File name')
55 |
56 | args = parser.parse_args()
57 | a = {}
58 | expr = args.expr
59 |
60 | if not expr:
61 | print(
62 | bold("ObjectPath interactive shell") + "\n" + bold("ctrl+c") +
63 | " to exit, documentation at " +
64 | const("http://adriank.github.io/ObjectPath") + ".\n"
65 | )
66 |
67 | if args.debug:
68 | a["debug"] = True
69 | if args.profile:
70 | try:
71 | from guppy import hpy
72 | except:
73 | pass
74 | File = args.file
75 | # if args.xml:
76 | # from utils.xmlextras import xml2tree
77 | src = False
78 |
79 | if args.URL:
80 | if sys.version_info[0] >= 3:
81 | from urllib.request import Request, build_opener # pylint: disable=E0611
82 | else:
83 | from urllib2 import Request, build_opener
84 | request = Request(args.URL)
85 | opener = build_opener()
86 | request.add_header('User-Agent', 'ObjectPath/1.0 +http://objectpath.org/')
87 | src = opener.open(request)
88 | elif File:
89 | src = open(File, "r")
90 |
91 | if not src:
92 | if not expr:
93 | print(
94 | "JSON document source not specified. Working with an empty object {}."
95 | )
96 | tree = Tree({}, a)
97 | else:
98 | if not expr:
99 | sys.stdout.write(
100 | "Loading JSON document from " + str(args.URL or File) + "..."
101 | )
102 | sys.stdout.flush()
103 | # if args.xml:
104 | # tree=Tree(json.loads(json.dumps(xml2tree(src))),a)
105 | # else:
106 | tree = Tree(json.load(src), a)
107 | if not expr: print(" " + bold("done") + ".")
108 |
109 | if expr:
110 | if args.profile:
111 | import cProfile, pstats, StringIO
112 | pr = cProfile.Profile()
113 | pr.enable()
114 | try:
115 | ret = tree.execute(expr)
116 | except Exception as e:
117 | print(e.__class__.__name__ + ": " + str(e))
118 | exit(1)
119 | if type(ret) in ITER_TYPES:
120 | ret = list(ret)
121 | print(json.dumps(ret))
122 | if args.profile:
123 | pr.disable()
124 | s = StringIO.StringIO()
125 | sortby = 'cumulative'
126 | ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
127 | ps.print_stats()
128 | print(s.getvalue())
129 | return
130 |
131 | try:
132 | while True:
133 | limitResult = 5
134 | try:
135 | if sys.version >= '3':
136 | r = input(">>> ")
137 | else:
138 | r = raw_input(">>> ")
139 |
140 | if r.startswith("all"):
141 | limitResult = -1
142 | r = tree.execute(r[3:].strip())
143 | else:
144 | r = tree.execute(r)
145 |
146 | # python 3 raises error here - unicode is not a proper type there
147 | try:
148 | if type(r) is unicode:
149 | r = r.encode("utf8")
150 | except NameError:
151 | pass
152 | print(printJSON(r, length=limitResult))
153 | if args.profile:
154 | h = hpy()
155 | print(h.heap())
156 | except Exception as e:
157 | print(e)
158 | except KeyboardInterrupt:
159 | pass
160 | # new line at the end forces command prompt to apear at left
161 | print(bold("\nbye!"))
162 |
163 | if __name__ == "__main__":
164 | main()
165 |
--------------------------------------------------------------------------------
/objectpath/utils/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # This file is part of ObjectPath released under MIT license.
5 | # Copyright (C) 2010-2014 Adrian Kalbarczyk
6 |
7 | from itertools import islice
8 | from xml.sax.saxutils import escape, unescape
9 | from objectpath.core import NUM_TYPES, generator, chain, ITER_TYPES
10 |
11 | escape = escape
12 | unescape = unescape
13 | unescapeDict = {"'": "'", """: "\""}
14 | escapeDict = {"'": "'", "\"": """}
15 |
16 | # islice=islice is an optimization
17 | def skip(iterable, n, islice=islice):
18 | try:
19 | return next(islice(iterable, n, None))
20 | except StopIteration:
21 | return None
22 | # raise IndexError("generator index out of range")
23 |
24 | def filter_dict(iterable, keys):
25 | """
26 | filters keys of each element of iterable
27 | $.(a,b) returns all objects from array that have at least one of the keys:
28 | [1,"aa",{"a":2,"c":3},{"c":3},{"a":1,"b":2}].(a,b) -> [{"a":2},{"a":1,"b":2}]
29 | """
30 | if type(keys) is not list:
31 | keys = [keys]
32 | for i in iterable:
33 | try:
34 | d = {}
35 | for a in keys:
36 | try:
37 | d[a] = i[a]
38 | except KeyError:
39 | pass
40 | if d != {}:
41 | yield d
42 | except Exception:
43 | pass
44 |
45 | def flatten(fragment, skip=False):
46 | def rec(frg):
47 | typefrg = type(frg)
48 | if typefrg in ITER_TYPES:
49 | for i in frg:
50 | for j in rec(i):
51 | yield j
52 | elif typefrg is dict:
53 | yield frg
54 | for i in frg.items():
55 | for j in rec(i[1]):
56 | yield j
57 |
58 | g = rec(fragment)
59 | if skip:
60 | for i in xrange(skip):
61 | g.next()
62 | for i in g:
63 | yield i
64 |
--------------------------------------------------------------------------------
/objectpath/utils/colorify.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | def color(c, s):
5 | return '\033[%sm%s\033[0m' % (c, s)
6 |
7 | def bold(s):
8 | return color(1, s)
9 |
10 | def op(s):
11 | return color(32, bold(s))
12 |
13 | def const(s):
14 | return color(36, bold(s))
15 |
16 | def string(s):
17 | return color(33, bold(s))
18 |
--------------------------------------------------------------------------------
/objectpath/utils/debugger.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # move dbgMap outside
5 | import inspect
6 | from objectpath.core import ITER_TYPES
7 |
8 | class Debugger(object):
9 | dbg = None
10 | _debugStr = None
11 | dbgfn = None
12 | level = 40
13 | CUT_AFTER = 100
14 | CRITICAL = 50
15 | ERROR = 40
16 | WARNING = 30
17 | INFO = START = END = 20
18 | DEBUG = 10
19 | doDebug = False
20 |
21 | def __init__(self):
22 | self._debugStr = []
23 | self.dbgfn = self.consolelog
24 | self.dbgMap = {
25 | "debug": self.DEBUG,
26 | "info": self.INFO,
27 | "start": self.START,
28 | "end": self.END,
29 | "warning": self.WARNING,
30 | "error": self.ERROR,
31 | "critical": self.CRITICAL
32 | }
33 | try:
34 | self.doDebug = True
35 | self.level = self.dbgMap["debug"]
36 | self.info("All strings will be cut to %s chatacters." % self.CUT_AFTER)
37 | except (KeyError, TypeError):
38 | pass
39 |
40 | def debug(self, *s):
41 | if self.dbgfn and self.level <= self.DEBUG:
42 | self.dbgfn("DEBUG", s)
43 |
44 | def info(self, *s):
45 | if self.dbgfn and self.level <= self.INFO:
46 | self.dbgfn("INFO", s)
47 |
48 | def start(self, *s):
49 | if self.dbgfn and self.level <= self.INFO:
50 | self.dbgfn("START", s)
51 |
52 | def end(self, *s):
53 | if self.dbgfn and self.level <= self.INFO:
54 | self.dbgfn("END", s)
55 |
56 | def warning(self, *s):
57 | if self.dbgfn and self.level <= self.WARNING:
58 | self.dbgfn("WARNING", s)
59 |
60 | def error(self, *s):
61 | if self.dbgfn and self.level <= self.ERROR:
62 | self.dbgfn("ERROR", s)
63 |
64 | def critical(self, *s):
65 | if self.dbgfn and self.level <= self.CRITICAL:
66 | self.dbgfn("CRITICAL", s)
67 |
68 | def lineno(self):
69 | """Returns the current line number in our program."""
70 | return inspect.currentframe().f_back.f_back.f_back.f_lineno
71 |
72 | def cleanOutput(self, o):
73 | toOutput = o
74 | if type(toOutput) in ITER_TYPES:
75 | toOutput = list(toOutput)
76 | if type(toOutput) is tuple:
77 | return tuple(map(lambda x: type(x) in ITER_TYPES and list(x) or x, toOutput))
78 | return toOutput
79 |
80 | def consolelog(self, lvl, s):
81 | def f(x):
82 | try:
83 | if type(x) is unicode:
84 | x = x.encode("utf8")
85 | except NameError:
86 | pass
87 | if self.CUT_AFTER and type(x) is dict:
88 | s = []
89 | for i in x.items():
90 | s.append("'%s': %s" % (i[0], repr(i[1])[:self.CUT_AFTER]))
91 | if len(s[-1]) > self.CUT_AFTER:
92 | s.append("...\033[0m")
93 | return "{\n\t" + ",\n\t".join(s) + "\n}"
94 | s = str(x).replace("\n", "").replace("\t", "").replace("u'", "'")
95 | if self.CUT_AFTER and len(s) > self.CUT_AFTER:
96 | return s[:self.CUT_AFTER] + "...\033[0m"
97 | else:
98 | return x
99 |
100 | if len(s) > 1:
101 | v = tuple(map(f, s[1:]))
102 | self._debugStr.append((lvl, s[0] % v))
103 | print(
104 | lvl + "@" + str(self.lineno()) + " " + (s[0] % v).replace("u'", "'")
105 | )
106 | else:
107 | self._debugStr.append((lvl, s[0]))
108 | print(lvl + "@" + str(self.lineno()) + " " + f(s[0]))
109 |
--------------------------------------------------------------------------------
/objectpath/utils/json_ext.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | try:
3 | import simplejson as json
4 | except ImportError:
5 | try:
6 | import json
7 | except ImportError:
8 | raise Exception("JSONNotFound")
9 | try:
10 | import ujson as json_fast
11 | except ImportError:
12 | json_fast = json
13 |
14 | from types import GeneratorType as generator
15 |
16 | from objectpath.core import ITER_TYPES, STR_TYPES, NUM_TYPES
17 | from objectpath.utils.colorify import * # pylint: disable=W0614
18 |
19 | load = json_fast.load
20 |
21 | def loads(s):
22 | if s.find("u'") != -1:
23 | s = s.replace("u'", "'")
24 | s = s.replace("'", '"')
25 | try:
26 | return json_fast.loads(s) # ,object_hook=object_hook)
27 | except ValueError as e:
28 | raise Exception(str(e) + " " + s)
29 |
30 | def default(obj):
31 | if isinstance(obj, generator):
32 | return list(obj)
33 |
34 | def dumps(s, default=default, indent=None):
35 | return json.dumps(s, default=default, indent=indent, separators=(',', ':'))
36 |
37 | dump = json.dump
38 |
39 | def py2JSON(o):
40 | if o is True:
41 | return 'true'
42 | if o is False:
43 | return 'false'
44 | if o is None:
45 | return 'null'
46 | if type(o) in NUM_TYPES:
47 | return o
48 | # TODO - check if that is correct
49 | if type(o) is tuple:
50 | return list(o)
51 | elif type(o) in ITER_TYPES + [list, str]:
52 | return o
53 | try:
54 | return str(o)
55 | except UnicodeEncodeError:
56 | return o.encode("utf8")
57 | except Exception:
58 | return o
59 |
60 | LAST_LIST = None
61 |
62 | def printJSON(o, length=5, depth=5):
63 | spaces = 2
64 |
65 | def plus():
66 | currIndent[0] += 1
67 |
68 | def minus():
69 | currIndent[0] -= 1
70 |
71 | def out(s):
72 | try:
73 | s = str(s)
74 | except Exception:
75 | pass
76 | if not ret:
77 | ret.append(s)
78 | elif ret[-1][-1] == "\n":
79 | ret.append(currIndent[0]*spaces*" " + s)
80 | else:
81 | ret.append(s)
82 |
83 | def rec(o):
84 | if type(o) in ITER_TYPES:
85 | o = list(o)
86 | if currDepth[0] >= depth:
87 | out("")
88 | return
89 | out("[")
90 | if len(o) > 0:
91 | if len(o) > 1:
92 | out("\n")
93 | plus()
94 | for i in o[0:length]:
95 | rec(i)
96 | out(",\n")
97 | if length == -1:
98 | rec(o[-1])
99 | out(",\n")
100 |
101 | if length != -1 and len(o) > length:
102 | out("... (" + str(len(o) - length) + " more items)\n")
103 | else:
104 | ret.pop()
105 | if len(o) > 1:
106 | out("\n")
107 | if len(o) > 1:
108 | minus()
109 | out("]")
110 |
111 | elif type(o) is dict:
112 | currDepth[0] += 1
113 | if currDepth[0] > depth:
114 | out("{...}")
115 | return
116 | keys = o.keys()
117 | out("{")
118 | if len(keys) > 0:
119 | if len(keys) > 1:
120 | plus()
121 | out("\n")
122 | for k in o.keys():
123 | out(string('"' + str(k) + '"') + ": ")
124 | rec(o[k])
125 | out(",\n")
126 | ret.pop()
127 | if len(keys) > 1:
128 | minus()
129 | out("\n")
130 | out("}")
131 | else:
132 | if type(o) in NUM_TYPES:
133 | out(const(o))
134 | elif o in [None, False, True]:
135 | out(const(py2JSON(o)))
136 | elif type(o) in STR_TYPES:
137 | out(string('"' + o + '"'))
138 | else:
139 | out(string(o))
140 | currDepth[0] -= 1
141 |
142 | currIndent = [0]
143 | currDepth = [0]
144 | ret = []
145 | rec(o)
146 | currIndent[0] = 0
147 | currDepth[0] = 0
148 | return "".join(ret)
149 |
--------------------------------------------------------------------------------
/objectpath/utils/timeutils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | # This file is part of ObjectPath released under MIT license.
5 | # Copyright (C) 2008-2010 Adrian Kalbarczyk
6 |
7 | import datetime
8 | import sys, os
9 | try:
10 | import pytz
11 | TIMEZONE_CACHE = {"UTC": pytz.utc}
12 | except ImportError:
13 | pass
14 |
15 | from objectpath.core import STR_TYPES
16 |
17 | HOURS_IN_DAY = 24
18 |
19 | now = datetime.datetime.now
20 |
21 | def round9_10(n):
22 | i = int(n)
23 | if n - i > 0.9:
24 | return i + 1
25 | return i
26 |
27 | # TODO its 31 minuta, should be 31 minut - probably done
28 |
29 | def age(date, reference=None, lang="en"):
30 | if reference is None:
31 | reference = now()
32 | td = reference - date #TimeDelta
33 |
34 | days = float(td.days)
35 | langIsPL = lang == "pl"
36 | if days:
37 | years = round9_10(days/356)
38 | if years:
39 | if langIsPL:
40 | return (years, years == 1 and "rok" or years < 5 and "lata" or "lat")
41 | else:
42 | return (years, years == 1 and "year" or "years")
43 |
44 | months = round9_10(days/30)
45 | if months:
46 | if langIsPL:
47 | return (
48 | months, months == 1 and "miesiąc" or 1 < months < 5 and "miesiące"
49 | or "miesięcy"
50 | )
51 | else:
52 | return (months, months == 1 and "month" or "months")
53 |
54 | weeks = round9_10(days/7)
55 | if weeks:
56 | if langIsPL:
57 | return (
58 | weeks, weeks == 1 and "tydzień"
59 | or weeks % 10 in [0, 1, 5, 6, 7, 8, 9] and "tygodni" or "tygodnie"
60 | )
61 | else:
62 | return (weeks, weeks == 1 and "week" or "weeks")
63 |
64 | days = int(days)
65 | if langIsPL:
66 | return (days, days == 1 and "dzień" or "dni")
67 | else:
68 | return (days, days == 1 and "day" or "days")
69 |
70 | seconds = float(td.seconds)
71 | if seconds is not None:
72 | hours = round9_10(seconds/3600)
73 | if hours:
74 | if langIsPL:
75 | return (
76 | hours, hours == 1 and "godzina" or 1 < hours < 5 and "godziny"
77 | or "godzin"
78 | )
79 | else:
80 | return (hours, hours == 1 and "hour" or "hours")
81 |
82 | minutes = round9_10(seconds/60)
83 | if minutes:
84 | if langIsPL:
85 | return (
86 | minutes, minutes == 1 and "minuta" or 1 < minutes < 5 and "minuty"
87 | or "minut"
88 | )
89 | else:
90 | return (minutes, minutes == 1 and "minute" or "minutes")
91 |
92 | seconds = int(seconds)
93 | if langIsPL:
94 | return (
95 | seconds, seconds == 1 and "sekunda" or 1 < seconds < 5 and "sekundy"
96 | or "sekund"
97 | )
98 | else:
99 | return (seconds, seconds == 1 and "second" or "seconds")
100 | # return (0,"seconds")
101 |
102 | def date(d):
103 | if d:
104 | d = d[0]
105 | t = type(d)
106 | if t is datetime.datetime:
107 | return datetime.date(d.year, d.month, d.day)
108 | if t in (tuple, list):
109 | return datetime.date(*d)
110 | return datetime.date.today()
111 |
112 | def date2list(d):
113 | return [d.year, d.month, d.day]
114 |
115 | def time(d):
116 | if not d or not d[0]:
117 | d = now()
118 | else:
119 | d = d[0]
120 | t = type(d)
121 | if t in (tuple, list):
122 | return datetime.time(*d)
123 | return datetime.time(d.hour, d.minute, d.second, d.microsecond)
124 |
125 | def time2list(t):
126 | return [t.hour, t.minute, t.second, t.microsecond]
127 |
128 | def addTimes(fst, snd):
129 | l1 = time2list(fst)
130 | l2 = time2list(snd)
131 | t = [l1[0] + l2[0], l1[1] + l2[1], l1[2] + l2[2], l1[3] + l2[3]]
132 | t2 = []
133 | one = 0
134 | ms = t[3]
135 | if ms >= 1000000:
136 | t2.append(ms - 1000000)
137 | one = 1
138 | else:
139 | t2.append(ms)
140 | for i in (t[2], t[1]):
141 | i = i + one
142 | one = 0
143 | if i >= 60:
144 | t2.append(i - 60)
145 | one = 1
146 | # elif i==60:
147 | # t2.append(0)
148 | # one=1
149 | else:
150 | t2.append(i)
151 | hour = t[0] + one
152 | if hour >= HOURS_IN_DAY:
153 | t2.append(hour - HOURS_IN_DAY)
154 | else:
155 | t2.append(hour)
156 | return datetime.time(*reversed(t2))
157 |
158 | def subTimes(fst, snd):
159 | l1 = time2list(fst)
160 | l2 = time2list(snd)
161 | t = [l1[0] - l2[0], l1[1] - l2[1], l1[2] - l2[2], l1[3] - l2[3]]
162 | t2 = []
163 | one = 0
164 | ms = t[3]
165 | if ms < 0:
166 | t2.append(1000000 + ms)
167 | one = 1
168 | else:
169 | t2.append(ms)
170 | for i in (t[2], t[1]):
171 | i = i - one
172 | one = 0
173 | if i >= 0:
174 | t2.append(i)
175 | else:
176 | t2.append(60 + i)
177 | one = 1
178 | hour = t[0] - one
179 | if hour < 0:
180 | t2.append(HOURS_IN_DAY + hour)
181 | else:
182 | t2.append(hour)
183 | return datetime.time(*reversed(t2))
184 |
185 | def dateTime(arg):
186 | """
187 | d may be:
188 | - datetime()
189 | - [y,m,d,h[,m[,ms]]]
190 | - [date(),time()]
191 | - [[y,m,d],[h,m,s,ms]]
192 | and permutations of above
193 | """
194 | l = len(arg)
195 | if l == 1:
196 | dt = arg[0]
197 | typed = type(dt)
198 | if typed is datetime.datetime:
199 | return dt
200 | if typed in (tuple, list) and len(dt) in [5, 6, 7]:
201 | return datetime.datetime(*dt)
202 | if l == 2:
203 | date = time = None
204 | typeArg0 = type(arg[0])
205 | typeArg1 = type(arg[1])
206 | if typeArg0 in STR_TYPES:
207 | return datetime.datetime.strptime(arg[0], arg[1])
208 | if typeArg0 is datetime.date:
209 | d = arg[0]
210 | date = [d.year, d.month, d.day]
211 | if typeArg0 in (tuple, list):
212 | date = arg[0]
213 | if typeArg1 is datetime.time:
214 | t = arg[1]
215 | time = [t.hour, t.minute, t.second, t.microsecond]
216 | if typeArg1 in (tuple, list):
217 | time = arg[1]
218 | return datetime.datetime(*date + time)
219 |
220 | # dt - dateTime, tzName is e.g. 'Europe/Warsaw'
221 | def UTC2local(dt, tzName="UTC"):
222 | try:
223 | if tzName in TIMEZONE_CACHE:
224 | tz = TIMEZONE_CACHE[tzName]
225 | else:
226 | tz = TIMEZONE_CACHE[tzName] = pytz.timezone(tzName)
227 | return TIMEZONE_CACHE["UTC"].localize(dt).astimezone(tz)
228 | except Exception:
229 | return dt
230 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | pytz
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [bdist_wheel]
2 | universal = 1
3 | [yapf]
4 | # split_all_comma_separated_values=True
5 | no_spaces_around_selected_binary_operators="*,/"
6 | indent_width=2
7 | dedent_closing_brackets=True
8 | coalesce_brackets=True
9 | blank_lines_around_top_level_definition=1
10 | continuation_indent_width=2
11 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | import os
2 | from setuptools import setup
3 |
4 | def read(*rnames):
5 | return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
6 |
7 | long_description = (read('README.rst') + '\n' + 'Download\n' '********\n')
8 |
9 | setup(
10 | name='objectpath',
11 | version=read('VER').strip(),
12 | description='The agile query language for semi-structured data. #JSON',
13 | long_description=long_description,
14 | url='http://adriank.github.io/ObjectPath',
15 | author='Adrian Kalbarczyk',
16 | author_email='adrian.kalbarczyk@gmail.com',
17 | license='MIT License',
18 | packages=['objectpath', 'objectpath.utils', 'objectpath.core'],
19 | # package_dir={'': 'objectpath'},
20 | keywords="query, tree, JSON, nested structures",
21 | classifiers=[
22 | "Development Status :: 6 - Mature", "Intended Audience :: Developers",
23 | "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License",
24 | "Programming Language :: Python",
25 | "Topic :: Software Development :: Libraries :: Python Modules"
26 | ],
27 | install_requires=[
28 | 'pytz',
29 | ],
30 | zip_safe=True,
31 | entry_points={'console_scripts': ['objectpath = objectpath.shell:main']},
32 | test_suite="tests"
33 | )
34 |
--------------------------------------------------------------------------------
/shell.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from objectpath import shell
5 |
6 | shell.main()
7 |
--------------------------------------------------------------------------------
/testObjectPath.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from tests import doTests
5 |
6 | # starts all test suites
7 | doTests()
8 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #unit tests for ACR functions
4 |
5 | from .test_ObjectPath import op_test
6 | from .test_utils import utils_test
7 |
8 | import unittest
9 |
10 | def doTests():
11 | print('Started ObjectPath Python implementation testing.\n')
12 | unittest.TextTestRunner(verbosity=2).run(op_test)
13 | unittest.TextTestRunner(verbosity=2).run(utils_test)
14 |
--------------------------------------------------------------------------------
/tests/test_ObjectPath.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from objectpath.core.interpreter import *
5 | from objectpath.core import ProgrammingError, ExecutionError
6 | from random import randint, choice
7 | #from bson.objectid import ObjectId
8 | import sys, unittest, os
9 |
10 | sys.setrecursionlimit(20000)
11 |
12 | object1 = {
13 | "__lang__": "en",
14 | "test": {
15 | "_id": 1,
16 | "name": "aaa",
17 | "o": {
18 | "_id": 2
19 | },
20 | "l": [{
21 | "_id": 3,
22 | "aaa": "ddd",
23 | "false": 2
24 | }, {
25 | "_id": 4
26 | }]
27 | }
28 | }
29 |
30 | object2 = {
31 | "store": {
32 | "book": [{
33 | "id":1,
34 | "category": "reference",
35 | "author": "Nigel Rees",
36 | "title": "Sayings of the Century",
37 | "price": 8.95
38 | },
39 | {
40 | "category": "fiction",
41 | "author": "Evelyn Waugh",
42 | "title": "Sword of Honour",
43 | "price": 12.99
44 | },
45 | {
46 | "category": "fiction",
47 | "author": "Herman Melville",
48 | "title": "Moby Dick",
49 | "isbn": "0-553-21311-3",
50 | "price": 8.99
51 | },
52 | {
53 | "category": "fiction",
54 | "author": "J. R. R. Tolkien",
55 | "title": "The Lord of the Rings",
56 | "isbn": "0-395-19395-8",
57 | "price": 22.99
58 | }],
59 | "bicycle": {
60 | "color": "red",
61 | "price": 19.95
62 | },
63 | "k": [{
64 | "_id": 4
65 | }]
66 | }
67 | }
68 |
69 | object3 = {
70 | "item_1": {
71 | "value": "foo",
72 | "x": 5.6,
73 | "y": 9
74 | },
75 | "item_2": {
76 | "value": "bar",
77 | "x": 5.6,
78 | "y": 9.891
79 | },
80 | "item_3": {
81 | "value": "foobar",
82 | "x": 5.6,
83 | "y": 9.8
84 | }
85 | }
86 |
87 | object4 = {
88 | "root": {
89 | "response": {
90 | "200": {
91 | "value": 5,
92 | },
93 | "201": {
94 | "value": 4,
95 | },
96 | "404": {
97 | "value": 0,
98 | }
99 | }
100 | }
101 | }
102 |
103 | tree1 = Tree(object1)
104 | tree2 = Tree(object2)
105 | tree3 = Tree(object3)
106 | tree4 = Tree(object4)
107 |
108 | def execute_raw(expr):
109 | return tree1.execute(expr)
110 |
111 | TYPES = [generator, chain]
112 | if sys.version_info.major > 2:
113 | TYPES += [map]
114 |
115 | TYPES = tuple(TYPES)
116 |
117 | def execute(expr):
118 | r = tree1.execute(expr)
119 | if isinstance(r, TYPES):
120 | return list(r)
121 | else:
122 | return r
123 |
124 | def execute2(expr):
125 | r = tree2.execute(expr)
126 | if isinstance(r, TYPES):
127 | return list(r)
128 | else:
129 | return r
130 |
131 | def execute3(expr):
132 | r = tree3.execute(expr)
133 | if isinstance(r, TYPES):
134 | return list(r)
135 | else:
136 | return r
137 |
138 | def execute4(expr):
139 | r = tree4.execute(expr)
140 | if isinstance(r, TYPES):
141 | return list(r)
142 | else:
143 | return r
144 |
145 | class ObjectPath(unittest.TestCase):
146 |
147 | def test_simple_types(self):
148 | self.assertEqual(execute("null"), None)
149 | self.assertEqual(execute("true"), True)
150 | self.assertEqual(execute("false"), False)
151 | self.assertEqual(execute("''"), "")
152 | self.assertEqual(execute('""'), "")
153 | self.assertEqual(execute("2"), 2)
154 | self.assertEqual(execute("2.0"), 2.0)
155 |
156 | def test_arrays(self):
157 | self.assertEqual(execute("[]"), [])
158 | self.assertEqual(list(execute("[1,2,3]")), [1, 2, 3])
159 | self.assertEqual(
160 | list(execute("[false,null,true,'',\"\",2,2.0,{}]")),
161 | [False, None, True, '', "", 2, 2.0, {}]
162 | )
163 |
164 | def test_objects(self):
165 | self.assertEqual(execute("{}"), {})
166 | self.assertEqual(
167 | execute("{a:1,b:false,c:'string'}"), {
168 | "a": 1,
169 | "b": False,
170 | "c": 'string'
171 | }
172 | )
173 | self.assertEqual(
174 | execute("{'a':1,'b':false,'c':'string'}"), {
175 | "a": 1,
176 | "b": False,
177 | "c": 'string'
178 | }
179 | )
180 |
181 | def test_arithm_add(self):
182 | self.assertEqual(execute("2+3"), 5)
183 | self.assertEqual(execute("2+3+4"), 9)
184 | self.assertEqual(execute("++3"), 3)
185 | # null is treated as neutral value
186 | self.assertEqual(execute("null+3"), 3)
187 | self.assertEqual(execute("3+null"), 3)
188 |
189 | def test_arithm_sub(self):
190 | self.assertEqual(execute("-1"), -1)
191 | self.assertEqual(execute("2-3"), 2 - 3)
192 | self.assertEqual(execute("2.2-3.4"), 2.2 - 3.4)
193 | self.assertEqual(execute("-+-3"), 3)
194 | self.assertEqual(execute("+-+3"), -3)
195 |
196 | def test_arithm_mul(self):
197 | self.assertEqual(execute("2*3*5*6"), 180)
198 |
199 | def test_arithm_mod(self):
200 | self.assertEqual(execute("2%3"), 2.0 % 3)
201 | self.assertEqual(execute("2.0%3"), 2.0 % 3)
202 | self.assertEqual(execute("float(2)%3"), float(2) % 3)
203 |
204 | def test_arithm_div(self):
205 | self.assertEqual(execute("2/3"), 2.0/3)
206 | self.assertEqual(execute("2.0/3"), 2.0/3)
207 | self.assertEqual(execute("float(2)/3"), float(2)/3)
208 |
209 | def test_arithm_group(self):
210 | self.assertEqual(execute("2-3+4+5-7"), 2 - 3 + 4 + 5 - 7)
211 | self.assertEqual(execute("33*2/5-2"), 33*2/5.0 - 2)
212 | self.assertEqual(execute("33-4*5+2/6"), 33 - 4*5 + 2/6.0)
213 | #self.assertEqual(execute("2//3//4//5"), ('//', ('//', ('//', 2, 3), 4), 5))
214 |
215 | def test_arithm_parentheses(self):
216 | self.assertEqual(execute("+6"), 6)
217 | self.assertEqual(execute("2+2*2"), 6)
218 | self.assertEqual(execute("2+(2*2)"), 6)
219 | self.assertEqual(execute("(2+2)*2"), 8)
220 | self.assertEqual(execute("(33-4)*5+2/6"), (33 - 4)*5 + 2/6.0)
221 | self.assertEqual(execute("2/3/(4/5)*6"), 2/3.0/(4/5.0)*6)
222 | self.assertEqual(execute("((2+4))+6"), ((2 + 4)) + 6)
223 |
224 | def test_logic_negatives(self):
225 | self.assertEqual(execute("not false"), True)
226 | self.assertEqual(execute("not null"), True)
227 | self.assertEqual(execute("not 0"), True)
228 | self.assertEqual(execute("not 0.0"), True)
229 | self.assertEqual(execute("not ''"), True)
230 | self.assertEqual(execute("not []"), True)
231 | self.assertEqual(execute("not {}"), True)
232 |
233 | def test_logic_not(self):
234 | self.assertEqual(execute("not false"), True)
235 | self.assertEqual(execute("not not not false"), True)
236 |
237 | def test_logic_or(self):
238 | self.assertEqual(execute("1 or 2"), 1)
239 | self.assertEqual(execute("0 or 2"), 2)
240 | self.assertEqual(execute("'a' or 0 or 3"), 'a')
241 | self.assertEqual(
242 | execute("null or false or 0 or 0.0 or '' or [] or {}"), {}
243 | )
244 |
245 | def test_logic_and(self):
246 | self.assertEqual(execute("1 and 2"), 2)
247 | self.assertEqual(execute("0 and 2"), 0)
248 | self.assertEqual(execute("'a' and false and 3"), False)
249 | self.assertEqual(
250 | execute("true and 1 and 1.0 and 'foo' and [1] and {a:1}"), {"a": 1}
251 | )
252 |
253 | def test_comparison_regex(self):
254 | self.assertIsInstance(execute("/aaa/"), type(re.compile("")))
255 | self.assertEqual(execute("/.*aaa/ matches 'xxxaaaadddd'"), True)
256 | self.assertEqual(execute("'.*aaa' matches 'xxxaaaadddd'"), True)
257 | self.assertEqual(execute("'.*aaa' matches ['xxxaaaadddd', 'xxx']"), True)
258 |
259 | def test_comparison_is(self):
260 | self.assertEqual(execute("2 is 2"), True)
261 | self.assertEqual(execute("'2' is 2"), True)
262 | self.assertEqual(execute("2 is '2'"), True)
263 | self.assertEqual(execute("2 is 2.0"), True)
264 | self.assertEqual(execute("0.1+0.2 is 0.3"), True)
265 | self.assertEqual(execute("[] is []"), True)
266 | self.assertEqual(execute("[1] is [1]"), True)
267 | self.assertEqual(execute("{} is {}"), True)
268 | self.assertEqual(execute("{} is []"), False)
269 | self.assertEqual(execute("None is 'aaa'"), False)
270 | self.assertEqual(execute("None is None"), True)
271 | self.assertEqual(execute("{'aaa':1} is {'aaa':1}"), True)
272 | #oid=ObjectId()
273 | #self.assertEqual(execute("ObjectID('"+str(oid)+"') is '"+str(oid)+"'"), True)
274 |
275 | def test_comparison_isnot(self):
276 | self.assertEqual(execute("None is not None"), False)
277 | self.assertEqual(execute("None is not 'aaa'"), True)
278 | self.assertEqual(execute("{} is not []"), True)
279 | self.assertEqual(execute("3 is not 6"), True)
280 | self.assertEqual(execute("3 is not '3'"), False)
281 | self.assertEqual(execute("[] is not [1]"), True)
282 | self.assertEqual(execute("[] is not []"), False)
283 | self.assertEqual(execute("{'aaa':2} is not {'bbb':2}"), True)
284 | self.assertEqual(execute("{} is not {}"), False)
285 |
286 | def test_membership_in(self):
287 | self.assertEqual(execute("4 in [6,4,3]"), True)
288 | self.assertEqual(execute("4 in {4:true}"), True)
289 | self.assertEqual(execute("[2,3] in [6,4,3]"), True)
290 |
291 | def test_membership_notin(self):
292 | self.assertEqual(execute("4 not in []"), True)
293 | self.assertEqual(execute("1 not in {'232':2}"), True)
294 | self.assertEqual(execute("[2,5] not in [6,4,3]"), True)
295 |
296 | def test_complex(self):
297 | self.assertEqual(execute("23 is not 56 or 25 is 57"), True)
298 | self.assertEqual(execute("2+3/4-6*7>0 or 10 is not 11 and 14"), 14)
299 |
300 | def test_comparison_lt(self):
301 | self.assertEqual(execute("2<3"), True)
302 | self.assertEqual(execute("3<3"), False)
303 | self.assertEqual(execute("2<=2"), True)
304 | self.assertEqual(execute("2<=1"), False)
305 |
306 | def test_comparison_gt(self):
307 | self.assertEqual(execute("5>4"), True)
308 | self.assertEqual(execute("5>5"), False)
309 | self.assertEqual(execute("5>=5"), True)
310 |
311 | def test_concatenation(self):
312 | self.assertEqual(execute("'a'+'b'+\"c\""), 'abc')
313 | self.assertEqual(execute("'5'+5"), '55')
314 | self.assertEqual(execute("5+'5'"), 10)
315 | self.assertEqual(list(execute("[1,2,4] + [3,5]")), [1, 2, 4, 3, 5])
316 | self.assertEqual(
317 | execute('{"a":1,"b":2} + {"a":2,"c":3}'), {
318 | "a": 2,
319 | "b": 2,
320 | "c": 3
321 | }
322 | )
323 | self.assertRaises(
324 | ProgrammingError, lambda: execute('{"a":1,"b":2} + "sss"')
325 | )
326 |
327 | def test_builtin_casting(self):
328 | self.assertEqual(execute("str('foo')"), 'foo')
329 | self.assertEqual(execute("str(1)"), '1')
330 | self.assertEqual(execute("str(1.0)"), '1.0')
331 | self.assertEqual(execute("str(1 is 1)"), 'true')
332 | self.assertEqual(execute("int(1)"), 1)
333 | self.assertEqual(execute("int(1.0)"), 1)
334 | self.assertEqual(execute("int('1')"), 1)
335 | #Python can't handle that
336 | #self.assertEqual(execute("int('1.0')"), 1)
337 | self.assertEqual(execute("float(1.0)"), 1.0)
338 | self.assertEqual(execute("float(1)"), 1.0)
339 | self.assertEqual(execute("float('1')"), 1.0)
340 | self.assertEqual(execute("float('1.0')"), 1.0)
341 | self.assertEqual(execute("array()"), [])
342 | self.assertEqual(execute("array([])"), [])
343 | self.assertEqual(execute("array('abc')"), ['a', 'b', 'c'])
344 | self.assertEqual(
345 | execute("array(dateTime([2011,4,8,12,0]))"), [2011, 4, 8, 12, 0, 0, 0]
346 | )
347 | self.assertEqual(execute("array(date([2011,4,8]))"), [2011, 4, 8])
348 | self.assertEqual(execute("array(time([12,12,30]))"), [12, 12, 30, 0])
349 |
350 | def test_builtin_arithmetic(self):
351 | self.assertEqual(execute("sum([1,2,3,4])"), sum([1, 2, 3, 4]))
352 | self.assertEqual(execute("sum([2,3,4,'333',[]])"), 9)
353 | self.assertEqual(execute("sum(1)"), 1)
354 | self.assertEqual(execute("min([1,2,3,4])"), min([1, 2, 3, 4]))
355 | self.assertEqual(execute("min([2,3,4,'333',[]])"), 2)
356 | self.assertEqual(execute("min(1)"), 1)
357 | self.assertEqual(execute("max([1,2,3,4])"), max([1, 2, 3, 4]))
358 | self.assertEqual(execute("max([2,3,4,'333',[]])"), 4)
359 | self.assertEqual(execute("max(1)"), 1)
360 | self.assertEqual(execute("avg([1,2,3,4])"), 2.5)
361 | self.assertEqual(execute("avg([1,3,3,1])"), 2.0)
362 | self.assertEqual(execute("avg([1.1,1.3,1.3,1.1])"), 1.2000000000000002)
363 | self.assertEqual(execute("avg([2,3,4,'333',[]])"), 3)
364 | self.assertEqual(execute("avg(1)"), 1)
365 | self.assertEqual(execute("round(2/3)"), round(2.0/3))
366 | self.assertEqual(execute("round(2/3,3)"), round(2.0/3, 3))
367 | # edge cases
368 | self.assertEqual(execute("avg(1)"), 1)
369 | # should ommit 'sss'
370 | self.assertEqual(execute("avg([1,'sss',3,3,1])"), 2.0)
371 |
372 | def test_builtin_string(self):
373 | self.assertEqual(execute("replace('foobar','oob','baz')"), 'fbazar')
374 | self.assertEqual(execute("""escape('<')"""), "<")
375 | self.assertEqual(execute("""escape('<"&>')"""), "<"&>")
376 | self.assertEqual(execute("""unescape('<"&>')"""), "<\"&>")
377 | self.assertEqual(execute("upper('aaa')"), "AAA")
378 | self.assertEqual(execute("lower('AAA')"), "aaa")
379 | self.assertEqual(execute("title('AAA aaa')"), "Aaa Aaa")
380 | self.assertEqual(execute("capitalize('AAA Aaa')"), "Aaa aaa")
381 | self.assertEqual(execute("split('aaa aaa')"), ["aaa", "aaa"])
382 | self.assertEqual(execute("split('aaaxaaa','x')"), ["aaa", "aaa"])
383 | self.assertEqual(execute("join(['aaą','aaę'],'ć')"), "aaąćaaę")
384 | self.assertEqual(execute("join(['aaa','aaa'])"), "aaaaaa")
385 | self.assertEqual(execute("join(['aaa','aaa',3,55])"), "aaaaaa355")
386 | self.assertEqual(execute('slice("Hello world!", [6, 11])'), "world")
387 | self.assertEqual(execute('slice("Hello world!", [6, -1])'), "world")
388 | self.assertEqual(
389 | execute('slice("Hello world!", [[0,5], [6, 11]])'), ["Hello", "world"]
390 | )
391 | self.assertRaises(ProgrammingError, lambda: execute('slice()'))
392 | self.assertRaises(ExecutionError, lambda: execute('slice("", {})'))
393 | self.assertEqual(execute('map(upper, ["a", "b", "c"])'), ["A", "B", "C"])
394 |
395 | def test_builtin_arrays(self):
396 | self.assertEqual(execute("sort([1,2,3,4]+[2,4])"), [1, 2, 2, 3, 4, 4])
397 | self.assertEqual(execute("sort($.._id)"), [1, 2, 3, 4])
398 | self.assertEqual(
399 | execute("sort($..l.*, _id)"), [{
400 | '_id': 3,
401 | 'aaa': 'ddd',
402 | 'false': 2
403 | }, {
404 | '_id': 4
405 | }]
406 | )
407 | self.assertEqual(execute("reverse([1,2,3,4]+[2,4])"), [4, 2, 4, 3, 2, 1])
408 | self.assertEqual(execute("reverse(sort($.._id))"), [4, 3, 2, 1])
409 | self.assertEqual(execute("len([1,2,3,4]+[2,4])"), 6)
410 | self.assertEqual(execute("unique([1,1,3,3])"), [1, 3])
411 | # edge cases
412 | self.assertEqual(execute("len(True)"), True)
413 | self.assertEqual(execute("len('aaa')"), 3)
414 |
415 | def test_builtin_time(self):
416 | import datetime
417 | self.assertIsInstance(execute("now()"), datetime.datetime)
418 | self.assertIsInstance(execute("date()"), datetime.date)
419 | self.assertIsInstance(execute("date(now())"), datetime.date)
420 | self.assertIsInstance(execute("date([2001,12,30])"), datetime.date)
421 | self.assertIsInstance(execute("time()"), datetime.time)
422 | self.assertIsInstance(execute("time(now())"), datetime.time)
423 | self.assertIsInstance(execute("time([12,23])"), datetime.time)
424 | self.assertIsInstance(execute("time([12,23,21,777777])"), datetime.time)
425 | self.assertIsInstance(execute("dateTime(now())"), datetime.datetime)
426 | self.assertIsInstance(
427 | execute("dateTime([2001,12,30,12,23])"), datetime.datetime
428 | )
429 | self.assertIsInstance(
430 | execute("dateTime([2001,12,30,12,23,21,777777])"), datetime.datetime
431 | )
432 | self.assertIsInstance(execute('dateTime("1980-05-11 04:22:33", "%Y-%m-%d %H:%M:%S")'), datetime.datetime)
433 | self.assertEqual(str(execute('dateTime("1980-05-11 04:22:33", "%Y-%m-%d %H:%M:%S")')), "1980-05-11 04:22:33")
434 |
435 | self.assertEqual(
436 | execute("toMillis(dateTime([2001,12,30,12,23,21,777777]))"),
437 | 1009715001777
438 | )
439 | self.assertIsInstance(
440 | execute("dateTime(date(),time())"), datetime.datetime
441 | )
442 | self.assertIsInstance(
443 | execute("dateTime(date(),[12,23])"), datetime.datetime
444 | )
445 | self.assertIsInstance(
446 | execute("dateTime(date(),[12,23,21,777777])"), datetime.datetime
447 | )
448 | self.assertIsInstance(
449 | execute("dateTime([2001,12,30],time())"), datetime.datetime
450 | )
451 | self.assertEqual(
452 | execute("array(time([12,30])-time([8,00]))"), [4, 30, 0, 0]
453 | )
454 | self.assertEqual(
455 | execute("array(time([12,12,12,12])-time([8,8,8,8]))"), [4, 4, 4, 4]
456 | )
457 | self.assertEqual(
458 | execute("array(time([12,12,12,12])-time([1,2,3,4]))"), [11, 10, 9, 8]
459 | )
460 | self.assertEqual(
461 | execute("array(time([12,00])-time([1,10]))"), [10, 50, 0, 0]
462 | )
463 | self.assertEqual(
464 | execute("array(time([1,00])-time([1,10]))"), [23, 50, 0, 0]
465 | )
466 | self.assertEqual(
467 | execute("array(time([0,00])-time([0,0,0,1]))"), [23, 59, 59, 999999]
468 | )
469 | self.assertEqual(
470 | execute("array(time([0,0])+time([1,1,1,1]))"), [1, 1, 1, 1]
471 | )
472 | self.assertEqual(
473 | execute("array(time([0,0])+time([1,2,3,4]))"), [1, 2, 3, 4]
474 | )
475 | self.assertEqual(
476 | execute("array(time([23,59,59,999999])+time([0,0,0,1]))"), [0, 0, 0, 0]
477 | )
478 | # age tests
479 | self.assertEqual(execute("age(now())"), [0, "seconds"])
480 | self.assertEqual(
481 | execute("age(dateTime([2000,1,1,1,1]),dateTime([2001,1,1,1,1]))"),
482 | [1, "year"]
483 | )
484 | self.assertEqual(
485 | execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,2,1,1,1]))"),
486 | [1, "month"]
487 | )
488 | self.assertEqual(
489 | execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,2,1,1]))"),
490 | [1, "day"]
491 | )
492 | self.assertEqual(
493 | execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,1,2,1]))"),
494 | [1, "hour"]
495 | )
496 | self.assertEqual(
497 | execute("age(dateTime([2000,1,1,1,1]),dateTime([2000,1,1,1,2]))"),
498 | [1, "minute"]
499 | )
500 | self.assertEqual(
501 | execute("age(dateTime([2000,1,1,1,1,1]),dateTime([2000,1,1,1,1,2]))"),
502 | [1, "second"]
503 | )
504 | self.assertEqual(
505 | execute("""array(time([0,0]) - time([0,0,0,999999]))"""),
506 | [23, 59, 59, 1]
507 | )
508 | self.assertEqual(
509 | execute("""array(time([0,0]) + time([0,0,0,999999]))"""),
510 | [0, 0, 0, 999999]
511 | )
512 |
513 | def test_localize(self):
514 | pass
515 | #these tests are passing on computers with timezone set to UTC - not the case of TravisCI
516 | #test of non-DST time
517 | #if sys.version < "3":
518 | #self.assertEqual(execute("array(localize(dateTime([2000,1,1,10,10,1,0]),'Europe/Warsaw'))"), [2000,1,1,11,10,1,0])
519 | #test of DST time
520 | #self.assertEqual(execute("array(localize(dateTime([2000,7,1,10,10,1,0]),'Europe/Warsaw'))"), [2000,7,1,12,10,1,0])
521 |
522 | def test_builtin_type(self):
523 | self.assertEqual(execute("type([1,2,3,4]+[2,4])"), "array")
524 | self.assertEqual(execute("type({})"), "object")
525 | self.assertEqual(execute("type('')"), "str")
526 |
527 | def test_selector_with_empty_result(self):
528 | self.assertEqual(execute("$.missing is None"), True)
529 | self.assertEqual(execute("$.missing is not None"), False)
530 |
531 | def test_misc(self):
532 | self.assertEqual(execute(2), 2)
533 | self.assertEqual(execute('{"@aaa":1}.@aaa'), 1)
534 | self.assertEqual(execute('$ss.a'), None)
535 | self.assertEqual(execute("$..*[10]"), None)
536 | self.assertEqual(sorted(execute("keys({'a':1,'b':2})")), ['a', 'b'])
537 | self.assertRaises(ExecutionError, lambda: execute('keys([])'))
538 | self.assertRaises(ProgrammingError, lambda: execute('blah([])'))
539 |
540 | def test_optimizations(self):
541 | self.assertEqual(execute("$.*[@]"), execute("$.*"))
542 | self.assertIsInstance(execute_raw("$..*"), generator)
543 | self.assertIsInstance(execute_raw("$..* + $..*"), chain)
544 | self.assertIsInstance(execute_raw("$..* + 2"), chain)
545 | self.assertIsInstance(execute_raw("2 + $..*"), chain)
546 | self.assertEqual(execute("$.._id[0]"), 1)
547 | self.assertEqual(execute("sort($.._id + $.._id)[2]"), 2)
548 | self.assertIsInstance(execute("$.._id[2]"), int)
549 | self.assertEqual(
550 | execute2("$.store.book.(price)[0].price"),
551 | execute2("$.store.book[0].price")
552 | )
553 |
554 | class ObjectPath_Paths(unittest.TestCase):
555 | def assertItemsEqual(self, a, b, m=None):
556 | try:
557 | return self.assertCountEqual(a, b, m)
558 | except: pass
559 | return unittest.TestCase.assertItemsEqual(self, a, b, m)
560 |
561 | def test_simple_paths(self):
562 | self.assertEqual(execute("$.*"), object1)
563 | self.assertEqual(execute("$.a.b.c"), None)
564 | self.assertEqual(execute("$.a.b.c[0]"), None)
565 | self.assertEqual(execute("$.__lang__"), "en")
566 | self.assertEqual(execute("$.test.o._id"), 2)
567 | self.assertEqual(execute("$.test.l._id"), [3, 4])
568 | self.assertEqual(execute("$.*[test].o._id"), 2)
569 | self.assertEqual(execute("$.*['test'].o._id"), 2)
570 | self.assertEqual(
571 | execute('[1,"aa",{"a":2,"c":3},{"c":3},{"a":1,"b":2}].(a,b)'), [{
572 | "a": 2
573 | }, {
574 | "a": 1,
575 | "b": 2
576 | }]
577 | )
578 | self.assertEqual(
579 | execute2("$.store.book.(price,title)[0]"), {
580 | "price": 8.95,
581 | "title": "Sayings of the Century"
582 | }
583 | )
584 | self.assertEqual(len(execute2("$..*['Lord' in @.title]")), 1)
585 | self.assertEqual(
586 | execute2("$..book.(price,title)"), [{
587 | 'price': 8.95,
588 | 'title': 'Sayings of the Century'
589 | }, {
590 | 'price': 12.99,
591 | 'title': 'Sword of Honour'
592 | }, {
593 | 'price': 8.99,
594 | 'title': 'Moby Dick'
595 | }, {
596 | 'price': 22.99,
597 | 'title': 'The Lord of the Rings'
598 | }]
599 | )
600 | self.assertEqual(
601 | execute2("sort($..(price,title),'price')"),
602 | [{
603 | 'price': 8.95,
604 | 'title': 'Sayings of the Century'
605 | }, {
606 | 'price': 8.99,
607 | 'title': 'Moby Dick'
608 | }, {
609 | 'price': 12.99,
610 | 'title': 'Sword of Honour'
611 | }, {
612 | 'price': 19.95
613 | }, {
614 | 'price': 22.99,
615 | 'title': 'The Lord of the Rings'
616 | }]
617 | )
618 | self.assertIsInstance(execute("now().year"), int)
619 |
620 | def test_complex_paths(self):
621 | self.assertEqual(sorted(execute("$.._id")), [1, 2, 3, 4])
622 | self.assertEqual(execute("$..l"), object1["test"]["l"])
623 | self.assertEqual(execute("$..l.._id"), [3, 4])
624 | self.assertEqual(execute2("$.store.*"), object2["store"])
625 | self.assertEqual(
626 | execute2("$.store.book.author"),
627 | ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
628 | )
629 | #print()
630 | #print(execute2("$.store.book.(author,aaa)"))
631 | self.assertEqual(
632 | execute2("$.store.book.(author,aaa)"), [{
633 | "author": "Nigel Rees"
634 | }, {
635 | "author": "Evelyn Waugh"
636 | }, {
637 | "author": "Herman Melville"
638 | }, {
639 | "author": "J. R. R. Tolkien"
640 | }]
641 | )
642 | self.assertEqual(
643 | execute2("$.store.book.(author,price)"), [{
644 | 'price': 8.95,
645 | 'author': 'Nigel Rees'
646 | }, {
647 | 'price': 12.99,
648 | 'author': 'Evelyn Waugh'
649 | }, {
650 | 'price': 8.99,
651 | 'author': 'Herman Melville'
652 | }, {
653 | 'price': 22.99,
654 | 'author': 'J. R. R. Tolkien'
655 | }]
656 | )
657 | self.assertEqual(
658 | execute2("$.store.book.*[author]"),
659 | ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
660 | )
661 | self.assertEqual(
662 | execute2("$.store.book.*['author']"),
663 | ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
664 | )
665 | self.assertEqual(execute2("$.store.book"), object2["store"]["book"])
666 | self.assertEqual(
667 | list(execute2("$..author")),
668 | ['Nigel Rees', 'Evelyn Waugh', 'Herman Melville', 'J. R. R. Tolkien']
669 | )
670 |
671 | def test_selectors(self):
672 | self.assertEqual(
673 | execute2("$.store.book[@.id is not null]"),
674 | [{
675 | 'category': 'reference',
676 | 'price': 8.95,
677 | 'title': 'Sayings of the Century',
678 | 'id': 1,
679 | 'author': 'Nigel Rees'
680 | }]
681 | )
682 | self.assertEqual(len(execute2("$.store.book[@.id is null]")), 3)
683 | self.assertEqual(len(execute("$..*[@._id>2]")), 2)
684 | self.assertEqual(execute("$..*[3 in @.l._id]")[0], object1['test'])
685 | self.assertEqual(execute2("$.store..*[4 in @.k._id]")[0], object2['store'])
686 | self.assertEqual(execute("$..*[@._id>1 and @._id<3][0]"), {'_id': 2})
687 | # very bad syntax!!!
688 | self.assertEqual(
689 | sorted(execute2("$.store.book[@.price]")),
690 | sorted([8.95, 12.99, 8.99, 22.99])
691 | )
692 | self.assertEqual(
693 | execute3("$..*[@.x is 5.6 and @.y is 9.891].value"), ['bar']
694 | )
695 |
696 | def test_object_list(self):
697 | self.assertItemsEqual(execute3('values($.*).value'), ['foo', 'bar', 'foobar'])
698 | self.assertItemsEqual(execute3('keys($.*)'), ['item_1', 'item_2', 'item_3'])
699 | self.assertItemsEqual(
700 | execute4('map(values, $..root..response).value'), [5, 4, 0]
701 | )
702 |
703 | #testcase2=unittest.FunctionTestCase(test_efficiency(2))
704 | testcase1 = unittest.TestLoader().loadTestsFromTestCase(ObjectPath)
705 | testcase2 = unittest.TestLoader().loadTestsFromTestCase(ObjectPath_Paths)
706 |
707 | op_test = unittest.TestSuite([testcase1, testcase2])
708 | #utils_interpreter=unittest.TestSuite([testcase2])
709 |
--------------------------------------------------------------------------------
/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | from objectpath.utils import *
5 | from objectpath.utils.json_ext import *
6 | import sys, unittest, os
7 |
8 | sys.setrecursionlimit(20000)
9 |
10 | class Utils_test(unittest.TestCase):
11 | def test_Utils_JSON_compat(self):
12 | self.assertEqual(loads("['ddd']"), ['ddd'])
13 | if sys.version_info.major < 3:
14 | self.assertEqual(loads("[u'ddd']"), ['ddd'])
15 | self.assertRaises(Exception, lambda: loads(['ddd}']))
16 | self.assertEqual(dumps(['ddd']), '["ddd"]')
17 | self.assertEqual(py2JSON(False), 'false')
18 | self.assertEqual(py2JSON(None), 'null')
19 | self.assertEqual(py2JSON((2, 3, 4)), [2, 3, 4])
20 | if sys.version_info.major < 3:
21 | self.assertEqual(py2JSON(unicode('')), '')
22 | self.assertEqual(py2JSON(2), 2)
23 | self.assertEqual(
24 | printJSON([1, 2, 3, 4, 5, 6]),
25 | "[\n \x1b[36m\x1b[1m1\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m2\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m3\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m4\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m5\x1b[0m\x1b[0m,\n ... (1 more items)\n]"
26 | )
27 | self.assertEqual(
28 | printJSON([{}, 1]), '[\n {},\n \x1b[36m\x1b[1m1\x1b[0m\x1b[0m\n]'
29 | )
30 | self.assertEqual(
31 | printJSON({
32 | "aaa": 1
33 | }),
34 | '{\x1b[33m\x1b[1m"aaa"\x1b[0m\x1b[0m: \x1b[36m\x1b[1m1\x1b[0m\x1b[0m}'
35 | )
36 | self.assertEqual(
37 | printJSON({
38 | "a": [1, 2, 3]
39 | }),
40 | '{\x1b[33m\x1b[1m"a"\x1b[0m\x1b[0m: [\n \x1b[36m\x1b[1m1\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m2\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m3\x1b[0m\x1b[0m\n]}'
41 | )
42 | self.assertEqual(
43 | printJSON([[1], {
44 | "aa": 2
45 | }]),
46 | '[\n [\x1b[36m\x1b[1m1\x1b[0m\x1b[0m],\n {\x1b[33m\x1b[1m"aa"\x1b[0m\x1b[0m: \x1b[36m\x1b[1m2\x1b[0m\x1b[0m}\n]'
47 | )
48 | self.assertEqual(
49 | printJSON({
50 | "aaa": {
51 | "bbb": {
52 | "ccc": {
53 | "ddd": [1, 2, 3, 4, 5]
54 | }
55 | }
56 | }
57 | }),
58 | '{\x1b[33m\x1b[1m"aaa"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"bbb"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"ccc"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"ddd"\x1b[0m\x1b[0m: [\n \x1b[36m\x1b[1m1\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m2\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m3\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m4\x1b[0m\x1b[0m,\n \x1b[36m\x1b[1m5\x1b[0m\x1b[0m\n]}}}}'
59 | )
60 | if str(sys.version_info.major) + str(sys.version_info.minor) < '33':
61 | self.assertEqual(
62 | printJSON({
63 | "aaa": {
64 | "bbb": {
65 | "ccc": {
66 | "ddd": {
67 | "eee": [1, 2, 3, 4, 5],
68 | "ddd": {}
69 | }
70 | }
71 | }
72 | }
73 | }),
74 | '{\x1b[33m\x1b[1m"aaa"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"bbb"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"ccc"\x1b[0m\x1b[0m: {\x1b[33m\x1b[1m"ddd"\x1b[0m\x1b[0m: {\n \x1b[33m\x1b[1m"eee"\x1b[0m\x1b[0m: ,\n \x1b[33m\x1b[1m"ddd"\x1b[0m\x1b[0m: {...}\n}}}}}'
75 | )
76 |
77 | testcase1 = unittest.TestLoader().loadTestsFromTestCase(Utils_test)
78 |
79 | utils_test = unittest.TestSuite([testcase1])
80 |
--------------------------------------------------------------------------------