pyspecter


Namepyspecter JSON
Version 0.1.1 PyPI version JSON
download
home_pagehttps://github.com/yellowbean/pyspecter
SummaryA library to query nested data in Python
upload_time2023-07-18 17:11:10
maintainer
docs_urlNone
authorXiaoyu Zhang
requires_python>=3.10
license
keywords
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # pyspecter

[![Python version](https://img.shields.io/pypi/pyversions/pyspecter)](https://img.shields.io/pypi/pyversions/pyspecter)
[![PyPI version](https://badge.fury.io/py/pyspecter.svg)](https://badge.fury.io/py/pyspecter)
[![PyPI download](https://img.shields.io/pypi/dm/pyspecter)](https://img.shields.io/pypi/dm/pyspecter)

A Python library to query nested structure, inspired by [specter](https://github.com/redplanetlabs/specter)

If you are dealing with nested python structure and it require complex rule to search the data underneath like:

* Key start with a pattern
* Value to be filter
* Conditional path to walk into
* ...

this is the right library fit the use case as it extract the `Navigation` rule to a list, saving your a lot of trouble writting your own logic to navigate the nested data.

## Get started
![image](https://user-images.githubusercontent.com/1008321/210162209-a7cce888-99ad-48af-ac63-693d63f825ff.png)

## Examples

    m = {"A":{"B1":[10,20],"B2":2,"B3":3}
         ,"C":{"B1":[1,2],"B2":[3,4]}
         ,"D":[1,2,3,4]
         ,"E":[None,2,3,4]
         ,"F":{"G":1}
         ,"H":["A1","A2","A3"]}
    
### Navigate to specific item 
#### FIRST/LAST     

    assert query(m, ["D", S.FIRST]) == 1
    assert query(m, ["A", S.FIRST]) == ("B1", [10, 20]) # first element from dict.items()

    assert query(m, ["D", S.LAST]) == 4
    assert query(m, ["A", S.LAST]) == ("B3", 3) # last element from dict.items()
    
#### Nth    
Navigate to the Nth element

    assert query(m, ["D", (S.NTH, 1)]) == 2
    assert query(m, ["D",(S.NTH, 1, 2)]) == [ 2, 3 ]
        
#### Operation on dict    
    
Navigate to `values` or `keys` of current position
        
    assert query(m, ["C", S.MVALS]) == [[1, 2], [3, 4]]
    assert query(m, ["C", S.MKEYS]) == ['B1', 'B2']

Navigate to a sub map of current position

    assert query(m, ["A", (S.SUB_MAP, "B1")]) == {"B1":[10,20]}

Annotate with index with current position
    
    assert query(m, ["A", S.INDEXED_VALS]) == [(0, ("B1", [10,20])), (1, ("B2", 2)), (2, ("B3", 3))]
    assert query(m, ["C", "B1", S.INDEXED_VALS]) == [(0, 1), (1, 2)]
    
#### Filtering    
    
filter elements by supplying a function

    assert query(m, ["A", "B1", (S.FILTER, lambda x: x > 10)]) == [20]
    assert query(m, ["C", (S.FILTER, lambda k, v: k.endswith("2"))]) == [[3, 4]]

Navigate to map which its key or value satisify a custom function 

    assert query(m, ["A", (S.MKEY_IF, lambda x:x.endswith("1"))]) == [[10, 20]]
    assert query(m, ["A", (S.MVAL_IF, lambda x:x==[10,20])]) == [[10, 20]]
    
#### Branching paths

Branching by multiple paths

    mpath = {"A":{"B":1,
                  "C":[2,3,4,5]}}
    assert query(mpath,["A",
                        (S.MULTI_PATH,["B"]
                                     ,["C", S.LAST])]) == [1,5]



#### Conditional Navigation

Navigate to a specifie path

    assert query(m, ["D", (S.NTH_PATH, 2)]) == [3]

Navigate to a position if and only if the path exists

    assert query(m, [(S.MUST,"F","G")]) == 1
    assert query(m, [(S.MUST,"F","G","NOT_EXISTS")]) == None 

Navigate to a range

    assert query(m, ["D",(S.SRANGE,2,3)]) == [3]

Navigate to a `2nd path`  if `1st path` exists, else return `None`

    assert query(m,[(S.IF_PATH,["C","B1"],["E"])]) == [None,2,3,4]
    assert query(m,[(S.IF_PATH,["C","B1"],["NOT_EXISTS"])]) == None

Navigate to a `2nd path`  if `1st path` exists, else navigate to `3rd path`

    assert query(m,[(S.IF_PATH,["C","B3"],["E"],["F"])]) == {'G': 1}

Navigate to values of dict if key satisfy a regex expression:

    assert query(m, ["A",(S.REGEX,r"B[23]")]) == [ 2,3 ]

Navigate to values of list if elements satisfy a regex expression:

    assert query(m, ["H",(S.REGEX,r"\S1")]) == [ "A1" ]

Navigate with optional path node 

    assert query(m, [(S.MAYBE,"F","G")]) == 1

#### Handling None value

Return default value if current position is `None`

    assert query(None,[(S.NONE_VAL,10)]) == 10

If current position is not a None,then return the value of current position

    assert query(5,[(S.NONE_VAL,10)]) == 5
    
    
### Operation on results

user can operate on the query result by

* REUDCE -> perform `reduce` on the `list` or `dict` result ,from left to right.

<!-- -->

    m2 = {"A":{"B":["C1","C2","C3"]}}
    assert query(m2, ["A","B",(H.REDUCE,lambda acc,x:acc+"|"+x, ">>>")]) == ">>>|C1|C2|C3"
    assert query(m2, ["A","B",(H.REDUCE,lambda acc,x:acc+"|"+x)]) == "C1|C2|C3"

    m2 = {"A":{"B":1,"D":2}}
    assert query(m2, ["A",(H.REDUCE,lambda acc,x:acc+"|"+str(x[1]),">>")]) == ">>|1|2"
    assert query(m2, ["A",(H.REDUCE,lambda acc,x:acc+"|"+str(x[0]),">>")]) == ">>|B|D"

* MAP -> performn a `transformation` on the `list` or `dict` result 

<!-- -->

    m2 = {"A":{"B":["C1","C2","C3"]}}
    assert query(m2, ["A","B",(H.MAP,lambda x:x+"!")]) == ["C1!","C2!","C3!"]

    m2 = {"A":{"B":"C1","D":"C2"}}
    assert query(m2, ["A",(H.MAP,(lambda k,v: v+"!"))]) == ["C1!","C2!"]

* SUM -> `sum()` the result list, with optional custom function passed before summing.

<!-- -->

    m2 = {"A":{"B":[1,2]}}
    assert query(m2, ["A","B",H.SUM]) == 3

    m2 = {"A":{"B":"1","D":"2"}}
    assert query(m2, ["A",S.MVALS,(H.SUM,lambda x: int(x))]) == 3
 

* MAX/MIN -> Using built-in `max()`/`min()` on the `list` result, with optioanl custom function passed before max/min

<!-- -->

    m2 = {"A":{"B":[3,5,2]}}
    assert query(m2, ["A","B",H.MIN]) == 2
    assert query(m2, ["A","B",(H.MIN,str)]) == "2"
    assert query(m2, ["A","B",H.MAX]) == 5
    assert query(m2, ["A","B",(H.MAX, str)]) == "5"
 


* ORDER -> using `sorted()` to sort the result list, with optional custom function passed before sorting.

<!-- -->

    m2 = {"A":{"B":[3,5,2]}}
    m2 = {"A":{"B":[3,5,2]}}
    assert query(m2, ["A","B",H.ORDER]) == [2,3,5]
    assert query(m2, ["A","B",(H.ORDER, str)]) == ["2","3","5"]

    

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/yellowbean/pyspecter",
    "name": "pyspecter",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": "",
    "keywords": "",
    "author": "Xiaoyu Zhang",
    "author_email": "always.zhang@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/69/3e/9cf1083e08aada33115d2deb5215626a38c32325f76f1a26e29a005af047/pyspecter-0.1.1.tar.gz",
    "platform": null,
    "description": "# pyspecter\n\n[![Python version](https://img.shields.io/pypi/pyversions/pyspecter)](https://img.shields.io/pypi/pyversions/pyspecter)\n[![PyPI version](https://badge.fury.io/py/pyspecter.svg)](https://badge.fury.io/py/pyspecter)\n[![PyPI download](https://img.shields.io/pypi/dm/pyspecter)](https://img.shields.io/pypi/dm/pyspecter)\n\nA Python library to query nested structure, inspired by [specter](https://github.com/redplanetlabs/specter)\n\nIf you are dealing with nested python structure and it require complex rule to search the data underneath like:\n\n* Key start with a pattern\n* Value to be filter\n* Conditional path to walk into\n* ...\n\nthis is the right library fit the use case as it extract the `Navigation` rule to a list, saving your a lot of trouble writting your own logic to navigate the nested data.\n\n## Get started\n![image](https://user-images.githubusercontent.com/1008321/210162209-a7cce888-99ad-48af-ac63-693d63f825ff.png)\n\n## Examples\n\n    m = {\"A\":{\"B1\":[10,20],\"B2\":2,\"B3\":3}\n         ,\"C\":{\"B1\":[1,2],\"B2\":[3,4]}\n         ,\"D\":[1,2,3,4]\n         ,\"E\":[None,2,3,4]\n         ,\"F\":{\"G\":1}\n         ,\"H\":[\"A1\",\"A2\",\"A3\"]}\n    \n### Navigate to specific item \n#### FIRST/LAST     \n\n    assert query(m, [\"D\", S.FIRST]) == 1\n    assert query(m, [\"A\", S.FIRST]) == (\"B1\", [10, 20]) # first element from dict.items()\n\n    assert query(m, [\"D\", S.LAST]) == 4\n    assert query(m, [\"A\", S.LAST]) == (\"B3\", 3) # last element from dict.items()\n    \n#### Nth    \nNavigate to the Nth element\n\n    assert query(m, [\"D\", (S.NTH, 1)]) == 2\n    assert query(m, [\"D\",(S.NTH, 1, 2)]) == [ 2, 3 ]\n        \n#### Operation on dict    \n    \nNavigate to `values` or `keys` of current position\n        \n    assert query(m, [\"C\", S.MVALS]) == [[1, 2], [3, 4]]\n    assert query(m, [\"C\", S.MKEYS]) == ['B1', 'B2']\n\nNavigate to a sub map of current position\n\n    assert query(m, [\"A\", (S.SUB_MAP, \"B1\")]) == {\"B1\":[10,20]}\n\nAnnotate with index with current position\n    \n    assert query(m, [\"A\", S.INDEXED_VALS]) == [(0, (\"B1\", [10,20])), (1, (\"B2\", 2)), (2, (\"B3\", 3))]\n    assert query(m, [\"C\", \"B1\", S.INDEXED_VALS]) == [(0, 1), (1, 2)]\n    \n#### Filtering    \n    \nfilter elements by supplying a function\n\n    assert query(m, [\"A\", \"B1\", (S.FILTER, lambda x: x > 10)]) == [20]\n    assert query(m, [\"C\", (S.FILTER, lambda k, v: k.endswith(\"2\"))]) == [[3, 4]]\n\nNavigate to map which its key or value satisify a custom function \n\n    assert query(m, [\"A\", (S.MKEY_IF, lambda x:x.endswith(\"1\"))]) == [[10, 20]]\n    assert query(m, [\"A\", (S.MVAL_IF, lambda x:x==[10,20])]) == [[10, 20]]\n    \n#### Branching paths\n\nBranching by multiple paths\n\n    mpath = {\"A\":{\"B\":1,\n                  \"C\":[2,3,4,5]}}\n    assert query(mpath,[\"A\",\n                        (S.MULTI_PATH,[\"B\"]\n                                     ,[\"C\", S.LAST])]) == [1,5]\n\n\n\n#### Conditional Navigation\n\nNavigate to a specifie path\n\n    assert query(m, [\"D\", (S.NTH_PATH, 2)]) == [3]\n\nNavigate to a position if and only if the path exists\n\n    assert query(m, [(S.MUST,\"F\",\"G\")]) == 1\n    assert query(m, [(S.MUST,\"F\",\"G\",\"NOT_EXISTS\")]) == None \n\nNavigate to a range\n\n    assert query(m, [\"D\",(S.SRANGE,2,3)]) == [3]\n\nNavigate to a `2nd path`  if `1st path` exists, else return `None`\n\n    assert query(m,[(S.IF_PATH,[\"C\",\"B1\"],[\"E\"])]) == [None,2,3,4]\n    assert query(m,[(S.IF_PATH,[\"C\",\"B1\"],[\"NOT_EXISTS\"])]) == None\n\nNavigate to a `2nd path`  if `1st path` exists, else navigate to `3rd path`\n\n    assert query(m,[(S.IF_PATH,[\"C\",\"B3\"],[\"E\"],[\"F\"])]) == {'G': 1}\n\nNavigate to values of dict if key satisfy a regex expression:\n\n    assert query(m, [\"A\",(S.REGEX,r\"B[23]\")]) == [ 2,3 ]\n\nNavigate to values of list if elements satisfy a regex expression:\n\n    assert query(m, [\"H\",(S.REGEX,r\"\\S1\")]) == [ \"A1\" ]\n\nNavigate with optional path node \n\n    assert query(m, [(S.MAYBE,\"F\",\"G\")]) == 1\n\n#### Handling None value\n\nReturn default value if current position is `None`\n\n    assert query(None,[(S.NONE_VAL,10)]) == 10\n\nIf current position is not a None,then return the value of current position\n\n    assert query(5,[(S.NONE_VAL,10)]) == 5\n    \n    \n### Operation on results\n\nuser can operate on the query result by\n\n* REUDCE -> perform `reduce` on the `list` or `dict` result ,from left to right.\n\n<!-- -->\n\n    m2 = {\"A\":{\"B\":[\"C1\",\"C2\",\"C3\"]}}\n    assert query(m2, [\"A\",\"B\",(H.REDUCE,lambda acc,x:acc+\"|\"+x, \">>>\")]) == \">>>|C1|C2|C3\"\n    assert query(m2, [\"A\",\"B\",(H.REDUCE,lambda acc,x:acc+\"|\"+x)]) == \"C1|C2|C3\"\n\n    m2 = {\"A\":{\"B\":1,\"D\":2}}\n    assert query(m2, [\"A\",(H.REDUCE,lambda acc,x:acc+\"|\"+str(x[1]),\">>\")]) == \">>|1|2\"\n    assert query(m2, [\"A\",(H.REDUCE,lambda acc,x:acc+\"|\"+str(x[0]),\">>\")]) == \">>|B|D\"\n\n* MAP -> performn a `transformation` on the `list` or `dict` result \n\n<!-- -->\n\n    m2 = {\"A\":{\"B\":[\"C1\",\"C2\",\"C3\"]}}\n    assert query(m2, [\"A\",\"B\",(H.MAP,lambda x:x+\"!\")]) == [\"C1!\",\"C2!\",\"C3!\"]\n\n    m2 = {\"A\":{\"B\":\"C1\",\"D\":\"C2\"}}\n    assert query(m2, [\"A\",(H.MAP,(lambda k,v: v+\"!\"))]) == [\"C1!\",\"C2!\"]\n\n* SUM -> `sum()` the result list, with optional custom function passed before summing.\n\n<!-- -->\n\n    m2 = {\"A\":{\"B\":[1,2]}}\n    assert query(m2, [\"A\",\"B\",H.SUM]) == 3\n\n    m2 = {\"A\":{\"B\":\"1\",\"D\":\"2\"}}\n    assert query(m2, [\"A\",S.MVALS,(H.SUM,lambda x: int(x))]) == 3\n \n\n* MAX/MIN -> Using built-in `max()`/`min()` on the `list` result, with optioanl custom function passed before max/min\n\n<!-- -->\n\n    m2 = {\"A\":{\"B\":[3,5,2]}}\n    assert query(m2, [\"A\",\"B\",H.MIN]) == 2\n    assert query(m2, [\"A\",\"B\",(H.MIN,str)]) == \"2\"\n    assert query(m2, [\"A\",\"B\",H.MAX]) == 5\n    assert query(m2, [\"A\",\"B\",(H.MAX, str)]) == \"5\"\n \n\n\n* ORDER -> using `sorted()` to sort the result list, with optional custom function passed before sorting.\n\n<!-- -->\n\n    m2 = {\"A\":{\"B\":[3,5,2]}}\n    m2 = {\"A\":{\"B\":[3,5,2]}}\n    assert query(m2, [\"A\",\"B\",H.ORDER]) == [2,3,5]\n    assert query(m2, [\"A\",\"B\",(H.ORDER, str)]) == [\"2\",\"3\",\"5\"]\n\n    \n",
    "bugtrack_url": null,
    "license": "",
    "summary": "A library to query nested data in Python",
    "version": "0.1.1",
    "project_urls": {
        "Homepage": "https://github.com/yellowbean/pyspecter"
    },
    "split_keywords": [],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1d0fae4b663ae3324346bec9ff6a7a3b46583f4f94252b533fb4cc1f0af8167c",
                "md5": "c0ce680aeb69d0fdd4cd4fe69ea70b08",
                "sha256": "1101f053408fd7927eb7bf6cb89c6aeff3c0ba6de756195e773ada01f4a6ad43"
            },
            "downloads": -1,
            "filename": "pyspecter-0.1.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c0ce680aeb69d0fdd4cd4fe69ea70b08",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 6165,
            "upload_time": "2023-07-18T17:11:09",
            "upload_time_iso_8601": "2023-07-18T17:11:09.605299Z",
            "url": "https://files.pythonhosted.org/packages/1d/0f/ae4b663ae3324346bec9ff6a7a3b46583f4f94252b533fb4cc1f0af8167c/pyspecter-0.1.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "693e9cf1083e08aada33115d2deb5215626a38c32325f76f1a26e29a005af047",
                "md5": "777a515674ca18bb069a265a1ba13bac",
                "sha256": "aeb2a4254f55c3863b5004cbd08f82f4b1eea1428d80f2a4553ab3db2dd01a55"
            },
            "downloads": -1,
            "filename": "pyspecter-0.1.1.tar.gz",
            "has_sig": false,
            "md5_digest": "777a515674ca18bb069a265a1ba13bac",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 6127,
            "upload_time": "2023-07-18T17:11:10",
            "upload_time_iso_8601": "2023-07-18T17:11:10.892909Z",
            "url": "https://files.pythonhosted.org/packages/69/3e/9cf1083e08aada33115d2deb5215626a38c32325f76f1a26e29a005af047/pyspecter-0.1.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-07-18 17:11:10",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "yellowbean",
    "github_project": "pyspecter",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "pyspecter"
}
        
Elapsed time: 0.22045s