Name | pathtyped JSON |
Version |
0.1.4
JSON |
| download |
home_page | |
Summary | Create static path checking and type checking for your Python code |
upload_time | 2023-01-16 07:26:40 |
maintainer | |
docs_url | None |
author | nopeless |
requires_python | >=3.10 |
license | MIT License Copyright (c) 2023 nopeless@github.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
keywords |
path
file
type
|
VCS |
|
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# Typesafe resource management in Python
This package creates a definition file by traversing the resource folder and parsing the files
## Features
- [x] Typesafe resource management (needs compatible type checker)
- [x] Automatic type definition generation
- [x] Middlewares, loaders api
PyPI: https://pypi.org/project/pathtyped/
```sh
pip install pathtyped
```
# Showcase
> This code can be found in [demo](demo)
## Your text files
resources/
data.json
main.txt
speech_0.txt
speech_1.txt
speech_2.txt
```
```py
# main.py
from pathtyped import *
rm = ResourceManager(
f"resources",
DefinitionFile("", f"resource_definition.py"),
[remove_known_extensions(r"txt|json"), group_by(r"(speech)_(\d)")],
[Loaders.text, Loaders.json],
)
from resource_definition import root
resource: root = rm.root # type: ignore
# The file below does not exist yet
from definition import root
resource: root = resource_manager.root # type: ignore
```
```py
# defaults.py
# No need to import anything since type str is already imported
# Example
from some_code import YourClass
```
### Generates
```py
# 8587d74aed20f794cdfc800f26c456b7
# Automatically generated by ResourceManager at: 2023-01-15 23:37:27.451164
# DO NOT EDIT THIS FILE MANUALLY.
# If you want to regenerate this file, change the integrity hash in the first line to something else
# Some generic fixes
list = list[object]
dict = dict[object, object]
# Default import statement:
# Main content:
from typing import NamedTuple
root = NamedTuple("root", [
("speech", tuple[
str,
str,
str,
],),
("data", dict),
("main", str),
])
```
## And provides you with type compilation
![1673847586091](image/README/1673847586091.png)
# Documentation
## Middlewares
Middlewares are functions that have the ability to modify the tree structure itself as well as change the value of the nodes
They are applied in the order they are passed to the ResourceManager, with each middleware doing a full DFS preorder traversal of the tree
```py
for middleware in middlewares:
# The @middleware decorator will initiate the dfs preorder traversal
tree = middleware(self, "<root>", tree)
```
<img src="image/README/1673837228765.png" alt="drawing" width="200"/>
This also means that the middleware will be applied to the leaf of the trees that it has returned. This is the most desirable behavior for applying middlewares
```py
@middleware
def strip_numbers_in_string(r: ResourceManager, location: str, tree: EntryTree) -> Optional[EntryTree]:
"""Removes all number property from tree"""
if isinstance(tree, EntryDict):
new_tree = EntryDict()
for key in list(tree.keys()):
n = re.sub(r"\d+", "", key)
new_tree[n] = tree[key]
# If a new dict or list is returned, it will replace the old one
# Be sure to return a new EntryTree
return new_tree
# Returning None indicates that the tree should not be changed
# You can also modify tree in place and it will be reflected
return None
```
## Loaders
Loaders are simpler middlewares. They are functions that only take the leaf node and transform it into a useful value.
Most leaf nodes are `Path` objects
The resource manager will attempt to apply each loader to the leaf node in the order they are passed to the ResourceManager. Returning `None` indicates that the loader does not know how to handle the leaf node and will pass it to the next loader
```py
for loader in self.loaders:
if (r := loader(self, obj)) is not None:
return r
```
If you want multiple loaders to be applied the same leaf node see the example code at the bottom
```py
# Basic loaders
@loader
def return_suffix(r: ResourceManager, path: Path) -> str:
return path.suffix
@loader
@extension("mp3")
def pygame_load(r: ResourceManager, path: Path) -> pygame.mixer.Sound:
return pygame.mixer.Sound(path)
fallback_image = pygame.image.load("fallback.png")
@loader
@extension(r"png|jpg|jpeg")
@fallback(fallback_image) # If the loading raises an Exception
def pygame_image_load(r: ResourceManager, path: Path) -> pygame.Surface:
return pygame.image.load(path)
# Advanced loaders
# Loader that accepts certain object nodes
# Requires a compatible middleware to generate such leaves
@loader(Script)
def execute(r: ResourceManager, s: Script) -> object:
return s.execute()
# Two transformations
@loader
def str_node(r: ResourceManager, path: Path) -> str:
return path.read_text()
@loader(str)
def str_to_int(r: ResourceManager, s: str) -> int:
if s.isnumeric():
return int(s)
@loader
def combined(r: ResourceManager, path: Path) -> Union[str, int]:
res = path
for l in [str_node, str_to_int]:
if (t := l(r, res)) is not None:
res = t
return res
```
# FAQ
## Visual Studio Code shows that the type is any
Change the `python.analysis.typeCheckingMode` setting to `strict` and it not allow you to access invalid properties. Make sure that the Python language server is Pylance
```json
{
"python.analysis.typeCheckingMode": "strict"
}
```
## Cannot import `TypeGuard`
Project requires Python 3.10 or higher
# Contributing
If you have an idea for this library, please make an issue first instead of a PR. I will be happy to discuss it with you
Raw data
{
"_id": null,
"home_page": "",
"name": "pathtyped",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": "",
"keywords": "path,file,type",
"author": "nopeless",
"author_email": "",
"download_url": "https://files.pythonhosted.org/packages/6e/6f/3e90a5a16a6b401c0745ebac5db1569c1a00e04119718f8f1a7cafe04639/pathtyped-0.1.4.tar.gz",
"platform": null,
"description": "# Typesafe resource management in Python\r\n\r\nThis package creates a definition file by traversing the resource folder and parsing the files\r\n\r\n## Features\r\n - [x] Typesafe resource management (needs compatible type checker)\r\n - [x] Automatic type definition generation\r\n - [x] Middlewares, loaders api\r\n\r\nPyPI: https://pypi.org/project/pathtyped/\r\n\r\n```sh\r\npip install pathtyped\r\n```\r\n\r\n# Showcase\r\n\r\n> This code can be found in [demo](demo)\r\n\r\n## Your text files\r\nresources/\r\n data.json\r\n main.txt\r\n speech_0.txt\r\n speech_1.txt\r\n speech_2.txt\r\n```\r\n\r\n```py\r\n# main.py\r\nfrom pathtyped import *\r\n\r\nrm = ResourceManager(\r\n f\"resources\",\r\n DefinitionFile(\"\", f\"resource_definition.py\"),\r\n [remove_known_extensions(r\"txt|json\"), group_by(r\"(speech)_(\\d)\")],\r\n [Loaders.text, Loaders.json],\r\n)\r\n\r\nfrom resource_definition import root\r\n\r\nresource: root = rm.root # type: ignore\r\n# The file below does not exist yet\r\nfrom definition import root\r\n\r\nresource: root = resource_manager.root # type: ignore\r\n```\r\n\r\n```py\r\n# defaults.py\r\n# No need to import anything since type str is already imported\r\n# Example\r\nfrom some_code import YourClass\r\n```\r\n\r\n### Generates\r\n\r\n```py\r\n# 8587d74aed20f794cdfc800f26c456b7\r\n# Automatically generated by ResourceManager at: 2023-01-15 23:37:27.451164\r\n# DO NOT EDIT THIS FILE MANUALLY. \r\n# If you want to regenerate this file, change the integrity hash in the first line to something else\r\n\r\n# Some generic fixes\r\nlist = list[object]\r\ndict = dict[object, object]\r\n\r\n# Default import statement:\r\n\r\n# Main content:\r\nfrom typing import NamedTuple\r\n\r\nroot = NamedTuple(\"root\", [\r\n (\"speech\", tuple[\r\n str,\r\n str,\r\n str,\r\n ],),\r\n (\"data\", dict),\r\n (\"main\", str),\r\n])\r\n```\r\n\r\n## And provides you with type compilation\r\n\r\n![1673847586091](image/README/1673847586091.png)\r\n\r\n# Documentation\r\n\r\n## Middlewares\r\n\r\nMiddlewares are functions that have the ability to modify the tree structure itself as well as change the value of the nodes\r\n\r\nThey are applied in the order they are passed to the ResourceManager, with each middleware doing a full DFS preorder traversal of the tree\r\n\r\n```py\r\nfor middleware in middlewares:\r\n # The @middleware decorator will initiate the dfs preorder traversal\r\n tree = middleware(self, \"<root>\", tree)\r\n```\r\n<img src=\"image/README/1673837228765.png\" alt=\"drawing\" width=\"200\"/>\r\n\r\nThis also means that the middleware will be applied to the leaf of the trees that it has returned. This is the most desirable behavior for applying middlewares\r\n\r\n```py\r\n@middleware\r\ndef strip_numbers_in_string(r: ResourceManager, location: str, tree: EntryTree) -> Optional[EntryTree]:\r\n \"\"\"Removes all number property from tree\"\"\"\r\n if isinstance(tree, EntryDict):\r\n new_tree = EntryDict()\r\n for key in list(tree.keys()):\r\n n = re.sub(r\"\\d+\", \"\", key)\r\n new_tree[n] = tree[key]\r\n # If a new dict or list is returned, it will replace the old one\r\n # Be sure to return a new EntryTree\r\n return new_tree\r\n # Returning None indicates that the tree should not be changed\r\n # You can also modify tree in place and it will be reflected\r\n return None\r\n```\r\n\r\n## Loaders\r\n\r\nLoaders are simpler middlewares. They are functions that only take the leaf node and transform it into a useful value.\r\n\r\nMost leaf nodes are `Path` objects\r\n\r\nThe resource manager will attempt to apply each loader to the leaf node in the order they are passed to the ResourceManager. Returning `None` indicates that the loader does not know how to handle the leaf node and will pass it to the next loader\r\n\r\n```py\r\nfor loader in self.loaders:\r\n if (r := loader(self, obj)) is not None:\r\n return r\r\n```\r\n\r\nIf you want multiple loaders to be applied the same leaf node see the example code at the bottom\r\n\r\n```py\r\n# Basic loaders\r\n@loader\r\ndef return_suffix(r: ResourceManager, path: Path) -> str:\r\n return path.suffix\r\n\r\n@loader\r\n@extension(\"mp3\")\r\ndef pygame_load(r: ResourceManager, path: Path) -> pygame.mixer.Sound:\r\n return pygame.mixer.Sound(path)\r\n\r\nfallback_image = pygame.image.load(\"fallback.png\")\r\n\r\n@loader\r\n@extension(r\"png|jpg|jpeg\")\r\n@fallback(fallback_image) # If the loading raises an Exception\r\ndef pygame_image_load(r: ResourceManager, path: Path) -> pygame.Surface:\r\n return pygame.image.load(path)\r\n\r\n# Advanced loaders\r\n\r\n# Loader that accepts certain object nodes\r\n# Requires a compatible middleware to generate such leaves\r\n@loader(Script)\r\ndef execute(r: ResourceManager, s: Script) -> object:\r\n return s.execute()\r\n\r\n# Two transformations\r\n@loader\r\ndef str_node(r: ResourceManager, path: Path) -> str:\r\n return path.read_text()\r\n\r\n@loader(str)\r\ndef str_to_int(r: ResourceManager, s: str) -> int:\r\n if s.isnumeric():\r\n return int(s)\r\n\r\n@loader\r\ndef combined(r: ResourceManager, path: Path) -> Union[str, int]:\r\n res = path\r\n for l in [str_node, str_to_int]:\r\n if (t := l(r, res)) is not None:\r\n res = t\r\n return res\r\n```\r\n\r\n# FAQ\r\n\r\n## Visual Studio Code shows that the type is any\r\n\r\nChange the `python.analysis.typeCheckingMode` setting to `strict` and it not allow you to access invalid properties. Make sure that the Python language server is Pylance\r\n\r\n```json\r\n{\r\n \"python.analysis.typeCheckingMode\": \"strict\"\r\n}\r\n```\r\n\r\n## Cannot import `TypeGuard`\r\n\r\nProject requires Python 3.10 or higher\r\n\r\n# Contributing\r\n\r\nIf you have an idea for this library, please make an issue first instead of a PR. I will be happy to discuss it with you\r\n",
"bugtrack_url": null,
"license": "MIT License Copyright (c) 2023 nopeless@github.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
"summary": "Create static path checking and type checking for your Python code",
"version": "0.1.4",
"split_keywords": [
"path",
"file",
"type"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "40ec68df4cbbdca9fc1d1d5fd70c602be061c27ea5cb2c00ca84043940daeb48",
"md5": "857532ea60bf4ff8f2afe17924d61644",
"sha256": "3a6b1c5c0bbe484659c7a729f3144527c32d7bdb47c99ca69ef77800bc078547"
},
"downloads": -1,
"filename": "pathtyped-0.1.4-py3-none-any.whl",
"has_sig": false,
"md5_digest": "857532ea60bf4ff8f2afe17924d61644",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 12798,
"upload_time": "2023-01-16T07:26:38",
"upload_time_iso_8601": "2023-01-16T07:26:38.644992Z",
"url": "https://files.pythonhosted.org/packages/40/ec/68df4cbbdca9fc1d1d5fd70c602be061c27ea5cb2c00ca84043940daeb48/pathtyped-0.1.4-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6e6f3e90a5a16a6b401c0745ebac5db1569c1a00e04119718f8f1a7cafe04639",
"md5": "1866f97b2767e1bdf057e407134caf9f",
"sha256": "e2fde390358760fd183b2de4c44830318e4e1a3e35a48faa6f78d14602102978"
},
"downloads": -1,
"filename": "pathtyped-0.1.4.tar.gz",
"has_sig": false,
"md5_digest": "1866f97b2767e1bdf057e407134caf9f",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 14535,
"upload_time": "2023-01-16T07:26:40",
"upload_time_iso_8601": "2023-01-16T07:26:40.480489Z",
"url": "https://files.pythonhosted.org/packages/6e/6f/3e90a5a16a6b401c0745ebac5db1569c1a00e04119718f8f1a7cafe04639/pathtyped-0.1.4.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-01-16 07:26:40",
"github": false,
"gitlab": false,
"bitbucket": false,
"lcname": "pathtyped"
}