diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..5789bcc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: Run pytest + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: '3.6' + - name: Install dependencies + run: python -m pip install -r test-requirements.txt + - name: Run tests + run: python -m pytest -v diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a5d8f25..0000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: python - -python: - - "3.6" - -install: python -m pip install -r test-requirements.txt - -script: python -m pytest -v - -sudo: false diff --git a/LICENCE.CC-BY-SA b/LICENCE.CC-BY-SA new file mode 100644 index 0000000..2b56468 --- /dev/null +++ b/LICENCE.CC-BY-SA @@ -0,0 +1,372 @@ +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. + diff --git a/LICENCE.CC0 b/LICENCE.CC0 new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENCE.CC0 @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/README b/README index 3678fde..b5be14a 100644 --- a/README +++ b/README @@ -28,3 +28,19 @@ We have a few automatic tests. To run them, install dependencies: ... and run: $ python -m pytest -v + + +Licence +------- + +This guide is released under the [CC BY-SA 4.0] licence. + +Additionally, all code in this repository (including code samples in the text, +tests, build scripts) is dedicated to the public domain under the +[CC0 1.0] dedication. + +[CC BY-SA 4.0]: https://creativecommons.org/licenses/by-sa/4.0/ +[CC0 1.0]: https://creativecommons.org/publicdomain/zero/1.0/ + +See the files `LICENCE.CC-BY-SA` and `LICENCE.CC0`, respectively, for the +license text. diff --git a/source/builtins.rst b/source/builtins.rst index 5ee7075..4145bb4 100644 --- a/source/builtins.rst +++ b/source/builtins.rst @@ -105,12 +105,13 @@ aren't of the ``file`` type. If type-checking for files is necessary, we recommend using a tuple of types that includes :class:`io.IOBase` and, under Python 2, ``file``:: - import six import io - if six.PY2: + try: + # Python 2: "file" is built-in file_types = file, io.IOBase - else: + except NameError: + # Python 3: "file" fully replaced with IOBase file_types = (io.IOBase,) ... @@ -123,7 +124,7 @@ that includes :class:`io.IOBase` and, under Python 2, ``file``:: Removed ``apply()`` ~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_apply`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_apply`` (but see below) * Prevalence: Common In Python 2, the function :func:`apply` was built in. @@ -151,7 +152,7 @@ in some of your modules, revert the fixer's changes in that module. Moved ``reduce()`` ~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_reduce`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_reduce`` * Prevalence: Uncommon In Python 2, the function :func:`reduce` was built in. @@ -174,7 +175,7 @@ The recommended fixer will add this import automatically. The ``exec()`` function ~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_exec`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_exec`` * Prevalence: Rare In Python 2, :func:`exec` was a statement. In Python 3, it is a function. @@ -261,11 +262,13 @@ In Python 3, it is moved to the ``importlib`` module. Python 2.7 included an ``importlib`` module, but without a ``reload`` function. Python 2.6 and below didn't have an ``importlib`` module. -If your code uses ``reload()``, import it conditionally on Python 3:: +If your code uses ``reload()``, import it conditionally if it doesn't exist +(using `feature detection`_):: - import six - - if not six.PY2: + try: + # Python 2: "reload" is built-in + reload + except NameError: from importlib import reload @@ -281,11 +284,13 @@ Moved ``intern()`` The :func:`~sys.intern` function was built-in in Python 2. In Python 3, it is moved to the ``sys`` module. -If your code uses ``intern()``, import it conditionally on Python 3:: - - import six +If your code uses ``intern()``, import it conditionally if it doesn't exist +(using `feature detection`_):: - if not six.PY2: + try: + # Python 2: "intern" is built-in + intern + except NameError: from sys import intern @@ -307,3 +312,9 @@ If any of your classes defines the special method ``__coerce__``, remove that as well, and test that the removal did not break semantics. .. XXX: I've never seen serious use of ``coerce``, so the advice is limited. + + +.. Common links + ------------ + +.. _feature detection: https://docs.python.org/3/howto/pyporting.html#use-feature-detection-instead-of-version-detection diff --git a/source/comprehensions.rst b/source/comprehensions.rst index 23fb8fe..4a69677 100644 --- a/source/comprehensions.rst +++ b/source/comprehensions.rst @@ -1,7 +1,7 @@ Comprehensions -------------- -List comprehensions, a shrtcut for creating lists, have been in Python +List comprehensions, a shortcut for creating lists, have been in Python since version 2.0. Python 2.4 added a similar feature – generator expressions; then 2.7 (and 3.0) introduced set and dict comprehensions. @@ -47,7 +47,7 @@ To fix this, either rewrite the code to not use the iteration variable after a list comprehension, or convert the comprehension to a ``for`` loop:: powers = [] - for i in for i in range(10): + for i in range(10): powers.append(2**i) In some cases, the change might silently cause different behavior. @@ -75,7 +75,7 @@ Unfortunately, you will need to find and fix these cases manually. Comprehensions over Tuples ~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_paren`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_paren`` * Prevalence: Rare Python 2 allowed list comprehensions over bare, non-parenthesized tuples: diff --git a/source/core-obj-misc.rst b/source/core-obj-misc.rst index 41892b2..97e1f2c 100644 --- a/source/core-obj-misc.rst +++ b/source/core-obj-misc.rst @@ -17,7 +17,7 @@ classes. Function Attributes ~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_funcattrs`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_funcattrs`` (but see below) * Prevalence: Rare In Python, functions are mutable objects that support custom attributes. @@ -194,8 +194,8 @@ Do this change in all classes that implement ``__nonzero__``. Unbound Methods ~~~~~~~~~~~~~~~ -Python 2 had two kinds of methods: *bound* methods, which you could retreive -from a class object, and *unbound* methods, which were retreived from +Python 2 had two kinds of methods: *unbound* methods, which you could retreive +from a class object, and *bound* methods, which were retreived from an instance:: >>> class Hello(object): diff --git a/source/dicts.rst b/source/dicts.rst index c9539b0..1d1d3de 100644 --- a/source/dicts.rst +++ b/source/dicts.rst @@ -10,7 +10,7 @@ There are three most significant changes related to dictionaries in Python 3. Removed ``dict.has_key()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.has_key`` (See caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_has_key`` (See caveat below) * Prevalence: Common The ``dict.has_key()`` method, long deprecated in favor of the ``in`` operator, @@ -29,7 +29,7 @@ it does not check that its object is actually a dictionary. If you use a third-party dict-like class, it should implement ``in`` already. -If not, complain to its author: it should have been added as part of adding +If not, notify its author: it should have been added as part of adding Python 3 support. If your own codebase contains a custom dict-like class, add @@ -101,8 +101,8 @@ In Python 3.3+, this setting is the default:: [('c', 3), ('e', 5), ('d', 4), ('a', 1), ('b', 2)] Additionally, CPython 3.6+ uses a new implementation of dictionaries, -which makes them appear sorted by insertion order (though you shouldn't rely -on this behavior):: +which makes them appear sorted by insertion order (though you can only rely +on this behavior in Python 3.7+):: $ python3 order.py [('a', 1), ('b', 2), ('c', 3), ('d', 4), ('e', 5)] diff --git a/source/exceptions.rst b/source/exceptions.rst index 5694228..850aaa9 100644 --- a/source/exceptions.rst +++ b/source/exceptions.rst @@ -26,7 +26,7 @@ supporting Python 3. The new ``except`` syntax ~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_except`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_except`` * Prevalence: Very common In Python 2, the syntax for catching exceptions was @@ -137,7 +137,7 @@ to the exception to use it outside the ``except`` clause. Iterating Exceptions ~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_except`` (but see caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_except`` (but see caveat below) * Prevalence: Rare In Python 2, exceptions were *iterable*, so it was possible to “unpack” the @@ -196,7 +196,7 @@ Otherwise, switch to using a dedicated Exception class. The Removed ``StandardError`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_standarderror`` (but see caveat below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_standarderror`` (but see caveat below) * Prevalence: Rare The :class:`py2:StandardError` class is removed in Python 3. diff --git a/source/imports.rst b/source/imports.rst index 0ba5b26..91a80db 100644 --- a/source/imports.rst +++ b/source/imports.rst @@ -12,7 +12,7 @@ Absolute imports * :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_import`` (See caveat below) * Prevalence: Common -* Future import: ``from __future__ import absolute_imports`` +* Future import: ``from __future__ import absolute_import`` * Specification: `PEP 328 `_ Under Python 2, when importing from inside a package, the package's own modules @@ -43,7 +43,7 @@ Given the structure above, these statements would be equivalent Additionally, a *future import* was added to make all imports absolute (unless explicitly relative):: - from __future__ import absolute_imports + from __future__ import absolute_import Using this feature, ``from collections import deque`` will import from the standard library's ``collections`` module. @@ -103,7 +103,7 @@ Import Cycles * :ref:`Fixer `: None * Prevalence: Rare -Python 3 introduced a reworked importmentation of ``import`` in the form +Python 3 introduced a reworked implementation of ``import`` in the form of the :py:mod:`importlib` module. The new machinery is backwards-compatible in practice, except that some import cycles, especially those involving submodules, now raise diff --git a/source/index.rst b/source/index.rst index 9a51efb..13182ce 100644 --- a/source/index.rst +++ b/source/index.rst @@ -49,6 +49,7 @@ Still with us? Let's dive in! classes comprehensions core-obj-misc + invoking-python etc diff --git a/source/invoking-python.rst b/source/invoking-python.rst new file mode 100644 index 0000000..e307c05 --- /dev/null +++ b/source/invoking-python.rst @@ -0,0 +1,56 @@ +Invoking Python +--------------- + +While this is not a change in Python 3, the transition increased the number of +systems that have more than one Python interpreter installed: it is not +uncommon for ``python``, ``python2``, ``python3``, ``python3.6`` and +``python3.9`` to all be valid system commands; other interpreters may be +installed in non-standard locations. + +This makes it important to use the correct command for each situation. + + +Current interpreter +~~~~~~~~~~~~~~~~~~~ + +The current Python interpreter should be invoked via ``sys.executable``. + +Python provides the path of the currently running interpreter as +:data:`sys.executable`. +This variable should be preferred over ``python`` or other hard-coded commands. + +For example, rather than:: + + subprocess.Popen('python', 'somescript.py') + +use:: + + subprocess.Popen(sys.executable, 'somescript.py') + +The assumption that ``'python'`` is correct is only valid in tightly controlled +environments; however, even in those environments ``sys.executable`` is likely +to be correct. + +The documentation does include a warning: + + If Python is unable to retrieve the real path to its executable, + ``sys.executable`` will be an empty string or ``None``. + +In practice, this does not apply to mainstream platforms. +If ``sys.executable`` is unusable, then either your platform's concept of +launching a process via filename is somehow unusual (and in this +case you should know what to do), or there's an issue in Python itself. + + +Unix shebangs +~~~~~~~~~~~~~ + +On Unix, executables written in Python must have a shebang line identifying +the interpreter. +The correct shebang to use will depend on the environment you are targeting +and on the version compatibility of the project. + +General recommendations for Python shebangs are listed in +the `For Python script publishers`_ section of PEP 394. + +.. _For Python script publishers: https://www.python.org/dev/peps/pep-0394/#for-python-script-publishers diff --git a/source/iterators.rst b/source/iterators.rst index 57a5e89..4052a4e 100644 --- a/source/iterators.rst +++ b/source/iterators.rst @@ -255,3 +255,31 @@ fixer's output and revert the changes for objects of this class. The fixer will not add a ``__next__`` method to your classes. You will need to do this manually. + +Generators cannot raise ``StopIteration`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* :ref:`Fixer `: None +* Prevalence: Rare + +Since Python 3.7, generators cannot raise ``StopIteration`` directly, +but must stop with ``return`` (or at the end of the function). +This change was done to prevent subtle errors when a ``StopIteration`` +exception “leaks” between unrelated generators. + +For example, the following generator is considered a programming error, +and in Python 3.7+ it raises ``RuntimeError``:: + + def count_to(maximum): + i = 0 + while True: + yield i + i += 1 + if i >= maximum: + raise StopIteration() + +Convert the ``raise StopIteration()`` to ``return``. + +If your code uses a helper function that can raise ``StopIteration`` to +end the generator that calls it, you will need to move the returning logic +to the generator itself. diff --git a/source/numbers.rst b/source/numbers.rst index fd9b647..3370884 100644 --- a/source/numbers.rst +++ b/source/numbers.rst @@ -27,7 +27,7 @@ In Python 2, dividing two integers resulted in an integer:: This *truncating division* was inherited from C-based languages, but confused people who don't know those languages, -such as those coming from Javascript or pure math. +such as those coming from JavaScript or pure math. In Python 3, dividing two integers results in a float:: @@ -37,7 +37,7 @@ In Python 3, dividing two integers results in a float:: The ``//`` operator, which was added all the way back in Python 2.2, always performs truncating division:: - while_minutes = seconds // 60 + whole_minutes = seconds // 60 The ``from __future__ import division`` directive causes the ``/`` operator to behave the same in Python 2 as it does in Python 3. @@ -100,7 +100,7 @@ This change has several consequences. Removal of the ``long`` type ............................ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_long`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_long`` * Prevalence: Common The ``long`` builtin no longer exists. @@ -130,7 +130,7 @@ The recommended fixer will do this. The ``L`` suffix not allowed in numeric literals ................................................ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_numliterals`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_numliterals`` (but see below) * Prevalence: Very common In Python 2, ``12345L`` designated a ``long`` literal. @@ -175,7 +175,7 @@ Call ``str()`` instead of ``repr()`` when the result might be a (long) integer. Octal Literals ~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_numliterals`` (but see below) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_numliterals`` (but see below) * Prevalence: Uncommon Python 2's other holdover from C-based languages is the syntax of octal diff --git a/source/process.rst b/source/process.rst index 41a88bc..57ed0f7 100644 --- a/source/process.rst +++ b/source/process.rst @@ -148,6 +148,17 @@ tackled in a typical project. We recommend that you skim the introduction of each of the chapters, so that you know what you're up against before you start. +Note that while the guide is fairly comprehensive, there are changes it does +not cover. +Be prepared to find a few issues specific to your code base that you'll need +to figure out independently. + +Also note that the guide was written for Python 3.6. +It includes several updates for newer versions, but we recommend skimming +[What's New lists](https://docs.python.org/3/whatsnew/index.html) +in the Python documentation to familiarize yourself with +the changes in newer versions of Python. + .. index:: dropping Python 2 diff --git a/source/strings.rst b/source/strings.rst index 6b2c699..8bc3bdb 100644 --- a/source/strings.rst +++ b/source/strings.rst @@ -14,15 +14,15 @@ In Python 2, the ``str`` type was used for two different kinds of values – **Text** contains human-readable messages, represented as a sequence of Unicode codepoints. - Usually, it does not contain unprintable control characters such as NULL. + Usually, it does not contain unprintable control characters such as ``\0``. This type is available as ``str`` in Python 3, and ``unicode`` in Python 2. In code, we will refer to this type as ``unicode`` – a short, unambiguous name, although one that is not built-in in Python 3. - Some projects refer to it as ``six.text_type`` (from the :ref:`six` - library). + Some projects refer to it as ``six.text_type`` + (from the :ref:`six library `). * @@ -58,24 +58,25 @@ can use what is conceptually a third type: The **native string** (``str``) – text in Python 3, bytes in Python 2 -Custom ``__str__`` and ``__repr__`` methods, and code that deals with +Custom ``__str__`` and ``__repr__`` methods and code that deals with Python language objects (such as attribute/function names) will always need to use the native string, because that is what each version of Python uses for internal text-like data. Developer-oriented texts, such as exception messages, could also be native strings. -For other data, you can use the native string in these circumstances: +For other data, you should only use the native string if all of the following +hold: - * You are working with textual data - * Under Python 2, each “native string” value has a single well-defined - encoding (such as ``UTF-8`` or :func:`py2:locale.getpreferredencoding`) - * You do not mix native strings with either bytes or text – always - encode/decode diligently when converting to these types. +* you are working with textual data, +* Under Python 2, each “native string” value has a single well-defined + encoding (e.g. ``UTF-8`` or :func:`py2:locale.getpreferredencoding`), and +* you do not mix native strings with either bytes or text – always + encode/decode diligently when converting to these types. Native strings affect the semantics under Python 2 as little as possible, while not requiring the resulting Python 3 API to feel bad. But, having -a third incompatible type makes porting process harder. +a third incompatible type makes the porting process harder. Native strings are suitable mostly for conservative projects, where ensuring stability under Python 2 justifies extra porting effort. @@ -97,13 +98,19 @@ trying all alternatives. Unlike images, one bytestring can often be successfully decoded using more than one encoding. -So, never assume an encoding without consulting relevant documentation + +Encodings +......... + +Never assume a text encoding without consulting relevant documentation and/or researching a string's use cases. If an encoding for a particular use case is determined, document it. -For example, a docstring can specify that some argument is “a bytestring -holding UTF-8-encoded text data”. +For example, a function docstring can specify that some argument is +“a bytestring holding UTF-8-encoded text data”, or module documentation may +clarify that „as per RFC 4514, LDAP attribute names are encoded to UTF-8 +for transmission“. -Some common encodings are: +Some common text encodings are: * ``UTF-8``: A widely used encoding that can encode any Unicode text, using one to four bytes per character. @@ -118,23 +125,31 @@ Some common encodings are: * ``locale.getpreferredencoding()``: The “preferred encoding” for command-line arguments, environment variables, and terminal input/output. +If *you* are choosing an encoding to use – for example, your application +*defines* a file format rather than using a format standardized externally – +consider ``UTF-8``. +And whatever your choice is, explicitly document it. + Conversion to text ------------------- +.................. There is no built-in function that converts to text in both Python versions. -The :ref:`six` library provides ``six.text_type``, which is fine if it appears -once or twice in uncomplicated code. +The :ref:`six library ` provides ``six.text_type``, which is fine if it +appears once or twice in uncomplicated code. For better readability, we recommend using ``unicode``, which is unambiguous and clear, but it needs to be introduced with the following code at the beginning of a file:: - if not six.PY2: + try: + # Python 2: "unicode" is built-in + unicode + except NameError: unicode = str Conversion to bytes -------------------- +................... There is no good function that converts an arbitrary object to bytes, as this operation does not make sense on arbitrary objects. @@ -147,13 +162,51 @@ Depending on what you need, explicitly use a serialization function String Literals --------------- +* :ref:`Fixer `: None +* Prevalence: Very common + Quoted string literals can be prefixed with ``b`` or ``u`` to get bytes or text, respectively. These prefixes work both in Python 2 (2.6+) and 3 (3.3+). Literals without these prefixes result in native strings. +In Python 3, the ``u`` prefix does nothing; it is only allowed for backwards +compatibility. +Likewise, the ``b`` prefix does nothing in Python 2. + Add a ``b`` or ``u`` prefix to all strings, unless a native string is desired. +Unfortunately, the choice between text and bytes cannot generally be automated. + +Raw Unicode strings +................... + +* :ref:`Fixer `: None +* Prevalence: Rare + +In Python 2, the ``r`` prefix could be combined with ``u`` to avoid processing +backslash escapes. +However, this *did not* turn off processing Unicode escapes (``\u....`` or +``\U........``), as the ``u`` prefix took precedence over ``r``:: + + >>> print u"\x23☺\u2744" # Python 2 with Encoding: UTF-8 + #☺❄ + >>> print ur"\x23☺\u2744" # Python 2 with Encoding: UTF-8 + \x23☺❄ + +This may be confusing at first. +Keeping this would be even more confusing in Python 3, where the ``u`` prefix +is a no-op with backwards-compatible behavior. +Python 3 avoids the choice between confusing or backwards-incompatible +semantics by forbidding ``ur`` altogether. + +Avoid the ``ur`` prefix in string literals. + +The most straightforward way to do this is to use plain ``u`` literals +with ``\\`` for a literal backslash:: + + >>> print(u"\\x23☺\u2744") + \x23☺❄ .. index:: TypeError; mixing text and bytes @@ -170,7 +223,9 @@ For example, these are all illegal:: import re pattern = re.compile(b'a+') - pattern.patch('aaaaaa') + pattern.match('aaaaaa') + +Encode or decode the data to make the types match. Type checking @@ -191,7 +246,7 @@ In Python 3, the concept of ``basestring`` makes no sense: text is only represented by ``str``. For type-checking text strings in code compatible with both versions, the -:ref:`six` library offers ``string_types``, which is ``(basestring,)`` +:ref:`six library ` offers ``string_types``, which is ``(basestring,)`` in Python 2 and ``(str,)`` in Python 3. The above code can be replaced by:: @@ -204,12 +259,12 @@ The recommended fixer will import ``six`` and replace any uses of ``basestring`` by ``string_types``. -.. index:: file I/O +.. index:: file I/O, open .. _str-file-io: File I/O -~~~~~~~~ +-------- * :ref:`Fixer `: ``python-modernize -wnf libmodernize.fixes.fix_open`` * Prevalence: Common @@ -262,7 +317,7 @@ We recommend adding them manually if the encoding is known. .. _testing-str: Testing Strings -~~~~~~~~~~~~~~~ +--------------- When everything is ported and tests are passing, it is a good idea to make sure your code handles strings correctly – even in unusual situations. @@ -300,7 +355,7 @@ If your software handles multiple text encodings, or handles user-specified encodings, make sure this capability is well-tested. Under Linux, run your software with the ``LC_ALL`` environment variable -set to ``C`` and ``tr_TR.UTF-8``, and check handling of any command-line +set to ``C`` and to ``tr_TR.utf8``. Check handling of any command-line arguments and environment variables that may contain non-ASCII characters. diff --git a/source/syntax.rst b/source/syntax.rst index 75031fa..feb7953 100644 --- a/source/syntax.rst +++ b/source/syntax.rst @@ -39,7 +39,7 @@ You can use the following Bash command for this:: Tuple Unpacking in Parameter Lists ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_tuple_params`` (fixup needed) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_tuple_params`` (fixup needed) * Prevalence: Common Python 3 requires that each argument of a ``def`` function has a name. @@ -79,7 +79,7 @@ named function would be an improvement. Backticks ~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_repr`` (with caveat) +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_repr`` (with caveat) * Prevalence: Common The backtick (`````) operator was removed in Python 3. @@ -91,12 +91,12 @@ The recommended fixer does a good job, though it doesn't catch the case where the name ``repr`` is redefined, as in:: repr = None - print(`1+_2`) + print(`1+2`) which becomes:: repr = None - print(repr(1+_2)) + print(repr(1+2)) Re-defining built-in functions is usually considered bad style, but it never hurts to check if the code does it. @@ -108,7 +108,7 @@ hurts to check if the code does it. The Inequality Operator ~~~~~~~~~~~~~~~~~~~~~~~ -* :ref:`Fixer `: ``python-modernize -wnf lib2to3.fixes.fix_ne`` +* :ref:`Fixer `: ``python-modernize -wnf fissix.fixes.fix_ne`` * Prevalence: Rare In the spirit of “There's only one way to do it”, Python 3 removes the @@ -126,6 +126,9 @@ New Reserved Words * :ref:`Fixer `: None * Prevalence: Rare +Constants +......... + In Python 3, ``None``, ``True`` and ``False`` are syntactically keywords, not variable names, and cannot be assigned to. This was partially the case with ``None`` even in Python 2.6. @@ -133,6 +136,64 @@ This was partially the case with ``None`` even in Python 2.6. Hopefully, production code does not assign to ``True`` or ``False``. If yours does, figure a way to do it differently. +``async`` and ``await`` +....................... + +Since Python 3.7, ``async`` and ``await`` are also keywords. + +If your code uses these names, rename it. +If other code depends on the names, keep the old name available for +old Python versions. +The way to do this will be different in each case, but generally +you'll need to take advantage of the fact that in Python's various namespaces +the strings ``'async'`` and ``'await'`` are still valid keys, even if they +are not accesible usual with the syntax. + +For module-level functions, classes and constants, also assign the original +name using :py:func:`globals()`. +For example, a function previously named ``async`` could look like this:: + + def asynchronous(): + """... + + This function used to be called `async`. + It is still available under old name. + """ + + globals()['async'] = asynchronous + +For methods, and class-level constants, assign the original name using +``setattr``:: + + class MyClass: + def asynchronous(self): + """... + + This method used to be called `async`. + It is still available under old name. + """ + + setattr(MyClass, 'async', MyClass.asynchronous) + +For function parameters, more work is required. The result will depend on +whether the argument is optional and whether ``None`` is a valid value for it. +Here is a general starting point:: + + def process_something(asynchronous=None, **kwargs): + if asynchronous is None: + asynchronous = kwargs.get('async', None) + else: + if 'async' in kwargs: + raise TypeError('Both `asynchronous` and `async` specified') + if asynchronous is None: + raise TypeError('The argument `asynchronous` is required') + +For function arguments, if the parameter cannot be renamed as above, +use “double star” syntax that allows you to pass arbitrary argument names:: + + process_something(**{'async': True}) + + Other Syntax Changes ~~~~~~~~~~~~~~~~~~~~ diff --git a/source/tools.rst b/source/tools.rst index 09a7584..cb256fb 100644 --- a/source/tools.rst +++ b/source/tools.rst @@ -52,10 +52,15 @@ These are best handled by the ``python-modernize`` tool – a code-to-code translator that takes a Python 2 codebase and updates it to be compatible with both Python 2 and 3. -The tool builds on top of ``2to3``, a library that comes with Python. ``2to3`` -was once intended as the main porting tool. It turned out inadequate for that -task, but ``python-modernize`` (among others) successfully reuses its general -infrastructure. +.. note:: + + ``python-modernize`` was built on top of ``2to3`` from of Python's + standard library. ``2to3`` was once intended as the main porting tool. + It turned out inadequate for that task, but ``python-modernize`` + (among others) successfully reuses its general infrastructure. + Because ``2to3`` itself is built into Python and thus missing improvements + newer than the the Python that runs it, ``python-modernize`` now uses a + fork of ``2to3`` called ``fissix``. Assuming code is in version control, you'll generally want to run ``python-modernize`` with the ``-wn`` flags: ``-w`` flag causes the tool to diff --git a/test_portingdb/test_fixer_library.py b/test_portingguide/test_fixer_library.py similarity index 75% rename from test_portingdb/test_fixer_library.py rename to test_portingguide/test_fixer_library.py index 9835d3d..f012db7 100644 --- a/test_portingdb/test_fixer_library.py +++ b/test_portingguide/test_fixer_library.py @@ -4,14 +4,14 @@ import sys import pytest -import modernize basepath = Path(__file__).parent.parent / 'source' all_rst_files = [str(p.relative_to(basepath)) for p in basepath.glob('**/*.rst')] -# Fixer names are in the format ".fixes.fix_" -FIXER_RE = re.compile(r'\w+\.fixes\.fix_\w+') +# Fixer names are in the format ".fixes.fix_" but we test also +# for typos like ".fixes.ifx_". +FIXER_RE = re.compile(r'\w+\.fixes\.\w+') @pytest.fixture(scope='module') @@ -20,7 +20,10 @@ def fixer_names(): # use subprocess -- modernize doesn't have public fixer list API proc = subprocess.run([sys.executable, '-m', 'modernize', '-l'], stdout=subprocess.PIPE, encoding='ascii', check=True) - lines = proc.stdout.splitlines() + + # In modernize 0.6.1, the output has changed to list additional information + # This takes the first part only and works with older modernize as well + lines = [l.strip().split()[0] for l in proc.stdout.splitlines() if l] # first line is a header, all others should be fixers assert not FIXER_RE.fullmatch(lines[0]) diff --git a/test_portingguide/test_future_imports.py b/test_portingguide/test_future_imports.py new file mode 100644 index 0000000..14bd1dd --- /dev/null +++ b/test_portingguide/test_future_imports.py @@ -0,0 +1,30 @@ +from pathlib import Path +import subprocess +import re +import sys + +import pytest + +basepath = Path(__file__).parent.parent / 'source' +all_rst_files = [str(p.relative_to(basepath)) + for p in basepath.glob('**/*.rst')] + +# All future imports mentioned +FUTURE_RE = re.compile(r'from __future__ import [^\s`]+') + + +def try_import(line): + cp = subprocess.run([sys.executable, '-c', line]) + return cp.returncode + + +@pytest.mark.parametrize('filename', all_rst_files) +def test_future_imports_work(filename): + """Test that all mentioned future imports work""" + path = basepath / filename + with path.open() as f: + for lineno, line in enumerate(f, start=1): + for match in FUTURE_RE.finditer(line): + found_text = match.group(0) + print(f'{filename}:{lineno}:{found_text}') + assert try_import(found_text) == 0