Skip to content
This repository has been archived by the owner on May 4, 2021. It is now read-only.

Commit

Permalink
Merge pull request #14 from herczy/f/autodecorator-fix
Browse files Browse the repository at this point in the history
fixes for autodecorator
  • Loading branch information
attilammagyar committed May 20, 2015
2 parents a7d8d7e + 4141d32 commit b599f2e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 20 deletions.
44 changes: 44 additions & 0 deletions features/issue-13-indirect-import.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#
# Copyright (c) 2013-2015 BalaBit
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

Feature: Issue #13: indirect imports of foreign objects should not be checked

As a developer
In order to avoid conflicts with external libraries
I want typesafety to leave foreign objects alone

Scenario: autodecorator doesn't affect objects imported from external modules
Given that "external_lib.py" contains the following code:
"""
def external_function(a: int) -> int:
return 1
"""
And that "mylib.py" contains the following code:
"""
from external_lib import *
"""
And that "myapp.py" contains the following code:
"""
from typesafety.validator import Validator
import typesafety; typesafety.activate(filter_func=lambda name: name == 'mylib')
from mylib import *
assert not Validator.is_function_validated(external_function), \
"external_function should not be validated but it is."
"""
When "python3 myapp.py" is run
Then it must pass
5 changes: 3 additions & 2 deletions features/steps/steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
@given('that "{file_name}" contains the following code')
def write_file(context, file_name):
with open(os.path.join(context.test_dir, file_name), "w") as f:
f.write(context.text)
print(context.text, file=f)


@when('"{command}" is run')
Expand Down Expand Up @@ -64,4 +64,5 @@ def assert_success(context):

assert_equal(
0, return_code,
"Unexpected return code of {!r}\nSTDOUT:\n{}".format(command, output))
"Unexpected return code of {!r}\nSTDOUT:\n{}".format(command, output)
)
8 changes: 4 additions & 4 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ deps=
coverage
behave
commands=
pep8 --repeat typesafety
pylint -f parseable --rcfile=.pylintrc typesafety
pep8 --repeat typesafety
pylint -f parseable --rcfile=.pylintrc typesafety
nosetests --with-coverage --with-doctest typesafety
behave --format progress2
{toxinidir}/scripts/copyright.py --check .
behave
{toxinidir}/scripts/copyright.py --check .
py.test typesafety

[testenv:py32]
Expand Down
45 changes: 31 additions & 14 deletions typesafety/autodecorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,34 +51,51 @@ def __init__(self, decorator):
self.__decorator = decorator

def decorate(self, module):
self.__decorate(module, use_dict=module.__dict__)
if inspect.isclass(module):
self.__decorate_class(module, use_dict=module.__dict__)

else:
self.__decorate_module(module, use_dict=module.__dict__)

def __is_attribute_mutable(self, object_dict, attr):
if '__slots__' not in object_dict:
return True

return attr in object_dict['__slots__']

def __decorate(self, module, *, use_dict):
def __decorate_module(self, module, *, use_dict):
for key, value in self.__iterate_decorables(use_dict):
if self.__is_object_external(module, value):
continue

self.__decorate_item(module, key, value)

def __is_object_external(self, module, obj):
return hasattr(obj, '__module__') and obj.__module__ != module.__name__

def __decorate_class(self, cls, *, use_dict):
for key, value in self.__iterate_decorables(use_dict):
self.__decorate_item(cls, key, value)

def __iterate_decorables(self, use_dict):
for key, value in use_dict.items():
if not self.__is_attribute_mutable(use_dict, key):
continue

if inspect.isfunction(value):
self.__decorate_function(module, key, value)
yield key, value

elif inspect.isclass(value):
self.__decorate(value, use_dict=value.__dict__)
def __decorate_item(self, module, key, value):
if inspect.isfunction(value):
self.__decorate_function(module, key, value)

elif inspect.ismodule(value):
if self.__submodule_of(value, module):
self.__decorate(value, use_dict=value.__dict__)
elif inspect.isclass(value):
self.__decorate_class(value, use_dict=value.__dict__)

elif isinstance(value, property):
self.__decorate_property(module, key, value)
elif isinstance(value, property):
self.__decorate_property(module, key, value)

elif isinstance(value, (staticmethod, classmethod)):
self.__decorate_special_method(module, key, value)
elif isinstance(value, (staticmethod, classmethod)):
self.__decorate_special_method(module, key, value)

def __decorate_function(self, module, key, value):
setattr(module, key, self.__decorator(value))
Expand Down Expand Up @@ -106,7 +123,7 @@ def __decorate_special_method(self, module, key, value):
setattr(module, key, decorated)

def __submodule_of(self, basemodule, submodule):
return submodule.__name__.startswith(basemodule.__name__ + '.')
return submodule.startswith(basemodule + '.')


def decorate_module(module, *, decorator):
Expand Down
28 changes: 28 additions & 0 deletions typesafety/tests/externalmodule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# Copyright (c) 2013-2015 BalaBit
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#

# External module whose objects should not be decorated when another
# module imports some of its objects.


class UndecoratedClass:
def method(self, argument: int) -> int:
return argument * 2


def undecorated_function(argument: int) -> int:
return argument * 3
3 changes: 3 additions & 0 deletions typesafety/tests/mockmodule.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import collections
import sys

# External objects not native to this module
from .externalmodule import UndecoratedClass, undecorated_function


if not is_above_version('3.2'):
class ClassWithSlots:
Expand Down
4 changes: 4 additions & 0 deletions typesafety/tests/test_autodecorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,7 @@ def __unload_test_module(self, name):
def test_immutable_class_attributes_are_not_decorated(self):
self.assertEquals(1, self._module.ClassWithSlots().immutable)
self.assertEquals(1234, self._module.ClassWithSlots().mutable)

def test_dont_decorate_objects_not_native_to_the_module(self):
self.assertEqual(2, self._module.UndecoratedClass().method(1))
self.assertEqual(3, self._module.undecorated_function(1))

0 comments on commit b599f2e

Please sign in to comment.