[
  {
    "path": ".flake8",
    "content": "[flake8]\nmax-line-length=99\nexclude=.git\nselect=E,W,F,C,N\nignore=\n"
  },
  {
    "path": ".github/workflows/integtests.yaml",
    "content": "name: Integration Tests\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\njobs:\n\n  archlinux:\n    runs-on: ubuntu-latest\n    container:\n      # Use https://github.com/gilgamezh/archlinux-python39 to save the python build time\n      image: gilgamezh/archlinux-python39:latest\n      volumes:\n        - ${{ github.workspace }}:/fades\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Install dependencies\n        run: |\n          pacman -Suy --noconfirm python3 python-packaging\n      - name: Simple fades run\n        run: |\n          cd /fades\n          bin/fades -v -d pytest -x pytest --version\n      - name: Using a different Python\n        run: |\n          python bin/fades -v --python=python3.9 -d pytest -x pytest -v --integtest-pyversion=3.9 tests/integtest.py\n\n  fedora:\n    runs-on: ubuntu-latest\n    container:\n      image: fedora:latest\n      volumes:\n        - ${{ github.workspace }}:/fades\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Install dependencies\n        run: |\n          yum install --assumeyes python3.13 python-packaging\n      - name: Simple fades run\n        run: |\n          cd /fades\n          bin/fades -v -d pytest -x pytest --version\n      - name: Using a different Python\n        run: |\n          yum install --assumeyes python3.9\n          cd /fades\n          python3.13 bin/fades -v --python=python3.9 -d pytest -x pytest -v --integtest-pyversion=3.9 tests/integtest.py\n\n  native-windows:\n    strategy:\n      matrix:\n        # just a selection otherwise it's too much\n        # - latest OS (left here even if it's only one to simplify upgrading later)\n        # - oldest and newest Python\n        os: [windows-2025]\n        python-version: [3.8, \"3.13\"]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}\n        uses: actions/setup-python@v5\n        id: matrixpy\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Also set up Python 3.10 for cross-Python test\n        uses: actions/setup-python@v5\n        id: otherpy\n        with:\n          python-version: \"3.10\"\n\n      - name: Install dependencies\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} -m pip install -U packaging\n      - name: Simple fades run\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} bin/fades -v -d pytest -x pytest --version\n\n      - name: Using a different Python\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} bin/fades -v --python=${{ steps.otherpy.outputs.python-path }} -d pytest -x pytest -v --integtest-pyversion=3.10 tests/integtest.py\n\n  native-generic:\n    strategy:\n      matrix:\n        # just a selection otherwise it's too much\n        # - latest OSes\n        # - oldest and newest Python\n        os: [ubuntu-24.04, macos-15]\n        python-version: [3.8, \"3.13\"]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}\n        uses: actions/setup-python@v5\n        id: matrixpy\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Also set up Python 3.10 for cross-Python test\n        uses: actions/setup-python@v5\n        with:\n          python-version: \"3.10\"\n\n      - name: Install dependencies\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} -m pip install -U packaging\n      - name: Simple fades run\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} bin/fades -v -d pytest -x pytest --version\n\n      - name: Using a different Python\n        run: |\n          ${{ steps.matrixpy.outputs.python-path }} bin/fades -v --python=python3.10 -d pytest -x pytest -v --integtest-pyversion=3.10 tests/integtest.py\n"
  },
  {
    "path": ".github/workflows/tests.yaml",
    "content": "name: Tests\n\non:\n  push:\n    branches: [ master ]\n  pull_request:\n    branches: [ master ]\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\njobs:\n  run-tests:\n    strategy:\n      matrix:\n        os: [ubuntu-22.04, ubuntu-24.04, macos-14, macos-15, windows-2022, windows-2025]\n        python-version: [3.8, 3.9, \"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n\n    runs-on: ${{ matrix.os }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}\n        uses: actions/setup-python@v4\n        with:\n          python-version: ${{ matrix.python-version }}\n      - name: Install dependencies\n        run: |\n          pip install -U -r requirements.txt\n      - name: Run tests\n        run: |\n          pytest\n"
  },
  {
    "path": ".gitignore",
    "content": "# Generated as a side effect of development\nREADME.html\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n.mypy_cache/\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.cache\nnosetests.xml\ncoverage.xml\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# archlinux\n!pkg/archlinux/PKGBUILD\npkg/archlinux/*\n\n# Ides\n.vscode/\n.idea/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file for Sphinx projects\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n---\nversion: 2\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.12\"\nsphinx:\n  configuration: docs/conf.py\npython:\n  install:\n    - requirements: docs/requirements.txt\n"
  },
  {
    "path": "AUTHORS",
    "content": "Alejandro Dau <alejandro@dau.com.ar>\nAndrés Delfino <adelfino@gmail.com>\nAriel Rossanigo <arielrossanigo@gmail.com>\nBerenice Larsen Pereyra <berelarsenp@gmail.com>\nDavid Litvak Bruno <david.litvakb@gmail.com>\nDiego Duncan <diegoduncan21@gmail.com>\nDiego Mascialino <dmascialino@gmail.com>\nEduardo Enriquez <eduardo.a.enriquez@gmail.com>\nFacundo Batista <facundo@taniquetil.com.ar>\nFaQ <facundofc@gmail.com>\nFilipe Ximenes <filipeximenes@gmail.com>\ngera <gera@satellogic.com>\njairot <jairotrad@gmail.com>\nJavier Andres Mansilla <jmansilla@machinalis.com>\nJuan <juan.carizza@gmail.com>\nJuan Carlos <juancarlospaco@gmail.com>\nLucio Torre <lucio@satellogic.com>\nManuel Kaufmann <humitos@gmail.com>\nMartin Alderete <malderete@gmail.com>\nmatuu <matu.varela@gmail.com>\nNicolás Demarchi <mail@gilgamezh.me>\nRicardo Kirkner <ricardo@kirkner.com.ar>\nRushil Patel <rushil.patel20@imperial.ac.uk>"
  },
  {
    "path": "COPYING",
    "content": "\n\t\t    GNU GENERAL PUBLIC LICENSE\n\t\t       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\t\t\t    Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t\t       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n \n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n  \n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n\t\t     END OF TERMS AND CONDITIONS\n\n\t    How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n\n"
  },
  {
    "path": "HOWTO_RELEASE.txt",
    "content": "Steps before a release is done\n------------------------------\n\nCheck all is crispy\n\n  rm -rf build dist\n  ./setup.py clean build\n  ./setup.py clean sdist\n\n\nEdit the ``fades/_version.py`` file properly, then tag and commit/push\n\n  git tag release-VERSION\n  git commit -am \"Release VERSION\"\n  git push --tag\n\n\nHow to release it to PyPI\n-------------------------\n\nDead simple:\n\n  rm -rf build dist\n  ./setup.py clean sdist\n  fades -d twine -x twine upload dist/fades-*\n\n\nHow to create a .deb\n--------------------\n\nCreate the tarball:\n\n  rm -rf build dist\n  ./setup.py clean sdist\n\n\nCopy this tarball to a clean dir, renaming as \"orig\"\n\n  mkdir /tmp/fades_pack\n  cp dist/fades-X.Y.tar.gz /tmp/fades_pack/fades_X.Y.orig.tar.gz\n  cd /tmp/fades_pack\n\n\nMost of next instructions come from http://wiki.debian.org/Python/GitPackaging\n\n  tar -xf fades_X.Y.orig.tar.gz\n  cd fades-X.Y\n  git init\n  git add .\n  git commit -m \"import fades_X.Y.orig.tar.gz\"\n  git checkout -b upstream\n  pristine-tar commit ../fades_X.Y.orig.tar.gz upstream\n  git-dpm init ../fades_X.Y.orig.tar.gz\n\n\nCopy the project's debian dir and change changelog (be sure that this\n\"debian\" dir is properly updated in the project... notably, be sure\ncopyright year is current one and also that no new dependencies were\nintroduced since last release).\n\n  cp -pr $DEVEL/fades/pkg/debian .\n  dch   # doing the following:\n    - version should be   (X.Y-1) unstable\n    - just leave one \"* Initial release.\"\n\n\nContinue with preparations:\n\n  git add debian/*\n  git commit -m \"Added debian dir.\"\n  git-dpm prepare\n  git-dpm status\n\n\nBuild the .deb\n\n  debuild -us -uc -I -i\n\n\nTo test the .deb you just created:\n\n  sudo dpkg -i *.deb\n\n\nIf you want to uninstall it do:\n\n    sudo dpkg -r fades\n\n\nHow to release it to Debian\n---------------------------\n\nNeed just to report a bug very similar to this one:  https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=814913\n\nFor that, just run the following and answer the questions:\n\n    reportbug -B debian\n\n(I had to do that twice, not sure why: first time it asked some questions and then errored out, second time it picked up from there and finish the job)\n\n\nHow to release it to Arch\n-------------------------\n\n\nChanges should be applied in the AUR repo ``ssh://aur@aur.archlinux.org/fades.git``\n\nEdit ``pkg/archlinux/PKGBUILD`` and set *pkgver* and *sha256sums*, then run\n``makepkg --printsrcinfo > .SRCINFO`` to update the .SRCINFO file.\n\nFinally commit and push the changes. The package will be automatically updated in AUR.\n\n``https://aur.archlinux.org/packages/fades``\n\n\nHow to release it to the Snap store\n-----------------------------------\n\nEdit the snapcraft yaml and change the \"version\" number\n\n    vi pkg/snap/snapcraft.yaml\n\nBuild the snap (no need to specify the YAML, there is a hidden symlink to it):\n\n    snapcraft\n\nPush it to the store and release:\n\n    snapcraft push fades_6.0_amd64.snap\n    snapcraft release fades <revno just pushed> edge beta\n\nTest it:\n\n    sudo snap install fades --channel=beta --classic\n    /snap/fades/current/bin/fades -V\n\nIf all is fine, release it to candidate:\n\n    snapcraft release fades <revno just pushed> candidate\n\nAnnounce internally: telegram, fades mail list.\n\nWait 3 days or so, and release to stable:\n\n    snapcraft release fades <revno just pushed> stable\n\nAnnounce in the snapcraft forum, something similar to:\n\n    https://forum.snapcraft.io/t/call-for-testing-fades-7/5070\n\n\nRead the Docs\n-------------\n\n- go and login there, go to \"fades\" project\n- in \"Versions\" tab, activate the new version (corresponding to the release in github)\n- in \"Administrator\" tab, go to option \"Advanced configuration\" at the left, choose latest release as \"default version\"\n- verify all latest is seen in \n\n    http://fades.rtfd.org/ \n\n\nHow to sign the files\n---------------------\n\nIf you are putting files to download (notably, installators: .deb,\ntarballs, etc) it's a good idea to sign them and offer checksums, in\ncase of somebody wanting to validate the files.\n\nTo sign it:\n\n    gpg --armor --sign --detach-sig FILENAME\n\nTo create the checksum:\n\n    sha1sum FILENAME > FILENAME.sha1\n\n\nFinal steps\n-----------\n\n- Remember to update the .deb and .tar.gz in www.taniquetil.com.ar/fades\n\n- Create a change log and send press releases:\n    - a tweet,\n    - PyAr mail list, IRC and Telegram group\n    - fades' Telegram group and mail list\n    - python-announces\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    {one line to give the program's name and a brief idea of what it does.}\n    Copyright (C) {year}  {name of author}\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <http://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    {project}  Copyright (C) {year}  {fullname}\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<http://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<http://www.gnu.org/philosophy/why-not-lgpl.html>.\n\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include README.rst\ninclude COPYING\ninclude AUTHORS\ninclude man/fades.1\n"
  },
  {
    "path": "README.rst",
    "content": "What is fades?\n==============\n\n\n.. image:: https://github.com/PyAr/fades/actions/workflows/test.uaml/badge.svg\n    :target: https://github.com/PyAr/fades/actions/workflows/test.uaml/badge.svg\n.. image:: https://readthedocs.org/projects/fades/badge/?version=latest\n    :target: http://fades.readthedocs.org/en/latest/?badge=latest\n    :alt: Documentation Status\n.. image:: https://badge.fury.io/py/fades.svg\n    :target: https://badge.fury.io/py/fades\n.. image:: https://coveralls.io/repos/PyAr/fades/badge.svg?branch=master&service=github\n    :target: https://coveralls.io/github/PyAr/fades?branch=master\n.. image:: https://build.snapcraft.io/badge/PyAr/fades.svg\n    :target: https://build.snapcraft.io/user/PyAr/fades\n    :alt: Snap Status\n.. image:: https://ci.appveyor.com/api/projects/status/crkqv82t1l731fms/branch/master?svg=true\n    :target: https://ci.appveyor.com/project/facundobatista/fades\n    :alt: Appveyor Status\n\n\nfades is a system that automatically handles the virtual environments in the\ncases normally found when writing scripts and simple programs, and\neven helps to administer big projects.\n\n.. image:: resources/logo256.png\n\n*fades* will automagically create a new virtual environment (or reuse a previous\ncreated one), installing the necessary dependencies, and execute\nyour script inside that virtual environment, with the only requirement\nof executing the script with *fades* and also marking the required\ndependencies.\n\n*(If you don't have a clue why this is necessary or useful, I'd recommend you\nto read this small text about* `Python and the Management of Dependencies\n<https://github.com/PyAr/fades/blob/master/docs/pydepmanag.rst>`_ *.)*\n\nThe first non-option parameter (if any) would be then the child program\nto execute, and any other parameters after that are passed as is to that\nchild script.\n\n*fades* can also be executed without passing a child script to execute:\nin this mode it will open a Python interactive interpreter inside the\ncreated/reused virtual environment (taking dependencies from ``--dependency`` or\n``--requirement`` options).\n\n.. contents::\n\n\nHow to use it?\n==============\n\nClick in the following image to see a video/screencast that shows most of\nfades features in just 5'...\n\n.. image:: resources/video/screenshot.png\n    :target: https://www.youtube.com/watch?v=BCTd_TyCm98\n\n...or inspect `these several small GIFs <resources/gifs/gifs.rst>`_ that\nshow each a particular `fades` functionality, but please keep also reading\nfor more detailed information...\n\n\nYes, please, I want to read\n---------------------------\n\nWhen you write an script, you have to take two special measures:\n\n- need to execute it with *fades* (not *python*)\n\n- need to mark those dependencies\n\nAt the moment you execute the script, fades will search a\nvirtual environment with the marked dependencies, if it doesn't exists\nfades will create it, and execute the script in that environment.\n\n\nHow to execute the script with fades?\n-------------------------------------\n\nYou can always call your script directly with fades::\n\n    fades myscript.py\n\nHowever, for you to not forget about fades and to not execute it\ndirectly with python, it's better if you put at the beggining of\nthe script the indication for the operating system that it should\nbe executed with fades... ::\n\n    #!/usr/bin/env fades\n\n...and also set the executable bit in the script::\n\n    chmod +x yourscript.py\n\nYou can also execute scripts directly from the web, passing directly the\nURL of the pastebin where the script is pasted (most common pastebines are\nsupported, pastebin.com, gist, linkode.org, but also it's supported if\nthe URL points to the script directly)::\n\n    fades http://myserver.com/myscript.py\n\n\nHow to mark the dependencies to be installed?\n---------------------------------------------\n\nThe procedure to mark a module imported by the script as a *dependency\nto be installed by fades* is by using a comment.\n\nThis comment will normally be in the same line of the import (recommended,\nless confusing and less error prone in the future), but it also can be in\nthe previous one.\n\nThe simplest comment is like::\n\n    import somemodule   # fades\n    from somepackage import othermodule    # fades\n\nThe ``fades`` is mandatory, in this examples the repository is PyPI,\nsee `About different repositories`_ below for other examples.\n\nWith that comment, *fades* will install automatically in the virtual environment the\n``somemodule`` or ``somepackage`` from PyPI.\n\nAlso, you can indicate a particular version condition, examples::\n\n    import somemodule   # fades == 3\n    import somemodule   # fades >= 2.1\n    import somemodule   # fades >=2.1,<2.8,!=2.6.5\n\nSometimes, the project itself doesn't match the name of the module; in\nthese cases you can specify the project name (optionally, before the\nversion)::\n\n    import bs4   # fades beautifulsoup4\n    import bs4   # fades beautifulsoup4 == 4.2\n\n\nWhat if no script is given to execute?\n--------------------------------------\n\nIf no script or program is passed to execute, *fades* will provide a virtual environment \nwith all the indicated dependencies, and then open an interactive interpreter \nin the context of that virtual environment.\n\nHere is where it comes very handy the ``-i/--ipython`` option, if that REPL\nis preferred over the standard one.\n\nIn the case of using an interactive interpreter, it's also very useful to\nmake *fades* to automatically import all the indicated dependencies, \npassing the ``--autoimport`` parameter.\n\n\nOther ways to specify dependencies\n----------------------------------\n\nApart of marking the imports in the source file, there are other ways\nto tell *fades* which dependencies to install in the virtual environment.\n\nOne way is through command line, passing the ``--dependency`` parameter.\nThis option can be specified multiple times (once per dependency), and\neach time the format is ``repository::dependency``. The dependency may\nhave versions specifications, and the repository is optional (defaults\nto 'pypi').\n\nAnother way is to specify the dependencies in a text file, one dependency\nper line, with each line having the format previously described for\nthe ``--dependency`` parameter. This file is then indicated to fades\nthrough the ``--requirement`` parameter. This option can be specified\nmultiple times.\n\nIn case of multiple definitions of the same dependency, command line\noverrides everything else, and requirements file overrides what is\nspecified in the source code.\n\nFinally, you can include package names in the script docstring, after\na line where \"fades\" is written, until the end of the docstring;\nfor example::\n\n    \"\"\"Script to do stuff.\n\n    It's a very important script.\n\n    We need some dependencies to run ok, installed by fades:\n        request\n        otherpackage\n    \"\"\"\n\n\nAbout different repositories\n----------------------------\n\n*fades* supports installing the required dependencies from multiples repositories: besides PyPI, you can specify URLs that can point to projects from GitHub, Launchpad, etc. (basically, everything that is supported by ``pip`` itself).\n\nWhen a dependency is specified, *fades* deduces the proper repository. For example, in the following examples *fades* will install requests from the latest revision from PyPI in the first case, and in the second case the latest revision from the project itself from GitHub::\n\n    -d requests\n    -d git+https://github.com/kennethreitz/requests.git#egg=requests\n\nIf you prefer, you can be explicit about which kind of repository *fades* should use, prefixing the dependency with the special token double colon (``::``)::\n\n    -d pypi::requests\n    -d vcs::git+https://github.com/kennethreitz/requests.git#egg=requests\n\nThere are two basic repositories: ``pypi`` which will make *fades* to install the desired dependency from PyPI, and ``vcs``, which will make *fades* to treat the dependency as a URL for a version control system site. In the first case, for PyPI, a full range of version comparators can be specified, as usual. For ``vcs`` repositories, though, the comparison is always exact: if the very same dependency is specified, a *virtual environment* is reused, otherwise a new one will be created and populated.\n\nIn both cases (specifying the repository explicitly or implicitly) there is no difference if the dependency is specified in the command line, in a ``requirements.txt`` file, in the script's docstring, etc.  In the case of marking the ``import`` directly in the script, it slightly different.\n\nWhen marking the ``import`` it normally happens that the package itself to be installed has the name of the imported module, and because of that it can only be found in PyPI. So, in the following cases the ``pypi`` repository is not only deduced, but unavoidable::\n\n    import requests  # fades\n    from foo import bar  # fades\n    import requests  # fades <= 3\n\nBut if the package is specified (normally needed because it's different than the module name), or if a version control system URL is specified, the same possibilities stated above are available: let *fades* to deduce the proper repository or mark it explicitly::\n\n    import bs4  # fades beautifulsoup\n    import bs4  # fades pypi::beautifulsoup\n    import requests  # fades git+https://github.com/kennethreitz/requests.git#egg=requests\n    import requests  # fades vcs::git+https://github.com/kennethreitz/requests.git#egg=requests\n\nOne last detail about the ``vcs`` repository: the format to write the URLs is the same (as it's passed without modifications) than what ``pip`` itself supports (see `pip docs <https://pip.readthedocs.io/en/stable/reference/pip_install/#vcs-support>`_ for more details).\n\nFurthermore, you can install from local projects. It's just fine to use a\ndependency that starts with ``file:``. E.g. (please note the triple slash,\nbecause we're mixing the protocol indication with the path)::\n\n    fades -d file:///home/crazyuser/myproject/allstars/\n\n\nHow to control the virtual environment creation and usage?\n----------------------------------------------------------\n\nYou can influence several details of all the virtual environment related process.\n\nThe most important detail is which version of Python will be used in\nthe virtual environment. Of course, the corresponding version of Python needs to\nbe installed in your system, but you can control exactly which one to use.\n\nNo matter which way you're executing the script (see above), you can\npass a ``-p`` or ``--python`` argument, indicating the Python version to\nbe used just with the number (``3.9``), the whole name (``python3.9``) or\nthe whole path (``/usr/bin/python3.9``).\n\nOther detail is the verbosity of *fades* when telling what is doing. By\ndefault, *fades* only will use stderr to tell if a virtual environment is being\ncreated, and to let the user know that is doing an operation that\nrequires an active network connection (e.g. installing a new dependency).\n\nIf you call *fades* with ``-v`` or ``--verbose``, it will send all internal\ndebugging lines to stderr, which may be very useful if any problem arises.\nOn the other hand if you pass the ``-q`` or ``--quiet`` parameter, *fades*\nwill not show anything (unless it has a real problem), so the original\nscript stderr is not polluted at all.\n\nIf you want to use IPython shell you need to call *fades* with ``-i`` or\n``--ipython`` option. This option will add IPython as a dependency to *fades*\nand it will launch this shell instead of the python one.\n\nYou can also use ``--system-site-packages`` to create a venv with access to\nthe system libs.\n\nFinally, no matter how the virtual environment was created, you can always get the\nbase directory of the virtual environment in your system using the ``--where`` (or its\nalias ``--get-venv-dir``) option.\n\n\nRunning programs in the context of the virtual environment\n----------------------------------------------------------\n\nThe ``-x/--exec`` parameter allows you to execute any program (not just\na Python one) in the context of the virtual environment.\n\nBy default the mandatory given argument is considered the executable \nname, relative to the environment's ``bin`` directory, so this is \nspecially useful to execute installed scripts/program by the declared \ndependencies. E.g.::\n\n    fades -d flake8 -x flake8 my_script_to_be_verified_by_flake8.py\n\nTake in consideration that you can pass an absolute path and it will be \nrespected (but not a relative path, as it will depend of the virtual environment\nlocation). \n\nFor example, if you want to run a shell script that in turn runs a Python\nprogram that needs to be executed in the context of the virtual environment, you \ncan do the following::\n\n    fades -r requirements.txt --exec /var/lib/foobar/special.sh\n\nFinally, if the intended code to run is prepared to be executed as a module \n(what you would normally run as `python3 -m some_module`), you can \nuse the same parameter with *fades* to run that module inside the virtual environment::\n\n    fades -r requirements.txt -m some_module\n\n\nHow to deal with packages that are upgraded in PyPI\n---------------------------------------------------\n\nWhen you tell *fades* to create a virtual environment using one dependency and\ndon't specify a version, it will install the latest one from PyPI.\n\nFor example, you do ``fades -d foobar`` and it installs foobar in\nversion 7. At some point, there is a new version of foobar in PyPI,\nversion 8, but if do ``fades -d foobar`` it will just reuse previously\ncreated virtual environment, with version 7, not downloading the new version and\ncreating a new virtual environment with it!\n\nYou can tell fades to do otherwise, just do::\n\n    fades -d foobar --check-updates\n\n...and *fades* will search updates for the package on PyPI, and as it will\nfound version 8, will create a new virtual environment using the latest version. You\ncan also use the ``-U`` option as an alias for ``--check-updates``::\n    \n    fades -d foobar -U\n\nFrom this moment on, if you request ``fades -d foobar`` it will bring the\nvirtual environment with the new version. If you want to get a virtual environment with\nnot-the-latest version for any dependency, just specify the proper versions.\n\nYou can even use the ``--check-updates`` parameter when specifying the package\nversion. Say you call ``fades -d foobar==7``, *fades* will install version 7 no\nmatter which one is the latest. But if you do::\n\n    fades -d foobar==7 --check-updates\n\n...it will still use version 7, but will inform you that a new version\nis available!\n\n\nWhat about pinning dependencies?\n--------------------------------\n\nOne nice benefit of *fades* is that every time dependencies change in your \nproject, you actually get to use a new virtual environment automatically.\n\nIf you don't pin the dependencies in your requirements file, this has \nanother nice side effect: everytime you use them in a new environment (or\nif you have `--check-updates` set) you will get latest versions, effectively\navoiding the trap of sticking in old versions forever.\n\nHowever, this has a bad side. If it happens that a dependency of your \nproject released a revision between the moment you run the tests and the \nmoment your project is deployed to the server, it may happen that you \nactually put in production an untested combination. Furthermore, it may \nhappen that even if you do pin your dependencies, the dependencies of \nthose dependencies may not be pinned, and you get into the same situation.\n\nFor example, you may have the ``requests == 2.19.1`` dependency, but\n``requests`` declares its own dependencies, for example\n``chardet >= 3.0.2``, and when running tests locally you may get ``chardet``\nin version ``3.0.3``, but nothing guarantees you that when deploying your\nproject to a server (effectively building everything from scratch) you will \nnot get a newer version of ``chardet``, which may be totally fine but in fact\nit's something that you did NOT test locally.\n\nHere is where *fades* comes to the rescue with the ``--freeze`` option. If \nthis parameter is given, *fades* will operate exactly as it normally would,\nbut also will dump the result of ``pip freeze`` into the specified file.\n\nSo to continue with the example above, you could run your tests like::\n\n    fades -d \"requests == 2.19.1\" --freeze=reqs-frozen.txt -x python3 -m unittest\n\n...which will leave you ``reqs-frozen.txt`` with a content similar to::\n\n    certifi==2018.4.16\n    chardet==3.0.4\n    pip==18.0\n    requests==2.19.1\n    ...\n\nAnd then you could use *that file* for deployment, which has *all packages*\npinned, so you will get exactly what you was expecting.\n\n\nUnder the hood options\n----------------------\n\nFor particular use cases you can send specifics arguments to the ``venv`` module, ``pip`` and ``python`` itself, using the ``--venv-options``, ``--pip-options`` and ``--python-options`` modifiers respectively. You have to use that argument for each argument sent.\n\nExamples:\n\n``fades -d requests --venv-options=\"--symlinks\"``\n\n``fades -d requests --pip-options=\"--index-url='http://example.com'\"``\n\n``fades --python-options=-B foo.py``\n\n\nSetting options using config files\n----------------------------------\n\nYou can also configure fades using `.ini` config files. fades will search config files in\n`/etc/fades/fades.ini`, the path indicated by `xdg` for your system\n(for example `~/config/fades/fades.ini`) and `.fades.ini`.\n\nSo you can have different settings at system, user and project level.\n\nWith fades installed you can get your config dir running::\n\n    python -c \"from fades.helpers import get_confdir; print(get_confdir())\"\n\n\nThe config files are in `.ini` format. (configparser) and fades will search for a `[fades]` section.\n\nYou have to use the same configurations that in the CLI. The only difference is with the config\noptions with a dash, it has to be replaced with a underscore.::\n\n    [fades]\n    ipython=true\n    verbose=true\n    python=python3\n    check_updates=true\n    dependency=requests;django>=1.8  # separated by semicolon\n\nThere is a little difference in how fades handle these settings: \"dependency\", \"pip-options\" and\n\"venv-options\". In these cases you have to use a semicolon separated list.\n\nThe most important thing is that these options will be merged. So if you configure in\n`/etc/fades/fades.ini` \"dependency=requests\" you will have requests in all the virtual environments\ncreated by fades.\n\n\nHow to clean up old virtual environments?\n-----------------------------------------\n\nWhen using *fades* virtual environments are something you should not have to think about.\n*fades* will do the right thing and create a new virtual environment that matches the required\ndependencies. There are cases however when you'll want to do some clean up to remove\nunnecessary virtual environments from disk.\n\nBy running *fades* with the ``--rm`` argument, *fades* will remove the\nvirtual environment matching the provided UUID if such environment exists (one easy\nway to find out the environment's UUID is calling *fades* with the\n``--where`` option).\n\nAnother way to clean up the cache is to remove all venvs that haven't been used for some time.\nIn order to do this you need to call *fades* with ``--clean-unused-venvs``.\nWhen fades it's called with this option, it runs in mantain mode, this means that fades will exit\nafter finished this task.\n\nAll virtual environments that haven't been used for more days than the value indicated in param will be\nremoved.\n\nIt is recommended to have some automatically way of run this option;\nie, add a cron task that perform this command::\n\n    fades --clean-unused-venvs=42\n\n\nSome command line examples\n--------------------------\n\nExecute ``foo.py`` under *fades*, passing the ``--bar`` parameter to the child program, in a virtual environment with the dependencies indicated in the source code::\n\n    fades foo.py --bar\n\nExecute ``foo.py`` under *fades*, showing all the *fades* messages (verbose mode)::\n\n    fades -v foo.py\n\nExecute ``foo.py`` under *fades* (passing the ``--bar`` parameter to it), in a virtual environment with the dependencies indicated in the source code and also ``dependency1`` and ``dependency2`` (any version > 3.2)::\n\n    fades -d dependency1 -d \"dependency2>3.2\" foo.py --bar\n\nExecute the Python interactive interpreter in a virtual environment with ``dependency1`` installed::\n\n    fades -d dependency1\n\nExecute the Python interactive interpreter in a virtual environment after installing there all dependencies taken from the ``requirements.txt`` file::\n\n    fades -r requirements.txt\n\nExecute the Python interactive interpreter in a virtual environment after installing there all dependencies taken from files ``requirements.txt`` and ``requirements_devel.txt``::\n\n    fades -r requirements.txt -r requirements_devel.txt\n\nUse the ``django-admin.py`` script to start a new project named ``foo``, without having to have django previously installed::\n\n    fades -d django -x django-admin.py startproject foo\n\nRemove a virtual environment matching the given uuid from disk and cache index::\n\n    fades --rm 89a2bf83-c280-4918-a78d-c35506efd69d\n\nDownload the script from the given pastebin and executes it (previously building a virtual environment for the dependencies indicated in that pastebin, of course)::\n\n    fades http://linkode.org/#4QI4TrPlGf1gK2V7jPBC47\n\nRun all the tests in a project (running ``pytest`` directly as a module, for better behaviour) and at the same time freeze dependencies for later deployment::\n\n    fades -r requirements.txt --freeze -m pytest -v\n\n\nSome examples using fades in project scripts\n--------------------------------------------\n\nIncluding *fades* in project helper scripts makes it easy to stop \nworrying about the virtual environment activation/deactivation when working \nin that project, and also solves the problem of needing to \nupdate/change/fix an already created virtual environment if the \ndependencies change.\n\nThis is an example of how a script to run your project may look like::\n\n    #!/bin/sh\n    if (command -v fades > /dev/null)\n    then\n        # fades FTW!\n        fades -r requirements.txt bin/start\n    else\n        echo 2\n        # hope you are in the correct virtual environment\n        python3 bin/start\n    fi\n\nTo run the tests, it's super handy to have a script that also takes care\nof the development dependencies::\n\n    #!/bin/sh\n    fades -r requirements.txt -r reqs-dev.txt -x python -m pytest -s \"$@\"\n\n\nWhat if Python is updated in my system?\n---------------------------------------\n\nThe virtual environments created by fades depend on the Python version used to\ncreate them, considering its major and minor version.\n\nThis means that if run fades with a Python version and then run it again\nwith a different Python version, it may need to create a new virtual environment.\n\nLet's see some examples. Let's say you run fades with ``python``, which\nis a symlink in your ``/usr/bin/`` to ``python3.6`` (running it directly\nby hand or because fades is installed to use that Python version).\n\nIf you have Python 3.6.2 installed in your system, and it's upgraded to\nPython 3.6.3, fades will keep reusing the already created virtual environments, as\nonly the micro version changed, not minor or major.\n\nBut if Python 3.7 is installed in your system, and the default ``python``\nis pointed to this new one, fades will start creating all the\nvirtual environments again, with this new version.\n\nThis is a good thing, because you want that the dependencies installed\nwith one specific Python in the virtual environment are kept being used by the\nsame Python version.\n\nHowever, if you want to avoid this behaviour, be sure to always call fades\nwith the specific Python version (``/usr/bin/python3.6`` or ``python3.6``,\nfor example), so it won't matter if a new version is available in the\nsystem.\n\n\nHow to install it\n=================\n\nSeveral instructions to install ``fades`` in different platforms.\n\nSimplest way\n------------\n\nIn some systems you can install ``fades`` directly, no needing to\ninstall previously any dependency.\n\nIf you are in debian unstable or testing, just do:\n\n    sudo apt-get install fades\n\nFor Arch Linux, you can install it from the **AUR** using any `AUR helper <https://wiki.archlinux.org/index.php/AUR_helpers>`_, e.g. with ``pikaur``:\n\n    pikaur -S fades\n\nIn systems with Snaps:\n\n    snap install fades --classic\n\n(why `--classic`? Because it's the only way that `fades` could, from\ninside the snap, access the rest of the system in case you want to\nuse a different Python version, or a dependency that needs\ncompilation, etc).\n\nFor Mac OS X (and `Homebrew <http://brew.sh/>`_):\n\n    brew install fades\n\nElse, keep reading to know how to install the dependencies first, and\n``fades`` in your system next.\n\n\nDependencies\n------------\n\nBesides needing Python 3.6 or greater, fades depends on the ``python-xdg`` package. This package should be installed on any GNU/Linux OS wiht a freedesktop.org GUI. However it is an **optional** dependency.\n\nYou can install it in Ubuntu/Debian with::\n\n    apt-get install python3-xdg\n\nAnd on Arch Linux with::\n\n    pacman -S python-xdg\n\n\nFor others debian and ubuntu\n----------------------------\n\nIf you are NOT in debian unstable or testing (if you are, see\nabove for better instructions), you can use this\n`.deb <http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1-2_all.deb>`_.\n\nDownload it and install doing::\n\n    sudo dpkg -i fades_*.deb\n\n\nUsing pip if you want\n----------------------\n::\n\n    pip3 install fades\n\n\nMultiplatform tarball\n---------------------\n\nFinally you can always get the multiplatform tarball and install\nit in the old fashion way::\n\n    wget http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1.orig.tar.gz\n    tar -xf fades_*.tar.gz\n    cd fades-*\n    sudo ./setup.py install\n\n\nCan I try it without installing it?\n-----------------------------------\n\nYes! Branch the project and use the executable::\n\n    git clone https://github.com/PyAr/fades.git\n    cd fades\n    bin/fades your_script.py\n\n\nWhat about Windows?\n-------------------\n\nWindows is a platform supported by fades.\n\nHowever, we don't have a proper Windows installer (a ``.exe`` or\n``.msi``), but you can install it using ``pip``, or from the tarball,\nor try it directly from the project. All these options are properly\ndescribed above.\n\nWe *do* want to have a Windows installer. If you can help us in this\nregard, please contact us. Also we would want a Travis running in\nWindows so that GitHub runs all the tests in this platform too before\nlanding any code. Thanks!\n\n\nGet some help, give some feedback\n=================================\n\nYou can ask any question or send any recommendation or request to\nthe `mailing list <http://listas.python.org.ar/mailman/listinfo/fades>`_.\n\nCome chat with us on IRC. The #fades channel is located at the `Freenode <http://freenode.net/>`_ network.\n\nAlso, you can open an issue\n`here <https://github.com/PyAr/fades/issues/new>`_ (please do if you\nfind any problem!).\n\nThanks in advance for your time.\n\n\nHow to develop fades itself\n===========================\n\nQuick guide to get you up and running in fades development.\n\n\nGetting the code\n----------------\n\nClone the project::\n\n    git clone git@github.com:PyAr/fades.git\n\n\nInstall dependencies\n--------------------\n\n*fades* manages it's own dependencies, so there is nothing extra you need to install.\n\nTo try it, just do::\n\n    bin/fades -V\n\n\nHow to run the tests\n--------------------\n\nWhen starting development, at all times, and specially before wrapping up\na new branch, you need to be sure that all tests pass ok.\n\nThis is very simple, actually, just run::\n\n    ./test\n\nThat will not only check test cases, but also that the code complies with\naesthetic recommendations, and that the README document has a proper format.\n\nIf you want to run *one* particular test, just specify it. Example::\n\n    ./test tests.test_main:DepsMergingTestCase.test_two_different\n\n\nDevelopment process\n-------------------\n\nJust pick an issue from `the list <https://github.com/PyAr/fades/issues>`_.\n\nDevelop, assure ``./test`` is happy, commit, push, create a pull request, etc.\n\nPlease, if you aim for creating a Pull Request with new code (functionality\nor fixes), include tests for your changes.\n\nThanks! Enjoy.\n"
  },
  {
    "path": "bin/fades",
    "content": "#!/usr/bin/env python3\n\n#\n# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Script to run the 'fades' utility.\"\"\"\n\nimport os\nimport sys\n\ntry:\n    import packaging\nexcept ImportError:\n    print(\"Import failed for `packaging` dependency. Please do `pip3 install packaging` and try again\")\n    exit(-1)\n\n# small hack to allow fades to be run directly from the project, using code\n# from project itself, not anything already installed in the system\nparent_dir = os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0])))\nif os.path.basename(parent_dir).startswith('fades'):\n    # inside the project or an opened tarball!!\n    sys.path.insert(0, parent_dir)\n\nfrom fades import main, FadesError  # noqa (imports after fixing the path, not at the top)\n\ntry:\n    rc = main.go()\nexcept FadesError:\n    sys.exit(-1)\n\nsys.exit(rc)\n"
  },
  {
    "path": "bin/fades.cmd",
    "content": "::\n:: Copyright 2018 Facundo Batista, Nicolás Demarchi\n::\n:: This program is free software: you can redistribute it and/or modify it\n:: under the terms of the GNU General Public License version 3, as published\n:: by the Free Software Foundation.\n::\n:: This program is distributed in the hope that it will be useful, but\n:: WITHOUT ANY WARRANTY; without even the implied warranties of\n:: MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n:: PURPOSE.  See the GNU General Public License for more details.\n::\n:: You should have received a copy of the GNU General Public License along\n:: with this program.  If not, see <http://www.gnu.org/licenses/>.\n::\n:: For further info, check  https://github.com/PyAr/fades\n\n:: Script to run the 'fades' utility in Windows\n\npython -m fades %*\n"
  },
  {
    "path": "build_readme",
    "content": "FADES='./bin/fades -r requirements.txt'\npython3 setup.py --long-description | $FADES -x rst2html5 > README.html\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext\n\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\nclean:\n\trm -rf $(BUILDDIR)/*\n\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/fades.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/fades.qhc\"\n\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/fades\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/fades\"\n\t@echo \"# devhelp\"\n\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/conf.py",
    "content": "# Configuration file for the Sphinx documentation builder.\n#\n# For the full list of built-in configuration values, see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Project information -----------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information\n\nproject = 'fades'\ncopyright = '2024, Facundo Batista, Nicolás Demarchi'\nauthor = 'Facundo Batista, Nicolás Demarchi'\n\n# -- General configuration ---------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration\n\nextensions = [\n    'sphinx_rtd_theme',\n]\n\ntemplates_path = ['_templates']\nexclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']\n\n# -- Options for HTML output -------------------------------------------------\n# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output\n\nhtml_theme = 'sphinx_rtd_theme'\nhtml_static_path = ['_static']\n"
  },
  {
    "path": "docs/index.rst",
    "content": ".. fades documentation master file, created by\n   sphinx-quickstart on Sat Dec 26 19:29:13 2015.\n   You can adapt this file completely to your liking, but it should at least\n   contain the root `toctree` directive.\n\nWelcome to fades's documentation!\n=================================\n\n\nContents:\n--------- \n\n.. toctree::\n   :maxdepth: 2\n\n   readme \n   development \n\n\n\nIndices and tables\n==================\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n\n"
  },
  {
    "path": "docs/pydepmanag.rst",
    "content": "Python and the Management of Dependencies\n=========================================\n\nPython has an extensive standard library (\"batteries included!\"), but is\nfrequent the necessity of using other modules not included there, mostly\nfrom the Python Package Index (`PyPI <https://pypi.python.org/pypi>`_).\n\nThe original way of installing those modules is at \"system level\"\n(`sudo pip install foobar`), in the operating system in a general way,\nmaking them available to be used by any program that is executed.\n\nBeyond needing root or administrator level to install the dependencies in\nthat way, the first problem we find are conflicts: the typical case of two\nprograms needing two different versions of the same dependency, which can\nnot be achieved when installing the dependencies globally.\n\nThis is why is so normal in Python to use \"virtual environments\" (or\n\"virtualenvs\"). A new virtualenv is created for each program, the needed\ndependencies for each program are installed in the corresponding virtualenv,\nand as stuff in a virtualenv is only accessible from inside the virtualenv,\nthere are no conflicts anymore.\n\nAt this point, however, we hit the problem of the administration of the\nvirtualenvs themselves: create them, install modules in them, activate them\nto be uses by each program and deactivate them later, remember the names of\neach environment for each program, etc.\n\nTo automatize this, `fades <https://fades.readthedocs.org/>`_ was born.\n\n*fades* allows you to unleash all the power of virtualenvs without needing\nto worry about them.\n\nDo you want to execute a script that needs the ``foobar`` dependency?\n``fades -d foobar script.py``\n\nDo you want an interactive interpreter having ``foobar`` installed as\ndependency? ``fades -d foobar``\n\nDo you need to execute the script but with several dependencies, one with\na specific version? ``fades -d foo -d bar -d baz==1.1 script.py``\n\nDo you have all the dependencies in a requirements file?\n``fades -r requirements.txt script.py``\n\nThese are only simple examples of what you can do with *fades*. Virtual\nenvironments are a very powerful tool, and automate and simplify their\nuse makes *fades* to have a lot of options, some that you will use\neveryday, others that will prove useful in some specific situations.\n\nStart to use *fades* step by step (`check the docs\n<https://fades.readthedocs.org/en/latest/readme.html>`_) and will find\nthat it will solve the dependencies management in your programs and\nscripts, using virtualenvs but without the complexity of having to deal\nwith them by hand.\n"
  },
  {
    "path": "docs/readme.rst",
    "content": ".. include:: ../README.rst\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "sphinx_rtd_theme\n"
  },
  {
    "path": "fades/__init__.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General\n# Public License version 3, as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.\n# See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.\n# If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Main package.\"\"\"\n\nfrom ._version import __version__, VERSION  # NOQA; provides module level version attr\n\n\nclass FadesError(Exception):\n    \"\"\"Provides a Fades exception.\"\"\"\n\n\nREPO_PYPI = 'pypi'\nREPO_VCS = 'vcs'\n"
  },
  {
    "path": "fades/__main__.py",
    "content": "\"\"\"Init file to allow execution of fades as a module.\"\"\"\n\nimport sys\n\nfrom fades import main, FadesError\n\ntry:\n    rc = main.go()\nexcept FadesError:\n    sys.exit(-1)\n\nsys.exit(rc)\n"
  },
  {
    "path": "fades/_version.py",
    "content": "\"\"\"Holder of the fades version number.\"\"\"\n\nVERSION = (9, 0, 2)\n__version__ = '.'.join([str(x) for x in VERSION])\n"
  },
  {
    "path": "fades/cache.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General\n# Public License version 3, as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.\n# See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.\n# If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"The cache manager for virtualenvs.\"\"\"\n\nimport json\nimport logging\nimport os\nimport time\n\nfrom fades import REPO_VCS\nfrom fades.multiplatform import filelock\nfrom fades.parsing import VCSDependency, NameVerDependency\n\nlogger = logging.getLogger(__name__)\n\n\nclass VEnvsCache:\n    \"\"\"A cache for virtualenvs.\"\"\"\n\n    def __init__(self, filepath):\n        \"\"\"Init.\"\"\"\n        logger.debug(\"Using cache index: %r\", filepath)\n        self.filepath = filepath\n        self.lockpath = filepath + \".lock\"\n\n    def _venv_match(self, installed, requirements):\n        \"\"\"Return True if what is installed satisfies the requirements.\n\n        This method has multiple exit-points, but only for None (because\n        if *anything* is not satisified, the venv is no good). Only after\n        all was checked, and it didn't exit, the venv is ok and it so\n        returns the satisfying dependencies.\n        \"\"\"\n        if not requirements:\n            # special case for no requirements, where we can't actually\n            # check anything: the venv is useful if nothing installed too\n            return None if installed else []\n\n        satisfying_deps = []\n        for repo, req_deps in requirements.items():\n            useful_inst = set()\n            if repo not in installed:\n                # the venv doesn't even have the repo\n                return None\n\n            if repo == REPO_VCS:\n                inst_namevers = {(url, None) for url in installed[repo].keys()}\n            else:\n                inst_namevers = {(dep, ver) for (dep, ver) in installed[repo].items()}\n\n            for req in req_deps:\n                for inst_name, inst_ver in inst_namevers:\n                    if req.name == inst_name and req.specifier.contains(inst_ver):\n                        useful_inst.add((inst_name, inst_ver))\n                        break\n                else:\n                    # nothing installed satisfied that requirement\n                    return None\n\n            # assure *all* that is installed is useful for the requirements\n            if useful_inst == inst_namevers:\n                inst_reqs = set()\n                for name, ver in inst_namevers:\n                    if ver is None:\n                        inst_reqs.add(VCSDependency(name))\n                    else:\n                        inst_reqs.add(NameVerDependency(name, ver))\n                satisfying_deps.extend(inst_reqs)\n            else:\n                return None\n\n        # it did it through!\n        return satisfying_deps\n\n    def _match_by_uuid(self, current_venvs, uuid):\n        \"\"\"Select a venv matching exactly by uuid.\"\"\"\n        for venv_str in current_venvs:\n            venv = json.loads(venv_str)\n            env_path = venv.get('metadata', {}).get('env_path')\n            _, env_uuid = os.path.split(env_path)\n            if env_uuid == uuid:\n                return venv\n\n    def _select_better_fit(self, matching_venvs):\n        \"\"\"Receive a list of matching venvs, and decide which one is the best fit.\"\"\"\n        # keep the venvs in a separate array, to pick up the winner, and the (sorted, to compare\n        # each dependency with its equivalent) in other structure to later compare\n        venvs = []\n        to_compare = []\n        for matching, venv in matching_venvs:\n            to_compare.append(sorted(matching, key=lambda req: getattr(req, 'key', '')))\n            venvs.append(venv)\n\n        # compare each n-tuple of dependencies to see which one is bigger, and add score to the\n        # position of the winner\n        scores = [0] * len(venvs)\n        for dependencies in zip(*to_compare):\n            if not isinstance(dependencies[0], NameVerDependency):\n                # only distribution URLs can be compared\n                continue\n\n            winner = dependencies.index(max(dependencies))\n            scores[winner] = scores[winner] + 1\n\n        # get the rightmost winner (in case of ties, to select the latest venv)\n        winner_pos = None\n        winner_score = -1\n        for i, score in enumerate(scores):\n            if score >= winner_score:\n                winner_score = score\n                winner_pos = i\n        return venvs[winner_pos]\n\n    def _match_by_requirements(self, current_venvs, requirements, interpreter, options):\n        \"\"\"Select a venv matching interpreter and options, complying with requirements.\n\n        Several venvs can be found in this case, will return the better fit.\n        \"\"\"\n        matching_venvs = []\n        for venv_str in current_venvs:\n            venv = json.loads(venv_str)\n\n            # simple filter, need to have exactly same options and interpreter\n            if venv.get('options') != options or venv.get('interpreter') != interpreter:\n                continue\n\n            # requirements complying: result can be None (no comply) or a score to later sort\n            matching = self._venv_match(venv['installed'], requirements)\n            if matching is not None:\n                matching_venvs.append((matching, venv))\n\n        if not matching_venvs:\n            return\n\n        return self._select_better_fit(matching_venvs)\n\n    def _select(self, current_venvs, requirements=None, interpreter='', uuid='', options=None):\n        \"\"\"Select which venv satisfy the received requirements.\"\"\"\n        if uuid:\n            logger.debug(\"Searching a venv by uuid: %s\", uuid)\n            venv = self._match_by_uuid(current_venvs, uuid)\n        else:\n            logger.debug(\"Searching a venv for: reqs=%s interpreter=%s options=%s\",\n                         requirements, interpreter, options)\n            venv = self._match_by_requirements(current_venvs, requirements, interpreter, options)\n\n        if venv is None:\n            logger.debug(\"No matching venv found :(\")\n            return\n\n        logger.debug(\"Found a matching venv! %s\", venv)\n        return venv['metadata']\n\n    def get_venv(self, requirements=None, interpreter='', uuid='', options=None):\n        \"\"\"Find a venv that serves these requirements, if any.\"\"\"\n        lines = self._read_cache()\n        return self._select(lines, requirements, interpreter, uuid=uuid, options=options)\n\n    def get_venvs_metadata(self):\n        \"\"\"Yield metadata of each existing venv.\"\"\"\n        for line in self._read_cache():\n            yield json.loads(line)['metadata']\n\n    def store(self, installed_stuff, metadata, interpreter, options):\n        \"\"\"Store the virtualenv metadata for the indicated installed_stuff.\"\"\"\n        new_content = {\n            'timestamp': int(time.mktime(time.localtime())),\n            'installed': installed_stuff,\n            'metadata': metadata,\n            'interpreter': interpreter,\n            'options': options\n        }\n        logger.debug(\"Storing installed=%s metadata=%s interpreter=%s options=%s\",\n                     installed_stuff, metadata, interpreter, options)\n        with filelock(self.lockpath):\n            self._write_cache([json.dumps(new_content)], append=True)\n\n    def remove(self, env_path):\n        \"\"\"Remove metadata for a given virtualenv from cache.\"\"\"\n        with filelock(self.lockpath):\n            cache = self._read_cache()\n            logger.debug(\"Removing virtualenv from cache: %s\" % env_path)\n            lines = [\n                line for line in cache\n                if json.loads(line).get('metadata', {}).get('env_path') != env_path\n            ]\n            self._write_cache(lines)\n\n    def _read_cache(self):\n        \"\"\"Read virtualenv metadata from cache.\"\"\"\n        if os.path.exists(self.filepath):\n            with open(self.filepath, 'rt', encoding='utf8') as fh:\n                lines = [x.strip() for x in fh]\n        else:\n            logger.debug(\"Index not found, starting empty\")\n            lines = []\n        return lines\n\n    def _write_cache(self, lines, append=False):\n        \"\"\"Write virtualenv metadata to cache.\"\"\"\n        mode = 'at' if append else 'wt'\n        with open(self.filepath, mode, encoding='utf8') as fh:\n            fh.writelines(line + '\\n' for line in lines)\n"
  },
  {
    "path": "fades/envbuilder.py",
    "content": "# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tools to create, destroy and handle usage of virtual environments.\"\"\"\n\nimport logging\nimport os\nimport pathlib\nimport shutil\n\nfrom datetime import datetime, timezone\nfrom venv import EnvBuilder\nfrom uuid import uuid4\n\nfrom fades import FadesError, REPO_PYPI, REPO_VCS\nfrom fades import helpers\nfrom fades.pipmanager import PipManager\nfrom fades.multiplatform import filelock\n\nlogger = logging.getLogger(__name__)\n\n# UTC can be imported directly from datetime from Python 3.11\nUTC = timezone.utc\n\n\nclass _FadesEnvBuilder(EnvBuilder):\n    \"\"\"Create always a virtual environment.\n\n    This is structured as a class mostly to take advantage of EnvBuilder, not because\n    it's provides the best interface: external callers should just use module's ``create_env``\n    and ``destroy_env``.\n    \"\"\"\n\n    def __init__(self):\n        basedir = helpers.get_basedir()\n        self.env_path = os.path.join(basedir, str(uuid4()))\n        self.env_bin_path = ''\n        logger.debug(\"Env will be created at: %s\", self.env_path)\n\n        if os.environ.get(\"SNAP\"):\n            # running inside a snap: we need to avoid EnvBuilder ending up running ensurepip\n            # because it doesn't work properly (it does a special magic to run the script\n            # and ends up mixing external and internal pips)\n            self.pip_installed = False\n\n        else:\n            # try to install pip using default machinery (which will work in a lot\n            # of systems, noticeably it won't in some debians or ubuntus, like\n            # Trusty; in that cases mark it to install manually later)\n            try:\n                import ensurepip  # NOQA\n                self.pip_installed = True\n            except ImportError:\n                self.pip_installed = False\n\n        super().__init__(with_pip=self.pip_installed, symlinks=True)\n\n    def create_with_external_venv(self, interpreter, options):\n        \"\"\"Create a virtual environment using the venv module externally.\"\"\"\n        args = [interpreter, \"-m\", \"venv\", self.env_path]\n        args.extend(options)\n        if not self.pip_installed:\n            args.insert(3, '--without-pip')\n\n        try:\n            helpers.logged_exec(args)\n        except helpers.ExecutionError as error:\n            error.dump_to_log(logger)\n            raise FadesError(\"Failed to run venv module externally\")\n        except Exception as error:\n            logger.exception(\"Error creating virtual environment:  %s\", error)\n            raise FadesError(\"General error while running external venv\")\n\n        # XXX Facundo 2024-06-29: the helper uses pathlib; eventually everything will be\n        # pathlib (see #435), so these translations will be cleaned up\n        self.env_bin_path = str(helpers.get_env_bin_path(pathlib.Path(self.env_path)))\n\n    def create_env(self, interpreter, is_current, options):\n        \"\"\"Create the virtual environment and return its info.\"\"\"\n        venv_options = options['venv_options']\n        if is_current:\n            # apply venv options\n            logger.debug(\"Creating virtual environment internally; options=%s\", venv_options)\n            for option in venv_options:\n                attrname = option[2:].replace(\"-\", \"_\")  # '--system-packgs' ->  'system_packgs'\n                setattr(self, attrname, True)\n            self.create(self.env_path)\n        else:\n            logger.debug(\n                \"Creating virtual environment with external venv; options=%s\", venv_options)\n            self.create_with_external_venv(interpreter, venv_options)\n        logger.debug(\"env_bin_path: %s\", self.env_bin_path)\n\n        # Re check if pip was installed (supporting both binary and .exe for Windows)\n        pip_bin = os.path.join(self.env_bin_path, \"pip\")\n        pip_exe = os.path.join(self.env_bin_path, \"pip.exe\")\n        if not (os.path.exists(pip_bin) or os.path.exists(pip_exe)):\n            logger.debug(\"pip isn't installed in the venv, setting pip_installed=False\")\n            self.pip_installed = False\n\n        return self.env_path, self.env_bin_path, self.pip_installed\n\n    def post_setup(self, context):\n        \"\"\"Get the bin path from context.\"\"\"\n        self.env_bin_path = context.bin_path\n\n\ndef create_venv(requested_deps, interpreter, is_current, options, pip_options, avoid_pip_upgrade):\n    \"\"\"Create a new virtualvenv with the requirements of this script.\"\"\"\n    # create virtual environment\n    env = _FadesEnvBuilder()\n    env_path, env_bin_path, pip_installed = env.create_env(interpreter, is_current, options)\n    venv_data = {}\n    venv_data['env_path'] = env_path\n    venv_data['env_bin_path'] = env_bin_path\n    venv_data['pip_installed'] = pip_installed\n\n    # install deps\n    installed = {}\n    for repo in requested_deps.keys():\n        if repo in (REPO_PYPI, REPO_VCS):\n            mgr = PipManager(\n                env_bin_path, pip_installed=pip_installed, options=pip_options,\n                avoid_pip_upgrade=avoid_pip_upgrade)\n        else:\n            logger.warning(\"Install from %r not implemented\", repo)\n            continue\n        installed[repo] = {}\n\n        repo_requested = requested_deps[repo]\n        logger.debug(\"Installing dependencies for repo %r: requested=%s\", repo, repo_requested)\n        for dependency in repo_requested:\n            try:\n                mgr.install(dependency)\n            except Exception:\n                logger.debug(\"Installation Step failed, removing virtual environment\")\n                destroy_venv(env_path)\n                raise FadesError('Dependency installation failed')\n\n            if repo == REPO_VCS:\n                # no need to request the installed version, as we'll always compare\n                # to the url itself\n                project = dependency.url\n                version = None\n            else:\n                # always store the installed dependency, as in the future we'll select the venv\n                # based on what is installed, not what used requested (remember that user may\n                # request >, >=, etc!)\n                project = dependency.name\n                version = mgr.get_version(project)\n            installed[repo][project] = version\n\n        logger.debug(\"Installed dependencies: %s\", installed)\n    return venv_data, installed\n\n\ndef destroy_venv(env_path, venvscache=None):\n    \"\"\"Destroy a venv.\"\"\"\n    # remove the venv itself in disk\n    logger.debug(\"Destroying virtual environment at: %s\", env_path)\n    shutil.rmtree(env_path, ignore_errors=True)\n\n    # remove venv from cache\n    if venvscache is not None:\n        venvscache.remove(env_path)\n\n\nclass UsageManager:\n    \"\"\"Class to handle usage file and venv cleanning.\"\"\"\n\n    def __init__(self, stat_file_path, venvscache):\n        \"\"\"Init.\"\"\"\n        self.stat_file_path = stat_file_path\n        self.stat_file_lock = stat_file_path + '.lock'\n        self.venvscache = venvscache\n        self._create_initial_usage_file_if_not_exists()\n\n    def store_usage_stat(self, venv_data, cache):\n        \"\"\"Log an usage record for venv_data.\"\"\"\n        with open(self.stat_file_path, 'at') as f:\n            self._write_venv_usage(f, venv_data)\n\n    def _create_initial_usage_file_if_not_exists(self):\n        if not os.path.exists(self.stat_file_path):\n            existing_venvs = self.venvscache.get_venvs_metadata()\n            with open(self.stat_file_path, 'wt') as f:\n                for venv_data in existing_venvs:\n                    self._write_venv_usage(f, venv_data)\n\n    def _write_venv_usage(self, file_, venv_data):\n        _, uuid = os.path.split(venv_data['env_path'])\n        file_.write('{} {}\\n'.format(uuid, self._datetime_to_str(datetime.now(UTC))))\n\n    def _datetime_to_str(self, datetime_):\n        return datetime.strftime(datetime_, \"%Y-%m-%dT%H:%M:%S.%f\")\n\n    def _str_to_datetime(self, str_):\n        return datetime.strptime(str_, \"%Y-%m-%dT%H:%M:%S.%f\")\n\n    def clean_unused_venvs(self, max_days_to_keep):\n        \"\"\"Compact usage stats and remove venvs.\n\n        This method loads the complete file usage in memory, for every venv compact all records in\n        one (the lastest), updates this info for every env deleted and, finally, write the entire\n        file to disk.\n\n        If something failed during this steps, usage file remains unchanged and can contain some\n        data about some deleted env. This is not a problem, the next time this function it's\n        called, this records will be deleted.\n        \"\"\"\n        with filelock(self.stat_file_lock):\n            now = datetime.now(UTC)\n            venvs_dict = self._get_compacted_dict_usage_from_file()\n            for venv_uuid, usage_date in venvs_dict.copy().items():\n                usage_date = self._str_to_datetime(usage_date)\n                if (now - usage_date).days > max_days_to_keep:\n                    # remove venv from usage dict\n                    del venvs_dict[venv_uuid]\n                    venv_meta = self.venvscache.get_venv(uuid=venv_uuid)\n                    if venv_meta is None:\n                        # if meta isn't found means that something had failed previously and\n                        # usage_file wasn't updated.\n                        continue\n                    env_path = venv_meta['env_path']\n                    logger.info(\"Destroying virtual environment at: %s\", env_path)\n                    destroy_venv(env_path, self.venvscache)\n\n            self._write_compacted_dict_usage_to_file(venvs_dict)\n\n    def _get_compacted_dict_usage_from_file(self):\n        all_lines = open(self.stat_file_path).readlines()\n        return dict(x.split() for x in all_lines)\n\n    def _write_compacted_dict_usage_to_file(self, dict_usage):\n        with open(self.stat_file_path, 'wt') as file_:\n            for uuid, date in dict_usage.items():\n                file_.write('{} {}\\n'.format(uuid, date))\n"
  },
  {
    "path": "fades/file_options.py",
    "content": "# Copyright 2016-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Parse fades options from config files.\"\"\"\n\nimport logging\nimport os\n\nfrom configparser import ConfigParser, NoSectionError\n\nfrom fades.helpers import get_confdir\n\nlogger = logging.getLogger(__name__)\n\nCONFIG_FILES = (\"/etc/fades/fades.ini\", os.path.join(get_confdir(), 'fades.ini'), \".fades.ini\")\n\nMERGEABLE_CONFIGS = (\"dependency\", \"pip_options\", \"venv-options\")\n\n\ndef options_from_file(args):\n    \"\"\"Get a argparse.Namespace and return it updated with options from config files.\n\n    Config files will be parsed with priority equal to his order in CONFIG_FILES.\n    \"\"\"\n    logger.debug(\"updating options from config files\")\n    updated_from_file = []\n    for config_file in CONFIG_FILES:\n        logger.debug(\"updating from: %s\", config_file)\n        parser = ConfigParser()\n        parser.read(config_file)\n        try:\n            items = parser.items('fades')\n        except NoSectionError:\n            continue\n\n        for config_key, config_value in items:\n            if config_value in ['true', 'false']:\n                config_value = config_value == 'true'\n            if config_key in MERGEABLE_CONFIGS:\n                current_value = getattr(args, config_key, [])\n                if current_value is None:\n                    current_value = []\n                current_value.append(config_value)\n                setattr(args, config_key, current_value)\n            if not getattr(args, config_key, False) or config_key in updated_from_file:\n                # By default all 'store-true' arguments are False. So we only\n                # override them if they are False. If they are True means that the\n                # user is setting those on the CLI.\n                setattr(args, config_key, config_value)\n                updated_from_file.append(config_key)\n                logger.debug(\"updating %s to %s from file settings\", config_key, config_value)\n\n    return args\n"
  },
  {
    "path": "fades/helpers.py",
    "content": "# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"A collection of utilities for fades.\"\"\"\n\nimport os\nimport sys\nimport json\nimport logging\nimport subprocess\nimport tempfile\nfrom http.server import HTTPStatus\nfrom urllib import request, parse\nfrom urllib.error import HTTPError\n\nfrom packaging.requirements import Requirement\nfrom packaging.version import Version\n\nfrom fades import FadesError, _version\n\nlogger = logging.getLogger(__name__)\n\n# command to retrieve the version from an external Python\nSHOW_VERSION_CMD = \"\"\"\nimport sys, json\nd = dict(path=sys.executable)\nd.update(zip('major minor micro releaselevel serial'.split(), sys.version_info))\nprint(json.dumps(d))\n\"\"\"\n\n# the url to query PyPI for project versions\nBASE_PYPI_URL = 'https://pypi.org/pypi/{name}/json'\nBASE_PYPI_URL_WITH_VERSION = 'https://pypi.org/pypi/{name}/{version}/json'\n\n# prefix for all stdout lines when running a command\nSTDOUT_LOG_PREFIX = \":: \"\n\n# env var name provided by snappy where process can read/write; this path already includes\n# 'fades' in it, it's a different dir for each user, and accessable by different versions of fades\nSNAP_BASEDIR_NAME = 'SNAP_USER_COMMON'\n\n\nclass ExecutionError(Exception):\n    \"\"\"Execution of subprocess ended not in 0.\"\"\"\n\n    def __init__(self, retcode, cmd, collected_stdout):\n        \"\"\"Init.\"\"\"\n        self._retcode = retcode\n        self._cmd = cmd\n        self._collected_stdout = collected_stdout\n        super().__init__()\n\n    def dump_to_log(self, logger):\n        \"\"\"Send the cmd info and collected stdout to logger.\"\"\"\n        logger.error(\"Execution ended in %s for cmd %s\", self._retcode, self._cmd)\n        for line in self._collected_stdout:\n            logger.error(STDOUT_LOG_PREFIX + line)\n\n\ndef logged_exec(cmd):\n    \"\"\"Execute a command, redirecting the output to the log.\"\"\"\n    logger = logging.getLogger('fades.exec')\n    logger.debug(\"Executing external command: %r\", cmd)\n    p = subprocess.Popen(\n        cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)\n    stdout = []\n    for line in p.stdout:\n        line = line[:-1]\n        stdout.append(line)\n        logger.debug(STDOUT_LOG_PREFIX + line)\n    retcode = p.wait()\n    if retcode:\n        raise ExecutionError(retcode, cmd, stdout)\n    return stdout\n\n\ndef _get_basedirectory():\n    from xdg import BaseDirectory\n    return BaseDirectory\n\n\ndef _get_specific_dir(dir_type):\n    \"\"\"Get a specific directory, using some XDG base, with sensible default.\"\"\"\n    if SNAP_BASEDIR_NAME in os.environ:\n        logger.debug(\"Getting base dir information from SNAP_BASEDIR_NAME env var.\")\n        direct = os.path.join(os.environ[SNAP_BASEDIR_NAME], dir_type)\n    else:\n        try:\n            basedirectory = _get_basedirectory()\n        except ImportError:\n            logger.debug(\"Using last resort base dir: ~/.fades\")\n            from os.path import expanduser\n            direct = os.path.join(expanduser(\"~\"), \".fades\")\n        else:\n            xdg_attrib = 'xdg_{}_home'.format(dir_type)\n            base = getattr(basedirectory, xdg_attrib)\n            direct = os.path.join(base, 'fades')\n\n    if not os.path.exists(direct):\n        os.makedirs(direct)\n    return direct\n\n\ndef get_basedir():\n    \"\"\"Get the base fades directory, from xdg or kinda hardcoded.\"\"\"\n    return _get_specific_dir('data')\n\n\ndef get_confdir():\n    \"\"\"Get the config fades directory, from xdg or kinda hardcoded.\"\"\"\n    return _get_specific_dir('config')\n\n\ndef _get_interpreter_info(interpreter=None):\n    \"\"\"Return the interpreter's full path using pythonX.Y format.\"\"\"\n    if interpreter is None:\n        # If interpreter is None by default returns the current interpreter data.\n        major, minor = sys.version_info[:2]\n        executable = sys.executable\n    else:\n        args = [interpreter, '-c', SHOW_VERSION_CMD]\n        try:\n            requested_interpreter_info = logged_exec(args)\n        except Exception as error:\n            logger.error(\"Error getting requested interpreter version: %s\", error)\n            raise FadesError(\"Could not get interpreter version\")\n        requested_interpreter_info = json.loads(requested_interpreter_info[0])\n        executable = requested_interpreter_info['path']\n        major = requested_interpreter_info['major']\n        minor = requested_interpreter_info['minor']\n    if executable[-1].isdigit():\n        executable = executable.split(\".\")[0][:-1]\n    interpreter = \"{}{}.{}\".format(executable, major, minor)\n    return interpreter\n\n\ndef get_interpreter_version(requested_interpreter):\n    \"\"\"Return a 'sanitized' interpreter and indicates if it is the current one.\"\"\"\n    logger.debug('Getting interpreter version for: %s', requested_interpreter)\n    current_interpreter = _get_interpreter_info()\n    logger.debug('Current interpreter is %s', current_interpreter)\n    if requested_interpreter is None:\n        return (current_interpreter, True)\n\n    requested_interpreter = _get_interpreter_info(requested_interpreter)\n    is_current = requested_interpreter == current_interpreter\n    logger.debug('Interpreter=%s. It is the same as fades?=%s',\n                 requested_interpreter, is_current)\n    return (requested_interpreter, is_current)\n\n\ndef get_latest_version_number(project_name):\n    \"\"\"Return latest version of a package.\"\"\"\n    try:\n        raw = request.urlopen(BASE_PYPI_URL.format(name=project_name)).read()\n    except HTTPError as error:\n        logger.warning(\"Network error. Error: %s\", error)\n        raise error\n    try:\n        data = json.loads(raw.decode(\"utf8\"))\n        latest_version = data[\"info\"][\"version\"]\n        return latest_version\n    except (KeyError, ValueError) as error:  # malformed json or empty string\n        logger.error(\"Could not get the version of the package. Error: %s\", error)\n        raise error\n\n\ndef check_pypi_updates(dependencies):\n    \"\"\"Return a list of dependencies to upgrade.\"\"\"\n    dependencies_up_to_date = []\n    for dependency in dependencies.get('pypi', []):\n        # get latest version from PyPI api\n        try:\n            latest_version = Version(get_latest_version_number(dependency.name))\n        except Exception as error:\n            logger.warning(\"--check-updates command will be aborted. Error: %s\", error)\n            return dependencies\n        # get required version\n\n        if dependency.specifier:\n            spec = list(dependency.specifier)[0]\n            required_version = Version(spec.version)\n\n            dependencies_up_to_date.append(dependency)\n            if latest_version > required_version:\n                logger.info(\"There is a new version of %s: %s\",\n                            dependency.name, latest_version)\n            elif latest_version < required_version:\n                logger.warning(\"The requested version for %s is greater \"\n                               \"than latest found in PyPI: %s\",\n                               dependency.name, latest_version)\n            else:\n                logger.info(\"The requested version for %s is the latest one in PyPI: %s\",\n                            dependency.name, latest_version)\n        else:\n            name_plus = \"{}=={}\".format(dependency.name, latest_version)\n            dependencies_up_to_date.append(Requirement(name_plus))\n            logger.info(\"The latest version of %r is %s and will use it.\",\n                        dependency.name, latest_version)\n\n    dependencies[\"pypi\"] = dependencies_up_to_date\n    return dependencies\n\n\ndef _pypi_head_package(dependency):\n    \"\"\"Hit pypi with a http HEAD to check if pkg_name exists.\"\"\"\n    if dependency.specifier:\n        spec = list(dependency.specifier)[0]\n        version = spec.version\n        url = BASE_PYPI_URL_WITH_VERSION.format(name=dependency.name, version=version)\n    else:\n        url = BASE_PYPI_URL.format(name=dependency.name)\n    logger.debug(\"Doing HEAD requests against %s\", url)\n    req = request.Request(url, method='HEAD')\n    try:\n        response = request.urlopen(req)\n    except HTTPError as http_error:\n        if http_error.code == HTTPStatus.NOT_FOUND:\n            return False\n        else:\n            raise\n    if response.status == HTTPStatus.OK:\n        logger.debug(\"%r exists in PyPI.\", dependency)\n    else:\n        # Maybe we are getting somethink like a redirect. In this case we are only\n        # warning to the user and trying to install the dependency.\n        # In the worst scenery fades will fail to install it.\n        logger.warning(\"Got a (unexpected) HTTP_STATUS=%r and reason=%r checking if %r exists\",\n                       response.status, response.reason, dependency)\n    return True\n\n\ndef check_pypi_exists(dependencies):\n    \"\"\"Check if the indicated dependencies actually exists in pypi.\"\"\"\n    for dependency in dependencies.get('pypi', []):\n        logger.debug(\"Checking if %r exists in PyPI\", dependency)\n        try:\n            exists = _pypi_head_package(dependency)\n        except Exception as error:\n            logger.error(\"Error checking %s in PyPI: %r\", dependency, error)\n            raise FadesError(\"Could not check if dependency exists in PyPI\")\n        else:\n            if not exists:\n                logger.error(\"%s doesn't exists in PyPI.\", dependency)\n                return False\n    return True\n\n\nclass _ScriptDownloader:\n    \"\"\"Grouping of different backends downloaders.\"\"\"\n\n    # a user-agent for hitting the network\n    USER_AGENT = \"fades/{} (https://github.com/PyAr/fades/)\".format(_version.__version__)\n    HEADERS_PLAIN = {\n        'Accept': 'text/plain',\n        'User-Agent': USER_AGENT,\n    }\n    HEADERS_JSON = {\n        'Accept': 'application/json',\n        'User-Agent': USER_AGENT,\n    }\n\n    # simple network locations to name map\n    NETLOCS = {\n        'linkode.org': 'linkode',\n        'pastebin.com': 'pastebin',\n        'gist.github.com': 'gist',\n    }\n\n    def __init__(self, url):\n        \"\"\"Init.\"\"\"\n        self.url = url\n        self.name = self._decide()\n\n    def _decide(self):\n        \"\"\"Find out which method should be applied to download that URL.\"\"\"\n        netloc = parse.urlparse(self.url).netloc\n        name = self.NETLOCS.get(netloc, 'raw')\n        return name\n\n    def get(self):\n        \"\"\"Get the script content from the URL using the decided downloader.\"\"\"\n        method_name = \"_download_\" + self.name\n        method = getattr(self, method_name)\n        return method()\n\n    def _download_raw(self, url=None):\n        \"\"\"Download content from URL directly.\"\"\"\n        if url is None:\n            url = self.url\n        req = request.Request(url, headers=self.HEADERS_PLAIN)\n        resp = request.urlopen(req)\n\n        # check if the response url is different than the original one; in this case we had\n        # redirected, and we need to pass the new url response through the proper\n        # pastebin-dependant adapter, so recursively go into another _ScriptDownloader\n        if resp.geturl() != url:\n            new_url = resp.geturl()\n            downloader = _ScriptDownloader(new_url)\n            logger.info(\n                \"Download redirect detect, now downloading from %r using %r downloader\",\n                new_url, downloader.name)\n            return downloader.get()\n\n        # simple non-redirect response\n        return resp.read().decode(\"utf8\")\n\n    def _download_linkode(self):\n        \"\"\"Download content from Linkode pastebin.\"\"\"\n        # build the API url\n        linkode_id = self.url.split(\"/\")[-1]\n        if linkode_id.startswith(\"#\"):\n            linkode_id = linkode_id[1:]\n        url = \"https://linkode.org/api/1/linkodes/\" + linkode_id\n\n        req = request.Request(url, headers=self.HEADERS_JSON)\n        resp = request.urlopen(req)\n        raw = resp.read()\n        data = json.loads(raw.decode(\"utf8\"))\n        content = data['content']\n        return content\n\n    def _download_pastebin(self):\n        \"\"\"Download content from Pastebin itself.\"\"\"\n        paste_id = self.url.split(\"/\")[-1]\n        url = \"https://pastebin.com/raw/\" + paste_id\n        return self._download_raw(url)\n\n    def _download_gist(self):\n        \"\"\"Download content from github's pastebin.\"\"\"\n        parts = parse.urlparse(self.url)\n        url = \"https://gist.github.com\" + parts.path + \"/raw\"\n        return self._download_raw(url)\n\n\ndef download_remote_script(url):\n    \"\"\"Download the content of a remote script to a local temp file.\"\"\"\n    temp_fh = tempfile.NamedTemporaryFile('wt', encoding='utf8', suffix=\".py\", delete=False)\n    downloader = _ScriptDownloader(url)\n    logger.info(\n        \"Downloading remote script from %r (using %r downloader) to %r\",\n        url, downloader.name, temp_fh.name)\n\n    content = downloader.get()\n    temp_fh.write(content)\n    temp_fh.close()\n    return temp_fh.name\n\n\ndef get_env_bin_path(base_env_path):\n    \"\"\"Find and return the environment's binary path in a multiplatformy way.\"\"\"\n    for subdir in (\"bin\", \"Scripts\"):\n        binpath = base_env_path / subdir\n        if binpath.exists():\n            return binpath\n    raise ValueError(f\"Binary subdir not found in {base_env_path!r}\")\n"
  },
  {
    "path": "fades/logger.py",
    "content": "# Copyright 2014-2018 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Logging set up.\"\"\"\n\nimport logging\nimport logging.handlers\nimport os.path\n\nfrom fades._version import __version__\n\n\nFMT_SIMPLE = \"*** fades ***  %(asctime)s  %(levelname)-8s %(message)s\"\nFMT_DETAILED = \"*** fades ***  %(asctime)s  %(name)-18s %(levelname)-8s %(message)s\"\nFMT_SYSLOG = \"[%(process)d] %(name)-18s %(levelname)-8s %(message)s\"\n\nSALUTATION = \"Hi! This is fades {}, automatically managing your dependencies\".format(__version__)\n\n\nclass SalutingStreamHandler(logging.StreamHandler):\n    \"\"\"A handler that salutes once before polluting user screen.\n\n    Note that the salutation is done in INFO level, to respect \"verbose\" modifiers.\n    \"\"\"\n\n    def __init__(self, logger):\n        \"\"\"Init.\"\"\"\n        super().__init__()\n        self._already_saluted = False\n        self._logger = logger\n\n    def emit(self, record):\n        \"\"\"Call father's emit, but salute first (just once).\"\"\"\n        if not self._already_saluted:\n            self._already_saluted = True\n            self._logger.info(SALUTATION)\n        super().emit(record)\n\n\ndef set_up(verbose, quiet):\n    \"\"\"Set up the logging.\"\"\"\n    logger = logging.getLogger('fades')\n    logger.setLevel(logging.DEBUG)\n\n    # select logging level according to user desire; also use a simpler\n    # formatting for non-verbose logging\n    if verbose:\n        log_level = logging.DEBUG\n        log_format = FMT_DETAILED\n    elif quiet:\n        log_level = logging.WARNING\n        log_format = FMT_SIMPLE\n    else:\n        log_level = logging.INFO\n        log_format = FMT_SIMPLE\n\n    # all to the stdout\n    handler = SalutingStreamHandler(logger)\n    handler.setLevel(log_level)\n    logger.addHandler(handler)\n    formatter = logging.Formatter(log_format)\n    handler.setFormatter(formatter)\n\n    # and to the syslog\n    for syslog_path in ('/dev/log', '/var/run/syslog'):\n        if not os.path.exists(syslog_path):\n            continue\n        try:\n            handler = logging.handlers.SysLogHandler(address=syslog_path)\n        except Exception:\n            # silently ignore that the user doesn't have a syslog active; can\n            # see all the info with \"-v\" anyway\n            pass\n        else:\n            logger.addHandler(handler)\n            formatter = logging.Formatter(FMT_SYSLOG)\n            handler.setFormatter(formatter)\n            break\n\n    return logger\n"
  },
  {
    "path": "fades/main.py",
    "content": "# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General\n# Public License version 3, as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.\n# See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.\n# If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Main 'fades' modules.\"\"\"\n\nimport argparse\nimport logging\nimport os\nimport platform\nimport signal\nimport sys\nimport subprocess\nimport tempfile\n\nimport fades\nfrom fades import (\n    FadesError,\n    cache,\n    envbuilder,\n    file_options,\n    helpers,\n    parsing,\n    pipmanager,\n    pkgnamesdb,\n)\nfrom fades.logger import set_up as logger_set_up\n\n\n# Get the logger here; it will be properly setup at bootstrap, but can be used from\n# the rest of the module just fine\nlogger = logging.getLogger('fades')\n\n# the signals to redirect to the child process (note: only these are\n# allowed in Windows, see 'signal' doc).\nREDIRECTED_SIGNALS = [\n    signal.SIGABRT,\n    signal.SIGFPE,\n    signal.SIGILL,\n    signal.SIGINT,\n    signal.SIGSEGV,\n    signal.SIGTERM,\n]\n\nHELP_EPILOG = \"\"\"\nThe \"child program\" is the script that fades will execute. It's an\noptional parameter, it will be the first thing received by fades that\nis not a parameter.  If no child program is indicated, a Python\ninteractive interpreter will be opened.\n\nThe \"child options\" (everything after the child program) are\nparameters passed as is to the child program.\n\"\"\"\n\nAUTOIMPORT_HEADER = \"\"\"\nimport sys\nprint(\"Python {} on {}\".format(sys.version, sys.platform))\nprint('Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.')\n\"\"\"\n\nAUTOIMPORT_MOD_IMPORTER = \"\"\"\ntry:\n    import {module}\nexcept ImportError:\n    print(\"::fades:: FAILED to autoimport {module!r}\")\nelse:\n    print(\"::fades:: automatically imported {module!r}\")\n\"\"\"\n\nAUTOIMPORT_MOD_SKIPPING = (\n    \"\"\"print(\"::fades:: autoimport skipped because not a PyPI package: {dependency!r}\")\\n\"\"\")\n\n\ndef get_autoimport_scriptname(dependencies, is_ipython):\n    \"\"\"Return the path of script that will import dependencies for interactive mode.\n\n    The script has:\n\n    - a safe import of the dependencies, taking in consideration that the module may be named\n      differently than the package, and printing a message accordingly\n\n    - if regular Python, also print the normal interactive interpreter first information lines,\n      that are not shown when starting it with `-i` (but IPython shows them anyway).\n    \"\"\"\n    fd, tempfilepath = tempfile.mkstemp(prefix='fadesinit-', suffix='.py')\n    fh = os.fdopen(fd, 'wt', encoding='utf8')\n\n    if not is_ipython:\n        fh.write(AUTOIMPORT_HEADER)\n\n    for repo, dependencies in dependencies.items():\n        for dependency in dependencies:\n            if repo == fades.REPO_PYPI:\n                package = dependency.name\n                if is_ipython and package == 'ipython':\n                    # Ignore this artificially added dependency.\n                    continue\n\n                module = pkgnamesdb.PACKAGE_TO_MODULE.get(package, package)\n                fh.write(AUTOIMPORT_MOD_IMPORTER.format(module=module))\n            else:\n                fh.write(AUTOIMPORT_MOD_SKIPPING.format(dependency=dependency))\n\n    fh.close()\n    return tempfilepath\n\n\ndef consolidate_dependencies(needs_ipython, child_program,\n                             requirement_files, manual_dependencies):\n    \"\"\"Parse files, get deps and merge them. Deps read later overwrite those read earlier.\"\"\"\n    if needs_ipython:\n        logger.debug(\"Adding ipython dependency because --ipython was detected\")\n        ipython_dep = parsing.parse_manual(['ipython'])\n    else:\n        ipython_dep = {}\n\n    if child_program:\n        srcfile_deps = parsing.parse_srcfile(child_program)\n        logger.debug(\"Dependencies from source file: %s\", srcfile_deps)\n        docstring_deps = parsing.parse_docstring(child_program)\n        logger.debug(\"Dependencies from docstrings: %s\", docstring_deps)\n    else:\n        srcfile_deps = {}\n        docstring_deps = {}\n\n    all_dependencies = [ipython_dep, srcfile_deps, docstring_deps]\n\n    if requirement_files is not None:\n        for rf_path in requirement_files:\n            rf_deps = parsing.parse_reqfile(rf_path)\n            logger.debug('Dependencies from requirements file %r: %s', rf_path, rf_deps)\n            all_dependencies.append(rf_deps)\n\n    manual_deps = parsing.parse_manual(manual_dependencies)\n    logger.debug(\"Dependencies from parameters: %s\", manual_deps)\n    all_dependencies.append(manual_deps)\n\n    # Merge dependencies\n    indicated_deps = {}\n    for dep in all_dependencies:\n        for repo, info in dep.items():\n            indicated_deps.setdefault(repo, set()).update(info)\n\n    return indicated_deps\n\n\ndef decide_child_program(args_executable, args_module, args_child_program):\n    \"\"\"Decide which the child program really is (if any).\"\"\"\n    if args_executable:\n        # If --exec given, check that it's just the executable name or an absolute path;\n        # relative paths are forbidden (as the location of the venv should not be known).\n        if os.path.sep in args_child_program and args_child_program[0] != os.path.sep:\n            logger.error(\n                \"The parameter to --exec must be a file name (to be found \"\n                \"inside venv's bin directory), not a file path: %r\",\n                args_child_program)\n            raise FadesError(\"File path given to --exec parameter\")\n\n        # indicated --execute, local and not analyzable for dependencies\n        analyzable_child_program = None\n        child_program = args_child_program\n    elif args_module:\n        # If --module given, the module may be installed (nothing can be really checked),\n        # but surely it's not used as a source for dependencies.\n        analyzable_child_program = None\n        child_program = args_child_program\n    elif args_child_program is not None:\n        # normal case, the child program is to be analyzed (being it local or remote)\n        if args_child_program.startswith((\"http://\", \"https://\")):\n            args_child_program = helpers.download_remote_script(args_child_program)\n        else:\n            if not os.access(args_child_program, os.R_OK):\n                logger.error(\"'%s' not found. If you want to run an executable \"\n                             \"file from a library installed in the virtualenv \"\n                             \"check the `--exec` option in the help.\",\n                             args_child_program)\n                raise FadesError(\"child program  not found.\")\n        analyzable_child_program = args_child_program\n        child_program = args_child_program\n    else:\n        # not indicated executable, not child program, \"interpreter\" mode\n        analyzable_child_program = None\n        child_program = None\n\n    return analyzable_child_program, child_program\n\n\ndef detect_inside_virtualenv(prefix, real_prefix, base_prefix):\n    \"\"\"Tell if fades is running inside a virtualenv.\n\n    The params 'real_prefix' and 'base_prefix' may be None.\n\n    This is copied from pip code (slightly modified), see\n\n        https://github.com/pypa/pip/blob/281eb61b09d87765d7c2b92f6982b3fe76ccb0af/\n            pip/locations.py#L39\n    \"\"\"\n    if os.environ.get(\"SNAP\"):\n        # snaps under core20 are really virtualenvs but we have full control of the\n        # system layout, \"this is fine\" (<insert meme>), skip this control\n        return False\n\n    if real_prefix is not None:\n        return True\n\n    if base_prefix is None:\n        return False\n\n    # if prefix is different than base_prefix, it's a venv\n    return prefix != base_prefix\n\n\ndef go():\n    \"\"\"Make the magic happen.\"\"\"\n    parser = argparse.ArgumentParser(\n        prog='fades', epilog=HELP_EPILOG, formatter_class=argparse.RawDescriptionHelpFormatter)\n    parser.add_argument(\n        '-V', '--version', action='store_true',\n        help=\"show version and info about the system, and exit\")\n    parser.add_argument(\n        '-d', '--dependency', action='append',\n        help=\"specify dependencies through command line (this option can be used multiple times)\")\n    parser.add_argument(\n        '-r', '--requirement', action='append',\n        help=\"indicate files to read dependencies from (this option can be used multiple times)\")\n    parser.add_argument(\n        '-p', '--python', action='store',\n        help=\"specify the Python interpreter to use; the default is: {}\".format(sys.executable))\n    parser.add_argument(\n        '-i', '--ipython', action='store_true', help=\"use IPython shell when in interactive mode\")\n    parser.add_argument(\n        '--system-site-packages', action='store_true', default=False,\n        help=\"give the virtual environment access to the system site-packages dir.\")\n    parser.add_argument(\n        '--venv-options', action='append', default=[],\n        help=\"extra options to be supplied to the venv module \"\n             \"(this option can be used multiple times)\")\n    parser.add_argument(\n        '-U', '--check-updates', action='store_true', help=\"check for packages updates\")\n    parser.add_argument(\n        '--no-precheck-availability', action='store_true',\n        help=\"don't check if the packages exists in PyPI before actually try to install them\")\n    parser.add_argument(\n        '--pip-options', action='append', default=[],\n        help=\"extra options to be supplied to pip (this option can be used multiple times)\")\n    parser.add_argument(\n        '--python-options', action='append', default=[],\n        help=\"extra options to be supplied to python (this option can be used multiple times)\")\n    parser.add_argument(\n        '--rm', dest='remove', metavar='UUID',\n        help=\"remove a virtualenv by UUID; see --where option to easily find out the UUID\")\n    parser.add_argument(\n        '--clean-unused-venvs', action='store',\n        help=\"remove venvs that haven't been used for more than the indicated days and compact \"\n             \"usage stats file (all this takes place at the beginning of the execution)\")\n    parser.add_argument(\n        '--where', '--get-venv-dir', action='store_true',\n        help=\"show the virtualenv base directory (including the venv's UUID) and quit\")\n    parser.add_argument(\n        '-a', '--autoimport', action='store_true',\n        help=\"automatically import the specified dependencies in the interactive mode \"\n             \"(ignored otherwise).\")\n    parser.add_argument(\n        '--freeze', action='store', metavar='FILEPATH',\n        help=\"dump all the dependencies and its versions to the specified filepath \"\n             \"(operating normally beyond that)\")\n    parser.add_argument(\n        '--avoid-pip-upgrade', action='store_true',\n        help=\"disable the automatic pip upgrade that happens after the virtualenv is created \"\n             \"and before the dependencies begin to be installed.\")\n\n    mutexg = parser.add_mutually_exclusive_group()\n    mutexg.add_argument(\n        '-v', '--verbose', action='store_true',\n        help=\"send all internal debugging lines to stderr, which may be very \"\n             \"useful to debug any problem that may arise\")\n    mutexg.add_argument(\n        '-q', '--quiet', action='store_true',\n        help=\"don't show anything (unless it has a real problem), so the \"\n             \"original script stderr is not polluted at all\")\n\n    mutexg = parser.add_mutually_exclusive_group()\n    mutexg.add_argument(\n        '-x', '--exec', dest='executable', action='store_true',\n        help=\"execute the child_program (must be present) in the context of the virtualenv\")\n    mutexg.add_argument(\n        '-m', '--module', action='store_true',\n        help=\"run library module as a script (same behaviour than Python's -m parameter)\")\n\n    parser.add_argument('child_program', nargs='?', default=None)\n    parser.add_argument('child_options', nargs=argparse.REMAINDER)\n\n    cli_args = parser.parse_args()\n\n    # update args from config file (if needed).\n    args = file_options.options_from_file(cli_args)\n\n    # validate input, parameters, and support some special options\n    if args.version:\n        print(\"Running 'fades' version\", fades.__version__)\n        print(\"    Python:\", sys.version_info)\n        print(\"    System:\", platform.platform())\n        return 0\n\n    # The --exec and --module flags needs child_program to exist (this is not handled at\n    # argparse level because it's easier to collect the executable as the\n    # normal child_program, so everything after that are parameteres\n    # considered for the executable itself, not for fades).\n    if args.executable and not args.child_program:\n        parser.print_usage()\n        print(\"fades: error: argument -x/--exec needs child_program to be present\")\n        return -1\n    if args.module and not args.child_program:\n        parser.print_usage()\n        print(\"fades: error: argument -m/--module needs child_program (module) to be present\")\n        return -1\n\n    # set up the logger and dump basic version info\n    logger_set_up(args.verbose, args.quiet)\n    logger.debug(\"Running Python %s on %r\", sys.version_info, platform.platform())\n    logger.debug(\"Starting fades v. %s\", fades.__version__)\n    logger.debug(\"Arguments: %s\", args)\n\n    # verify that the module is NOT being used from a virtualenv\n    _real_prefix = getattr(sys, 'real_prefix', None)\n    _base_prefix = getattr(sys, 'base_prefix', None)\n    if detect_inside_virtualenv(sys.prefix, _real_prefix, _base_prefix):\n        logger.error(\n            \"fades is running from inside a virtualenv (%r), which is not supported\", sys.prefix)\n        raise FadesError(\"Cannot run from a virtualenv\")\n\n    if args.verbose and args.quiet:\n        logger.warning(\"Overriding 'quiet' option ('verbose' also requested)\")\n\n    # start the virtualenvs manager\n    venvscache = cache.VEnvsCache(os.path.join(helpers.get_basedir(), 'venvs.idx'))\n    # start usage manager\n    usage_manager = envbuilder.UsageManager(\n        os.path.join(helpers.get_basedir(), 'usage_stats'), venvscache)\n\n    if args.clean_unused_venvs:\n        try:\n            max_days_to_keep = int(args.clean_unused_venvs)\n        except ValueError:\n            logger.error(\"clean_unused_venvs must be an integer.\")\n            raise FadesError('clean_unused_venvs not an integer')\n\n        usage_manager.clean_unused_venvs(max_days_to_keep)\n        return 0\n\n    uuid = args.remove\n    if uuid:\n        venv_data = venvscache.get_venv(uuid=uuid)\n        if venv_data:\n            # remove this venv from the cache\n            env_path = venv_data.get('env_path')\n            if env_path:\n                envbuilder.destroy_venv(env_path, venvscache)\n            else:\n                logger.warning(\n                    \"Invalid 'env_path' found in virtualenv metadata: %r. \"\n                    \"Not removing virtualenv.\", env_path)\n        else:\n            logger.warning('No virtualenv found with uuid: %s.', uuid)\n        return 0\n\n    # decided which the child program really is\n    analyzable_child_program, child_program = decide_child_program(\n        args.executable, args.module, args.child_program)\n\n    # Group and merge dependencies\n    indicated_deps = consolidate_dependencies(\n        args.ipython, analyzable_child_program, args.requirement, args.dependency)\n\n    # Check for packages updates\n    if args.check_updates:\n        helpers.check_pypi_updates(indicated_deps)\n\n    # get the interpreter version requested for the child_program\n    interpreter, is_current = helpers.get_interpreter_version(args.python)\n\n    # options\n    pip_options = args.pip_options  # pip_options mustn't store.\n    python_options = args.python_options\n    options = {}\n    options['venv_options'] = args.venv_options\n    if args.system_site_packages:\n        options['venv_options'].append(\"--system-site-packages\")\n\n    create_venv = False\n    venv_data = venvscache.get_venv(indicated_deps, interpreter, uuid, options)\n    if venv_data:\n        env_path = venv_data['env_path']\n        # A venv was found in the cache check if its valid or re-generate it.\n        if not os.path.exists(env_path):\n            logger.warning(\"Missing directory (the virtualenv will be re-created): %r\", env_path)\n            venvscache.remove(env_path)\n            create_venv = True\n    else:\n        create_venv = True\n\n    if create_venv:\n        # Check if the requested packages exists in pypi.\n        if not args.no_precheck_availability and indicated_deps.get('pypi'):\n            logger.info(\n                \"Checking the availabilty of dependencies in PyPI. \"\n                \"You can use '--no-precheck-availability' to avoid it.\")\n            if not helpers.check_pypi_exists(indicated_deps):\n                logger.error(\"An indicated dependency doesn't exist. Exiting\")\n                raise FadesError(\"Required dependency does not exist\")\n\n        # Create a new venv\n        venv_data, installed = envbuilder.create_venv(\n            indicated_deps, args.python, is_current, options, pip_options, args.avoid_pip_upgrade)\n        # store this new venv in the cache\n        venvscache.store(installed, venv_data, interpreter, options)\n\n    if args.where:\n        # all it was requested is the virtualenv's path, show it and quit (don't run anything)\n        print(venv_data['env_path'])\n        return 0\n\n    if args.freeze:\n        # beyond all the rest of work, dump the dependencies versions to a file\n        mgr = pipmanager.PipManager(venv_data['env_bin_path'])\n        mgr.freeze(args.freeze)\n\n    # run forest run!!\n    python_exe = 'ipython' if args.ipython else 'python'\n    python_exe = os.path.join(venv_data['env_bin_path'], python_exe)\n\n    # add the virtualenv /bin path to the child PATH.\n    environ_path = venv_data['env_bin_path']\n    if 'PATH' in os.environ:\n        environ_path += os.pathsep + os.environ['PATH']\n    os.environ['PATH'] = environ_path\n\n    # store usage information\n    usage_manager.store_usage_stat(venv_data, venvscache)\n\n    if child_program is None:\n        interactive = True\n        cmd = [python_exe] + python_options\n\n        # get possible extra python options and environement for auto import\n        if indicated_deps and args.autoimport:\n            temp_scriptpath = get_autoimport_scriptname(indicated_deps, args.ipython)\n            cmd += ['-i', temp_scriptpath]\n\n        logger.debug(\"Calling the interactive Python interpreter: %s\", cmd)\n        proc = subprocess.Popen(cmd)\n    else:\n        interactive = False\n        if args.executable:\n            # Build the exec path relative to 'bin' dir; note that if child_program's path\n            # is absolute (starting with '/') the resulting exec_path will be just it,\n            # which is something fades supports\n            exec_path = os.path.join(venv_data['env_bin_path'], child_program)\n            cmd = [exec_path]\n        elif args.module:\n            cmd = [python_exe, '-m'] + python_options + [child_program]\n        else:\n            cmd = [python_exe] + python_options + [child_program]\n\n        # Incorporate the child options, always at the end, log and run.\n        cmd += args.child_options\n        logger.debug(\"Calling %s\", cmd)\n\n        try:\n            proc = subprocess.Popen(cmd)\n        except FileNotFoundError:\n            logger.error(\"Command not found: %s\", child_program)\n            raise FadesError(\"Command not found\")\n\n    def _signal_handler(signum, _):\n        \"\"\"Handle signals received by parent process, send them to child.\n\n        The only exception is CTRL-C, that is generated *from* the interactive\n        interpreter (it's a keyboard combination!), so we swallow it for the\n        interpreter to not see it twice.\n        \"\"\"\n        if interactive and signum == signal.SIGINT:\n            logger.debug(\"Swallowing signal %s\", signum)\n        else:\n            logger.debug(\"Redirecting signal %s to child\", signum)\n            os.kill(proc.pid, signum)\n\n    # redirect the useful signals\n    for s in REDIRECTED_SIGNALS:\n        signal.signal(s, _signal_handler)\n\n    # wait child to finish, end\n    rc = proc.wait()\n    if rc:\n        logger.debug(\"Child process not finished correctly: returncode=%d\", rc)\n    return rc\n"
  },
  {
    "path": "fades/multiplatform.py",
    "content": "# Copyright 2016 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General\n# Public License version 3, as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.\n# See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.\n# If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Platform agnostic collection of utilities.\"\"\"\n\nimport os\n\nfrom contextlib import contextmanager\n\ntry:\n    import fcntl\n\n    @contextmanager\n    def filelock(filepath):\n        \"\"\"Context manager to lock over a file using best method: fcntl.\"\"\"\n        with open(filepath, 'w') as fh:\n            fcntl.flock(fh, fcntl.LOCK_EX)\n            yield\n            fcntl.flock(fh, fcntl.LOCK_UN)\n        if os.path.exists(filepath):\n            os.remove(filepath)\n\nexcept ImportError:\n    import time\n\n    @contextmanager\n    def filelock(filepath):\n        \"\"\"Context manager to lock over a file where fcntl doesn't exist.\"\"\"\n        try:\n            while True:\n                try:\n                    with open(filepath, \"x\"):\n                        yield\n                    break\n                except FileExistsError:\n                    time.sleep(.5)\n        finally:\n            if os.path.exists(filepath):\n                os.remove(filepath)\n"
  },
  {
    "path": "fades/parsing.py",
    "content": "# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Script parsing to get needed dependencies.\"\"\"\n\nimport logging\nimport os\nimport re\n\nfrom packaging.requirements import Requirement\nfrom packaging.version import Version\n\nfrom fades import REPO_PYPI, REPO_VCS\nfrom fades.pkgnamesdb import MODULE_TO_PACKAGE\n\nlogger = logging.getLogger(__name__)\n\n\nclass _VCSSpecifier:\n    \"\"\"A simple specifier that works with VCSDependency.\"\"\"\n\n    def contains(self, other):\n        \"\"\"VCS dependency does not handle versions.\"\"\"\n        return other is None\n\n\nclass VCSDependency:\n    \"\"\"A dependency object for VCS urls (git, bzr, etc.).\n\n    It stores as unique identifier the whole URL; there may be a little\n    inefficiency because we may consider as different two urls for same\n    project but using different transports, but it's a small price for\n    not needing to parse and analyze url parts.\n    \"\"\"\n\n    def __init__(self, url):\n        \"\"\"Init.\"\"\"\n        self.url = self.name = self.project_name = self.version = url\n        self.specifier = _VCSSpecifier()\n\n    def __str__(self):\n        \"\"\"Return the URL as this is the interface to get what pip will use.\"\"\"\n        return self.url\n\n    def __repr__(self):\n        \"\"\"Repr.\"\"\"\n        return \"<VCSDependency: {!r}>\".format(self.url)\n\n    def __eq__(self, other):\n        \"\"\"Tell if one VCSDependency is equal to other.\"\"\"\n        if not isinstance(other, VCSDependency):\n            return False\n        return self.url == other.url\n\n    def __hash__(self):\n        \"\"\"Pair to __eq__ to make this hashable.\"\"\"\n        return hash(self.url)\n\n\nclass NameVerDependency:\n    \"\"\"A dependency indicated by name and version.\"\"\"\n\n    def __init__(self, name, version):\n        self.name = name\n        self.version = Version(version)\n\n    def __eq__(self, other):\n        return self.name == other.name and self.version == other.version\n\n    def __hash__(self):\n        return hash((self.name, self.version))\n\n    def __lt__(self, other):\n        assert not isinstance(self.version, str)\n        return (self.name, self.version) < (other.name, other.version)\n\n\ndef parse_fade_requirement(text):\n    \"\"\"Return a requirement and repo from the given text, already parsed and converted.\"\"\"\n    text = text.strip()\n\n    if \"::\" in text:\n        repo_raw, requirement = text.split(\"::\", 1)\n        try:\n            repo = {'pypi': REPO_PYPI, 'vcs': REPO_VCS}[repo_raw]\n        except KeyError:\n            logger.warning(\"Not understood fades repository: %r\", repo_raw)\n            return\n    else:\n        if \":\" in text and \"/\" in text:\n            repo = REPO_VCS\n        else:\n            repo = REPO_PYPI\n        requirement = text\n\n    if repo == REPO_VCS:\n        dependency = VCSDependency(requirement)\n    else:\n        dependency = Requirement(requirement)\n    return repo, dependency\n\n\ndef _parse_content(fh):\n    \"\"\"Parse the content of a script to find marked dependencies.\"\"\"\n    content = iter(fh)\n    deps = {}\n\n    for line in content:\n        # quickly discard most of the lines\n        if 'fades' not in line:\n            continue\n\n        # discard other string with 'fades' that isn't a comment\n        if '#' not in line:\n            continue\n\n        # assure that it's a well commented line and no other stuff\n        line = line.strip()\n        index_of_last_fades = line.rfind('fades')\n        index_of_first_hash = line.index('#')\n\n        # discard when fades does not appear after #\n        if index_of_first_hash > index_of_last_fades:\n            continue\n\n        import_part, fades_part = line.rsplit(\"#\", 1)\n\n        # discard other comments in the same line that aren't for fades\n        if \"fades\" not in fades_part:\n            import_part, fades_part = import_part.rsplit(\"#\", 1)\n\n        fades_part = fades_part.strip()\n        if not fades_part.startswith(\"fades\"):\n            continue\n\n        if not import_part:\n            # the fades comment was done at the beginning of the line,\n            # which means that the import info is in the next one\n            import_part = next(content).strip()\n\n        if import_part.startswith('#'):\n            continue\n\n        # Get the module.\n        import_tokens = import_part.split()\n        if import_tokens[0] == 'import':\n            module_path = import_tokens[1]\n        elif import_tokens[0] == 'from' and import_tokens[2] == 'import':\n            module_path = import_tokens[1]\n        else:\n            logger.debug(\"Not understood import info: %s\", import_tokens)\n            continue\n        module = module_path.split(\".\")[0]\n\n        # The package has the same name (most of the times! if fades knows the conversion, use it).\n        if module in MODULE_TO_PACKAGE:\n            package = MODULE_TO_PACKAGE[module]\n        else:\n            package = module\n\n        # To match the \"safe\" name\n        package = package.replace('_', '-')\n\n        # get the fades info after 'fades' mark, if any\n        if len(fades_part) == 5 or fades_part[5:].strip()[0] in \"<>=!\":\n            # just the 'fades' mark, and maybe a version specification, the requirement is what\n            # was imported (maybe with that version comparison)\n            requirement = package + fades_part[5:]\n        elif fades_part[5] != \" \":\n            # starts with fades but it's part of a longer weird word\n            logger.warning(\"Not understood fades info: %r\", fades_part)\n            continue\n        else:\n            # more complex stuff, to be parsed as a normal requirement\n            requirement = fades_part[5:]\n\n        # parse and convert the requirement\n        parsed_req = parse_fade_requirement(requirement)\n        if parsed_req is None:\n            continue\n        repo, dependency = parsed_req\n        deps.setdefault(repo, []).append(dependency)\n\n    return deps\n\n\ndef _parse_docstring(fh):\n    \"\"\"Parse the docstrings of a script to find marked dependencies.\"\"\"\n    find_fades = re.compile(r'\\b(fades)\\b:').search\n\n    for line in fh:\n        if line.startswith(\"'\"):\n            quote = \"'\"\n            break\n        if line.startswith('\"'):\n            quote = '\"'\n            break\n    else:\n        return {}\n\n    if line[1] == quote:\n        # comment start with triple quotes\n        endquote = quote * 3\n    else:\n        endquote = quote\n\n    if endquote in line[len(endquote):]:\n        docstring_lines = [line[:line.index(endquote)]]\n    else:\n        docstring_lines = [line]\n        for line in fh:\n            if endquote in line:\n                docstring_lines.append(line[:line.index(endquote)])\n                break\n            docstring_lines.append(line)\n\n    docstring_lines = iter(docstring_lines)\n    for doc_line in docstring_lines:\n        if find_fades(doc_line):\n            break\n    else:\n        return {}\n\n    return _parse_requirement(list(docstring_lines))\n\n\ndef _parse_requirement(iterable):\n    \"\"\"Actually parse the requirements, from file or manually specified.\"\"\"\n    deps = {}\n    for line in iterable:\n        line = line.strip()\n        if \"#\" in line:\n            line = line[:line.index(\"#\")]\n        if not line:\n            continue\n\n        parsed_req = parse_fade_requirement(line)\n        if parsed_req is None:\n            continue\n        repo, dependency = parsed_req\n        deps.setdefault(repo, []).append(dependency)\n\n    return deps\n\n\ndef parse_manual(dependencies):\n    \"\"\"Parse an iterable and return specified dependencies.\"\"\"\n    if dependencies is None:\n        return {}\n    return _parse_requirement(dependencies)\n\n\ndef _read_lines(filepath):\n    \"\"\"Read a req file to a list to support nested requirement files.\"\"\"\n    with open(filepath, 'rt', encoding='utf8') as fh:\n        for line in fh:\n            line = line.strip()\n            if line.startswith(\"-r\"):\n                logger.debug(\"Reading deps from nested requirement file: %s\", line)\n                try:\n                    nested_filename = line.split()[1]\n                except IndexError:\n                    logger.warning(\n                        \"Invalid format to indicate a nested requirements file: '%r'\", line)\n                else:\n                    nested_filepath = os.path.join(\n                        os.path.dirname(filepath), nested_filename)\n                    yield from _read_lines(nested_filepath)\n            else:\n                yield line\n\n\ndef parse_reqfile(filepath):\n    \"\"\"Parse a requirement file and return the indicated dependencies.\"\"\"\n    if filepath is None:\n        return {}\n    return _parse_requirement(_read_lines(filepath))\n\n\ndef parse_srcfile(filepath):\n    \"\"\"Parse a source file and return its marked dependencies.\"\"\"\n    if filepath is None:\n        return {}\n    with open(filepath, 'rt', encoding='utf8') as fh:\n        return _parse_content(fh)\n\n\ndef parse_docstring(filepath):\n    \"\"\"Parse a source file and return its dependencies specified into docstrings.\"\"\"\n    if filepath is None:\n        return {}\n    with open(filepath, 'rt', encoding='utf8') as fh:\n        return _parse_docstring(fh)\n"
  },
  {
    "path": "fades/pipmanager.py",
    "content": "# Copyright 2014-2020 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Interface to handle pip.\n\nWe are not using pip as a API because fades is not running in the same\nenv that the child program. So we have to call the pip binary that is inside\nthe created virtualenv.\n\"\"\"\n\nimport os\nimport logging\nimport shutil\nimport contextlib\n\nfrom urllib import request\n\nfrom fades import helpers\n\nlogger = logging.getLogger(__name__)\n\nPIP_INSTALLER = \"https://bootstrap.pypa.io/get-pip.py\"\n\n\nclass PipManager():\n    \"\"\"A manager for all PIP related actions.\"\"\"\n\n    def __init__(self, env_bin_path, pip_installed=False, options=None, avoid_pip_upgrade=False):\n        \"\"\"Init.\"\"\"\n        self.env_bin_path = env_bin_path\n        self.pip_installed = pip_installed\n        self.options = options\n        self.pip_exe = os.path.join(self.env_bin_path, \"pip\")\n        basedir = helpers.get_basedir()\n        self.pip_installer_fname = os.path.join(basedir, \"get-pip.py\")\n        self.avoid_pip_upgrade = avoid_pip_upgrade\n\n    def install(self, dependency):\n        \"\"\"Install a new dependency.\"\"\"\n        if not self.pip_installed:\n            logger.info(\"Need to install a dependency with pip, but no builtin, \"\n                        \"doing it manually (just wait a little, all should go well)\")\n            self._brute_force_install_pip()\n\n        # Always update pip to get latest behaviours (specially regarding security); this has\n        # the nice side effect of getting logged the pip version that is used.\n        if not self.avoid_pip_upgrade:\n            python_exe = os.path.join(self.env_bin_path, \"python\")\n            helpers.logged_exec([python_exe, '-m', 'pip', 'install', 'pip', '--upgrade'])\n\n        # split to pass several tokens on multiword dependency (this is very specific for '-e' on\n        # external requirements, but implemented generically; note that this does not apply for\n        # normal reqs, because even if it originally is 'foo > 1.2', after parsing it loses the\n        # internal spaces)\n        str_dep = str(dependency)\n        args = [self.pip_exe, \"install\"] + str_dep.split()\n\n        if self.options:\n            for option in self.options:\n                args.extend(option.split())\n        logger.info(\"Installing dependency: %r\", str_dep)\n        try:\n            helpers.logged_exec(args)\n        except helpers.ExecutionError as error:\n            error.dump_to_log(logger)\n            raise error\n        except Exception as error:\n            logger.exception(\"Error installing %s: %s\", str_dep, error)\n            raise error\n\n    def get_version(self, dependency):\n        \"\"\"Return the installed version parsing the output of 'pip show'.\"\"\"\n        logger.debug(\"getting installed version for %s\", dependency)\n        stdout = helpers.logged_exec([self.pip_exe, \"show\", str(dependency)])\n        version = [line for line in stdout if line.startswith('Version:')]\n        if len(version) == 1:\n            version = version[0].strip().split()[1]\n            logger.debug(\"Installed version of %s is: %s\", dependency, version)\n            return version\n        else:\n            logger.error('Fades is having problems getting the installed version. '\n                         'Run with -v or check the logs for details')\n            return ''\n\n    def _download_pip_installer(self):\n        u = request.urlopen(PIP_INSTALLER)\n        temp_location = self.pip_installer_fname + '.temp'\n        with contextlib.closing(u), open(temp_location, 'wb') as f:\n            shutil.copyfileobj(u, f)\n        os.rename(temp_location, self.pip_installer_fname)\n\n    def _brute_force_install_pip(self):\n        \"\"\"Check a brute force install of pip itself.\"\"\"\n        if os.path.exists(self.pip_installer_fname):\n            logger.debug(\"Using pip installer from %r\", self.pip_installer_fname)\n        else:\n            logger.debug(\n                \"Installer for pip not found in %r, downloading it\", self.pip_installer_fname)\n            self._download_pip_installer()\n\n        logger.debug(\"Installing PIP manually in the virtualenv\")\n        python_exe = os.path.join(self.env_bin_path, \"python\")\n        helpers.logged_exec([python_exe, self.pip_installer_fname, '-I'])\n        self.pip_installed = True\n\n    def freeze(self, filepath):\n        \"\"\"Dump venv contents to the indicated filepath.\"\"\"\n        logger.debug(\"running freeze to store in %r\", filepath)\n        stdout = helpers.logged_exec([self.pip_exe, \"freeze\", \"--all\", \"--local\"])\n        with open(filepath, \"wt\", encoding='utf8') as fh:\n            fh.writelines(line + '\\n' for line in sorted(stdout))\n"
  },
  {
    "path": "fades/pkgnamesdb.py",
    "content": "# Copyright 2015-2020 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"A module to package and viceversa conversion DB.\n\nThis is needed for names which don't match with the distrbution's name.\n\"\"\"\n\nMODULE_TO_PACKAGE = {\n    'bs4': 'beautifulsoup4',\n    'github3': 'github3.py',\n    'uritemplate': 'uritemplate.py',\n    'postgresql': 'py-postgresql',\n    'yaml': 'pyyaml',\n    'PIL': 'pillow',\n    'Crypto': 'pycrypto',\n}\n\nPACKAGE_TO_MODULE = {v: k for k, v in MODULE_TO_PACKAGE.items()}\n"
  },
  {
    "path": "man/fades.1",
    "content": ".TH FADES 1\n.SH NAME\nfades - A system that automatically handles the virtualenvs in the cases normally found when writing scripts and simple programs, and even helps to administer big projects.\n\n\n.SH SYNOPSIS\n.B fades\n[\\fB-h\\fR][\\fB--help\\fR]\n[\\fB-V\\fR][\\fB--version\\fR]\n[\\fB-v\\fR][\\fB--verbose\\fR]\n[\\fB-q\\fR][\\fB--quiet\\fR]\n[\\fB-i\\fR][\\fB--ipython\\fR]\n[\\fB-d\\fR][\\fB--dependency\\fR]\n[\\fB-r\\fR][\\fB--requirement\\fR]\n[\\fB-x\\fR][\\fB--exec\\fR]\n[\\fB-p\\fR \\fIversion\\fR][\\fB--python\\fR=\\fIversion\\fR]\n[\\fB--rm\\fR=\\fIUUID\\fR]\n[\\fB--system-site-packages\\fR]\n[\\fB--venv-options\\fR=\\fIoptions\\fR]\n[\\fB--pip-options\\fR=\\fIoptions\\fR]\n[\\fB--python-options\\fR=\\fIoptions\\fR]\n[\\fB-U\\fR][\\fB--check-updates\\fR]\n[\\fB--clean-unused-venvs\\fR=\\fImax_days_to_keep\\fR]\n[\\fB--where\\fR][\\fB--get-venv-dir\\fR]\n[\\fB--no-precheck-availability\\fR]\n[\\fB-a\\fR][\\fB--autoimport\\fR]\n[\\fB--freeze\\fR]\n[\\fB-m\\fR][\\fB--module\\fR]\n[\\fB--avoid-pip-upgrade\\fR]\n[child_program [child_options]]\n\n\\fBfades\\fR can be used to execute directly your script, or put it with a #! at your script's beginning.\n\n\n.SH DESCRIPTION\n\n\\fBfades\\fR will automagically create a new virtual environment (or reuse a previous created one), installing the necessary dependencies, and execute your script inside that virtual environment, with the only requirement of executing the script with \\fBfades\\fR and also marking the required dependencies.\n\nThe first non-option parameter (if any) would be then the child program to execute, and any other parameters after that are passed as is to that child script.\n\n\\fBfades\\fR can also be executed without passing a child script to execute: in this mode it will open a Python interactive interpreter inside the created/reused virtual environment (taking dependencies from \\fI--dependency\\fR or \\fI--requirement\\fR options). If \\fI--autoimport\\fR is given, it will automatically import all the installed dependencies.\n\nIf the \\fIchild_program\\fR parameter is really an URL, the script will be automatically downloaded from there (supporting also the most common pastebins URLs: pastebin.com, linkode.org, gist, etc.).\n\n.SH OPTIONS\n\n.TP\n.BR -h \", \"--help\nShow help about all the parameters and options, and quit.\n\n.TP\n.BR -V \", \"--version\nShow the program version and info about the system, and quit.\n\n.TP\n.BR -v \", \"--verbose\nSend all internal debugging lines to stderr, which may be very useful if any problem arises.\n\n.TP\n.BR -q \", \" --quiet\nDon't show anything (unless it has a real problem), so the original script stderr is not polluted at all.\n\n.TP\n.BR -i \", \" --ipython\nRuns IPython shell instead of python ones.\n\n.TP\n.BR -d \", \" --dependency\nSpecify dependencies through command line. This option can be specified multiple times (once per dependency), and each time the format is \\fBrepository::dependency\\fR. The dependency may have versions specifications, and the repository is optional (it will infer it). Examples:\n\n    requests\n    pypi::requests > 2.3\n    requests<=3\n    git+https://github.com/kennethreitz/requests.git#egg=requests\n    vcs::git+https://github.com/kennethreitz/requests.git#egg=requests\n\nSee more examples below for real command line usage explanations.\n\n.TP\n.BR -r \", \" --requirement\nRead the dependencies from a file. Format in each line is the same than dependencies specified with \\fI--dependency\\fR. This option can be specified multiple times.\n\n.TP\n.BR -p \" \" \\fIversion\\fR \", \" --python=\\fIversion\\fR\nSelect which Python version to be used; the argument can be just the number (3.9), the whole name (python3.9) or the whole path (/usr/bin/python3.9).  Of course, the corresponding version of Python needs to be installed in your system.\n\nThe dependencies can be indicated in multiple places (in the Python source file, with a comment besides the import, in a \\fIrequirements\\fRfile, and/or through command line. In case of multiple definitions of the same dependency, command line overrides everything else, and requirements file overrides what is specified in the source code.\n\n.TP\n.BR -x \", \" --exec\nExecute the \\fIchild_program\\fR in the context of the virtual environment. The child_program, which in this case becomes a mandatory parameter, can be just the executable name (relative to the venv's bin directory) or an absolute path.\n\n.TP\n.BR --rm \" \" \\fIUUID\\fR\nRemove a virtual environment by UUID.  See \\fB--get-venv-dir\\fR option to easily find out the UUID.\n\n.TP\n.BR --system-site-packages \"\"\nGive the virtual environment access to thesystem site-packages dir\n\n.TP\n.BR --venv-options=\\fIVIRTUALENV_OPTION\\fR\nExtra options to be supplied to the venv module (this option can be used multiple times)\n\n.TP\n.BR --pip-options=\\fIPIP_OPTION\\fR\nExtra options to be supplied to pip. (this option can be used multiple times)\n\n.TP\n.BR --python-options=\\fIPYTHON_OPTION\\fR\nExtra options to be supplied to python. (this option can be used multiple times)\n\n.TP\n.BR -U \", \" --check-updates\nWill check for updates in PyPI to verify if there are new versions for the requested dependencies. If a new version is available for a dependency, it will use it (if the dependency was requested without version) or just inform which new version is available (if the dependency was requested with a specific version).\n\n.TP\n.BR --clean-unused-venvs=\\fIMAX_DAYS_TO_KEEP\\fR\nWill remove all virtualenvs that haven't been used for more than MAX_DAYS_TO_KEEP days.\n\n.TP\n.BR --where \", \" --get-venv-dir\nShow the virtual environment base directory (which includes the virtual environment UUID) and quit.\n\n.TP\n.BR --no-precheck-availability\nDon't check if the packages exists in PyPI before actually try to install them.\n\n.TP\n.BR -a \", \" --autoimport\nAutomatically import the dependencies when in interactive interpreter mode (ignored otherwise).\n\n.TP\n.BR --freeze \" \" \\fIFILEPATH\\fR\nWill operate exactly as without the command, but also it will dump the revisions of installed dependencies to the given \\fBfilepath\\fR.\n\n.TP\n.BR -m \", \" --module\nRun library module as a script (same behaviour than Python's \\fB-m\\fR parameter).\n\n.TP\n.BR --avoid-pip-upgrade\nDisable the automatic \\fBpip\\fR upgrade that happens after the virtual environment is created and before the dependencies begin to be installed.\n\n\n.SH EXAMPLES\n\n.TP\nfades foo.py --bar\n\nExecutes foo.py under fades, passing the --bar parameter to the child program, in a virtual environment with the dependencies indicated in the source code.\n\n.TP\nfades -v foo.py\n\nExecutes foo.py under fades, showing all the fades messages (verbose mode).\n\n.TP\nfades -d dependency1 -d dependency2>3.2 foo.py --bar\n\nExecutes foo.py under fades (passing the --bar parameter to it), in a virtual environment with the dependencies indicated in the source code and also dependency1 and dependency2 (any version > 3.2).\n\n.TP\nfades -d dependency1\n\nExecutes the Python interactive interpreter in a virtual environment with dependency1 installed.\n\n.TP\nfades -r requirements.txt\n\nExecutes the Python interactive interpreter in a virtual environment after installing there all dependencies taken from the requirements.txt file.\n\n.TP\nfades -r requirements.txt -r requirements_devel.txt\n\nExecutes the Python interactive interpreter in a virtual environment after installing there all dependencies taken from files requirements.txt and requirements_devel.txt.\n\n.SH USING CONFIGURATION FILES\n\nYou can also configure fades using \\fB.ini\\fR config files. fades will search config files in\n\\fB/etc/fades/fades.ini\\fR, the path indicated by \\fBxdg\\fR for your system\n(for example ~/config/fades/fades.ini) and .fades.ini.\nSo you can have different settings at system, user and project level.\n\nThe config files are in .ini format. (configparser) and fades will search for a [fades] section.\nYou have to use the same configurations that in the CLI. The only difference is with the config\noptions with a dash, it has to be replaced with a underscore.\n\nCheck http://fades.readthedocs.org/en/latest/readme.html#setting-options-using-config-files for full examples.\n\n\n.SH SEE ALSO\nDevelopment is centralized in https://github.com/PyAr/fades\n\nCheck that site for a better explanation of \\fBfades\\fR usage.\n\n.SH AUTHORS\nFacundo Batista, Nicolás Demarchi (see development page for contact info).\n\n.SH LICENSING\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3, as published by the Free Software Foundation.\n"
  },
  {
    "path": "pkg/debian/changelog",
    "content": "fades (4-1) unstable; urgency=medium\n\n  * Initial release.  (Closes: #802806)\n\n -- Facundo Batista <facundo@taniquetil.com.ar>  Sun, 25 Oct 2015 12:30:40 -0300\n"
  },
  {
    "path": "pkg/debian/compat",
    "content": "9\n"
  },
  {
    "path": "pkg/debian/control",
    "content": "Source: fades\nSection: python\nPriority: extra\nBuild-Depends: debhelper (>= 9),\n               dh-python,\n               dh-translations | dh-python,\n               python3-packaging,\n               python3-all (>= 3.6),\n               python3-xdg\nMaintainer: Facundo Batista <facundo@taniquetil.com.ar>\nUploaders: Debian Python Modules Team \n           <python-modules-team@lists.alioth.debian.org>\nHomepage: https://github.com/PyAr/fades\nStandards-Version: 3.9.7\nX-Python3-Version: >= 3.6\n\nPackage: fades\nArchitecture: all\nDepends: python3-pkg-resources, ${misc:Depends}, ${python3:Depends}\nDescription: system for automatically handling virtual environments\n fades is a system that automatically handles the virtualenvs in the cases\n normally found when writing scripts and simple programs, and even helps to\n administer big projects.\n"
  },
  {
    "path": "pkg/debian/copyright",
    "content": "Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\nUpstream-Name: fades\nUpstream-Contact: Facundo Batista <facundo@taniquetil.com.ar>\nSource: https://github.com/PyAr/fades/\n\nFiles: *\nCopyright: (C) 2014-2024\n Facundo Batista <facundo@taniquetil.com.ar>\n Nicolás Demarchi <mail@gilgamezh.me>\nLicense: GPL-3\n The full text of the GPL is distributed in\n /usr/share/common-licenses/GPL-3 on Debian systems.\n"
  },
  {
    "path": "pkg/debian/rules",
    "content": "#!/usr/bin/make -f\n\nexport PYBUILD_NAME=fades\n\n# Debian doesn't have dh-translations.\n%:\nifneq ($(shell dh -l | grep -xF translations),)\n\tdh $@ --with python3,translations --buildsystem=pybuild\nelse\n\tdh $@ --with python3 --buildsystem=pybuild\nendif\n"
  },
  {
    "path": "pkg/debian/watch",
    "content": "version=3\nopts=uversionmangle=s/(rc|a|b|c)/~$1/ \\\nhttps://pypi.debian.net/fades/fades-(.+)\\.(?:zip|tgz|tbz|txz|(?:tar\\.(?:gz|bz2|xz)))\n"
  },
  {
    "path": "pkg/snap/snapcraft.yaml",
    "content": "name: fades\nsummary: system for automatically handling virtual environments\ndescription: |\n    fades is a system that automatically handles the virtualenvs in the cases\n    normally found when writing scripts and simple programs, and even helps\n    to administer big projects.\n\n    fades will automagically create a new virtualenv (or reuse a previous\n    created one), installing the necessary dependencies, and execute your\n    script inside that virtualenv, with the only requirement of executing\n    the script with fades and also marking the required dependencies.\n\n    (If you don’t have a clue why this is necessary or useful, I'd recommend\n    you to read this small text about Python and the Management of Dependencies:\n    https://github.com/PyAr/fades/blob/master/docs/pydepmanag.rst)\n\n    Check the full documentation here: https://fades.readthedocs.io/\n\n    For developers, the project is here: https://github.com/PyAr/fades\nicon: resources/logo256.png\nbase: core20\nconfinement: classic\ngrade: stable\nadopt-info: fades  # look for 'snapcraftctl set-*' in the fades part\n\napps:\n  fades:\n    command: bin/python3 -m fades\n\nparts:\n  # Classic core20 snaps require staged python.\n  python3:\n    plugin: nil\n    build-packages:\n      - python3-dev\n    stage-packages:\n      - libpython3-stdlib\n      - libpython3.8-minimal\n      - libpython3.8-stdlib\n      - python3.8-minimal\n      - python3-distutils\n      - python3-minimal\n      - python3-pip\n      - python3-packaging\n      - python3-venv\n      - python3-wheel\n\n  fades:\n    after: [python3]\n    source: .\n    plugin: python\n    override-pull: |\n      snapcraftctl pull\n      snapcraftctl set-version \"$( python3 -c 'import fades; print(fades._version.__version__)' )\"\n    override-build: |\n      snapcraftctl build\n      # python3 fixup symlink (snapcraft bug)\n      ln -sf ../usr/bin/python3.8 $SNAPCRAFT_PART_INSTALL/bin/python3\n"
  },
  {
    "path": "press.txt",
    "content": "Hello all,\n\nWe're glad to announce the release of fades 9.0.\n\nfades is a system that automatically handles the virtualenvs in the\ncases normally found when writing scripts and simple programs, and\neven helps to administer big projects.\n\nIt will automagically create a new virtualenv (or reuse a previous\ncreated one), installing the necessary dependencies, and execute\nyour script inside that virtualenv.\n\nYou only need to execute the script with fades (instead of Python) and\nalso mark the required dependencies. More details here:\n\n    http://fades.rtfd.org/\n\n\nWhat's new in this release?\n\n- Get pip automatically upgraded to latest version on each virtualenv \n  creation (unless explicitly avoided)\n\n- Provides the --freeze parameter, which dumps the detailed package \n  information of the virtualenv, to duplicate future installations.\n\n- The -x/--exec parameter behaviour is extended/normalized to \n  support arbitrary paths.\n\n- Has the --autoimport parameter to automatically import the \n  dependencies to the interactive interpreter\n\n- Added more examples and descriptions to the documentation\n\n- Improved argument parsing when fades is used in the shebang\n\n- Worked on infrastructure: better testing, multiplatform installation support, etc.\n\n\nNicolás and I want to say a big thank you to the following collaborators\nthat helped to improve and enhance fades in different ways for this\nversion (in alphabetical order):\n\n    Alejandro Dau -https://github.com/alejandrodau \n    Carlos Joel -https://github.com/c0x6a\n    Diego Mascialino - https://github.com/dmascialino\n    Eduardo Enriquez - https://github.com/eduzen\n    Iñaki Malerba - https://github.com/inakimalerba\n\n\nTo install and enjoy fades...\n\n- If you are in Ubuntu or Debian, you can easily install like this\n  (but probably won't get *latest* fades:\n\n    sudo apt-get install fades\n\n- For not latest debian/ubuntu you have a .deb here (with its Debian\n  source file):\n\n    http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1-1_all.deb\n    http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1-1.dsc\n\n- Install it in Arch is very simple:\n\n    yaourt -S fades\n\n- In any Linux if you have the Snap system:\n\n    snap install fades\n\n- Using pip if you want:\n\n    pip3 install fades\n\n- You can always get the multiplatform tarball and install it in the\n  old fashion way:\n\n    wget http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1.orig.tar.gz\n    tar -xf fades_*.tar.gz\n    cd fades-*\n    sudo ./setup.py install\n\n\nHelp / questions:\n\n- You can ask any question or send any recommendation or request\n  in the Telegram group:\n\n    https://t.me/fadesmagic\n\n- Also, you can open an issue here (please do if you find any problem!).\n\n    https://github.com/PyAr/fades/issues/new\n\n- The project itself is in\n\n    https://github.com/PyAr/fades\n\n  It's very easy to run latest development version:\n\n    git clone https://github.com/PyAr/fades.git\n    cd fades\n    bin/fades\n\n\nThanks in advance for your time!\n\n\n----\n\n\nHola a todas y todos,\n\nEstamos encantados de anunciar la liberación de fades 9.0.\n\nfades es un sistema que maneja automáticamente los virtualenvs en los\ncasos que uno normalmente encuentra al escribir scripts y programas\npequeños, e incluso ayuda a administrar proyectos grandes.\n\nCrea automáticamente un nuevo virtualenv (o reusa uno creado previamente)\ninstalando las dependencias necesarias, y ejecutando el script\ndentro de ese virtualenv.\n\nTodo lo que necesitás hacer es ejecutar el script con fades (en lugar de\nPython) y también marcar las dependencias necesarias. Más detalles en \nla [documentación del proyecto](http://fades.rtfd.org/).\n\n\n**¿Qué hay de nuevo en esta release?**\n\n- Hace que pip se actualice automáticamente a la última versión en la \n  creación del virtualenv (a menos que se indique lo contrario).\n\n- Provee la opción `--freeze`, que graba la info detallada de los paquetes\n  del virtualenv, para duplicar instalaciones futuras.\n\n- Extiende y normaliza el comportamiento del parámetro `-x/--exec` para \n  soportar paths arbitrarios.\n\n- Crea la opción `--autoimport` para importar automáticamente las \n  dependencias instaladas al entrar al intérprete interactivo.\n\n- Agrega ejemplos y descripciones a la documentación.\n\n- Mejora el parseo de argumentos cuando fades se usa en el shebang.\n\n- Se mejoró la infrastructura: mejores pruebas, soporte \n  multiplataforma, etc.\n\n\nNicolás y yo queremos darles muchas gracias a los siguientes\ncolaboradores que ayudaron a mejorar a fades de distintas maneras para\nesta versión (en orden alfabético):\n\n- [Alejandro Dau](https://github.com/alejandrodau )\n- [Carlos Joel](https://github.com/c0x6a)\n- [Diego Mascialino](https://github.com/dmascialino)\n- [Eduardo Enriquez](https://github.com/eduzen)\n- [Iñaki Malerba](https://github.com/inakimalerba)\n\n\n**Para instalar y disfrutar fades:**\n\n- Si estás en Ubuntu o Debian, podés facilmente instalarlo así (aunque\n  probablemente no obtengas la *última* versión: `sudo apt-get install fades`\n\n- Para debian/ubuntu que no sea lo último, acá hay [un .deb](http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1-1_all.deb) (con su respectivo [archivo fuente Debian](http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1-1.dsc)).\n\n- Instalarlo en Arch es muy simple: `yaourt -S fades`\n\n- En cualquier Linux si tenés el sistema Snap: `snap install fades`\n\n- Podés usar pip si querés: `pip3 install fades`\n\n- Siempre podés usar el tarball multiplataforma e instalarlo de\n  la manera clásica:\n\n```bash\n    wget http://ftp.debian.org/debian/pool/main/f/fades/fades_9.0.1.orig.tar.gz\n    tar -xf fades_*.tar.gz\n    cd fades-*\n    sudo ./setup.py install\n```\n-  Es muy fácil ejecutar la última versión de desarrollo::\n\n```bash\n    git clone https://github.com/PyAr/fades.git\n    cd fades\n    bin/fades\n```\n\n**Ayuda / preguntas:**\n\n- Podés hacer cualquier pregunta o mandar una recomendación o pedido\n  en [el grupo de Telegram](https://t.me/fadesmagic).\n\n- También podés [abrir un issue](https://github.com/PyAr/fades/issues/new) (por favor hacelo si\n  encontrás algún problema!)::\n\n- El proyecto en sí está en [Github](https://github.com/PyAr/fades).\n\nDesde ya, muchas gracias por tu tiempo!\n"
  },
  {
    "path": "requirements.txt",
    "content": "flake8\nlogassert\npackaging\npydocstyle\npytest\npyuca\npyxdg\nrst2html5\n"
  },
  {
    "path": "resources/gifs/gifs.rst",
    "content": "Several small gifs showing specific fades functionality\n-------------------------------------------------------\n\nHow to cleanly test a Python library from PyPI without having to install\nor create anything:\n\n.. image:: testpylibnoinstall.gif\n\n\nHow to use a specific version of a Python library without messing your\nsystem:\n\n.. image:: usespecificlibversion.gif\n\n\nHow to cleanly test a Python library from GitHub without having to\ninstall or create anything:\n\n.. image:: testpylibgithub.gif\n\n\nHow to cleanly test a Python library without having to install or create\nanything and using `ipython`:\n\n.. image:: testlibwithipython.gif\n\n\nHow to cleanly test a Python 2 library from PyPI without having to install\nor create anything:\n\n.. image:: testlibpylegacy.gif\n\n\nHow to cleanly test multiple Python libraries without having to\ninstall or create anything:\n\n.. image:: multiplelibs.gif\n\n\nHow to get a Python interpreter using some specific requirements:\n\n.. image:: usingreqs.gif\n\n\nHow to make a Python script to be autonomous, not needing to\nremember any virtualenv details:\n\n.. image:: autonomousscript.gif\n\n\nHow to create a Python web project using whatever Django specific version:\n\n.. image:: createdjangoproject.gif\n\n\nHow to prepare your Python project for anybody to run it or its\ntests without any previous setup:\n\n.. image:: projecttests.gif\n\n\nHow to open an isolated Jupyter notebook for Python with `pandas`,\n`matplotlib` and `numpy`:\n\n.. image:: jupyternotebook.gif\n\n\nHow to run a modern timeit on a code snippet:\n\n.. image:: moderntimeit.gif\n\n\nHow to use a library from a local branch, but isolated from the system:\n\n.. image:: locallib.gif\n\n\nHow to automatically use the latest code to download YouTube videos:\n\n.. image:: youtubedl.gif\n"
  },
  {
    "path": "resources/notes.txt",
    "content": "wmctrl -r :ACTIVE: -e 0,424,52,880,495\n\n\n        880 x 482 !!\n\n\nkazam\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright 2014-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Build tar.gz for fades.\n\nNeeded packages to run (using Debian/Ubuntu package names):\n\n    python3\n    python3-xdg   (optional)\n\"\"\"\n\nimport os\nimport re\nimport shutil\nimport sys\n\nfrom distutils.core import setup\nfrom setuptools.command.install import install\n\n\ndef get_version():\n    \"\"\"Retrieves package version from the file.\"\"\"\n    with open('fades/_version.py') as fh:\n        m = re.search(r\"\\(([^']*)\\)\", fh.read())\n    if m is None:\n        raise ValueError(\"Unrecognized version in 'fades/_version.py'\")\n    return m.groups()[0].replace(', ', '.')\n\n\n# the different scripts according to the platform\nSCRIPT_WIN = 'bin/fades.cmd'\nSCRIPT_REST = 'bin/fades'\n\n\nclass CustomInstall(install):\n    \"\"\"Custom installation to fix script info and install man.\"\"\"\n\n    def initialize_options(self):\n        \"\"\"Run parent initialization and then fix the scripts var.\"\"\"\n        install.initialize_options(self)\n\n        # leave the proper script according to the platform\n        script = SCRIPT_WIN if sys.platform == \"win32\" else SCRIPT_REST\n        self.distribution.scripts = [script]\n\n    def run(self):\n        \"\"\"Run parent install, and then save the man file.\"\"\"\n        install.run(self)\n\n        # man directory\n        if self._custom_man_dir is not None:\n            if not os.path.exists(self._custom_man_dir):\n                os.makedirs(self._custom_man_dir)\n            shutil.copy(\"man/fades.1\", self._custom_man_dir)\n\n    def finalize_options(self):\n        \"\"\"Alter the installation path.\"\"\"\n        install.finalize_options(self)\n        if self.prefix is None:\n            # no place for man page (like in a 'snap')\n            man_dir = None\n        else:\n            man_dir = os.path.join(self.prefix, \"share\", \"man\", \"man1\")\n\n            # if we have 'root', put the building path also under it (used normally\n            # by pbuilder)\n            if self.root is not None:\n                man_dir = os.path.join(self.root, man_dir[1:])\n        self._custom_man_dir = man_dir\n\n\nsetup(\n    name='fades',\n    version=get_version(),\n    license='GPL-3',\n    author='Facundo Batista, Nicolás Demarchi',\n    author_email='facundo@taniquetil.com.ar, mail@gilgamezh.me',\n    description=(\n        'A system that automatically handles the virtualenvs in the cases '\n        'normally found when writing scripts and simple programs, and '\n        'even helps to administer big projects.'),\n    long_description=open('README.rst').read(),\n    url='https://github.com/PyAr/fades',\n    download_url=\"https://github.com/PyAr/fades/releases\",  # Release download URL.\n    packages=[\"fades\"],\n    scripts=[SCRIPT_WIN, SCRIPT_REST],\n    keywords=\"virtualenv utils utility scripts\",  # to get found easily on PyPI results, etc.\n    cmdclass={\n        'install': CustomInstall,\n    },\n    install_requires=['setuptools'],\n    tests_require=['logassert', 'pyxdg', 'pyuca', 'pytest', 'flake8',\n                   'pep257', 'rst2html5'],  # what unittests require\n    python_requires='>=3.6',  # Minimum Python version supported.\n    extras_require={\n        'pyxdg': 'pyxdg',\n        'packaging': 'packaging',\n    },\n\n    # https://pypi.python.org/pypi?%3Aaction=list_classifiers\n    classifiers=[\n        'Development Status :: 5 - Production/Stable',\n        # 'Development Status :: 6 - Mature',\n        # 'Development Status :: 7 - Inactive',\n\n        'Environment :: Console',\n\n        'Intended Audience :: Developers',\n        'Intended Audience :: System Administrators',\n\n        'License :: OSI Approved :: GNU General Public License (GPL)',\n        'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',\n        'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',\n\n        'Natural Language :: English',\n        'Natural Language :: Spanish',\n\n        'Operating System :: MacOS',\n        'Operating System :: MacOS :: MacOS X',\n        'Operating System :: Microsoft',\n        'Operating System :: Microsoft :: Windows',\n        'Operating System :: POSIX',\n        'Operating System :: POSIX :: Linux',\n\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 3',\n        'Programming Language :: Python :: 3 :: Only',\n        'Programming Language :: Python :: 3.6',\n        'Programming Language :: Python :: 3.7',\n        'Programming Language :: Python :: 3.8',\n        'Programming Language :: Python :: 3.9',\n        'Programming Language :: Python :: 3.10',\n        'Programming Language :: Python :: 3.11',\n        'Programming Language :: Python :: 3.12',\n        'Programming Language :: Python :: Implementation',\n        'Programming Language :: Python :: Implementation :: CPython',\n\n        'Topic :: Software Development',\n        'Topic :: Utilities',\n    ],\n)\n"
  },
  {
    "path": "test",
    "content": "#!/bin/bash\n#\n# Copyright 2014-2018 Facundo Batista, Nicolás Demarchi\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    TARGET_TESTS=\"$@\"\nelse\n    TARGET_TESTS=\"\"\nfi\n\n./bin/fades -r requirements.txt -d pytest-cov -x pytest --cov=fades $TARGET_TESTS\n\n# check if we are using exit() in the code.\nif grep -r -n ' exit(' --include=\"*.py\" .; then echo 'Please use sys.exit() instead of exit(). https://github.com/PyAr/fades/issues/280'; fi\n"
  },
  {
    "path": "testdev",
    "content": "#!/bin/bash\n#\n# Copyright 2014-2016 Facundo Batista, Nicolás Demarchi\n\nset -eu\n\nif [ $# -ne 0 ]; then\n    TARGET_TESTS=\"$@\"\nelse\n    TARGET_TESTS=\"\"\nfi\n\n./bin/fades -r requirements.txt -x pytest -s $TARGET_TESTS\n"
  },
  {
    "path": "testdev.bat",
    "content": "@echo off\n\nrem Copyright 2018 Facundo Batista, Nicolás Demarchi\n\nif not [%*] == [] (\n    set TARGET_TESTS=\"%*\"\n) else (\n    set TARGET_TESTS=fades tests\n)\n\nbin\\fades -r requirements.txt -x pytest -v %TARGET_TESTS%\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# Copyright 2017-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Common code for the tests.\"\"\"\n\nimport os\nfrom tempfile import mkstemp\n\nfrom packaging.requirements import Requirement\n\n\ndef get_tempfile(testcase):\n    \"\"\"Return the name of a temp file that will be removed when the test finishes.\"\"\"\n    # create the file and close its descriptor\n    descriptor, tempfile = mkstemp(prefix=\"test-temp-file\")\n    os.close(descriptor)\n\n    def clean():\n        \"\"\"Clean the file from disk, if still there.\"\"\"\n        if os.path.exists(tempfile):\n            os.remove(tempfile)\n    testcase.addCleanup(clean)\n\n    return tempfile\n\n\ndef create_tempfile(testcase, lines):\n    tempfile = get_tempfile(testcase)\n\n    with open(tempfile, 'w', encoding='utf-8') as f:\n        for line in lines:\n            f.write(line + '\\n')\n\n    return tempfile\n\n\ndef get_python_filepaths(roots):\n    \"\"\"Helper to retrieve paths of Python files.\"\"\"\n    python_paths = []\n    for root in roots:\n        for dirpath, dirnames, filenames in os.walk(root):\n            for filename in filenames:\n                if filename.endswith(\".py\"):\n                    python_paths.append(os.path.join(dirpath, filename))\n    return python_paths\n\n\ndef get_reqs(*items):\n    \"\"\"Transform text requirements into Requirement objects.\"\"\"\n    return [Requirement(item) for item in items]\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "# Copyright 2019-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport uuid\n\nfrom pytest import fixture\n\n\n@fixture(scope=\"function\")\ndef tmp_file(tmp_path):\n    \"\"\"Fixture for a unique tmpfile for each test.\"\"\"\n    yield str(tmp_path / \"testfile\")  # XXX Facundo 2024-04-17: remove str() after #435\n\n\n@fixture(scope=\"function\")\ndef create_tmpfile(tmp_path):\n\n    def add_content(lines):\n        \"\"\"Fixture for a unique tmpfile for each test.\"\"\"\n        namefile = tmp_path / f\"testfile_{uuid.uuid4()}\"\n        with open(namefile, \"w\", encoding=\"utf-8\") as f:\n            for line in lines:\n                f.write(line + \"\\n\")\n\n        return namefile\n\n    yield add_content\n\n\ndef pytest_addoption(parser):\n    \"\"\"Define new pytest command line argument to be used by integration tests.\"\"\"\n    parser.addoption(\"--integtest-pyversion\", action=\"store\")\n"
  },
  {
    "path": "tests/examples/pypi_get_version_fail.json",
    "content": "{\"MALFORMED\": {\"json\": \"1.0\"}}\n"
  },
  {
    "path": "tests/examples/pypi_get_version_ok.json",
    "content": "{\n    \"info\": {\n        \"maintainer\": null,\n        \"docs_url\": null,\n        \"requires_python\": null,\n        \"maintainer_email\": null,\n        \"cheesecake_code_kwalitee_id\": null,\n        \"keywords\": null,\n        \"package_url\": \"http://pypi.python.org/pypi/requests\",\n        \"author\": \"Kenneth Reitz\",\n        \"author_email\": \"me@kennethreitz.com\",\n        \"download_url\": \"UNKNOWN\",\n        \"platform\": \"UNKNOWN\",\n        \"version\": \"2.8.1\",\n        \"cheesecake_documentation_id\": null,\n        \"_pypi_hidden\": false,\n        \"description\": \"Requests: HTTP for Humans\\n=========================\\n\\n.. image:: https://img.shields.io/pypi/v/requests.svg\\n    :target: https://pypi.python.org/pypi/requests\\n\\n.. image:: https://img.shields.io/pypi/dm/requests.svg\\n        :target: https://pypi.python.org/pypi/requests\\n\\n\\n\\n\\nRequests is an Apache2 Licensed HTTP library, written in Python, for human\\nbeings.\\n\\nMost existing Python modules for sending HTTP requests are extremely\\nverbose and cumbersome. Python's builtin urllib2 module provides most of\\nthe HTTP capabilities you should need, but the api is thoroughly broken.\\nIt requires an enormous amount of work (even method overrides) to\\nperform the simplest of tasks.\\n\\nThings shouldn't be this way. Not in Python.\\n\\n.. code-block:: python\\n\\n    >>> r = requests.get('https://api.github.com', auth=('user', 'pass'))\\n    >>> r.status_code\\n    204\\n    >>> r.headers['content-type']\\n    'application/json'\\n    >>> r.text\\n    ...\\n\\nSee `the same code, without Requests <https://gist.github.com/973705>`_.\\n\\nRequests allow you to send HTTP/1.1 requests. You can add headers, form data,\\nmultipart files, and parameters with simple Python dictionaries, and access the\\nresponse data in the same way. It's powered by httplib and `urllib3\\n<https://github.com/shazow/urllib3>`_, but it does all the hard work and crazy\\nhacks for you.\\n\\n\\nFeatures\\n--------\\n\\n- International Domains and URLs\\n- Keep-Alive & Connection Pooling\\n- Sessions with Cookie Persistence\\n- Browser-style SSL Verification\\n- Basic/Digest Authentication\\n- Elegant Key/Value Cookies\\n- Automatic Decompression\\n- Unicode Response Bodies\\n- Multipart File Uploads\\n- Connection Timeouts\\n- Thread-safety\\n- HTTP(S) proxy support\\n\\n\\nInstallation\\n------------\\n\\nTo install Requests, simply:\\n\\n.. code-block:: bash\\n\\n    $ pip install requests\\n\\n\\nDocumentation\\n-------------\\n\\nDocumentation is available at http://docs.python-requests.org/.\\n\\n\\nContribute\\n----------\\n\\n#. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a `Contributor Friendly`_ tag for issues that should be ideal for people who are not very familiar with the codebase yet.\\n#. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it).\\n#. Write a test which shows that the bug was fixed or that the feature works as expected.\\n#. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_.\\n\\n.. _`the repository`: http://github.com/kennethreitz/requests\\n.. _AUTHORS: https://github.com/kennethreitz/requests/blob/master/AUTHORS.rst\\n.. _Contributor Friendly: https://github.com/kennethreitz/requests/issues?direction=desc&labels=Contributor+Friendly&page=1&sort=updated&state=open\\n\\n\\n.. :changelog:\\n\\nRelease History\\n---------------\\n\\n2.8.1 (2015-10-13)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Update certificate bundle to match ``certifi`` 2015.9.6.2's weak certificate\\n  bundle.\\n- Fix a bug in 2.8.0 where requests would raise ``ConnectTimeout`` instead of\\n  ``ConnectionError``\\n- When using the PreparedRequest flow, requests will now correctly respect the\\n  ``json`` parameter. Broken in 2.8.0.\\n- When using the PreparedRequest flow, requests will now correctly handle a\\n  Unicode-string method name on Python 2. Broken in 2.8.0.\\n\\n2.8.0 (2015-10-05)\\n++++++++++++++++++\\n\\n**Minor Improvements** (Backwards Compatible)\\n\\n- Requests now supports per-host proxies. This allows the ``proxies``\\n  dictionary to have entries of the form\\n  ``{'<scheme>://<hostname>': '<proxy>'}``. Host-specific proxies will be used\\n  in preference to the previously-supported scheme-specific ones, but the\\n  previous syntax will continue to work.\\n- ``Response.raise_for_status`` now prints the URL that failed as part of the\\n  exception message.\\n- ``requests.utils.get_netrc_auth`` now takes an ``raise_errors`` kwarg,\\n  defaulting to ``False``. When ``True``, errors parsing ``.netrc`` files cause\\n  exceptions to be thrown.\\n- Change to bundled projects import logic to make it easier to unbundle\\n  requests downstream.\\n- Changed the default User-Agent string to avoid leaking data on Linux: now\\n  contains only the requests version.\\n\\n**Bugfixes**\\n\\n- The ``json`` parameter to ``post()`` and friends will now only be used if\\n  neither ``data`` nor ``files`` are present, consistent with the\\n  documentation.\\n- We now ignore empty fields in the ``NO_PROXY`` enviroment variable.\\n- Fixed problem where ``httplib.BadStatusLine`` would get raised if combining\\n  ``stream=True`` with ``contextlib.closing``.\\n- Prevented bugs where we would attempt to return the same connection back to\\n  the connection pool twice when sending a Chunked body.\\n- Miscellaneous minor internal changes.\\n- Digest Auth support is now thread safe.\\n\\n**Updates**\\n\\n- Updated urllib3 to 1.12.\\n\\n2.7.0 (2015-05-03)\\n++++++++++++++++++\\n\\nThis is the first release that follows our new release process. For more, see\\n`our documentation\\n<http://docs.python-requests.org/en/latest/community/release-process/>`_.\\n\\n**Bugfixes**\\n\\n- Updated urllib3 to 1.10.4, resolving several bugs involving chunked transfer\\n  encoding and response framing.\\n\\n2.6.2 (2015-04-23)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Fix regression where compressed data that was sent as chunked data was not\\n  properly decompressed. (#2561)\\n\\n2.6.1 (2015-04-22)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Remove VendorAlias import machinery introduced in v2.5.2.\\n\\n- Simplify the PreparedRequest.prepare API: We no longer require the user to\\n  pass an empty list to the hooks keyword argument. (c.f. #2552)\\n\\n- Resolve redirects now receives and forwards all of the original arguments to\\n  the adapter. (#2503)\\n\\n- Handle UnicodeDecodeErrors when trying to deal with a unicode URL that\\n  cannot be encoded in ASCII. (#2540)\\n\\n- Populate the parsed path of the URI field when performing Digest\\n  Authentication. (#2426)\\n\\n- Copy a PreparedRequest's CookieJar more reliably when it is not an instance\\n  of RequestsCookieJar. (#2527)\\n\\n2.6.0 (2015-03-14)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- CVE-2015-2296: Fix handling of cookies on redirect. Previously a cookie\\n  without a host value set would use the hostname for the redirected URL\\n  exposing requests users to session fixation attacks and potentially cookie\\n  stealing. This was disclosed privately by Matthew Daley of\\n  `BugFuzz <https://bugfuzz.com>`_. This affects all versions of requests from\\n  v2.1.0 to v2.5.3 (inclusive on both ends).\\n\\n- Fix error when requests is an ``install_requires`` dependency and ``python\\n  setup.py test`` is run. (#2462)\\n\\n- Fix error when urllib3 is unbundled and requests continues to use the\\n  vendored import location.\\n\\n- Include fixes to ``urllib3``'s header handling.\\n\\n- Requests' handling of unvendored dependencies is now more restrictive.\\n\\n**Features and Improvements**\\n\\n- Support bytearrays when passed as parameters in the ``files`` argument.\\n  (#2468)\\n\\n- Avoid data duplication when creating a request with ``str``, ``bytes``, or\\n  ``bytearray`` input to the ``files`` argument.\\n\\n2.5.3 (2015-02-24)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Revert changes to our vendored certificate bundle. For more context see\\n  (#2455, #2456, and http://bugs.python.org/issue23476)\\n\\n2.5.2 (2015-02-23)\\n++++++++++++++++++\\n\\n**Features and Improvements**\\n\\n- Add sha256 fingerprint support. (`shazow/urllib3#540`_)\\n\\n- Improve the performance of headers. (`shazow/urllib3#544`_)\\n\\n**Bugfixes**\\n\\n- Copy pip's import machinery. When downstream redistributors remove\\n  requests.packages.urllib3 the import machinery will continue to let those\\n  same symbols work. Example usage in requests' documentation and 3rd-party\\n  libraries relying on the vendored copies of urllib3 will work without having\\n  to fallback to the system urllib3.\\n\\n- Attempt to quote parts of the URL on redirect if unquoting and then quoting\\n  fails. (#2356)\\n\\n- Fix filename type check for multipart form-data uploads. (#2411)\\n\\n- Properly handle the case where a server issuing digest authentication\\n  challenges provides both auth and auth-int qop-values. (#2408)\\n\\n- Fix a socket leak. (`shazow/urllib3#549`_)\\n\\n- Fix multiple ``Set-Cookie`` headers properly. (`shazow/urllib3#534`_)\\n\\n- Disable the built-in hostname verification. (`shazow/urllib3#526`_)\\n\\n- Fix the behaviour of decoding an exhausted stream. (`shazow/urllib3#535`_)\\n\\n**Security**\\n\\n- Pulled in an updated ``cacert.pem``.\\n\\n- Drop RC4 from the default cipher list. (`shazow/urllib3#551`_)\\n\\n.. _shazow/urllib3#551: https://github.com/shazow/urllib3/pull/551\\n.. _shazow/urllib3#549: https://github.com/shazow/urllib3/pull/549\\n.. _shazow/urllib3#544: https://github.com/shazow/urllib3/pull/544\\n.. _shazow/urllib3#540: https://github.com/shazow/urllib3/pull/540\\n.. _shazow/urllib3#535: https://github.com/shazow/urllib3/pull/535\\n.. _shazow/urllib3#534: https://github.com/shazow/urllib3/pull/534\\n.. _shazow/urllib3#526: https://github.com/shazow/urllib3/pull/526\\n\\n2.5.1 (2014-12-23)\\n++++++++++++++++++\\n\\n**Behavioural Changes**\\n\\n- Only catch HTTPErrors in raise_for_status (#2382)\\n\\n**Bugfixes**\\n\\n- Handle LocationParseError from urllib3 (#2344)\\n- Handle file-like object filenames that are not strings (#2379)\\n- Unbreak HTTPDigestAuth handler. Allow new nonces to be negotiated (#2389)\\n\\n2.5.0 (2014-12-01)\\n++++++++++++++++++\\n\\n**Improvements**\\n\\n- Allow usage of urllib3's Retry object with HTTPAdapters (#2216)\\n- The ``iter_lines`` method on a response now accepts a delimiter with which\\n  to split the content (#2295)\\n\\n**Behavioural Changes**\\n\\n- Add deprecation warnings to functions in requests.utils that will be removed\\n  in 3.0 (#2309)\\n- Sessions used by the functional API are always closed (#2326)\\n- Restrict requests to HTTP/1.1 and HTTP/1.0 (stop accepting HTTP/0.9) (#2323)\\n\\n**Bugfixes**\\n\\n- Only parse the URL once (#2353)\\n- Allow Content-Length header to always be overridden (#2332)\\n- Properly handle files in HTTPDigestAuth (#2333)\\n- Cap redirect_cache size to prevent memory abuse (#2299)\\n- Fix HTTPDigestAuth handling of redirects after authenticating successfully\\n  (#2253)\\n- Fix crash with custom method parameter to Session.request (#2317)\\n- Fix how Link headers are parsed using the regular expression library (#2271)\\n\\n**Documentation**\\n\\n- Add more references for interlinking (#2348)\\n- Update CSS for theme (#2290)\\n- Update width of buttons and sidebar (#2289)\\n- Replace references of Gittip with Gratipay (#2282)\\n- Add link to changelog in sidebar (#2273)\\n\\n2.4.3 (2014-10-06)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Unicode URL improvements for Python 2.\\n- Re-order JSON param for backwards compat.\\n- Automatically defrag authentication schemes from host/pass URIs. (`#2249 <https://github.com/kennethreitz/requests/issues/2249>`_)\\n\\n\\n2.4.2 (2014-10-05)\\n++++++++++++++++++\\n\\n**Improvements**\\n\\n- FINALLY! Add json parameter for uploads! (`#2258 <https://github.com/kennethreitz/requests/pull/2258>`_)\\n- Support for bytestring URLs on Python 3.x (`#2238 <https://github.com/kennethreitz/requests/pull/2238>`_)\\n\\n**Bugfixes**\\n\\n- Avoid getting stuck in a loop (`#2244 <https://github.com/kennethreitz/requests/pull/2244>`_)\\n- Multiple calls to iter* fail with unhelpful error. (`#2240 <https://github.com/kennethreitz/requests/issues/2240>`_, `#2241 <https://github.com/kennethreitz/requests/issues/2241>`_)\\n\\n**Documentation**\\n\\n- Correct redirection introduction (`#2245 <https://github.com/kennethreitz/requests/pull/2245/>`_)\\n- Added example of how to send multiple files in one request. (`#2227 <https://github.com/kennethreitz/requests/pull/2227/>`_)\\n- Clarify how to pass a custom set of CAs (`#2248 <https://github.com/kennethreitz/requests/pull/2248/>`_)\\n\\n\\n\\n2.4.1 (2014-09-09)\\n++++++++++++++++++\\n\\n- Now has a \\\"security\\\" package extras set, ``$ pip install requests[security]``\\n- Requests will now use Certifi if it is available.\\n- Capture and re-raise urllib3 ProtocolError\\n- Bugfix for responses that attempt to redirect to themselves forever (wtf?).\\n\\n\\n2.4.0 (2014-08-29)\\n++++++++++++++++++\\n\\n**Behavioral Changes**\\n\\n- ``Connection: keep-alive`` header is now sent automatically.\\n\\n**Improvements**\\n\\n- Support for connect timeouts! Timeout now accepts a tuple (connect, read) which is used to set individual connect and read timeouts.\\n- Allow copying of PreparedRequests without headers/cookies.\\n- Updated bundled urllib3 version.\\n- Refactored settings loading from environment -- new `Session.merge_environment_settings`.\\n- Handle socket errors in iter_content.\\n\\n\\n2.3.0 (2014-05-16)\\n++++++++++++++++++\\n\\n**API Changes**\\n\\n- New ``Response`` property ``is_redirect``, which is true when the\\n  library could have processed this response as a redirection (whether\\n  or not it actually did).\\n- The ``timeout`` parameter now affects requests with both ``stream=True`` and\\n  ``stream=False`` equally.\\n- The change in v2.0.0 to mandate explicit proxy schemes has been reverted.\\n  Proxy schemes now default to ``http://``.\\n- The ``CaseInsensitiveDict`` used for HTTP headers now behaves like a normal\\n  dictionary when references as string or viewed in the interpreter.\\n\\n**Bugfixes**\\n\\n- No longer expose Authorization or Proxy-Authorization headers on redirect.\\n  Fix CVE-2014-1829 and CVE-2014-1830 respectively.\\n- Authorization is re-evaluated each redirect.\\n- On redirect, pass url as native strings.\\n- Fall-back to autodetected encoding for JSON when Unicode detection fails.\\n- Headers set to ``None`` on the ``Session`` are now correctly not sent.\\n- Correctly honor ``decode_unicode`` even if it wasn't used earlier in the same\\n  response.\\n- Stop advertising ``compress`` as a supported Content-Encoding.\\n- The ``Response.history`` parameter is now always a list.\\n- Many, many ``urllib3`` bugfixes.\\n\\n2.2.1 (2014-01-23)\\n++++++++++++++++++\\n\\n**Bugfixes**\\n\\n- Fixes incorrect parsing of proxy credentials that contain a literal or encoded '#' character.\\n- Assorted urllib3 fixes.\\n\\n2.2.0 (2014-01-09)\\n++++++++++++++++++\\n\\n**API Changes**\\n\\n- New exception: ``ContentDecodingError``. Raised instead of ``urllib3``\\n  ``DecodeError`` exceptions.\\n\\n**Bugfixes**\\n\\n- Avoid many many exceptions from the buggy implementation of ``proxy_bypass`` on OS X in Python 2.6.\\n- Avoid crashing when attempting to get authentication credentials from ~/.netrc when running as a user without a home directory.\\n- Use the correct pool size for pools of connections to proxies.\\n- Fix iteration of ``CookieJar`` objects.\\n- Ensure that cookies are persisted over redirect.\\n- Switch back to using chardet, since it has merged with charade.\\n\\n2.1.0 (2013-12-05)\\n++++++++++++++++++\\n\\n- Updated CA Bundle, of course.\\n- Cookies set on individual Requests through a ``Session`` (e.g. via ``Session.get()``) are no longer persisted to the ``Session``.\\n- Clean up connections when we hit problems during chunked upload, rather than leaking them.\\n- Return connections to the pool when a chunked upload is successful, rather than leaking it.\\n- Match the HTTPbis recommendation for HTTP 301 redirects.\\n- Prevent hanging when using streaming uploads and Digest Auth when a 401 is received.\\n- Values of headers set by Requests are now always the native string type.\\n- Fix previously broken SNI support.\\n- Fix accessing HTTP proxies using proxy authentication.\\n- Unencode HTTP Basic usernames and passwords extracted from URLs.\\n- Support for IP address ranges for no_proxy environment variable\\n- Parse headers correctly when users override the default ``Host:`` header.\\n- Avoid munging the URL in case of case-sensitive servers.\\n- Looser URL handling for non-HTTP/HTTPS urls.\\n- Accept unicode methods in Python 2.6 and 2.7.\\n- More resilient cookie handling.\\n- Make ``Response`` objects pickleable.\\n- Actually added MD5-sess to Digest Auth instead of pretending to like last time.\\n- Updated internal urllib3.\\n- Fixed @Lukasa's lack of taste.\\n\\n2.0.1 (2013-10-24)\\n++++++++++++++++++\\n\\n- Updated included CA Bundle with new mistrusts and automated process for the future\\n- Added MD5-sess to Digest Auth\\n- Accept per-file headers in multipart file POST messages.\\n- Fixed: Don't send the full URL on CONNECT messages.\\n- Fixed: Correctly lowercase a redirect scheme.\\n- Fixed: Cookies not persisted when set via functional API.\\n- Fixed: Translate urllib3 ProxyError into a requests ProxyError derived from ConnectionError.\\n- Updated internal urllib3 and chardet.\\n\\n2.0.0 (2013-09-24)\\n++++++++++++++++++\\n\\n**API Changes:**\\n\\n- Keys in the Headers dictionary are now native strings on all Python versions,\\n  i.e. bytestrings on Python 2, unicode on Python 3.\\n- Proxy URLs now *must* have an explicit scheme. A ``MissingSchema`` exception\\n  will be raised if they don't.\\n- Timeouts now apply to read time if ``Stream=False``.\\n- ``RequestException`` is now a subclass of ``IOError``, not ``RuntimeError``.\\n- Added new method to ``PreparedRequest`` objects: ``PreparedRequest.copy()``.\\n- Added new method to ``Session`` objects: ``Session.update_request()``. This\\n  method updates a ``Request`` object with the data (e.g. cookies) stored on\\n  the ``Session``.\\n- Added new method to ``Session`` objects: ``Session.prepare_request()``. This\\n  method updates and prepares a ``Request`` object, and returns the\\n  corresponding ``PreparedRequest`` object.\\n- Added new method to ``HTTPAdapter`` objects: ``HTTPAdapter.proxy_headers()``.\\n  This should not be called directly, but improves the subclass interface.\\n- ``httplib.IncompleteRead`` exceptions caused by incorrect chunked encoding\\n  will now raise a Requests ``ChunkedEncodingError`` instead.\\n- Invalid percent-escape sequences now cause a Requests ``InvalidURL``\\n  exception to be raised.\\n- HTTP 208 no longer uses reason phrase ``\\\"im_used\\\"``. Correctly uses\\n  ``\\\"already_reported\\\"``.\\n- HTTP 226 reason added (``\\\"im_used\\\"``).\\n\\n**Bugfixes:**\\n\\n- Vastly improved proxy support, including the CONNECT verb. Special thanks to\\n  the many contributors who worked towards this improvement.\\n- Cookies are now properly managed when 401 authentication responses are\\n  received.\\n- Chunked encoding fixes.\\n- Support for mixed case schemes.\\n- Better handling of streaming downloads.\\n- Retrieve environment proxies from more locations.\\n- Minor cookies fixes.\\n- Improved redirect behaviour.\\n- Improved streaming behaviour, particularly for compressed data.\\n- Miscellaneous small Python 3 text encoding bugs.\\n- ``.netrc`` no longer overrides explicit auth.\\n- Cookies set by hooks are now correctly persisted on Sessions.\\n- Fix problem with cookies that specify port numbers in their host field.\\n- ``BytesIO`` can be used to perform streaming uploads.\\n- More generous parsing of the ``no_proxy`` environment variable.\\n- Non-string objects can be passed in data values alongside files.\\n\\n1.2.3 (2013-05-25)\\n++++++++++++++++++\\n\\n- Simple packaging fix\\n\\n\\n1.2.2 (2013-05-23)\\n++++++++++++++++++\\n\\n- Simple packaging fix\\n\\n\\n1.2.1 (2013-05-20)\\n++++++++++++++++++\\n\\n- 301 and 302 redirects now change the verb to GET for all verbs, not just\\n  POST, improving browser compatibility.\\n- Python 3.3.2 compatibility\\n- Always percent-encode location headers\\n- Fix connection adapter matching to be most-specific first\\n- new argument to the default connection adapter for passing a block argument\\n- prevent a KeyError when there's no link headers\\n\\n1.2.0 (2013-03-31)\\n++++++++++++++++++\\n\\n- Fixed cookies on sessions and on requests\\n- Significantly change how hooks are dispatched - hooks now receive all the\\n  arguments specified by the user when making a request so hooks can make a\\n  secondary request with the same parameters. This is especially necessary for\\n  authentication handler authors\\n- certifi support was removed\\n- Fixed bug where using OAuth 1 with body ``signature_type`` sent no data\\n- Major proxy work thanks to @Lukasa including parsing of proxy authentication\\n  from the proxy url\\n- Fix DigestAuth handling too many 401s\\n- Update vendored urllib3 to include SSL bug fixes\\n- Allow keyword arguments to be passed to ``json.loads()`` via the\\n  ``Response.json()`` method\\n- Don't send ``Content-Length`` header by default on ``GET`` or ``HEAD``\\n  requests\\n- Add ``elapsed`` attribute to ``Response`` objects to time how long a request\\n  took.\\n- Fix ``RequestsCookieJar``\\n- Sessions and Adapters are now picklable, i.e., can be used with the\\n  multiprocessing library\\n- Update charade to version 1.0.3\\n\\nThe change in how hooks are dispatched will likely cause a great deal of\\nissues.\\n\\n1.1.0 (2013-01-10)\\n++++++++++++++++++\\n\\n- CHUNKED REQUESTS\\n- Support for iterable response bodies\\n- Assume servers persist redirect params\\n- Allow explicit content types to be specified for file data\\n- Make merge_kwargs case-insensitive when looking up keys\\n\\n1.0.3 (2012-12-18)\\n++++++++++++++++++\\n\\n- Fix file upload encoding bug\\n- Fix cookie behavior\\n\\n1.0.2 (2012-12-17)\\n++++++++++++++++++\\n\\n- Proxy fix for HTTPAdapter.\\n\\n1.0.1 (2012-12-17)\\n++++++++++++++++++\\n\\n- Cert verification exception bug.\\n- Proxy fix for HTTPAdapter.\\n\\n1.0.0 (2012-12-17)\\n++++++++++++++++++\\n\\n- Massive Refactor and Simplification\\n- Switch to Apache 2.0 license\\n- Swappable Connection Adapters\\n- Mountable Connection Adapters\\n- Mutable ProcessedRequest chain\\n- /s/prefetch/stream\\n- Removal of all configuration\\n- Standard library logging\\n- Make Response.json() callable, not property.\\n- Usage of new charade project, which provides python 2 and 3 simultaneous chardet.\\n- Removal of all hooks except 'response'\\n- Removal of all authentication helpers (OAuth, Kerberos)\\n\\nThis is not a backwards compatible change.\\n\\n0.14.2 (2012-10-27)\\n+++++++++++++++++++\\n\\n- Improved mime-compatible JSON handling\\n- Proxy fixes\\n- Path hack fixes\\n- Case-Insensistive Content-Encoding headers\\n- Support for CJK parameters in form posts\\n\\n\\n0.14.1 (2012-10-01)\\n+++++++++++++++++++\\n\\n- Python 3.3 Compatibility\\n- Simply default accept-encoding\\n- Bugfixes\\n\\n\\n0.14.0 (2012-09-02)\\n++++++++++++++++++++\\n\\n- No more iter_content errors if already downloaded.\\n\\n0.13.9 (2012-08-25)\\n+++++++++++++++++++\\n\\n- Fix for OAuth + POSTs\\n- Remove exception eating from dispatch_hook\\n- General bugfixes\\n\\n0.13.8 (2012-08-21)\\n+++++++++++++++++++\\n\\n- Incredible Link header support :)\\n\\n0.13.7 (2012-08-19)\\n+++++++++++++++++++\\n\\n- Support for (key, value) lists everywhere.\\n- Digest Authentication improvements.\\n- Ensure proxy exclusions work properly.\\n- Clearer UnicodeError exceptions.\\n- Automatic casting of URLs to strings (fURL and such)\\n- Bugfixes.\\n\\n0.13.6 (2012-08-06)\\n+++++++++++++++++++\\n\\n- Long awaited fix for hanging connections!\\n\\n0.13.5 (2012-07-27)\\n+++++++++++++++++++\\n\\n- Packaging fix\\n\\n0.13.4 (2012-07-27)\\n+++++++++++++++++++\\n\\n- GSSAPI/Kerberos authentication!\\n- App Engine 2.7 Fixes!\\n- Fix leaking connections (from urllib3 update)\\n- OAuthlib path hack fix\\n- OAuthlib URL parameters fix.\\n\\n0.13.3 (2012-07-12)\\n+++++++++++++++++++\\n\\n- Use simplejson if available.\\n- Do not hide SSLErrors behind Timeouts.\\n- Fixed param handling with urls containing fragments.\\n- Significantly improved information in User Agent.\\n- client certificates are ignored when verify=False\\n\\n0.13.2 (2012-06-28)\\n+++++++++++++++++++\\n\\n- Zero dependencies (once again)!\\n- New: Response.reason\\n- Sign querystring parameters in OAuth 1.0\\n- Client certificates no longer ignored when verify=False\\n- Add openSUSE certificate support\\n\\n0.13.1 (2012-06-07)\\n+++++++++++++++++++\\n\\n- Allow passing a file or file-like object as data.\\n- Allow hooks to return responses that indicate errors.\\n- Fix Response.text and Response.json for body-less responses.\\n\\n0.13.0 (2012-05-29)\\n+++++++++++++++++++\\n\\n- Removal of Requests.async in favor of `grequests <https://github.com/kennethreitz/grequests>`_\\n- Allow disabling of cookie persistiance.\\n- New implementation of safe_mode\\n- cookies.get now supports default argument\\n- Session cookies not saved when Session.request is called with return_response=False\\n- Env: no_proxy support.\\n- RequestsCookieJar improvements.\\n- Various bug fixes.\\n\\n0.12.1 (2012-05-08)\\n+++++++++++++++++++\\n\\n- New ``Response.json`` property.\\n- Ability to add string file uploads.\\n- Fix out-of-range issue with iter_lines.\\n- Fix iter_content default size.\\n- Fix POST redirects containing files.\\n\\n0.12.0 (2012-05-02)\\n+++++++++++++++++++\\n\\n- EXPERIMENTAL OAUTH SUPPORT!\\n- Proper CookieJar-backed cookies interface with awesome dict-like interface.\\n- Speed fix for non-iterated content chunks.\\n- Move ``pre_request`` to a more usable place.\\n- New ``pre_send`` hook.\\n- Lazily encode data, params, files.\\n- Load system Certificate Bundle if ``certify`` isn't available.\\n- Cleanups, fixes.\\n\\n0.11.2 (2012-04-22)\\n+++++++++++++++++++\\n\\n- Attempt to use the OS's certificate bundle if ``certifi`` isn't available.\\n- Infinite digest auth redirect fix.\\n- Multi-part file upload improvements.\\n- Fix decoding of invalid %encodings in URLs.\\n- If there is no content in a response don't throw an error the second time that content is attempted to be read.\\n- Upload data on redirects.\\n\\n0.11.1 (2012-03-30)\\n+++++++++++++++++++\\n\\n* POST redirects now break RFC to do what browsers do: Follow up with a GET.\\n* New ``strict_mode`` configuration to disable new redirect behavior.\\n\\n\\n0.11.0 (2012-03-14)\\n+++++++++++++++++++\\n\\n* Private SSL Certificate support\\n* Remove select.poll from Gevent monkeypatching\\n* Remove redundant generator for chunked transfer encoding\\n* Fix: Response.ok raises Timeout Exception in safe_mode\\n\\n0.10.8 (2012-03-09)\\n+++++++++++++++++++\\n\\n* Generate chunked ValueError fix\\n* Proxy configuration by environment variables\\n* Simplification of iter_lines.\\n* New `trust_env` configuration for disabling system/environment hints.\\n* Suppress cookie errors.\\n\\n0.10.7 (2012-03-07)\\n+++++++++++++++++++\\n\\n* `encode_uri` = False\\n\\n0.10.6 (2012-02-25)\\n+++++++++++++++++++\\n\\n* Allow '=' in cookies.\\n\\n0.10.5 (2012-02-25)\\n+++++++++++++++++++\\n\\n* Response body with 0 content-length fix.\\n* New async.imap.\\n* Don't fail on netrc.\\n\\n\\n0.10.4 (2012-02-20)\\n+++++++++++++++++++\\n\\n* Honor netrc.\\n\\n0.10.3 (2012-02-20)\\n+++++++++++++++++++\\n\\n* HEAD requests don't follow redirects anymore.\\n* raise_for_status() doesn't raise for 3xx anymore.\\n* Make Session objects picklable.\\n* ValueError for invalid schema URLs.\\n\\n0.10.2 (2012-01-15)\\n+++++++++++++++++++\\n\\n* Vastly improved URL quoting.\\n* Additional allowed cookie key values.\\n* Attempted fix for \\\"Too many open files\\\" Error\\n* Replace unicode errors on first pass, no need for second pass.\\n* Append '/' to bare-domain urls before query insertion.\\n* Exceptions now inherit from RuntimeError.\\n* Binary uploads + auth fix.\\n* Bugfixes.\\n\\n\\n0.10.1 (2012-01-23)\\n+++++++++++++++++++\\n\\n* PYTHON 3 SUPPORT!\\n* Dropped 2.5 Support. (*Backwards Incompatible*)\\n\\n0.10.0 (2012-01-21)\\n+++++++++++++++++++\\n\\n* ``Response.content`` is now bytes-only. (*Backwards Incompatible*)\\n* New ``Response.text`` is unicode-only.\\n* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Response.text`` will guess an encoding.\\n* Default to ISO-8859-1 (Western) encoding for \\\"text\\\" subtypes.\\n* Removal of `decode_unicode`. (*Backwards Incompatible*)\\n* New multiple-hooks system.\\n* New ``Response.register_hook`` for registering hooks within the pipeline.\\n* ``Response.url`` is now Unicode.\\n\\n0.9.3 (2012-01-18)\\n++++++++++++++++++\\n\\n* SSL verify=False bugfix (apparent on windows machines).\\n\\n0.9.2 (2012-01-18)\\n++++++++++++++++++\\n\\n* Asynchronous async.send method.\\n* Support for proper chunk streams with boundaries.\\n* session argument for Session classes.\\n* Print entire hook tracebacks, not just exception instance.\\n* Fix response.iter_lines from pending next line.\\n* Fix but in HTTP-digest auth w/ URI having query strings.\\n* Fix in Event Hooks section.\\n* Urllib3 update.\\n\\n\\n0.9.1 (2012-01-06)\\n++++++++++++++++++\\n\\n* danger_mode for automatic Response.raise_for_status()\\n* Response.iter_lines refactor\\n\\n0.9.0 (2011-12-28)\\n++++++++++++++++++\\n\\n* verify ssl is default.\\n\\n\\n0.8.9 (2011-12-28)\\n++++++++++++++++++\\n\\n* Packaging fix.\\n\\n\\n0.8.8 (2011-12-28)\\n++++++++++++++++++\\n\\n* SSL CERT VERIFICATION!\\n* Release of Cerifi: Mozilla's cert list.\\n* New 'verify' argument for SSL requests.\\n* Urllib3 update.\\n\\n0.8.7 (2011-12-24)\\n++++++++++++++++++\\n\\n* iter_lines last-line truncation fix\\n* Force safe_mode for async requests\\n* Handle safe_mode exceptions more consistently\\n* Fix iteration on null responses in safe_mode\\n\\n0.8.6 (2011-12-18)\\n++++++++++++++++++\\n\\n* Socket timeout fixes.\\n* Proxy Authorization support.\\n\\n0.8.5 (2011-12-14)\\n++++++++++++++++++\\n\\n* Response.iter_lines!\\n\\n0.8.4 (2011-12-11)\\n++++++++++++++++++\\n\\n* Prefetch bugfix.\\n* Added license to installed version.\\n\\n0.8.3 (2011-11-27)\\n++++++++++++++++++\\n\\n* Converted auth system to use simpler callable objects.\\n* New session parameter to API methods.\\n* Display full URL while logging.\\n\\n0.8.2 (2011-11-19)\\n++++++++++++++++++\\n\\n* New Unicode decoding system, based on over-ridable `Response.encoding`.\\n* Proper URL slash-quote handling.\\n* Cookies with ``[``, ``]``, and ``_`` allowed.\\n\\n0.8.1 (2011-11-15)\\n++++++++++++++++++\\n\\n* URL Request path fix\\n* Proxy fix.\\n* Timeouts fix.\\n\\n0.8.0 (2011-11-13)\\n++++++++++++++++++\\n\\n* Keep-alive support!\\n* Complete removal of Urllib2\\n* Complete removal of Poster\\n* Complete removal of CookieJars\\n* New ConnectionError raising\\n* Safe_mode for error catching\\n* prefetch parameter for request methods\\n* OPTION method\\n* Async pool size throttling\\n* File uploads send real names\\n* Vendored in urllib3\\n\\n0.7.6 (2011-11-07)\\n++++++++++++++++++\\n\\n* Digest authentication bugfix (attach query data to path)\\n\\n0.7.5 (2011-11-04)\\n++++++++++++++++++\\n\\n* Response.content = None if there was an invalid response.\\n* Redirection auth handling.\\n\\n0.7.4 (2011-10-26)\\n++++++++++++++++++\\n\\n* Session Hooks fix.\\n\\n0.7.3 (2011-10-23)\\n++++++++++++++++++\\n\\n* Digest Auth fix.\\n\\n\\n0.7.2 (2011-10-23)\\n++++++++++++++++++\\n\\n* PATCH Fix.\\n\\n\\n0.7.1 (2011-10-23)\\n++++++++++++++++++\\n\\n* Move away from urllib2 authentication handling.\\n* Fully Remove AuthManager, AuthObject, &c.\\n* New tuple-based auth system with handler callbacks.\\n\\n\\n0.7.0 (2011-10-22)\\n++++++++++++++++++\\n\\n* Sessions are now the primary interface.\\n* Deprecated InvalidMethodException.\\n* PATCH fix.\\n* New config system (no more global settings).\\n\\n\\n0.6.6 (2011-10-19)\\n++++++++++++++++++\\n\\n* Session parameter bugfix (params merging).\\n\\n\\n0.6.5 (2011-10-18)\\n++++++++++++++++++\\n\\n* Offline (fast) test suite.\\n* Session dictionary argument merging.\\n\\n\\n0.6.4 (2011-10-13)\\n++++++++++++++++++\\n\\n* Automatic decoding of unicode, based on HTTP Headers.\\n* New ``decode_unicode`` setting.\\n* Removal of ``r.read/close`` methods.\\n* New ``r.faw`` interface for advanced response usage.*\\n* Automatic expansion of parameterized headers.\\n\\n\\n0.6.3 (2011-10-13)\\n++++++++++++++++++\\n\\n* Beautiful ``requests.async`` module, for making async requests w/ gevent.\\n\\n\\n0.6.2 (2011-10-09)\\n++++++++++++++++++\\n\\n* GET/HEAD obeys allow_redirects=False.\\n\\n\\n0.6.1 (2011-08-20)\\n++++++++++++++++++\\n\\n* Enhanced status codes experience ``\\\\o/``\\n* Set a maximum number of redirects (``settings.max_redirects``)\\n* Full Unicode URL support\\n* Support for protocol-less redirects.\\n* Allow for arbitrary request types.\\n* Bugfixes\\n\\n\\n0.6.0 (2011-08-17)\\n++++++++++++++++++\\n\\n* New callback hook system\\n* New persistent sessions object and context manager\\n* Transparent Dict-cookie handling\\n* Status code reference object\\n* Removed Response.cached\\n* Added Response.request\\n* All args are kwargs\\n* Relative redirect support\\n* HTTPError handling improvements\\n* Improved https testing\\n* Bugfixes\\n\\n\\n0.5.1 (2011-07-23)\\n++++++++++++++++++\\n\\n* International Domain Name Support!\\n* Access headers without fetching entire body (``read()``)\\n* Use lists as dicts for parameters\\n* Add Forced Basic Authentication\\n* Forced Basic is default authentication type\\n* ``python-requests.org`` default User-Agent header\\n* CaseInsensitiveDict lower-case caching\\n* Response.history bugfix\\n\\n\\n0.5.0 (2011-06-21)\\n++++++++++++++++++\\n\\n* PATCH Support\\n* Support for Proxies\\n* HTTPBin Test Suite\\n* Redirect Fixes\\n* settings.verbose stream writing\\n* Querystrings for all methods\\n* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicitly raised\\n  ``r.requests.get('hwe://blah'); r.raise_for_status()``\\n\\n\\n0.4.1 (2011-05-22)\\n++++++++++++++++++\\n\\n* Improved Redirection Handling\\n* New 'allow_redirects' param for following non-GET/HEAD Redirects\\n* Settings module refactoring\\n\\n\\n0.4.0 (2011-05-15)\\n++++++++++++++++++\\n\\n* Response.history: list of redirected responses\\n* Case-Insensitive Header Dictionaries!\\n* Unicode URLs\\n\\n\\n0.3.4 (2011-05-14)\\n++++++++++++++++++\\n\\n* Urllib2 HTTPAuthentication Recursion fix (Basic/Digest)\\n* Internal Refactor\\n* Bytes data upload Bugfix\\n\\n\\n\\n0.3.3 (2011-05-12)\\n++++++++++++++++++\\n\\n* Request timeouts\\n* Unicode url-encoded data\\n* Settings context manager and module\\n\\n\\n0.3.2 (2011-04-15)\\n++++++++++++++++++\\n\\n* Automatic Decompression of GZip Encoded Content\\n* AutoAuth Support for Tupled HTTP Auth\\n\\n\\n0.3.1 (2011-04-01)\\n++++++++++++++++++\\n\\n* Cookie Changes\\n* Response.read()\\n* Poster fix\\n\\n\\n0.3.0 (2011-02-25)\\n++++++++++++++++++\\n\\n* Automatic Authentication API Change\\n* Smarter Query URL Parameterization\\n* Allow file uploads and POST data together\\n* New Authentication Manager System\\n    - Simpler Basic HTTP System\\n    - Supports all build-in urllib2 Auths\\n    - Allows for custom Auth Handlers\\n\\n\\n0.2.4 (2011-02-19)\\n++++++++++++++++++\\n\\n* Python 2.5 Support\\n* PyPy-c v1.4 Support\\n* Auto-Authentication tests\\n* Improved Request object constructor\\n\\n0.2.3 (2011-02-15)\\n++++++++++++++++++\\n\\n* New HTTPHandling Methods\\n    - Response.__nonzero__ (false if bad HTTP Status)\\n    - Response.ok (True if expected HTTP Status)\\n    - Response.error (Logged HTTPError if bad HTTP Status)\\n    - Response.raise_for_status() (Raises stored HTTPError)\\n\\n\\n0.2.2 (2011-02-14)\\n++++++++++++++++++\\n\\n* Still handles request in the event of an HTTPError. (Issue #2)\\n* Eventlet and Gevent Monkeypatch support.\\n* Cookie Support (Issue #1)\\n\\n\\n0.2.1 (2011-02-14)\\n++++++++++++++++++\\n\\n* Added file attribute to POST and PUT requests for multipart-encode file uploads.\\n* Added Request.url attribute for context and redirects\\n\\n\\n0.2.0 (2011-02-14)\\n++++++++++++++++++\\n\\n* Birth!\\n\\n\\n0.0.1 (2011-02-13)\\n++++++++++++++++++\\n\\n* Frustration\\n* Conception\",\n        \"release_url\": \"http://pypi.python.org/pypi/requests/2.8.1\",\n        \"downloads\": {\n            \"last_month\": 5397123,\n            \"last_week\": 1226167,\n            \"last_day\": 232917\n        },\n        \"_pypi_ordering\": 98,\n        \"classifiers\": [\n            \"Development Status :: 5 - Production/Stable\",\n            \"Intended Audience :: Developers\",\n            \"License :: OSI Approved :: Apache Software License\",\n            \"Natural Language :: English\",\n            \"Programming Language :: Python\",\n            \"Programming Language :: Python :: 2.7\",\n            \"Programming Language :: Python :: 3\",\n            \"Programming Language :: Python :: 3.3\",\n            \"Programming Language :: Python :: 3.4\",\n            \"Programming Language :: Python :: 3.5\"\n        ],\n        \"name\": \"requests\",\n        \"bugtrack_url\": null,\n        \"license\": \"Apache 2.0\",\n        \"summary\": \"Python HTTP for Humans.\",\n        \"home_page\": \"http://python-requests.org\",\n        \"cheesecake_installability_id\": null\n    },\n    \"releases\": {\n        \"1.0.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-12-23T07:45:10\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.0.4.tar.gz\",\n                \"md5_digest\": \"0b7448f9e1a077a7218720575003a1b6\",\n                \"downloads\": 112618,\n                \"filename\": \"requests-1.0.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 336280\n            }\n        ],\n        \"1.0.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-12-17T15:00:05\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.0.0.tar.gz\",\n                \"md5_digest\": \"099c9035c4b30a7ae5484b1beabc7407\",\n                \"downloads\": 16077,\n                \"filename\": \"requests-1.0.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 335548\n            }\n        ],\n        \"1.0.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-12-17T18:53:51\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.0.1.tar.gz\",\n                \"md5_digest\": \"2e938f26f2bdf2899862c751bfa7eff5\",\n                \"downloads\": 3679,\n                \"filename\": \"requests-1.0.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 335625\n            }\n        ],\n        \"1.0.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-12-17T19:04:31\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.0.2.tar.gz\",\n                \"md5_digest\": \"e5c1a5a5472cd61f144743dd25a2a29f\",\n                \"downloads\": 6921,\n                \"filename\": \"requests-1.0.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 335653\n            }\n        ],\n        \"1.0.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-12-18T09:51:12\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.0.3.tar.gz\",\n                \"md5_digest\": \"a3169a33973d4b5b51843ead01c5e999\",\n                \"downloads\": 51762,\n                \"filename\": \"requests-1.0.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 335757\n            }\n        ],\n        \"0.3.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-04-15T23:30:50\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.3.2.tar.gz\",\n                \"md5_digest\": \"bde777f4c5b7bbb09033901c443962b3\",\n                \"downloads\": 5216,\n                \"filename\": \"requests-0.3.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 15515\n            }\n        ],\n        \"0.3.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-05-12T10:03:24\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.3.3.tar.gz\",\n                \"md5_digest\": \"84c762c116617ba4dd03c19e2b61eb53\",\n                \"downloads\": 4934,\n                \"filename\": \"requests-0.3.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 18995\n            }\n        ],\n        \"0.3.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-25T14:58:38\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.3.0.tar.gz\",\n                \"md5_digest\": \"aa1306575a78ba8b5e625dd2645d2ef0\",\n                \"downloads\": 5249,\n                \"filename\": \"requests-0.3.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 15021\n            }\n        ],\n        \"0.3.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-04-01T20:55:03\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.3.1.tar.gz\",\n                \"md5_digest\": \"3f4701e2ab414cd7018804a70328c527\",\n                \"downloads\": 5143,\n                \"filename\": \"requests-0.3.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 15275\n            }\n        ],\n        \"0.3.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-05-14T20:30:44\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.3.4.tar.gz\",\n                \"md5_digest\": \"55152cc2b135bc8989dc4fa279295f8b\",\n                \"downloads\": 4894,\n                \"filename\": \"requests-0.3.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 19773\n            }\n        ],\n        \"0.12.01\": [],\n        \"2.8.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-10-13T12:56:41\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.8.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"46f1d621daa3ab38958a42f51478b1ee\",\n                \"downloads\": 3302171,\n                \"filename\": \"requests-2.8.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 497953\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-10-13T12:56:34\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.8.1.tar.gz\",\n                \"md5_digest\": \"a27ea3d72d7822906ddce5e252d6add9\",\n                \"downloads\": 2462803,\n                \"filename\": \"requests-2.8.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 480803\n            }\n        ],\n        \"2.8.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-10-06T14:47:57\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.8.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"52236eb6f886db4d2afba43775c97050\",\n                \"downloads\": 416112,\n                \"filename\": \"requests-2.8.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 476582\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-10-06T14:48:08\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.8.0.tar.gz\",\n                \"md5_digest\": \"3ec7198fc935d83c3eacff1ed4095ce4\",\n                \"downloads\": 388624,\n                \"filename\": \"requests-2.8.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 457879\n            }\n        ],\n        \"0.6.5\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-19T07:30:59\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.5.tar.gz\",\n                \"md5_digest\": \"52f8bc956e027c8a0eb2684f6928169d\",\n                \"downloads\": 4778,\n                \"filename\": \"requests-0.6.5.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 30647\n            }\n        ],\n        \"2.0.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-11-15T19:12:20\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.0.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"d524f9a38a29efe1732fd130e5ebe433\",\n                \"downloads\": 316311,\n                \"filename\": \"requests-2.0.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 439330\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-10-24T14:33:21\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.0.1.tar.gz\",\n                \"md5_digest\": \"38e61c2856d2ba2782286730241975e6\",\n                \"downloads\": 1450321,\n                \"filename\": \"requests-2.0.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 412648\n            }\n        ],\n        \"2.0.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-11-15T19:09:51\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.0.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"6af9c16dbddd2fc751ae4f1606d041e8\",\n                \"downloads\": 299730,\n                \"filename\": \"requests-2.0.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 391141\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-09-24T18:39:33\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.0.0.tar.gz\",\n                \"md5_digest\": \"856fc825c17483e25fd55db115028e3f\",\n                \"downloads\": 1063100,\n                \"filename\": \"requests-2.0.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 362994\n            }\n        ],\n        \"0.6.6\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-19T09:39:56\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.6.tar.gz\",\n                \"md5_digest\": \"2180dacebc0e30ba730d083739907af6\",\n                \"downloads\": 6671,\n                \"filename\": \"requests-0.6.6.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 30809\n            }\n        ],\n        \"2.2.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-01-23T18:26:15\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.2.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"1e38addb978e50bd86f62bda53956b03\",\n                \"downloads\": 2658147,\n                \"filename\": \"requests-2.2.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 625382\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-01-23T18:26:12\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.2.1.tar.gz\",\n                \"md5_digest\": \"ac27081135f58d1a43e4fb38258d6f4e\",\n                \"downloads\": 3407542,\n                \"filename\": \"requests-2.2.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 421978\n            }\n        ],\n        \"2.2.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-01-09T19:33:37\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.2.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"8f989615bb0d276d5f3158e7efab494c\",\n                \"downloads\": 144823,\n                \"filename\": \"requests-2.2.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 623932\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-01-09T19:33:32\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.2.0.tar.gz\",\n                \"md5_digest\": \"4d2e17221d478ece045e2e81cdb177f5\",\n                \"downloads\": 392673,\n                \"filename\": \"requests-2.2.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 421997\n            }\n        ],\n        \"0.6.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-14T03:35:13\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.3.tar.gz\",\n                \"md5_digest\": \"35a954ae85b358e498fb0e602f1dce9d\",\n                \"downloads\": 4769,\n                \"filename\": \"requests-0.6.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 26606\n            }\n        ],\n        \"0.6.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-09T13:12:45\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.2.tar.gz\",\n                \"md5_digest\": \"0583bb5393b9cfcb022dc2aef7d6ffc8\",\n                \"downloads\": 7850,\n                \"filename\": \"requests-0.6.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 26524\n            }\n        ],\n        \"0.6.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-08-21T00:25:37\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.1.tar.gz\",\n                \"md5_digest\": \"07770334d48bd69ede1cc28cd0dd7680\",\n                \"downloads\": 21399,\n                \"filename\": \"requests-0.6.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 26107\n            }\n        ],\n        \"0.6.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-08-17T10:33:05\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.0.tar.gz\",\n                \"md5_digest\": \"235e9fb6bfd71a48c0f00c0d5aef8896\",\n                \"downloads\": 6029,\n                \"filename\": \"requests-0.6.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 25692\n            }\n        ],\n        \"0.11.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-03-31T05:47:56\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.11.1.tar.gz\",\n                \"md5_digest\": \"c903c32a0e1f04889e693da8e9c71872\",\n                \"downloads\": 38150,\n                \"filename\": \"requests-0.11.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 63100\n            }\n        ],\n        \"0.11.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-04-23T04:29:36\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.11.2.tar.gz\",\n                \"md5_digest\": \"5acd23600c897bf1560dca18005b428c\",\n                \"downloads\": 36476,\n                \"filename\": \"requests-0.11.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 71080\n            }\n        ],\n        \"0.6.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-14T04:23:31\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.6.4.tar.gz\",\n                \"md5_digest\": \"e0eec314178ad9a7bb14f2ec32f35ba3\",\n                \"downloads\": 9277,\n                \"filename\": \"requests-0.6.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 30212\n            }\n        ],\n        \"0.13.8\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-08-20T15:24:42\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.8.tar.gz\",\n                \"md5_digest\": \"d01596bd344db94763b2e4dfaa7bc7b9\",\n                \"downloads\": 26725,\n                \"filename\": \"requests-0.13.8.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 522140\n            }\n        ],\n        \"0.13.9\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-08-25T15:26:50\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.9.tar.gz\",\n                \"md5_digest\": \"66d52b8f47be517fc91a6e18d6b9ce82\",\n                \"downloads\": 436286,\n                \"filename\": \"requests-0.13.9.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 522477\n            }\n        ],\n        \"0.13.6\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-08-06T08:46:22\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.6.tar.gz\",\n                \"md5_digest\": \"9ea0f38cc4bf444be5a4c90f127211f2\",\n                \"downloads\": 83586,\n                \"filename\": \"requests-0.13.6.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 520031\n            }\n        ],\n        \"0.13.7\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-08-19T00:47:48\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.7.tar.gz\",\n                \"md5_digest\": \"9212044f915d44fe3010cb923c0e08e5\",\n                \"downloads\": 20167,\n                \"filename\": \"requests-0.13.7.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 521660\n            }\n        ],\n        \"0.13.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-07-27T08:22:09\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.4.tar.gz\",\n                \"md5_digest\": \"286cd3352509691e81c520accc5b9e48\",\n                \"downloads\": 4033,\n                \"filename\": \"requests-0.13.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 519515\n            }\n        ],\n        \"0.13.5\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-07-27T09:23:41\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.5.tar.gz\",\n                \"md5_digest\": \"805fd122b4cfd224e15ff2f5288c5ba0\",\n                \"downloads\": 89067,\n                \"filename\": \"requests-0.13.5.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 519553\n            }\n        ],\n        \"0.13.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-06-29T02:37:41\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.2.tar.gz\",\n                \"md5_digest\": \"fac5635391778e2394a411d37e69ae5e\",\n                \"downloads\": 117591,\n                \"filename\": \"requests-0.13.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 514484\n            }\n        ],\n        \"0.13.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-07-12T23:20:43\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.3.tar.gz\",\n                \"md5_digest\": \"54387d7df6c69580b906dcb5a2bd0724\",\n                \"downloads\": 144912,\n                \"filename\": \"requests-0.13.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 515192\n            }\n        ],\n        \"0.13.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-05-30T02:54:18\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.0.tar.gz\",\n                \"md5_digest\": \"7d41e51c273806456faab61370d5147e\",\n                \"downloads\": 40543,\n                \"filename\": \"requests-0.13.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 68172\n            }\n        ],\n        \"0.13.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-06-08T04:22:28\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.13.1.tar.gz\",\n                \"md5_digest\": \"31a08091feeefe60817e45122d933219\",\n                \"downloads\": 121874,\n                \"filename\": \"requests-0.13.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 68474\n            }\n        ],\n        \"0.8.9\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-28T10:34:17\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.9.tar.gz\",\n                \"md5_digest\": \"ff5b3bf5bc3ad19930d3f3afe51f182b\",\n                \"downloads\": 10963,\n                \"filename\": \"requests-0.8.9.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 55153\n            }\n        ],\n        \"0.8.8\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-28T09:55:45\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.8.tar.gz\",\n                \"md5_digest\": \"bfb182cfd3ed839b97744c553b87f502\",\n                \"downloads\": 5368,\n                \"filename\": \"requests-0.8.8.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 54212\n            }\n        ],\n        \"0.14.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-10-27T15:08:51\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.14.2.tar.gz\",\n                \"md5_digest\": \"488508ba3e8270992ad5b3fb54d364ca\",\n                \"downloads\": 918155,\n                \"filename\": \"requests-0.14.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 361488\n            }\n        ],\n        \"0.8.5\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-14T16:43:21\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.5.tar.gz\",\n                \"md5_digest\": \"5f2975ee9e57f4ea000e5a3f50fc85d1\",\n                \"downloads\": 6281,\n                \"filename\": \"requests-0.8.5.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 52351\n            }\n        ],\n        \"0.8.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-11T17:40:28\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.4.tar.gz\",\n                \"md5_digest\": \"642e5c70250989e4feda9c50be57b100\",\n                \"downloads\": 13119,\n                \"filename\": \"requests-0.8.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 52100\n            }\n        ],\n        \"0.8.7\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-24T09:18:54\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.7.tar.gz\",\n                \"md5_digest\": \"e4d4ee3a90396908bd04b50bf2136617\",\n                \"downloads\": 5369,\n                \"filename\": \"requests-0.8.7.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 53578\n            }\n        ],\n        \"0.8.6\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-19T01:18:29\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.6.tar.gz\",\n                \"md5_digest\": \"21b03926ab38417a704ebce57972571a\",\n                \"downloads\": 7981,\n                \"filename\": \"requests-0.8.6.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 52670\n            }\n        ],\n        \"0.8.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-15T16:01:47\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.1.tar.gz\",\n                \"md5_digest\": \"6135f837fbd113fc62904c60dcc5c70d\",\n                \"downloads\": 8542,\n                \"filename\": \"requests-0.8.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 39046\n            }\n        ],\n        \"0.8.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-13T06:52:10\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.0.tar.gz\",\n                \"md5_digest\": \"64dc0095cb645aa7f0083957950d524d\",\n                \"downloads\": 5776,\n                \"filename\": \"requests-0.8.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 38785\n            }\n        ],\n        \"0.8.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-27T16:44:51\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.3.tar.gz\",\n                \"md5_digest\": \"93e4cd27ab646fb613a926fede1cc4f5\",\n                \"downloads\": 12461,\n                \"filename\": \"requests-0.8.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 51252\n            }\n        ],\n        \"0.8.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-19T22:28:31\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.8.2.tar.gz\",\n                \"md5_digest\": \"bdbbd7f45688e23e87eec52835959943\",\n                \"downloads\": 30687,\n                \"filename\": \"requests-0.8.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 51162\n            }\n        ],\n        \"0.4.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-05-25T18:54:05\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.4.1.tar.gz\",\n                \"md5_digest\": \"812ff0ce63d14f7b940bacd880d54ee0\",\n                \"downloads\": 9781,\n                \"filename\": \"requests-0.4.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 18443\n            }\n        ],\n        \"0.4.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-05-15T05:58:43\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.4.0.tar.gz\",\n                \"md5_digest\": \"77a7a7edd54169c6fa7ace49dcb0b20c\",\n                \"downloads\": 5713,\n                \"filename\": \"requests-0.4.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 17194\n            }\n        ],\n        \"2.4.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-10-06T09:44:49\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.4.3-py2.py3-none-any.whl\",\n                \"md5_digest\": \"0a66a9c4c22272680430fbb9fb4ca34f\",\n                \"downloads\": 2501282,\n                \"filename\": \"requests-2.4.3-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 459464\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-10-06T09:44:44\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.4.3.tar.gz\",\n                \"md5_digest\": \"02214b3a179e445545de4b7a98d3dd17\",\n                \"downloads\": 2827852,\n                \"filename\": \"requests-2.4.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 438132\n            }\n        ],\n        \"2.4.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-10-05T17:15:53\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.4.2-py2.py3-none-any.whl\",\n                \"md5_digest\": \"f49f34b1fcdef6b557964deea1a80cf3\",\n                \"downloads\": 26784,\n                \"filename\": \"requests-2.4.2-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 459326\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-10-05T17:15:45\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.4.2.tar.gz\",\n                \"md5_digest\": \"a2476d2dd83a0520847f216ce0b5f9d1\",\n                \"downloads\": 46933,\n                \"filename\": \"requests-2.4.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 437898\n            }\n        ],\n        \"2.4.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-09-09T16:35:12\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.4.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"19d5413dc71309e4fb1f8103b8eb99ce\",\n                \"downloads\": 921215,\n                \"filename\": \"requests-2.4.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 458354\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-09-09T16:35:08\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.4.1.tar.gz\",\n                \"md5_digest\": \"931461f761c70708c46ea65b7889da58\",\n                \"downloads\": 1352590,\n                \"filename\": \"requests-2.4.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 436872\n            }\n        ],\n        \"2.4.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-08-29T14:32:48\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.4.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"47948d2fb3f2aa04235e6f637814b226\",\n                \"downloads\": 308274,\n                \"filename\": \"requests-2.4.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 457810\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-08-29T14:32:45\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.4.0.tar.gz\",\n                \"md5_digest\": \"99b830d1afe2e5920adbea0fe3120948\",\n                \"downloads\": 597502,\n                \"filename\": \"requests-2.4.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 436334\n            }\n        ],\n        \"2.6.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-04-23T02:27:04\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.6.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"adb8e91b3367bc0417ef1e4a6dced9b1\",\n                \"downloads\": 47681,\n                \"filename\": \"requests-2.6.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 469962\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-04-23T02:27:12\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.6.1.tar.gz\",\n                \"md5_digest\": \"da6e487f89e6a531699b7fd97ff182af\",\n                \"downloads\": 43134,\n                \"filename\": \"requests-2.6.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 450975\n            }\n        ],\n        \"2.6.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-03-14T16:44:37\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.6.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"3ab1972bbaf2802d94516fb86b9b0d0b\",\n                \"downloads\": 2003507,\n                \"filename\": \"requests-2.6.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 469802\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-03-14T16:44:48\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.6.0.tar.gz\",\n                \"md5_digest\": \"25287278fa3ea106207461112bb37050\",\n                \"downloads\": 2402265,\n                \"filename\": \"requests-2.6.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 450389\n            }\n        ],\n        \"2.6.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-04-23T16:30:52\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.6.2-py2.py3-none-any.whl\",\n                \"md5_digest\": \"36746c275589b2154307bbcc6d28320a\",\n                \"downloads\": 839011,\n                \"filename\": \"requests-2.6.2-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 470140\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-04-23T16:31:01\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.6.2.tar.gz\",\n                \"md5_digest\": \"0d703e5be558566e0f8c37f960d95372\",\n                \"downloads\": 598600,\n                \"filename\": \"requests-2.6.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 451109\n            }\n        ],\n        \"1.1.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-01-10T07:13:41\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.1.0.tar.gz\",\n                \"md5_digest\": \"a0158815af244c32041a3147ee09abf3\",\n                \"downloads\": 1423824,\n                \"filename\": \"requests-1.1.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 337229\n            }\n        ],\n        \"0.2.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-19T07:03:14\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.2.4.tar.gz\",\n                \"md5_digest\": \"62dbe8cf12bc1ccd03776e74f59e9ef6\",\n                \"downloads\": 5627,\n                \"filename\": \"requests-0.2.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 13653\n            }\n        ],\n        \"0.2.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-15T15:47:29\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.2.3.tar.gz\",\n                \"md5_digest\": \"102243646fc0cffdc82269f4bb5c6d5d\",\n                \"downloads\": 5009,\n                \"filename\": \"requests-0.2.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 13255\n            }\n        ],\n        \"0.2.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-14T18:58:40\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.2.2.tar.gz\",\n                \"md5_digest\": \"a703489b1a4a650698ddcf84857360c6\",\n                \"downloads\": 4960,\n                \"filename\": \"requests-0.2.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 13049\n            }\n        ],\n        \"0.2.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-14T16:38:12\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.2.1.tar.gz\",\n                \"md5_digest\": \"7e9590f3985ece46fc8306e906b458c7\",\n                \"downloads\": 4947,\n                \"filename\": \"requests-0.2.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 12715\n            }\n        ],\n        \"0.2.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-02-14T08:49:42\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.2.0.tar.gz\",\n                \"md5_digest\": \"637ae94cb6f2f1d9ea9020293055964a\",\n                \"downloads\": 5066,\n                \"filename\": \"requests-0.2.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 5533\n            }\n        ],\n        \"0.14.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-10-01T17:30:05\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.14.1.tar.gz\",\n                \"md5_digest\": \"3de30600072cbc7214ae342d1d08aa46\",\n                \"downloads\": 299590,\n                \"filename\": \"requests-0.14.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 523254\n            }\n        ],\n        \"2.1.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-12-05T22:51:41\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.1.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"0848cbc0cc7edd150cb8d6ddc25ca906\",\n                \"downloads\": 802779,\n                \"filename\": \"requests-2.1.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 445280\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-12-05T22:51:38\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.1.0.tar.gz\",\n                \"md5_digest\": \"28543001831f46b1ff40686ebc027deb\",\n                \"downloads\": 980428,\n                \"filename\": \"requests-2.1.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 420289\n            }\n        ],\n        \"0.14.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-09-02T08:50:39\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.14.0.tar.gz\",\n                \"md5_digest\": \"a809c747e4f09b92147721ebc3e23dd6\",\n                \"downloads\": 502482,\n                \"filename\": \"requests-0.14.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 523133\n            }\n        ],\n        \"0.10.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-01-23T08:22:52\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.1.tar.gz\",\n                \"md5_digest\": \"699147d2143bff95238befa58980b912\",\n                \"downloads\": 26671,\n                \"filename\": \"requests-0.10.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 63234\n            }\n        ],\n        \"0.10.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-01-22T05:08:17\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.0.tar.gz\",\n                \"md5_digest\": \"c90a48af18eb4170dbe4832c1104440c\",\n                \"downloads\": 10399,\n                \"filename\": \"requests-0.10.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 62046\n            }\n        ],\n        \"0.10.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-02-20T20:10:57\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.3.tar.gz\",\n                \"md5_digest\": \"a055af00593f4828c3becd0ccfab503f\",\n                \"downloads\": 4709,\n                \"filename\": \"requests-0.10.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 60493\n            }\n        ],\n        \"0.10.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-02-15T09:48:52\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.2.tar.gz\",\n                \"md5_digest\": \"47c3cf85a0112d423137b43989663bef\",\n                \"downloads\": 17350,\n                \"filename\": \"requests-0.10.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 60158\n            }\n        ],\n        \"0.0.1\": [],\n        \"0.10.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-02-20T22:21:31\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.4.tar.gz\",\n                \"md5_digest\": \"5e465e9e739bcc9f71935ca4e9706168\",\n                \"downloads\": 13489,\n                \"filename\": \"requests-0.10.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 60889\n            }\n        ],\n        \"0.10.7\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-03-08T01:50:58\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.7.tar.gz\",\n                \"md5_digest\": \"a3ac9d431981dcfd592fd0f35c499e4a\",\n                \"downloads\": 6757,\n                \"filename\": \"requests-0.10.7.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 61826\n            }\n        ],\n        \"0.10.6\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-02-26T05:17:54\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.6.tar.gz\",\n                \"md5_digest\": \"c889401445de3cbbac98509208a73b83\",\n                \"downloads\": 28073,\n                \"filename\": \"requests-0.10.6.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 61673\n            }\n        ],\n        \"0.7.6\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-07T20:19:31\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.6.tar.gz\",\n                \"md5_digest\": \"728b21bf3914d69a4ff1012c66d9b6ba\",\n                \"downloads\": 8941,\n                \"filename\": \"requests-0.7.6.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 32748\n            }\n        ],\n        \"0.10.8\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-03-09T17:59:54\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.10.8.tar.gz\",\n                \"md5_digest\": \"0fc89a30eef76b2393cbc7ebace91750\",\n                \"downloads\": 97221,\n                \"filename\": \"requests-0.10.8.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 62201\n            }\n        ],\n        \"0.7.4\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-27T00:36:25\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.4.tar.gz\",\n                \"md5_digest\": \"c015765399b8c1e309c84ade0d38f07b\",\n                \"downloads\": 10308,\n                \"filename\": \"requests-0.7.4.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 31873\n            }\n        ],\n        \"0.7.5\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-11-05T04:32:37\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.5.tar.gz\",\n                \"md5_digest\": \"9a12281a811ca25d347d806c456d96f1\",\n                \"downloads\": 5899,\n                \"filename\": \"requests-0.7.5.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 32298\n            }\n        ],\n        \"0.7.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-23T21:40:37\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.2.tar.gz\",\n                \"md5_digest\": \"03eb97ed6aacb4102fd434bbfc13ce17\",\n                \"downloads\": 5630,\n                \"filename\": \"requests-0.7.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 31837\n            }\n        ],\n        \"0.7.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-23T23:04:13\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.3.tar.gz\",\n                \"md5_digest\": \"267f6f7d1109775d24a288f798e3ab4a\",\n                \"downloads\": 9194,\n                \"filename\": \"requests-0.7.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 31805\n            }\n        ],\n        \"0.7.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-23T03:33:24\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.0.tar.gz\",\n                \"md5_digest\": \"83a1a7d79218756efd19b254eeb6b1f0\",\n                \"downloads\": 4993,\n                \"filename\": \"requests-0.7.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 31260\n            }\n        ],\n        \"0.7.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-10-23T21:19:22\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.7.1.tar.gz\",\n                \"md5_digest\": \"4821c6902d8e83c910c69c6492388e5f\",\n                \"downloads\": 4675,\n                \"filename\": \"requests-0.7.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 31804\n            }\n        ],\n        \"0.12.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-05-08T07:21:59\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.12.1.tar.gz\",\n                \"md5_digest\": \"fe9e0515d09733d0eb9e2031c03401b2\",\n                \"downloads\": 131145,\n                \"filename\": \"requests-0.12.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 78245\n            }\n        ],\n        \"0.12.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-05-03T01:18:47\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.12.0.tar.gz\",\n                \"md5_digest\": \"c38bacf4d6a065f3c47463e63efdfb5a\",\n                \"downloads\": 12488,\n                \"filename\": \"requests-0.12.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 76859\n            }\n        ],\n        \"0.5.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-06-22T04:44:39\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.5.0.tar.gz\",\n                \"md5_digest\": \"6dfdc1688217d774d524e056ec6605a6\",\n                \"downloads\": 8450,\n                \"filename\": \"requests-0.5.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 21945\n            }\n        ],\n        \"0.5.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-07-24T05:01:45\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.5.1.tar.gz\",\n                \"md5_digest\": \"33a6e65d6a4e5b2d91df76256f607b81\",\n                \"downloads\": 7189,\n                \"filename\": \"requests-0.5.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 23080\n            }\n        ],\n        \"0.9.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2011-12-28T10:51:35\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.9.0.tar.gz\",\n                \"md5_digest\": \"5f6f03ec76f68a7a3f35120ab5a6c589\",\n                \"downloads\": 16697,\n                \"filename\": \"requests-0.9.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 55217\n            }\n        ],\n        \"0.9.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-01-06T07:11:02\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.9.1.tar.gz\",\n                \"md5_digest\": \"8ed4667edb5d57945b74a9137adbb8bd\",\n                \"downloads\": 14372,\n                \"filename\": \"requests-0.9.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 55547\n            }\n        ],\n        \"0.9.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-01-19T03:39:58\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.9.2.tar.gz\",\n                \"md5_digest\": \"65b36d99a4d2f78a22f08c95d2475e33\",\n                \"downloads\": 4700,\n                \"filename\": \"requests-0.9.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 60967\n            }\n        ],\n        \"0.9.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2012-01-19T16:51:33\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-0.9.3.tar.gz\",\n                \"md5_digest\": \"b13b6fbfa8fc3fc3c25bae300748053f\",\n                \"downloads\": 11556,\n                \"filename\": \"requests-0.9.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 61006\n            }\n        ],\n        \"2.3.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-05-16T17:57:05\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.3.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"f2d850fd48fc10a93aa03d69b87b96b4\",\n                \"downloads\": 4032003,\n                \"filename\": \"requests-2.3.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 452902\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-05-16T17:57:02\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.3.0.tar.gz\",\n                \"md5_digest\": \"7449ffdc8ec9ac37bbcd286003c80f00\",\n                \"downloads\": 4723580,\n                \"filename\": \"requests-2.3.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 429521\n            }\n        ],\n        \"1.2.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-05-21T21:44:44\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.2.2.tar.gz\",\n                \"md5_digest\": \"1f655ab7f2aa7447a1657ed69786f436\",\n                \"downloads\": 219258,\n                \"filename\": \"requests-1.2.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 348851\n            }\n        ],\n        \"1.2.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-05-25T16:48:36\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.2.3.tar.gz\",\n                \"md5_digest\": \"adbd3f18445f7fe5e77f65c502e264fb\",\n                \"downloads\": 3278337,\n                \"filename\": \"requests-1.2.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 348854\n            }\n        ],\n        \"1.2.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-03-31T05:28:47\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.2.0.tar.gz\",\n                \"md5_digest\": \"22af2682233770e5468a986f451c51c0\",\n                \"downloads\": 896245,\n                \"filename\": \"requests-1.2.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 341511\n            }\n        ],\n        \"1.2.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2013-05-20T20:11:09\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-1.2.1.tar.gz\",\n                \"md5_digest\": \"4d019670b94b17e329007d64e67e045e\",\n                \"downloads\": 8076,\n                \"filename\": \"requests-1.2.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 348710\n            }\n        ],\n        \"2.5.2\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-02-23T22:37:39\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.5.2-py2.py3-none-any.whl\",\n                \"md5_digest\": \"7e72dfe8ed9d4ce5fd9dd9d799b3add1\",\n                \"downloads\": 162592,\n                \"filename\": \"requests-2.5.2-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 474275\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-02-23T22:37:46\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.5.2.tar.gz\",\n                \"md5_digest\": \"424e2469202c9bace4e8bf4642d4217a\",\n                \"downloads\": 53980,\n                \"filename\": \"requests-2.5.2.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 455688\n            }\n        ],\n        \"2.5.3\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-02-24T16:33:49\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.5.3-py2.py3-none-any.whl\",\n                \"md5_digest\": \"233249f4627ac5481c948e494d2a090e\",\n                \"downloads\": 873743,\n                \"filename\": \"requests-2.5.3-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 468593\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-02-24T16:33:58\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.5.3.tar.gz\",\n                \"md5_digest\": \"23bf4fcc89ea8d353eb5353bb4a475b1\",\n                \"downloads\": 874483,\n                \"filename\": \"requests-2.5.3.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 448318\n            }\n        ],\n        \"2.5.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-12-01T23:27:51\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.5.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"9d29a8a0210c236d9329bed49277b3fa\",\n                \"downloads\": 877099,\n                \"filename\": \"requests-2.5.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 464196\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-12-01T23:27:58\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.5.0.tar.gz\",\n                \"md5_digest\": \"b8bf3ddca75e7ecf1b6776da1e6e3385\",\n                \"downloads\": 1056687,\n                \"filename\": \"requests-2.5.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 443222\n            }\n        ],\n        \"2.5.1\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-12-23T17:55:59\",\n                \"comment_text\": \"\",\n                \"python_version\": \"py2.py3\",\n                \"url\": \"https://pypi.python.org/packages/py2.py3/r/requests/requests-2.5.1-py2.py3-none-any.whl\",\n                \"md5_digest\": \"11dc91bc96c5c5e0b566ce8f9c9644ab\",\n                \"downloads\": 1893375,\n                \"filename\": \"requests-2.5.1-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 464421\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2014-12-23T17:56:08\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.5.1.tar.gz\",\n                \"md5_digest\": \"c270eb5551a02e8ab7a4cbb83e22af2e\",\n                \"downloads\": 2583555,\n                \"filename\": \"requests-2.5.1.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 443633\n            }\n        ],\n        \"2.7.0\": [\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-05-03T15:01:28\",\n                \"comment_text\": \"\",\n                \"python_version\": \"2.7\",\n                \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.7.0-py2.py3-none-any.whl\",\n                \"md5_digest\": \"564fb256f865a79f977e57b79d31659a\",\n                \"downloads\": 8550789,\n                \"filename\": \"requests-2.7.0-py2.py3-none-any.whl\",\n                \"packagetype\": \"bdist_wheel\",\n                \"size\": 470641\n            },\n            {\n                \"has_sig\": false,\n                \"upload_time\": \"2015-05-03T15:01:21\",\n                \"comment_text\": \"\",\n                \"python_version\": \"source\",\n                \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.7.0.tar.gz\",\n                \"md5_digest\": \"29b173fd5fa572ec0764d1fd7b527260\",\n                \"downloads\": 7696812,\n                \"filename\": \"requests-2.7.0.tar.gz\",\n                \"packagetype\": \"sdist\",\n                \"size\": 451723\n            }\n        ]\n    },\n    \"urls\": [\n        {\n            \"has_sig\": false,\n            \"upload_time\": \"2015-10-13T12:56:41\",\n            \"comment_text\": \"\",\n            \"python_version\": \"2.7\",\n            \"url\": \"https://pypi.python.org/packages/2.7/r/requests/requests-2.8.1-py2.py3-none-any.whl\",\n            \"md5_digest\": \"46f1d621daa3ab38958a42f51478b1ee\",\n            \"downloads\": 3302171,\n            \"filename\": \"requests-2.8.1-py2.py3-none-any.whl\",\n            \"packagetype\": \"bdist_wheel\",\n            \"size\": 497953\n        },\n        {\n            \"has_sig\": false,\n            \"upload_time\": \"2015-10-13T12:56:34\",\n            \"comment_text\": \"\",\n            \"python_version\": \"source\",\n            \"url\": \"https://pypi.python.org/packages/source/r/requests/requests-2.8.1.tar.gz\",\n            \"md5_digest\": \"a27ea3d72d7822906ddce5e252d6add9\",\n            \"downloads\": 2462803,\n            \"filename\": \"requests-2.8.1.tar.gz\",\n            \"packagetype\": \"sdist\",\n            \"size\": 480803\n        }\n    ]\n}\n"
  },
  {
    "path": "tests/integtest.py",
    "content": "# Copyright 2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Helper to run integration tests.\n\nThis is not part of the regular test suite, but a test that is used in a very specific\nway from the integration tests defined in the Github CI infrastructure.\n\"\"\"\n\nimport sys\n\n\ndef test_assert_python_version(pytestconfig):\n    expected = pytestconfig.getoption(\"integtest_pyversion\")\n    vi = sys.version_info\n    current = f\"{vi.major}.{vi.minor}\"\n    assert current == expected\n"
  },
  {
    "path": "tests/test_cache/__init__.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Helpers for the Cache tests collection.\"\"\"\n\nfrom fades.parsing import NameVerDependency\n\n\ndef get_distrib(*dep_ver_pairs):\n    \"\"\"Build some Distributions with indicated info.\"\"\"\n    return [NameVerDependency(dep, ver) for dep, ver in dep_ver_pairs]\n"
  },
  {
    "path": "tests/test_cache/conftest.py",
    "content": "# Copyright 2015-2019 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport shutil\n\nfrom pytest import fixture\n\nfrom fades import cache\n\n\n@fixture(scope=\"function\")\ndef venvscache(tmpdir_factory):\n    \"\"\"Fixture for a cache file for virtualenvs.\"\"\"\n    dir_path = tmpdir_factory.mktemp(\"test\")\n    venvs_cache = cache.VEnvsCache(dir_path.join(\"test_venv_cache\"))\n    yield venvs_cache\n    shutil.rmtree(str(dir_path))\n"
  },
  {
    "path": "tests/test_cache/test_caches.py",
    "content": "# Copyright 2015-2022 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nfrom unittest.mock import patch\n\nfrom fades import cache\n\n\ndef test_missing_file_pytest(tmp_file):\n    venvscache = cache.VEnvsCache(str(tmp_file))\n    with patch.object(venvscache, '_select', return_value=None) as mock:\n        resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options')\n    mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options='options')\n    assert not resp\n\n\ndef test_empty_file_pytest(tmp_file):\n    open(tmp_file, 'wt', encoding='utf8').close()\n    venvscache = cache.VEnvsCache(tmp_file)\n    with patch.object(venvscache, '_select', return_value=None) as mock:\n        resp = venvscache.get_venv('requirements', 'interpreter')\n    mock.assert_called_with([], 'requirements', 'interpreter', uuid='', options=None)\n    assert not resp\n\n\ndef test_some_file_content_pytest(tmp_file):\n    with open(tmp_file, 'wt', encoding='utf8') as fh:\n        fh.write('foo\\nbar\\n')\n    venvscache = cache.VEnvsCache(tmp_file)\n    with patch.object(venvscache, '_select', return_value=\"resp\") as mock:\n        resp = venvscache.get_venv('requirements', 'interpreter', uuid='', options='options')\n    mock.assert_called_with(['foo', 'bar'], 'requirements', 'interpreter', uuid='',\n                            options='options')\n    assert resp == 'resp'\n\n\ndef test_get_by_uuid_pytest(tmp_file):\n    with open(tmp_file, 'wt', encoding='utf8') as fh:\n        fh.write('foo\\nbar\\n')\n    venvscache = cache.VEnvsCache(tmp_file)\n    with patch.object(venvscache, '_select', return_value='resp') as mock:\n        resp = venvscache.get_venv(uuid='uuid')\n    mock.assert_called_with(['foo', 'bar'], None, '', uuid='uuid', options=None)\n    assert resp == 'resp'\n"
  },
  {
    "path": "tests/test_cache/test_comparisons.py",
    "content": "# Copyright 2015-2019 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport json\nimport pytest\n\nfrom fades import parsing\n\nfrom tests import get_reqs\nfrom tests.test_cache import get_distrib\n\n\n@pytest.mark.parametrize(\"req,installed,expected\", [\n    # Equal\n    (\"==5\", \"5\", \"ok\"),\n    (\"==5\", \"2\", None),\n    # Greater than\n    (\">5\", \"4\", None),\n    (\">5\", \"5\", None),\n    (\">5\", \"6\", \"ok\"),\n    # Greater than or equal\n    (\">=5\", \"4\", None),\n    (\">=5\", \"5\", \"ok\"),\n    (\">=5\", \"6\", \"ok\"),\n    # Less than\n    (\"<5\", \"4\", \"ok\"),\n    (\"<5\", \"5\", None),\n    (\"<5\", \"6\", None),\n    # Less than or equal\n    (\"<=5\", \"4\", \"ok\"),\n    (\"<=5\", \"5\", \"ok\"),\n    (\"<=5\", \"6\", None),\n    # Complex cases\n    (\"== 2.5\", \"2.5.0\", \"ok\"),\n    (\"> 2.7\", \"2.12\", \"ok\"),\n    (\"> 2.7a0\", \"2.7\", \"ok\"),\n    (\"> 2.7\", \"2.7a0\", None),\n    # Crazy picky\n    (\">1.6,<1.9,!=1.9.6\", \"1.5.0\", None),\n    (\">1.6,<1.9,!=1.9.6\", \"1.6.7\", \"ok\"),\n    (\">1.6,<1.9,!=1.8.6\", \"1.8.7\", \"ok\"),\n    (\">1.6,<1.9,!=1.9.6\", \"1.9.6\", None),\n])\ndef test_check_versions(venvscache, req, installed, expected):\n    \"\"\"The comparison in the selection.\"\"\"\n    reqs = {\"pypi\": get_reqs(\"dep\" + req)}\n    interpreter = \"pythonX.Y\"\n    options = {\"foo\": \"bar\"}\n    venv = json.dumps({\n        \"metadata\": \"ok\",\n        \"installed\": {\"pypi\": {\"dep\": installed}},\n        \"interpreter\": \"pythonX.Y\",\n        \"options\": {\"foo\": \"bar\"}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid=\"\", options=options)\n    assert resp == expected\n\n\n@pytest.mark.parametrize(\"possible_venvs\", [\n    [\n        (get_distrib(('dep', '3')), 'venv_best_fit'),\n    ],\n    [\n        (get_distrib(('dep1', '3'), ('dep2', '3')), 'venv_best_fit'),\n    ],\n    [\n        (get_distrib(('dep', '5')), 'venv_best_fit'),\n        (get_distrib(('dep', '3')), 'venv_1'),\n    ],\n    [\n        (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'),\n        (get_distrib(('dep1', '3'), ('dep2', '6')), 'venv_1'),\n    ],\n    [\n        (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_1'),\n        (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'),\n    ],\n    [\n        (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_1'),\n        (get_distrib(('dep1', '3'), ('dep2', '9')), 'venv_best_fit'),\n    ],\n    [\n        (get_distrib(('dep1', '3'), ('dep2', '9'), ('dep3', '4')), 'venv_best_fit'),\n        (get_distrib(('dep1', '5'), ('dep2', '7'), ('dep3', '2')), 'venv_1'),\n    ],\n    [\n        (get_distrib(('dep2', '3'), ('dep1', '2'), ('dep3', '8')), 'venv_best_fit'),\n        (get_distrib(('dep1', '7'), ('dep3', '5'), ('dep2', '2')), 'venv_1'),\n    ],\n    [\n        (get_distrib(('dep1', '3'), ('dep2', '2')), 'venv_1'),\n        (get_distrib(('dep1', '4'), ('dep2', '2')), 'venv_2'),\n        (get_distrib(('dep1', '5'), ('dep2', '7')), 'venv_best_fit'),\n        (get_distrib(('dep1', '5'), ('dep2', '6')), 'venv_3'),\n    ],\n    [\n        ([parsing.VCSDependency('someurl')], 'venv_best_fit'),\n    ],\n    [\n        ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'),\n    ],\n    [\n        ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '3')), 'venv_best_fit'),\n        ([parsing.VCSDependency('someurl')] + get_distrib(('dep', '1')), 'venv_1'),\n    ],\n])\ndef test_best_fit(venvscache, possible_venvs):\n    \"\"\"Check the venv best fitting decissor.\"\"\"\n    assert venvscache._select_better_fit(possible_venvs) == 'venv_best_fit'\n"
  },
  {
    "path": "tests/test_cache/test_remove.py",
    "content": "# Copyright 2015-2019 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport json\nimport os\nimport time\n\nfrom threading import Thread\n\nfrom fades import cache\n\n\ndef test_missing_file(tmp_file):\n    venvscache = cache.VEnvsCache(tmp_file)\n    venvscache.remove('missing/path')\n\n    lines = venvscache._read_cache()\n    assert lines == []\n\n\ndef test_missing_env_in_cache(tmp_file):\n    venvscache = cache.VEnvsCache(tmp_file)\n    options = {'foo': 'bar'}\n    venvscache.store('installed', {'env_path': 'some/path'}, 'interpreter', options=options)\n    lines = venvscache._read_cache()\n    assert len(lines) == 1\n\n    venvscache.remove('some/path')\n\n    lines = venvscache._read_cache()\n    assert lines == []\n\n\ndef test_preserve_cache_data_ordering(tmp_file):\n    venvscache = cache.VEnvsCache(tmp_file)\n    # store 3 venvs\n    options = {'foo': 'bar'}\n    venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options)\n    venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options)\n    venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options)\n\n    venvscache.remove('path/env2')\n\n    lines = venvscache._read_cache()\n    assert len(lines) == 2\n    assert json.loads(lines[0]).get('metadata').get('env_path') == 'path/env1'\n    assert json.loads(lines[1]).get('metadata').get('env_path') == 'path/env3'\n\n\ndef test_lock_cache_for_remove(tmp_file):\n    venvscache = cache.VEnvsCache(tmp_file)\n    # store 3 venvs\n    options = {'foo': 'bar'}\n    venvscache.store('installed1', {'env_path': 'path/env1'}, 'interpreter', options=options)\n    venvscache.store('installed2', {'env_path': 'path/env2'}, 'interpreter', options=options)\n    venvscache.store('installed3', {'env_path': 'path/env3'}, 'interpreter', options=options)\n\n    # patch _write_cache so it emulates a slow write during which\n    # another process managed to modify the cache file before the\n    # first process finished writing the modified cache data\n    original_write_cache = venvscache._write_cache\n\n    other_process = Thread(target=venvscache.remove, args=('path/env1',))\n\n    def slow_write_cache(*args, **kwargs):\n        venvscache._write_cache = original_write_cache\n\n        # start \"other process\" and wait a little to ensure it must wait\n        # for the lock to be released\n        other_process.start()\n        time.sleep(0.01)\n\n        original_write_cache(*args, **kwargs)\n\n    venvscache._write_cache = slow_write_cache\n\n    # just a sanity check\n    assert not os.path.exists(venvscache.filepath + '.lock')\n    # remove a virtualenv from the cache\n    venvscache.remove('path/env2')\n    other_process.join()\n\n    # when cache file is properly locked both virtualenvs\n    # will have been removed from the cache\n    lines = venvscache._read_cache()\n    assert len(lines) == 1\n    assert json.loads(lines[0]).get('metadata').get('env_path') == 'path/env3'\n    assert not os.path.exists(venvscache.filepath + '.lock')\n"
  },
  {
    "path": "tests/test_cache/test_selection.py",
    "content": "# Copyright 2015-2019 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport os\nimport json\nimport uuid\n\nfrom fades import helpers, parsing\nfrom tests import get_reqs\n\n\ndef test_empty(venvscache):\n    resp = venvscache._select([], {}, 'pythonX.Y', 'options')\n    assert resp is None\n\n\ndef test_nomatch_repo_dependency(venvscache):\n    reqs = {\"repoloco\": get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_nomatch_pypi_dependency(venvscache):\n    reqs = {'pypi': get_reqs('dep1 == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep2': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options)\n    resp is None\n\n\ndef test_nomatch_vcs_dependency(venvscache):\n    reqs = {'vcs': [parsing.VCSDependency('someurl')]}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'vcs': {'otherurl': None}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_nomatch_version(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '7'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_simple_pypi_match(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar'\n\n\ndef test_simple_vcs_match(venvscache):\n    reqs = {'vcs': [parsing.VCSDependency('someurl')]}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'vcs': {'someurl': None}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar'\n\n\ndef test_match_mixed_single(venvscache):\n    reqs = {'vcs': [parsing.VCSDependency('someurl')], 'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv1 = json.dumps({\n        'metadata': 'foobar1',\n        'installed': {'vcs': {'someurl': None}, 'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv2 = json.dumps({\n        'metadata': 'foobar2',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv3 = json.dumps({\n        'metadata': 'foobar3',\n        'installed': {'vcs': {'someurl': None}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select(\n        [venv1, venv2, venv3], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar1'\n\n\ndef test_match_mixed_multiple(venvscache):\n    reqs = {'vcs': [parsing.VCSDependency('url1'), parsing.VCSDependency('url2')],\n            'pypi': get_reqs('dep1 == 5', 'dep2')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {\n            'vcs': {'url1': None, 'url2': None},\n            'pypi': {'dep1': '5', 'dep2': '7'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar'\n\n\ndef test_match_noversion(venvscache):\n    reqs = {'pypi': get_reqs('dep')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar'\n\n\ndef test_middle_match(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv1 = json.dumps({\n        'metadata': 'venv1',\n        'installed': {'pypi': {'dep': '3'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv2 = json.dumps({\n        'metadata': 'venv2',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv3 = json.dumps({\n        'metadata': 'venv3',\n        'installed': {'pypi': {'dep': '7'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv1, venv2, venv3], reqs, interpreter, uuid='',\n                               options=options)\n    assert resp == 'venv2'\n\n\ndef test_multiple_match_bigger_version(venvscache):\n    reqs = {'pypi': get_reqs('dep')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv1 = json.dumps({\n        'metadata': 'venv1',\n        'installed': {'pypi': {'dep': '3'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv2 = json.dumps({\n        'metadata': 'venv2',\n        'installed': {'pypi': {'dep': '7'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    venv3 = json.dumps({\n        'metadata': 'venv3',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv1, venv2, venv3], reqs, interpreter, uuid='',\n                               options=options)\n    # matches venv2 because it has the bigger version for 'dep' (even if it's not the\n    # latest virtualenv created)\n    assert resp == 'venv2'\n\n\ndef test_multiple_deps_ok(venvscache):\n    reqs = {'pypi': get_reqs('dep1 == 5', 'dep2 == 7')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep1': '5', 'dep2': '7'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == 'foobar'\n\n\ndef test_multiple_deps_just_one(venvscache):\n    reqs = {'pypi': get_reqs('dep1 == 5', 'dep2 == 7')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep1': '5', 'dep2': '2'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_not_too_crowded(venvscache):\n    reqs = {'pypi': get_reqs('dep1')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep1': '5', 'dep2': '2'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_same_quantity_different_deps(venvscache):\n    reqs = {'pypi': get_reqs('dep1', 'dep2')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep1': '5', 'dep3': '2'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_no_requirements_some_installed(venvscache):\n    reqs = {}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep1': '5', 'dep3': '2'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_no_requirements_empty_venv(venvscache):\n    reqs = {}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == \"foobar\"\n\n\ndef test_simple_match_empty_options(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp == \"foobar\"\n\n\ndef test_no_match_due_to_options(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv = json.dumps({\n        'metadata': 'foobar',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {}\n    })\n    resp = venvscache ._select([venv], reqs, interpreter, uuid='', options=options)\n    assert resp is None\n\n\ndef test_match_due_to_options(venvscache):\n    reqs = {'pypi': get_reqs('dep == 5')}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv1 = json.dumps({\n        'metadata': 'venv1',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {}\n    })\n    venv2 = json.dumps({\n        'metadata': 'venv2',\n        'installed': {'pypi': {'dep': '5'}},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv1, venv2], reqs, interpreter, uuid='', options=options)\n    assert resp == \"venv2\"\n\n\ndef test_no_deps_but_options(venvscache):\n    reqs = {}\n    interpreter = 'pythonX.Y'\n    options = {'foo': 'bar'}\n    venv1 = json.dumps({\n        'metadata': 'venv1',\n        'installed': {},\n        'interpreter': 'pythonX.Y',\n        'options': {}\n    })\n    venv2 = json.dumps({\n        'metadata': 'venv2',\n        'installed': {},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv1, venv2], reqs, interpreter, uuid='', options=options)\n    assert resp == \"venv2\"\n\n\ndef test_match_uuid(venvscache):\n    venv_uuid = str(uuid.uuid4())\n    metadata = {\n        'env_path': os.path.join(helpers.get_basedir(), venv_uuid),\n    }\n    venv = json.dumps({\n        'metadata': metadata,\n        'installed': {},\n        'interpreter': 'pythonX.Y',\n        'options': {'foo': 'bar'}\n    })\n    resp = venvscache ._select([venv], uuid=venv_uuid)\n    assert resp == metadata\n"
  },
  {
    "path": "tests/test_cache/test_store.py",
    "content": "# Copyright 2015-2019 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\nimport json\n\nfrom fades import cache\n\n\ndef test_missing_file(tmp_file):\n    venvscache = cache.VEnvsCache(tmp_file)\n    venvscache.store('installed', 'metadata', 'interpreter', 'options')\n\n    with open(tmp_file, 'rt', encoding='utf8') as fh:\n        data = json.loads(fh.readline())\n        assert 'timestamp' in data\n        assert data['installed'], 'installed'\n        assert data['metadata'], 'metadata'\n        assert data['interpreter'], 'interpreter'\n        assert data['options'], 'options'\n\n\ndef test_with_previous_content(tmp_file):\n    with open(tmp_file, 'wt', encoding='utf8') as fh:\n        fh.write(json.dumps({'foo': 'bar'}) + '\\n')\n\n    venvscache = cache.VEnvsCache(tmp_file)\n    venvscache.store('installed', 'metadata', 'interpreter', 'options')\n\n    with open(tmp_file, 'rt', encoding='utf8') as fh:\n        data = json.loads(fh.readline())\n        assert data, {'foo': 'bar'}\n\n        data = json.loads(fh.readline())\n        assert 'timestamp' in data\n        assert data['installed'], 'installed'\n        assert data['metadata'], 'metadata'\n        assert data['interpreter'], 'interpreter'\n        assert data['options'], 'options'\n"
  },
  {
    "path": "tests/test_envbuilder.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for the venv builder module.\"\"\"\n\nimport os\nimport shutil\nimport tempfile\nimport unittest\nfrom datetime import datetime, timedelta\nfrom unittest.mock import Mock, patch, call\n\nfrom packaging.requirements import Requirement\n\nimport logassert\n\nfrom fades import FadesError, REPO_PYPI, REPO_VCS\nfrom fades import cache, envbuilder, parsing\nfrom venv import EnvBuilder\n\n\ndef get_req(text):\n    \"\"\"Transform a text requirement into the Requirement object.\"\"\"\n    return Requirement(text)\n\n\nclass EnvCreationTestCase(unittest.TestCase):\n    \"\"\"Check all the new venv creation.\"\"\"\n\n    class FakeManager:\n        \"\"\"A fake repo manager.\"\"\"\n        def __init__(self):\n            self.req_installed = []\n            self.really_installed = {}\n\n        def install(self, dependency):\n            self.req_installed.append(dependency)\n\n        def get_version(self, dependency):\n            return self.really_installed[dependency]\n\n    class FailInstallManager(FakeManager):\n        def install(self, dependency):\n            raise Exception(\"Kapow!\")\n\n    def setUp(self):\n        logassert.setup(self, 'fades.envbuilder')\n\n    def test_create_simple(self):\n        requested = {\n            REPO_PYPI: [get_req('dep1 == v1'), get_req('dep2 == v2')]\n        }\n        interpreter = 'python3'\n        is_current = True\n        avoid_pip_upgrade = False\n        options = {\"venv_options\": []}\n        pip_options = []\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:\n            with patch.object(envbuilder, 'PipManager') as mock_mgr_c:\n                mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')\n                mock_mgr_c.return_value = fake_manager = self.FakeManager()\n                fake_manager.really_installed = {'dep1': 'v1', 'dep2': 'v2'}\n                venv_data, installed = envbuilder.create_venv(\n                    requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)\n\n        self.assertEqual(venv_data, {\n            'env_bin_path': 'env_bin_path',\n            'env_path': 'env_path',\n            'pip_installed': 'pip_installed',\n        })\n        self.assertDictEqual(installed, {\n            REPO_PYPI: {\n                'dep1': 'v1',\n                'dep2': 'v2',\n            }\n        })\n        expected_pipmanager_call = call(\n            'env_bin_path', pip_installed='pip_installed', options=[],\n            avoid_pip_upgrade=avoid_pip_upgrade)\n        self.assertEqual(mock_mgr_c.call_args, expected_pipmanager_call)\n\n    def test_create_vcs(self):\n        requested = {\n            REPO_VCS: [parsing.VCSDependency(\"someurl\")]\n        }\n        interpreter = 'python3'\n        is_current = True\n        avoid_pip_upgrade = False\n        options = {\"venv_options\": []}\n        pip_options = []\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:\n            with patch.object(envbuilder, 'PipManager') as mock_mgr_c:\n                mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')\n                mock_mgr_c.return_value = self.FakeManager()\n                venv_data, installed = envbuilder.create_venv(\n                    requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)\n\n        self.assertEqual(venv_data, {\n            'env_bin_path': 'env_bin_path',\n            'env_path': 'env_path',\n            'pip_installed': 'pip_installed',\n        })\n        self.assertDictEqual(installed, {REPO_VCS: {'someurl': None}})\n\n    def test_unknown_repo(self):\n        requested = {\n            'unknown': {'dep': ''}\n        }\n        interpreter = 'python3'\n        is_current = True\n        avoid_pip_upgrade = False\n        options = {\"venv_options\": []}\n        pip_options = []\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:\n            with patch.object(envbuilder, 'PipManager') as mock_mgr_c:\n                mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')\n                mock_mgr_c.return_value = self.FakeManager()\n                envbuilder.create_venv(\n                    requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)\n\n        self.assertLoggedWarning(\"Install from 'unknown' not implemented\")\n\n    def test_non_existing_dep(self):\n        requested = {\n            REPO_PYPI: [get_req('dep1 == 1000')]\n        }\n        interpreter = 'python3'\n        is_current = True\n        avoid_pip_upgrade = False\n        options = {'venv_options': []}\n        pip_options = []\n\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:\n            with patch.object(envbuilder, 'PipManager') as mock_mgr_c:\n                mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')\n                mock_mgr_c.return_value = self.FailInstallManager()\n                with patch.object(envbuilder, 'destroy_venv', spec=True) as mock_destroy:\n                    with self.assertRaises(FadesError) as cm:\n                        envbuilder.create_venv(\n                            requested, interpreter, is_current, options, pip_options,\n                            avoid_pip_upgrade)\n                    self.assertEqual(str(cm.exception), 'Dependency installation failed')\n                    mock_destroy.assert_called_once_with('env_path')\n\n        self.assertLoggedDebug(\"Installation Step failed, removing virtual environment\")\n\n    def test_different_versions(self):\n        requested = {\n            REPO_PYPI: [get_req('dep1 == v1'), get_req('dep2 == v2')]\n        }\n        interpreter = 'python3'\n        is_current = True\n        avoid_pip_upgrade = False\n        options = {\"venv_options\": []}\n        pip_options = []\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_env') as mock_create:\n            with patch.object(envbuilder, 'PipManager') as mock_mgr_c:\n                mock_create.return_value = ('env_path', 'env_bin_path', 'pip_installed')\n                mock_mgr_c.return_value = fake_manager = self.FakeManager()\n                fake_manager.really_installed = {'dep1': 'vX', 'dep2': 'v2'}\n                _, installed = envbuilder.create_venv(\n                    requested, interpreter, is_current, options, pip_options, avoid_pip_upgrade)\n\n        self.assertEqual(installed, {\n            REPO_PYPI: {\n                'dep1': 'vX',\n                'dep2': 'v2',\n            }\n        })\n\n    def test_create_system_site_pkgs_venv(self):\n        env_builder = envbuilder._FadesEnvBuilder()\n        interpreter = 'python3'\n        is_current = True\n        options = {\"venv_options\": ['--system-site-packages']}\n        with patch.object(EnvBuilder, 'create') as mock_create:\n            env_builder.create_env(interpreter, is_current, options)\n            self.assertTrue(env_builder.system_site_packages)\n            self.assertTrue(mock_create.called)\n\n    def test_create_pyvenv(self):\n        env_builder = envbuilder._FadesEnvBuilder()\n        interpreter = 'python3'\n        is_current = True\n        options = {\"venv_options\": []}\n        with patch.object(EnvBuilder, 'create') as mock_create:\n            env_builder.create_env(interpreter, is_current, options)\n            self.assertFalse(env_builder.system_site_packages)\n            self.assertTrue(mock_create.called)\n\n    def test_create_virtual_environment(self):\n        env_builder = envbuilder._FadesEnvBuilder()\n        interpreter = 'pythonX.Y'\n        is_current = False\n        options = {\"venv_options\": []}\n        with patch.object(envbuilder._FadesEnvBuilder, 'create_with_external_venv') as mock_create:\n            env_builder.create_env(interpreter, is_current, options)\n            mock_create.assert_called_with(interpreter, options['venv_options'])\n\n\nclass EnvDestructionTestCase(unittest.TestCase):\n\n    def test_destroy_venv(self):\n        builder = envbuilder._FadesEnvBuilder()\n        # make sure the virtualenv exists on disk\n        options = {\"venv_options\": [], \"pip-options\": []}\n\n        def fake_create(*_):\n            \"\"\"Fake venv create.\n\n            This is for the test to avoid network usage on venv creation, but also create\n            and set the fake dir.\n            \"\"\"\n            os.mkdir(fake_venv_path)\n            builder.env_path = fake_venv_path\n\n        fake_venv_path = tempfile.TemporaryDirectory().name\n        builder.create_with_external_venv = fake_create\n        builder.create_env('python', False, options=options)\n        assert os.path.exists(builder.env_path)\n\n        cache_mock = Mock()\n        envbuilder.destroy_venv(builder.env_path, cache_mock)\n        self.assertFalse(os.path.exists(builder.env_path))\n        cache_mock.remove.assert_called_with(builder.env_path)\n\n    def test_destroy_venv_if_env_path_not_found(self):\n        builder = envbuilder._FadesEnvBuilder()\n        assert not os.path.exists(builder.env_path)\n\n        cache_mock = Mock()\n        envbuilder.destroy_venv(builder.env_path, cache_mock)\n        self.assertFalse(os.path.exists(builder.env_path))\n        cache_mock.remove.assert_called_with(builder.env_path)\n\n\nclass UsageManagerTestCase(unittest.TestCase):\n\n    def setUp(self):\n        temp_file_descriptor, self.tempfile = tempfile.mkstemp(prefix=\"test-temp-file\")\n        os.close(temp_file_descriptor)\n        self.temp_folder = tempfile.mkdtemp()\n        self.file_path = os.path.join(self.temp_folder, 'usage_stats')\n        self.addCleanup(lambda: os.path.exists(self.tempfile) and os.remove(self.tempfile))\n        self.addCleanup(shutil.rmtree, self.temp_folder, ignore_errors=True)\n\n        self.uuids = ['env1', 'env2', 'env3']\n\n        self.venvscache = cache.VEnvsCache(self.tempfile)\n        for uuid in self.uuids:\n            self.venvscache.store('', {'env_path': os.path.join(self.temp_folder, uuid)}, '', '')\n\n    def get_usage_lines(self, manager):\n        self.assertTrue(os.path.exists(self.file_path), msg=\"File usage exists\")\n        lines = []\n        for line in open(self.file_path).readlines():\n            uuid, d = line.split()\n            d = manager._str_to_datetime(d)\n            lines.append((uuid, d))\n        return lines\n\n    def test_file_usage_dont_exists_then_it_is_created_and_initialized(self):\n        self.assertFalse(os.path.exists(self.file_path), msg=\"First file doesn't exists\")\n        manager = envbuilder.UsageManager(self.file_path, self.venvscache)\n        lines = self.get_usage_lines(manager)\n        self.assertEqual(len(lines), len(self.uuids), msg=\"File have one line per venv\")\n\n        pending_uuids = self.uuids[:]\n        for uuid, dt in lines:\n            self.assertTrue(uuid in pending_uuids, msg=\"Every uuid is in file\")\n            pending_uuids.remove(uuid)\n\n    def test_usage_record_is_recorded(self):\n        manager = envbuilder.UsageManager(self.file_path, self.venvscache)\n        lines = self.get_usage_lines(manager)\n        self.assertEqual(len(lines), len(self.uuids), msg=\"File have one line per venv\")\n\n        venv = self.venvscache.get_venv(uuid=self.uuids[0])\n        manager.store_usage_stat(venv, self.venvscache)\n\n        lines = self.get_usage_lines(manager)\n        self.assertEqual(2, len([1 for u, d in lines if u == self.uuids[0]]),\n                         msg=\"Selected uuid is two times in file\")\n\n    def test_usage_file_is_compacted_when_though_no_venv_is_removed(self):\n        old_date = datetime.now()\n        new_date = old_date + timedelta(days=1)\n\n        with patch('fades.envbuilder.datetime') as mock_datetime:\n            mock_datetime.now.return_value = old_date\n            mock_datetime.strptime.side_effect = lambda *args, **kw: datetime.strptime(*args, **kw)\n            mock_datetime.strftime.side_effect = lambda *args, **kw: datetime.strftime(*args, **kw)\n\n            manager = envbuilder.UsageManager(self.file_path, self.venvscache)\n            lines = self.get_usage_lines(manager)\n            for u, d in lines:\n                self.assertEqual(old_date, d, msg=\"All records have the same date\")\n\n            venv = self.venvscache.get_venv(uuid=self.uuids[0])\n            manager.store_usage_stat(venv, self.venvscache)\n\n            mock_datetime.now.return_value = new_date\n            manager.store_usage_stat(venv, self.venvscache)\n\n            lines = self.get_usage_lines(manager)\n            self.assertEqual(len(self.uuids) + 2, len(lines))\n\n            manager.clean_unused_venvs(4)\n            lines = self.get_usage_lines(manager)\n            self.assertEqual(len(self.uuids), len(lines))\n\n            for u, d in lines:\n                if u == self.uuids[0]:\n                    self.assertEqual(new_date, d, msg=\"Selected env have new date\")\n                else:\n                    self.assertEqual(old_date, d, msg=\"Others envs have old date\")\n\n    def test_executionerror_exception(self):\n        env_builder = envbuilder._FadesEnvBuilder()\n        interpreter = 'python3'\n        is_current = False\n        options = {\"venv_options\": []}\n        with patch('fades.envbuilder.helpers.logged_exec') as mock_lexec:\n            mock_lexec.side_effect = envbuilder.helpers.ExecutionError(1, 'cmd', ['stdout'])\n            with self.assertRaises(FadesError) as cm:\n                env_builder.create_env(interpreter, is_current, options)\n            self.assertEqual(str(cm.exception), \"Failed to run venv module externally\")\n\n    def test_general_error_exception(self):\n        env_builder = envbuilder._FadesEnvBuilder()\n        interpreter = 'python3'\n        is_current = False\n        options = {\"venv_options\": []}\n        with patch('fades.envbuilder.helpers.logged_exec') as mock_lexec:\n            mock_lexec.side_effect = Exception()\n            with self.assertRaises(FadesError) as cm:\n                env_builder.create_env(interpreter, is_current, options)\n            self.assertEqual(str(cm.exception), \"General error while running external venv\")\n\n    def test_when_a_venv_is_removed_it_is_removed_from_everywhere(self):\n        old_date = datetime.now()\n        new_date = old_date + timedelta(days=5)\n\n        with patch('fades.envbuilder.datetime') as mock_datetime:\n            mock_datetime.now.return_value = old_date\n            mock_datetime.strptime.side_effect = lambda *args, **kw: datetime.strptime(*args, **kw)\n            mock_datetime.strftime.side_effect = lambda *args, **kw: datetime.strftime(*args, **kw)\n\n            manager = envbuilder.UsageManager(self.file_path, self.venvscache)\n            lines = self.get_usage_lines(manager)\n            for u, d in lines:\n                self.assertEqual(old_date, d, msg=\"All records have the same date\")\n\n            venv = self.venvscache.get_venv(uuid=self.uuids[0])\n            manager.store_usage_stat(venv, self.venvscache)\n\n            mock_datetime.now.return_value = new_date\n            manager.store_usage_stat(venv, self.venvscache)\n\n            lines = self.get_usage_lines(manager)\n            self.assertEqual(len(self.uuids) + 2, len(lines))\n\n            with patch('fades.envbuilder.destroy_venv') as destroy_venv_mock:\n                manager.clean_unused_venvs(4)\n                lines = self.get_usage_lines(manager)\n                self.assertEqual(1, len(lines), msg=\"Only one venv remains alive\")\n                uuid, d = lines[0]\n\n                self.assertEqual(self.uuids[0], uuid,\n                                 msg=\"The env who survive is the last used one.\")\n\n                # destroy_env and cache.remove was called for the others\n                for uuid in self.uuids[1:]:\n                    env_path = self.venvscache.get_venv(uuid=uuid)['env_path']\n                    destroy_venv_mock.assert_any_call(env_path, self.venvscache)\n"
  },
  {
    "path": "tests/test_file_options.py",
    "content": "# Copyright 2016 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for file_options.\"\"\"\n\nimport argparse\nimport unittest\nfrom configparser import ConfigParser\nfrom unittest.mock import patch\n\nfrom fades import file_options\n\n\nclass OptionsFileTestCase(unittest.TestCase):\n    \"\"\"Check file_options.options_from_file().\"\"\"\n\n    def setUp(self):\n        self.argparser = argparse.ArgumentParser()\n        self.argparser.add_argument\n        self.argparser.add_argument('-f', '--foo', action='store_true')\n        self.argparser.add_argument('-b', '--bar', action='store')\n        self.argparser.add_argument('-d', '--dependency', action='append')\n        self.argparser.add_argument('positional', nargs='?', default=None)\n\n    def build_parser(self, args):\n        config_parser = ConfigParser()\n        config_parser['fades'] = args\n        return config_parser\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('/foo/none', '/dev/null'))\n    def test_no_config_files(self):\n        args = self.argparser.parse_args([])\n        result = file_options.options_from_file(args)\n\n        self.assertEqual(args, result)\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini',))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_single_config_file_no_cli(self, mocked_parser):\n        mocked_parser.return_value = [('foo', 'true'), ('bar', 'hux')]\n        args = self.argparser.parse_args(['positional'])\n        result = file_options.options_from_file(args)\n\n        self.assertTrue(result.foo)\n        self.assertEqual(result.bar, 'hux')\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini',))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_single_config_file_with_cli(self, mocked_parser):\n        mocked_parser.return_value = [('foo', 'false'), ('bar', 'hux'), ('no_in_cli', 'testing')]\n        args = self.argparser.parse_args(['--foo', '--bar', 'other', 'positional'])\n        result = file_options.options_from_file(args)\n\n        self.assertTrue(result.foo)\n        self.assertEqual(result.bar, 'other')\n        self.assertEqual(result.no_in_cli, 'testing')\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini',))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_single_config_file_with_mergeable(self, mocked_parser):\n        mocked_parser.return_value = [('dependency', 'two')]\n        args = self.argparser.parse_args(\n            ['--foo', '--bar', 'other', '--dependency', 'one', 'positional'])\n        result = file_options.options_from_file(args)\n\n        self.assertTrue(result.foo)\n        self.assertEqual(result.bar, 'other')\n        self.assertEqual(result.dependency, ['one', 'two'])\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini',))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_single_config_file_complex_mergeable(self, mocked_parser):\n        mocked_parser.return_value = [('dependency', 'requests>=2.1,<2.8,!=2.6.5')]\n        args = self.argparser.parse_args(\n            ['--foo', '--bar', 'other', '--dependency', 'one', 'positional'])\n        result = file_options.options_from_file(args)\n\n        self.assertTrue(result.foo)\n        self.assertEqual(result.bar, 'other')\n        self.assertEqual(result.dependency, ['one', 'requests>=2.1,<2.8,!=2.6.5'])\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini', 'mock2.ini'))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_two_config_file_with_mergeable(self, mocked_parser):\n        mocked_parser.side_effect = [\n            [('dependency', 'two')],\n            [('dependency', 'three')],\n        ]\n        args = self.argparser.parse_args(\n            ['--foo', '--bar', 'other', '--dependency', 'one', 'positional'])\n        result = file_options.options_from_file(args)\n\n        self.assertTrue(result.foo)\n        self.assertEqual(result.bar, 'other')\n        self.assertEqual(result.dependency, ['one', 'two', 'three'])\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini', 'mock2.ini'))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_two_config_file_with_booleans(self, mocked_parser):\n        mocked_parser.side_effect = [\n            [('foo', 'true')],\n            [('foo', 'false')],\n        ]\n        args = self.argparser.parse_args([])\n        result = file_options.options_from_file(args)\n\n        self.assertFalse(result.foo)\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini', 'mock2.ini'))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_two_config_file_override_by_cli(self, mocked_parser):\n        mocked_parser.side_effect = [\n            [('bar', 'no_this')],\n            [('bar', 'no_this_b')],\n        ]\n        args = self.argparser.parse_args(['--bar', 'this'])\n        result = file_options.options_from_file(args)\n\n        self.assertEqual(result.bar, 'this')\n        self.assertIsInstance(args, argparse.Namespace)\n\n    @patch(\"fades.file_options.CONFIG_FILES\", ('mock.ini', 'mock2.ini', 'mock3.ini'))\n    @patch(\"configparser.ConfigParser.items\")\n    def test_three_config_file_override(self, mocked_parser):\n        mocked_parser.side_effect = [\n            [('bar', 'no_this')],\n            [('bar', 'neither_this')],\n            [('bar', 'this')],\n        ]\n        args = self.argparser.parse_args([])\n        result = file_options.options_from_file(args)\n\n        self.assertEqual(result.bar, 'this')\n        self.assertIsInstance(args, argparse.Namespace)\n"
  },
  {
    "path": "tests/test_files/fades_as_part_of_other_word.py",
    "content": "import logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef def_function():\n    \"\"\"\n    the fades sweetly flower.\n    foo==1.4\n\n    No te enfades con python3!\n    bar>1.9\n    \"\"\"\n    pass\n"
  },
  {
    "path": "tests/test_files/no_req.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n\"\"\"\nExtended class from EnvBuilder to create a venv using a uuid4 id.\n\nNOTE: this class only work in the same python version that Fades is\nrunning. So, you don't need to have installed a virtualenv tool. For\nother python versions Fades needs a virtualenv tool installed.\n\"\"\"\n\n\nclass FooClass():\n    \"\"\"Create always a virtualenv.\"\"\"\n    def foo(self):\n        pass\n"
  },
  {
    "path": "tests/test_files/req_all.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n\"\"\"\nExtended class from EnvBuilder to create a venv using a uuid4 id.\n\nNOTE: this class only work in the same python version that Fades is\nrunning. So, you don't need to have installed a virtualenv tool. For\nother python versions Fades needs a virtualenv tool installed.\nfades:\n    foo==1.4\n\"\"\"\n\n\nclass FooClass():\n    \"\"\"Create always a virtualenv.\n    requirements for fades:\n    bar>1.8.9\n    \"\"\"\n    def __init__(self):\n        pass\n\n    def create(self, interpreter):\n        \"\"\"\n        Create a virtualenv using the virtualenv lib.\n        fades:\n        more>1.6\n        \"\"\"\n        print(\"create\")\n"
  },
  {
    "path": "tests/test_files/req_class.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n\"\"\"\nExtended class from EnvBuilder to create a venv using a uuid4 id.\n\nNOTE: this class only work in the same python version that Fades is\nrunning. So, you don't need to have installed a virtualenv tool. For\nother python versions Fades needs a virtualenv tool installed.\n\"\"\"\n\n\nclass FooClass():\n    \"\"\"Foo class docstring.\n    requirements for fades:\n    foo\n    bar\n    \"\"\"\n    def __init__(self):\n        pass\n"
  },
  {
    "path": "tests/test_files/req_def.py",
    "content": "import logging\n\nlogger = logging.getLogger(__name__)\n\n\ndef def_function():\n    \"\"\"Something.\n    requirements for fades:\n    foo\n    bar\n    !fades\n    More info...\n    \"\"\"\n    pass\n"
  },
  {
    "path": "tests/test_files/req_mixed_backends.py",
    "content": "\"\"\"\nBleh, groovy.\n\nfades:\n    foo\n    pypi::bar\n    git+http://whatever\n    vcs::anotherurl\n\"\"\"\n"
  },
  {
    "path": "tests/test_files/req_module.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n\"\"\"\nExtended class from EnvBuilder to create a venv using a uuid4 id.\n\nNOTE: this class only work in the same python version that Fades is\nrunning. So, you don't need to have installed a virtualenv tool. For\nother python versions Fades needs a virtualenv tool installed.\nfades:\nfoo\nbar\n\"\"\"\n\n\nclass FooClass():\n    \"\"\"Create always a virtualenv.\"\"\"\n    def foo(self):\n        pass\n"
  },
  {
    "path": "tests/test_files/req_module_2.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n'''\nExtended class from EnvBuilder to create a venv using a uuid4 id.\n\nNOTE: this class only work in the same python version that Fades is\nrunning. So, you don't need to have installed a virtualenv tool. For\nother python versions Fades needs a virtualenv tool installed.\nfades:\nfoo\nbar\n'''\n\n\nclass FooClass():\n    \"\"\"Create always a virtualenv.\"\"\"\n    def foo(self):\n        pass\n"
  },
  {
    "path": "tests/test_files/req_module_3.py",
    "content": "# Copyright 2014 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n\"Extended class from EnvBuilder to create a venv using a uuid4 id.\"\n\n\nclass FooClass():\n    \"\"\"Create always a virtualenv.\"\"\"\n    def foo(self):\n        pass\n"
  },
  {
    "path": "tests/test_helpers.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for functions in helpers.\"\"\"\n\nimport io\nimport json\nimport os\nimport sys\nimport tempfile\nimport unittest\nfrom http.server import HTTPStatus\nfrom unittest.mock import patch\nfrom urllib.error import HTTPError\nfrom urllib.request import Request\n\nimport logassert\nimport pytest\nfrom xdg import BaseDirectory\n\nfrom fades import helpers, parsing\n\n\nPATH_TO_EXAMPLES = \"tests/examples/\"\n\n\nclass GetInterpreterVersionTestCase(unittest.TestCase):\n    \"\"\"Some tests for get_interpreter_version.\"\"\"\n\n    def test_current_version(self):\n        values = {None: ('/path/to/python1.0'), \"/path/to/python\": ('/path/to/python1.0')}\n\n        def side_effect(arg=None):\n            return values[arg]\n\n        with patch.object(helpers, '_get_interpreter_info') as mock:\n            mock.side_effect = side_effect\n            interpreter, is_current = helpers.get_interpreter_version('/path/to/python')\n        self.assertEqual(is_current, True)\n\n    def test_other_version(self):\n        values = {None: ('/path/to/python1.0'), \"/path/to/python\": ('/path/to/python9.8')}\n\n        def side_effect(arg=None):\n            return values[arg]\n\n        with patch.object(helpers, '_get_interpreter_info') as mock:\n            mock.side_effect = side_effect\n            interpreter, is_current = helpers.get_interpreter_version('/path/to/python')\n        self.assertEqual(is_current, False)\n\n    def test_none_requested(self):\n        values = {None: ('/path/to/python1.0'), \"/path/to/python\": ('/path/to/python9.8')}\n\n        def side_effect(arg=None):\n            return values[arg]\n\n        with patch.object(helpers, '_get_interpreter_info') as mock:\n            mock.side_effect = side_effect\n            interpreter, is_current = helpers.get_interpreter_version(requested_interpreter=None)\n        self.assertEqual(is_current, True)\n        self.assertTrue(mock.call_count, 1)\n\n\nclass GetInterpreterInfoTestCase(unittest.TestCase):\n    \"\"\"Some tests for _get_interpreter_info.\"\"\"\n\n    def setUp(self):\n        logassert.setup(self, 'fades.helpers')\n\n    def test_none_requested(self):\n        with patch.object(sys, 'version_info', (9, 8)), patch.object(sys,\n                                                                     'executable',\n                                                                     '/path/to/python'):\n            interpreter = helpers._get_interpreter_info(None)\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_fullpath_nodigit(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('/path/to/python')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_fullpath_with_major(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python9\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('/path/to/python9')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_fullpath_with_minor(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python9.8\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('/path/to/python9.8')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_nodigit(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('python')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_with_major(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python9\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('python9')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_with_minor(self):\n        response = [('{\"serial\": 0,\"path\": \"/path/to/python9.8\",\"minor\": 8,\"major\": 9,\"micro\": 0,'\n                    '\"releaselevel\": \"ultimate\"}')]\n        with patch.object(helpers, 'logged_exec', return_value=response):\n            interpreter = helpers._get_interpreter_info('python9.8')\n        self.assertEqual(interpreter, '/path/to/python9.8')\n\n    def test_requested_not_exists(self):\n        side_effect = IOError(\"[Errno 2] No such file or directory: 'pythonME'\")\n\n        with patch('fades.helpers.logged_exec') as mock_lexec:\n            mock_lexec.side_effect = side_effect\n\n            with self.assertRaises(Exception):\n                helpers._get_interpreter_info('pythonME')\n\n        self.assertLoggedError(\"Error getting requested interpreter version:\"\n                               \" [Errno 2] No such file or directory: 'pythonME'\")\n\n\nclass GetLatestVersionNumberTestCase(unittest.TestCase):\n    \"\"\"Some tests for get_latest_version_number.\"\"\"\n\n    def setUp(self):\n        logassert.setup(self, 'fades.helpers')\n\n    def test_get_version_correct(self):\n        with open(os.path.join(PATH_TO_EXAMPLES, 'pypi_get_version_ok.json'), \"rb\") as fh:\n            with patch('urllib.request.urlopen') as mock_urlopen:\n                mock_urlopen.return_value = fh\n                last_version = helpers.get_latest_version_number(\"some_package\")\n        mock_urlopen.assert_called_once_with(helpers.BASE_PYPI_URL.format(name=\"some_package\"))\n        self.assertEqual(last_version, '2.8.1')\n\n    def test_get_version_wrong(self):\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = HTTPError(\"url\",\n                                                                500,\n                                                                \"mgs\",\n                                                                {},\n                                                                io.BytesIO())\n                mock_urlopen.return_value = mock_http_response\n                self.assertRaises(Exception, helpers.get_latest_version_number, \"some_package\")\n                self.assertLoggedWarning(\"Network error.\")\n\n    def test_get_version_fail(self):\n        with open(os.path.join(PATH_TO_EXAMPLES, 'pypi_get_version_fail.json'), \"rb\") as fh:\n            with patch('urllib.request.urlopen') as mock_urlopen:\n                mock_urlopen.return_value = fh\n                self.assertRaises(KeyError, helpers.get_latest_version_number, \"some_package\")\n                self.assertLoggedError(\"Could not get the version of the package. Error:\")\n\n\nclass CheckPyPIUpdatesTestCase(unittest.TestCase):\n    \"\"\"Some tests for check_pypi_updates.\"\"\"\n\n    def setUp(self):\n        logassert.setup(self, 'fades.helpers')\n\n    def test_check_pypi_updates_with_and_without_version(self):\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = [b'{\"info\": {\"version\": \"1.9\"}}',\n                                                       b'{\"info\": {\"version\": \"2.1\"}}']\n                mock_urlopen.return_value = mock_http_response\n                requested = parsing.parse_manual([\"django==1.7.5\", \"requests\"])\n                dependencies = helpers.check_pypi_updates(requested)\n                dep_django = dependencies['pypi'][0]\n                dep_request = dependencies['pypi'][1]\n                self.assertLoggedInfo('There is a new version of django: 1.9')\n                self.assertEqual(str(dep_request.specifier), \"==2.1\")\n                self.assertEqual(str(dep_django.specifier), \"==1.7.5\")\n                self.assertLoggedInfo(\"The latest version of 'requests' is 2.1 and will use it.\")\n\n    def test_check_pypi_updates_with_a_higher_version_of_a_package_simple(self):\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = [b'{\"info\": {\"version\": \"1.9\"}}']\n                mock_urlopen.return_value = mock_http_response\n                helpers.check_pypi_updates(parsing.parse_manual([\"django==100.1.1\"]))\n                self.assertLoggedWarning(\n                    \"The requested version for django is greater than latest found in PyPI: 1.9\")\n\n    def test_check_pypi_updates_with_a_higher_version_of_a_package_real_order(self):\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = [b'{\"info\": {\"version\": \"2.9\"}}']\n                mock_urlopen.return_value = mock_http_response\n                helpers.check_pypi_updates(parsing.parse_manual([\"django==10.1\"]))\n                self.assertLoggedWarning(\n                    \"The requested version for django is greater than latest found in PyPI: 2.9\")\n\n    def test_check_pypi_updates_with_the_latest_version_of_a_package(self):\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = [b'{\"info\": {\"version\": \"1.9\"}}']\n                mock_urlopen.return_value = mock_http_response\n                helpers.check_pypi_updates(parsing.parse_manual([\"django==1.9\"]))\n                self.assertLoggedInfo(\n                    \"The requested version for django is the latest one in PyPI: 1.9\")\n\n\nclass GetDirsTestCase(unittest.TestCase):\n    \"\"\"Utilities to get dir.\"\"\"\n\n    _home = os.path.expanduser(\"~\")\n\n    def test_basedir_xdg(self):\n        direct = helpers.get_basedir()\n        self.assertEqual(direct, os.path.join(BaseDirectory.xdg_data_home, 'fades'))\n\n    def _fake_snap_env_dir(self, direct):\n        \"\"\"Fake Snap's environment variable.\"\"\"\n        os.environ[helpers.SNAP_BASEDIR_NAME] = direct\n        self.addCleanup(os.environ.pop, helpers.SNAP_BASEDIR_NAME)\n\n    def test_basedir_snap(self):\n        with tempfile.TemporaryDirectory() as dirname:\n            self._fake_snap_env_dir(dirname)\n            direct = helpers.get_basedir()\n            self.assertEqual(direct, os.path.join(dirname, 'data'))\n\n    def test_basedir_default(self):\n        with patch.object(helpers, \"_get_basedirectory\") as mock:\n            mock.side_effect = ImportError()\n            direct = helpers.get_basedir()\n            self.assertEqual(direct, os.path.join(self._home, '.fades'))\n\n    def test_basedir_xdg_nonexistant(self):\n        with patch(\"xdg.BaseDirectory\") as mock:\n            with tempfile.TemporaryDirectory() as dirname:\n                mock.xdg_data_home = dirname\n                direct = helpers.get_basedir()\n                self.assertTrue(os.path.exists(direct))\n\n    def test_basedir_snap_nonexistant(self):\n        with tempfile.TemporaryDirectory() as dirname:\n            self._fake_snap_env_dir(dirname)\n            direct = helpers.get_basedir()\n            self.assertTrue(os.path.exists(direct))\n\n    def test_confdir_xdg(self):\n        direct = helpers.get_confdir()\n        self.assertEqual(direct, os.path.join(BaseDirectory.xdg_config_home, 'fades'))\n\n    def test_confdir_snap(self):\n        with tempfile.TemporaryDirectory() as dirname:\n            self._fake_snap_env_dir(dirname)\n            direct = helpers.get_confdir()\n            self.assertEqual(direct, os.path.join(dirname, 'config'))\n\n    def test_confdir_default(self):\n        with patch.object(helpers, \"_get_basedirectory\") as mock:\n            mock.side_effect = ImportError()\n            direct = helpers.get_confdir()\n            self.assertEqual(direct, os.path.join(self._home, '.fades'))\n\n    def test_confdir_xdg_nonexistant(self):\n        with patch(\"xdg.BaseDirectory\") as mock:\n            with tempfile.TemporaryDirectory() as dirname:\n                mock.xdg_config_home = dirname\n                direct = helpers.get_confdir()\n                self.assertTrue(os.path.exists(direct))\n\n    def test_confdir_snap_nonexistant(self):\n        with tempfile.TemporaryDirectory() as dirname:\n            self._fake_snap_env_dir(dirname)\n            direct = helpers.get_confdir()\n            self.assertTrue(os.path.exists(direct))\n\n\nclass CheckPackageExistenceTestCase(unittest.TestCase):\n    \"\"\"Test for check_pypi_exists.\"\"\"\n\n    def setUp(self):\n        logassert.setup(self, 'fades.helpers')\n\n    def test_exists(self):\n        deps = parsing.parse_manual([\"foo\"])\n\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.status = HTTPStatus.OK\n                mock_urlopen.return_value = mock_http_response\n\n                exists = helpers.check_pypi_exists(deps)\n        self.assertTrue(exists)\n        self.assertLogged(\"exists in PyPI\")\n\n    def test_all_exists(self):\n        dependencies = parsing.parse_manual(['foo', 'bar', 'baz'])\n\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.status = HTTPStatus.OK\n                mock_urlopen.side_effect = [mock_http_response] * 3\n\n                exists = helpers.check_pypi_exists(dependencies)\n        self.assertTrue(exists)\n        self.assertLogged(\"exists in PyPI\")\n\n    def test_doesnt_exists(self):\n        dependency = parsing.parse_manual([\"foo\"])\n\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            mock_http_error = HTTPError(\"url\", HTTPStatus.NOT_FOUND, \"mgs\", {}, io.BytesIO())\n            mock_urlopen.side_effect = mock_http_error\n\n            exists = helpers.check_pypi_exists(dependency)\n\n        self.assertFalse(exists)\n        self.assertLoggedError(\"foo doesn't exists in PyPI.\")\n\n    def test_one_doesnt_exists(self):\n        dependencies = parsing.parse_manual([\"foo\", \"bar\"])\n\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_error = HTTPError(\"url\", HTTPStatus.NOT_FOUND, \"mgs\", {}, io.BytesIO())\n                mock_http_response.status = HTTPStatus.OK\n                mock_urlopen.side_effect = [mock_http_response, mock_http_error]\n\n                exists = helpers.check_pypi_exists(dependencies)\n\n        self.assertFalse(exists)\n        self.assertLoggedError(\"bar doesn't exists in PyPI.\")\n\n    def test_error_hitting_pypi(self):\n        dependency = parsing.parse_manual([\"foo\"])\n\n        with self.assertRaises(Exception):\n            with patch('urllib.request.urlopen') as mock_urlopen:\n                mock_urlopen.side_effect = ValueError(\"cabum!!\")\n\n                helpers.check_pypi_exists(dependency)\n\n    def test_status_code_error(self):\n        dependency = parsing.parse_manual([\"foo\"])\n\n        with self.assertRaises(Exception):\n            with patch('urllib.request.urlopen') as mock_urlopen:\n                mock_http_error = HTTPError(\"url\", 400, \"mgs\", {}, io.BytesIO())\n                mock_urlopen.side_effect = mock_http_error\n\n                helpers.check_pypi_exists(dependency)\n\n    def test_redirect_response(self):\n        deps = parsing.parse_manual([\"foo\"])\n\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.status = 302  # redirect\n                mock_urlopen.return_value = mock_http_response\n\n                exists = helpers.check_pypi_exists(deps)\n        self.assertTrue(exists)\n        self.assertLoggedWarning(\"Got a (unexpected) HTTP_STATUS\")\n\n\nclass ScriptDownloaderTestCase(unittest.TestCase):\n    \"\"\"Check the script downloader.\"\"\"\n\n    def setUp(self):\n        logassert.setup(self, 'fades.helpers')\n\n    def test_external_public_function(self):\n        test_url = \"http://scripts.com/foobar.py\"\n        test_content = \"test content of the remote script ññ\"\n        with patch('fades.helpers._ScriptDownloader') as mock_downloader_class:\n            mock_downloader = mock_downloader_class()\n            mock_downloader.get.return_value = test_content\n            mock_downloader.name = 'mock downloader'\n            filepath = helpers.download_remote_script(test_url)\n\n        # plan to remove the downloaded content (so test remains clean)\n        self.addCleanup(os.unlink, filepath)\n\n        # checks\n        mock_downloader_class.assert_called_with(test_url)\n        self.assertLoggedInfo(\n            \"Downloading remote script from {!r}\".format(test_url),\n            repr(filepath), \"(using 'mock downloader' downloader)\")\n        with open(filepath, \"rt\", encoding='utf8') as fh:\n            self.assertEqual(fh.read(), test_content)\n\n    def test_decide_linkode(self):\n        url = \"http://linkode.org/#02c5nESQBLEjgBRhUwJK74\"\n        downloader = helpers._ScriptDownloader(url)\n        name = downloader._decide()\n        self.assertEqual(name, 'linkode')\n\n    def test_decide_pastebin(self):\n        url = \"https://pastebin.com/sZGwz7SL\"\n        downloader = helpers._ScriptDownloader(url)\n        name = downloader._decide()\n        self.assertEqual(name, 'pastebin')\n\n    def test_decide_gist(self):\n        url = \"https://gist.github.com/facundobatista/6ff4f75760a9acc35e68bae8c1d7da1c\"\n        downloader = helpers._ScriptDownloader(url)\n        name = downloader._decide()\n        self.assertEqual(name, 'gist')\n\n    def test_downloader_raw(self):\n        test_url = \"http://scripts.com/foobar.py\"\n        raw_service_response = b\"test content of the remote script\"\n        downloader = helpers._ScriptDownloader(test_url)\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.return_value = raw_service_response\n                mock_urlopen.return_value = mock_http_response\n                mock_http_response.geturl.return_value = test_url\n\n            content = downloader.get()\n\n        # check urlopen was called with the proper url, and passing correct headers\n        headers = {\n            'Accept': 'text/plain',\n            'User-agent': helpers._ScriptDownloader.USER_AGENT,\n        }\n        (call,) = mock_urlopen.mock_calls\n        (called_request,) = call[1]\n        self.assertIsInstance(called_request, Request)\n        self.assertEqual(called_request.full_url, test_url)\n        self.assertEqual(called_request.headers, headers)\n        self.assertEqual(content, raw_service_response.decode(\"utf8\"))\n\n    def test_downloader_linkode(self):\n        test_url = \"http://linkode.org/#02c5nESQBLEjgBRhUwJK74\"\n        test_content = \"test content of the remote script áéíóú\"\n        raw_service_response = json.dumps({\n            'content': test_content,\n            'morestuff': 'whocares',\n        }).encode(\"utf8\")\n\n        downloader = helpers._ScriptDownloader(test_url)\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.return_value = raw_service_response\n                mock_urlopen.return_value = mock_http_response\n\n            content = downloader.get()\n\n        # check urlopen was called with the proper url, and passing correct headers\n        headers = {\n            'Accept': 'application/json',\n            'User-agent': helpers._ScriptDownloader.USER_AGENT,\n        }\n        (call,) = mock_urlopen.mock_calls\n        (called_request,) = call[1]\n        self.assertIsInstance(called_request, Request)\n        self.assertEqual(\n            called_request.full_url, \"https://linkode.org/api/1/linkodes/02c5nESQBLEjgBRhUwJK74\")\n        self.assertEqual(called_request.headers, headers)\n        self.assertEqual(content, test_content)\n\n    def test_downloader_pastebin(self):\n        test_url = \"http://pastebin.com/sZGwz7SL\"\n        real_url = \"https://pastebin.com/raw/sZGwz7SL\"\n        test_content = \"test content of the remote script áéíóú\"\n        raw_service_response = test_content.encode(\"utf8\")\n\n        downloader = helpers._ScriptDownloader(test_url)\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.return_value = raw_service_response\n                mock_urlopen.return_value = mock_http_response\n                mock_http_response.geturl.return_value = real_url\n\n            content = downloader.get()\n\n        # check urlopen was called with the proper url, and passing correct headers\n        headers = {\n            'Accept': 'text/plain',\n            'User-agent': helpers._ScriptDownloader.USER_AGENT,\n        }\n        (call,) = mock_urlopen.mock_calls\n        (called_request,) = call[1]\n        self.assertIsInstance(called_request, Request)\n        self.assertEqual(called_request.full_url, real_url)\n        self.assertEqual(called_request.headers, headers)\n        self.assertEqual(content, test_content)\n\n    def test_downloader_gist(self):\n        test_url = \"http://gist.github.com/facundobatista/6ff4f75760a9acc35e68bae8c1d7da1c\"\n        real_url = \"https://gist.github.com/facundobatista/6ff4f75760a9acc35e68bae8c1d7da1c/raw\"\n        test_content = \"test content of the remote script áéíóú\"\n        raw_service_response = test_content.encode(\"utf8\")\n\n        downloader = helpers._ScriptDownloader(test_url)\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.return_value = raw_service_response\n                mock_urlopen.return_value = mock_http_response\n                mock_http_response.geturl.return_value = real_url\n\n            content = downloader.get()\n\n        # check urlopen was called with the proper url, and passing correct headers\n        headers = {\n            'Accept': 'text/plain',\n            'User-agent': helpers._ScriptDownloader.USER_AGENT,\n        }\n        (call,) = mock_urlopen.mock_calls\n        (called_request,) = call[1]\n        self.assertIsInstance(called_request, Request)\n        self.assertEqual(called_request.full_url, real_url)\n        self.assertEqual(called_request.headers, headers)\n        self.assertEqual(content, test_content)\n\n    def test_downloader_raw_with_redirection(self):\n        test_url = \"http://bit.ly/will-redirect\"\n        final_url = \"http://real-service.com/\"\n        raw_service_response = b\"test content of the remote script\"\n        downloader = helpers._ScriptDownloader(test_url)\n        response_contents = [\n            b\"whatever; we don't care as we are redirectect\",\n            raw_service_response,\n        ]\n        with patch('urllib.request.urlopen') as mock_urlopen:\n            with patch('http.client.HTTPResponse') as mock_http_response:\n                mock_http_response.read.side_effect = lambda: response_contents.pop()\n                mock_http_response.geturl.return_value = final_url\n                mock_urlopen.return_value = mock_http_response\n\n                content = downloader.get()\n\n        # two calls, first to the service that will redirect us, second to the final one\n        call1, call2 = mock_urlopen.mock_calls\n\n        (called_request,) = call1[1]\n        self.assertEqual(called_request.full_url, test_url)\n\n        (called_request,) = call2[1]\n        self.assertEqual(called_request.full_url, final_url)\n        self.assertEqual(content, raw_service_response.decode(\"utf8\"))\n\n        self.assertLoggedInfo(\"Download redirect detect, now downloading from\", final_url)\n\n\ndef test_getbinpath_posix(tmp_path):\n    realbin = tmp_path / \"bin\"\n    realbin.mkdir()\n    path = helpers.get_env_bin_path(tmp_path)\n    assert path == realbin\n\n\ndef test_getbinpath_windows(tmp_path):\n    realbin = tmp_path / \"Scripts\"\n    realbin.mkdir()\n    path = helpers.get_env_bin_path(tmp_path)\n    assert path == realbin\n\n\ndef test_getbinpath_missing(tmp_path):\n    with pytest.raises(ValueError):\n        helpers.get_env_bin_path(tmp_path)\n"
  },
  {
    "path": "tests/test_infra.py",
    "content": "# Copyright 2017-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for infrastructure stuff.\"\"\"\n\nimport io\nimport logging\nfrom unittest.mock import patch\n\nimport docutils.core\nimport pydocstyle\nimport pytest\nimport rst2html5_\nfrom flake8.api.legacy import get_style_guide\nfrom pyuca import Collator\n\nfrom tests import get_python_filepaths\n\nFLAKE8_ROOTS = ['fades', 'tests']\nFLAKE8_OPTIONS = {'max_line_length': 99, 'select': ['E', 'W', 'F', 'C', 'N']}\nPEP257_ROOTS = ['fades']\n\n# avoid seeing all DEBUG logs if the test fails\nfor logger_name in ('flake8.plugins', 'flake8.api', 'flake8.checker', 'flake8.main'):\n    logging.getLogger(logger_name).setLevel(logging.CRITICAL)\n\n\ndef test_flake8_pytest(capsys):\n    python_filepaths = get_python_filepaths(FLAKE8_ROOTS)\n    style_guide = get_style_guide(**FLAKE8_OPTIONS)\n    report = style_guide.check_files(python_filepaths)\n    if report.total_errors != 0:\n        out, _ = capsys.readouterr()\n        pytest.fail(f\"There are {report.total_errors} issues!\\n{''.join(out)}\")\n\n\ndef test_pep257_pytest():\n    python_filepaths = get_python_filepaths(PEP257_ROOTS)\n    to_ignore = {\n        \"D105\",  # Missing docstring in magic method\n        \"D107\",  # Missing docstring in __init__\n    }\n    to_include = pydocstyle.violations.conventions.pep257 - to_ignore\n    result = list(pydocstyle.check(python_filepaths, select=to_include))\n    assert len(result) == 0, \"There are issues!\\n\" + '\\n'.join(map(str, result))\n\n\ndef test_readme_sanity():\n    fake_stdout = io.StringIO()  # just to ignore the output\n    fake_stderr = io.StringIO()  # will have content if there are problems\n    with open('README.rst', 'rt', encoding='utf8') as fh:\n        with patch('sys.stdout', fake_stdout):\n            with patch('sys.stderr', fake_stderr):\n                docutils.core.publish_file(source=fh, writer=rst2html5_.HTML5Writer())\n\n    errors = fake_stderr.getvalue()\n    assert not bool(errors), \"There are issues!\\n\" + errors\n\n\ndef test_authors_ordering():\n    with open('AUTHORS', 'rt', encoding='utf8') as fh:\n        authors = fh.readlines()\n    ordered_authors = sorted(authors, key=Collator().sort_key)\n    assert authors == ordered_authors\n"
  },
  {
    "path": "tests/test_logger.py",
    "content": "# Copyright 2018 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for logger related code.\"\"\"\nfrom fades.logger import set_up as log_set_up\n\n\ndef test_salutes_info(logs):\n    \"\"\"Check saluting handler.\"\"\"\n    logger = log_set_up(verbose=False, quiet=True)\n    logger.warning(\"test foobar\")\n\n    assert \"Hi! This is fades\" in logs.info\n    assert \"test foobar\" in logs.warning\n\n\ndef test_salutes_once(logs):\n    logger = log_set_up(verbose=False, quiet=False)\n    logger.info(\"test foobar\")\n    assert \"Hi! This is fades\" in logs.info\n    assert \"test foobar\" in logs.info\n\n    # again, check this time it didn't salute, but original log message is ok\n    logs.reset()\n    logger.info(\"test barbarroja\")\n\n    assert \"Hi! This is fades\" not in logs.info\n    assert \"test barbarroja\" in logs.info\n"
  },
  {
    "path": "tests/test_main.py",
    "content": "# Copyright 2015-2024 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for some code in main.\"\"\"\n\nimport os\nimport unittest\nfrom unittest.mock import patch\n\nfrom packaging.requirements import Requirement\n\nfrom fades import VERSION, FadesError, __version__, main, parsing, REPO_PYPI, REPO_VCS\nfrom tests import create_tempfile\n\n\nclass VirtualenvCheckingTestCase(unittest.TestCase):\n    \"\"\"Tests for the virtualenv checker.\"\"\"\n\n    def test_have_realprefix(self):\n        resp = main.detect_inside_virtualenv('prefix', 'real_prefix', 'base_prefix')\n        self.assertTrue(resp)\n\n    def test_no_baseprefix(self):\n        resp = main.detect_inside_virtualenv('prefix', None, None)\n        self.assertFalse(resp)\n\n    def test_prefix_is_baseprefix(self):\n        resp = main.detect_inside_virtualenv('prefix', None, 'prefix')\n        self.assertFalse(resp)\n\n    def test_prefix_is_not_baseprefix(self):\n        resp = main.detect_inside_virtualenv('prefix', None, 'other prefix')\n        self.assertTrue(resp)\n\n\nclass DepsGatheringTestCase(unittest.TestCase):\n    \"\"\"Tests for the gathering stage of consolidate_dependencies.\"\"\"\n\n    def test_needs_ipython(self):\n        d = main.consolidate_dependencies(needs_ipython=True, child_program=None,\n                                          requirement_files=None, manual_dependencies=None)\n\n        self.assertDictEqual(d, {'pypi': {Requirement('ipython')}})\n\n    def test_child_program(self):\n        child_program = 'tests/test_files/req_module.py'\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=child_program,\n                                          requirement_files=None, manual_dependencies=None)\n\n        self.assertDictEqual(d, {'pypi': {Requirement('foo'), Requirement('bar')}})\n\n    def test_requirement_files(self):\n        requirement_files = [create_tempfile(self, ['dep'])]\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=None)\n\n        self.assertDictEqual(d, {'pypi': {Requirement('dep')}})\n\n    def test_manual_dependencies(self):\n        manual_dependencies = ['dep']\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=None,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertDictEqual(d, {'pypi': {Requirement('dep')}})\n\n\nclass DepsMergingTestCase(unittest.TestCase):\n    \"\"\"Tests for the merging stage of consolidate_dependencies.\"\"\"\n\n    def test_two_different(self):\n        requirement_files = [create_tempfile(self, ['1', '2'])]\n        manual_dependencies = ['vcs::3', 'vcs::4']\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertEqual(d, {\n            'pypi': {Requirement('1'), Requirement('2')},\n            'vcs': {parsing.VCSDependency('3'), parsing.VCSDependency('4')}\n        })\n\n    def test_two_same_repo(self):\n        requirement_files = [create_tempfile(self, ['1', '2'])]\n        manual_dependencies = ['3', '4']\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertDictEqual(d, {\n            'pypi': {Requirement('1'), Requirement('2'), Requirement('3'), Requirement('4')}\n        })\n\n    def test_complex_case(self):\n        child_program = create_tempfile(self, ['\"\"\"fades:', '1', '2', '\"\"\"'])\n        requirement_files = [create_tempfile(self, ['3', 'vcs::5'])]\n        manual_dependencies = ['vcs::4', 'vcs::6']\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=child_program,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertEqual(d, {\n            'pypi': {Requirement('1'), Requirement('2'), Requirement('3')},\n            'vcs': {parsing.VCSDependency('5'), parsing.VCSDependency('4'),\n                    parsing.VCSDependency('6')}\n        })\n\n    def test_one_duplicated(self):\n        requirement_files = [create_tempfile(self, ['2', '2'])]\n        manual_dependencies = None\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertDictEqual(d, {\n            'pypi': {Requirement('2')}\n        })\n\n    def test_two_different_with_dups(self):\n        requirement_files = [create_tempfile(self, ['1', '2', '2', '2'])]\n        manual_dependencies = ['vcs::3', 'vcs::4', 'vcs::1', 'vcs::2']\n\n        d = main.consolidate_dependencies(needs_ipython=False, child_program=None,\n                                          requirement_files=requirement_files,\n                                          manual_dependencies=manual_dependencies)\n\n        self.assertEqual(d, {\n            'pypi': {Requirement('1'), Requirement('2')},\n            'vcs': {parsing.VCSDependency('1'), parsing.VCSDependency('2'),\n                    parsing.VCSDependency('3'), parsing.VCSDependency('4')}\n        })\n\n\nclass MiscTestCase(unittest.TestCase):\n    \"\"\"Miscellaneous tests.\"\"\"\n\n    def test_version_show(self):\n        self.assertEqual(\n            __version__,\n            '.'.join([str(v) for v in VERSION]),\n        )\n\n\nclass ChildProgramDeciderTestCase(unittest.TestCase):\n    \"\"\"Check how the child program is decided.\"\"\"\n\n    def test_indicated_with_executable_flag(self):\n        analyzable, child = main.decide_child_program(True, False, \"foobar.py\")\n        self.assertIsNone(analyzable)\n        self.assertEqual(child, \"foobar.py\")\n\n    def test_no_child_at_all(self):\n        analyzable, child = main.decide_child_program(False, False, None)\n        self.assertIsNone(analyzable)\n        self.assertIsNone(child)\n\n    def test_normal_child_program(self):\n        child_path = create_tempfile(self, \"\")\n        analyzable, child = main.decide_child_program(False, False, child_path)\n        self.assertEqual(analyzable, child_path)\n        self.assertEqual(child, child_path)\n\n    def test_normal_child_program_not_found(self):\n        with self.assertRaises(FadesError):\n            main.decide_child_program(False, False, 'does_not_exist.py')\n\n    def test_normal_child_program_no_access(self):\n        child_path = create_tempfile(self, \"\")\n        os.chmod(child_path, 333)  # Remove read permission.\n        self.addCleanup(os.chmod, child_path, 644)\n        with self.assertRaises(FadesError):\n            main.decide_child_program(False, False, 'does_not_exist.py')\n\n    def test_remote_child_program_simple(self):\n        with patch('fades.helpers.download_remote_script') as mock:\n            mock.return_value = \"new_path_script\"\n            analyzable, child = main.decide_child_program(\n                False, False, \"http://scripts.com/foobar.py\")\n            mock.assert_called_with(\"http://scripts.com/foobar.py\")\n\n        # check that analyzable and child are the same, and that its content is the remote one\n        self.assertEqual(analyzable, \"new_path_script\")\n        self.assertEqual(child, \"new_path_script\")\n\n    def test_remote_child_program_ssl(self):\n        with patch('fades.helpers.download_remote_script') as mock:\n            mock.return_value = \"new_path_script\"\n            analyzable, child = main.decide_child_program(\n                False, False, \"https://scripts.com/foobar.py\")\n            mock.assert_called_with(\"https://scripts.com/foobar.py\")\n\n        # check that analyzable and child are the same, and that its content is the remote one\n        self.assertEqual(analyzable, \"new_path_script\")\n        self.assertEqual(child, \"new_path_script\")\n\n    def test_indicated_with_executable_flag_with_relative_path(self):\n        \"\"\"Relative paths not allowed when using --exec.\"\"\"\n        with self.assertRaises(FadesError):\n            main.decide_child_program(True, False, os.path.join(\"path\", \"../foobar.py\"))\n\n    def test_indicated_with_executable_flag_with_absolute_path(self):\n        \"\"\"Absolute paths are allowed when using --exec.\"\"\"\n        analyzable, child = main.decide_child_program(True, False, \"/tmp/foo/bar.py\")\n        self.assertIsNone(analyzable)\n        self.assertEqual(child, \"/tmp/foo/bar.py\")\n\n    def test_module(self):\n        child_path = 'foo.bar'\n        analyzable, child = main.decide_child_program(False, True, child_path)\n        self.assertIsNone(analyzable)\n        self.assertEqual(child, child_path)\n\n\n# ---------------------------------------\n# autoimport tests\n\ndef _autoimport_safe_call(*args, **kwargs):\n    \"\"\"Call the tested function and always remove the tempfile after the test.\"\"\"\n    fpath = main.get_autoimport_scriptname(*args, **kwargs)\n\n    with open(fpath, \"rt\", encoding='utf8') as fh:\n        content = fh.read()\n    os.unlink(fpath)\n\n    return content\n\n\ndef test_autoimport_simple():\n    \"\"\"Simplest autoimport call.\"\"\"\n    dependencies = {\n        REPO_PYPI: {Requirement('mymod')},\n    }\n    content = _autoimport_safe_call(dependencies, is_ipython=False)\n\n    assert content.startswith(main.AUTOIMPORT_HEADER)\n    assert main.AUTOIMPORT_MOD_IMPORTER.format(module='mymod') in content\n\n\ndef test_autoimport_several_dependencies():\n    \"\"\"Indicate several dependencies.\"\"\"\n    dependencies = {\n        REPO_PYPI: {Requirement('mymod1'), Requirement('mymod2')},\n    }\n    content = _autoimport_safe_call(dependencies, is_ipython=False)\n\n    assert content.startswith(main.AUTOIMPORT_HEADER)\n    assert main.AUTOIMPORT_MOD_IMPORTER.format(module='mymod1') in content\n    assert main.AUTOIMPORT_MOD_IMPORTER.format(module='mymod2') in content\n\n\ndef test_autoimport_including_ipython():\n    \"\"\"Call with ipython modifier.\"\"\"\n    dependencies = {\n        REPO_PYPI: {\n            Requirement('mymod'),\n            Requirement('ipython'),  # this one is automatically added\n        },\n    }\n    content = _autoimport_safe_call(dependencies, is_ipython=True)\n\n    assert main.AUTOIMPORT_HEADER not in content\n    assert main.AUTOIMPORT_MOD_IMPORTER.format(module='mymod') in content\n    assert 'ipython' not in content\n\n\ndef test_autoimport_no_pypi_dep():\n    \"\"\"Case with no pypi dependencies.\"\"\"\n    dependencies = {\n        REPO_PYPI: {Requirement('my_pypi_mod')},\n        REPO_VCS: {'my_vcs_dependency'},\n    }\n    content = _autoimport_safe_call(dependencies, is_ipython=False)\n\n    assert main.AUTOIMPORT_MOD_IMPORTER.format(module='my_pypi_mod') in content\n    assert main.AUTOIMPORT_MOD_SKIPPING.format(dependency='my_vcs_dependency') in content\n\n\ndef test_autoimport_importer_mod_ok(capsys):\n    \"\"\"Check the generated code to import a module when works fine.\"\"\"\n    code = main.AUTOIMPORT_MOD_IMPORTER.format(module='time')  # something from stdlib, always ok\n    exec(code)\n    assert capsys.readouterr().out == \"::fades:: automatically imported 'time'\\n\"\n\n\ndef test_autoimport_importer_mod_fail(capsys):\n    \"\"\"Check the generated code to import a module when works fine.\"\"\"\n    code = main.AUTOIMPORT_MOD_IMPORTER.format(module='not_there_should_explode')\n    exec(code)\n    assert capsys.readouterr().out == \"::fades:: FAILED to autoimport 'not_there_should_explode'\\n\"\n"
  },
  {
    "path": "tests/test_multiplatform.py",
    "content": "# Copyright 2016 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify\n# it under the terms of the GNU General\n# Public License version 3, as published by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful,\n# but WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE.\n# See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License\n# along with this program.\n# If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for the helpers in multiplatform.\"\"\"\n\nimport os\nimport threading\nimport time\nimport unittest\n\nfrom fades.multiplatform import filelock\n\n\nclass LockChecker(threading.Thread):\n    \"\"\"Helper to check the lock in other thread.\n\n    The time.sleep() in the middle of the process is for time.time()\n    granularity in different platforms to not mess our tests.\n    \"\"\"\n\n    def __init__(self, filepath):\n        self.filepath = filepath\n        self.in_lock = self.post_work = None\n        self.middle_work = threading.Event()\n        super().__init__()\n\n    def run(self):\n        with filelock(self.filepath):\n            time.sleep(.01)\n            self.in_lock = time.time()\n            self.middle_work.wait()\n            self.post_work = time.time()\n\n\nclass LockCacheTestCase(unittest.TestCase):\n    \"\"\"Tests for the locking utility.\"\"\"\n\n    def setUp(self):\n        self.test_path = \"test_filelock\"\n\n    def tearDown(self):\n        if os.path.exists(self.test_path):\n            os.remove(self.test_path)\n\n    def wait(self, lock_checker, attr_name):\n        \"\"\"Wait at most a second for the LockChecker to end.\"\"\"\n        for i in range(10):\n            attr = getattr(lock_checker, attr_name)\n            if attr is not None:\n                # ended!\n                return\n            time.sleep(.3)\n        self.fail(\"LC didnt end: %s\" % (lock_checker,))\n\n    def test_lock_alone(self):\n        lc = LockChecker(self.test_path)\n        lc.start()\n        lc.middle_work.set()\n        self.wait(lc, 'post_work')\n\n    def test_lock_intermixed(self):\n        lc1 = LockChecker(self.test_path)\n        lc1.start()\n        self.wait(lc1, 'in_lock')\n\n        lc2 = LockChecker(self.test_path)\n        lc2.start()\n\n        lc1.middle_work.set()\n        self.wait(lc1, 'post_work')\n\n        lc2.middle_work.set()\n        self.wait(lc2, 'post_work')\n\n        # check LC 2 waited to enter\n        self.assertGreater(lc2.in_lock, lc1.post_work)\n\n    def test_lock_exploding(self):\n        # get the lock and explode in the middle (then ignore the blast)\n        try:\n            with filelock(self.test_path):\n                raise ValueError(\"pumba\")\n        except ValueError:\n            pass\n\n        # get the lock again\n        with filelock(self.test_path):\n            pass\n"
  },
  {
    "path": "tests/test_parsing/test_docstrings.py",
    "content": "\"\"\"Check the docstring parsing.\"\"\"\nimport io\n\nfrom fades import parsing, REPO_PYPI, REPO_VCS\n\nfrom tests import get_reqs\n\n\ndef test_empty():\n    parsed = parsing._parse_docstring(\n        io.StringIO(\"\"\"\n\n        \"\"\")\n    )\n    assert parsed == {}\n\n\ndef test_only_comment():\n    with open(\"tests/test_files/no_req.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    assert parsed == {}\n\n\ndef test_req_in_module_docstring_triple_doublequoute():\n    with open(\"tests/test_files/req_module.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    assert parsed == {REPO_PYPI: get_reqs(\"foo\", \"bar\")}\n\n\ndef test_req_in_module_docstring_triple_singlequote():\n    with open(\"tests/test_files/req_module_2.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    assert parsed == {REPO_PYPI: get_reqs(\"foo\", \"bar\")}\n\n\ndef test_req_in_module_docstring_one_doublequote():\n    with open(\"tests/test_files/req_module_3.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    assert parsed == {}\n\n\ndef test_req_in_class_docstring():\n    with open(\"tests/test_files/req_class.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    # no requirements found\n    assert parsed == {}\n\n\ndef test_req_in_def_docstring():\n    with open(\"tests/test_files/req_def.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    # no requirements found\n    assert parsed == {}\n\n\ndef test_req_in_multi_docstring():\n    with open(\"tests/test_files/req_all.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    # Only module requirements was found\n    assert parsed == {REPO_PYPI: get_reqs(\"foo==1.4\")}\n\n\ndef test_fades_word_as_part_of_text():\n    with open(\"tests/test_files/fades_as_part_of_other_word.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    assert parsed == {}\n\n\ndef test_mixed_backends():\n    with open(\"tests/test_files/req_mixed_backends.py\") as f:\n        parsed = parsing._parse_docstring(f)\n    # Only module requirements was found\n    assert parsed == {\n        REPO_PYPI: get_reqs(\"foo\", \"bar\"),\n        REPO_VCS: [\n            parsing.VCSDependency(\"git+http://whatever\"),\n            parsing.VCSDependency(\"anotherurl\"),\n        ],\n    }\n"
  },
  {
    "path": "tests/test_parsing/test_file.py",
    "content": "\"\"\"Check the imports parsing.\"\"\"\nimport io\n\nfrom logassert import Exact\n\nfrom fades import parsing, REPO_PYPI, REPO_VCS\n\nfrom tests import get_reqs\n\n\ndef test_nocomment():\n    # note that we're testing the import at the beginning of the line, and\n    # in also indented\n    parsed = parsing._parse_content(io.StringIO(\"\"\"import time\n        import time\n        from time import foo\n    \"\"\"))\n    assert parsed == {}\n\n\ndef test_simple_default():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import time\n        import foo    # fades\n    \"\"\"))\n    assert parsed == {REPO_PYPI: get_reqs('foo')}\n\n\ndef test_double():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import time  # fades\n        import foo   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('time') + get_reqs('foo')\n    }\n\n\ndef test_version_same_default():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades == 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_different():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades !=3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo !=3.5')\n    }\n\n\ndef test_version_same_no_spaces():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades==3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo ==3.5')\n    }\n\n\ndef test_version_same_two_spaces():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades  ==  3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo ==  3.5')\n    }\n\n\ndef test_version_same_one_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades == 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_same_two_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades  == 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_same_one_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades== 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_same_two_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades==  3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo ==  3.5')\n    }\n\n\ndef test_version_greater():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades > 2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_version_greater_no_space():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >2')\n    }\n\n\ndef test_version_greater_no_space_default():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >2')\n    }\n\n\ndef test_version_greater_two_spaces():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades  >  2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >  2')\n    }\n\n\ndef test_version_greater_one_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades> 2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_version_greater_two_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>  2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_version_greater_one_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades> 2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_version_greater_two_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>  2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_version_same_or_greater():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades >= 2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >= 2')\n    }\n\n\ndef test_version_same_or_greater_no_spaces():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>=2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >= 2')\n    }\n\n\ndef test_version_same_or_greater_one_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades >=2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >=2')\n    }\n\n\ndef test_version_same_or_greater_two_space_before():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades  >=2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >=2')\n    }\n\n\ndef test_version_same_or_greater_one_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>= 2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >= 2')\n    }\n\n\ndef test_version_same_or_greater_two_space_after():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades>=  2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >= 2')\n    }\n\n\ndef test_continuation_line():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bar\n        # fades > 2\n        import foo\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo > 2')\n    }\n\n\ndef test_from_import_simple():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from foo import bar   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo')\n    }\n\n\ndef test_import():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo.bar   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo')\n    }\n\n\ndef test_from_import_complex():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from baz.foo import bar   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('baz')\n    }\n\n\ndef test_allow_other_comments():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from foo import *   # NOQA   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo')\n    }\n\n\ndef test_allow_other_comments_reverse_default():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from foo import * # fades # NOQA\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo')\n    }\n\n\ndef test_strange_import(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from foo bar import :(   # fades\n    \"\"\"))\n    assert Exact(\n        \"Not understood import info: ['from', 'foo', 'bar', 'import', ':(']\") in logs.debug\n    assert parsed == {}\n\n\ndef test_strange_fadesinfo(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo   # fades  broken::whatever\n    \"\"\"))\n    assert \"Not understood fades repository: 'broken'\" in logs.warning\n    assert parsed == {}\n\n\ndef test_strange_fadesinfo2(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo   # fadesbroken\n    \"\"\"))\n    assert \"Not understood fades info: 'fadesbroken'\" in logs.warning\n    assert parsed == {}\n\n\ndef test_projectname_noversion_implicit():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades othername\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername')\n    }\n\n\ndef test_projectname_noversion_explicit():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades pypi::othername\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername')\n    }\n\n\ndef test_projectname_version_explicit():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades pypi::othername >= 3\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername >= 3')\n    }\n\n\ndef test_projectname_version_nospace():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades othername==5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername==5')\n    }\n\n\ndef test_projectname_version_space():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades othername <5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername <5')\n    }\n\n\ndef test_projectname_pkgnamedb():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bs4   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('beautifulsoup4')\n    }\n\n\ndef test_projectname_pkgnamedb_version():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bs4   # fades >=5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('beautifulsoup4 >=5')\n    }\n\n\ndef test_projectname_pkgnamedb_othername_default():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bs4   # fades othername\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername')\n    }\n\n\ndef test_projectname_pkgnamedb_version_othername():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bs4   # fades othername >=5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('othername >=5')\n    }\n\n\ndef test_comma_separated_import():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        from foo import bar, baz, qux   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo')\n    }\n\n\ndef test_other_lines_with_fades_string():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bar # fades\n        print(\"screen fades to black\")\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('bar')\n    }\n\n\ndef test_commented_line(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        #import foo   # fades\n    \"\"\"))\n    assert parsed == {}\n    assert \"Not understood fades\" not in logs.warning\n\n\ndef test_with_fades_commented_line(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        #import foo   # fades\n        import bar   # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('bar')\n    }\n    assert \"Not understood fades\" not in logs.warning\n\n\ndef test_with_commented_line(logs):\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import bar   # fades\n        # a commented line\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('bar')\n    }\n    assert \"Not understood fades\" not in logs.warning\n\n\ndef test_vcs_explicit():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades vcs::superurl\n    \"\"\"))\n    assert parsed == {\n        REPO_VCS: [parsing.VCSDependency('superurl')]\n    }\n\n\ndef test_vcs_implicit():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades   http://www.whatever/project\n    \"\"\"))\n    assert parsed == {\n        REPO_VCS: [parsing.VCSDependency('http://www.whatever/project')]\n    }\n\n\ndef test_mixed():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n        import foo    # fades vcs::superurl\n        import bar    # fades\n    \"\"\"))\n    assert parsed == {\n        REPO_VCS: [parsing.VCSDependency('superurl')],\n        REPO_PYPI: get_reqs('bar'),\n    }\n\n\ndef test_fades_and_hashtag_mentioned_in_code():\n    \"\"\"Test the case where a string contains both: fades and hashtag (#)\n    but is not an import.\n    \"\"\"\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n      'http://fades.readthedocs.io/en/release-7-0/readme.html#how-to-use-it'\n    \"\"\"))\n    assert parsed == {}\n\n\ndef test_fades_and_hashtag_mentioned_in_code_mixed_with_imports():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"import requests  # fades\n\n      'http://fades.readthedocs.io/en/release-7-0/readme.html#how-to-use-it'\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('requests')\n    }\n\n\ndef test_fades_user_strange_comment_with_hashtag_ignored():\n    parsed = parsing._parse_content(io.StringIO(\"\"\"\n      import foo # fades==2 # Some comment with #hashtash\n    \"\"\"))\n    assert parsed == {}\n"
  },
  {
    "path": "tests/test_parsing/test_file_reqs.py",
    "content": "\"\"\"Check the requirements parsing from a reqs.txt file.\"\"\"\nimport os\n\nfrom fades import parsing, REPO_PYPI\n\nfrom tests import get_reqs\n\n\ndef test_requirement_files(create_tmpfile):\n    parsed = parsing.parse_reqfile(create_tmpfile(['foo']))\n    assert parsed == {REPO_PYPI: get_reqs('foo')}\n\n\ndef test_nested_requirement_files(create_tmpfile):\n    requirement_file = create_tmpfile(['foo'])\n    requirement_file_nested = create_tmpfile(\n        ['bar\\n-r {}'.format(requirement_file)]\n    )\n    parsed = parsing.parse_reqfile(requirement_file_nested)\n\n    assert parsed == {REPO_PYPI: get_reqs('bar', 'foo')}\n\n\ndef test_nested_requirement_files_invalid_format(logs, create_tmpfile):\n    requirement_file_nested = create_tmpfile(['foo\\n-r'])\n    parsed = parsing.parse_reqfile(requirement_file_nested)\n\n    assert parsed == {REPO_PYPI: get_reqs('foo')}\n    assert \"Invalid format to indicate a nested requirements file:\" in logs.warning\n\n\ndef test_nested_requirement_files_not_pwd(create_tmpfile):\n    requirement_file = create_tmpfile(['foo'])\n    fname = os.path.basename(requirement_file)\n    requirement_file_nested = create_tmpfile(\n        ['bar\\n-r {}'.format(fname)])\n    parsed = parsing.parse_reqfile(requirement_file_nested)\n\n    assert parsed, {REPO_PYPI: get_reqs('bar', 'foo')}\n\n\ndef test_nested_requirement_files_first_line(create_tmpfile):\n    requirement_file = create_tmpfile(['foo'])\n    requirement_file_nested = create_tmpfile(\n        ['\\n-r {}\\nbar'.format(requirement_file)])\n    parsed = parsing.parse_reqfile(requirement_file_nested)\n\n    assert parsed == {REPO_PYPI: get_reqs('foo', 'bar')}\n\n\ndef test_two_nested_requirement_files(create_tmpfile):\n    requirement_file = create_tmpfile(['foo'])\n    requirement_file_nested1 = create_tmpfile(\n        ['bar\\n-r {}'.format(requirement_file)])\n    requirement_file_nested2 = create_tmpfile(\n        ['baz\\n-r {}'.format(requirement_file_nested1)])\n    parsed = parsing.parse_reqfile(requirement_file_nested2)\n\n    assert parsed == {REPO_PYPI: get_reqs('baz', 'bar', 'foo')}\n"
  },
  {
    "path": "tests/test_parsing/test_manual.py",
    "content": "\"\"\"Tests for the check of the manual parsing.\"\"\"\nfrom fades import parsing, REPO_PYPI, REPO_VCS\nfrom tests import get_reqs\n\n\ndef test_none():\n    parsed = parsing.parse_manual(None)\n    assert parsed == {}\n\n\ndef test_nothing():\n    parsed = parsing.parse_manual([])\n    assert parsed == {}\n\n\ndef test_simple():\n    parsed = parsing.parse_manual([\"pypi::foo\"])\n    assert parsed == {REPO_PYPI: get_reqs(\"foo\")}\n\n\ndef test_simple_default_pypi():\n    parsed = parsing.parse_manual([\"foo\"])\n    assert parsed == {REPO_PYPI: get_reqs(\"foo\")}\n\n\ndef test_double():\n    parsed = parsing.parse_manual([\"pypi::foo\", \"pypi::bar\"])\n    assert parsed == {REPO_PYPI: get_reqs(\"foo\", \"bar\")}\n\n\ndef test_version():\n    parsed = parsing.parse_manual([\"pypi::foo == 3.5\"])\n    assert parsed == {REPO_PYPI: get_reqs(\"foo == 3.5\")}\n\n\ndef test_version_default():\n    parsed = parsing.parse_manual([\"foo == 3.5\"])\n    assert parsed == {REPO_PYPI: get_reqs(\"foo == 3.5\")}\n\n\ndef test_vcs_simple():\n    url = \"git+git://server.com/etc\"\n    parsed = parsing.parse_manual([\"vcs::\" + url])\n    assert parsed == {REPO_VCS: [parsing.VCSDependency(url)]}\n\n\ndef test_vcs_simple_default():\n    url = \"git+git://server.com/etc\"\n    parsed = parsing.parse_manual([url])\n    assert parsed == {REPO_VCS: [parsing.VCSDependency(url)]}\n\n\ndef test_mixed():\n    parsed = parsing.parse_manual([\"pypi::foo\", \"vcs::git+git://server.com/etc\"])\n    assert parsed == {\n        REPO_PYPI: get_reqs(\"foo\"),\n        REPO_VCS: [parsing.VCSDependency(\"git+git://server.com/etc\")],\n    }\n"
  },
  {
    "path": "tests/test_parsing/test_reqs.py",
    "content": "\"\"\"Check the requirements parsing.\"\"\"\nimport io\n\nfrom logassert import Multiple\n\nfrom fades import parsing, REPO_PYPI, REPO_VCS\n\nfrom tests import get_reqs\n\n\ndef test_empty():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n\n    \"\"\"))\n    assert parsed == {}\n\n\ndef test_simple():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        pypi::foo\n    \"\"\"))\n    assert parsed == {REPO_PYPI: get_reqs('foo')}\n\n\ndef test_simple_default():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo\n    \"\"\"))\n    assert parsed == {REPO_PYPI: get_reqs('foo')}\n\n\ndef test_double():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        pypi::time\n        foo\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('time') + get_reqs('foo')\n    }\n\n\ndef test_version_same():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        pypi::foo == 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_same_default():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo == 3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo == 3.5')\n    }\n\n\ndef test_version_different():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo  !=3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo !=3.5')\n    }\n\n\ndef test_version_same_no_spaces():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo==3.5\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo ==3.5')\n    }\n\n\ndef test_version_greater_two_spaces():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo   >  2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >  2')\n    }\n\n\ndef test_version_same_or_greater():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        foo   >=2\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo >= 2')\n    }\n\n\ndef test_comments():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        pypi::foo   # some text\n        # other text\n        bar\n    \"\"\"))\n    assert parsed == {\n        REPO_PYPI: get_reqs('foo') + get_reqs('bar')\n    }\n\n\ndef test_strange_repo(logs):\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        unknown::foo\n    \"\"\"))\n    assert Multiple(\"Not understood fades repository\", \"unknown\") in logs.warning\n    assert parsed == {}\n\n\ndef test_vcs_simple():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        vcs::strangeurl\n    \"\"\"))\n    assert parsed == {REPO_VCS: [parsing.VCSDependency(\"strangeurl\")]}\n\n\ndef test_vcs_simple_default():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        bzrhttp://server/bleh\n    \"\"\"))\n    assert parsed == {REPO_VCS: [parsing.VCSDependency(\"bzrhttp://server/bleh\")]}\n\n\ndef test_mixed():\n    parsed = parsing._parse_requirement(io.StringIO(\"\"\"\n        vcs::strangeurl\n        pypi::foo\n    \"\"\"))\n    assert parsed == {\n        REPO_VCS: [parsing.VCSDependency(\"strangeurl\")],\n        REPO_PYPI: get_reqs('foo'),\n    }\n"
  },
  {
    "path": "tests/test_parsing/test_vcs_dependency.py",
    "content": "\"\"\"Check the VCSDependency.\"\"\"\nfrom fades import parsing\n\n\ndef test_string_representation():\n    \"\"\"This is particularly tested because it's the interface to be installed.\"\"\"\n    dep = parsing.VCSDependency(\"testurl\")\n    assert str(dep), \"testurl\"\n\n\ndef test_contains():\n    \"\"\"This is particularly tested because it's how fulfilling is tested.\"\"\"\n    dep1 = parsing.VCSDependency(\"testurl\")\n    assert dep1.specifier.contains(None)\n    assert not dep1.specifier.contains(\"123\")\n\n\ndef test_equality():\n    dep1 = parsing.VCSDependency(\"testurl\")\n    dep2 = parsing.VCSDependency(\"testurl\")\n    dep3 = parsing.VCSDependency(\"otherurl\")\n    assert dep1 == dep2\n    assert not (dep1 == dep3)\n    assert not (dep1 != dep2)\n    assert dep1 != dep3\n    assert not (dep1 == 123)\n    assert not (dep1 == \"testurl\")\n"
  },
  {
    "path": "tests/test_pipmanager.py",
    "content": "# Copyright 2015-2022 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for pip related code.\"\"\"\n\nimport os\nimport io\nimport pytest\nfrom unittest.mock import patch, call\n\nfrom fades.pipmanager import PipManager\nfrom fades import pipmanager\nfrom fades import helpers\n\nBIN_PATH = \"somepath\"\n\n\ndef test_get_parsing_ok_pytest():\n    mocked_stdout = [\n        \"Name: foo\",\n        \"Version: 2.0.0\",\n        \"Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages\",\n        \"Requires: \",\n    ]\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    with patch.object(helpers, \"logged_exec\", return_value=mocked_stdout):\n        version = mgr.get_version(\"foo\")\n    assert version, \"2.0.0\"\n\n\ndef test_get_parsing_error(logs):\n    mocked_stdout = [\n        \"Name: foo\",\n        \"Release: 2.0.0\",\n        \"Location: ~/.local/share/fades/86cc492/lib/python3.4/site-packages\",\n        \"Requires: \",\n    ]\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    with patch.object(helpers, \"logged_exec\", return_value=mocked_stdout):\n        version = mgr.get_version(\"foo\")\n\n    assert version == \"\"\n    assert (\n        'Fades is having problems getting the installed version. '\n        'Run with -v or check the logs for details'\n    ) in logs.error\n\n\ndef test_real_case_levenshtein():\n    mocked_stdout = [\n        \"Metadata-Version: 1.1\",\n        \"Name: python-Levenshtein\",\n        \"Version: 0.12.0\",\n        \"License: GPL\",\n    ]\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    with patch.object(helpers, \"logged_exec\", return_value=mocked_stdout):\n        version = mgr.get_version(\"foo\")\n    assert version == \"0.12.0\"\n\n\ndef test_install():\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mgr.install(\"foo\")\n\n    # check it always upgrades pip, and then the proper install\n    python_path = os.path.join(BIN_PATH, \"python\")\n    c1 = call([python_path, \"-m\", \"pip\", \"install\", \"pip\", \"--upgrade\"])\n    c2 = call([pip_path, \"install\", \"foo\"])\n    assert mock.call_args_list == [c1, c2]\n\n\ndef test_install_without_pip_upgrade():\n    mgr = PipManager(BIN_PATH, pip_installed=True, avoid_pip_upgrade=True)\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mgr.install(\"foo\")\n    mock.assert_called_with([pip_path, \"install\", \"foo\"])\n\n\ndef test_install_multiword_dependency():\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mgr.install(\"foo bar\")\n    mock.assert_called_with([pip_path, \"install\", \"foo\", \"bar\"])\n\n\ndef test_install_with_options():\n    mgr = PipManager(BIN_PATH, pip_installed=True, options=[\"--bar baz\"])\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mgr.install(\"foo\")\n    mock.assert_called_with([pip_path, \"install\", \"foo\", \"--bar\", \"baz\"])\n\n\ndef test_install_with_options_using_equal():\n    mgr = PipManager(BIN_PATH, pip_installed=True, options=[\"--bar=baz\"])\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mgr.install(\"foo\")\n    mock.assert_called_with([pip_path, \"install\", \"foo\", \"--bar=baz\"])\n\n\ndef test_install_raise_error(logs):\n    mgr = PipManager(BIN_PATH, pip_installed=True)\n    with patch.object(helpers, \"logged_exec\", side_effect=['ok', Exception(\"Kapow!\")]):\n        with pytest.raises(Exception):\n            mgr.install(\"foo\")\n\n    assert \"Error installing foo: Kapow!\" in logs.error\n\n\ndef test_install_without_pip():\n    mgr = PipManager(BIN_PATH, pip_installed=False)\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    with patch.object(helpers, \"logged_exec\") as mocked_exec:\n        with patch.object(mgr, \"_brute_force_install_pip\") as mocked_install_pip:\n            mgr.install(\"foo\")\n    assert mocked_install_pip.call_count == 1\n    mocked_exec.assert_called_with([pip_path, \"install\", \"foo\"])\n\n\ndef test_brute_force_install_pip_installer_exists(tmp_path):\n    tmp_file = str(tmp_path / \"hello.txt\")\n    mgr = PipManager(BIN_PATH, pip_installed=False)\n    python_path = os.path.join(BIN_PATH, \"python\")\n\n    # get the tempfile but leave it there to be found\n    open(tmp_file, 'wt', encoding='utf8').close()\n    mgr.pip_installer_fname = tmp_file\n\n    with patch.object(helpers, \"logged_exec\") as mocked_exec:\n        with patch.object(mgr, \"_download_pip_installer\") as download_installer:\n            mgr._brute_force_install_pip()\n\n    assert not download_installer.called\n    mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, \"-I\"])\n    assert mgr.pip_installed\n\n\ndef test_brute_force_install_pip_no_installer(tmp_path):\n    tmp_file = str(tmp_path / \"hello.txt\")\n    mgr = PipManager(BIN_PATH, pip_installed=False)\n    python_path = os.path.join(BIN_PATH, \"python\")\n\n    mgr.pip_installer_fname = tmp_file\n    with patch.object(helpers, \"logged_exec\") as mocked_exec:\n        with patch.object(mgr, \"_download_pip_installer\") as download_installer:\n            mgr._brute_force_install_pip()\n\n    download_installer.assert_called_once_with()\n    mocked_exec.assert_called_with([python_path, mgr.pip_installer_fname, \"-I\"])\n    assert mgr.pip_installed\n\n\ndef test_download_pip_installer(tmp_path):\n    tmp_file = str(tmp_path / \"hello.txt\")\n    mgr = PipManager(BIN_PATH, pip_installed=False)\n\n    mgr.pip_installer_fname = tmp_file\n    with patch(\"fades.pipmanager.request.urlopen\", return_value=io.BytesIO(b\"hola\")) as urlopen:\n        mgr._download_pip_installer()\n    assert os.path.exists(mgr.pip_installer_fname)\n    urlopen.assert_called_once_with(pipmanager.PIP_INSTALLER)\n\n\ndef test_freeze(tmp_path):\n    tmp_file = str(tmp_path / \"reqtest.txt\")\n\n    # call and check pip was executed ok\n    mgr = PipManager(BIN_PATH)\n    with patch.object(helpers, \"logged_exec\") as mock:\n        mock.return_value = ['moño>11', 'foo==1.2']  # \"bad\" order, on purpose\n        mgr.freeze(tmp_file)\n\n    pip_path = os.path.join(BIN_PATH, \"pip\")\n    mock.assert_called_with([pip_path, \"freeze\", \"--all\", \"--local\"])\n\n    # check results were stored properly\n    with open(tmp_file, 'rt', encoding='utf8') as fh:\n        stored = fh.read()\n    assert stored == 'foo==1.2\\nmoño>11\\n'\n"
  },
  {
    "path": "tests/test_pkgnamesdb.py",
    "content": "# Copyright 2020 Facundo Batista, Nicolás Demarchi\n#\n# This program is free software: you can redistribute it and/or modify it\n# under the terms of the GNU General Public License version 3, as published\n# by the Free Software Foundation.\n#\n# This program is distributed in the hope that it will be useful, but\n# WITHOUT ANY WARRANTY; without even the implied warranties of\n# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR\n# PURPOSE.  See the GNU General Public License for more details.\n#\n# You should have received a copy of the GNU General Public License along\n# with this program.  If not, see <http://www.gnu.org/licenses/>.\n#\n# For further info, check  https://github.com/PyAr/fades\n\n\"\"\"Tests for the package names DB.\"\"\"\n\nfrom fades import pkgnamesdb\n\n\ndef test_db_consistency():\n    \"\"\"Ensure multiple DB entrypoints are consistent between them.\"\"\"\n    assert len(pkgnamesdb.MODULE_TO_PACKAGE) == len(pkgnamesdb.PACKAGE_TO_MODULE)\n"
  }
]