Skip to content

attempt to improve import time #50

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
coverage:
precision: 2
range: [95, 100]

comment:
layout: 'header, diff, flags, files, footer'
10 changes: 6 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ jobs:
make check-dist

- name: test with extras
run: make test
run: |
make test
coverage xml

- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v1.0.7
with:
file: ./coverage.xml
env_vars: EXTRAS,PYTHON,OS
Expand All @@ -58,10 +60,10 @@ jobs:
make test
coverage xml

- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v1.0.7
with:
file: ./coverage.xml
env_vars: DEPS,PYTHON,OS
env_vars: EXTRAS,PYTHON,OS
env:
EXTRAS: no

Expand Down
22 changes: 9 additions & 13 deletions devtools/ansi.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import re
import sys
from enum import IntEnum
from typing import Any

from .utils import isatty

_ansi_template = '\033[{}m'
_ansi_re = re.compile('\033\\[((?:\\d|;)*)([a-zA-Z])')

__all__ = 'sformat', 'sprint'


def isatty(stream=None):
stream = stream or sys.stdout
try:
return stream.isatty()
except Exception:
return False
MYPY = False
if MYPY:
from typing import Any


def strip_ansi(value):
return _ansi_re.sub('', value)
import re

return re.sub('\033\\[((?:\\d|;)*)([a-zA-Z])', '', value)


class Style(IntEnum):
Expand Down Expand Up @@ -68,7 +64,7 @@ class Style(IntEnum):
# this is a meta value used for the "Style" instance which is the "style" function
function = -1

