FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

initial commit · dlitz/python-devtools@a341360 · GitHub

Commit a341360

Browse files
committed
initial commit
0 parents  commit a341360

16 files changed

Lines changed: 458 additions & 0 deletions

‎.gitignore‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.idea/
2+
env/
3+
*.py[cod]
4+
*.egg-info/
5+
build/
6+
dist/
7+
.cache/
8+
.mypy_cache/
9+
test.py
10+
.coverage
11+
htmlcov/
12+
benchmarks/*.json
13+
docs/_build/

‎.pyup.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
schedule: 'every week'

‎.travis.yml‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
language: python
2+
3+
cache: pip
4+
5+
python:
6+
- '3.5'
7+
- '3.6'
8+
- 'nightly' # currently 3.7
9+
10+
matrix:
11+
allow_failures:
12+
- python: 'nightly'
13+
14+
install:
15+
- make install
16+
- pip freeze
17+
18+
script:
19+
- make lint
20+
21+
- make test
22+
#- make docs-lint
23+
#- make docs
24+
- ./tests/check_tag.py
25+
26+
after_success:
27+
- ls -lha
28+
- bash <(curl -s https://codecov.io/bash)
29+
30+
env:
31+
- secure: "vpTd8bkwPBP0CV3EJBAwSMNMnNK3m/71dvTvBd1T4YGuefyJvYhtA7wauA5xRL9jpK2mu5QR5eo0owTUJhKi4DjpafMMd1bc4PnXlrdZFzkn3VsGmlKt74D/aJgiuiNyhd/Qvq4OxMHrMhf4f6lKWoMM1vh6yT0yp3+51SexSh2Me0Q+npxbjXwoxX5XUHRcoSLtFk4GbYI88a2I+08XWI6v+Awo/giQ5QurUJhjAklbosrrQVr1FCOkU0em5jeyZvEbZSLmaMtbX1JlRdKoJm6WMU+y9I7zj35w6ue/vgfcLz7b/HDZrBx7/L9g1LxRo80briueX/IbHvN7DOVFKvaXVmnEa6lIDdCeOLOyESpIbmjqmDKi8JeexdPNxKq4Tvo2VEA9dL2w2aw+aALNtU2OF5iEMfPTUQyosu/CNu2PKtiuZkSOdvpYbSy1WUNHJRvomdR4Olzg8ZIScNsxU3IIPdrlG/LUA8auXcE9juFeZfD6D2hQZATqWeEe/C2J7amNSD+mLLaTf6nMQw8oNtKYOvYK17M7xyvi7HXDy711Bi18U3x6Ye0xGx8CDbFwl0ICNzIk9rrSAh9hEHTvfdUUkk35pxifvO0Hrh4SArCA20ozcH/hHWBhyqGdxoIQ6KoDgNbIFIGQ6/vugxL/pt8z1sJwPfJnq8tRDAyWZvE="
32+
33+
deploy:
34+
- provider: pypi
35+
user: samuelcolvin
36+
password:
37+
secure: QbXFF2puEWjhFUpD0yu2R+wP4QI1IKIomBkMizsiCyMutlexERElranyYB8bsakvjPaJ+zU14ufffh2u7UA7Zhep/iE4skRHq4XWxnnRLHGu5nyGf3+zSM3F9MOzV32eZ4CDLJtFb6I0ensjTpodJH2EsIYHYxTgndIZn56Qbh6CStj7Xg1zm0Ujxdzm4ZLgcS28SOF/tpjsDW9+GXwc6L1mAZWYiS98gVgzL1vBd9tL9uFbbuFwGz9uhFMzFJko7vXSl8urWB4qeCspKXa9iKH7/AOYSwXTCwcg8U2hhC9UsOapnga2BubZKlU5HRfSs9fQcpnzcP2lwhSmkrEFa8VOw83hX6+bL564xK1Q4kanfGZ1fLU4FYge3iOnqjH7ajO7xEcUrcOEYUPfxM4EfdiDw0xnAzE1ITGH1/pZikF+wjlu+ez7RmmnejgK7quT1WU7keo7pSlRSfQtNgNl6xu818x0xZ1TScfN6e9npNy4TYyIooMOOeI4tMdfcR4JClkjGKhAtBk81DH7isZgPv3uwocGnKZ2S7La97CE3ADzU3MTA9xVIOSOjzwuvAe72uS2nwzqXkS9KATdATkC9QCvheJ9jIBB4UcqnHbD8L1gkqdmZwXZqHZldq8wcqNYZb+81lumy5EZ6xSoEzlLDpXHe80EjMUOBkb5fz3D44s=
38+
distributions: sdist bdist_wheel
39+
skip_upload_docs: true
40+
on:
41+
tags: true
42+
python: 3.6
43+
- provider: script
44+
script: make publish
45+
on:
46+
tags: true
47+
python: 3.6

‎LICENSE‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 Samuel Colvin
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.

‎Makefile‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
.DEFAULT_GOAL := all
2+
3+
.PHONY: install
4+
install:
5+
pip install -U setuptools pip
6+
pip install -r requirements.txt
7+
pip install -U .
8+
9+
.PHONY: isort
10+
isort:
11+
isort -rc -w 120 devtools
12+
isort -rc -w 120 tests
13+
14+
.PHONY: lint
15+
lint:
16+
python setup.py check -rms
17+
flake8 devtools/ tests/
18+
pytest devtools -p no:sugar -q
19+
20+
.PHONY: test
21+
test:
22+
pytest --cov=devtools
23+
24+
.PHONY: testcov
25+
testcov:
26+
pytest --cov=devtools
27+
@echo "building coverage html"
28+
@coverage html
29+
30+
.PHONY: all
31+
all: testcov lint
32+
33+
.PHONY: clean
34+
clean:
35+
rm -rf `find . -name __pycache__`
36+
rm -f `find . -type f -name '*.py[co]' `
37+
rm -f `find . -type f -name '*~' `
38+
rm -f `find . -type f -name '.*~' `
39+
rm -rf .cache
40+
rm -rf htmlcov
41+
rm -rf *.egg-info
42+
rm -f .coverage
43+
rm -f .coverage.*
44+
rm -rf build
45+
python setup.py clean
46+
make -C docs clean
47+
48+
.PHONY: docs
49+
docs:
50+
make -C docs html
51+
52+
.PHONY: docs-lint
53+
docs-lint:
54+
make -C docs lint

‎README.rst‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
python devtools
2+
===============
3+
4+
|BuildStatus| |Coverage| |pypi|
5+
6+
Dev tools for python. **WIP**
7+
8+
Usage
9+
-----
10+
11+
.. code:: python
12+
13+
from devtools import debug
14+
15+
whatever = [1, 2, 3]
16+
debug(whatever)
17+
18+
Usage without Import
19+
--------------------
20+
21+
modify ``/usr/lib/python3.6/sitecustomize.py`` making ``debug`` available in any python 3.6 code
22+
23+
.. code:: python
24+
25+
from devtools import debug
26+
__builtins__['debug'] = debug
27+
28+
29+
.. |BuildStatus| image:: https://travis-ci.org/samuelcolvin/python-devtools.svg?branch=master
30+
:target: https://travis-ci.org/samuelcolvin/python-devtools
31+
.. |Coverage| image:: https://codecov.io/gh/samuelcolvin/python-devtools/branch/master/graph/badge.svg
32+
:target: https://codecov.io/gh/samuelcolvin/python-devtools
33+
.. |pypi| image:: https://img.shields.io/pypi/v/python-devtools.svg
34+
:target: https://pypi.python.org/pypi/python-devtools

‎devtools/__init__.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# flake8: noqa
2+
from .debug import *
3+
from .version import VERSION

‎devtools/debug.py‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import ast
2+
import inspect
3+
import re
4+
import warnings
5+
from pathlib import Path
6+
from textwrap import dedent
7+
from typing import Generator, List
8+
9+
__all__ = ['Debug', 'debug']
10+
CWD = Path('.').resolve()
11+
12+
13+
class DebugArgument:
14+
__slots__ = 'value', 'name', 'extra'
15+
16+
def __init__(self, value, *, name=None, **extra):
17+
self.value = value
18+
self.name = name
19+
self.extra = {k: v for k, v in extra.items() if v}
20+
21+
def value_str(self):
22+
if isinstance(self.value, str):
23+
return '"{}"'.format(self.value)
24+
return str(self.value)
25+
26+
def __str__(self) -> str:
27+
template = '{value} ({self.value.__class__.__name__}) {extra}'
28+
if self.name:
29+
template = '{self.name} = ' + template
30+
return template.format(
31+
self=self,
32+
value=self.value_str(),
33+
extra=' '.join('{}={}'.format(k, v) for k, v in self.extra.items())
34+
).rstrip(' ') # trailing space if extra is empty
35+
36+
37+
class DebugOutput:
38+
"""
39+
Represents the output of a debug command.
40+
"""
41+
arg_class = DebugArgument
42+
__slots__ = 'filename', 'lineno', 'frame', 'arguments'
43+
44+
def __init__(self, *, filename: str, lineno: int, frame: str, arguments: List[DebugArgument]):
45+
self.filename = filename
46+
self.lineno = lineno
47+
self.frame = frame
48+
self.arguments = arguments
49+
50+
def __str__(self) -> str:
51+
template = '{s.filename}:{s.lineno} {s.frame}'
52+
if len(self.arguments) == 1:
53+
return (template + ': {a}').format(s=self, a=self.arguments[0])
54+
else:
55+
return (template + '\n {a}').format(s=self, a='\n '.join(str(a) for a in self.arguments))
56+
57+
def __repr__(self) -> str:
58+
arguments = ' '.join(str(a) for a in self.arguments)
59+
return '<DebugOutput {s.filename}:{s.lineno} {s.frame} arguments: {a}>'.format(s=self, a=arguments)
60+
61+
62+
class Debug:
63+
output_class = DebugOutput
64+
65+
def __call__(self, *args, **kwargs):
66+
print(self._process(args, kwargs), flush=True)
67+
68+
def format(self, *args, **kwargs):
69+
return self._process(args, kwargs)
70+
71+
def _process(self, args, kwargs):
72+
curframe = inspect.currentframe()
73+
frames = inspect.getouterframes(curframe, context=20)
74+
# BEWARE: this must be call by a method which in turn is called "directly" for the frame to be correct
75+
call_frame = frames[2]
76+
77+
filename = call_frame.filename
78+
if filename.startswith('/'):
79+
# make the path relative
80+
try:
81+
filename = str(Path(filename).relative_to(CWD))
82+
except ValueError:
83+
# happens if filename path is not within CWD
84+
pass
85+
86+
call_lines = []
87+
for line in range(call_frame.index, 0, -1):
88+
new_line = call_frame.code_context[line]
89+
call_lines.append(new_line)
90+
if re.search('debug *\(', new_line):
91+
break
92+
call_lines.reverse()
93+
94+
return self.output_class(
95+
filename=filename,
96+
lineno=call_frame.lineno - len(call_lines),
97+
frame=call_frame.function,
98+
arguments=list(self._process_args(call_lines, args, kwargs))
99+
)
100+
101+
def _process_args(self, call_lines, args, kwargs) -> Generator[DebugArgument, None, None]: # noqa: C901
102+
code = dedent(''.join(call_lines))
103+
func_ast = ast.parse(code).body[0].value
104+
105+
arg_offsets = list(self._get_offsets(func_ast))
106+
for arg, ast_node, i in zip(args, func_ast.args, range(1000)):
107+
if isinstance(ast_node, ast.Name):
108+
yield self.output_class.arg_class(arg, name=ast_node.id)
109+
elif isinstance(ast_node, ast.Str):
110+
yield self.output_class.arg_class(arg, len=len(arg))
111+
elif isinstance(ast_node, (ast.Num, ast.List, ast.Dict, ast.Set)):
112+
yield self.output_class.arg_class(arg)
113+
elif isinstance(ast_node, (ast.Call, ast.Compare)):
114+
# TODO replace this hack with astor when it get's a new release
115+
end = -2
116+
try:
117+
next_line, next_offset = arg_offsets[i + 1]
118+
if next_line == ast_node.lineno:
119+
end = next_offset
120+
except IndexError:
121+
pass
122+
name = call_lines[ast_node.lineno - 1][ast_node.col_offset:end]
123+
yield self.output_class.arg_class(arg, name=name.strip(' ,'))
124+
else:
125+
warnings.warn('Unknown type: {}'.format(ast.dump(ast_node)), category=RuntimeError)
126+
yield self.output_class.arg_class(arg)
127+
128+
kw_arg_names = {}
129+
for kw in func_ast.keywords:
130+
if isinstance(kw.value, ast.Name):
131+
kw_arg_names[kw.arg] = kw.value.id
132+
for name, value in kwargs.items():
133+
yield self.output_class.arg_class(value, name=name, variable=kw_arg_names.get(name))
134+
135+
@classmethod
136+
def _get_offsets(cls, func_ast):
137+
for arg in func_ast.args:
138+
yield arg.lineno, arg.col_offset
139+
for kw in func_ast.keywords:
140+
yield kw.value.lineno, kw.value.col_offset - len(kw.arg) - 1
141+
142+
143+
debug = Debug()

‎devtools/version.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from distutils.version import StrictVersion
2+
3+
__all__ = ['VERSION']
4+
5+
VERSION = StrictVersion('0.0.1')

‎requirements.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#-r docs/requirements.txt
2+
-r tests/requirements.txt
3+
4+
ipython # pyup: ignore

0 commit comments

Comments
 (0)

Back | FazBrowse Home | New Git URL