Skip to content

Commit

Permalink
v1.2.0. Adds a mock interface that I needed for another project, this…
Browse files Browse the repository at this point in the history
… interface is only partly related to issue #35 and issue #45. It might eventually work for all of those ideas but right now the interface is rough. Moves data.py into the data/ util to remove a warning, I'm not sure why python was complaining about the data directory not having an __init__.py file when there was a data.py file, but it was. Updates the setup.py file
  • Loading branch information
Jaymon committed Feb 5, 2020
1 parent 046c8d4 commit 8839674
Show file tree
Hide file tree
Showing 6 changed files with 217 additions and 31 deletions.
60 changes: 35 additions & 25 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,16 @@
from codecs import open


name = 'testdata'
with open(os.path.join(name, "__init__.py")) as f:
version = re.search("^__version__\s*=\s*[\'\"]([^\'\"]+)", f.read(), flags=re.I | re.M).group(1)

long_description = ""
if os.path.isfile('README.rst'):
with open('README.rst', encoding='utf-8') as f:
long_description = f.read()

setup(
name=name,
version=version,
description='Easily generate random unicode test data among other things',
long_description=long_description,
author='Jay Marcyes',
author_email='jay@marcyes.com',
url='http://github.com/Jaymon/{}'.format(name),
#py_modules=[name],
packages=find_packages(),
package_data={name: ['data/*']},
license="MIT",
#tests_require=[],
classifiers=[ # https://pypi.python.org/pypi?:action=list_classifiers
name = "testdata"
kwargs = {
"name": name
"description": 'Easily generate random unicode test data among other things',
"author": 'Jay Marcyes',
"author_email": 'jay@marcyes.com',
"url": 'http://github.com/Jaymon/{}'.format(name),
"package_data": {name: ['data/*']},
"license": "MIT",
"classifiers": [ # https://pypi.python.org/pypi?:action=list_classifiers
'Development Status :: 4 - Beta',
'Environment :: Plugins',
'Intended Audience :: Developers',
Expand All @@ -41,6 +28,29 @@
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
],
test_suite = "{}_test".format(name),
)
"test_suite": "{}_test".format(name),
}

def read(path):
if os.path.isfile(path):
with open(path, encoding='utf-8') as f:
return f.read()
return ""


vpath = os.path.join(name, "__init__.py")
if os.path.isfile(vpath):
kwargs["packages"] = find_packages()
else:
vpath = "{}.py".format(name)
kwargs["py_modules"] = [name]
kwargs["version"] = re.search(r"^__version__\s*=\s*[\'\"]([^\'\"]+)", read(vpath), flags=re.I | re.M).group(1)


# https://pypi.org/help/#description-content-type
kwargs["long_description"] = read('README.md')
kwargs["long_description_content_type"] = "text/markdown"


setup(**kwargs)

45 changes: 43 additions & 2 deletions testdata/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import pkgutil

from .compat import *
from .utils import String, ByteString
from .utils import String, ByteString, Mock
from .data import (
_names,
_unicode_names,
Expand Down Expand Up @@ -75,7 +75,7 @@
from .image import make_png


__version__ = '1.1.6'
__version__ = '1.2.0'


# get rid of "No handler found" warnings (cribbed from requests)
Expand Down Expand Up @@ -669,6 +669,8 @@ def get_filename(ext="", prefix="", name=""):

return get_module_name(prefix=prefix, postfix=ext, name=name)
get_file_name = get_filename
filename = get_filename
file_name = get_filename


def get_module_name(bits=1, prefix="", postfix="", name=""):
Expand Down Expand Up @@ -704,6 +706,16 @@ def get_module_name(bits=1, prefix="", postfix="", name=""):
get_modpath = get_module_name
get_modulepath = get_module_name
get_module_path = get_module_name
modulename = get_module_name
module_name = get_module_name


def get_classname(name=""):
n = get_filename(name=name)
return n.title()
get_class_name = get_classname
classname = get_classname
class_name = get_classname


def get_source_filepath(v):
Expand Down Expand Up @@ -1541,6 +1553,35 @@ def patch(mod, patches=None, **kwargs_patches):
return m


def mock_class(name="", **props_and_methods):
"""create a class with the given method and properties
:param name: string, the name of the class, default is just a random classname
:param **props_and_methods: dict, keys will be attributes on the object
:returns: type, the object
"""
classname = get_classname(name=name)
return type(ByteString(classname), (object,), props_and_methods)


def mock_instance(name="", **props_and_methods):
"""This is the same as mock_class but returns an instance of that class
see mock_class() docs"""
return mock_class(name, **props_and_methods)()


def mock(**props_and_methods):
"""Create a mocked object that tries to be really magical
This is different than mock_instance because it creates an object that uses a lot
of magic to try and be a jack of all trades
:returns: Mock instance
"""
return Mock(**props_and_methods)


def get_birthday(as_str=False, start_age=18, stop_age=100):
"""
return a random YYYY-MM-DD
Expand Down
File renamed without changes.
8 changes: 4 additions & 4 deletions testdata/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,21 +193,21 @@ def assertRegex(self, s, r):
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRegex
"""
self.assertRegexpMatches(s, r)
return self.assertRegexpMatches(s, r)

def assertNotRegex(self, s, r):
"""brings py3 assert to py2
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertNotRegex
"""
self.assertNotRegexpMatches(s, r)
return self.assertNotRegexpMatches(s, r)

def assertRaisesRegex(self, exception, regex, callable, *args, **kwds):
def assertRaisesRegex(self, exception, regex, *args, **kwds):
"""brings py3 assert to py2
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaisesRegex
"""
self.assertRaisesRegexp(exception, regex, callable, *args, **kwds)
return self.assertRaisesRegexp(exception, regex, *args, **kwds)

def assertLogs(self, logger=None, level=None):
"""This just makes sure something gets logged, and raises an exception
Expand Down
96 changes: 96 additions & 0 deletions testdata/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, division, print_function, absolute_import
import types
import inspect
import sys

from .compat import *


Expand Down Expand Up @@ -37,3 +40,96 @@ def __new__(cls, val, encoding="UTF-8"):
instance.encoding = encoding
return instance


class Mock(object):
"""Do our very best to mock functionality
This class tries to make it easy to mock an object, and can be used to quickly
get an object that can match a certain state, it isn't exhaustive and will fail
things like isinstance checks, but it does it's very best to get you a mocked
up object that you can use for testing.
If the given attribute doesn't exist then this will return an instance of self
so you can chain together objects of any depth with just the first object
:Example:
m = Mock(foo=1)
m.foo() # 1
m.foo # 1
m.bar.che.foo # 1
m = Mock(foo=ValueError("the error you want to raise"))
m.foo(1, 2) # raises ValueError
"""
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)

def __getitem__(self, key):
try:
v = super(Mock, self).__getattribute__(key)

except AttributeError:
return self

else:
self._raise_if_error(v)
#self.__dict__["_raise_if_error"](v)
return v

def __getattribute__(self, key):
if hasattr(type(self), key):
return super(Mock, self).__getattribute__(key)

try:
v = super(Mock, self).__getattribute__(key)

except AttributeError:
return self

else:
if v is not None:

self._raise_if_error(v)
#self.__dict__["_raise_if_error"](v)

if not hasattr(v, "__call__"):

frames = inspect.stack()
frame = frames[1]
loc = "\n".join(frame[4])
if ".{}(".format(key) in loc or ".{}".format(key) not in loc:

class MockAttr(type(v)):
def __new__(cls, *args, **kwargs):
return super(MockAttr, cls).__new__(cls, *args, **kwargs)

def __call__(self, *args, **kwargs):
return v

return MockAttr(v)

return v

def _raise_if_error(self, v):
do_raise = False
try:
do_raise = isinstance(v, Exception)

except TypeError:
pass

else:
if do_raise:
raise v

try:
do_raise = issubclass(v, Exception)

except TypeError:
pass

else:
if do_raise:
raise v()

39 changes: 39 additions & 0 deletions testdata_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,45 @@ def test_modules_prefix(self):
self.assertTrue(modpath in mps)


class MockTest(TestCase):
# def test_mock_instance(self):
# instance = testdata.mock_instance(foo=1, bar=lambda *a, **kw: 5)
# pout.v(instance.foo)
# pout.v(instance.bar())

def test_mock_instance(self):
instance = testdata.mock(foo="1", bar=2, che=lambda *a, **k: 3)
self.assertEqual("1", instance.foo)
self.assertEqual("1", instance.foo())
self.assertTrue(isinstance(instance.bar, int))
self.assertTrue(isinstance(instance.bar(), int))

def test_mock_dict(self):
d = testdata.mock(foo="1", bar=2)
self.assertEqual("1", d["foo"])
self.assertEqual("1", d["che"]["baz"]["foo"])

def test_mock_depth(self):
"""make sure we can mock one object and have it look like many objects"""
instance = testdata.mock(foo=1)
self.assertEqual(1, instance.bar.che.foo)

def test_mock_error(self):
instance = testdata.mock(foo=AttributeError, bar=RuntimeError("bar is bad"))

with self.assertRaises(AttributeError):
instance.foo()

with self.assertRaises(AttributeError):
instance.foo

with self.assertRaisesRegex(RuntimeError, "bar\s+is\s+bad"):
instance.bar

with self.assertRaisesRegex(RuntimeError, "bar\s+is\s+bad"):
instance.bar()


class TestdataTest(TestCase):
def test_get_filename(self):
n = testdata.get_filename(ext="py", name="foo")
Expand Down

0 comments on commit 8839674

Please sign in to comment.