Coder Social home page Coder Social logo

clarete / forbiddenfruit Goto Github PK

View Code? Open in Web Editor NEW
810.0 29.0 52.0 149 KB

Patch built-in python objects

Home Page: https://clarete.li/forbiddenfruit/

License: GNU General Public License v3.0

Python 83.22% C 11.92% Makefile 4.86%
python monkey-patching

forbiddenfruit's Introduction

Build Status

Forbidden Fruit

Forbidden Fruit

This project allows Python code to extend built-in types.

If that's a good idea or not, you tell me. The first need this project attended was allowing a Python assertion library to implement a similar API to RSpec Expectations and should.js. But people got creative and used it to among other things spy on things or to integrate profiling.

Tiny Example

It basically allows you to patch built-in objects, declared in C through python. Just like this:

  1. Add a new method to the int class:
from forbiddenfruit import curse


def words_of_wisdom(self):
    return self * "blah "


curse(int, "words_of_wisdom", words_of_wisdom)

assert (2).words_of_wisdom() == "blah blah "
  1. Add a classmethod to the str class:
from forbiddenfruit import curse


def hello(self):
    return "blah"


curse(str, "hello", classmethod(hello))

assert str.hello() == "blah"

Reversing a curse

If you want to free your object from a curse, you can use the reverse() function. Just like this:

from forbiddenfruit import curse, reverse

curse(str, "test", "blah")
assert 'test' in dir(str)

# Time to reverse the curse
reverse(str, "test")
assert 'test' not in dir(str)

Beware: reverse() only deletes attributes. If you curse()'d to replace a pre-existing attribute, reverse() won't re-install the existing attribute.

Context Manager / Decorator

cursed() acts as a context manager to make a curse(), and then reverse() it on exit. It uses contextlib.contextmanager(), so on Python 3.2+ it can also be used as a function decorator. Like so:

from forbiddenfruit import cursed

with cursed(str, "test", "blah"):
    assert str.test == "blah"

assert "test" not in dir(str)


@cursed(str, "test", "blah")
def function():
    assert str.test == "blah"


function()

assert "test" not in dir(str)

Compatibility

Forbbiden Fruit is tested on CPython 2.7, 3.0, and 3.3-3.7.

Since Forbidden Fruit is fundamentally dependent on the C API, this library won't work on other python implementations, such as Jython, pypy, etc.

License

Copyright (C) 2013,2019 Lincoln Clarete [email protected]

This software is available under two different licenses at your choice:

GPLv3

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.

MIT

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Logo by

Kimberly Chandler, from The Noun Project

Changelog

0.1.4

  • Add cursed() context manager/decorator
  • Conditionally build test C extension
  • Allow cursing dunder methods with non functions
  • Fix dual licensing issues. Distribute both GPLv3 & MIT license files.

forbiddenfruit's People

Contributors

adamchainz avatar bitdeli-chef avatar clarete avatar cologler avatar jongy avatar julian avatar lukasgraf avatar pschanely avatar smola avatar stephenbrodiewild avatar timgates42 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

forbiddenfruit's Issues

curse error

import forbiddenfruit

forbiddenfruit.curse(str, 'first', lambda x: x[0])
'123'.first() # '1'

is Okay. But if you try:

import forbiddenfruit

hasattr(str, 'first')
forbiddenfruit.curse(str, 'first', lambda x: x[0])
'123'.first() # raise AttributeError

Any patch for this?

Cursing a reversed curse

Example:

Python 3.5.0 (default, Oct 14 2015, 19:49:57) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse, reverse
>>> curse(str, 'one', 1)
>>> str.one
1
>>> reverse(str, 'one')
>>> curse(str, 'one', 2)
>>> str.one
1

Is this the expected behaviour? I don't see it documented anywhere and the tests don't seem to cover this 'edge case'.

Is the C ffruit module needed?

Is the C ffruit module required for the actual module to work? I can't see it referenced outside of the tests folder, and including it in the setup.py means that Windows users can't install the module without a C compiler.

Possible simplification of patchable_builtin()

