Aspyct.org

aspyct

Well I guess this library deserves its own page :) Because, before being the name of this website and behind all this stuff, aspyct used to be the name of a small, ahead-of-time library.

If you’d like to read more about the aspyct library and download it, please refer to the old aspyct wiki.

The development of it is currently discontinued. Why that? Because what aspyct allows you to do is actually quite easy with python! Basically, aspyct did two things:

  1. wrap functions with custom behavior, like transaction handling or security enforcement;
  2. cut through modules and classes to apply these behaviors.

Aspyct was and will always be a major step in my developer’s life, so it makes me a little sad to say that it’s not really added value! But still, it’s true, so let’s see what python can do for us :)

Using python decorators

The first thing is really easy to do with decorators. Let me tell you about them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Basically, a decorator is a function that returns a function.
# They usually take another function as argument, however, and wrap the latter
# with some behavior.

# Create our decorator
def simple_decorator(func):
    # This is were your neurons should start heating up...
    def wrapper():
        '''Prints "before" and "after" around `func`'''
        print("before")
        func()
        print("after")

    return wrapper

# Still alive? See, was easy ;)
# All we did is create a function that call the given `func`,
# and return this new function

# Now decorate a function with the '@' sign
@simple_decorator
def test():
    print("inside")

Now try & run this test function.

1
2
3
4
5
6
7
8
9
>>> test()
before
inside
after

# By the way, what do you think is the result of this line? Why?
>>> test.__name__

# And when you got it, have a look at functools.wraps ;)

So what can we do with that? Imagine we need to handle database transactions: before the method is called, start a transaction. If everything goes right, commit, otherwise rollback. We could create a class for this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class TransactionHandler(object):
    def wrap(self, func):
        def wrapper(*args, **kwargs): # Standard python varargs notation
            self.start_transaction()
            try:
                func(*args, **kwargs)
                self.commit_transaction()
            except Exception as e:
                self.rollback_transaction()
                raise e

        return wrapper

    def start_transaction(self):
        print("Starting transaction")

    def commit_transaction(self):
        print("Commit")

    def rollback_transaction(self):
        print("Rollback")


# And later...
tx_handler = TransactionHandler()

@tx_handler.wrap
def save_into_db(value):
    # Verify that the data is correct.
    # In our case, it must be true, otherwise an error occurs
    if not value:
        raise ValueError("Must be true")
    else:
        print("Saving value") # Imagine we save this into database

And now try it:

1
2
3
4
5
6
7
8
9
10
11
12
>>> save_into_db(True)
Starting transaction
Saving value
Commit

>>> save_into_db(False)
Starting transaction
Rollback
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 10, in wrapper
ValueError: Must be true

Interesting point: you can stack up several decorators on a single function, or also use decorators on methods (i.e. “class functions”). By the way, what do you think is @instancemethod, @property et al?

1
2
3
4
5
6
7
8
class MyClass:
    @second_decorator
    @first_decorator
    def my_method(self):
        # Do something useful...

# Notice the weird order "second, first"?
# That's because the decorator closest to the function is applied first.

So why use decorators rather than aspyct? Well for this kind of purpose aspyct was more like a hammer to kill a fly. A lot of logic was put into doing not a lot of things, with the inherent bugs and additional debugging complexity.

Monkey patching

The second thing is a bit more tricky to accomplish, but is based on a simple fact: you can replace functions and methods at runtime. This is called “monkey patching”. Let’s reuse our previous simple_decorator, with a slight modification

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def simple_decorator(func):
    # This is were your neurons should start heating up...
    def wrapper(*args, **kwargs):
        '''Prints "before" and "after" around `func`'''
        print("before")
        func(*args, **kwargs)
        print("after")

    return wrapper

def my_function():
    print("inside")

# And do the monkey patch!
my_function = simple_decorator(my_function)
# Actually, that's exactly the equivalent of decorating my_function


# This can be applied to methods as well, with a little twist
class MyClass:
    def my_method(self):
        print("inside")

# Suspense...
MyClass.my_method = simple_decorator(MyClass.my_method)

# For methods, it's important that the `self` argument be passed to function
# Therefore, we need the *args, **kwargs from the decorator

And… let’s do it!

1
2
3
4
5
6
7
8
9
10
>>> my_function()
before
inside
after

>>> a = MyClass()
>>> a.my_method()
before
inside
after

That’s it folks!

Hope this helps! And long live aspyct ;) Thanks to all of you who sent (send?) me messages about aspyct, it’s always a great feeling to see how’s it’s appreciated :)

Comments