Code Monkey home page Code Monkey logo

decorator's Introduction

Various articles I have written in the past about programming languages, including Python and Scheme.

decorator's People

Contributors

altendky avatar amli avatar arthurzam avatar borishim avatar cclauss avatar cfra avatar ddaanet avatar hroncok avatar hugovk avatar infinisil avatar kasium avatar larsoner avatar micheles avatar mtelka avatar pelme avatar pmav99 avatar pohlt avatar pombredanne avatar smarie avatar sobolevn avatar svetlyak40wt avatar zbenjamin avatar zearin 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

decorator's Issues

Python 3.6 - Trace example - kw is empty

It looks like the kw array is not being preserved or passed at all in 3.6. I have not tested in previous versions, but based on the documentation it is a different behavior.

Python 3.6.0 |Anaconda 4.3.1 (64-bit)| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from decorator import decorator
>>> @decorator
... def trace(f, *args, **kw):
...     kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw))
...     print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr))
...
>>> @trace
... def samplefn(x, a="a1"):
...     pass
...
>>> samplefn(1)
calling samplefn with args (1, 'a1'), {}
>>> samplefn(1, "inline")
calling samplefn with args (1, 'inline'), {}
>>> samplefn(1, a="askeyword") # notice kwstr is coming back blank
calling samplefn with args (1, 'askeyword'), {}

Newline insertion in FunctionMaker.make

Hi, I was wondering what the reason was behind removing the append of the newline in this commit. As far as I can tell, without a trailing newline python 2.7.x will raise a SyntaxError: unexpected EOF while parsing for compile("def foo(): return bar", ..) (and since its a SyntaxError, it can be pretty difficult to trace with the pdb family). Thanks

args or kwargs

First off, glad you are updating the project again!

I was actually trying to fix this issue at your old repo and saw you have closed the issue as "not an issue".

To reiterate the problem, here is a test that shows the issue:

@decorator
def test_decorator(f, *args, **kwargs):
    assert args == (1, 2)  # <---- failed here, args is (1,2,3,4) instead
    assert kwargs == dict(c=3, d=4, e=5)  # <---- kwargs is dict(e=5)
    return f(*args, **kwargs)

@test_decorator
def foo(a, b, c=-1, d=-2, **kwargs):
    assert kwargs['e'] == 5
    return a, b, c, d

assert foo(1, 2, c=3, d=4, e=5) == (1, 2, 3, 4)

In this case, args giving (1,2,3,4) is the surprising behaviour. In fact, if we use vanilla python, we would have args = (1, 2) and kwargs = dict(c=3, d=4, e=5):

def decor(f):       
    def wrapper(*args, **kwargs):
        print args, kwargs
        return f(*args,**kwargs)
    return wrapper

@decor              
def foo(a, b, c=-1,d=-2, **kwargs):
    pass

foo(1,2,c=3,d=4,e=5)

> (1, 2) {'c': 3, 'e': 5, 'd': 4}

Of foo's parameters, I believe a, b, c, and d are called positional-or-keyword parameters. IMHO we should do the least surprising behaviour here, and put parameters that has default to be placed in kwargs.

Question concerning decorators with arguments

First thanks for the great work.

I've been reading your whole documentation and I am certainly missing the point, I am struggling with a syntax issue, cannot find the proper one.

Suppose I want to do the following dumb thing:

import decorator

def printer(text):
    def _printer(func, *args, **kwargs):
        print(text)
        func(*args, **kwargs)
    return decorator.decorator(_printer)

@printer("Hello")
def world():
    print("World")

When executing, I get the following result:

>>>world()
Hello
World

And that's totally fine !

Suppose now that I want my text to be optional. To do so I give a default value of None to the text parameter, check whether there is something to print or not in my _printer function and omit the argument when decorating. Resulting in the following code:

import decorator

def printer(text=None):
    def _printer(func, *args, **kwargs):
        if text:
            print(text)
        func(*args, **kwargs)
    return decorator.decorator(_printer)

@printer()
def world():
    print("World")

Execution is now:

>>>world()
World

And once again that's fine but a detail bothers me, I am forced to write @printer() instead of @printer which I would prefer. But I cannot do so because if I do this leads to the following error:

>>>world()
Traceback (most recent call last):
  File "toto.py", line 14, in <module>
    world()
TypeError: _printer() missing 1 required positional argument: 'func'

I have based my code on your blocking example in the documentation but that last detail beats me.
Is there a way to modify this code to achieve what I want or must I stay with the @printer() syntax ?
Thanks very much.

