{ "info": { "author": "Glyph", "author_email": "glyph@twistedmatrix.com", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3" ], "description": "What is this?\n=============\n\nSetting expectations around what APIs you can rely on in a Python\nlibrary is very difficult.\n\nPublication makes it easy.\n\nThe Problem\n-----------\n\nAs `Hyrum's Law `_ somewhat grimly states,\n\n | With a sufficient number of users of an API,\n | it does not matter what you promise in the contract:\n | all observable behaviors of your system\n | will be depended on by somebody.\n\nIn general, Python famously has a somewhat different philosophical view of this\nreality. We assume each other to be `responsible users\n`_ of the libraries\nwe consume. Mucking with implementation details might break every time you\nupgrade, but it's sufficiently useful for testing, debugging, and\nexperimentation that retaining that ability is worth paying the cost.\n\nBut, critical to this assumption is that everybody *knows* when they're\nbreaking into the \"private\" area of the library's interface. Here, there's a\nmismatch of expectations:\n\n- *library authors* write documentation, and then think that users sit down and\n read the documentation, front to back, and learn about what the \"public\"\n interface is by doing so. they then assume that users will know that they've\n used private implementation details if they ever deviate from these\n documented features.\n- *library users* ``pip install`` a thing, open up a REPL, import the module,\n and discover the library by doing ``dir()`` on the module and its contents,\n assuming that their program is not using any private implementation details\n as long as they never had to type ``library._something_private()`` while\n doing so. If they ever encounter a traceback they may consult the\n documentation, briefly, until it is resolved.\n\nPublication makes it possible to align the wildly divergent expectations of\nthese groups, so that users can still get the benefits of being able to use\ninternal details if they want, but they'll know that they're doing so. It\nmakes the runtime namespace of your module look like the public documentation\nof your library.\n\nHow does this look in practice?\n-------------------------------\n\nYou, a prospective library author, want to write a library that makes it easy to zorf a sprocket.\nGreat! You do, and it looks like this:\n\n.. code:: python\n\n # sprocket_zorfer.py\n from sprocket import sprocket_with_name\n from zorf import zorfable_thing\n\n def zorf_sprocket_internal(sprocket, zorfulations):\n ...\n def compute_zorfulations():\n ...\n\n def zorf_sprocket_named(sprocket_name, how_much):\n sprocket = sprocket_with_name(sprocket_name)\n zorfulations = compute_zorfulations(how_much)\n return zorf_sprocket_internal(sprocket, zorfulations)\n\n\n __all__ = [\n 'zorf_sprocket_named'\n ]\n\nYour intent here, of course, is that you have exposed a module with a\nsingle function: ``zorf_sprocket_named``, and everything else is an\nimplementation detail. You even said so, explicitly, with ``__all__``.\nYour API documentation says the same.\n\nHowever, reading reference documentation and cleanly respecting\nconventions is not how working programmers really figure out how to use\nstuff. Your users all do stuff like:\n\n- Load up an interactive ``python`` interpreter and call ``dir()`` on\n your module\n- Install Jupyter and tab-complete their way around your module to find\n what they want\n- use the auto-import function in PyCharm to grab some private\n implementation detail\n\nand, before you know it, you have thousands of users of your library\nwith code like\n\n.. code:: python\n\n from sprocket_zorfer import compute_zorfulations, zorf_sprocket_internal, sprocket_with_name\n\n sprocket = sprocket_with_name(name)\n zorf_sprocket_internal(sprocket, compute_zorfulations(7) * 2)\n\nNow you can never change *any* of your implementation details! Worse\nyet, ``sprocket_with_name`` isn\u2019t even your own code; that\u2019s something\nyou got from a library! But when someone does\n``import sprocket_zorfer; sprocket_zorfer.`` in an interactive\nshell, none of that information comes through.\n\nUnderscore Paranoia\n-------------------\n\nThe convention in Python is that we use ``_`` to indicate private names.\nSo when we library authors notice this problem starting to happen, a\ncommon reaction is to start putting ``_`` in front of *everything* \u2013\nclass names, function names, module names \u2013 and only explicitly export\nthose things that should be public by \u201cmoving\u201d them via an import and an\nentry in a public module\u2019s ``__all__``.\n\nHowever, this has a bunch of disadvantages:\n\n- Most code inspection tooling and IDEs won\u2019t see that the public name\n is \u201cmoved\u201d, so code exploration just makes it seem like *everything*\n is an implementation detail now, rather than making it seem like\n nothing is.\n\n- All your ``__repr__``\\ s now have ugly and inaccurate function and\n class names in them, at least from the perspective of your users; how\n are they supposed to know ``zorf_sprocket_named`` is actually defined\n in ``zorf_sprocket._impl_details.funcs._zorf_sprocket_public`` now?\n How are they supposed to find the good, public name once they\u2019re\n looking at the goofy internal one?\n\n- You constantly need to remember to put *all* of your code in these\n ugly ``_``-prefixed modules, and educate new contributors as to the\n risks of creating new modules in your package that are not carefully\n hidden away from public users.\n\nA Better World\n--------------\n\nWhat if you could write all your code *as if* it were just regular\npublic code, and have all your implementation details and imports\nautomatically squirreled away in an underscore namespace so that curious\ncoders won\u2019t accidentally find every module you ever imported and every\ntemporary helper function you ever defined and think they\u2019re part of the\npermanent public face of your library?\n\nEnter ``publication``.\n\n``publication`` uses the existing convention of ``__all__`` and a little\nruntime hackery to hide everything that you have not marked as\nexplicitly public, like so:\n\n.. code:: python\n\n # sprocket_zorfer.py\n\n from publication import publish\n\n from sprocket import sprocket_with_name\n from zorf import zorfable_thing\n\n def zorf_sprocket_internal(sprocket, zorfulations):\n ...\n def compute_zorfulations():\n ...\n\n def zorf_sprocket_named(sprocket_name, how_much):\n sprocket = sprocket_with_name(sprocket_name)\n zorfulations = compute_zorfulations(how_much)\n return zorf_sprocket_internal(sprocket, zorfulations)\n\n\n __all__ = [\n 'zorf_sprocket_named'\n ]\n\n publish()\n\nThat\u2019s it! Now, ``from sprocket_zorfer import zorf_sprocket_named``\nworks as intended, but\n``from sprocket_zorfer import compute_zorfulations`` is an\n``ImportError``.\n\nBut what about\u2026\n---------------\n\nOther modules in my package, like tests, that need to peek at implementation details?\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nDon\u2019t worry, your code didn\u2019t go anywhere. The original module is still\navailable as a special pseudo-module called ``._private``.\nIn the example above, ``sprocket_zorfer.py``\\ \u2019s tests can still do:\n\n.. code:: python\n\n from sprocket_zorfer._private import compute_zorfulations\n\n def test_compute_zorfulations():\n assert compute_zorfulations(0) > 7\n\nMypy?\n~~~~~\n\nYour types should *probably* just be part of your published API, if\nyou\u2019re expecting that users will need to know about them. But, if there\nare cases which need to be type-checked internally in your library, as\nfar as Mypy is concerned, all your private classes are still there. So,\nin the simple case you can just do this:\n\n.. code:: python\n\n from typing import TYPE_CHECKING\n if TYPE_CHECKING:\n from something import T\n\n def returns_a() -> \"T\":\n ...\n\nand in the hopefully very unusual case you need to mix runtime and\ntype-checking access to a different module\u2019s private details,\n\n.. code:: python\n\n from typing import TYPE_CHECKING\n if TYPE_CHECKING:\n from something import T\n else:\n from something._private import T\n\n def returns_a() -> A:\n ...\n\n", "description_content_type": "text/x-rst", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/glyph/publication", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "publication", "package_url": "https://pypi.org/project/publication/", "platform": "", "project_url": "https://pypi.org/project/publication/", "project_urls": { "Homepage": "https://github.com/glyph/publication" }, "release_url": "https://pypi.org/project/publication/0.0.3/", "requires_dist": null, "requires_python": "", "summary": "Publication helps you maintain public-api-friendly modules by preventing unintentional access to private implementation details via introspection.", "version": "0.0.3" }, "last_serial": 4862144, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "e9f89e0b93c7f6177096e0fd25fdbf83", "sha256": "fa1be80081ab35f9cba38ceee0927d6defd1874502e7658bf745e8fbe9230751" }, "downloads": -1, "filename": "publication-0.0.1-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "e9f89e0b93c7f6177096e0fd25fdbf83", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 6563, "upload_time": "2018-07-09T10:38:58", "url": "https://files.pythonhosted.org/packages/d6/fd/236765098c9641f0ec7d8d560233deb1c0e1eef22b8fb4417749ed81f93d/publication-0.0.1-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "341cc5c8d1381425a9cea203a50161c5", "sha256": "282238362c6b10d78aaa33efc9d8e2e590286e6a46cf91b161b289c03adfc993" }, "downloads": -1, "filename": "publication-0.0.1.tar.gz", "has_sig": false, "md5_digest": "341cc5c8d1381425a9cea203a50161c5", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4458, "upload_time": "2018-07-09T10:38:59", "url": "https://files.pythonhosted.org/packages/49/99/3d52347e4c4dc530743deaffa3e10cb6b14838ec8afc93ae6acc5f835572/publication-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "58dc094394578992ffce325e452d333d", "sha256": "9b4c2a62fda58e9255a8b7d8fd084707a367b5303e597453c221118f55b3f58c" }, "downloads": -1, "filename": "publication-0.0.2-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "58dc094394578992ffce325e452d333d", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 6825, "upload_time": "2018-11-10T00:59:23", "url": "https://files.pythonhosted.org/packages/b6/92/a5d56bc9606d8a29ff057a6167844ea0a0496c6e39153ba81b4e1c8515fd/publication-0.0.2-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "9fa8402980456c2944b0556fe783242d", "sha256": "81cb12e1413b41e2d508d0931b5214f5d3982d4b621a5c6177ca35ca23e7427e" }, "downloads": -1, "filename": "publication-0.0.2.tar.gz", "has_sig": false, "md5_digest": "9fa8402980456c2944b0556fe783242d", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 4562, "upload_time": "2018-11-10T00:59:25", "url": "https://files.pythonhosted.org/packages/ca/54/82efbb38452e60efce4269334144e39aef2ff801c38e3e72373b3a599e2e/publication-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "676c7a5bb6317f9699c4b224fcaf0006", "sha256": "0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6" }, "downloads": -1, "filename": "publication-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "676c7a5bb6317f9699c4b224fcaf0006", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7687, "upload_time": "2019-01-15T07:52:22", "url": "https://files.pythonhosted.org/packages/f8/d3/6308debad7afcdb3ea5f50b4b3d852f41eb566a311fbcb4da23755a28155/publication-0.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "046d10ae8616faefb949e2c9acded223", "sha256": "68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4" }, "downloads": -1, "filename": "publication-0.0.3.tar.gz", "has_sig": false, "md5_digest": "046d10ae8616faefb949e2c9acded223", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5484, "upload_time": "2019-01-15T07:52:23", "url": "https://files.pythonhosted.org/packages/6b/8e/8c9fe7e32fdf9c386f83d59610cc819a25dadb874b5920f2d0ef7d35f46d/publication-0.0.3.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "676c7a5bb6317f9699c4b224fcaf0006", "sha256": "0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6" }, "downloads": -1, "filename": "publication-0.0.3-py2.py3-none-any.whl", "has_sig": false, "md5_digest": "676c7a5bb6317f9699c4b224fcaf0006", "packagetype": "bdist_wheel", "python_version": "py2.py3", "requires_python": null, "size": 7687, "upload_time": "2019-01-15T07:52:22", "url": "https://files.pythonhosted.org/packages/f8/d3/6308debad7afcdb3ea5f50b4b3d852f41eb566a311fbcb4da23755a28155/publication-0.0.3-py2.py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "046d10ae8616faefb949e2c9acded223", "sha256": "68416a0de76dddcdd2930d1c8ef853a743cc96c82416c4e4d3b5d901c6276dc4" }, "downloads": -1, "filename": "publication-0.0.3.tar.gz", "has_sig": false, "md5_digest": "046d10ae8616faefb949e2c9acded223", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 5484, "upload_time": "2019-01-15T07:52:23", "url": "https://files.pythonhosted.org/packages/6b/8e/8c9fe7e32fdf9c386f83d59610cc819a25dadb874b5920f2d0ef7d35f46d/publication-0.0.3.tar.gz" } ] }