[
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# dotenv\n.env\n\n# virtualenv\n.venv\nvenv/\nENV/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Alan Cristhian\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# statically\n\nCompiles a function or class with [Cython](http://www.cython.org). Use annotations for static type\ndeclarations.\n\nPython 3 is required.\n\nTo compile, you must decorate the function or class with `typed`. Example:\n\n```python\nimport cython\nimport statically\n\n@statically.typed\ndef func():\n    # Cython types are evaluated as for cdef declarations\n    x: cython.int               # cdef int x\n    y: cython.double = 0.57721  # cdef double y = 0.57721\n    z: cython.float  = 0.57721  # cdef float z  = 0.57721\n\n    # Python types shadow Cython types for compatibility reasons\n    a: float = 0.54321          # cdef double a = 0.54321\n    b: int = 5                  # cdef object b = 5\n    c: long = 6                 # cdef object c = 6\n\n@statically.typed\nclass A:\n    a: cython.int\n    b: cython.int\n    def __init__(self, b=0):\n        self.a = 3\n        self.b = b\n```\n\nPython 3.5 or less supports type hints in the parameters but not in the body\nof the code. Read the next case:\n\n```python\n@statically.typed\ndef sum_powers(x: cython.int):\n    return sum([x**n for n in range(1, x + 1)])\n```\n\nWorks, but provides just a 5% increase in speed over the Python equivalent\nbecause the `n` variable is not annotated. In this case you can declare\nthe type of the variable in the parameter with a default value.\n\n```python\n@statically.typed\ndef sum_powers(x: cython.int, n: cython.int = 0):\n    return sum([x**n for n in range(1, x + 1)])\n```\n\nFor this function I got more than 1,400% speed increase.\n\n## Installation\n\n```shell\n$ pip install git+https://github.com/AlanCristhian/statically.git\n```\n\n## Caveats\n\n- Async generators are not supported.\n- In contexts that doesn't have file - IDLE or REPL - will fallback to normal Python code. Works fine with [IPython shell](http://ipython.readthedocs.io/en/stable/).\n\n## Contribute\n\nI am not a native english speaker, so you can help me with the documentation.\nAlso I am not convinced about the module name. The `typed` decorator can\ncompile functions or classes without type declarations.\n\nFeel free to send me a pull request or open an issue.\n"
  },
  {
    "path": "README.rst",
    "content": "statically\n==========\n\nCompiles a function or class with `Cython <http://www.cython.org>`_. Use annotations for static type declarations.\n\nPython 3.5+ is required.\n\nTo compile, you must decorate the function or class with ``typed``. Example: ::\n\n    import cython\n    import statically\n\n    @statically.typed\n    def func():\n        # Cython types are evaluated as for cdef declarations\n        x: cython.int               # cdef int x\n        y: cython.double = 0.57721  # cdef double y = 0.57721\n        z: cython.float  = 0.57721  # cdef float z  = 0.57721\n\n        # Python types shadow Cython types for compatibility reasons\n        a: float = 0.54321          # cdef double a = 0.54321\n        b: int = 5                  # cdef object b = 5\n        c: long = 6                 # cdef object c = 6\n\n    @statically.typed\n    class A:\n        a: cython.int\n        b: cython.int\n        def __init__(self, b=0):\n            self.a = 3\n            self.b = b\n\nPython 3.5 or less supports type hints in the parameters but not in the body\nof the code. Read the next case: ::\n\n    @statically.typed\n    def sum_powers(x: cython.int):\n        return sum([x**n for n in range(1, x + 1)])\n\n\nWorks, but provides just a 5% increase in speed over the Python equivalent\nbecause the `n` variable is not annotated. In this case you can declare\nthe type of the variable in the parameter with a default value. ::\n\n    @statically.typed\n    def sum_powers(x: cython.int, n: cython.int = 0):\n        return sum([x**n for n in range(1, x + 1)])\n\nFor this function I got more than 1,400% speed increase.\n\nInstallation\n------------\n\n    $ pip install git+https://github.com/AlanCristhian/statically.git\n\nCaveats\n-------\n\n- Async generators are not supported.\n- In contexts that doesn't have file - IDLE or REPL - will fallback to normal Python code. Works fine with `IPython shell <http://ipython.readthedocs.io/en/stable/>`_.\n\nContribute\n----------\n\nI am not a native english speaker, so you can help me with the documentation.\nAlso I am not convinced about the module name. The `typed` decorator can\ncompile functions or classes without type declarations.\n\nFeel free to send me a pull request or open an issue.\n"
  },
  {
    "path": "setup.py",
    "content": "\"\"\"Installation script.\"\"\"\n\nfrom setuptools import setup\nfrom sys import version_info\n\nassert version_info >= (3,), \\\n\"Python 3 is required. Got %s.%s\" % (version_info.major, version_info.minor)\n\nsetup(\n    name=\"statically\",\n    version=\"1.0a2\",\n    py_modules=[\"statically\"],\n    zip_safe=True,\n    author=\"Alan Cristhian\",\n    author_email=\"alan.cristh@gmail.com\",\n    description=\"Compiles a python function with cython using only a decorator.\",\n    license=\"MIT\",\n    keywords=\"cython decorator compilation\",\n    url=\"https://github.com/AlanCristhian/statically\",\n    install_requires=[\"cython\"],\n)\n"
  },
  {
    "path": "statically.py",
    "content": "import inspect\nimport sys\nimport warnings\n\nimport cython\n\n\n__all__ = [\"typed\"]\n__version__ = \"1.0a2\"\n\n\n# async generators only present in Python 3.6+\nhas_async_gen_fun = (3, 6) <= sys.version_info[:2]\n\n\ndef _can_cython_inline():\n    \"\"\"Checks whether we are in a context which makes it possible to compile code inline with Cython.\n\n    Currently it is known that standard REPL and IDLE can't do that.\n    \"\"\"\n    import __main__ as main\n\n    # statically works with IPython\n    if hasattr(main, 'get_ipython'):\n        return True\n\n    # standard REPL doesn't have __file__\n    return hasattr(main, '__file__')\n\n\ndef _get_source_code(obj):\n    if inspect.isclass(obj):\n        lines = inspect.getsourcelines(obj)[0]\n        extra_spaces = lines[0].find(\"class\")\n        return \"\".join(l[extra_spaces:] for l in lines)\n    elif callable(obj):\n        lines = inspect.getsourcelines(obj)[0]\n        extra_spaces = lines[0].find(\"@\")\n        return \"\".join(l[extra_spaces:] for l in lines[1:])\n    else:\n        message = \"Function or class expected, got {}.\".format(type(obj).__name__)\n        raise TypeError(message)\n\n\ndef _get_non_local_scopes(frame):\n    while frame:\n        yield frame.f_locals\n        frame = frame.f_back\n\n\ndef _get_outer_variables(obj):\n    frame = inspect.currentframe().f_back\n    non_local_scopes = _get_non_local_scopes(frame)\n    non_local_variables = list(obj.__code__.co_freevars)\n    variables = {}\n    for scope in non_local_scopes:\n        for name in non_local_variables:\n            if name in scope:\n                variables[name] = scope[name]\n                non_local_variables.remove(name)\n        if not non_local_variables:\n            break\n    return variables\n\n\ndef typed(obj):\n    \"\"\"Compiles a function or class with cython.\n\n    Use annotations for static type declarations. Example:\n\n        import statically\n\n        @statically.typed\n        def add_two(x: int) -> int:\n            two: int = 2\n            return x + two\n    \"\"\"\n    if not _can_cython_inline():\n        return obj\n    elif has_async_gen_fun and inspect.isasyncgenfunction(obj):\n        raise TypeError(\"Async generator funcions are not supported.\")\n\n    source = _get_source_code(obj)\n    frame = inspect.currentframe().f_back\n    if inspect.isclass(obj):\n        locals_ = frame.f_locals\n    else:\n        locals_ = _get_outer_variables(obj)\n    with warnings.catch_warnings():\n        warnings.simplefilter(\"ignore\")\n        compiled = cython.inline(source, locals=locals_,\n                                 globals=frame.f_globals, quiet=True)\n        return compiled[obj.__name__]\n\n\nif not _can_cython_inline():\n    warnings.warn(\n        \"Current code isn't launched from a file so statically.typed isn't able to cythonize stuff.\"\n        \"Falling back to normal Python code.\",\n        RuntimeWarning\n    )\n"
  },
  {
    "path": "test_statically.py",
    "content": "import unittest\nimport asyncio\nimport subprocess\nimport sys\nimport os.path\n\nimport cython\ntry:\n    import IPython\nexcept ImportError:\n    ipython_installed = False\nelse:\n    ipython_installed = True\n\nimport statically\n\n\nONE_HUNDRED = 100\nexecute = asyncio.get_event_loop().run_until_complete\n\n\ndef is_cython_function(obj):\n    return 'cython_function_or_method' in str(type(obj))\n\n\ndef anext(agen):\n    gen = agen.asend(None)\n    try:\n        gen.send(None)\n    except StopIteration as error:\n        return error.args[0]\n\n\nclass CompilationSuite(unittest.TestCase):\n    def test_function(self):\n        @statically.typed\n        def identity(x: cython.int):\n            return x\n        self.assertEqual(identity(14), 14)\n\n    def test_is_compiled(self):\n        @statically.typed\n        def compiled(x: cython.int):\n            return x\n        self.assertTrue(is_cython_function(compiled))\n\n    def test_non_local_var_in_class(self):\n        one = 1\n        @statically.typed\n        class Class:\n            number = 100 + one\n        self.assertEqual(Class.number, 101)\n\n    def test_non_local_var_in_method(self):\n        two = 2\n        class Class:\n            @statically.typed\n            def add_two(self, x):\n                return x + two\n        obj = Class()\n        self.assertEqual(obj.add_two(100), 102)\n\n    def test_non_local_var_in_function(self):\n        tree = 3\n        @statically.typed\n        def add_tree(x):\n            return x + tree\n        self.assertEqual(add_tree(100), 103)\n\n    def test_non_local_var_in_generator_function(self):\n        four = 4\n        @statically.typed\n        def add_four(x):\n            yield x + four\n        self.assertEqual(next(add_four(100)), 104)\n\n    def test_non_local_var_in_coroutine_function(self):\n        five = 5\n        @statically.typed\n        async def add_five(x):\n            return x + five\n        self.assertEqual(execute(add_five(100)), 105)\n\n    def test_global_var_in_class(self):\n        @statically.typed\n        class Class_:\n            number = 1 + ONE_HUNDRED\n        self.assertEqual(Class_.number, 101)\n\n    def test_global_var_in_method(self):\n        class Class:\n            @statically.typed\n            def add_one_hundred(self, x):\n                return ONE_HUNDRED + x\n        obj = Class()\n        self.assertEqual(obj.add_one_hundred(2), 102)\n\n    def test_global_var_in_function(self):\n        @statically.typed\n        def add_one_hundred(x):\n            return ONE_HUNDRED + x\n        self.assertEqual(add_one_hundred(3), 103)\n\n    def test_global_var_in_generator_function(self):\n        @statically.typed\n        def add_one_hundred(x):\n            yield ONE_HUNDRED + x\n        self.assertEqual(next(add_one_hundred(4)), 104)\n\n    def test_global_var_in_coroutine_function(self):\n        @statically.typed\n        async def add_one_hundred(x):\n            return ONE_HUNDRED + x\n        self.assertEqual(execute(add_one_hundred(5)), 105)\n\n    @unittest.skipUnless(statically.has_async_gen_fun, \"Test does not apply for this version of Python\")\n    def test_async_generator(self):\n        message = r\"Async generator funcions are not supported.\"\n        with self.assertRaisesRegex(TypeError, message):\n             from test_statically_async import generator\n\n\n@unittest.skipUnless(ipython_installed, \"IPython not installed\")\nclass IPythonSuite(unittest.TestCase):\n    def test_ipython(self):\n        base_dir = os.path.dirname(sys.executable)\n        executable = os.path.join(base_dir, \"ipython\")\n        process = subprocess.Popen([executable], stdin=subprocess.PIPE,\n                                   stdout=subprocess.PIPE)\n        script = \"import statically\\n\" \\\n                 \"@statically.typed\\n\" \\\n                 \"def add(a: int, b: int): return a + b\\n\\n\" \\\n                 \"'cython_function_or_method' in str(type(add))\\n\".encode()\n        stdout, _ = process.communicate(script)\n        lines = stdout.decode().split(\"\\n\")\n        process.terminate()\n        self.assertEqual(lines[-4], \"In [3]: Out[3]: True\")\n\n\nif __name__ == '__main__':\n    unittest.main()\n"
  },
  {
    "path": "test_statically_async.py",
    "content": "import statically\n\n@statically.typed\nasync def generator():\n    yield\n"
  }
]