{ "info": { "author": "Zygmunt Krynicki", "author_email": "zygmunt.krynicki@canonical.com", "bugtrack_url": null, "classifiers": [], "description": "About call\n==========\n\nYou call python functions a million times every day.\nDid you ever wonder how this actually happens?\n\nWell, I did.\n\nI was interested in writing some decorators for my functions but I quickly\nrealized I needed to know how passed arguments are bound to function\nparameters. This involves positional arguments, keyword arguments, defaults\nvalues, variable argument lists and variable keyword arguments. Python 3 also\nbrings in the rather exotic keyword-only arguments. All in all it seemed to be\nsomething more than a quick hack if I wanted to do it properly.\n\nI've started with simple functions and some tests and quickly discovered that,\npython actually changes quite a lot between versions (and we're still talking\nabout function calls!) and the call semantics, specially reported errors,\ndiffer from version to version.\n\nSo I set out to use the weekend in bed (I'm having a bad flu now) to write pure\npython implementation of python function call semantics that correctly mimics\npython 3.1, python 3.2 and python 3.3.\n\nUsage\n=====\n\nThe one thing this library does is allows one to see how a given function\n_would_ bind positional and keyword argument to the declared parameters without\nactually calling it.\n\nThis is useful for constructing decorators that take action on value or type of\neach argument and they don't want to reimplement the same (rather lengthy if\nyou want to check the code) logic. If you're interested in just that then skip\nto the 'Applications' section below.\n\nHere's a simple example that illustrates this. We'll start with a simple\nfunction that takes two arguments, including one with a default value. \n\n``` python\n>>> def foo(a, b=2):\n>>> return a, b\n```\n\nThe call module really exports just one class, PythonCall. You need to\ninstantiate it with a function or any other callable object.\n\n``` python\n>>> from call import PythonCall\n>>> c = PythonCall(foo)\n```\n\nThen we can call the bind() method to see how python would have reacted to the\nvery same function call. The method takes two arguments, a list of positional\narguments and a dictionary or any other mapping of keyword arguments. The\nreturn value is a dictionary with argument names and their actual values.\n\n``` python\n>>> c.bind([1, 2], {})\n>>> {'a': 1, 'b': 2}\n>>> c.bind([1], {})\n>>> {'a': 1, 'b': 2}\n>>> c.bind([1], {'b': 3})\n>>> {'a': 1, 'b': 3}\n>>> c.bind([], {'a': 4, 'b': 5})\n>>> {'a': 4, 'b': 5}\n```\n\nNote that any incorrect arguments raise the same exception that python would.\n\nApplications\n============\n\nAs mentioned earlier this library is intended for developing decorators. That\nneed to inspect function arguments before calling it. Here's a simple example\nof how this can be done to enforce simple type checking\n\n``` python\nfrom functools import wraps\n\nfrom call import PythonCall\n\ndef check_types(func):\n \"\"\"\n Check if annotated function arguments are of the correct type\n \"\"\"\n call = PythonCall(func)\n\n @wraps(func)\n def decorator(*args, **kwargs):\n parameters = call.bind(args, kwargs)\n for arg_name, expected_type in func.__annotations__.items():\n if not isinstance(parameters[arg_name], expected_type):\n raise TypeError(\"{} must be a {}\".format(\n arg_name, expected_type))\n return call.apply(args, kwargs)\n return decorator\n\n\n@check_types\ndef example_func(a: int, b: str=\"text\"):\n print(\"I was called with {!r} and {!r}\".format(a, b))\n```\n\nHere's another example allows us to define arbitrary validators to any\nargument. \n\n``` python\nfrom functools import wraps\n\nfrom call import PythonCall\n\ndef validate(func):\n call = PythonCall(func)\n\n @wraps(func)\n def decorator(*args, **kwargs):\n parameters = call.bind(args, kwargs)\n for arg_name, validator in func.__annotations__.items():\n if not validator(parameters[arg_name]):\n raise TypeError(\n \"Argument {!r} failed to validate\".format(arg_name))\n return call.apply(args, kwargs)\n return decorator\n\n@validate\ndef example_func(a: lambda a: a > 1, b: lambda b: b.startswith(\"foo\")=\"foo\"):\n print(\"I was called with {!r} and {!r}\".format(a, b))\n```\n\nIt's possible to extend this decorator pattern to implement arbitrary\nvalidation system, multi-method call dispatching and other constructs known\nfrom various languages.\n\nTesting\n=======\n\nTo really test the code you want to have 'tox' installed (pip install it if you\ndon't have it already). Tox will read the configuration file and run tests for\nall supported versions of python in a correct and fully automatic way.\n\nIt is usually hard to get all the required python versions on Ubuntu so if\nyou're using that I can recommend the 'deadsnakes' ppa that carries all the\nversions available. See: https://launchpad.net/~fkrull/+archive/deadsnakes\n\nPatches and issues\n==================\n\nFeel free to send patches and to report issues via github\nat https://github.com/zyga/call/issues\n", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/zyga/call/", "keywords": null, "license": "LGPL", "maintainer": null, "maintainer_email": null, "name": "call", "package_url": "https://pypi.org/project/call/", "platform": "UNKNOWN", "project_url": "https://pypi.org/project/call/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/zyga/call/" }, "release_url": "https://pypi.org/project/call/0.1/", "requires_dist": null, "requires_python": null, "summary": "Pure-python reimplementation of python call semantics", "version": "0.1" }, "last_serial": 787209, "releases": { "0.1": [ { "comment_text": "", "digests": { "md5": "7083ce1ce86474165f36eec9579d8999", "sha256": "c06ad8d570b73a9e6f4462002833806e1f11bb1e0f40726f08227be33e3f4f52" }, "downloads": -1, "filename": "call-0.1.tar.gz", "has_sig": false, "md5_digest": "7083ce1ce86474165f36eec9579d8999", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10729, "upload_time": "2012-11-04T15:47:41", "url": "https://files.pythonhosted.org/packages/c4/0b/b2ed20d83d3dad385a67821ffa45f8385aad2fbbb1db5b7a56b1a632ec3a/call-0.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "7083ce1ce86474165f36eec9579d8999", "sha256": "c06ad8d570b73a9e6f4462002833806e1f11bb1e0f40726f08227be33e3f4f52" }, "downloads": -1, "filename": "call-0.1.tar.gz", "has_sig": false, "md5_digest": "7083ce1ce86474165f36eec9579d8999", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 10729, "upload_time": "2012-11-04T15:47:41", "url": "https://files.pythonhosted.org/packages/c4/0b/b2ed20d83d3dad385a67821ffa45f8385aad2fbbb1db5b7a56b1a632ec3a/call-0.1.tar.gz" } ] }