Skip to content

Commit

Permalink
Project start commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gaogaotiantian committed Dec 6, 2020
1 parent e9b9c94 commit 0330e2a
Show file tree
Hide file tree
Showing 14 changed files with 486 additions and 0 deletions.
73 changes: 73 additions & 0 deletions .github/workflows/build_test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
strategy:
matrix:
python-version: [3.6, 3.7, 3.8, 3.9]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install minimal nightly
uses: actions-rs/toolchain@v1
with:
profile: minimal
default: true
override: true
toolchain: nightly-2020-09-14
- name: Install dependencies
if: matrix.os != 'windows-latest'
run: |
python -m pip install --upgrade pip
pip install flake8 setuptools wheel twine coverage
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Install dependencies on Windows
if: matrix.os == 'windows-latest'
run: |
python -m pip install --upgrade pip
pip install flake8 setuptools wheel twine coverage
if (Test-Path -Path '.\requirements.txt' -PathType Leaf) {pip install -r requirements.txt}
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 src tests --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings.
flake8 src tests --exclude tests/data/ --count --exit-zero --statistic --ignore=E501,E122,E126,E127,E128,W503
- name: Build dist and test with unittest
if: matrix.os != 'windows-latest'
run: |
python setup.py sdist bdist_wheel
pip install dist/*.whl
python -m unittest
- name: Build dist and test with unittest on Windows
if: matrix.os == 'windows-latest'
run: |
python setup.py sdist bdist_wheel
pip install (Get-ChildItem dist/*.whl)
python -m unittest
- name: Generate coverage report
run: |
coverage run --parallel-mode --pylib -m unittest
coverage combine
coverage xml -i --include=*watchpoints* --omit=*tests*
env:
COVERAGE_RUN: True
- name: Upload report to Codecov
uses: codecov/codecov-action@v1
with:
file: ./coverage.xml
33 changes: 33 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
refresh: clean build install lint

build:
python setup.py build

install:
python setup.py install

build_dist:
make clean
python setup.py sdist bdist_wheel
pip install dist/*.whl
make test

release:
python -m twine upload dist/*

lint:
flake8 src tests --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 src tests --exclude tests/data/ --count --exit-zero --statistic --ignore=E501,E122,E126,E127,E128,W503

test:
python -m unittest

clean:
rm -rf __pycache__
rm -rf tests/__pycache__
rm -rf src/watchpoints/__pycache__
rm -rf build
rm -rf dist
rm -rf watchpoints.egg-info
rm -rf src/watchpoints.egg-info
pip uninstall -y watchpoints
13 changes: 13 additions & 0 deletions NOTICE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
Copyright 2020 Tian Gao

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
34 changes: 34 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import setuptools

with open("README.md") as f:
long_description = f.read()

with open("./src/watchpoints/__init__.py") as f:
for line in f.readlines():
if line.startswith("__version__"):
# __version__ = "0.9"
delim = '"' if '"' in line else "'"
version = line.split(delim)[1]
break
else:
print("Can't find version! Stop Here!")
exit(1)

setuptools.setup(
name="watchpoints",
version=version,
author="Tian Gao",
author_email="[email protected]",
description="watchpoints monitors read and write on variables",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/gaogaotiantian/watchpoints",
packages=setuptools.find_packages("src"),
package_dir={"":"src"},
classifiers=[
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent"
],
)
10 changes: 10 additions & 0 deletions src/watchpoints/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


from .watch import Watch

__version__ = "0.0.1"


watch = Watch()
39 changes: 39 additions & 0 deletions src/watchpoints/ast_monkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


import ast
import copy


def to_store(node):
node.ctx = ast.Store()
return node


def ast_transform(node):
"""
:param ast.Node node: an ast node representing an expression of variable
:return ast.Node: an ast node for ```var = transform(var)```
"""
root = ast.Module(
body=[
ast.Assign(
targets=[
to_store(copy.deepcopy(node))
],
value=ast.Call(
func=ast.Name(id="_watch_transform", ctx=ast.Load()),
args=[
node
],
keywords=[]
)
)
],
type_ignores=[]
)
ast.fix_missing_locations(root)

return root
22 changes: 22 additions & 0 deletions src/watchpoints/decorator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


import functools
import inspect


def add_callback(func):

@functools.wraps(func)
def wrapper(self, *args, **kwargs):
if self._callback:
frame = inspect.currentframe().f_back
self._callback(frame, method=func, local_vars=locals(), when="pre", **self._callback_kwargs)
ret = func(self, *args, **kwargs)
if self._callback:
self._callback(frame, method=func, local_vars=locals(), when="post", **self._callback_kwargs)

return ret

return wrapper
40 changes: 40 additions & 0 deletions src/watchpoints/util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


import ast
from tokenize import generate_tokens, NEWLINE, INDENT, NL
from io import StringIO


def getline(frame):
"""
get the current logic line from the frame
"""
lineno = frame.f_lineno
filename = frame.f_code.co_filename

with open(filename, "r") as f:
linesio = StringIO("".join(f.readlines()[lineno - 1:]))
lst = []
code_string = ""
for toknum, tokval, _, _, _ in generate_tokens(linesio.readline):
if toknum == NEWLINE:
code_string = " ".join(lst)
break
elif toknum != INDENT and toknum != NL:
lst.append(tokval)

return code_string


def getargnodes(frame):
"""
get the list of arguments of the current line function
"""
line = getline(frame)
try:
tree = ast.parse(line)
return tree.body[0].value.args
except Exception:
raise Exception("Unable to parse the line {}".format(line))
30 changes: 30 additions & 0 deletions src/watchpoints/watch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


import inspect
from .ast_monkey import ast_transform
from .util import getargnodes
from .watch_list import WatchList


class Watch:
def __call__(self, *args, **kwargs):
frame = inspect.currentframe().f_back
argnodes = getargnodes(frame)
if "alias" in kwargs:
self.alias = kwargs["alias"]
else:
self.alias = None
for node in argnodes:
self._instrument(frame, node)

def _instrument(self, frame, node):
code = compile(ast_transform(node), "<string>", "exec")
frame.f_locals["_watch_transform"] = self.transform
exec(code, {}, frame.f_locals)
frame.f_locals.pop("_watch_transform")

def transform(self, val):
if type(val) is list:
return WatchList(val, alias=self.alias)
54 changes: 54 additions & 0 deletions src/watchpoints/watch_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/gaogaotiantian/watchpoints/blob/master/NOTICE.txt


from .decorator import add_callback
from .watch_print import WatchPrint


class WatchList(list):
def __init__(self, *args, **kwargs):
self._callback = WatchPrint("list")
self._callback_kwargs = {}
self._alias = kwargs.get("alias", None)
list.__init__(self, *args, **kwargs)

@add_callback
def __setitem__(self, key, value):
list.__setitem__(self, key, value)

@add_callback
def append(self, x):
list.append(self, x)

@add_callback
def extend(self, iterable):
list.extend(self, iterable)

@add_callback
def insert(self, i, x):
list.insert(self, i, x)

@add_callback
def remove(self, x):
list.remove(self, x)

@add_callback
def pop(self, i=-1):
return list.pop(self, i)

@add_callback
def clear(self):
list.clear(self)

@add_callback
def sort(self, *args, key=None, reverse=False):
list.sort(self, *args, key=key, reverse=reverse)

@add_callback
def reverse(self):
list.reverse(self)

def set_callback(self, cb, **kwargs):
self._callback = cb
self._callback_kwargs = kwargs
Loading

0 comments on commit 0330e2a

Please sign in to comment.