Neimad

How do I create a "signature-changing" decorator with decorator.py?

The docs (https://github.com/micheles/decorator/blob/master/docs/documentation.md#decoratordecorator) say:

The decorator function can be used as a signature-changing decorator, just like classmethod and staticmethod.

But then goes on to say:

But classmethod and staticmethod return generic objects which are not callable. Instead, decorator returns signature-preserving decorators (i.e. functions with a single argument).

And the following trivial example doesn't appear to change the signature at all?

What I want to do is create a decorator that will add a few optional keyword arguments to the decorated function. At first I thought I should be able to do something like:

@decorator.decorator
def mydec(func, new_kw=None, another_new_kw=None, *args, **kwargs):
    if new_kw:
        # do something
    return func(*args, **kwargs)

But this returns a decorator factory (also useful, but not for me in this case).

Then, I thought I could do it with decorate, like this:

def _mydec(func, new_kw=None, another_new_kw=None, *args, **kwargs):
    if new_kw:
        # do something else when the decorated function is executed
    return func(*args, **kwargs)

def mydec(func):
    newfunc = decorator.decorate(func, _mydec)
    # do something else with func when the decorator is created
    return newfunc

But this doesn't work. The decorated function still only accepts the original keyword arguments.

I guess the two methods are functionally equivalent, but decorator.decorate allows to do something with the original function at the time the decorator is created (vs at the time runtime, with decorator.decorator.

I also found #55 and #58 which seem to imply that signature-changing decorators are not supported at all and go against the entire philosophy of decorator.py.

But this seems to be a direct contradiction of the quoted docs:

The decorator function can be used as a signature-changing decorator, just like classmethod and staticmethod.

Can I use decorator.py to create signature-changing decorators, with new required or optional args or keyword args?

Tarball on PyPI contains src/tests/x.py which is not compatible with Py3

The tarball which I use to package decorator in Arch Linux has the file x.py in it (which can not be found in the Git repo)

The file itself is not compatible with Python 3, which makes python3 setup.py test fail.
I've created a patch for it in our svn repo but I rather see a fix in the tarball (so I don't have to patch it all the time)

Thanks!

Dynamic decorator exception

When using the decorator dynamically the function signature if ok, but an exception occurs:

THIS WORKS:

def args_decorator(*args):
    def funcval_wt_args(func):
        def wrapper(value):
            return func(value), args
        return wrapper
    return funcval_wt_args

@args_decorator(1, 2, 3)
def func(x):
    return x

print(func(10)) # -> (10, (1, 2, 3))

THIS DOES NOT WORK:

from decorator import decorator

def args_decorator(*args):
    @decorator
    def funcval_wt_args(func):
        def wrapper(value):
            return func(value), args
        return wrapper
    return funcval_wt_args

@args_decorator(1, 2, 3)
def func(x):
    return x

print(func(10)) # -> TypeError: funcval_wt_args() takes 1 positional argument but 2 were given

Decorator 4.0.8

Hello,

It's there any way to place a sdist (legacy egg) for decorator 4.0.8 in pypi. We have legacy buildout code that requires it.

Thanks a lot

Carlos

Looks like it works on staticmethods in python3.7

In Python 3.5, many such limitations have been removed, but I still think that it is cleaner and safer to decorate only functions. If you want to decorate things like classmethods/staticmethods and general callables - which I will never support in the decorator module - I suggest you to look at the wrapt project by Graeme Dumpleton.

from decorator import decorator
import asyncio
import time


@decorator
async def log_start_stop(func, *args, **kwargs):
    t0 = int(time.time())
    print(t0, f'core={func}, args={args},kwargs={kwargs}')
    result = await func(*args, **kwargs)
    t1 = int(time.time())
    print(t1, f'result={result}')


class A:
    @staticmethod
    @log_start_stop
    async def make_task(n):
        for i in range(n):
            await asyncio.sleep(1)
        return 100


a = A()
print(a)
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(A.make_task(5))
finally:
    loop.run_until_complete(loop.shutdown_asyncgens())
    loop.close()

The documentation of this library is not very accessible

I'm opening this as an issue because it is hindrance to it's usability. Even thought there are plenty of examples and explanation, the documentation is ineffective, in my subjective opinion. As a user I want to see/access the library API as fast as possible to see if it does what I need, without having to scan through Introduction, What’s New in version 4, Usefulness of decorators, Definitions, Statement of the problem and The solution.
Suggestions:

  • Put few simple examples on the README page showing the salient properties of the library.
  • Put the gist of the functionality in a separate section at the top of the documentation.

License?

Looks like BSD 3-clause. Is that correct?

4.3.0 has back-incompatible changes

The version 4.3.0 removes getargspec from the module, which broke some scripts that worked with 4.2.1. We've resolved our issues by relying on a specific snapshot instead of 4.x, but I imagine there are others who might be affected as well.

Was this change intentional? Semantic versioning dictates that minor versions are back-compatible, so it might be best to add it back until a 5.x version.

Python 2.7: tests fail randomly

Running python2 setup.py test multiple times yields multiple results. I could observe 3 different error outputs and one success output. Each variant seems to be equally likely.

Error 1

running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation1
Failed example:
    operation1()
Expected:
    operation1 is slow
Got nothing
**********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation2
Failed example:
    operation2()
Expected:
    operation2 is slow
Got nothing
**********************************************************************
2 items had failures:
   1 of   1 in tests.documentation.operation1
   1 of   1 in tests.documentation.operation2
***Test Failed*** 2 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok

======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
    self.assertEqual(err, 0)
AssertionError: 2 != 0

----------------------------------------------------------------------
Ran 18 tests in 5.631s

FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>

Error 2

running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation1
Failed example:
    operation1()
Expected:
    operation1 is slow
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in tests.documentation.operation1
***Test Failed*** 1 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok

======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
    self.assertEqual(err, 0)
AssertionError: 1 != 0

----------------------------------------------------------------------
Ran 18 tests in 5.634s

FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>

Error 3

running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation2
Failed example:
    operation2()
Expected:
    operation2 is slow
Got nothing
**********************************************************************
1 items had failures:
   1 of   1 in tests.documentation.operation2
***Test Failed*** 1 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok

======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
    self.assertEqual(err, 0)
AssertionError: 1 != 0

----------------------------------------------------------------------
Ran 18 tests in 5.632s

FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>

Success

running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... ok
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok

----------------------------------------------------------------------
Ran 18 tests in 5.635s

OK

How to reproduce

Dockerfile

FROM debian:buster

RUN apt-get update \
  && apt-get install -y python2 python-setuptools wget \
  && cd /tmp \
  && wget https://files.pythonhosted.org/packages/dc/c3/9d378af09f5737cfd524b844cd2fbb0d2263a35c11d712043daab290144d/decorator-4.4.1.tar.gz \
  && tar xf decorator-4.4.1.tar.gz \
  && rm decorator-4.4.1.tar.gz \
  && apt-get clean \
  && rm -R /var/lib/apt/lists

WORKDIR /tmp/decorator-4.4.1
ENTRYPOINT [ "/usr/bin/python2", "setup.py", "test" ]
for i in {1..10}; do docker run --rm -it <image>; done

No files uploaded to PyPI for 4.0.7

On PyPI it seems the 4.0.7 release has nothing uploaded, so installing it fails:

$ pip install decorator==4.0.7
Collecting decorator==4.0.7
  Could not find a version that satisfies the requirement decorator==4.0.7 (from versions: 3.3.1, 3.3.2, 3.3.3, 3.4.0, 3.4.2, 4.0.0, 4.0.1, 4.0.2, 4.0.3, 4.0.4, 4.0.6)
No matching distribution found for decorator==4.0.7

Deprecated Warning with Python 3.5

Hi,
I'm learning Python. I installed my first python program (Chatterbot 0.4.6) that uses NLTK. I installed last version of nltk (3.2.1), but it doesn't work for inspect.getargspec in decorators.py

How to solve this problem? I tried to install decorator.py and put it in nltk fold but nltk has a module named as decorators (not decorator).

Can you help me, please?

memoize decorator example has cache leak when used on an instance

The memoize decorator as shown on https://pythonhosted.org/decorator/documentation.html#the-solution will cause a memory leak when decorating an instancemethod, as the instance (self) is considered part of the cache key and cached results are not cleaned up as the instance is deleted. In fact, the instance is never deleted as the cache key still holds a reference to it.

While this does not necessarily need fixing (as it's just an example), there should be a big warning on the code. Or rather, maybe you can come up with a better example that can be implemented without such issues. The decorator as-is is currently copied to several projects or used as a base for extensions. See https://www.google.com/search?q="frozenset+is+used+to+ensure+hashability"

No git tags

There are no git tags. It'd be great if there were git tags.

4.0.1 No such file or directory: 'docs/README.rst'

Upgrading to 4.0.1 (latest as of this moment) gives the following error:

$ pip install decorator==4.0.1
Downloading/unpacking decorator==4.0.1
  Running setup.py egg_info for package decorator
    Traceback (most recent call last):
      File "<string>", line 16, in <module>
      File "XXX/build/decorator/setup.py", line 12, in <module>
        long_description=open('docs/README.rst').read(),
    IOError: [Errno 2] No such file or directory: 'docs/README.rst'
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 16, in <module>

  File "XXX/build/decorator/setup.py", line 12, in <module>

    long_description=open('docs/README.rst').read(),

IOError: [Errno 2] No such file or directory: 'docs/README.rst'

----------------------------------------
Command python setup.py egg_info failed with error code 1 in XXX/build/decorator
Storing complete log in /home/ubuntu/.pip/pip.log

I see the fix in 18dd1e5, but it doesn't seem to have done the job.

4.0.0 works as expected.

Output coroutine functions

It would be great if @decorator could be used to create decorators which produce coroutine functions. Is there already such functionality and I just didn't find it? If not, would this be feasible? If so, are there any plans for this? Would you accept PRs?

Example:

from inspect import iscoroutinefunction
from decorator import decorator

@decorator
async def ensure_coro(function, *args, **kwargs): 
    ret = function(*args, **kwargs)
    if iscoroutinefunction(function):
        ret = await ret
    return ret

@ensure_coro
def foo():
    return 42

assert iscoroutinefunction(foo)

new release breaks IPython

Traceback (most recent call last):
File "/usr/local/bin/ipython", line 7, in
from IPython import start_ipython
File "/usr/local/lib/python2.7/site-packages/IPython/init.py", line 48, in
from .core.application import Application
File "/usr/local/lib/python2.7/site-packages/IPython/core/application.py", line 23, in
from traitlets.config.application import Application, catch_config_error
File "/usr/local/lib/python2.7/site-packages/traitlets/config/init.py", line 6, in
from .application import *
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 120, in
class Application(SingletonConfigurable):
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 291, in Application
def initialize(self, argv=None):
TypeError: catch_config_error() takes exactly 2 arguments (1 given)

catch_config_error is a decorator decorated with this package

No support for adding parameters in decorators.

There is no support for adding arguments to a decorated function inside a decorator.
A simple example will show the issue.

import decorator

@decorator.decorator
def foo(f, *args, **kwargs):
    return f(1, *args, **kwargs)

def bar(a):
    print "bar got {}".format(a)

bar()

The following code results in the following exception:

python crap.py                                                                                                                                                                                                          
Traceback (most recent call last):
  File "crap.py", line 10, in <module>
    bar()
TypeError: bar() takes exactly 1 argument (0 given)

I understand that this error occurs since the decorator function copies the signature of the decorated function. Is there a way to make this work? Or is this not considered a valid use case for the decorator library?

kwargs is empty with decorator

Python : 2.7, decorator (4.0.4)

Test with the following code:

import decorator

@decorator.decorator
def test_decorator(f, *args, **kwargs):
    print('check args:')
    print(args)
    print('check kwargs')
    print(kwargs)
    return f(*args, **kwargs)

class TestObject(object):

    @test_decorator
    def test(self, x, y, z):
        return x + y + z


test = TestObject()
test.test(x=2,y=2,z=4)

The output is:

check args:
(<__main__.TestObject object at 0x7f33a0b4ec50>, 2, 2, 4)
check kwargs
{}

A confused question.

For the code below, I can use handle() with default name="name" or handle(name="h name"). It extends a keyword parameter "name" of my handle function.

def get(url: str):
    """Get it."""

    def _decorator(func):
        """Decorate func."""
        res = f"Got url: {url}"

        @wraps(func)
        def wrapper(*, name: str = "name"):
            """Wrap func."""
            return func(f"Name: {name}\nThe url: {url}\n{res}")

        return wrapper

    return _decorator

@get("(xxx.com)")
def handle(res):
    print(res)
>>> handle()
Name: name
The url: (xxx.com)
Get url: (xxx.com)

>>>handle(name = "h name")
Name: h name
The url: (xxx.com)
Get url: (xxx.com)

When I used the decorator, is there a way for me to extend the keyword parameters of handle function as above?

docs: first paragraph in first section ("introduction") section describe what this is/does

I read somewhere today that "In regards to PyPI, the "decorator" package is ~300 lines in total, and is one of the top most downloaded packages from pypi.". So I was curious and set off to read up on what the library is and what it does.

Your README.rst does not explain what this library does, it just explains how to install it, or run the test suite, etc. The "For the impatient" is the closest to explaining what this does, by providing an example of using it for something. So it might be possible to extract what it does from that context, although I couldn't on first attempt.

Following the link to the full docs, https://decorator.readthedocs.io/en/latest/, Nothing on the front page explains what this is or does, but fair enough, it's just a ToC. However, the "introduction" section does not explain what this or does, either. The next section, "Whats new", is not relevant if you don't know what it is or does (yet), and the section after that, "Usefulness of decorators", seems to explain why the libraries does what it does, without explaining what it does.

I'm not trying to be too pedantic here. I've written a lot of Python, although I do not consider myself to be a Python programmer, in particular; and I suspect I've used decorators and probably even this library before (by virtue of hacking on something that used it). But despite that I genuinely do not know what this library does despite having read all of the above.

FunctionMaker does not understand sig. defaults

Hi,

great module !!

Little Issue: FunctionMaker does drop defaults in the sign. string:

(Pdb) sig = ('foo', 'bar')
(Pdb) fsig = 'f1(%s=None)' % '=None, '.join(sig)
(Pdb) fsig
'f1(foo=None, bar=None)'
(Pdb) f = decorator.FunctionMaker.create(fsig, 'pass', {}, addsource=True)
(Pdb) f(1)
*** TypeError: f1() takes exactly 2 arguments (1 given)
(Pdb) inspect.getargspec(f)
ArgSpec(args=['foo', 'bar'], varargs=None, keywords=None, defaults=())

Possibility to add additional arguments to the decorated function

Hi there,

I'm a big fan of your library and I used it in many of mine :).

I recently came in pytest-steps with the need to

  • add arguments to a decorated function
  • but tolerate that these arguments can already be there

The use-case is that the decorated function is a pytest test function, that can or can not already have the "request" argument in its signature, but I need the wrapped function to have it always. I came up with the following hack, maybe this is something you would be interested in ?

class MyFunctionMaker(FunctionMaker):
    """
    Overrides FunctionMaker so that additional arguments can be inserted in the resulting signature.
    """

    @classmethod
    def create(cls, obj, body, evaldict, defaults=None,
               doc=None, module=None, addsource=True, add_args=None, **attrs):
        """
        Create a function from the strings name, signature and body.
        evaldict is the evaluation dictionary. If addsource is true an
        attribute __source__ is added to the result. The attributes attrs
        are added, if any.
        """
        if isinstance(obj, str):  # "name(signature)"
            name, rest = obj.strip().split('(', 1)
            signature = rest[:-1]  # strip a right parens
            func = None
        else:  # a function
            name = None
            signature = None
            func = obj
        self = cls(func, name, signature, defaults, doc, module)
        ibody = '\n'.join('    ' + line for line in body.splitlines())
        caller = evaldict.get('_call_')  # when called from `decorate`
        if caller and iscoroutinefunction(caller):
            body = ('async def %(name)s(%(signature)s):\n' + ibody).replace(
                'return', 'return await')
        else:
            body = 'def %(name)s(%(signature)s):\n' + ibody

        # --- HACK part 1 -----
        if add_args is not None:
            for arg in add_args:
                if arg not in self.args:
                    self.args = [arg] + self.args
                else:
                    # the argument already exists in the wrapped function, no problem.
                    pass

            # update signatures (this is a copy of the init code)
            allargs = list(self.args)
            allshortargs = list(self.args)
            if self.varargs:
                allargs.append('*' + self.varargs)
                allshortargs.append('*' + self.varargs)
            elif self.kwonlyargs:
                allargs.append('*')  # single star syntax
            for a in self.kwonlyargs:
                allargs.append('%s=None' % a)
                allshortargs.append('%s=%s' % (a, a))
            if self.varkw:
                allargs.append('**' + self.varkw)
                allshortargs.append('**' + self.varkw)
            self.signature = ', '.join(allargs)
            self.shortsignature = ', '.join(allshortargs)
        # ---------------------------

        func = self.make(body, evaldict, addsource, **attrs)

        # ----- HACK part 2
        if add_args is not None:
            # delete this annotation otherwise the inspect.signature method relies on the wrapped object's signature
            del func.__wrapped__

        return func


def my_decorate(func, caller, extras=(), additional_args=None):
    """
    A clone of 'decorate' with the possibility to add additional args to the function signature.
    Additional arguments will be positional and will sit at the beginning of 
    """
    evaldict = dict(_call_=caller, _func_=func)
    es = ''
    for i, extra in enumerate(extras):
        ex = '_e%d_' % i
        evaldict[ex] = extra
        es += ex + ', '
    fun = MyFunctionMaker.create(
        func, "return _call_(_func_, %s%%(shortsignature)s)" % es,
        evaldict, add_args=reversed(additional_args or ()), __wrapped__=func)
    if hasattr(func, '__qualname__'):
        fun.__qualname__ = func.__qualname__
    return fun

Here is how I use it in a complex case. One additional argument ________step_name_ is guaranteed to not be in the function signature, while the 'request' argument may or may not be there

# -- first create the logic
def _execute_step_with_monitor(step_name, request, *args, **kwargs):
    # ...

# -- then create the appropriate function signature according to wrapped function signature
if 'request' not in f_sig.parameters:
    # easy: we can add it explicitly in our signature
    def step_function_wrapper(f, ________step_name_, request, *args, **kwargs):
        """Executes a step with the execution monitor for this pytest node"""
        _execute_step_with_monitor(________step_name_, request, *args, **kwargs)
else:
    # harder: we have to retrieve the value for request. Thanks, inspect package !
    def step_function_wrapper(f, ________step_name_, *args, **kwargs):
        """Executes a step with the execution monitor for this pytest node"""
        request = f_sig.bind(*args, **kwargs).arguments['request']
        _execute_step_with_monitor(________step_name_, request, *args, **kwargs)

# decorate it so that its signature is the same than test_func, with just an additional argument for test step
# and if needed an additional argument for request
wrapped_test_function = my_decorate(test_func, step_function_wrapper,
                                    additional_args=['________step_name_', 'request'])

If you think that this feature is interesting, I can make a Pull Request.

Brown bag Release 4.0.1 (readme missing from manifest)

In commit 24d5f15 you moved README.rst to a different place. However, because MANIFEST.in was not updated to reflect the new path, the README.txt file was not included in the source distribution for the 4.0.1 release, and now setup.py fails when trying to generate the description.

Getting distribution for 'decorator'.
error: docs/README.rst: No such file or directory
An error occurred when trying to install decorator 4.0.1. Look above this message for any errors that were output by easy_install.
While:
   Updating test.
   Getting distribution for 'decorator'.
 Error: Couldn't install: decorator 4.0.1
 Attempt 1 of "bin/buildout -n -t 5" failed.

@micheles

ContextManager + access the decorated function in __enter__

I have a context manager that creates some resources and cleans them up on exit. I would like, if possible, to use it as a decorator, too. But when I do, I also need to have access to the name of the decorated function. I managed to do this by subclassing decorator.ContextManager and overriding __call__:

    def __call__(self, func):
        """Context manager decorator"""
        self.__func__ = func    # this is the line I added
        return decorator.FunctionMaker.create(
            func, "with _self_: return _func_(%(shortsignature)s)",
            dict(_self_=self, _func_=func), __wrapped__=func)
  1. Is it possible to achieve the same thing with "vanilla" decorator.ContextManager?
  2. If not, would you accept a PR for this?

A decorated generator is not a generator... and other similar issues

In a library for pytest I need to wrap a fixture written by the user. Pytest supports both generator and normal functions as fixture. I would like to preserve this nature in the decorated function.

Surprisingly, decorator does not seem to support this case. Below the corresponding test. I will give it a try because I need the feature, and if it seems elegant/interesting enough, I might propose a PR.

from inspect import isgeneratorfunction
from decorator import decorate

# A my_normal function
def my_normal():
    for i in range(10):
        print(i)
    return

assert not isgeneratorfunction(my_normal)


# A generator
def my_generator():
    for i in range(10):
        print(i)
        yield i

assert isgeneratorfunction(my_generator)


# A my_normal wrapper around a normal function
def normal_around_normal(f, *args, **kwargs):
    return my_normal()

assert not isgeneratorfunction(decorate(my_generator, normal_around_normal))


# A my_normal wrapper around a generator function
def normal_around_gen(f, *args, **kwargs):
    for res in my_generator():
        # do not yield !
        pass
    return 15

assert not isgeneratorfunction(decorate(my_generator, normal_around_gen))


# A generator wrapper around a generator function
def gen_around_gen(f, *args, **kwargs):
    for res in my_generator():
        yield res

assert isgeneratorfunction(decorate(my_generator, gen_around_gen))


# A generator wrapper around a my_normal function
def gen_around_normal(f, *args, **kwargs):
    yield my_normal()

assert isgeneratorfunction(decorate(my_normal, gen_around_normal))

Description could be “decorator for Humans”~

Python has many "XX for Humans" third-party libs, for example-- Python HTTP Requests for Humans, SQL for Humans;

Your decorator is an awesome and easy to use lib compared to Python official decorator, why not modify Description to “decorator for Humans” ?

@decorator.decorator on top of @decorator.decorator

import decorator


@decorator.decorator
def first(f, *args, **kwargs):
    print('Print this first')
    return f(*args, **kwargs)

@first
@decorator.decorator
def second(f, *args, **kwargs):
    print('Print this second')
    return f(*args, **kwargs)

@second
def main():
    return

Hi, I was expecting the code to print out

Print this first
Print this second

but it's only printing out

Print this first

Am I doing anything wrong?

nicer multiple decorator support

I had a suggestion and wasn't sure where to put it, the basic idea is could the decorate function support multiple decorators in a single call so that:

def my_decorator(func):
    return decorate(decorate(func, _caller), _precondition_caller)

could be rewritten as:

def my_decorator(func):
    return decorate(func, _precondition_caller, _caller)

Maybe I'm just approaching decorator composition wrong, if so I'd love to see an alternative way.

How do I covert below using your decorator module

Hello micheles,

Great work

How do I covert below using your decorator module

def decorator_name(f):
    @another_decorator_name
    def wrapper(*args, **kwargs):
        return f(*args, **kwargs)

    return wrapper

Include "argument-resolving decorator" into decorator package ?

I've developed a handy utility which looks like @decorator, but pre-resolves arguments before passing them to the wrapper function (using https://docs.python.org/2/library/inspect.html#inspect.getcallargs).

It's very useful when you need to decorate many functions, without knowing it advanced if they'll be passed arguments by position (<args>) or by keywords (<*kwargs>). Since incoming arguments are normalized in a simple dict, you can inspect and modify them freely.

I used it to automatically inject or commit/rollback SQL sessions which travelled through program, for example.

Below is an example of use.

Would you be OK for me to prepare a pull request including that utility next to the standard "decorator".

        @resolving_decorator
        def inject_session(func, **all_kwargs):
            if not all_kwargs["session"]:
                all_kwargs["session"] = "<SESSION>"
            return func(**all_kwargs)

        @inject_session
        def myfunc(session):
            return session

        assert myfunc(None) == myfunc(session=None) == "<SESSION>"
        assert myfunc("<stuff>") == myfunc(session="<stuff>") == "<stuff>"

version 3.4.1 is not installable via pip

% pip install -v decorator==3.4.1 
Collecting decorator==3.4.1
  Getting page https://pypi.python.org/simple/decorator/
  Starting new HTTPS connection (1): pypi.python.org
  "GET /simple/decorator/ HTTP/1.1" 200 488
  URLs to search for versions for decorator==3.4.1:
  * https://pypi.python.org/simple/decorator/
  Getting page https://pypi.python.org/simple/decorator/
  Analyzing links from page https://pypi.python.org/simple/decorator/
    Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.1.tar.gz#md5=a8fc62acd705f487a71bc406e19e0cc6 (from https://pypi.python.org/simple/decorator/), version: 3.3.1
    Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.2.tar.gz#md5=446f5165af67eb0fcd8fd28abd259e86 (from https://pypi.python.org/simple/decorator/), version: 3.3.2
    Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.3.tar.gz#md5=f5a0227cb1c34a0e7d5b7f9cd2ae3135 (from https://pypi.python.org/simple/decorator/), version: 3.3.3
    Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.4.0.tar.gz#md5=1e8756f719d746e2fc0dd28b41251356 (from https://pypi.python.org/simple/decorator/), version: 3.4.0
    Skipping link http://code.google.com/p/micheles/source/browse/#hg%2Fdecorator (from https://pypi.python.org/simple/decorator/); not a file
    Skipping link http://micheles.googlecode.com/hg/decorator/documentation.html (from https://pypi.python.org/simple/decorator/); unknown archive format: .html
    Skipping link http://micheles.googlecode.com/hg/decorator/documentation.pdf (from https://pypi.python.org/simple/decorator/); unknown archive format: .pdf
    Skipping link http://micheles.googlecode.com/hg/decorator/documentation3.html (from https://pypi.python.org/simple/decorator/); unknown archive format: .html
    Skipping link http://micheles.googlecode.com/hg/decorator/documentation3.pdf (from https://pypi.python.org/simple/decorator/); unknown archive format: .pdf
    Skipping link http://packages.python.org/distribute/ (from https://pypi.python.org/simple/decorator/); not a file
    Skipping link http://science.webhostinggeeks.com/dekorater-modula (from https://pypi.python.org/simple/decorator/); not a file
  Could not find a version that satisfies the requirement decorator==3.4.1 (from versions: 3.3.1, 3.3.2, 3.3.3, 3.4.0)
  Cleaning up...
  No distributions matching the version for decorator==3.4.1

Python 3.9: AttributeError: 'Thread' object has no attribute 'isAlive'

Hello, we are already testing packages in Fedora with Python 3.9.0a2. The tests of decorator fail with:

+ /usr/bin/python3 setup.py test
running test
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing dependency_links to src/decorator.egg-info/dependency_links.txt
writing top-level names to src/decorator.egg-info/top_level.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'README.rst'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test_before_after (tests.test.CoroutineTestCase) ... ok
test_coro_to_func (tests.test.CoroutineTestCase) ... ok
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 531, in tests.documentation
Failed example:
    print(read_data())  # data is not available yet
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest tests.documentation[33]>", line 1, in <module>
        print(read_data())  # data is not available yet
      File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
      File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
        elif f.thread.isAlive():
    AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 535, in tests.documentation
Failed example:
    print(read_data())  # data is not available yet
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest tests.documentation[35]>", line 1, in <module>
        print(read_data())  # data is not available yet
      File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
      File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
        elif f.thread.isAlive():
    AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 539, in tests.documentation
Failed example:
    print(read_data())
Exception raised:
    Traceback (most recent call last):
      File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
        exec(compile(example.source, filename, "single",
      File "<doctest tests.documentation[37]>", line 1, in <module>
        print(read_data())
      File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
      File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
        elif f.thread.isAlive():
    AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
1 items had failures:
   3 of  88 in tests.documentation
***Test Failed*** 3 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok

======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/builddir/build/BUILD/decorator-4.4.0/src/tests/test.py", line 81, in test
    self.assertEqual(err, 0)
AssertionError: 3 != 0

----------------------------------------------------------------------
Ran 20 tests in 5.660s

FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=20 errors=0 failures=1>

Indeed, isAlive() is deprecated, use is_alive() instead:

$ python3.8 -c 'import threading; t = threading.Thread(); t.isAlive()'
<string>:1: DeprecationWarning: isAlive() is deprecated, use is_alive() instead
$ python3.9 -c 'import threading; t = threading.Thread(); t.isAlive()'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AttributeError: 'Thread' object has no attribute 'isAlive'

Will submit a PR.

Fully signature preserving equivalent of functools.wraps

On Python 3, functools.wraps nominally preserves function signatures, e.g.,

import functools

def original(x):
    return x

@functools.wraps(original)
def wrapper(*args, **kwargs):
    return original(*args, **kwargs)

The function has the right associated signature object (so introspection works properly) but abstraction is still a little leaky, e.g., for handling invalid function arguments:

In [11]: wrapper(y=1)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-11-1e0b778ae20b> in <module>()
----> 1 wrapper(y=1)

<ipython-input-4-878d38897098> in wrapper(*args, **kwargs)
      6 @functools.wraps(original)
      7 def wrapper(*args, **kwargs):
----> 8     return original(*args, **kwargs)

TypeError: original() got an unexpected keyword argument 'y'

I think the decorator library can produce fully compatible function signatures, but the interface is a little different:

@functools.partial(decorate, original)
def wrapper(original, *args, **kwargs):
    return original(*args, **kwargs)

In particular, it's awkward that I need to include the original function in the wrapper signature (e.g., def wrapper(original, *args, **kwargs) rather than def wrapper(*args, **kwargs) .

Is there an easy way to write a drop-in replacement for functools.wraps with the decorator library?

bug in pypi distfile

The distfiles for decorator 4.0.9 and 4.0.10 contain a backup file, src/decorator.egg-info/SOURCES.txt~, that is installed. Please remove it for the next release.

__qualname__ shold not be set if not existing

At the moment, the qualname attribute is set as None if the decorated function does not have this attribute. This can be considered unexpected behavior and can cause problems in some cases.

This attribute should either not be set if the decorated function does not have it, either use the name value instead.

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.