def __call__(self, input: Any, *styles, reset: bool = True, apply: bool = True):
def __call__(self, input: 'Any', *styles: 'Style', reset: bool = True, apply: bool = True) -> str:
"""
Styles text with ANSI styles and returns the new string.

Expand Down
82 changes: 49 additions & 33 deletions devtools/debug.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import ast
import dis
import inspect
import os
import pdb
import sys
from pathlib import Path
from textwrap import dedent, indent
from types import FrameType
from typing import Generator, List, Optional, Tuple

from .ansi import isatty, sformat
from .ansi import sformat
from .prettier import PrettyFormat, env_true
from .timer import Timer
from .utils import isatty

__all__ = 'Debug', 'debug'
CWD = Path('.').resolve()
MYPY = False
if MYPY:
import ast
from types import FrameType
from typing import Generator, List, Optional, Tuple


pformat = PrettyFormat(
Expand Down Expand Up @@ -78,7 +75,7 @@ class DebugOutput:
arg_class = DebugArgument
__slots__ = 'filename', 'lineno', 'frame', 'arguments', 'warning'

def __init__(self, *, filename: str, lineno: int, frame: str, arguments: List[DebugArgument], warning=None):
def __init__(self, *, filename: str, lineno: int, frame: str, arguments: 'List[DebugArgument]', warning=None):
self.filename = filename
self.lineno = lineno
self.frame = frame
Expand Down Expand Up @@ -110,22 +107,9 @@ def __repr__(self) -> str:

class Debug:
output_class = DebugOutput
complex_nodes = (
ast.Call,
ast.Attribute,
ast.Subscript,
ast.IfExp,
ast.BoolOp,
ast.BinOp,
ast.Compare,
ast.DictComp,
ast.ListComp,
ast.SetComp,
ast.GeneratorExp,
)

def __init__(
self, *, warnings: Optional[bool] = None, highlight: Optional[bool] = None, frame_context_length: int = 50
self, *, warnings: 'Optional[bool]' = None, highlight: 'Optional[bool]' = None, frame_context_length: int = 50
):
self._show_warnings = self._env_bool(warnings, 'PY_DEVTOOLS_WARNINGS', True)
self._highlight = self._env_bool(highlight, 'PY_DEVTOOLS_HIGHLIGHT', None)
Expand All @@ -149,6 +133,8 @@ def format(self, *args, **kwargs) -> DebugOutput:
return self._process(args, kwargs, 'format')

def breakpoint(self):
import pdb

pdb.Pdb(skip=['devtools.*']).set_trace()

def timer(self, name=None, *, verbose=True, file=None, dp=3) -> Timer:
Expand All @@ -160,7 +146,7 @@ def _process(self, args, kwargs, func_name: str) -> DebugOutput:
"""
# HELP: any errors other than ValueError from _getframe? If so please submit an issue
try:
call_frame: FrameType = sys._getframe(2)
call_frame: 'FrameType' = sys._getframe(2)
except ValueError:
# "If [ValueError] is deeper than the call stack, ValueError is raised"
return self.output_class(
Expand All @@ -175,15 +161,20 @@ def _process(self, args, kwargs, func_name: str) -> DebugOutput:
function = call_frame.f_code.co_name
if filename.startswith('/'):
# make the path relative
from pathlib import Path

cwd = Path('.').resolve()
try:
filename = str(Path(filename).relative_to(CWD))
filename = str(Path(filename).relative_to(cwd))
except ValueError:
# happens if filename path is not within CWD
pass

lineno = call_frame.f_lineno
warning = None

import inspect

try:
file_lines, _ = inspect.findsource(call_frame)
except OSError:
Expand Down Expand Up @@ -214,7 +205,23 @@ def _args_inspection_failed(self, args, kwargs):
for name, value in kwargs.items():
yield self.output_class.arg_class(value, name=name)

def _process_args(self, func_ast, code_lines, args, kwargs) -> Generator[DebugArgument, None, None]: # noqa: C901
def _process_args(self, func_ast, code_lines, args, kwargs) -> 'Generator[DebugArgument, None, None]': # noqa: C901
import ast

complex_nodes = (
ast.Call,
ast.Attribute,
ast.Subscript,
ast.IfExp,
ast.BoolOp,
ast.BinOp,
ast.Compare,
ast.DictComp,
ast.ListComp,
ast.SetComp,
ast.GeneratorExp,
)

arg_offsets = list(self._get_offsets(func_ast))
for i, arg in enumerate(args):
try:
Expand All @@ -226,7 +233,7 @@ def _process_args(self, func_ast, code_lines, args, kwargs) -> Generator[DebugAr

if isinstance(ast_node, ast.Name):
yield self.output_class.arg_class(arg, name=ast_node.id)
elif isinstance(ast_node, self.complex_nodes):
elif isinstance(ast_node, complex_nodes):
# TODO replace this hack with astor when it get's round to a new release
start_line, start_col = arg_offsets[i]

Expand All @@ -252,12 +259,14 @@ def _process_args(self, func_ast, code_lines, args, kwargs) -> Generator[DebugAr
yield self.output_class.arg_class(value, name=name, variable=kw_arg_names.get(name))

def _parse_code(
self, filename: str, file_lines: List[str], first_line: int, last_line: int
) -> Tuple[ast.AST, List[str]]:
self, filename: str, file_lines: 'List[str]', first_line: int, last_line: int
) -> 'Tuple[ast.AST, List[str]]':
"""
All we're trying to do here is build an AST of the function call statement. However numerous ugly interfaces,
lack on introspection support and changes between python versions make this extremely hard.
"""
from textwrap import dedent
import ast

def get_code(_last_line: int) -> str:
lines = file_lines[first_line - 1 : _last_line]
Expand Down Expand Up @@ -295,10 +304,12 @@ def get_code(_last_line: int) -> str:
return func_ast, code_lines

@staticmethod # noqa: C901
def _statement_range(call_frame: FrameType, func_name: str) -> Tuple[int, int]: # noqa: C901
def _statement_range(call_frame: 'FrameType', func_name: str) -> 'Tuple[int, int]': # noqa: C901
"""
Try to find the start and end of a frame statement.
"""
import dis

# dis.disassemble(call_frame.f_code, call_frame.f_lasti)
# pprint([i for i in dis.get_instructions(call_frame.f_code)])

Expand Down Expand Up @@ -343,15 +354,20 @@ def _statement_range(call_frame: FrameType, func_name: str) -> Tuple[int, int]:
return first_line, last_line

@staticmethod
def _wrap_parse(code, filename):
def _wrap_parse(code: str, filename: str) -> 'ast.Call':
"""
async wrapper is required to avoid await calls raising a SyntaxError
"""
import ast
from textwrap import indent

code = 'async def wrapper():\n' + indent(code, ' ')
return ast.parse(code, filename=filename).body[0].body[0].value

@staticmethod
def _get_offsets(func_ast):
import ast

for arg in func_ast.args:
start_line, start_col = arg.lineno - 2, arg.col_offset - 1

Expand Down
Loading