{ "info": { "author": "Carel van Dam", "author_email": "carelvdam@gmail.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Utilities" ], "description": "------------\nIntroduction\n------------\n\nThis package provides a form of layer management for Python.\nIt transparently substitutes an existing module with ones patch thereof.\nThis `normalizes` or `standardizes` the original modules across ones code base.\n\nApeMan intercepts ones ``import`` calls to substitute the desired module with a patched variant provided in an overlay.\nAn overlay\\ [#gentoo]_ is simply a python package containing ones patches for other packages.\nAdditionally the overlays :file:`__init__.py` file must invoke ApeMan.\n\nOverlays make ones patches available across multiple projects; consistently exposing the additional API features provided by them.\nSimilarly a set of patches may quickly be substituted for another by simply importing a different overlay.\n\nThis formalizes monkey patching where the Ape in question has an affection for books, dislikes their readers and discourages, quite aggressively one might say, the use of the m... word\\ [#librarian]_.\n\n.. rubric:: Footnotes\n\n.. [#gentoo] The term overlay is taken from Portage the package manager for Gentoo Linux.\n.. [#librarian] Someone out there was about to find out their worst nightmare was a maddened librarian. With a Badge.\n\n.. Suppose...\n.. ----------\n.. \n.. One is writing a script that imports a :class:`CLASS` from a :mod:`MODULE` in ones Python installation,\n.. ::\n.. \n.. from MODULE import CLASS\n.. \n.. print(CLASS())\n.. \n.. and one desperately wished that the class had a certain feature, say a nicer string representation.\n.. One implements the following \n.. ::\n.. from MODULE import CLASS\n.. \n.. class CLASS(CLASS) :\n.. def __str__(self):\n.. return \"Nicer {} representation \".format(str(self))\n.. \n.. while in their original script they would now import the patch\n.. ::\n.. \n.. from .MODULE import CLASS\n.. \n.. print(CLASS())\n.. \n.. This proves so useful that one wishes to make their patched implementation of :class:`CLASS` available to all of their projects.\n.. ApeMan allows one to package their patch into an overlay so that\n.. ::\n.. \n.. import OVERLAY\n.. from MODULE import CLASS\n.. \n.. print(CLASS())\n\n-------\nProblem\n-------\n\nOccasionally one wants to patch the functions and/or classes provided by some other package; when it lacks features or to normalize the provided |API|.\nA naive implementation relying upon the following structure;\n::\n\n PROJECT/ # The root folder for ones project\n PACKAGE/ # The root folder of ones package.\n PATCH.py # The module containing ones patches.\n ... # The other modules in the package.\n __main__.py # The main script importing and using the patched module.\n\nwould patch the features from the `SOURCE` module by importing and overloading it's `FUNCTION`\\ s and `CLASS`\\ es.\n::\n \n from SOURCE import *\n \n _FUNCTION_ = FUNCTION\n def FUNCTION(*args, **kvps):\n ...\n _FUNCTION_(*args, **kvps)\n ...\n \n class CLASS(CLASS):\n ...\n \nOnes modules would then the `PATCH` in favour of the original package; pulling in the modifications.\n::\n\n from .PATCH import *\n ...\n\nThis works well for once off patches in standalone projects.\n\nNow, should a particular patch be especially useful, one might wish to use it across multiple projects.\nAt this point, one might copy the patch across to the other project(s) creating copies; copies that diverge from one another over time as features are added.\n\nShould the original patch grow over time may become necessary to duplicate more of the structure of the original package; creating more files and exacerbating the problem.\nEventually one ends up with multiple `PATCH` files, spread across various projects, whose contents deviate further from one another to increasingly varied degrees.\n\n--------\nSolution\n--------\n\nApeMan offers, a hopefully better strategy, that consistently manages these patches.\nIt resolves this by placing ones patches into an overlay; a package dedicated to ones patches.\nThis may be done locally, within a sub-package, for one shot usage; or globally, within a separate package, for repeated usage by multiple packages.\n\n.. . If the following represents ones package structure\n.. . ::\n.. . \n.. . PACKAGE/ # The root folder of ones package.\n.. . overlay/ # The overlay containing the patches.\n.. . SOURCE # The target package one is wrapping or patching.\n.. . __init__.py # The __init__.py script importing ApeMan.\n.. . __main__.py # The packages main script, executed when invoked as a module.\n\n.. The packages main file makes it's usual calls to import the `SOURCE` modules but by importing the overlay first ApeMan redirects later imports to use ones patched modules instead.\n.. ::\n.. \n.. import overlay\n.. from SOURCE import *\n.. \n.. ...\n\nOverlay Structure\n=================\n\nWhether it is made available globally or locally ones structures their overlay(s) as follows::\n\n OVERLAY/ # The root folder of the ApeMan overlay\n _PACKAGE_.py # The module containing ones patches, renamed after the source module or package\n ... # Further patches provided by the overlay\n __init__.py # The file invoking ApeMan; identifying it as an overlay\n\nThe overlays' :file:`__init__.py` file then imports and registers the :class:`ApeMan` instance;\n::\n\n from apeman import ApeMan; \n apeman = ApeMan()\n\nwhich intercepts later imports, substituting ones patches for the original modules.\n\nLocal Overlay(s)\n================\n\nLocally one may create an overlay at any level within their package by including a sibling package along side it's modules.\n::\n\n PROJECT/ # The root folder for ones project\n PACKAGE/ # The root folder of ones package.\n OVERLAY/ # The ApeMan overlay\n ... # The contents of the overlay\n ... # The other modules in the package.\n __main__.py # The main script importing and using the patched module.\n\nOther modules within ones package may then invoke the overlay via relative import.\n::\n\n import .OVERLAY\n from SOURCE import *\n \n ...\n\nGlobal Overlay(s)\n=================\n\nGlobally, an overlay, is provided as a separate, standalone package.\n::\n\n PROJECT/ # The root folder for ones project\n OVERLAY/ # The root folder of the ApeMan Overlay\n ... # The contents of the overlay\n PACKAGE/ # The root folder of ones package.\n ... # The other modules in the package.\n __main__.py # The main script importing and using the patched module.\n\nIn this case the modules in ones package must invoke the overlay using an absolute import.\n::\n\n import OVERLAY\n from SOURCE import *\n \n ...\n\n.. One must explicitly import the features they need as the `OverlayImporter` actually blocks further imports.\n\n.. Note that an overlay package is meant to reside alongside its sibling module to afford the most flexibility. \n.. Whether or not this is possible at every level within a package depends upon how python enforces scoping.\n\n-------\nExample\n-------\n\nConsider patching the :class:`Decimal` class from the :mod:`decimal` module.\n\nMonkey Patching\n===============\n\nThe following structure is the simplest to implement.\n::\n\n PACKAGE/ # The root folder of ones package.\n _decimal_.py # The module containing ones patches to the decimal module.\n __main__.py # The packages main script, executed when invoked as a module.\n\nWithin :file:`_decimal_.py` import everything from the :mod:`decimal` module then subclass and monkey patch the `Decimal` class; modifying it's string representation.\n::\n \n from decimal import *\n \n class Decimal(Decimal): \n def __str__(self) :\n return super().__str__().split(\"'\")[1]\n \nThen within the :file:`__main__.py` file one would import and use the patch as follows::\n\n import ._decimal_ as decimal\n from decimal import Decimal\n \n print(Decimal(42))\n\nThis should output `42` instead of `Decimal('42')` when we invoke the package using :code:`python -m PACKAGE`.\n\nApe Patching\n============\n\nUsing ApeMan we would move the `_decimal_.py` file into a sub-folder called `overlay`, with the resulting structure;\n::\n\n PACKAGE/ # The root folder of ones package.\n overlay/ # The overlay containing the patches.\n _decimal_.py # The module containing ones patches to the decimal module.\n __init__.py # The __init__.py script importing ApeMan.\n __main__.py # The packages main script, executed when invoked as a module.\n\naccordingly the :file:`__init__.py` file should contain ::\n \n from apeman import ApeMan\n apeman = ApeMan\n\nThe main file is then adapted to reflect the following.\n::\n\n import .overlay\n from decimal import Decimal\n \n print(Decimal(42))\n\nWithout ...\n===========\n\nOne might argue that a cleaner structure still, is as follows\n::\n\n PACKAGE/ # The root folder of ones package.\n decimal.py # The module containing ones patches to the decimal module.\n __main__.py # The packages main script, executed when invoked as a module.\n\nbut this results in a whole series of clashes and the following error\n::\n\n AttributeError: 'module' object has no attribute 'Decimal'\n \n.. Other related errors include : \n.. SystemError: Parent module '' not loaded, cannot perform relative import\n \n.. Essentially the :file:`decimal.py` module gets installed within the decimal name space preventing the import of the original library.\n\nEssentially the :file:`PACKAGE/decimal.py` file gets loaded as the :mod:`decimal` module and is assigned under :attr:`sys.modules` reserving the `decimal` key; preventing the subsequent import of the actual :mod:`decimal` module.\n\n.. note ::\n\n This method actually works if one tells python it's executing a module using the `-m` switch, :code:`python -m PACKAGE`, but only I found this out after creating the package.\n\n-------------\nCompatability\n-------------\n\nThe machinery underlying :meth:`import` has undergone some radical changes over the lastest releases of Python (Particularly versions 3.3-3.5 and next in 3.7).\nIn light of this ApeMan aims to support a minimal set of features; namely explicit and implicit overlays providing patches whose structure matches their intended source packages.\nAny functionality offered beyond these base features is considered sugar e.g. repeated imports, stacked overlays, restructured patches and substructured patches;\n\n.. tested by the :mod:`*Explcit` :mod:`*Implcit` and :mod:`*Init` tests (See :ref:`Testing`).\n\n.. table :: Set of features supported by the Python import system in the different Python implementations\n :align: center\n :widths: auto\n \n ====================== === === === ===\n Python 2.7 3.3 3.4 3.7\n ====================== === === === ===\n explicit packages X X X X \n implicit packages X X X \n lazy loading X \n C implementation X X \n Python implementation X X\n ====================== === === === ===\n\nIt should be noted that Apeman has only been developed and tested in Python 2.7, 3.4 and 3.6.\nThe Python 3.4 implementation was last tested when the author still had it installed, before switching to 3.6. \nThe author also maintains a flaky build of Python 3.5 but this is not a good testing envvironment as a result ApeMan in 3.5 is flaky.\nGenerally speaking if you're using ApeMan in anything other then Python 2.7 or 3.6 you're on your own.\n\n.. table :: Set of features supported ApeMan under the different Python implementations\n :align: center\n :widths: 4,1,1,1,1\n \n ======================= === === === ===\n Python 2.7 3.4 3.5 3.6\n ======================= === === === ===\n explicit packages X X ? X \n implicit packages N/A X ? X \n repeated imports ? ? ? X\n Substructured overlays \n Restructured overlays \n Stacking overlays \n ======================= === === === ===\n\n.. There are tworules for success ...\n.. 1) Never reveal everything that you know\n\n -a command line option \n -b path command line option \n \n \nHaving said that the 2.7 implementation ought to work in Python 2.7-3.3.\nPython 3.4 saw a big overhaul from 3.3 but one did develop ApeMan against this version and, unless one is grossly mistaken, the implementation should still work.\nPython 3.5 included a few leftovers that were forgotten for Python 3.4. \nThe 3.4 implementation ought to work in 3.5 but the current 3.5 implementation diverges from the one for 3.4 and appears broken upon the authors machine.\nThe Python 3.6 implementation is presently the most tested and stable while Python 3.7 has not been attempted just yet.\n\n \n------------------\nLive and Let Die !\n------------------\n\nThis is largely inspired by Portage, the package manager for Gentoo Linux and the tutorial by David Beasley.\nHowever it is only possible through the contributions of Brett Cannon, who ported the Python import machinery from C/C++ to Python. \n\nIn general a big thank you is also made to the developers of Python and all the other third party packages that come withit.\n\n.. .. tikz:: Title\n.. :libs: calc\n.. \n.. \\draw (0,0) circle (3em) circle (4em) circle (5em);\n\n---------\nLicencing\n---------\n\nThis software is licenced under a GPL v3 licence.\nOne requests that anyone hosting a fork of this code inform the author accordingly so that any useful modifications one has made may periodically be merged into the code base.", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "http://www.manaikan.com/", "keywords": "", "license": "GNU Affero General Public License v3 or later (AGPLv3+)", "maintainer": "", "maintainer_email": "", "name": "ApeMan", "package_url": "https://pypi.org/project/ApeMan/", "platform": "any", "project_url": "https://pypi.org/project/ApeMan/", "project_urls": { "Homepage": "http://www.manaikan.com/" }, "release_url": "https://pypi.org/project/ApeMan/0.1.1/", "requires_dist": null, "requires_python": "", "summary": "Overlays for Python", "version": "0.1.1" }, "last_serial": 3993080, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "bf735ebdf142be63cdd8e558d8f5baf3", "sha256": "b5de6d1c5fdeb19870fafbfa9c63bb5f5e4f0cd7c67c4cbb99a20aadae13e7da" }, "downloads": -1, "filename": "ApeMan-0.1.0.zip", "has_sig": false, "md5_digest": "bf735ebdf142be63cdd8e558d8f5baf3", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 1019223, "upload_time": "2018-06-23T14:41:24", "url": "https://files.pythonhosted.org/packages/02/45/742d2658b374584bb2ac8c243bff728534f487ee6e540ac899fbf95815ae/ApeMan-0.1.0.zip" } ], "0.1.1": [ { "comment_text": "", "digests": { "md5": "8d5852e1f6a52eebdd707e1406c1f617", "sha256": "3767b07ed216e5355666b693d7ab6adb50f3df5627377574171d9911e0edabb3" }, "downloads": -1, "filename": "ApeMan-0.1.1.tar.gz", "has_sig": false, "md5_digest": "8d5852e1f6a52eebdd707e1406c1f617", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 897185, "upload_time": "2018-06-23T15:04:34", "url": "https://files.pythonhosted.org/packages/34/d1/e5453846786f355d4135d39a79e05811b4f3c82cc3f80c31a7fac617011b/ApeMan-0.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "8d5852e1f6a52eebdd707e1406c1f617", "sha256": "3767b07ed216e5355666b693d7ab6adb50f3df5627377574171d9911e0edabb3" }, "downloads": -1, "filename": "ApeMan-0.1.1.tar.gz", "has_sig": false, "md5_digest": "8d5852e1f6a52eebdd707e1406c1f617", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 897185, "upload_time": "2018-06-23T15:04:34", "url": "https://files.pythonhosted.org/packages/34/d1/e5453846786f355d4135d39a79e05811b4f3c82cc3f80c31a7fac617011b/ApeMan-0.1.1.tar.gz" } ] }