# Rattr rats on your attrs.
Rattr (pronounced 'ratter') is a tool to determine attribute usage in python functions. It can parse python files, follow imports and then report to you about the attributes accessed by function calls in that file.
# Status
Currently this project is under active development and likely to change significantly in the future. However we hope it might be useful and interesting to the wider python community.
# But why?
We developed rattr to help with some analytics work in python where type checkers like mypy and pyre are cumbersome.
In analytics work, we often have functions that look like this:
```python
def compute_cost_effectiveness(person):
return person.sales / person.salary
```
because we're pythonistas, the exact type of `person` is unimportant to us in this example - what's important is that it has a sales and salary attribute and that those are numbers. Annotating this function with that information for mypy would be cumbersome - and with thousands of functions it would be hard to do.
Rattr is a tool that solves the first part of this - it can detect that `compute_cost_effectiveness` needs to access "sales" and "salary" attributes and so it could tell us that the following would fail:
```python
def create_report():
people = some_database.query(Person.name, Person.sales).all()
return {person.name: compute_cost_effectiveness(person) for person in people}
```
It can also effectively compute the provenance of attributes. Suppose that you have a wide array of functions for computing information about financial products - like
```python
def compute_some_complex_risk_metric_for(security, other_data):
# proprietary and complicated logic here
security.riskiness = bla
return security
```
and you have other functions that consume that information:
```python
def should_i_buy(security):
if security.riskiness > 5:
return False
# More logic here ...
```
rattr can help you determine which functions are required for a calculation. Effectively allowing you to build powerful directed graph structures for your function libraries.
# Configuration
Rattr is configurable both via pyproject.toml and the command line.
## Command Line Args:
```
-h, --help show this help message and exit
-c TOML, --config TOML
override the default 'pyproject.toml' with another config file
-v, --version show program's version number and exit
-f {0,1,2,3}, --follow-imports {0,1,2,3}
follow imports level meanings:
0 - do not follow imports
1 - follow imports to local modules (default)
2 - follow imports to local and pip installed modules
3 - follow imports to local, pip installed, and stdlib modules
NB: following stdlib imports when using CPython will cause issues
-F PATTERN, --exclude-import PATTERN
do not follow imports to modules matching the given pattern,
regardless of the level of -f
TOML example: exclude-imports=['a', 'b']
-x PATTERN, --exclude PATTERN
exclude functions and classes matching the
given regular expression from being analysed
TOML example: exclude=['a', 'b']
-w {none,local,default,all}, --warning-level {none,local,default,all}
warnings level meaning:
none - do not show warnings
local - show warnings for <file>
default - show warnings for all files (default)
all - show warnings for all files, including low-priority
NB: errors and fatal errors are always shown
TOML example: warning-level='all'
-H, --collapse-home collapse the user's home directory in error messages
E.g.: "/home/user/path/to/file" becomes "~/path/to/file"
TOML example: collapse-home=true
-T, --truncate-deep-paths
truncate deep file paths in error messages
E.g.: "/home/user/very/deep/dir/to/file" becomes "/.../dir/to/file"
TOML example: truncate-deep-paths=true
--strict select strict mode, i.e. fail on any error
TOML example: strict=true
--threshold N set the 'badness' threshold, where 0 is infinite (default: --threshold 0)
typical badness values:
+0 - info
+1 - warning
+5 - error
+∞ - fatal
NB: badness is calculated form the target file and simplification stage
TOML example: threshold=10
-o {stats,ir,results}, --stdout {stats,ir,results}
output selection:
silent - do not print to stdout
ir - print the intermediate representation to stdout
results - print the results to stdout (default)
TOML example: stdout='results'
<file> the target source file
```
## pyproject.toml
Example toml config:
```toml
[tool.rattr]
follow-imports = 0 # options are: (0, 1, 2, 3)
strict = true
# permissive = 1 # (can be any positive int & mutex with 'strict = true')
silent = true
# show-ir = true # (mutex with 'silent = true')
# show-results = true # (mutex with 'silent = true')
# show-stats = true # (mutex with 'silent = true')
show-path = 'none' # options are: ('none', 'short', 'full')
show-warnings = 'none' # options are: ('none', 'file', 'all', 'ALL')
exclude-import = [
'a\.b\.c.*',
'd\..*',
'^e$'
]
exclude = [
'a_.*',
'b_.*',
'_c.*',
]
cache = 'cache.json'
```
Without setting any command line or toml arguments specifically, the default configuration for rattr is the following:
```toml
[tool.rattr]
follow-imports = 1
permissive = 0
show-ir = false
show-results = true
show-stats = false
show-path = 'short'
show-warnings = 'all'
exclude-import = []
exclude = []
cache = ''
```
# Developer Notes
## Annotations
Rattr provides the ability to annotate functions in the target file such that
they may be ignored completely, ignored with their results determined manually,
etc. Additionally, each assertor may provide it's own annotations to ignore
and/or define behaviour.
General Rattr annotations are located in `rattr/analyser/annotations.py` and
assertor specific annotations are to be added by the assertion developer --
however they should be placed in the file containing the `Assertor` class.
### Annotation Format
Annotations should take the form `rattr_<annotation_name>` to avoid namespace
conflicts.
### Detecting and Parsing Annotations
The `rattr/analyser/utils.py` file provides the following annotation utility
functions:
* `has_annotation(name: str, target: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> bool`
* `get_annotation(name: str, target: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> ast.expr | None`
* `parse_annotation(name: str, fn_def: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[list[Any], dict[str, Any]]` \
where the first return value is the list of the safely evaluated literal values of the positional arguments, and the second argument os the dict from the name to safely evaluated literal value of the keyword arguments\
see `safe_eval(...)`
* `parse_rattr_results_from_annotation(fn_def: ast.FunctionDef | ast.AsyncFunctionDef, *, context: Context) -> FunctionIr`
* `safe_eval(expr: ast.expr, culprit: ast.AST) -> Any | None`
* `is_name(target: str | object) -> bool`
* `is_set_of_names(target: set[str] | object) -> bool`
* `is_list_of_names(target: list[str] | object) -> bool`
* `is_list_of_call_specs(call_specs: list[tuple[Identifier, tuple[list[Identifier], dict[Identifier, str]]]] | object) -> bool`
### Provided Annotations
The provided annotations can be found in `rattr/analyser/annotations.py`.
Ignore a function (treated as though it were not defined):
```python
def rattr_ignore(*optional_target: Callable[P, R]) -> Callable[P, R]:
...
```
Usage:
```python
@rattr_ignore
def i_can_be_used_without_brackets():
...
@rattr_ignore()
def or_with_them():
...
```
Manually specify the results of a function:
```python
def rattr_results(
*,
gets: set[Identifier] | None = None,
sets: set[Identifier] | None = None,
dels: set[Identifier] | None = None,
calls: list[
tuple[
TargetName,
tuple[
list[PositionalArgumentName],
dict[KeywordArgumentName, LocalIdentifier],
],
]
]
| None = None,
) -> Callable[P, R]
...
```
For usage see below.
### Results Annotation Grammar
```python
@rattr_results(
sets={"a", "b.attr"},
calls=[
("the_name_of_the_callee_function", (["arg", "another"], {"kwarg": "a.some_attr"}))
]
)
def decorated_function(...):
...
```
Any argument to the decorator can be omitted and a default value will be used.
## Known Issues
Nested functions are not currently analysed properly, functions containing
nested functions must be annotated manually.
Comprehensions are not fully analysed, should be solvable by the same approach
as nested functions -- "un-nest" them.
--------------------------------------------------------------------------------
# Usage Notes
See `python rattr -h`.
## Errors and Warnings
Rattr can give five types of error/warnings: raised Python errors, output
beginning with "info:" or "warning:", output beginning with "error:", and
output beginning with "fatal:". The former can be seen as a developer caused
error, and the latter four are user errors.
### User Error: "info" and "warning"
Warns the user of potential issues or bad practise that should not affect the
results of analysis. Low-priority (often class based) warnings begin with
"info".
### User Error: "error"
Warns the user of potential issues or bad practise that will likely affect the
results of analysis (though there are times when the results will still be
correct).
### User Error: "fatal"
Warns the user of potential issues or bad practise so severe that the results
can not be produced for the given file.
## Results Structure
A dictionary from functions to results, which is in turn a dictionary of the
variables, attributes, etc (collectively nameables) that are get, set, called,
or deleted.
## Nameables Format
Pythonic Name | Python Example | Rattr Result Format
:---------------|:------------------------------|:---------------------
Name | `name` | `name`
Starred | `*name` | `*name`
Attribute | `name.attr` | `name.attr`
Call | `name(a, b, ...)` | `name()`
Subscript | `name[0]` or `name['key']` | `name[]`
The above can be nested in Rattr as in Python, for example the Python snippet
`name.method(arg_one, arg_two, ...).result_attr['some key']` will become
`name.method().result_attr[]`.
However, some expression do not have resolvable names. For example, given the
class `A` and instances `a_one`, `a_two`; assuming that `A` implements
`__add__`, which over two names of type `A` returns an `A`; and, `A` provides
the attribute `some_attr`; the following is legal Python code
`(a_one + a_two).some_attr`. Another example of code whose name is unresolvable
is `(3).to_bytes(length=1, byteorder='big')`.
Rattr will handle the above cases by returning a produced local name -- the
result of prepending the AST node type with an '@'. The former example
will become `@BinOp.some_attr`, and the latter `@Int.to_bytes`.
## Example Results
```
{
...
"my_function": {
"gets": [
"variable_a",
"variable_b",
"object_a.some_attr",
],
"sets": [
"object_a.set_me",
],
"dels": [],
"calls": [
"min()",
"max()"
]
},
}
```
## Supported Python Versions
At present `rattr` officially supports, and is tested under, Python versions 3.8
through 3.11 as the run-time interpreter (i.e. supports `python3.8 -m rattr ...`).
Previously Python 3.7 was supported but due to a number of `ast` changes (named
expressions and constant changes) and useful stdlib changes in Python 3.8, this it is
no longer supported (some code still makes exceptions for Python 3.7 which will be
refactored over time).
Python version outside of the given range may not be able to run `rattr` but,
hypothetically, any Python 3 code is a valid target. That is to say that while
`python3.7 -m rattr my_python_3_7_file.py` will not work
`python3.8 -m rattr my_python_3_7_file.py` (*sic*) will work.
--------------------------------------------------------------------------------
# Known Issues
For now these will throw a fatal error -- in the future Rattr should be more
"feature complete" and should handle these cases properly.
## Globals
```python
>>> y = 8
>>> def fn(a):
... global y
... y = a
...
>>> fn(3)
>>> y
3
>>>
```
## Redefinitions
```python
def fn(m):
print(m.attr_one)
m = MyCoolClass()
print(m.attr_two)
```
Rattr will produce `{ "sets": {"m.attr_one", "m.attr_two"} }`\
But should produce `{ "sets": {"m.attr_one", "@local:m.atter_two"} }`?
Raw data
{
"_id": null,
"home_page": "https://github.com/SuadeLabs/rattr",
"name": "rattr",
"maintainer": "Brandon Harris",
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": "brandon@saude.org, bpharris@pm.me",
"keywords": "automation linting type-checking attributes rats",
"author": "Suade Labs, Brandon Harris",
"author_email": "brandon@saude.org, bpharris@pm.me",
"download_url": "https://files.pythonhosted.org/packages/c4/58/c03386cc28dcb61c675f228e68de7f5201aea405ff46c02e0d39f388e196/rattr-0.2.2.tar.gz",
"platform": null,
"description": "# Rattr rats on your attrs.\n\nRattr (pronounced 'ratter') is a tool to determine attribute usage in python functions. It can parse python files, follow imports and then report to you about the attributes accessed by function calls in that file.\n\n# Status\n\nCurrently this project is under active development and likely to change significantly in the future. However we hope it might be useful and interesting to the wider python community.\n\n# But why?\n\nWe developed rattr to help with some analytics work in python where type checkers like mypy and pyre are cumbersome.\nIn analytics work, we often have functions that look like this:\n```python\ndef compute_cost_effectiveness(person):\n return person.sales / person.salary\n```\n\nbecause we're pythonistas, the exact type of `person` is unimportant to us in this example - what's important is that it has a sales and salary attribute and that those are numbers. Annotating this function with that information for mypy would be cumbersome - and with thousands of functions it would be hard to do.\n\nRattr is a tool that solves the first part of this - it can detect that `compute_cost_effectiveness` needs to access \"sales\" and \"salary\" attributes and so it could tell us that the following would fail:\n\n```python\ndef create_report():\n people = some_database.query(Person.name, Person.sales).all()\n return {person.name: compute_cost_effectiveness(person) for person in people}\n```\n\nIt can also effectively compute the provenance of attributes. Suppose that you have a wide array of functions for computing information about financial products - like\n```python\ndef compute_some_complex_risk_metric_for(security, other_data):\n # proprietary and complicated logic here\n security.riskiness = bla\n return security\n```\n\nand you have other functions that consume that information:\n```python\ndef should_i_buy(security):\n if security.riskiness > 5:\n return False\n # More logic here ...\n```\n\nrattr can help you determine which functions are required for a calculation. Effectively allowing you to build powerful directed graph structures for your function libraries.\n\n# Configuration\n\nRattr is configurable both via pyproject.toml and the command line.\n\n## Command Line Args:\n\n```\n -h, --help show this help message and exit\n -c TOML, --config TOML\n override the default 'pyproject.toml' with another config file\n\n -v, --version show program's version number and exit\n\n -f {0,1,2,3}, --follow-imports {0,1,2,3}\n follow imports level meanings:\n 0 - do not follow imports\n 1 - follow imports to local modules (default)\n 2 - follow imports to local and pip installed modules\n 3 - follow imports to local, pip installed, and stdlib modules\n\n NB: following stdlib imports when using CPython will cause issues\n\n -F PATTERN, --exclude-import PATTERN\n do not follow imports to modules matching the given pattern,\n regardless of the level of -f\n \n TOML example: exclude-imports=['a', 'b']\n\n -x PATTERN, --exclude PATTERN\n exclude functions and classes matching the\n given regular expression from being analysed\n \n TOML example: exclude=['a', 'b']\n\n -w {none,local,default,all}, --warning-level {none,local,default,all}\n warnings level meaning:\n none - do not show warnings\n local - show warnings for <file>\n default - show warnings for all files (default)\n all - show warnings for all files, including low-priority\n \n NB: errors and fatal errors are always shown\n \n TOML example: warning-level='all'\n\n -H, --collapse-home collapse the user's home directory in error messages\n E.g.: \"/home/user/path/to/file\" becomes \"~/path/to/file\"\n \n TOML example: collapse-home=true\n -T, --truncate-deep-paths\n truncate deep file paths in error messages\n E.g.: \"/home/user/very/deep/dir/to/file\" becomes \"/.../dir/to/file\"\n \n TOML example: truncate-deep-paths=true\n\n --strict select strict mode, i.e. fail on any error\n \n TOML example: strict=true\n --threshold N set the 'badness' threshold, where 0 is infinite (default: --threshold 0)\n \n typical badness values:\n +0 - info\n +1 - warning\n +5 - error\n +\u221e - fatal\n \n NB: badness is calculated form the target file and simplification stage\n \n TOML example: threshold=10\n\n -o {stats,ir,results}, --stdout {stats,ir,results}\n output selection:\n silent - do not print to stdout\n ir - print the intermediate representation to stdout\n results - print the results to stdout (default)\n \n TOML example: stdout='results'\n\n <file> the target source file\n```\n## pyproject.toml\n\nExample toml config:\n\n```toml\n[tool.rattr]\nfollow-imports = 0 # options are: (0, 1, 2, 3)\nstrict = true\n# permissive = 1 # (can be any positive int & mutex with 'strict = true')\nsilent = true\n# show-ir = true # (mutex with 'silent = true')\n# show-results = true # (mutex with 'silent = true')\n# show-stats = true # (mutex with 'silent = true')\nshow-path = 'none' # options are: ('none', 'short', 'full')\nshow-warnings = 'none' # options are: ('none', 'file', 'all', 'ALL')\nexclude-import = [ \n 'a\\.b\\.c.*',\n 'd\\..*',\n '^e$'\n]\nexclude = [\n 'a_.*',\n 'b_.*',\n '_c.*',\n]\ncache = 'cache.json'\n```\n\nWithout setting any command line or toml arguments specifically, the default configuration for rattr is the following:\n\n```toml\n[tool.rattr]\nfollow-imports = 1\npermissive = 0\nshow-ir = false\nshow-results = true\nshow-stats = false\nshow-path = 'short'\nshow-warnings = 'all'\nexclude-import = []\nexclude = []\ncache = ''\n```\n\n\n# Developer Notes\n\n## Annotations\n\nRattr provides the ability to annotate functions in the target file such that\nthey may be ignored completely, ignored with their results determined manually,\netc. Additionally, each assertor may provide it's own annotations to ignore\nand/or define behaviour.\n\nGeneral Rattr annotations are located in `rattr/analyser/annotations.py` and\nassertor specific annotations are to be added by the assertion developer --\nhowever they should be placed in the file containing the `Assertor` class.\n\n### Annotation Format\n\nAnnotations should take the form `rattr_<annotation_name>` to avoid namespace\nconflicts.\n\n### Detecting and Parsing Annotations\n\nThe `rattr/analyser/utils.py` file provides the following annotation utility\nfunctions:\n\n* `has_annotation(name: str, target: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> bool`\n* `get_annotation(name: str, target: ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef) -> ast.expr | None`\n* `parse_annotation(name: str, fn_def: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[list[Any], dict[str, Any]]` \\\n where the first return value is the list of the safely evaluated literal values of the positional arguments, and the second argument os the dict from the name to safely evaluated literal value of the keyword arguments\\\n see `safe_eval(...)`\n* `parse_rattr_results_from_annotation(fn_def: ast.FunctionDef | ast.AsyncFunctionDef, *, context: Context) -> FunctionIr`\n* `safe_eval(expr: ast.expr, culprit: ast.AST) -> Any | None`\n* `is_name(target: str | object) -> bool`\n* `is_set_of_names(target: set[str] | object) -> bool`\n* `is_list_of_names(target: list[str] | object) -> bool`\n* `is_list_of_call_specs(call_specs: list[tuple[Identifier, tuple[list[Identifier], dict[Identifier, str]]]] | object) -> bool`\n\n\n### Provided Annotations\n\nThe provided annotations can be found in `rattr/analyser/annotations.py`.\n\nIgnore a function (treated as though it were not defined):\n```python\ndef rattr_ignore(*optional_target: Callable[P, R]) -> Callable[P, R]:\n ...\n```\n\nUsage:\n```python\n@rattr_ignore\ndef i_can_be_used_without_brackets():\n ...\n\n\n@rattr_ignore()\ndef or_with_them():\n ...\n```\n\nManually specify the results of a function:\n```python\ndef rattr_results(\n *,\n gets: set[Identifier] | None = None,\n sets: set[Identifier] | None = None,\n dels: set[Identifier] | None = None,\n calls: list[\n tuple[\n TargetName,\n tuple[\n list[PositionalArgumentName],\n dict[KeywordArgumentName, LocalIdentifier],\n ],\n ]\n ]\n | None = None,\n) -> Callable[P, R]\n ...\n```\n\nFor usage see below.\n\n\n### Results Annotation Grammar\n\n```python\n\n@rattr_results(\n sets={\"a\", \"b.attr\"},\n calls=[\n (\"the_name_of_the_callee_function\", ([\"arg\", \"another\"], {\"kwarg\": \"a.some_attr\"}))\n ]\n)\ndef decorated_function(...):\n ...\n\n```\n\nAny argument to the decorator can be omitted and a default value will be used.\n\n\n## Known Issues\n\nNested functions are not currently analysed properly, functions containing\nnested functions must be annotated manually.\n\nComprehensions are not fully analysed, should be solvable by the same approach\nas nested functions -- \"un-nest\" them.\n\n\n--------------------------------------------------------------------------------\n\n\n# Usage Notes\n\nSee `python rattr -h`.\n\n\n## Errors and Warnings\n\nRattr can give five types of error/warnings: raised Python errors, output\nbeginning with \"info:\" or \"warning:\", output beginning with \"error:\", and\noutput beginning with \"fatal:\". The former can be seen as a developer caused\nerror, and the latter four are user errors.\n\n### User Error: \"info\" and \"warning\"\n\nWarns the user of potential issues or bad practise that should not affect the\nresults of analysis. Low-priority (often class based) warnings begin with\n\"info\".\n\n### User Error: \"error\"\n\nWarns the user of potential issues or bad practise that will likely affect the\nresults of analysis (though there are times when the results will still be\ncorrect).\n\n### User Error: \"fatal\"\n\nWarns the user of potential issues or bad practise so severe that the results\ncan not be produced for the given file.\n\n\n## Results Structure\n\nA dictionary from functions to results, which is in turn a dictionary of the\nvariables, attributes, etc (collectively nameables) that are get, set, called,\nor deleted.\n\n\n## Nameables Format\n\nPythonic Name | Python Example | Rattr Result Format\n:---------------|:------------------------------|:---------------------\nName | `name` | `name`\nStarred | `*name` | `*name`\nAttribute | `name.attr` | `name.attr`\nCall | `name(a, b, ...)` | `name()`\nSubscript | `name[0]` or `name['key']` | `name[]`\n\nThe above can be nested in Rattr as in Python, for example the Python snippet\n`name.method(arg_one, arg_two, ...).result_attr['some key']` will become\n`name.method().result_attr[]`.\n\nHowever, some expression do not have resolvable names. For example, given the\nclass `A` and instances `a_one`, `a_two`; assuming that `A` implements\n`__add__`, which over two names of type `A` returns an `A`; and, `A` provides\nthe attribute `some_attr`; the following is legal Python code\n`(a_one + a_two).some_attr`. Another example of code whose name is unresolvable\nis `(3).to_bytes(length=1, byteorder='big')`.\n\nRattr will handle the above cases by returning a produced local name -- the\nresult of prepending the AST node type with an '@'. The former example\nwill become `@BinOp.some_attr`, and the latter `@Int.to_bytes`.\n\n\n## Example Results\n\n```\n{\n ...\n\n \"my_function\": {\n \"gets\": [\n \"variable_a\",\n \"variable_b\",\n \"object_a.some_attr\",\n ],\n \"sets\": [\n \"object_a.set_me\",\n ],\n \"dels\": [],\n \"calls\": [\n \"min()\",\n \"max()\"\n ]\n },\n}\n```\n\n## Supported Python Versions\n\nAt present `rattr` officially supports, and is tested under, Python versions 3.8\nthrough 3.11 as the run-time interpreter (i.e. supports `python3.8 -m rattr ...`).\nPreviously Python 3.7 was supported but due to a number of `ast` changes (named\nexpressions and constant changes) and useful stdlib changes in Python 3.8, this it is\nno longer supported (some code still makes exceptions for Python 3.7 which will be\nrefactored over time).\n\nPython version outside of the given range may not be able to run `rattr` but,\nhypothetically, any Python 3 code is a valid target. That is to say that while\n`python3.7 -m rattr my_python_3_7_file.py` will not work\n`python3.8 -m rattr my_python_3_7_file.py` (*sic*) will work.\n\n\n--------------------------------------------------------------------------------\n\n\n# Known Issues\n\nFor now these will throw a fatal error -- in the future Rattr should be more\n\"feature complete\" and should handle these cases properly.\n\n\n## Globals\n\n```python\n>>> y = 8\n>>> def fn(a):\n... global y\n... y = a\n...\n>>> fn(3)\n>>> y\n3\n>>>\n```\n\n\n## Redefinitions\n\n```python\n\ndef fn(m):\n print(m.attr_one)\n\n m = MyCoolClass()\n\n print(m.attr_two)\n\n```\n\nRattr will produce `{ \"sets\": {\"m.attr_one\", \"m.attr_two\"} }`\\\nBut should produce `{ \"sets\": {\"m.attr_one\", \"@local:m.atter_two\"} }`?\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Rattr rats on your attrs.",
"version": "0.2.2",
"project_urls": {
"Homepage": "https://github.com/SuadeLabs/rattr"
},
"split_keywords": [
"automation",
"linting",
"type-checking",
"attributes",
"rats"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "2a749ce1e191c7cc3b79d9134f237effbc19f5d99432bb14a024a025adc1dd86",
"md5": "0c2a2c4b1adc87ff1b00cc97b2d8b5fe",
"sha256": "d86e507a3b03a407ee1de5d889f26202494894bc2aaa5945041630b8fec6d0d9"
},
"downloads": -1,
"filename": "rattr-0.2.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "0c2a2c4b1adc87ff1b00cc97b2d8b5fe",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.9",
"size": 153449,
"upload_time": "2024-08-02T10:31:54",
"upload_time_iso_8601": "2024-08-02T10:31:54.522229Z",
"url": "https://files.pythonhosted.org/packages/2a/74/9ce1e191c7cc3b79d9134f237effbc19f5d99432bb14a024a025adc1dd86/rattr-0.2.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "c458c03386cc28dcb61c675f228e68de7f5201aea405ff46c02e0d39f388e196",
"md5": "7f860a2defc6eb4411bd0fafc855f66b",
"sha256": "0a0440af1fec259b7bb2b454815c9691d7dce43a009b2df924c2053a29ff1a3e"
},
"downloads": -1,
"filename": "rattr-0.2.2.tar.gz",
"has_sig": false,
"md5_digest": "7f860a2defc6eb4411bd0fafc855f66b",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 180915,
"upload_time": "2024-08-02T10:31:56",
"upload_time_iso_8601": "2024-08-02T10:31:56.075973Z",
"url": "https://files.pythonhosted.org/packages/c4/58/c03386cc28dcb61c675f228e68de7f5201aea405ff46c02e0d39f388e196/rattr-0.2.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-08-02 10:31:56",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "SuadeLabs",
"github_project": "rattr",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "isort",
"specs": [
[
"==",
"5.13.2"
]
]
},
{
"name": "tomli",
"specs": [
[
"==",
"2.0.1"
]
]
},
{
"name": "attrs",
"specs": [
[
"==",
"23.2.0"
]
]
},
{
"name": "cattrs",
"specs": [
[
"==",
"23.2.3"
]
]
},
{
"name": "frozendict",
"specs": [
[
"==",
"2.4.0"
]
]
}
],
"lcname": "rattr"
}