I recently needed to patch builtin methods. Didn't know of forbiddenfruit at first, so I searched & found some workaround, and later I found this cool project via this SO question.

To work around the dictproxy/mappingproxy "issue" without diving into ctypes and messing with raw objects, I used the nice gc.get_referents method, which (together with its counterpart gc.get_referrers) lets you "get your hands" on objects you might have not been able to reach otherwise. dictproxy/mappingproxy are 2 dummy objects holding only a single reference, to the underlying mapping. So int_dict = gc.get_referents(int.__dict__)[0] is enough. With this simpler patchable_builtin, tests pass:

def patchable_builtin(klass):
    import gc
    refs = gc.get_referents(klass.__dict__)
    assert len(refs) == 1
    return refs[0]

Works the same, but simpler, so nicer IMO. Can open a PR with this improvement if you'd like.

Some redefinitions blow up Python

Some redefinitions set the interpreter on fire. For example:

Python 2.7.2 (default, Oct 11 2012, 20:14:37)
[GCC 4.2.1 Compatible Apple Clang 4.0 (tags/Apple/clang-418.0.60)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse
>>> def notpop(self, key):
...     return self[key]
...
>>> curse(dict, 'pop', notpop)
>>> d = dict(a=1,b=2,c=3)
>>> d.pop('a')
Segmentation fault: 11

I had good results adding methods to int and dict, and adding or modifying methods in str. But my explorations in modifying existing dict methods all go BOOM.

This may be related to the int circumvention of cursed methods reported in Issue #4, but the crash behavior is more severe and it could be caused by something else...so reporting separately.

overriding __getitem__ seems to have no effect

Hi,

Thank you for your useful module.

In my case, I would like to override __getitem__, but that does not seems to be working.
I have installed forbiddenfruit 0.1.3 and I run python 3.5.2.

For the following piece of code (test.py):

from forbiddenfruit import *

def my_getitem(self, key):
    print("my_getitem : self ", self)
    print("my_getitem : key ", key)
    return self.__getitem__(key)


curse(list, "__getitem__", my_getitem)

def my_contains(self, other):
    print("my_contains : self ", self)
    print("my_contains : other ", other)
    return self.__contains__(other)

curse(list, "__contains__", my_contains)

l = [1, 2, 3, 4]

print("l:", l)
print(l[0])
print(2 in l)
print(l.__getitem__(2))

I obtain (python3 test.py):

l: [1, 2, 3, 4]
1
my_contains : self  [1, 2, 3, 4]
my_contains : other  2
True
3

So, while __contains__ is well intercepted, this is not the case for __getitem__.
I wonder if this is something that is missing in forbiddenfruit or if this is something related to the language.
Best,
christophe

0.1.3 breaks overriding __init_subclass__

Hi.

I have this: https://pypi.org/project/composition/ which works great on 0.1.2,
but 0.1.3 appears to have broken it (highly important production software, so
we had a full blown outage today, millions were lost).

The traceback looks like:

?  venv/bin/python -c 'import composition'                                                                                                                              julian@Air
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/Users/julian/Desktop/venv/lib/python3.7/site-packages/composition.py", line 24, in <module>
    forbiddenfruit.curse(object, "__init_subclass__", dikembe_mutombo)
  File "/Users/julian/Desktop/venv/lib/python3.7/site-packages/forbiddenfruit/__init__.py", line 412, in curse
    _curse_special(klass, attr, value)
  File "/Users/julian/Desktop/venv/lib/python3.7/site-packages/forbiddenfruit/__init__.py", line 327, in _curse_special
    assert isinstance(func, FunctionType)
AssertionError

which I assume fails because classmethods are not FunctionTypes. Not sure
what the purpose of that assertion is at all (rather than just letting whatever
the user passed in through), but my suspicion even if it's there is that it
just means to check the thing is callable.

(Will see about sending a PR in a bit).

Create a 0.1.2 release

The currently released version 0.1.1 on PyPi contains a bug that breaks the standard library in a subtle way, and has been fixed in master.

Could you please create a new release?

Patching datetime crashes Python 2.7.13,2.7.14 on Debian/Ubuntu

The following program crashes Python with various memory corruption related errors on Debian 9 and Ubuntu 17.10. The program works fine on my Mac.

forbiddenfruit version 0.1.2 installed with pip install forbiddenfruit

Working system

  • Mac OS X 10.13.2; Python 2.7.10 (shipped by Apple)

Failing systems

  • Debian 9.3; Python 2.7.13 2.7.13-2+deb9u2
  • Ubuntu 16.04.3 LTS; Python 2.7.12 2.7.12-1ubuntu0~16.04.2
  • Ubuntu 17.10; Python 2.7.14 2.7.14-2ubuntu2

Reproducing the errors

On a new virtual machine, run the following:

sudo apt-get install gcc libpython2.7-dev virtualenv
virtualenv bugvenv
bugvenv/bin/pip install forbiddenfruit
bugvenv/bin/python bug.py

Example failing output

before datetime.now: 2017-12-17 15:03:16.985094
after datetime.now: 2017-12-17 15:03:16.985267
before datetime.now: 2017-12-17 15:03:16.985314
Traceback (most recent call last):
  File "bug.py", line 21, in <module>
    activate_deactivate()
  File "bug.py", line 14, in activate_deactivate
    f = datetime.datetime.now()
TypeError: &traceback' object is not callable
Fatal Python error: Inconsistent interned string state.
Aborted

Correct output

before datetime.now: 2017-12-17 10:04:33.392523
after datetime.now: 2017-12-17 15:04:33.392366
before datetime.now: 2017-12-17 15:04:33.392366
after datetime.now: 2017-12-17 15:04:33.392366
before datetime.now: 2017-12-17 15:04:33.392366
after datetime.now: 2017-12-17 15:04:33.392366

Script

import datetime
import forbiddenfruit


datetime_now = datetime.datetime.utcnow()
def fake_now(cls):
    return datetime_now


def activate_deactivate():
    now = datetime.datetime.now()
    print 'before datetime.now:', now
    forbiddenfruit.curse(datetime.datetime, 'now', classmethod(fake_now))
    f = datetime.datetime.now()
    print 'after datetime.now:', f


if __name__ == '__main__':
    for i in xrange(3):
        activate_deactivate()

how to use reverse with existing attributes

Beware: reverse() only deletes attributes. If you curse()'d to replace a pre-existing attribute, reverse() won't re-install the existing attribute.

Any workarounds for this?

Cursing int __iter__ silently fails.

>>> def int_iter(self):
	i = 0
	while i < self:
		yield i
		i += 1

		
>>> curse(int, '__iter__', int_iter)
>>> for i in 12:
	print('1231')

	
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    for i in 12:
TypeError: 'int' object is not iterable

Not sure if this is a bug or just unimplemented. Maybe raising a NotImplementedError exception would be helpful if it's the latter.

__init_subclass__ seems to not be implemented as a cursable magic method

from forbiddenfruit import curse

def test(*args, **kwargs):
    ...

curse(object, "__init_subclass__", test)

Raises the error

Traceback (most recent call last):
  File "/Users/Lexion/Code/Monkeypatching/main.py", line 9, in <module>
    curse(object, "__init_subclass__", test)
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/forbiddenfruit/__init__.py", line 426, in curse
    _curse_special(klass, attr, value)
  File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/forbiddenfruit/__init__.py", line 333, in _curse_special
    tp_as_name, impl_method = override_dict[attr]
                              ~~~~~~~~~~~~~^^^^^^
KeyError: '__init_subclass__'

because __init_subclass__ is not in the override dictionary

Crash on Windows if dunder methods cursed into `ctypes.c_int` raises an exception

In addition to #56, if the dunder method is cursed into ctypes' integer types, a raised exception causes Python to crash on Windows.

Proof of concept:

#!/usr/bin/env python3

from ctypes import c_int
from forbiddenfruit import curse

def c_int_add(self: c_int, other: int) -> c_int:
    raise TypeError('This exception should be caught')

curse(c_int, '__add__', c_int_add)

try:
    print(c_int(1) + 1) # TypeError (correct) then crash on Windows (wrong)
except TypeError:
    print('TypeError caught')

print('If this gets printed then nothing has crashed')

Test result on Windows (version 10.0.19043.1052, Python 3.8.6, both 64-bit):

(venv) C:\dev\test> python bug2.py 
Exception ignored on calling ctypes callback function: <function c_int_add at 0x000001E12AC1FDC0>
Traceback (most recent call last):
  File "E:\src\pytrickz\venv\lib\site-packages\forbiddenfruit\__init__.py", line 328, in wrapper
    return func(*args, **kwargs)
  File "bug2.py", line 7, in c_int_add
    raise TypeError('This exception should be caught')
TypeError: This exception should be caught

(venv) C:\dev\test> echo %=ExitCode%
C0000005

(venv) C:\dev\test> 

Modify/Change __add__ magic method for str type

I want to attach/concatenate a string with bytes of its to Python3 for solving TypeError exception.

I use this sample code:

from forbiddenfruit import curse

def __add__(self, a):
    """
        this is the test
    """
    if isinstance(a, bytes):
        self += a.decode('utf-8')
    else:
        self += a


curse(str, '__add__', __add__)

s = "sample string"a
print(s + "encode string".encode('utf-8'))

And returns the error below (exactly the normal state):

Traceback (most recent call last):
  File "test1.py", line 16, in <module>
    print(s + "encode string".encode('utf-8'))
TypeError: Can't convert 'bytes' object to str implicitly

How can I to fix this error?

[request] patch the `__bases__` of builtin types

I need to monkeypatch the base class of builtin types. No fancy magic methods, I just need to inherit normal method like this (contrived example):

class Iterator:
    def map(self, f):
        return map(f, self)

Naturally, this fails:

>>> list.__bases__ = (Iterator,)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'list'

But forbiddenfruit also fails:

>>> ff.curse(list, '__bases__', (Iterator,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.7/site-packages/forbiddenfruit/__init__.py", line 412, in curse
    _curse_special(klass, attr, value)
  File "/usr/local/lib/python3.7/site-packages/forbiddenfruit/__init__.py", line 327, in _curse_special
    assert isinstance(func, FunctionType)
AssertionError

Could you please add this to forbiddenfruit?

Exceptions raised in cursed-in dunder methods are not raised to calling code

Proof of concept:

#!/usr/bin/env python3

from forbiddenfruit import curse

class foo(object):
    def __init__(self): pass

def foo_add(self: foo, other: int) -> foo:
    raise TypeError('This exception should be caught')

curse(foo, '__add__', foo_add)

try:
    print(foo() + 1) # <class '__main__.foo'> (on Windows), __main__ (on Linux)
except TypeError:
    print('TypeError caught') # Not caught (wrong)

Test result:

(venv) C:\dev\test> python bug.py  
Exception ignored on calling ctypes callback function: <function foo_add at 0x0000026EC623FEE0>
Traceback (most recent call last):
  File "E:\src\pytrickz\venv\lib\site-packages\forbiddenfruit\__init__.py", line 328, in wrapper
    return func(*args, **kwargs)
  File "bug.py", line 13, in foo_add
    raise TypeError('This exception should be caught')
TypeError: This exception should be caught
<class '__main__.foo'>

(venv) C:\dev\test> 

The exception is printed to the screen (presumably by ctypes?) but not raised, except TypeError not triggered, print() receives a garbage result that depends on Python version and class structure (for example, if I remove foo.__init__ from code the result becomes None on Windows).

Provide wheel packages?

Hi,
I am trying to install forbiddenfruit on a Windows machine. (Forbiddenfruit is a dependency of CrossHair package which I am trying to originally install.) Unfortunately, forbiddenfruit needs to compile ffruit extension, see the error log below.

Any chance you could ship wheel packages along the source code so that Windows users do not have to compile the package (and don't have to install Visual Studio C++ 14)?

Is there a reason (apart from lack of time :)) that you didn't ship the wheel packages for Windows? Anything Posix specific? If not, would you like a pull request to include wheel packages on every release (e.g., as a Github Workflow)?

Here is the error log:

(venv) C:\Users\rist\workspace\pshanely\CrossHair>pip3 install .
Processing c:\users\rist\workspace\pshanely\crosshair
Collecting forbiddenfruit
  Using cached forbiddenfruit-0.1.3.tar.gz (31 kB)
Requirement already satisfied: typing-inspect in c:\users\rist\workspace\pshanely\crosshair\venv\lib\site-packages (from crosshair-tool==0.0.8) (0.6.0)
Collecting z3-solver==4.8.9.0
  Downloading z3_solver-4.8.9.0-py2.py3-none-win_amd64.whl (33.9 MB)
     |████████████████████████████████| 33.9 MB 3.2 MB/s
Requirement already satisfied: typing-extensions>=3.7.4 in c:\users\rist\workspace\pshanely\crosshair\venv\lib\site-packages (from typing-inspect->crosshair-tool==0.0.8) (3.7.4.3)
Requirement already satisfied: mypy-extensions>=0.3.0 in c:\users\rist\workspace\pshanely\crosshair\venv\lib\site-packages (from typing-inspect->crosshair-tool==0.0.8) (0.4.3)
Using legacy setup.py install for crosshair-tool, since package 'wheel' is not installed.
Using legacy setup.py install for forbiddenfruit, since package 'wheel' is not installed.
Installing collected packages: forbiddenfruit, z3-solver, crosshair-tool
    Running setup.py install for forbiddenfruit ... error
    ERROR: Command errored out with exit status 1:
     command: 'c:\users\rist\workspace\pshanely\crosshair\venv\scripts\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\rist\\AppData\\Local\\Temp\\pip-install-01p_f0iq\\forbiddenfruit\\setup.py'"'"'; __file__='"'"'C:\\Users\\rist\\AppData\\Local\\Temp\\pip-install-01p_f0iq\\forbiddenfruit\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\rist\AppData\Local\Temp\pip-record-gddpmjm9\install-record.txt' --single-version-externally-managed --compile --install-headers 'c:\users\rist\workspace\pshanely\crosshair\venv\include\site\python3.8\forbiddenfruit'
         cwd: C:\Users\rist\AppData\Local\Temp\pip-install-01p_f0iq\forbiddenfruit\
    Complete output (10 lines):
    running install
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-3.8
    creating build\lib.win-amd64-3.8\forbiddenfruit
    copying forbiddenfruit\__init__.py -> build\lib.win-amd64-3.8\forbiddenfruit
    running build_ext
    building 'ffruit' extension
    error: Microsoft Visual C++ 14.0 is required. Get it with "Build Tools for Visual Studio": https://visualstudio.microsoft.com/downloads/
    ----------------------------------------
ERROR: Command errored out with exit status 1: 'c:\users\rist\workspace\pshanely\crosshair\venv\scripts\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'C:\\Users\\rist\\AppData\\Local\\Temp\\pip-install-01p_f0iq\\forbiddenfruit\\setup.py'"'"'; __file__='"'"'C:\\Users\\rist\\AppData\\Local\\Temp\\pip-install-01p_f0iq\\forbiddenfruit\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'C:\Users\rist\AppData\Local\Temp\pip-record-gddpmjm9\install-record.txt' --single-version-externally-managed --compile --install-headers 'c:\users\rist\workspace\pshanely\crosshair\venv\include\site\python3.8\forbiddenfruit' Check the logs for full command output.
WARNING: You are using pip version 20.1.1; however, version 20.3.3 is available.
You should consider upgrading via the 'c:\users\rist\workspace\pshanely\crosshair\venv\scripts\python.exe -m pip install --upgrade pip' command.

Undiagnosed crash (Windows 11, Python 3.10)

Here's a quick session on Windows 11 with Python 3.10 (from the Windows app "store"):

PS C:\Users\user> .\python3\Scripts\python.exe
Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse
>>> def add(self, other): return 2
...
>>> curse(type(lambda: 0), '__add__', add)
>>> curse(object, '__add__', add)
>>>
>>> f = (_ + 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_' is not defined
>>> f = (_ + 0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name '_' is not defined
>>> from placeholder import _
PS C:\Users\user>

Notice that it spontaneously exited after I ran an import statement after cursing __add__ on <type 'function'> and object.

(I accidentally ran f = (_ + 0) twice before importing _, but I don't think that changed anything.)

Might be related to, or the same core issue as, the previous issue I opened.

Don't have time to chase it down right now, just wanted to document it in case it's useful or works to repro the bug for others.

Redefining methods on modules causes a segfault

Python 2.7.4 (default, Apr 19 2013, 18:28:01) 
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse
>>> curse(__builtins__, round, lambda x, y: y)
Segmentation fault (core dumped)

This is not very surprising but there should be a way to hack around it. At least I hope so.

P.S.
This project is great. I haven't found the time to do it myself but I wanted to do so forever. Thank you very much.

Is (and/or Why) __truediv__ are not patchable?

Why doesn't it support __truediv __ attribute?
In python3. truediv replaces div
In Python2, the __truediv __ () method is used when __future __. division is in effect, otherwise __div __ () is used. If only one of these two methods is defined, the object will not support division in the alternate context; TypeError will be raised instead.

Fix simple typo: infomation -> information

Issue Type

[x] Bug (Typo)

Steps to Replicate

  1. Examine forbiddenfruit/init.py.
  2. Search for infomation.

Expected Behaviour

  1. Should read information.

Semi-automated issue generated by
https://github.com/timgates42/meticulous/blob/master/docs/NOTE.md

To avoid wasting CI processing resources a branch with the fix has been
prepared but a pull request has not yet been created. A pull request fixing
the issue can be prepared from the link below, feel free to create it or
request @timgates42 create the PR. Alternatively if the fix is undesired please
close the issue with a small comment about the reasoning.

https://github.com/timgates42/forbiddenfruit/pull/new/bugfix_typo_information

Thanks.

object has no attribute '__dict__'

I'm using 0.1.4 and get

E       AttributeError: 'cimpl.Consumer' object has no attribute '__dict__'

when trying to mock https://docs.confluent.io/platform/current/clients/confluent-kafka-python/html/index.html#confluent_kafka.Consumer.poll but im faced with this error


>       forbiddenfruit.curse(consumer, "poll", MockKafkaMessage())

tests/test_consumer.py:179: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../site-packages/forbiddenfruit/__init__.py:428: in curse
    dikt = patchable_builtin(klass)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

klass = <cimpl.Consumer object at 0x1149baa60>

    def patchable_builtin(klass):
>       refs = gc.get_referents(klass.__dict__)
E       AttributeError: 'cimpl.Consumer' object has no attribute '__dict__'

../site-packages/forbiddenfruit/__init__.py:223: AttributeError

Where consumer is an instance of

confluent_kafka.Consumer

Patch for str __add__ not working

I want to modify __add__ magic function for str, but somehow it is not working. Refer to a previous issue #21, the code below is working fine:

from forbiddenfruit import curse

def __add__(self, a):
    """
        this is the test
    """
    if isinstance(a, bytes):
        self += a.decode('utf-8')
    else:
        self += a


curse(str, '__add__', __add__)

s = "sample string"a
print(s + "encode string".encode('utf-8'))

However, I discovered that if you try to do "a" + "b" instead of "a" + b"b", the modified function is not called (I tried to print something out inside the new __add__ function). It turns out the modified function only works when the type of the second variable is not str.

Any idea why this happens? By the way, I tried on Python 3.8.10

Use a non-copyleft license

I read here forbiddenfruit was being relicensed to something like MIT, BSD, or at least LGPL. Is this still happening? I was planning to use it in a small library of mine, but I do not want to get GPL in the mix.

Add `pyproject.toml` or implement wheel-based distribution

After installing from pip, I got this deprecation warning:

PowerShell session

This isn't related to the functionality of forbiddenfruit, but to how it's distributed via pip. In text, it looks like this:

PS C:\Users\Keyacom> py -m pip install forbiddenfruit
Collecting forbiddenfruit
  Downloading forbiddenfruit-0.1.4.tar.gz (43 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 43.8/43.8 kB 1.1 MB/s eta 0:00:00
  Preparing metadata (setup.py) ... done
Installing collected packages: forbiddenfruit
  DEPRECATION: forbiddenfruit is being installed using the legacy 'setup.py install' method, because it 
does not have a 'pyproject.toml' and the 'wheel' package is not installed. pip 23.1 will enforce this 
behaviour change. A possible replacement is to enable the '--use-pep517' option. Discussion can be found 
at https://github.com/pypa/pip/issues/8559
  Running setup.py install for forbiddenfruit ... done
Successfully installed forbiddenfruit-0.1.4

This basically says that once pip 23.1 comes out, this package must have pyproject.toml (PEP 518), or be distributed via wheels, as per PEP 517.

MacOS errors monkeypatching `object`

I don't have a MacOS system to check anymore, but when I was last using a Mac at work, I tested some monkeypatching of object, and I noticed an interesting behavior:

  1. cursing __add__ on object seemed to do nothing,

  2. after cursing __add__ on object, I would pretty consistently see Python segfault soon after (I can't remember perfect reproduction steps, but I think if you just fiddle it'll eventually do it).

Both Python and MacOS were on the latest versions at the time, and this was around August 15th 2021.

Redefine magic methods

I was able to redefine str.lower very nicely, but int.__add__ failed to be redefined on either 2.7 or 3.3. For example:

Python 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 01:25:11)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse
>>> def notplus(a,b):
...     return a * b
...
>>> curse(int, '__add__', notplus)
>>> 5 + 9
14

test fails with py3.8 and pytest

As nose won't be supported with python 3.9 and up, I was trying to port the tests to pytest.
All tests but 1 passed. In particular, I faced an issue with test_dir_without_args_returns_names_in_local_scope:

[    3s] =================================== FAILURES ===================================
[    3s] ______________ test_dir_without_args_returns_names_in_local_scope ______________
[    3s] 
[    3s]     def test_dir_without_args_returns_names_in_local_scope():
[    3s]         """dir() without arguments should return the names from the local scope
[    3s]         of the calling frame, taking into account any indirection added
[    3s]         by __filtered_dir__
[    3s]         """
[    3s]     
[    3s]         # Given that I have a local scope with some names bound to values
[    3s]         z = 1
[    3s]         some_name = 42
[    3s]     
[    3s]         # Then I see that `dir()` correctly returns a sorted list of those names
[    3s]         assert 'some_name' in dir()
[    3s] >       assert dir() == sorted(locals().keys())
[    3s] E       AssertionError: assert ['@py_assert0...me_name', 'z'] == ['@py_assert0...me_name', 'z']
[    3s] E         At index 1 diff: '@py_assert2' != '@py_assert1'
[    3s] E         Right contains one more item: 'z'
[    3s] E         Full diff:
[    3s] E         - ['@py_assert0', '@py_assert2', '@py_assert4', 'some_name', 'z']
[    3s] E         + ['@py_assert0', '@py_assert1', '@py_assert2', '@py_assert4', 'some_name', 'z']
[    3s] E         ?                 +++++++++++++++
[    3s] 
[    3s] tests/unit/test_forbidden_fruit.py:172: AssertionError
[    3s] ========================= 1 failed, 16 passed in 0.06s =========================

I'm using python 3.8 and pytest on openSUSE.

Unable to perfectly re-curse to __add__

After cursing np.ndarray.__add__ , even if I re-curse after storing the original in a variable, the __radd__ method breaks.

Way to reproduce

import numpy as np
import forbiddenfruit

orig = np.ndarray.__add__

forbiddenfruit.curse(np.ndarray, '__add__', lambda x, y: 42)
arr = np.arange(5)
print(arr + 2)  # prints 42
print(1 + arr)  # prints 42

forbiddenfruit.curse(np.ndarray, '__add__', orig)
arr2 = np.arange(5)
print(arr + 3)  # prints [3, 4, 5, 6, 7]
print(4 + arr)  # raises: TypeError: descriptor '__add__' requires a 'numpy.ndarray' object but received a 'int'

Questions for cursing __radd__ (and others, such as __rsub__)

  1. Is it possible? Or perhaps dependent on the underlying class?
  2. Do you know where can I find documentation on the nb_* methods so I can find these things out for myself? I attempted to follow common patterns to include __radd__ to the as_number list (wouldve expected nb_radd to be its equivalent) and such to no success

Unable to curse __call__

Attempting to curse the __call__ method results in a KeyError. Minimum working example below.

def wrap(func):
    def wrapped(*args, **kwargs):
      print("intercepted")
      return func(*args, **kwargs)
    return wrapped

curse(int, "__call__", wrap(int.__call__))

license uncertainity

Hi!

Sorry for this boring report as license issues always are. You claim to license this module under GPLv3 or MIT. However, COPYING is LGPL. Could you please consider to package GPLv3 as COPYING instead?

Cursing dunder methods fails with: KeyError: '<method_to_curse>'

curse(str, '__eq__', str.__ne__)

fails with:
...

  File ".../python3.6/site-packages/forbiddenfruit/__init__.py", line 425, in curse
    _curse_special(klass, attr, value)
  File ".../python3.6/site-packages/forbiddenfruit/__init__.py", line 332, in _curse_special
    tp_as_name, impl_method = override_dict[attr]
KeyError: '__eq__'

Also other dunder methods fails. e.g.:

curse(int, '__new__', <some other method>)
curse(float, '__dict__', <some other method>)

Redefined magic methods are not inherited

Cursing object type is not working as expected.
It is ok for ordinary methods:

>>> from forbiddenfruit import curse
>>> curse(object, 'f', lambda x: print("boo"))
>>> [].f()
boo

But not for magic methods:

>>> from forbiddenfruit import curse
>>> curse(object, '__matmul__', (lambda x, y: y(x)))
>>> [1,2,3] @ len
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for @: 'list' and 'builtin_function_or_method'

Little testing shows that subclasses do not inherit redefined magic methods.

@alendit , please, could you pay some attention to this?

Can't add a method to None

$ python
Python 2.7.3 (default, Aug  1 2012, 05:14:39) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from forbiddenfruit import curse
>>> None.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'method'
>>> curse(type(None), 'method', lambda *a, **kw: None)
>>> None.method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method <lambda>() must be called with NoneType instance as first argument (got nothing instead)

Cursing __init_subclass__

Since builtins don't have a __metaclass__ attribute, __init_subclass__ can be used to at least manipulate this to some degree.

The issue:

In [1]: from forbiddenfruit import curse

In [2]: class x:
   ...:     def __init_subclass__(cls, **kwargs):
   ...:         cls.__init_subclass_o__(**kwargs)  # We can't call __init_subclass__ because recursion
   ...:         print(cls, kwargs)
   ...:         

In [3]: curse(object, "__init_subclass_o__", object.__init_subclass__)

In [4]: curse(object, "__init_subclass__", x.__init_subclass__)

In [5]: class y:
   ...:     pass
   ...: 
<class '__main__.x'> {}

As you can tell, no matter what you do, the cls argument will ALWAYS be <class '__main__.x'>
Creating a regular function (not a method on class x) will result in

In [6]: def __init_subclass__(cls, **kwargs):
   ...:     cls.__init_subclass_o__(**kwargs)  # We can't call __init_subclass__ because recursion
   ...:     print(cls, kwargs)
   ...:         

In [7]: curse(object, "__init_subclass__", __init_subclass__)

In [8]: class y:
   ...:     pass
   ...: 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-8-9a1635f8452b> in <module>()
----> 1 class y:
      2     pass

TypeError: __init_subclass__() missing 1 required positional argument: 'cls'

What should be done here?
Also, will magic methods be patchable yet anytime soon?

Cursing int with __iter__ fails with an error

Python 3.8.3

Code -

from forbiddenfruit import curse

def __iter__(self):
	return iter(range(self))
	
curse(int, "__iter__", __iter__)

Output -

forbiddenfruit/__init__.py", line 329, in _curse_special
    tp_as_name, impl_method = override_dict[attr]
KeyError: '__iter__'

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.