├── tests ├── __init__.py ├── samples │ ├── package1 │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ ├── __init__.py │ │ │ │ └── sp_data_dir │ │ │ │ │ └── test.json │ │ │ ├── subpkg2 │ │ │ │ └── __init__.py │ │ │ ├── data_dir │ │ │ │ └── foo.sh │ │ │ └── __init__.py │ │ ├── my-description.rst │ │ └── pyproject.toml │ ├── altdistname │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ ├── __init__.py │ │ │ │ └── sp_data_dir │ │ │ │ │ └── test.json │ │ │ ├── subpkg2 │ │ │ │ └── __init__.py │ │ │ ├── data_dir │ │ │ │ └── foo.sh │ │ │ └── __init__.py │ │ └── pyproject.toml │ ├── no_docstring.py │ ├── package2 │ │ ├── src │ │ │ └── package2 │ │ │ │ ├── foo.py │ │ │ │ └── __init__.py │ │ ├── package2-pkg.ini │ │ └── pyproject.toml │ ├── sample-stubs │ │ ├── src │ │ │ └── wcwidth-stubs │ │ │ │ ├── py.typed │ │ │ │ └── __init__.pyi │ │ └── pyproject.toml │ ├── with_flit_ini │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ └── __init__.py │ │ │ └── __init__.py │ │ ├── some_entry_points.txt │ │ └── flit.ini │ ├── entrypoints_valid │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ ├── __init__.py │ │ │ │ └── sp_data_dir │ │ │ │ │ └── test.json │ │ │ ├── subpkg2 │ │ │ │ └── __init__.py │ │ │ ├── data_dir │ │ │ │ └── foo.sh │ │ │ └── __init__.py │ │ └── pyproject.toml │ ├── entrypoints_conflict │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ ├── __init__.py │ │ │ │ └── sp_data_dir │ │ │ │ │ └── test.json │ │ │ ├── subpkg2 │ │ │ │ └── __init__.py │ │ │ ├── data_dir │ │ │ │ └── foo.sh │ │ │ └── __init__.py │ │ ├── console_entry_points.txt │ │ └── pyproject.toml │ ├── my-description.rst │ ├── module1.py │ ├── module3 │ │ ├── LICENSE │ │ ├── src │ │ │ └── module3.py │ │ └── pyproject.toml │ ├── extras │ │ ├── module1.py │ │ └── pyproject.toml │ ├── pep517 │ │ ├── module1.py │ │ └── pyproject.toml │ ├── module1_ini │ │ ├── module1.py │ │ └── flit.ini │ ├── module1_toml │ │ ├── module1.py │ │ ├── EG_README.rst │ │ └── pyproject.toml │ ├── packageinsrc │ │ ├── src │ │ │ └── module1.py │ │ └── pyproject.toml │ ├── requires-envmark │ │ ├── module1.py │ │ └── pyproject.toml │ ├── requires-extra-envmark │ │ ├── module1.py │ │ └── pyproject.toml │ ├── module2.py │ ├── ns1-pkg-mod │ │ ├── ns1 │ │ │ └── module.py │ │ └── pyproject.toml │ ├── invalid_version1.py │ ├── moduleunimportable.py │ ├── ns1-pkg │ │ ├── ns1 │ │ │ └── pkg │ │ │ │ └── __init__.py │ │ ├── EG_README.rst │ │ └── pyproject.toml │ ├── ns1-pkg2 │ │ ├── ns1 │ │ │ └── pkg2 │ │ │ │ └── __init__.py │ │ ├── EG_README.rst │ │ └── pyproject.toml │ ├── modulewithlocalversion │ │ ├── modulewithlocalversion.py │ │ └── pyproject.toml │ ├── modulewithconstructedversion.py │ ├── EG_README.rst │ ├── requires-requests.toml │ ├── bad-description-ext.toml │ ├── missing-description-file.toml │ ├── no_docstring-pkg.toml │ └── invalid_classifier.toml ├── test_config.py ├── conftest.py ├── test_command.py ├── test_vcs.py ├── test_tomlify.py ├── test_find_python_executable.py ├── test_sdist.py ├── test_build.py ├── test_upload.py └── test_validate.py ├── codecov.yml ├── flit ├── vendorized │ ├── __init__.py │ └── readme │ │ ├── __init__.py │ │ ├── clean.py │ │ └── rst.py ├── __main__.py ├── wheel.py ├── buildapi.py ├── vcs │ ├── __init__.py │ ├── git.py │ └── hg.py ├── logo.py ├── config.py ├── _get_dirs.py ├── license_templates │ └── mit ├── build.py ├── tomlify.py ├── log.py ├── sdist.py └── __init__.py ├── flit_core ├── tests_core │ ├── __init__.py │ ├── samples │ │ ├── package1 │ │ │ ├── foo.py │ │ │ ├── subpkg │ │ │ │ ├── __init__.py │ │ │ │ └── sp_data_dir │ │ │ │ │ └── test.json │ │ │ ├── subpkg2 │ │ │ │ └── __init__.py │ │ │ ├── data_dir │ │ │ │ └── foo.sh │ │ │ └── __init__.py │ │ ├── pep621_nodynamic │ │ │ ├── module1.py │ │ │ ├── README.rst │ │ │ └── pyproject.toml │ │ ├── conflicting_modules │ │ │ ├── module1.py │ │ │ ├── src │ │ │ │ └── module1.py │ │ │ └── pyproject.toml │ │ ├── normalization │ │ │ ├── my_python_module.py │ │ │ └── pyproject.toml │ │ ├── inclusion │ │ │ ├── doc │ │ │ │ ├── subdir │ │ │ │ │ ├── subsubdir │ │ │ │ │ │ └── test.md │ │ │ │ │ └── test.txt │ │ │ │ ├── test.rst │ │ │ │ └── test.txt │ │ │ ├── module1.py │ │ │ ├── LICENSES │ │ │ │ └── README │ │ │ └── pyproject.toml │ │ ├── license_in_src │ │ │ ├── license_in_src │ │ │ │ ├── lic.txt │ │ │ │ └── __init__.py │ │ │ └── pyproject.toml │ │ ├── no_docstring.py │ │ ├── pep621_license_files │ │ │ ├── README.rst │ │ │ ├── LICENSE │ │ │ ├── module │ │ │ │ └── vendor │ │ │ │ │ └── LICENSE_VENDOR │ │ │ ├── module1a.py │ │ │ └── pyproject.toml │ │ ├── my-description.rst │ │ ├── pep517 │ │ │ ├── LICENSE │ │ │ ├── README.rst │ │ │ ├── module1.py │ │ │ └── pyproject.toml │ │ ├── pep621 │ │ │ ├── LICENSE │ │ │ ├── README.rst │ │ │ ├── module1a.py │ │ │ └── pyproject.toml │ │ ├── module1.py │ │ ├── with_data_dir │ │ │ ├── LICENSE │ │ │ ├── README.rst │ │ │ ├── data │ │ │ │ └── share │ │ │ │ │ └── man │ │ │ │ │ └── man1 │ │ │ │ │ └── foo.1 │ │ │ ├── module1.py │ │ │ └── pyproject.toml │ │ ├── imported_version │ │ │ ├── package1 │ │ │ │ ├── _version.py │ │ │ │ └── __init__.py │ │ │ └── pyproject.toml │ │ ├── invalid_version1.py │ │ ├── annotated_version │ │ │ ├── module1.py │ │ │ └── pyproject.toml │ │ ├── moduleunimportable.py │ │ ├── ns1-pkg │ │ │ ├── ns1 │ │ │ │ └── pkg │ │ │ │ │ └── __init__.py │ │ │ ├── EG_README.rst │ │ │ └── pyproject.toml │ │ ├── constructed_version │ │ │ ├── module1.py │ │ │ └── pyproject.toml │ │ ├── moduleunimportabledouble.py │ │ ├── EG_README.rst │ │ ├── module2.py │ │ ├── bad-description-ext.toml │ │ ├── missing-description-file.toml │ │ ├── misspelled-key.toml │ │ ├── requires-requests.toml │ │ ├── extras-newstyle.toml │ │ ├── requires-envmark.toml │ │ ├── module1-pkg.toml │ │ ├── no_docstring-pkg.toml │ │ ├── requires-extra-envmark.toml │ │ ├── module1-old-metadata.toml │ │ ├── extras.toml │ │ └── package1.toml │ ├── test_versionno.py │ ├── test_build_thyself.py │ ├── test_wheel.py │ ├── test_sdist.py │ └── test_buildapi.py ├── flit_core │ ├── vendor │ │ ├── __init__.py │ │ ├── tomli │ │ │ ├── py.typed │ │ │ ├── _types.py │ │ │ ├── __init__.py │ │ │ └── _re.py │ │ ├── README │ │ └── tomli-1.2.3.dist-info │ │ │ └── LICENSE │ ├── __init__.py │ ├── buildapi.py │ ├── versionno.py │ └── sdist.py ├── README.rst ├── build_dists.py ├── update-vendored-tomli.sh ├── pyproject.toml ├── LICENSE └── bootstrap_install.py ├── .coveragerc ├── doc ├── requirements.txt ├── _static │ ├── flit_logo_nobg_cropped.png │ └── flit_logo_nobg.svg ├── index.rst ├── development.rst ├── reproducible.rst ├── bootstrap.rst ├── rationale.rst ├── upload.rst ├── Makefile ├── make.bat └── conf.py ├── .github ├── dependabot.yml └── workflows │ └── test.yml ├── .pre-commit-config.yaml ├── .readthedocs.yml ├── .gitignore ├── .bumpversion.cfg ├── SECURITY.md ├── prepare_license_list.py ├── tox.ini ├── pyproject.toml ├── bootstrap_dev.py ├── LICENSE └── README.rst /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | -------------------------------------------------------------------------------- /flit/vendorized/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit/vendorized/readme/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/no_docstring.py: -------------------------------------------------------------------------------- 1 | __version__ = '7.0' 2 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/subpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/package2/src/package2/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /tests/samples/sample-stubs/src/wcwidth-stubs/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/with_flit_ini/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /flit/__main__.py: -------------------------------------------------------------------------------- 1 | from . import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/subpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_nodynamic/module1.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/subpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /tests/samples/with_flit_ini/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/conflicting_modules/module1.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/conflicting_modules/src/module1.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/normalization/my_python_module.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/foo.py: -------------------------------------------------------------------------------- 1 | a = 1 2 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/subpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/subpkg/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/subpkg2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/doc/subdir/subsubdir/test.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/license_in_src/license_in_src/lic.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/no_docstring.py: -------------------------------------------------------------------------------- 1 | __version__ = '7.0' 2 | -------------------------------------------------------------------------------- /tests/samples/my-description.rst: -------------------------------------------------------------------------------- 1 | Sample description for test. 2 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = */tests/* 3 | */flit_core/vendor/* 4 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/tomli/py.typed: -------------------------------------------------------------------------------- 1 | # Marker file for PEP 561 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/license_in_src/license_in_src/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_license_files/README.rst: -------------------------------------------------------------------------------- 1 | Readme 2 | -------------------------------------------------------------------------------- /tests/samples/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/module3/LICENSE: -------------------------------------------------------------------------------- 1 | Dummy license - check that it gets packaged 2 | -------------------------------------------------------------------------------- /tests/samples/package1/my-description.rst: -------------------------------------------------------------------------------- 1 | Sample description for test. 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/my-description.rst: -------------------------------------------------------------------------------- 1 | Sample description for test. 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep517/LICENSE: -------------------------------------------------------------------------------- 1 | This file should be added to wheels 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621/LICENSE: -------------------------------------------------------------------------------- 1 | This file should be added to wheels 2 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/subpkg/sp_data_dir/test.json: -------------------------------------------------------------------------------- 1 | {"example": true} 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/subpkg/sp_data_dir/test.json: -------------------------------------------------------------------------------- 1 | {"example": true} 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep517/README.rst: -------------------------------------------------------------------------------- 1 | This contains a nön-ascii character 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621/README.rst: -------------------------------------------------------------------------------- 1 | This contains a nön-ascii character 2 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/subpkg/sp_data_dir/test.json: -------------------------------------------------------------------------------- 1 | {"example": true} 2 | -------------------------------------------------------------------------------- /tests/samples/extras/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/pep517/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/with_data_dir/LICENSE: -------------------------------------------------------------------------------- 1 | This file should be added to wheels 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/with_data_dir/README.rst: -------------------------------------------------------------------------------- 1 | This contains a nön-ascii character 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/with_data_dir/data/share/man/man1/foo.1: -------------------------------------------------------------------------------- 1 | Example data file 2 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/data_dir/foo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Example data file" 3 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/subpkg/sp_data_dir/test.json: -------------------------------------------------------------------------------- 1 | {"example": true} 2 | -------------------------------------------------------------------------------- /tests/samples/module1_ini/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/module1_toml/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/module3/src/module3.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/data_dir/foo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Example data file" 3 | -------------------------------------------------------------------------------- /tests/samples/with_flit_ini/some_entry_points.txt: -------------------------------------------------------------------------------- 1 | [myplugins] 2 | package1=package1:main 3 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/module1.py: -------------------------------------------------------------------------------- 1 | """For tests""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/data_dir/foo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Example data file" 3 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_license_files/LICENSE: -------------------------------------------------------------------------------- 1 | This file should be added to wheels 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_nodynamic/README.rst: -------------------------------------------------------------------------------- 1 | This contains a nön-ascii character 2 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/console_entry_points.txt: -------------------------------------------------------------------------------- 1 | [console_scripts] 2 | foo=bar:baz 3 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/subpkg/sp_data_dir/test.json: -------------------------------------------------------------------------------- 1 | {"example": true} 2 | -------------------------------------------------------------------------------- /tests/samples/packageinsrc/src/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/requires-envmark/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinx ~= 7.0 2 | sphinxcontrib_github_alt ~= 1.2 3 | sphinx-rtd-theme ~= 2.0 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/doc/test.rst: -------------------------------------------------------------------------------- 1 | sdists should include this (see pyproject.toml) 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/doc/test.txt: -------------------------------------------------------------------------------- 1 | sdists should exclude this (see pyproject.toml) 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep517/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621/module1a.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/data_dir/foo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Example data file" 3 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/data_dir/foo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo "Example data file" 3 | -------------------------------------------------------------------------------- /tests/samples/requires-extra-envmark/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/with_data_dir/module1.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /tests/samples/module2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Docstring formatted like this. 3 | """ 4 | 5 | __version__ = '7.0' 6 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/doc/subdir/test.txt: -------------------------------------------------------------------------------- 1 | sdists should include this (see pyproject.toml) 2 | -------------------------------------------------------------------------------- /flit/vendorized/readme/clean.py: -------------------------------------------------------------------------------- 1 | ## shim readme clean to simplify vendorizing of readme.rst 2 | clean = lambda x:x 3 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_license_files/module/vendor/LICENSE_VENDOR: -------------------------------------------------------------------------------- 1 | This file should be added to wheels 2 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_license_files/module1a.py: -------------------------------------------------------------------------------- 1 | """Example module""" 2 | 3 | __version__ = '0.1' 4 | -------------------------------------------------------------------------------- /doc/_static/flit_logo_nobg_cropped.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pypa/flit/HEAD/doc/_static/flit_logo_nobg_cropped.png -------------------------------------------------------------------------------- /tests/samples/ns1-pkg-mod/ns1/module.py: -------------------------------------------------------------------------------- 1 | """An example single file module in a namespace package 2 | """ 3 | 4 | __version__ = '0.1' 5 | -------------------------------------------------------------------------------- /tests/samples/invalid_version1.py: -------------------------------------------------------------------------------- 1 | """Sample module with invalid __version__ string""" 2 | 3 | __version__ = "not starting with a number" 4 | -------------------------------------------------------------------------------- /tests/samples/moduleunimportable.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A sample unimportable module 4 | """ 5 | 6 | raise ImportError() 7 | 8 | __version__ = "0.1" 9 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg/ns1/pkg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ================== 3 | ns1.pkg 4 | ================== 5 | """ 6 | 7 | __version__ = '0.1' 8 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg2/ns1/pkg2/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ================== 3 | ns1.pkg2 4 | ================== 5 | """ 6 | 7 | __version__ = '0.1' 8 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/imported_version/package1/_version.py: -------------------------------------------------------------------------------- 1 | """Imposter docstring that shouldn't be used""" 2 | 3 | __version__ = '0.5.8' 4 | -------------------------------------------------------------------------------- /tests/samples/package1/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/invalid_version1.py: -------------------------------------------------------------------------------- 1 | """Sample module with invalid __version__ string""" 2 | 3 | __version__ = "not starting with a number" 4 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /tests/samples/altdistname/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /tests/samples/package2/src/package2/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /tests/samples/with_flit_ini/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/annotated_version/module1.py: -------------------------------------------------------------------------------- 1 | 2 | """This module has a __version__ that has a type annotation""" 3 | 4 | __version__: str = '0.1' 5 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/LICENSES/README: -------------------------------------------------------------------------------- 1 | This directory will match the LICENSE* glob which Flit uses to add license 2 | files to wheel metadata. 3 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/moduleunimportable.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A sample unimportable module 4 | """ 5 | 6 | raise ImportError() 7 | 8 | __version__ = "0.1" 9 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/ns1-pkg/ns1/pkg/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | ================== 3 | ns1.pkg 4 | ================== 5 | """ 6 | 7 | __version__ = '0.1' 8 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """A sample package""" 2 | 3 | __version__ = '0.1' 4 | 5 | def main(): 6 | print("package1 main") 7 | -------------------------------------------------------------------------------- /tests/samples/modulewithlocalversion/modulewithlocalversion.py: -------------------------------------------------------------------------------- 1 | """ 2 | A module with a local version specifier 3 | """ 4 | 5 | __version__ = "0.1.dev0+test" 6 | -------------------------------------------------------------------------------- /tests/samples/module1_ini/flit.ini: -------------------------------------------------------------------------------- 1 | [metadata] 2 | module=module1 3 | author=Sir Robin 4 | author-email=robin@camelot.uk 5 | home-page=http://github.com/sirrobin/module1 6 | -------------------------------------------------------------------------------- /tests/samples/modulewithconstructedversion.py: -------------------------------------------------------------------------------- 1 | 2 | """This module has a __version__ that requires runtime interpretation""" 3 | 4 | __version__ = ".".join(["1", "2", "3"]) 5 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/tomli/_types.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Tuple 2 | 3 | # Type annotations 4 | ParseFloat = Callable[[str], Any] 5 | Key = Tuple[str, ...] 6 | Pos = int 7 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/constructed_version/module1.py: -------------------------------------------------------------------------------- 1 | 2 | """This module has a __version__ that requires runtime interpretation""" 3 | 4 | __version__ = ".".join(["1", "2", "3"]) 5 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v6.0.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/moduleunimportabledouble.py: -------------------------------------------------------------------------------- 1 | 2 | """ 3 | A sample unimportable module with double assignment 4 | """ 5 | 6 | raise ImportError() 7 | 8 | VERSION = __version__ = "0.1" 9 | -------------------------------------------------------------------------------- /tests/samples/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg2/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /tests/samples/module1_toml/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg-mod/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.5,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "ns1.module" 7 | dynamic = ["version", "description"] 8 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/imported_version/package1/__init__.py: -------------------------------------------------------------------------------- 1 | """This module has a __version__ that requires a relative import""" 2 | 3 | from ._version import __version__ 4 | 5 | import a_package_that_doesnt_exist 6 | -------------------------------------------------------------------------------- /tests/samples/package2/package2-pkg.ini: -------------------------------------------------------------------------------- 1 | [metadata] 2 | module=package2 3 | author=Sir Robin 4 | author-email=robin@camelot.uk 5 | home-page=http://github.com/sirrobin/package2 6 | 7 | [scripts] 8 | pkg_script=package2:main 9 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/ns1-pkg/EG_README.rst: -------------------------------------------------------------------------------- 1 | This is an example long description for tests to load. 2 | 3 | This file is `valid reStructuredText 4 | `_. 5 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | sphinx: 9 | configuration: doc/conf.py 10 | 11 | python: 12 | install: 13 | - requirements: doc/requirements.txt 14 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/module2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Docstring formatted like this. 3 | """ 4 | 5 | a = {} 6 | # An assignment to a subscript (a['test']) broke introspection 7 | # https://github.com/pypa/flit/issues/343 8 | a['test'] = 6 9 | 10 | __version__ = '7.0' 11 | -------------------------------------------------------------------------------- /tests/samples/with_flit_ini/flit.ini: -------------------------------------------------------------------------------- 1 | [metadata] 2 | module=package1 3 | author=Sir Robin 4 | author-email=robin@camelot.uk 5 | home-page=http://github.com/sirrobin/package1 6 | entry-points-file=some_entry_points.txt 7 | 8 | [scripts] 9 | pkg_script=package1:main 10 | -------------------------------------------------------------------------------- /flit_core/flit_core/__init__.py: -------------------------------------------------------------------------------- 1 | """Flit's core machinery for building packages. 2 | 3 | This package provides a standard PEP 517 API to build packages using Flit. 4 | All the convenient development features live in the main 'flit' package. 5 | """ 6 | 7 | __version__ = '3.12.0' 8 | -------------------------------------------------------------------------------- /tests/samples/packageinsrc/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit"] 3 | build-backend = "flit.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /dist/ 3 | /flit_core/dist/ 4 | __pycache__/ 5 | /doc/_build/ 6 | /tests/samples/build/ 7 | /tests/samples/dist/ 8 | /tests/samples/ns1-pkg/dist/ 9 | /htmlcov/ 10 | /.coverage 11 | /.pytest_cache 12 | /.tox 13 | .idea/ 14 | venv/ 15 | *.pyc 16 | .python-version 17 | -------------------------------------------------------------------------------- /tests/samples/module3/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module3" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | -------------------------------------------------------------------------------- /tests/samples/requires-requests.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit"] 3 | 4 | [project] 5 | name = "module1" 6 | authors = [ 7 | {name = "Sir Robin", email = "robin@camelot.uk"} 8 | ] 9 | readme = "EG_README.rst" 10 | dependencies = ["requests"] 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /tests/samples/sample-stubs/src/wcwidth-stubs/__init__.pyi: -------------------------------------------------------------------------------- 1 | def list_versions() -> tuple[str, ...]: ... 2 | def wcwidth(wc: str, unicode_version: str = ...) -> int: ... 3 | def wcswidth(pwcs: str, n: int | None = None, unicode_version: str = ...): ... 4 | 5 | __all__ = ("wcwidth", "wcswidth", "list_versions") 6 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/conflicting_modules/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/imported_version/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Röbin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | -------------------------------------------------------------------------------- /tests/samples/modulewithlocalversion/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "modulewithlocalversion" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.5,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "ns1.pkg" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /tests/samples/ns1-pkg2/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.5,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "ns1.pkg2" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /tests/samples/bad-description-ext.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "module1.py" # WRONG 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /flit/wheel.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import flit_core.wheel as core_wheel 4 | 5 | log = logging.getLogger(__name__) 6 | 7 | def make_wheel_in(ini_path, wheel_directory, editable=False): 8 | return core_wheel.make_wheel_in(ini_path, wheel_directory, editable) 9 | 10 | class WheelBuilder(core_wheel.WheelBuilder): 11 | pass 12 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/ns1-pkg/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.5,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "ns1.pkg" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /tests/test_config.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import pytest 3 | 4 | from flit.config import read_flit_config, ConfigError 5 | 6 | samples_dir = Path(__file__).parent / 'samples' 7 | 8 | def test_invalid_classifier(): 9 | with pytest.raises(ConfigError): 10 | read_flit_config(samples_dir / 'invalid_classifier.toml') 11 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/bad-description-ext.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "module1.py" # WRONG 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/license_in_src/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "license_in_src" 7 | version = "0" 8 | description = "A test project where a license file is inside the source directory" 9 | license-files = ["license_in_src/lic.txt"] 10 | -------------------------------------------------------------------------------- /flit/buildapi.py: -------------------------------------------------------------------------------- 1 | from warnings import warn 2 | warn('A package has specified `build-backend = "flit.buildapi"` and is being ' 3 | 'built with Flit >= 3.10. This is likely to break in a future version. ' 4 | 'Please change the backend to flit_core.buildapi, and/or specify a ' 5 | 'maximum version of Flit.') 6 | from flit_core.buildapi import * 7 | -------------------------------------------------------------------------------- /tests/samples/module1_toml/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit"] 3 | 4 | [project] 5 | name = "module1" 6 | authors = [ 7 | {name = "Sir Robin", email = "robin@camelot.uk"} 8 | ] 9 | readme = "EG_README.rst" 10 | dynamic = ["version", "description"] 11 | 12 | [project.urls] 13 | Documentation = "https://example.com/module1" 14 | -------------------------------------------------------------------------------- /tests/samples/altdistname/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package-Dist1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [tool.flit.module] 13 | name = "package1" 14 | -------------------------------------------------------------------------------- /tests/samples/missing-description-file.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "missingdescriptionfile" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "definitely-missing.rst" 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 3.12.0 3 | commit = True 4 | tag = False 5 | 6 | [bumpversion:file:pyproject.toml] 7 | search = flit_core >={current_version} 8 | replace = flit_core >={new_version} 9 | 10 | [bumpversion:file:flit/__init__.py] 11 | 12 | [bumpversion:file:flit_core/flit_core/__init__.py] 13 | 14 | [bumpversion:file:doc/conf.py] 15 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/missing-description-file.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "missingdescriptionfile" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "definitely-missing.rst" 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/misspelled-key.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | reedme = "my-description.rst" # Deliberate typo for test 11 | dynamic = ["version", "description"] 12 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/requires-requests.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dependencies = ["requests"] 12 | dynamic = ["version", "description"] 13 | -------------------------------------------------------------------------------- /tests/samples/pep517/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = [ 11 | "requests >= 2.18", 12 | "docutils", 13 | ] 14 | dynamic = ["version", "description"] 15 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/tomli/__init__.py: -------------------------------------------------------------------------------- 1 | """A lil' TOML parser.""" 2 | 3 | __all__ = ("loads", "load", "TOMLDecodeError") 4 | __version__ = "1.2.3" # DO NOT EDIT THIS LINE MANUALLY. LET bump2version UTILITY DO IT 5 | 6 | from ._parser import TOMLDecodeError, load, loads 7 | 8 | # Pretend this exception was created here. 9 | TOMLDecodeError.__module__ = "tomli" 10 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/annotated_version/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = [ 11 | "numpy >=1.16.0", 12 | ] 13 | dynamic = ["version", "description"] 14 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/constructed_version/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = [ 11 | "numpy >=1.16.0", 12 | ] 13 | dynamic = ["version", "description"] 14 | -------------------------------------------------------------------------------- /tests/samples/no_docstring-pkg.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "no_docstring" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [project.urls] 13 | Documentation = "https://example.com/no_docstring" 14 | -------------------------------------------------------------------------------- /flit_core/README.rst: -------------------------------------------------------------------------------- 1 | flit_core 2 | --------- 3 | 4 | This provides a `PEP 517 `_ build backend 5 | for packages using `Flit `_. The only public 6 | interface is the API specified by PEP 517, at ``flit_core.buildapi``. 7 | 8 | See the `Flit documentation `_ for more 9 | information. 10 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/extras-newstyle.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | version = "0.1" 8 | description = "Example for testing" 9 | dependencies = ["toml"] 10 | 11 | [project.optional-dependencies] 12 | test = ["pytest"] 13 | cus__Tom = ["requests"] # To test normalisation 14 | -------------------------------------------------------------------------------- /flit/vcs/__init__.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | from . import hg 4 | from . import git 5 | 6 | def identify_vcs(directory: Path): 7 | directory = directory.resolve() 8 | for p in [directory] + list(directory.parents): 9 | if (p / '.git').is_dir(): 10 | return git 11 | if (p / '.hg').is_dir(): 12 | return hg 13 | 14 | return None 15 | -------------------------------------------------------------------------------- /tests/samples/requires-envmark/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = [ 11 | "requests", 12 | "pathlib2; python_version == '2.7'", 13 | ] 14 | dynamic = ["version", "description"] 15 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/inclusion/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [tool.flit.sdist] 13 | include = ["doc"] 14 | exclude = ["doc/*.txt", "doc/**/*.md"] 15 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/requires-envmark.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = [ 11 | "requests", 12 | "pathlib2; python_version == '2.7'", 13 | ] 14 | dynamic = ["version", "description"] 15 | -------------------------------------------------------------------------------- /tests/samples/requires-extra-envmark/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [project.optional-dependencies] 13 | test = ["pathlib2; python_version == \"2.7\""] 14 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/module1-pkg.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | 13 | [project.urls] 14 | Documentation = "https://example.com/module1" 15 | -------------------------------------------------------------------------------- /tests/samples/extras/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dependencies = ["toml"] 11 | dynamic = ["version", "description"] 12 | 13 | [project.optional-dependencies] 14 | test = ["pytest"] 15 | custom = ["requests"] 16 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/no_docstring-pkg.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "no_docstring" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | 13 | [project.urls] 14 | Documentation = "https://example.com/no_docstring" 15 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/requires-extra-envmark.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dynamic = ["version", "description"] 12 | 13 | [project.optional-dependencies] 14 | test = ["pathlib2; python_version == \"2.7\""] 15 | -------------------------------------------------------------------------------- /tests/samples/package2/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package2" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [project.urls] 13 | home = "http://github.com/sirrobin/package2" 14 | 15 | 16 | [project.scripts] 17 | pkg_script = "package2:main" 18 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_valid/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | [project.scripts] 13 | pkg_script = "package1:main" 14 | 15 | [project.entry-points.myplugins] 16 | package1 = "package1:main" 17 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/module1-old-metadata.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [tool.flit.metadata] 6 | module = "module1" 7 | author = "Sir Robin" 8 | author-email = "robin@camelot.uk" 9 | home-page = "http://github.com/sirrobin/module1" 10 | description-file = "EG_README.rst" 11 | 12 | [tool.flit.metadata.urls] 13 | Documentation = "https://example.com/module1" 14 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/normalization/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.8,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "my-python-module" 7 | version = "0.0.1" 8 | description = "Hyphenated package name, inferred import name" 9 | authors = [ 10 | {name = "Sir Robin", email = "robin@camelot.uk"} 11 | ] 12 | 13 | [project.urls] 14 | homepage = "http://github.com/me/python-module" 15 | -------------------------------------------------------------------------------- /tests/samples/invalid_classifier.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "my-description.rst" 11 | dynamic = ["version", "description"] 12 | classifiers = [ 13 | "License :: OSI Approved :: BSD License", 14 | "Intended Audience :: Pacman", 15 | ] 16 | -------------------------------------------------------------------------------- /tests/samples/sample-stubs/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "wcwidth-stubs" 7 | version = "0.2.13.1" 8 | description = "typing stubs for wcwidth" 9 | requires-python = ">=3.6" 10 | authors = [ 11 | { name = "john doe", email = "john@example.com" }, 12 | ] 13 | dependencies = [ 14 | 'wcwidth==0.2.13', 15 | ] 16 | license = { text = "MIT" } 17 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import pytest 3 | from shutil import copytree 4 | 5 | samples_dir = Path(__file__).parent / 'samples' 6 | 7 | @pytest.fixture 8 | def copy_sample(tmp_path): 9 | """Copy a subdirectory from the samples dir to a temp dir""" 10 | def copy(dirname): 11 | dst = tmp_path / dirname 12 | copytree(str(samples_dir / dirname), str(dst)) 13 | return dst 14 | 15 | return copy 16 | -------------------------------------------------------------------------------- /tests/samples/package1/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "my-description.rst" 11 | dynamic = ["version", "description"] 12 | 13 | [project.urls] 14 | home = "http://github.com/sirrobin/package1" 15 | 16 | [project.scripts] 17 | pkg_script = "package1:main" 18 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/extras.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "EG_README.rst" 11 | dependencies = ["toml"] 12 | dynamic = ["version", "description"] 13 | 14 | [project.optional-dependencies] 15 | test = ["pytest"] 16 | cus__Tom = ["requests"] # To test normalisation 17 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/package1.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "my-description.rst" 11 | dynamic = ["version", "description"] 12 | 13 | [project.urls] 14 | home = "http://github.com/sirrobin/package1" 15 | 16 | [project.scripts] 17 | pkg_script = "package1:main" 18 | -------------------------------------------------------------------------------- /tests/samples/entrypoints_conflict/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "package1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | dynamic = ["version", "description"] 11 | 12 | # The sections below conflict 13 | [project.scripts] 14 | pkg_script = "package1:main" 15 | 16 | [project.entry-points.console_scripts] 17 | foo = "bar:baz" 18 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep517/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Robin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "README.rst" 11 | dependencies = [ 12 | "requests >= 2.18", 13 | "docutils", 14 | ] 15 | dynamic = ["version", "description"] 16 | 17 | [project.entry-points.flit_test_example] 18 | foo = "module1:main" 19 | -------------------------------------------------------------------------------- /flit_core/build_dists.py: -------------------------------------------------------------------------------- 1 | """Build flit_core to upload to PyPI. 2 | 3 | Normally, this should only be used by me when making a release. 4 | """ 5 | import os 6 | 7 | from flit_core import buildapi 8 | 9 | os.chdir(os.path.dirname(os.path.abspath(__file__))) 10 | 11 | print("Building sdist") 12 | sdist_fname = buildapi.build_sdist('dist/') 13 | print(os.path.join('dist', sdist_fname)) 14 | 15 | print("\nBuilding wheel") 16 | whl_fname = buildapi.build_wheel('dist/') 17 | print(os.path.join('dist', whl_fname)) 18 | -------------------------------------------------------------------------------- /tests/test_command.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE, STDOUT 2 | import sys 3 | 4 | def test_flit_help(): 5 | p = Popen([sys.executable, '-m', 'flit', '--help'], stdout=PIPE, stderr=STDOUT) 6 | out, _ = p.communicate() 7 | assert 'Build wheel' in out.decode('utf-8', 'replace') 8 | 9 | def test_flit_usage(): 10 | p = Popen([sys.executable, '-m', 'flit'], stdout=PIPE, stderr=STDOUT) 11 | out, _ = p.communicate() 12 | assert 'Build wheel' in out.decode('utf-8', 'replace') 13 | assert p.poll() == 1 14 | -------------------------------------------------------------------------------- /flit/logo.py: -------------------------------------------------------------------------------- 1 | """White and colored version for flit""" 2 | 3 | 4 | logo = """ 5 | ._ ._ 6 | ```. ```. .--.______ 7 | `. `-. `. / °,-—´ 8 | `. `~-.>.' / 9 | `. .` | 10 | -..;. / 11 | / /___ _____ 12 | /r_,.´| | | | 13 | ,' `/ |—— | | | 14 | .´ ,'/ | |__ | | 15 | .´ / . / 16 | '__/|/ V {version} 17 | 18 | """ 19 | 20 | clogo = '\x1b[36m'+logo+'\x1b[39m' 21 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | Flit |version| 2 | ============== 3 | 4 | .. raw:: html 5 | 6 | 7 | 8 | .. include:: ../README.rst 9 | 10 | Documentation contents 11 | ---------------------- 12 | 13 | .. toctree:: 14 | :maxdepth: 2 15 | 16 | pyproject_toml 17 | cmdline 18 | upload 19 | reproducible 20 | rationale 21 | bootstrap 22 | 23 | .. toctree:: 24 | :maxdepth: 1 25 | 26 | development 27 | history 28 | 29 | Indices and tables 30 | ================== 31 | 32 | * :ref:`genindex` 33 | * :ref:`search` 34 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/with_data_dir/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Röbin", email = "robin@camelot.uk"} 9 | ] 10 | readme = "README.rst" 11 | license = {file = "LICENSE"} 12 | requires-python = ">=3.7" 13 | dependencies = [ 14 | "requests >= 2.18", 15 | "docutils", 16 | ] 17 | dynamic = [ 18 | "version", 19 | "description", 20 | ] 21 | 22 | [project.scripts] 23 | foo = "module1:main" 24 | 25 | [tool.flit.external-data] 26 | directory = "data" 27 | -------------------------------------------------------------------------------- /flit/vcs/git.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import check_output 3 | 4 | name = 'git' 5 | 6 | def list_tracked_files(directory): 7 | outb = check_output(['git', 'ls-files', '--recurse-submodules', '-z'], 8 | cwd=str(directory)) 9 | return [os.fsdecode(l) for l in outb.strip(b'\0').split(b'\0') if l] 10 | 11 | def list_untracked_deleted_files(directory): 12 | outb = check_output(['git', 'ls-files', '--deleted', '--others', 13 | '--exclude-standard', '-z'], 14 | cwd=str(directory)) 15 | return [os.fsdecode(l) for l in outb.strip(b'\0').split(b'\0') if l] 16 | -------------------------------------------------------------------------------- /flit_core/update-vendored-tomli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Update the vendored copy of tomli 3 | set -euo pipefail 4 | 5 | version=$1 6 | echo "Bundling tomli version $version" 7 | 8 | rm -rf flit_core/vendor/tomli* 9 | pip install --target flit_core/vendor/ "tomli==$version" 10 | 11 | # Convert absolute imports to relative (from tomli.foo -> from .foo) 12 | for file in flit_core/vendor/tomli/*.py; do 13 | sed -i -E 's/((from|import)[[:space:]]+)tomli\./\1\./' "$file" 14 | done 15 | 16 | # Delete some files that aren't useful in this context. 17 | # Leave LICENSE & METADATA present. 18 | rm flit_core/vendor/tomli*.dist-info/{INSTALLER,RECORD,REQUESTED,WHEEL} 19 | -------------------------------------------------------------------------------- /flit/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from flit_core.config import * 4 | from flit_core.config import read_flit_config as _read_flit_config_core 5 | from .validate import validate_config 6 | 7 | 8 | def read_flit_config(path): 9 | """Read and check the `pyproject.toml` or `flit.ini` file with data about the package. 10 | """ 11 | res = _read_flit_config_core(path) 12 | 13 | if validate_config(res): 14 | if os.environ.get('FLIT_ALLOW_INVALID'): 15 | log.warning("Allowing invalid data (FLIT_ALLOW_INVALID set). Uploads may still fail.") 16 | else: 17 | raise ConfigError("Invalid config values (see log)") 18 | return res 19 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Only the latest non-prerelease version is supported. 6 | 7 | ## Security contact information 8 | 9 | To report a security vulnerability 10 | 11 | ### Directly on GitHub 12 | 13 | You can also directly propose a GitHub security advisory on the Flit Security page of github: 14 | 15 | [https://github.com/pypa/flit/security](https://github.com/pypa/flit/security) 16 | 17 | ### via Tidelift: 18 | 19 | You can use the 20 | [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the 21 | fix and disclosure. 22 | 23 | If you are a tidelift subscriber, this is the preferred path 24 | -------------------------------------------------------------------------------- /doc/development.rst: -------------------------------------------------------------------------------- 1 | Developing Flit 2 | =============== 3 | 4 | To get a development installation of Flit itself:: 5 | 6 | git clone https://github.com/pypa/flit.git 7 | cd flit 8 | python3 -m pip install docutils requests 9 | python3 bootstrap_dev.py 10 | 11 | This links Flit into the current Python environment, so you can make changes 12 | and try them without having to reinstall each time. 13 | 14 | Testing 15 | ------- 16 | 17 | To run the tests in separate environments for each available Python version:: 18 | 19 | tox 20 | 21 | `tox `_ has many options. 22 | 23 | To run the tests in your current environment, run:: 24 | 25 | pytest 26 | -------------------------------------------------------------------------------- /tests/test_vcs.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import os 3 | from pathlib import Path 4 | from tempfile import TemporaryDirectory 5 | 6 | from flit import vcs 7 | 8 | @contextmanager 9 | def cwd(path): 10 | if isinstance(path, Path): 11 | path = str(path) 12 | old_wd = os.getcwd() 13 | os.chdir(path) 14 | try: 15 | yield 16 | finally: 17 | os.chdir(old_wd) 18 | 19 | def test_identify_git_parent(): 20 | with TemporaryDirectory() as td: 21 | td = Path(td) 22 | (td / '.git').mkdir() 23 | subdir = (td / 'subdir') 24 | subdir.mkdir() 25 | with cwd(subdir): 26 | assert vcs.identify_vcs(Path('.')).name == 'git' 27 | -------------------------------------------------------------------------------- /prepare_license_list.py: -------------------------------------------------------------------------------- 1 | # Call with path to SPDX license-list-data repo, cloned from: 2 | # https://github.com/spdx/license-list-data 3 | 4 | import json 5 | import pprint 6 | import sys 7 | from pathlib import Path 8 | 9 | list_data_repo = Path(sys.argv[1]) 10 | with (list_data_repo / 'json' / 'licenses.json').open('rb') as f: 11 | licenses_json = json.load(f) 12 | 13 | condensed = { 14 | l['licenseId'].lower(): {'id': l['licenseId']} 15 | for l in licenses_json['licenses'] 16 | if not l['isDeprecatedLicenseId'] 17 | } 18 | 19 | with Path('flit_core', 'flit_core', '_spdx_data.py').open('w') as f: 20 | f.write("# This file is generated from SPDX license data; don't edit it manually.\n\n") 21 | 22 | f.write("licenses = \\\n") 23 | pprint.pprint(condensed, f) 24 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_nodynamic/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | version = "0.03" 8 | description = "Statically specified description" 9 | authors = [ 10 | {name = "Sir Robin", email = "robin@camelot.uk"} 11 | ] 12 | readme = {file = "README.rst", content-type = "text/x-rst"} 13 | classifiers = [ 14 | "Topic :: Internet :: WWW/HTTP", 15 | ] 16 | dependencies = [ 17 | "requests >= 2.18", 18 | "docutils", 19 | ] # N.B. Using this to check behaviour with dependencies but no optional deps 20 | 21 | [project.urls] 22 | homepage = "http://github.com/sirrobin/module1" 23 | 24 | [project.scripts] 25 | foo = "module1:main" 26 | 27 | [project.gui-scripts] 28 | foo-gui = "module1:main" 29 | -------------------------------------------------------------------------------- /tests/test_tomlify.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | try: 3 | import tomllib 4 | except ImportError: 5 | import tomli as tomllib 6 | from testpath import assert_isfile 7 | 8 | from flit import tomlify 9 | 10 | samples_dir = Path(__file__).parent / 'samples' 11 | 12 | def test_tomlify(copy_sample, monkeypatch): 13 | td = copy_sample('with_flit_ini') 14 | monkeypatch.chdir(td) 15 | 16 | tomlify.main(argv=[]) 17 | 18 | pyproject_toml = (td / 'pyproject.toml') 19 | assert_isfile(pyproject_toml) 20 | 21 | with pyproject_toml.open('rb') as f: 22 | content = tomllib.load(f) 23 | 24 | assert 'build-system' in content 25 | assert 'tool' in content 26 | assert 'flit' in content['tool'] 27 | flit_table = content['tool']['flit'] 28 | assert 'metadata' in flit_table 29 | assert 'scripts' in flit_table 30 | assert 'entrypoints' in flit_table 31 | -------------------------------------------------------------------------------- /flit_core/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [] 3 | build-backend = "flit_core.buildapi" 4 | backend-path = ["."] 5 | 6 | [project] 7 | name="flit_core" 8 | authors=[ 9 | {name = "Thomas Kluyver & contributors", email = "thomas@kluyver.me.uk"}, 10 | ] 11 | description = "Distribution-building parts of Flit. See flit package for more information" 12 | dependencies = [] 13 | requires-python = '>=3.8' 14 | readme = "README.rst" 15 | license = "BSD-3-Clause" 16 | license-files = ["LICENSE*", "flit_core/vendor/**/LICENSE*"] 17 | classifiers = [ 18 | "Topic :: Software Development :: Libraries :: Python Modules", 19 | ] 20 | dynamic = ["version"] 21 | 22 | [project.urls] 23 | Documentation = "https://flit.pypa.io" 24 | Source = "https://github.com/pypa/flit" 25 | 26 | [tool.flit.sdist] 27 | include = [ 28 | "bootstrap_install.py", 29 | "build_dists.py", 30 | "tests_core/", 31 | ] 32 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Röbin", email = "robin@camelot.uk"} 9 | ] 10 | maintainers = [ 11 | {name = "Sir Galahad"} 12 | ] 13 | readme = "README.rst" 14 | license = {file = "LICENSE"} 15 | requires-python = ">=3.7" 16 | dependencies = [ 17 | "requests >= 2.18", 18 | "docutils", 19 | ] 20 | keywords = ["example", "test"] 21 | dynamic = [ 22 | "version", 23 | "description", 24 | ] 25 | 26 | [project.optional-dependencies] 27 | test = [ 28 | "pytest", 29 | "mock; python_version<'3.6'" 30 | ] 31 | 32 | [project.urls] 33 | homepage = "http://github.com/sirrobin/module1" 34 | 35 | [project.entry-points.flit_test_example] 36 | foo = "module1:main" 37 | 38 | [tool.flit.module] 39 | name = "module1a" 40 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/README: -------------------------------------------------------------------------------- 1 | flit_core bundles the 'tomli' TOML parser, to avoid a 2 | [bootstrapping problem](https://github.com/pypa/packaging-problems/issues/342). 3 | tomli is packaged using Flit, so there would be a dependency cycle when building 4 | from source. Vendoring a copy of tomli avoids this. The code in tomli is under 5 | the MIT license, and the LICENSE file is in the .dist-info folder. 6 | 7 | If you want to unbundle tomli and rely on it as a separate package, you can 8 | replace the package with Python code doing 'from tomli import *'. You will 9 | probably need to work around the dependency cycle between flit_core and tomli. 10 | 11 | Bundling a TOML parser should be a special case - I don't plan on bundling 12 | anything else in flit_core (or depending on any other packages). 13 | I hope that a TOML parser will be added to the Python standard library, and then 14 | this bundled parser will go away. 15 | -------------------------------------------------------------------------------- /flit_core/tests_core/samples/pep621_license_files/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.2,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "module1" 7 | authors = [ 8 | {name = "Sir Röbin", email = "robin@camelot.uk"} 9 | ] 10 | maintainers = [ 11 | {name = "Sir Galahad"} 12 | ] 13 | readme = "README.rst" 14 | license-files = ["**/LICENSE*"] 15 | requires-python = ">=3.7" 16 | dependencies = [ 17 | "requests >= 2.18", 18 | "docutils", 19 | ] 20 | keywords = ["example", "test"] 21 | dynamic = [ 22 | "version", 23 | "description", 24 | ] 25 | 26 | [project.optional-dependencies] 27 | test = [ 28 | "pytest", 29 | "mock; python_version<'3.6'" 30 | ] 31 | 32 | [project.urls] 33 | homepage = "http://github.com/sirrobin/module1" 34 | 35 | [project.entry-points.flit_test_example] 36 | foo = "module1:main" 37 | 38 | [tool.flit.module] 39 | name = "module1a" 40 | -------------------------------------------------------------------------------- /flit/_get_dirs.py: -------------------------------------------------------------------------------- 1 | """get_dirs() is pulled out as a separate file so we can run it in a target Python. 2 | """ 3 | import os 4 | import sys 5 | import sysconfig 6 | 7 | def get_dirs(user=True): 8 | """Get the 'scripts' and 'purelib' directories we'll install into. 9 | 10 | This is now a thin wrapper around sysconfig.get_paths(). It's not inlined, 11 | because some tests mock it out to install to a different location. 12 | """ 13 | if user: 14 | if (sys.platform == "darwin") and sysconfig.get_config_var('PYTHONFRAMEWORK'): 15 | return sysconfig.get_paths('osx_framework_user') 16 | return sysconfig.get_paths(os.name + '_user') 17 | else: 18 | # The default scheme is 'posix_prefix' or 'nt', and should work for e.g. 19 | # installing into a virtualenv 20 | return sysconfig.get_paths() 21 | 22 | 23 | if __name__ == '__main__': 24 | import json 25 | user = '--user'in sys.argv 26 | dirs = get_dirs(user) 27 | json.dump(dirs, sys.stdout) 28 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{315,314,313,312,311,310,39,38},bootstrap 3 | skip_missing_interpreters = true 4 | 5 | [gh-actions] 6 | python = 7 | 3.8: py38, bootstrap 8 | 3.9: py39 9 | 3.10: py310 10 | 3.11: py311 11 | 3.12: py312 12 | 3.13: py313 13 | 3.14: py314 14 | 3.15: py315 15 | 16 | [testenv] 17 | deps = 18 | requests 19 | testpath 20 | responses 21 | docutils 22 | tomli;python_version < "3.11" 23 | tomli-w 24 | pytest>=2.7.3 25 | pytest-cov 26 | 27 | skip_install=true 28 | 29 | setenv = 30 | PYTHONPATH = flit_core 31 | 32 | commands = 33 | python -m pytest --cov=flit --cov=flit_core/flit_core {posargs} 34 | 35 | [testenv:bootstrap] 36 | skip_install = true 37 | # Make the install step a no-op, so nothing gets installed in the env 38 | install_command = true {packages} 39 | allowlist_externals = true 40 | changedir = flit_core 41 | commands = 42 | python -c "from flit_core.buildapi import build_wheel;\ 43 | from tempfile import mkdtemp;\ 44 | build_wheel(mkdtemp())" 45 | -------------------------------------------------------------------------------- /flit/license_templates/mit: -------------------------------------------------------------------------------- 1 | Copyright (c) {year} {author} 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/tomli-1.2.3.dist-info/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Taneli Hukkinen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["flit_core >=3.11,<4"] 3 | build-backend = "flit_core.buildapi" 4 | 5 | [project] 6 | name = "flit" 7 | authors = [ 8 | {name = "Thomas Kluyver", email = "thomas@kluyver.me.uk"}, 9 | ] 10 | dependencies = [ 11 | "flit_core >=3.12.0", 12 | "requests", 13 | "docutils", 14 | "tomli-w", 15 | "pip", 16 | ] 17 | requires-python = ">=3.8" 18 | readme = "README.rst" 19 | license = "BSD-3-Clause" 20 | license-files = ["LICENSE"] 21 | classifiers = ["Intended Audience :: Developers", 22 | "Programming Language :: Python :: 3", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ] 25 | dynamic = ['version', 'description'] 26 | 27 | [project.optional-dependencies] 28 | test = [ 29 | "testpath", 30 | "responses", 31 | "pytest>=2.7.3", 32 | "pytest-cov", 33 | "tomli", 34 | ] 35 | doc = [ 36 | "sphinx", 37 | "sphinxcontrib_github_alt", 38 | "pygments-github-lexers", # TOML highlighting 39 | ] 40 | 41 | [project.urls] 42 | Documentation = "https://flit.pypa.io" 43 | Source = "https://github.com/pypa/flit" 44 | Changelog = "https://flit.pypa.io/en/stable/history.html" 45 | 46 | [project.scripts] 47 | flit = "flit:main" 48 | -------------------------------------------------------------------------------- /bootstrap_dev.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Symlink install flit & flit_core for development. 4 | # Most projects can do the same with 'flit install --symlink'. 5 | # But that doesn't work until Flit is installed, so we need some bootstrapping. 6 | 7 | import argparse 8 | import logging 9 | import os 10 | from pathlib import Path 11 | import sys 12 | 13 | my_dir = Path(__file__).parent 14 | os.chdir(str(my_dir)) 15 | sys.path.insert(0, 'flit_core') 16 | 17 | from flit.install import Installer 18 | 19 | ap = argparse.ArgumentParser() 20 | ap.add_argument('--user') 21 | args = ap.parse_args() 22 | 23 | logging.basicConfig(level=logging.INFO) 24 | 25 | install_kwargs = {'symlink': True} 26 | if os.name == 'nt': 27 | # Use .pth files instead of symlinking on Windows 28 | install_kwargs = {'symlink': False, 'pth': True} 29 | 30 | # Install flit_core 31 | Installer.from_ini_path( 32 | my_dir / 'flit_core' / 'pyproject.toml', user=args.user, **install_kwargs 33 | ).install() 34 | print("Linked flit_core into site-packages.") 35 | 36 | # Install flit 37 | Installer.from_ini_path( 38 | my_dir / 'pyproject.toml', user=args.user, **install_kwargs 39 | ).install() 40 | print("Linked flit into site-packages.") 41 | -------------------------------------------------------------------------------- /flit_core/tests_core/test_versionno.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from flit_core.common import InvalidVersion 4 | from flit_core.versionno import normalise_version 5 | 6 | def test_normalise_version(): 7 | nv = normalise_version 8 | assert nv('4.3.1') == '4.3.1' 9 | assert nv('1.0b2') == '1.0b2' 10 | assert nv('2!1.3') == '2!1.3' 11 | 12 | # Prereleases 13 | assert nv('1.0B2') == '1.0b2' 14 | assert nv('1.0.b2') == '1.0b2' 15 | assert nv('1.0beta2') == '1.0b2' 16 | assert nv('1.01beta002') == '1.1b2' 17 | assert nv('1.0-preview2') == '1.0rc2' 18 | assert nv('1.0_c') == '1.0rc0' 19 | 20 | # Post releases 21 | assert nv('1.0post-2') == '1.0.post2' 22 | assert nv('1.0post') == '1.0.post0' 23 | assert nv('1.0-rev3') == '1.0.post3' 24 | assert nv('1.0-2') == '1.0.post2' 25 | 26 | # Development versions 27 | assert nv('1.0dev-2') == '1.0.dev2' 28 | assert nv('1.0dev') == '1.0.dev0' 29 | assert nv('1.0-dev3') == '1.0.dev3' 30 | 31 | assert nv('1.0+ubuntu-01') == '1.0+ubuntu.1' 32 | assert nv('v1.3-pre2') == '1.3rc2' 33 | assert nv(' 1.2.5.6\t') == '1.2.5.6' 34 | assert nv('1.0-alpha3-post02+ubuntu_xenial_5') == '1.0a3.post2+ubuntu.xenial.5' 35 | 36 | with pytest.raises(InvalidVersion): 37 | nv('3!') 38 | 39 | with pytest.raises(InvalidVersion): 40 | nv('abc') 41 | -------------------------------------------------------------------------------- /flit/vcs/hg.py: -------------------------------------------------------------------------------- 1 | import os 2 | from subprocess import check_output 3 | 4 | name = 'hg' 5 | 6 | def find_repo_root(directory): 7 | for p in [directory] + list(directory.parents): 8 | if (p / '.hg').is_dir(): 9 | return p 10 | 11 | def _repo_paths_to_directory_paths(paths, directory): 12 | # 'hg status' gives paths from repo root, which may not be our directory. 13 | directory = directory.resolve() 14 | repo = find_repo_root(directory) 15 | if directory != repo: 16 | directory_in_repo = str(directory.relative_to(repo)) + os.sep 17 | ix = len(directory_in_repo) 18 | paths = [p[ix:] for p in paths 19 | if os.path.normpath(p).startswith(directory_in_repo)] 20 | return paths 21 | 22 | 23 | def list_tracked_files(directory): 24 | outb = check_output(['hg', 'status', '--clean', '--added', '--modified', '--no-status'], 25 | cwd=str(directory)) 26 | paths = [os.fsdecode(l) for l in outb.strip().splitlines()] 27 | return _repo_paths_to_directory_paths(paths, directory) 28 | 29 | 30 | def list_untracked_deleted_files(directory): 31 | outb = check_output(['hg', 'status', '--unknown', '--deleted', '--no-status'], 32 | cwd=str(directory)) 33 | paths = [os.fsdecode(l) for l in outb.strip().splitlines()] 34 | return _repo_paths_to_directory_paths(paths, directory) 35 | -------------------------------------------------------------------------------- /tests/test_find_python_executable.py: -------------------------------------------------------------------------------- 1 | from os.path import isabs, basename, dirname 2 | import os 3 | import re 4 | import sys 5 | import venv 6 | 7 | import pytest 8 | 9 | from flit import PythonNotFoundError, find_python_executable 10 | 11 | 12 | def test_default(): 13 | assert find_python_executable(None) == sys.executable 14 | 15 | 16 | def test_self(): 17 | assert find_python_executable(sys.executable) == sys.executable 18 | 19 | 20 | def test_abs(): 21 | abs_path = "C:\\PythonXY\\python.exe" if os.name == 'nt' else '/usr/bin/python' 22 | assert find_python_executable(abs_path) == abs_path 23 | 24 | 25 | def test_find_in_path(): 26 | assert isabs(find_python_executable("python")) 27 | 28 | 29 | def test_env(tmp_path): 30 | path = tmp_path / "venv" 31 | venv.create(path) 32 | 33 | executable = find_python_executable(path) 34 | assert basename(dirname(dirname(executable))) == "venv" 35 | 36 | 37 | def test_env_abs(tmp_path, monkeypatch): 38 | path = tmp_path / "venv" 39 | venv.create(path) 40 | 41 | monkeypatch.chdir(tmp_path) 42 | assert isabs(find_python_executable("venv")) 43 | 44 | 45 | @pytest.mark.parametrize("bad_python_name", ["pyhton", "ls", "."]) 46 | def test_exception(bad_python_name: str): 47 | """Test that an appropriate exception (that contains the error string) is raised.""" 48 | with pytest.raises(PythonNotFoundError, match=re.escape(bad_python_name)): 49 | find_python_executable(bad_python_name) 50 | -------------------------------------------------------------------------------- /doc/reproducible.rst: -------------------------------------------------------------------------------- 1 | Reproducible builds 2 | =================== 3 | 4 | .. versionadded:: 0.8 5 | 6 | Wheels built by flit are reproducible: if you build from the same source code, 7 | you should be able to make wheels that are exactly identical, byte for byte. 8 | This is useful for verifying software. For more details, see 9 | `reproducible-builds.org `__. 10 | 11 | There is a caveat, however: wheels (which are zip files) include the 12 | modification timestamp from each file. This will 13 | probably be different on each computer, because it indicates when your local 14 | copy of the file was written, not when it was changed in version control. 15 | These timestamps can be overridden by the environment variable 16 | :envvar:`SOURCE_DATE_EPOCH`. 17 | 18 | .. code-block:: shell 19 | 20 | SOURCE_DATE_EPOCH=$(date +%s) 21 | flit publish 22 | # Record the value of SOURCE_DATE_EPOCH in release notes for reproduction 23 | 24 | .. versionchanged:: 0.12 25 | Normalising permission bits 26 | 27 | Flit normalises the permission bits of files copied into a wheel to either 28 | 755 (executable) or 644. This means that a file is readable by all users 29 | and writable only by the user who owns it. 30 | 31 | The most popular version control systems only track the executable bit, 32 | so checking out the same repository on systems with different umasks 33 | (e.g. Debian and Fedora) produces files with different permissions. With Flit 34 | 0.11 and earlier, this difference would produce non-identical wheels. 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Thomas Kluyver and contributors 2 | All rights reserved. 3 | 4 | BSD 3-clause license: 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its contributors 17 | may be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /flit_core/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Thomas Kluyver and contributors 2 | All rights reserved. 3 | 4 | BSD 3-clause license: 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation and/or 14 | other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its contributors 17 | may be used to endorse or promote products derived from this software without 18 | specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 24 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /flit_core/tests_core/test_build_thyself.py: -------------------------------------------------------------------------------- 1 | """Tests of flit_core building itself""" 2 | import os 3 | import os.path as osp 4 | import pytest 5 | import tarfile 6 | from testpath import assert_isdir, assert_isfile 7 | import zipfile 8 | 9 | from flit_core import buildapi 10 | 11 | @pytest.fixture() 12 | def cwd_project(): 13 | proj_dir = osp.dirname(osp.dirname(osp.abspath(buildapi.__file__))) 14 | if not osp.isfile(osp.join(proj_dir, 'pyproject.toml')): 15 | pytest.skip("need flit_core source directory") 16 | 17 | old_cwd = os.getcwd() 18 | try: 19 | os.chdir(proj_dir) 20 | yield 21 | finally: 22 | os.chdir(old_cwd) 23 | 24 | 25 | def test_prepare_metadata(tmp_path, cwd_project): 26 | tmp_path = str(tmp_path) 27 | dist_info = buildapi.prepare_metadata_for_build_wheel(tmp_path) 28 | 29 | assert dist_info.endswith('.dist-info') 30 | assert dist_info.startswith('flit_core') 31 | dist_info = osp.join(tmp_path, dist_info) 32 | assert_isdir(dist_info) 33 | 34 | assert_isfile(osp.join(dist_info, 'WHEEL')) 35 | assert_isfile(osp.join(dist_info, 'METADATA')) 36 | 37 | 38 | def test_wheel(tmp_path, cwd_project): 39 | tmp_path = str(tmp_path) 40 | filename = buildapi.build_wheel(tmp_path) 41 | 42 | assert filename.endswith('.whl') 43 | assert filename.startswith('flit_core') 44 | path = osp.join(tmp_path, filename) 45 | assert_isfile(path) 46 | assert zipfile.is_zipfile(path) 47 | 48 | 49 | def test_sdist(tmp_path, cwd_project): 50 | tmp_path = str(tmp_path) 51 | filename = buildapi.build_sdist(tmp_path) 52 | 53 | assert filename.endswith('.tar.gz') 54 | assert filename.startswith('flit_core') 55 | path = osp.join(tmp_path, filename) 56 | assert_isfile(path) 57 | assert tarfile.is_tarfile(path) 58 | -------------------------------------------------------------------------------- /flit_core/bootstrap_install.py: -------------------------------------------------------------------------------- 1 | """Install flit_core without using any other tools. 2 | 3 | Normally, you would install flit_core with pip like any other Python package. 4 | This script is meant to help with 'bootstrapping' other packaging 5 | systems, where you may need flit_core to build other packaging tools. 6 | 7 | Use 'python -m flit_core.wheel' to make a wheel, then: 8 | 9 | python bootstrap_install.py flit_core-3.6.0-py3-none-any.whl 10 | 11 | To install for something other than the Python running the script, pass a 12 | site-packages or equivalent directory with the --installdir option. 13 | """ 14 | import argparse 15 | import sys 16 | import sysconfig 17 | from pathlib import Path 18 | from zipfile import ZipFile 19 | 20 | def extract_wheel(whl_path, dest): 21 | print("Installing to", dest) 22 | with ZipFile(whl_path) as zf: 23 | zf.extractall(dest) 24 | 25 | if __name__ == "__main__": 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument( 28 | 'wheel', 29 | type=Path, 30 | help='flit_core wheel to install (.whl file)', 31 | ) 32 | purelib = Path(sysconfig.get_path('purelib')).resolve() 33 | parser.add_argument( 34 | '--installdir', 35 | '-i', 36 | type=Path, 37 | default=purelib, 38 | help=f'installdir directory (defaults to {purelib})', 39 | ) 40 | parser.add_argument( 41 | '--install-root', 42 | type=Path, 43 | default=None, 44 | help='if given, installdir is considered to be under this' 45 | ) 46 | 47 | args = parser.parse_args() 48 | 49 | if not args.wheel.name.startswith('flit_core-'): 50 | sys.exit("Use this script only for flit_core wheels") 51 | if args.install_root: 52 | installdir = args.install_root / args.installdir.relative_to("/") 53 | else: 54 | installdir = args.installdir 55 | 56 | installdir.mkdir(parents=True, exist_ok=True) 57 | extract_wheel(args.wheel, installdir) 58 | -------------------------------------------------------------------------------- /doc/bootstrap.rst: -------------------------------------------------------------------------------- 1 | Bootstrapping 2 | ============= 3 | 4 | Flit is itself packaged using Flit, as are some foundational packaging tools 5 | such as ``pep517``. So where can you start if you need to install everything 6 | from source? 7 | 8 | .. note:: 9 | 10 | For most users, ``pip`` handles all this automatically. You should only need 11 | to deal with this if you're building things entirely from scratch, such as 12 | putting Python packages into another package format. 13 | 14 | The key piece is ``flit_core``. This is a package which can build itself using 15 | nothing except Python and the standard library. From an unpacked source archive, 16 | you can make a wheel by running:: 17 | 18 | python -m flit_core.wheel 19 | 20 | And then you can install this wheel with the ``bootstrap_install.py`` script 21 | included in the sdist (or by unzipping it to the correct directory):: 22 | 23 | # Install to site-packages for this Python: 24 | python bootstrap_install.py dist/flit_core-*.whl 25 | 26 | # Install somewhere else: 27 | python bootstrap_install.py --installdir /path/to/site-packages dist/flit_core-*.whl 28 | 29 | .. note:: 30 | 31 | This note only applies if you need to unbundle or unvendor dependencies. 32 | 33 | ``flit_core`` unconditionally bundles the ``tomli`` TOML parser, 34 | to avoid a dependency cycle for older versions of Python that don't include 35 | the standard library :mod:`tomllib` module. 36 | 37 | The ``tomli`` library is only used on Python 3.10 and older, meaning that 38 | if you use Python 3.11 or newer you can simply remove the bundled ``tomli``. 39 | Otherwise, you must special-case installing ``flit_core`` and/or ``tomli`` 40 | to resolve the dependency cycle between the two packages. 41 | 42 | After ``flit_core``, I recommend that you get `installer 43 | `_ set up. You can use 44 | ``python -m flit_core.wheel`` again to make a wheel, and then use installer 45 | itself (from the source directory) to install it. 46 | 47 | After that, you probably want to get `build `_ 48 | and its dependencies installed as the goal of the bootstrapping phase. You can 49 | then use ``build`` to create wheels of any other Python packages, and 50 | ``installer`` to install them. 51 | -------------------------------------------------------------------------------- /flit_core/tests_core/test_wheel.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | from zipfile import ZipFile 3 | 4 | from testpath import assert_isfile 5 | 6 | from flit_core.wheel import make_wheel_in, main 7 | 8 | samples_dir = Path(__file__).parent / 'samples' 9 | 10 | def test_licenses_dir(tmp_path): 11 | # Smoketest for https://github.com/pypa/flit/issues/399 12 | info = make_wheel_in(samples_dir / 'inclusion' / 'pyproject.toml', tmp_path) 13 | assert_isfile(info.file) 14 | 15 | 16 | def test_source_date_epoch(tmp_path, monkeypatch): 17 | monkeypatch.setenv('SOURCE_DATE_EPOCH', '1633007882') 18 | info = make_wheel_in(samples_dir / 'pep621' / 'pyproject.toml', tmp_path) 19 | assert_isfile(info.file) 20 | # Minimum value for zip timestamps is 1980-1-1 21 | with ZipFile(info.file, 'r') as zf: 22 | assert zf.getinfo('module1a.py').date_time[:3] == (2021, 9, 30) 23 | 24 | 25 | def test_zero_timestamp(tmp_path, monkeypatch): 26 | monkeypatch.setenv('SOURCE_DATE_EPOCH', '0') 27 | info = make_wheel_in(samples_dir / 'pep621' / 'pyproject.toml', tmp_path) 28 | assert_isfile(info.file) 29 | # Minimum value for zip timestamps is 1980-1-1 30 | with ZipFile(info.file, 'r') as zf: 31 | assert zf.getinfo('module1a.py').date_time == (1980, 1, 1, 0, 0, 0) 32 | 33 | 34 | def test_main(tmp_path): 35 | main(['--outdir', str(tmp_path), str(samples_dir / 'pep621')]) 36 | wheels = list(tmp_path.glob('*.whl')) 37 | assert len(wheels) == 1 38 | # Minimum value for zip timestamps is 1980-1-1 39 | with ZipFile(wheels[0], 'r') as zf: 40 | assert 'module1a.py' in zf.namelist() 41 | 42 | 43 | def test_data_dir(tmp_path): 44 | info = make_wheel_in(samples_dir / 'with_data_dir' / 'pyproject.toml', tmp_path) 45 | assert_isfile(info.file) 46 | with ZipFile(info.file, 'r') as zf: 47 | assert 'module1-0.1.data/data/share/man/man1/foo.1' in zf.namelist() 48 | 49 | 50 | def test_license_files(tmp_path): 51 | info = make_wheel_in(samples_dir / 'pep621_license_files' / 'pyproject.toml', tmp_path) 52 | assert_isfile(info.file) 53 | with ZipFile(info.file, 'r') as zf: 54 | assert 'module1-0.1.dist-info/licenses/LICENSE' in zf.namelist() 55 | assert 'module1-0.1.dist-info/licenses/module/vendor/LICENSE_VENDOR' in zf.namelist() 56 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - "*" 9 | pull_request: 10 | 11 | concurrency: 12 | group: >- 13 | ${{ github.workflow }}- 14 | ${{ github.ref_type }}- 15 | ${{ github.event.pull_request.number || github.sha }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | test: 20 | runs-on: ${{ matrix.platform }} 21 | strategy: 22 | matrix: 23 | platform: 24 | - "ubuntu-latest" 25 | - "windows-latest" 26 | python-version: 27 | - "3.8" 28 | - "3.9" 29 | - "3.10" 30 | - "3.11" 31 | - "3.12" 32 | - "3.13" 33 | - "3.14" 34 | steps: 35 | - uses: actions/checkout@v6 36 | 37 | - name: Setup Python ${{ matrix.python-version }} 38 | uses: actions/setup-python@v6 39 | with: 40 | python-version: ${{ matrix.python-version }} 41 | 42 | - name: Install dependencies 43 | run: | 44 | python -m pip install --upgrade pip 45 | pip install tox tox-gh-actions codecov 46 | 47 | - name: Run tests 48 | run: tox 49 | 50 | - name: Codecov upload 51 | env: 52 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 53 | run: codecov 54 | 55 | packages: 56 | runs-on: ubuntu-latest 57 | needs: [test] 58 | permissions: 59 | id-token: write # OIDC for uploading to PyPI 60 | 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@v6 64 | 65 | - name: Set up Python 3.12 66 | uses: actions/setup-python@v6 67 | with: 68 | python-version: "3.12" 69 | 70 | - name: Build flit_core & flit packages 71 | run: | 72 | python flit_core/build_dists.py 73 | pip install requests docutils 74 | PYTHONPATH=flit_core/ python -m flit build 75 | # Copy flit_core packages to same location 76 | cp flit_core/dist/* dist/ 77 | 78 | - uses: actions/upload-artifact@v6 79 | with: 80 | name: packages 81 | path: ./dist/* 82 | 83 | - name: Publish package distributions to PyPI 84 | uses: pypa/gh-action-pypi-publish@release/v1 85 | if: ${{ startsWith(github.ref, 'refs/tags/') }} 86 | -------------------------------------------------------------------------------- /flit/build.py: -------------------------------------------------------------------------------- 1 | """flit build - build both wheel and sdist""" 2 | 3 | from contextlib import contextmanager 4 | import logging 5 | import os 6 | from pathlib import Path 7 | import tarfile 8 | from tempfile import TemporaryDirectory 9 | from types import SimpleNamespace 10 | import sys 11 | 12 | from .config import read_flit_config, ConfigError 13 | from .sdist import SdistBuilder 14 | from .wheel import make_wheel_in 15 | 16 | log = logging.getLogger(__name__) 17 | 18 | ALL_FORMATS = {'wheel', 'sdist'} 19 | 20 | @contextmanager 21 | def unpacked_tarball(path): 22 | tf = tarfile.open(str(path)) 23 | with TemporaryDirectory() as tmpdir: 24 | # Py >=3.12: restrict advanced tar features which sdists shouldn't use 25 | kw = {'filter': 'data'} if hasattr(tarfile, 'data_filter') else {} 26 | tf.extractall(tmpdir, **kw) 27 | files = os.listdir(tmpdir) 28 | assert len(files) == 1, files 29 | yield os.path.join(tmpdir, files[0]) 30 | 31 | def main(ini_file: Path, formats=None, use_vcs=True): 32 | """Build wheel and sdist""" 33 | if not formats: 34 | formats = ALL_FORMATS 35 | elif not formats.issubset(ALL_FORMATS): 36 | raise ValueError(f"Unknown package formats: {formats - ALL_FORMATS}") 37 | 38 | sdist_info = wheel_info = None 39 | dist_dir = ini_file.parent / 'dist' 40 | dist_dir.mkdir(parents=True, exist_ok=True) 41 | 42 | try: 43 | # Load the config file to make sure it gets validated 44 | read_flit_config(ini_file) 45 | 46 | if 'sdist' in formats: 47 | sb = SdistBuilder.from_ini_path(ini_file, use_vcs=use_vcs) 48 | sdist_file = sb.build(dist_dir) 49 | sdist_info = SimpleNamespace(builder=sb, file=sdist_file) 50 | # When we're building both, build the wheel from the unpacked sdist. 51 | # This helps ensure that the sdist contains all the necessary files. 52 | if 'wheel' in formats: 53 | with unpacked_tarball(sdist_file) as tmpdir: 54 | log.debug('Building wheel from unpacked sdist %s', tmpdir) 55 | tmp_ini_file = Path(tmpdir, ini_file.name) 56 | wheel_info = make_wheel_in(tmp_ini_file, dist_dir) 57 | elif 'wheel' in formats: 58 | wheel_info = make_wheel_in(ini_file, dist_dir) 59 | except ConfigError as e: 60 | sys.exit(f'Config error: {e}') 61 | 62 | return SimpleNamespace(wheel=wheel_info, sdist=sdist_info) 63 | -------------------------------------------------------------------------------- /flit/tomlify.py: -------------------------------------------------------------------------------- 1 | """Convert a flit.ini file to pyproject.toml 2 | """ 3 | import argparse 4 | from collections import OrderedDict 5 | import configparser 6 | import os 7 | from pathlib import Path 8 | import tomli_w 9 | 10 | from .config import metadata_list_fields 11 | 12 | 13 | TEMPLATE = """\ 14 | [build-system] 15 | requires = ["flit_core >=2,<4"] 16 | build-backend = "flit_core.buildapi" 17 | 18 | [tool.flit.metadata] 19 | {metadata} 20 | """ 21 | 22 | class CaseSensitiveConfigParser(configparser.ConfigParser): 23 | optionxform = staticmethod(str) 24 | 25 | def convert(path): 26 | cp = configparser.ConfigParser() 27 | with path.open(encoding='utf-8') as f: 28 | cp.read_file(f) 29 | 30 | ep_file = Path('entry_points.txt') 31 | metadata = OrderedDict() 32 | for name, value in cp['metadata'].items(): 33 | if name in metadata_list_fields: 34 | metadata[name] = [l for l in value.splitlines() if l.strip()] 35 | elif name == 'entry-points-file': 36 | ep_file = Path(value) 37 | else: 38 | metadata[name] = value 39 | 40 | if 'scripts' in cp: 41 | scripts = OrderedDict(cp['scripts']) 42 | else: 43 | scripts = {} 44 | 45 | entrypoints = CaseSensitiveConfigParser() 46 | if ep_file.is_file(): 47 | with ep_file.open(encoding='utf-8') as f: 48 | entrypoints.read_file(f) 49 | 50 | written_entrypoints = False 51 | with Path('pyproject.toml').open('w', encoding='utf-8') as f: 52 | f.write(TEMPLATE.format(metadata=tomli_w.dumps(metadata))) 53 | 54 | if scripts: 55 | f.write('\n[tool.flit.scripts]\n') 56 | f.write(tomli_w.dumps(scripts)) 57 | 58 | for groupname, group in entrypoints.items(): 59 | if not dict(group): 60 | continue 61 | 62 | if '.' in groupname: 63 | groupname = f'"{groupname}"' 64 | f.write(f'\n[tool.flit.entrypoints.{groupname}]\n') 65 | f.write(tomli_w.dumps(OrderedDict(group))) 66 | written_entrypoints = True 67 | 68 | print("Written 'pyproject.toml'") 69 | files = str(path) 70 | if written_entrypoints: 71 | files += ' and ' + str(ep_file) 72 | print("Please check the new file, then remove", files) 73 | 74 | def main(argv=None): 75 | ap = argparse.ArgumentParser() 76 | ap.add_argument('-f', '--ini-file', type=Path, default='flit.ini') 77 | args = ap.parse_args(argv) 78 | 79 | os.chdir(str(args.ini_file.parent)) 80 | convert(Path(args.ini_file.name)) 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | **Flit** is a simple way to put Python packages and modules on PyPI. 2 | It tries to require less thought about packaging and help you avoid common 3 | mistakes. 4 | See `Why use Flit? `_ for 5 | more about how it compares to other Python packaging tools. 6 | 7 | Install 8 | ------- 9 | 10 | :: 11 | 12 | $ python3 -m pip install flit 13 | 14 | Flit requires Python 3 and therefore needs to be installed using the Python 3 15 | version of pip. 16 | 17 | Python 2 modules can be distributed using Flit, but need to be importable on 18 | Python 3 without errors. 19 | 20 | Usage 21 | ----- 22 | 23 | Say you're writing a module ``foobar`` — either as a single file ``foobar.py``, 24 | or as a directory — and you want to distribute it. 25 | 26 | 1. Make sure that foobar's docstring starts with a one-line summary of what 27 | the module is, and that it has a ``__version__``: 28 | 29 | .. code-block:: python 30 | 31 | """An amazing sample package!""" 32 | 33 | __version__ = "0.1" 34 | 35 | 2. Install flit if you don't already have it:: 36 | 37 | python3 -m pip install flit 38 | 39 | 3. Run ``flit init`` in the directory containing the module to create a 40 | ``pyproject.toml`` file. It will look something like this: 41 | 42 | .. code-block:: ini 43 | 44 | [build-system] 45 | requires = ["flit_core >=3.2,<4"] 46 | build-backend = "flit_core.buildapi" 47 | 48 | [project] 49 | name = "foobar" 50 | authors = [{name = "Sir Robin", email = "robin@camelot.uk"}] 51 | dynamic = ["version", "description"] 52 | 53 | [project.urls] 54 | Home = "https://github.com/sirrobin/foobar" 55 | 56 | You can edit this file to add other metadata, for example to set up 57 | command line scripts. See the 58 | `pyproject.toml page `_ 59 | of the documentation. 60 | 61 | If you have already got a ``flit.ini`` file to use with older versions of 62 | Flit, convert it to ``pyproject.toml`` by running ``python3 -m flit.tomlify``. 63 | 64 | 4. Run this command to upload your code to PyPI:: 65 | 66 | flit publish 67 | 68 | Once your package is published, people can install it using *pip* just like 69 | any other package. In most cases, pip will download a 'wheel' package, a 70 | standard format it knows how to install. If you specifically ask pip to install 71 | an 'sdist' package, it will install and use Flit in a temporary environment. 72 | 73 | 74 | To install a package locally for development, run:: 75 | 76 | flit install [--symlink] [--python path/to/python] 77 | 78 | Flit packages a single importable module or package at a time, using the import 79 | name as the name on PyPI. All subpackages and data files within a package are 80 | included automatically. 81 | -------------------------------------------------------------------------------- /flit_core/tests_core/test_sdist.py: -------------------------------------------------------------------------------- 1 | from io import BytesIO 2 | import os.path as osp 3 | from pathlib import Path 4 | import tarfile 5 | from testpath import assert_isfile 6 | 7 | from flit_core import sdist 8 | 9 | samples_dir = Path(__file__).parent / 'samples' 10 | 11 | def test_make_sdist(tmp_path): 12 | # Smoke test of making a complete sdist 13 | builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'package1.toml') 14 | builder.build(tmp_path) 15 | assert_isfile(tmp_path / 'package1-0.1.tar.gz') 16 | 17 | 18 | def test_make_sdist_pep621(tmp_path): 19 | builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'pep621' / 'pyproject.toml') 20 | path = builder.build(tmp_path) 21 | assert path == tmp_path / 'module1-0.1.tar.gz' 22 | assert_isfile(path) 23 | 24 | 25 | def test_make_sdist_pep621_nodynamic(tmp_path): 26 | builder = sdist.SdistBuilder.from_ini_path( 27 | samples_dir / 'pep621_nodynamic' / 'pyproject.toml' 28 | ) 29 | path = builder.build(tmp_path) 30 | assert path == tmp_path / 'module1-0.3.tar.gz' 31 | assert_isfile(path) 32 | 33 | 34 | def test_clean_tarinfo(): 35 | with tarfile.open(mode='w', fileobj=BytesIO()) as tf: 36 | ti = tf.gettarinfo(str(samples_dir / 'module1.py')) 37 | cleaned = sdist.clean_tarinfo(ti, mtime=42) 38 | assert cleaned.uid == 0 39 | assert cleaned.uname == '' 40 | assert cleaned.mtime == 42 41 | 42 | 43 | def test_include_exclude(): 44 | builder = sdist.SdistBuilder.from_ini_path( 45 | samples_dir / 'inclusion' / 'pyproject.toml' 46 | ) 47 | files = builder.apply_includes_excludes(builder.select_files()) 48 | 49 | assert osp.join('doc', 'test.rst') in files 50 | assert osp.join('doc', 'test.txt') not in files 51 | assert osp.join('doc', 'subdir', 'test.txt') in files 52 | assert osp.join('doc', 'subdir', 'subsubdir', 'test.md') not in files 53 | 54 | 55 | def test_data_dir(): 56 | builder = sdist.SdistBuilder.from_ini_path( 57 | samples_dir / 'with_data_dir' / 'pyproject.toml' 58 | ) 59 | files = builder.apply_includes_excludes(builder.select_files()) 60 | 61 | assert osp.join('data', 'share', 'man', 'man1', 'foo.1') in files 62 | 63 | 64 | def test_pep625(tmp_path): 65 | builder = sdist.SdistBuilder.from_ini_path( 66 | samples_dir / 'normalization' / 'pyproject.toml' 67 | ) 68 | path = builder.build(tmp_path) 69 | assert path == tmp_path / 'my_python_module-0.0.1.tar.gz' 70 | assert_isfile(path) 71 | 72 | def test_license_inside_src(tmp_path): 73 | builder = sdist.SdistBuilder.from_ini_path( 74 | samples_dir / 'license_in_src' / 'pyproject.toml' 75 | ) 76 | path = builder.build(tmp_path) 77 | 78 | with tarfile.open(path, 'r|*') as tar: 79 | lic_count = sum( 80 | 1 for member in iter(tar.next, None) 81 | if member.name == 'license_in_src-0/license_in_src/lic.txt' 82 | ) 83 | 84 | assert lic_count == 1 85 | -------------------------------------------------------------------------------- /doc/rationale.rst: -------------------------------------------------------------------------------- 1 | Why use Flit? 2 | ============= 3 | 4 | *Make the easy things easy and the hard things possible* is an old motto from 5 | the Perl community. Flit is entirely focused on the *easy things* part of that, 6 | and leaves the hard things up to other tools. 7 | 8 | Specifically, the easy things are pure Python packages with no build steps 9 | (neither compiling C code, nor bundling Javascript, etc.). The vast majority of 10 | packages on PyPI are like this: plain Python code, with maybe some static data 11 | files like icons included. 12 | 13 | It's easy to underestimate the challenges involved in distributing and 14 | installing code, because it seems like you just need to copy some files into 15 | the right place. There's a whole lot of metadata and tooling that has to work 16 | together around that fundamental step. But with the right tooling, a developer 17 | who wants to release their code doesn't need to know about most of that. 18 | 19 | What, specifically, does Flit make easy? 20 | 21 | - ``flit init`` helps you set up the information Flit needs about your 22 | package. 23 | - Subpackages are automatically included: you only need to specify the 24 | top-level package. 25 | - Data files within a package directory are automatically included. 26 | Missing data files has been a common packaging mistake with other tools. 27 | - The version number is taken from your package's ``__version__`` attribute, 28 | so that always matches the version that tools like pip see. 29 | - ``flit publish`` uploads a package to PyPI, so you don't need a separate tool 30 | to do this. 31 | 32 | Setuptools, the most common tool for Python packaging, now has shortcuts for 33 | many of the same things. But it has to stay compatible with projects published 34 | many years ago, which limits what it can do by default. 35 | 36 | Flit also has some support for :doc:`reproducible builds `, 37 | a feature which some people care about. 38 | 39 | There have been many other efforts to improve the user experience of Python 40 | packaging, such as `pbr `_, but before Flit, 41 | these tended to build on setuptools and distutils. That was a pragmatic 42 | decision, but it's hard to build something radically different on top of those 43 | libraries. The existence of Flit spurred the development of new standards, 44 | like :pep:`518` and :pep:`517`, which are now used by other packaging tools 45 | such as `Poetry `_ and 46 | `Enscons `_. 47 | 48 | Other options 49 | ------------- 50 | 51 | If your package needs a build step, you won't be able to use Flit. 52 | `Setuptools `_ is the de-facto 53 | standard, but newer tools such as Enscons_ also cover this case. 54 | 55 | Flit also doesn't help you manage dependencies: you have to add them to 56 | ``pyproject.toml`` by hand. Tools like Poetry_ and `Pipenv 57 | `_ have features which help add and update 58 | dependencies on other packages. 59 | -------------------------------------------------------------------------------- /doc/upload.rst: -------------------------------------------------------------------------------- 1 | Controlling package uploads 2 | =========================== 3 | 4 | .. program:: flit publish 5 | 6 | The command ``flit publish`` will upload your package to a package index server. 7 | The default settings let you upload to `PyPI `_, 8 | the default Python Package Index, with a single user account. 9 | 10 | If you want to upload to other servers, or with more than one user account, 11 | or upload packages from a continuous integration job, 12 | you can configure Flit in two main ways: 13 | 14 | Using .pypirc 15 | ------------- 16 | 17 | You can create or edit a config file in your home directory, ``~/.pypirc`` that 18 | will be used by default or you can specify a custom location. 19 | This is also used by other Python tools such as `twine 20 | `_. 21 | 22 | For instance, to upload a package to the `Test PyPI server `_ 23 | instead of the normal PyPI, use a config file looking like this: 24 | 25 | .. code-block:: ini 26 | 27 | [distutils] 28 | index-servers = 29 | pypi 30 | testpypi 31 | 32 | [pypi] 33 | repository = https://upload.pypi.org/legacy/ 34 | username = sirrobin # Replace with your PyPI username 35 | 36 | [testpypi] 37 | repository = https://test.pypi.org/legacy/ 38 | username = sirrobin # Replace with your TestPyPI username 39 | 40 | You can select an index server from this config file with the 41 | :option:`--repository` option:: 42 | 43 | flit publish --repository testpypi 44 | 45 | If you don't use this option, 46 | Flit will use the server called ``pypi`` in the config file. If that doesn't 47 | exist, it uploads to PyPI at ``https://upload.pypi.org/legacy/`` by default. 48 | 49 | If you publish a package and you don't have a ``.pypirc`` file, Flit will create 50 | it to store your username. 51 | 52 | Flit tries to store your password securely using the 53 | `keyring `_ library. 54 | If keyring is not installed, Flit will ask for your password for each upload. 55 | Alternatively, you can also manually add your password to the ``.pypirc`` file 56 | (``password = ...``) 57 | 58 | .. _upload_envvars: 59 | 60 | Using environment variables 61 | --------------------------- 62 | 63 | You can specify a server to upload to with :envvar:`FLIT_INDEX_URL`, and 64 | pass credentials with :envvar:`FLIT_USERNAME` and :envvar:`FLIT_PASSWORD`. 65 | Environment variables take precedence over the config file, except if you use 66 | the :option:`--repository` option to explicitly pick a server from the config file. 67 | 68 | This can make it easier to automate uploads, for example to release packages 69 | from a continuous integration job. 70 | 71 | .. warning:: 72 | 73 | Storing a password in an environment variable is convenient, but it's 74 | `easy to accidentally leak it `_. 75 | Look out for scripts that helpfully print all environment variables for 76 | debugging, and remember that other scripts and libraries you run in 77 | that environment have access to your password. 78 | -------------------------------------------------------------------------------- /tests/test_sdist.py: -------------------------------------------------------------------------------- 1 | import ast 2 | from os.path import join as pjoin 3 | from pathlib import Path 4 | import pytest 5 | from shutil import which, copytree 6 | import sys 7 | import tarfile 8 | from tempfile import TemporaryDirectory 9 | from testpath import assert_isfile, MockCommand 10 | 11 | from flit import sdist, common 12 | 13 | samples_dir = Path(__file__).parent / 'samples' 14 | 15 | def test_auto_packages(): 16 | module = common.Module('package1', samples_dir / 'package1') 17 | packages, pkg_data = sdist.auto_packages(module) 18 | assert packages == ['package1', 'package1.subpkg', 'package1.subpkg2'] 19 | assert pkg_data == {'': ['*'], 20 | 'package1': ['data_dir/*'], 21 | 'package1.subpkg': ['sp_data_dir/*'], 22 | } 23 | 24 | def test_make_sdist(): 25 | # Smoke test of making a complete sdist 26 | if not which('git'): 27 | pytest.skip("requires git") 28 | builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'package1' / 'pyproject.toml') 29 | with TemporaryDirectory() as td: 30 | td = Path(td) 31 | builder.build(td) 32 | sdist_file = td / 'package1-0.1.tar.gz' 33 | assert_isfile(sdist_file) 34 | 35 | with tarfile.open(str(sdist_file)) as tf: 36 | assert 'package1-0.1/pyproject.toml' in tf.getnames() 37 | 38 | 39 | LIST_FILES = """\ 40 | #!{python} 41 | import sys 42 | from os.path import join 43 | if '--deleted' not in sys.argv: 44 | files = [ 45 | 'foo', 46 | join('dir1', 'bar'), 47 | join('dir1', 'subdir', 'qux'), 48 | join('dir2', 'abc'), 49 | join('dist', 'def'), 50 | ] 51 | mode = '{vcs}' 52 | if mode == 'git': 53 | print('\\0'.join(files), end='\\0') 54 | elif mode == 'hg': 55 | for f in files: 56 | print(f) 57 | """ 58 | 59 | LIST_FILES_GIT = LIST_FILES.format(python=sys.executable, vcs='git') 60 | LIST_FILES_HG = LIST_FILES.format(python=sys.executable, vcs='hg') 61 | 62 | 63 | def test_get_files_list_git(copy_sample): 64 | td = copy_sample('module1_toml') 65 | (td / '.git').mkdir() 66 | 67 | builder = sdist.SdistBuilder.from_ini_path(td / 'pyproject.toml') 68 | with MockCommand('git', LIST_FILES_GIT): 69 | files = builder.select_files() 70 | 71 | assert set(files) == { 72 | 'foo', pjoin('dir1', 'bar'), pjoin('dir1', 'subdir', 'qux'), 73 | pjoin('dir2', 'abc') 74 | } 75 | 76 | def test_get_files_list_hg(tmp_path): 77 | dir1 = tmp_path / 'dir1' 78 | copytree(str(samples_dir / 'module1_toml'), str(dir1)) 79 | (tmp_path / '.hg').mkdir() 80 | builder = sdist.SdistBuilder.from_ini_path(dir1 / 'pyproject.toml') 81 | with MockCommand('hg', LIST_FILES_HG): 82 | files = builder.select_files() 83 | 84 | assert set(files) == { 85 | 'bar', pjoin('subdir', 'qux') 86 | } 87 | 88 | def test_make_stubs_pkg(): 89 | builder = sdist.SdistBuilder.from_ini_path(samples_dir / 'sample-stubs' / 'pyproject.toml') 90 | with TemporaryDirectory() as td: 91 | td = Path(td) 92 | builder.build(td) 93 | sdist_file = td / 'wcwidth_stubs-0.2.13.1.tar.gz' 94 | assert_isfile(sdist_file) 95 | -------------------------------------------------------------------------------- /tests/test_build.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import pytest 3 | import shutil 4 | import sys 5 | from tempfile import TemporaryDirectory 6 | from testpath import assert_isdir, MockCommand 7 | 8 | from flit_core import common 9 | from flit import build 10 | 11 | samples_dir = Path(__file__).parent / 'samples' 12 | 13 | LIST_FILES_TEMPLATE = """\ 14 | #!{python} 15 | import sys 16 | from os.path import join 17 | if '--deleted' not in sys.argv: 18 | files = ['pyproject.toml', '{module}', 'EG_README.rst'] 19 | print('\\0'.join(files), end='\\0') 20 | """ 21 | 22 | def test_build_main(copy_sample): 23 | td = copy_sample('module1_toml') 24 | (td / '.git').mkdir() # Fake a git repo 25 | 26 | with MockCommand('git', LIST_FILES_TEMPLATE.format( 27 | python=sys.executable, module='module1.py')): 28 | res = build.main(td / 'pyproject.toml') 29 | assert res.wheel.file.suffix == '.whl' 30 | assert res.sdist.file.name.endswith('.tar.gz') 31 | 32 | assert_isdir(td / 'dist') 33 | 34 | def test_build_sdist_only(copy_sample): 35 | td = copy_sample('module1_toml') 36 | (td / '.git').mkdir() # Fake a git repo 37 | 38 | with MockCommand('git', LIST_FILES_TEMPLATE.format( 39 | python=sys.executable, module='module1.py')): 40 | res = build.main(td / 'pyproject.toml', formats={'sdist'}) 41 | assert res.wheel is None 42 | 43 | # Compare str path to work around pathlib/pathlib2 mismatch on Py 3.5 44 | assert [str(p) for p in (td / 'dist').iterdir()] == [str(res.sdist.file)] 45 | 46 | def test_build_wheel_only(copy_sample): 47 | td = copy_sample('module1_toml') 48 | (td / '.git').mkdir() # Fake a git repo 49 | 50 | with MockCommand('git', LIST_FILES_TEMPLATE.format( 51 | python=sys.executable, module='module1.py')): 52 | res = build.main(td / 'pyproject.toml', formats={'wheel'}) 53 | assert res.sdist is None 54 | 55 | # Compare str path to work around pathlib/pathlib2 mismatch on Py 3.5 56 | assert [str(p) for p in (td / 'dist').iterdir()] == [str(res.wheel.file)] 57 | 58 | def test_build_ns_main(copy_sample): 59 | td = copy_sample('ns1-pkg') 60 | (td / '.git').mkdir() # Fake a git repo 61 | 62 | with MockCommand('git', LIST_FILES_TEMPLATE.format( 63 | python=sys.executable, module='ns1/pkg/__init__.py')): 64 | res = build.main(td / 'pyproject.toml') 65 | assert res.wheel.file.suffix == '.whl' 66 | assert res.sdist.file.name.endswith('.tar.gz') 67 | 68 | assert_isdir(td / 'dist') 69 | 70 | 71 | def test_build_module_no_docstring(): 72 | with TemporaryDirectory() as td: 73 | pyproject = Path(td, 'pyproject.toml') 74 | shutil.copy(str(samples_dir / 'no_docstring-pkg.toml'), str(pyproject)) 75 | shutil.copy(str(samples_dir / 'no_docstring.py'), td) 76 | shutil.copy(str(samples_dir / 'EG_README.rst'), td) 77 | Path(td, '.git').mkdir() # Fake a git repo 78 | 79 | 80 | with MockCommand('git', LIST_FILES_TEMPLATE.format( 81 | python=sys.executable, module='no_docstring.py')): 82 | with pytest.raises(common.NoDocstringError) as exc_info: 83 | build.main(pyproject) 84 | assert 'no_docstring.py' in str(exc_info.value) 85 | -------------------------------------------------------------------------------- /flit_core/flit_core/vendor/tomli/_re.py: -------------------------------------------------------------------------------- 1 | from datetime import date, datetime, time, timedelta, timezone, tzinfo 2 | from functools import lru_cache 3 | import re 4 | from typing import Any, Optional, Union 5 | 6 | from ._types import ParseFloat 7 | 8 | # E.g. 9 | # - 00:32:00.999999 10 | # - 00:32:00 11 | _TIME_RE_STR = r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?" 12 | 13 | RE_NUMBER = re.compile( 14 | r""" 15 | 0 16 | (?: 17 | x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex 18 | | 19 | b[01](?:_?[01])* # bin 20 | | 21 | o[0-7](?:_?[0-7])* # oct 22 | ) 23 | | 24 | [+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part 25 | (?P 26 | (?:\.[0-9](?:_?[0-9])*)? # optional fractional part 27 | (?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part 28 | ) 29 | """, 30 | flags=re.VERBOSE, 31 | ) 32 | RE_LOCALTIME = re.compile(_TIME_RE_STR) 33 | RE_DATETIME = re.compile( 34 | fr""" 35 | ([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27 36 | (?: 37 | [Tt ] 38 | {_TIME_RE_STR} 39 | (?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset 40 | )? 41 | """, 42 | flags=re.VERBOSE, 43 | ) 44 | 45 | 46 | def match_to_datetime(match: "re.Match") -> Union[datetime, date]: 47 | """Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`. 48 | 49 | Raises ValueError if the match does not correspond to a valid date 50 | or datetime. 51 | """ 52 | ( 53 | year_str, 54 | month_str, 55 | day_str, 56 | hour_str, 57 | minute_str, 58 | sec_str, 59 | micros_str, 60 | zulu_time, 61 | offset_sign_str, 62 | offset_hour_str, 63 | offset_minute_str, 64 | ) = match.groups() 65 | year, month, day = int(year_str), int(month_str), int(day_str) 66 | if hour_str is None: 67 | return date(year, month, day) 68 | hour, minute, sec = int(hour_str), int(minute_str), int(sec_str) 69 | micros = int(micros_str.ljust(6, "0")) if micros_str else 0 70 | if offset_sign_str: 71 | tz: Optional[tzinfo] = cached_tz( 72 | offset_hour_str, offset_minute_str, offset_sign_str 73 | ) 74 | elif zulu_time: 75 | tz = timezone.utc 76 | else: # local date-time 77 | tz = None 78 | return datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz) 79 | 80 | 81 | @lru_cache(maxsize=None) 82 | def cached_tz(hour_str: str, minute_str: str, sign_str: str) -> timezone: 83 | sign = 1 if sign_str == "+" else -1 84 | return timezone( 85 | timedelta( 86 | hours=sign * int(hour_str), 87 | minutes=sign * int(minute_str), 88 | ) 89 | ) 90 | 91 | 92 | def match_to_localtime(match: "re.Match") -> time: 93 | hour_str, minute_str, sec_str, micros_str = match.groups() 94 | micros = int(micros_str.ljust(6, "0")) if micros_str else 0 95 | return time(int(hour_str), int(minute_str), int(sec_str), micros) 96 | 97 | 98 | def match_to_number(match: "re.Match", parse_float: "ParseFloat") -> Any: 99 | if match.group("floatpart"): 100 | return parse_float(match.group()) 101 | return int(match.group(), 0) 102 | -------------------------------------------------------------------------------- /flit_core/flit_core/buildapi.py: -------------------------------------------------------------------------------- 1 | """PEP-517 compliant buildsystem API""" 2 | import logging 3 | import os 4 | import os.path as osp 5 | from pathlib import Path 6 | 7 | from .common import ( 8 | Module, make_metadata, write_entry_points, dist_info_name, 9 | get_docstring_and_version_via_ast, 10 | ) 11 | from .config import read_flit_config 12 | from .wheel import make_wheel_in, _write_wheel_file 13 | from .sdist import SdistBuilder 14 | 15 | log = logging.getLogger(__name__) 16 | 17 | # PEP 517 specifies that the CWD will always be the source tree 18 | pyproj_toml = Path('pyproject.toml') 19 | 20 | def get_requires_for_build_wheel(config_settings=None): 21 | """Returns a list of requirements for building, as strings""" 22 | info = read_flit_config(pyproj_toml) 23 | # If we can get version & description from pyproject.toml (PEP 621), or 24 | # by parsing the module (_via_ast), we don't need any extra 25 | # dependencies. If not, we'll need to try importing it, so report any 26 | # runtime dependencies as build dependencies. 27 | docstring = None 28 | version = None 29 | want_summary = 'description' in info.dynamic_metadata 30 | want_version = 'version' in info.dynamic_metadata 31 | 32 | module = Module(info.module, Path.cwd()) 33 | if want_summary or want_version: 34 | docstring, version = get_docstring_and_version_via_ast(module) 35 | 36 | if (want_summary and not docstring) or (want_version and not version): 37 | return info.metadata.get('requires_dist', []) 38 | else: 39 | return [] 40 | 41 | # Requirements to build an sdist are the same as for a wheel 42 | get_requires_for_build_sdist = get_requires_for_build_wheel 43 | 44 | # Requirements to build an editable are the same as for a wheel 45 | get_requires_for_build_editable = get_requires_for_build_wheel 46 | 47 | def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): 48 | """Creates {metadata_directory}/foo-1.2.dist-info""" 49 | ini_info = read_flit_config(pyproj_toml) 50 | module = Module(ini_info.module, Path.cwd()) 51 | metadata = make_metadata(module, ini_info) 52 | 53 | dist_info = osp.join(metadata_directory, 54 | dist_info_name(metadata.name, metadata.version)) 55 | os.mkdir(dist_info) 56 | 57 | with open(osp.join(dist_info, 'WHEEL'), 'w', encoding='utf-8') as f: 58 | _write_wheel_file(f, supports_py2=metadata.supports_py2) 59 | 60 | with open(osp.join(dist_info, 'METADATA'), 'w', encoding='utf-8') as f: 61 | metadata.write_metadata_file(f) 62 | 63 | if ini_info.entrypoints: 64 | with open(osp.join(dist_info, 'entry_points.txt'), 'w', encoding='utf-8') as f: 65 | write_entry_points(ini_info.entrypoints, f) 66 | 67 | return osp.basename(dist_info) 68 | 69 | # Metadata for editable are the same as for a wheel 70 | prepare_metadata_for_build_editable = prepare_metadata_for_build_wheel 71 | 72 | def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): 73 | """Builds a wheel, places it in wheel_directory""" 74 | info = make_wheel_in(pyproj_toml, Path(wheel_directory)) 75 | return info.file.name 76 | 77 | def build_editable(wheel_directory, config_settings=None, metadata_directory=None): 78 | """Builds an "editable" wheel, places it in wheel_directory""" 79 | info = make_wheel_in(pyproj_toml, Path(wheel_directory), editable=True) 80 | return info.file.name 81 | 82 | def build_sdist(sdist_directory, config_settings=None): 83 | """Builds an sdist, places it in sdist_directory""" 84 | path = SdistBuilder.from_ini_path(pyproj_toml).build(Path(sdist_directory)) 85 | return path.name 86 | -------------------------------------------------------------------------------- /flit_core/tests_core/test_buildapi.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | import os 3 | import os.path as osp 4 | import tarfile 5 | from testpath import assert_isfile, assert_isdir 6 | from testpath.tempdir import TemporaryDirectory 7 | import zipfile 8 | 9 | from flit_core import buildapi 10 | 11 | samples_dir = osp.join(osp.dirname(__file__), 'samples') 12 | 13 | @contextmanager 14 | def cwd(directory): 15 | prev = os.getcwd() 16 | os.chdir(directory) 17 | try: 18 | yield 19 | finally: 20 | os.chdir(prev) 21 | 22 | def test_get_build_requires(): 23 | # This module can be inspected (for docstring & __version__) without 24 | # importing it, so there are no build dependencies. 25 | with cwd(osp.join(samples_dir,'pep517')): 26 | assert buildapi.get_requires_for_build_wheel() == [] 27 | assert buildapi.get_requires_for_build_editable() == [] 28 | assert buildapi.get_requires_for_build_sdist() == [] 29 | 30 | def test_get_build_requires_pep621_nodynamic(): 31 | # This module isn't inspected because version & description are specified 32 | # as static metadata in pyproject.toml, so there are no build dependencies 33 | with cwd(osp.join(samples_dir, 'pep621_nodynamic')): 34 | assert buildapi.get_requires_for_build_wheel() == [] 35 | assert buildapi.get_requires_for_build_editable() == [] 36 | assert buildapi.get_requires_for_build_sdist() == [] 37 | 38 | def test_get_build_requires_import(): 39 | # This one has to be imported, so its runtime dependencies are also 40 | # build dependencies. 41 | expected = ["numpy >=1.16.0"] 42 | with cwd(osp.join(samples_dir, 'constructed_version')): 43 | assert buildapi.get_requires_for_build_wheel() == expected 44 | assert buildapi.get_requires_for_build_editable() == expected 45 | assert buildapi.get_requires_for_build_sdist() == expected 46 | 47 | def test_build_wheel(): 48 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): 49 | filename = buildapi.build_wheel(td) 50 | assert filename.endswith('.whl'), filename 51 | assert_isfile(osp.join(td, filename)) 52 | assert zipfile.is_zipfile(osp.join(td, filename)) 53 | with zipfile.ZipFile(osp.join(td, filename)) as zip: 54 | assert "module1.py" in zip.namelist() 55 | assert "module1.pth" not in zip.namelist() 56 | 57 | def test_build_wheel_pep621(): 58 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir, 'pep621')): 59 | filename = buildapi.build_wheel(td) 60 | assert filename.endswith('.whl'), filename 61 | assert_isfile(osp.join(td, filename)) 62 | assert zipfile.is_zipfile(osp.join(td, filename)) 63 | 64 | def test_build_editable(): 65 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): 66 | filename = buildapi.build_editable(td) 67 | assert filename.endswith('.whl'), filename 68 | assert_isfile(osp.join(td, filename)) 69 | assert zipfile.is_zipfile(osp.join(td, filename)) 70 | with zipfile.ZipFile(osp.join(td, filename)) as zip: 71 | assert "module1.py" not in zip.namelist() 72 | assert "module1.pth" in zip.namelist() 73 | 74 | def test_build_sdist(): 75 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): 76 | filename = buildapi.build_sdist(td) 77 | assert filename.endswith('.tar.gz'), filename 78 | assert_isfile(osp.join(td, filename)) 79 | assert tarfile.is_tarfile(osp.join(td, filename)) 80 | 81 | def test_prepare_metadata_for_build_wheel(): 82 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): 83 | dirname = buildapi.prepare_metadata_for_build_wheel(td) 84 | assert dirname.endswith('.dist-info'), dirname 85 | assert_isdir(osp.join(td, dirname)) 86 | assert_isfile(osp.join(td, dirname, 'METADATA')) 87 | 88 | def test_prepare_metadata_for_build_editable(): 89 | with TemporaryDirectory() as td, cwd(osp.join(samples_dir,'pep517')): 90 | dirname = buildapi.prepare_metadata_for_build_editable(td) 91 | assert dirname.endswith('.dist-info'), dirname 92 | assert_isdir(osp.join(td, dirname)) 93 | assert_isfile(osp.join(td, dirname, 'METADATA')) 94 | -------------------------------------------------------------------------------- /flit/log.py: -------------------------------------------------------------------------------- 1 | """Nicer log formatting with colours. 2 | 3 | Code copied from Tornado, Apache licensed. 4 | """ 5 | # Copyright 2012 Facebook 6 | # 7 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 8 | # not use this file except in compliance with the License. You may obtain 9 | # a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, software 14 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 15 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 16 | # License for the specific language governing permissions and limitations 17 | # under the License. 18 | 19 | import logging 20 | import sys 21 | 22 | try: 23 | import curses 24 | except ImportError: 25 | curses = None 26 | 27 | def _stderr_supports_color(): 28 | color = False 29 | if curses and hasattr(sys.stderr, 'isatty') and sys.stderr.isatty(): 30 | try: 31 | curses.setupterm() 32 | if curses.tigetnum("colors") > 0: 33 | color = True 34 | except Exception: 35 | pass 36 | return color 37 | 38 | class LogFormatter(logging.Formatter): 39 | """Log formatter with colour support 40 | """ 41 | DEFAULT_COLORS = { 42 | logging.INFO: 2, # Green 43 | logging.WARNING: 3, # Yellow 44 | logging.ERROR: 1, # Red 45 | logging.CRITICAL: 1, 46 | } 47 | 48 | def __init__(self, color=True, datefmt=None): 49 | r""" 50 | :arg bool color: Enables color support. 51 | :arg string fmt: Log message format. 52 | It will be applied to the attributes dict of log records. The 53 | text between ``%(color)s`` and ``%(end_color)s`` will be colored 54 | depending on the level if color support is on. 55 | :arg dict colors: color mappings from logging level to terminal color 56 | code 57 | :arg string datefmt: Datetime format. 58 | Used for formatting ``(asctime)`` placeholder in ``prefix_fmt``. 59 | .. versionchanged:: 3.2 60 | Added ``fmt`` and ``datefmt`` arguments. 61 | """ 62 | logging.Formatter.__init__(self, datefmt=datefmt) 63 | self._colors = {} 64 | if color and _stderr_supports_color(): 65 | # The curses module has some str/bytes confusion in 66 | # python3. Until version 3.2.3, most methods return 67 | # bytes, but only accept strings. In addition, we want to 68 | # output these strings with the logging module, which 69 | # works with unicode strings. The explicit calls to 70 | # unicode() below are harmless in python2 but will do the 71 | # right conversion in python 3. 72 | fg_color = (curses.tigetstr("setaf") or 73 | curses.tigetstr("setf") or "") 74 | if (3, 0) < sys.version_info < (3, 2, 3): 75 | fg_color = str(fg_color, "ascii") 76 | 77 | for levelno, code in self.DEFAULT_COLORS.items(): 78 | self._colors[levelno] = str(curses.tparm(fg_color, code), "ascii") 79 | self._normal = str(curses.tigetstr("sgr0"), "ascii") 80 | 81 | scr = curses.initscr() 82 | self.termwidth = scr.getmaxyx()[1] 83 | curses.endwin() 84 | else: 85 | self._normal = '' 86 | # Default width is usually 80, but too wide is worse than too narrow 87 | self.termwidth = 70 88 | 89 | def formatMessage(self, record): 90 | l = len(record.message) 91 | right_text = f'{record.levelname[0]}-{record.name}' 92 | if l + len(right_text) < self.termwidth: 93 | space = ' ' * (self.termwidth - (l + len(right_text))) 94 | else: 95 | space = ' ' 96 | 97 | if record.levelno in self._colors: 98 | start_color = self._colors[record.levelno] 99 | end_color = self._normal 100 | else: 101 | start_color = end_color = '' 102 | 103 | return record.message + space + start_color + right_text + end_color 104 | 105 | def enable_colourful_output(level=logging.INFO): 106 | handler = logging.StreamHandler() 107 | handler.setFormatter(LogFormatter()) 108 | logging.root.addHandler(handler) 109 | logging.root.setLevel(level) 110 | -------------------------------------------------------------------------------- /flit/vendorized/readme/rst.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Donald Stufft 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import io 16 | from typing import Any, Dict, IO, Optional, Union 17 | 18 | from docutils.core import publish_parts 19 | from docutils.nodes import colspec, image 20 | from docutils.writers.html5_polyglot import HTMLTranslator, Writer 21 | from docutils.utils import SystemMessage 22 | 23 | from .clean import clean 24 | 25 | 26 | class ReadMeHTMLTranslator(HTMLTranslator): # type: ignore[misc] # docutils is incomplete, returns `Any` python/typeshed#7256 # noqa E501 27 | 28 | # Overrides base class not to output `` tag for SVG images. 29 | object_image_types: Dict[str, str] = {} 30 | 31 | def emptytag( 32 | self, 33 | node: Union[colspec, image], 34 | tagname: str, 35 | suffix: str = "\n", 36 | **attributes: Any 37 | ) -> Any: 38 | """Override this to add back the width/height attributes.""" 39 | if tagname == "img": 40 | if "width" in node: 41 | attributes["width"] = node["width"] 42 | if "height" in node: 43 | attributes["height"] = node["height"] 44 | 45 | return super().emptytag( 46 | node, tagname, suffix, **attributes 47 | ) 48 | 49 | 50 | SETTINGS = { 51 | # Cloaking email addresses provides a small amount of additional 52 | # privacy protection for email addresses inside of a chunk of ReST. 53 | "cloak_email_addresses": True, 54 | 55 | # Prevent a lone top level heading from being promoted to document 56 | # title, and thus second level headings from being promoted to top 57 | # level. 58 | "doctitle_xform": True, 59 | 60 | # Prevent a lone subsection heading from being promoted to section 61 | # title, and thus second level headings from being promoted to top 62 | # level. 63 | "sectsubtitle_xform": True, 64 | 65 | # Set our initial header level 66 | "initial_header_level": 2, 67 | 68 | # Prevent local files from being included into the rendered output. 69 | # This is a security concern because people can insert files 70 | # that are part of the system, such as /etc/passwd. 71 | "file_insertion_enabled": False, 72 | 73 | # Halt rendering and throw an exception if there was any errors or 74 | # warnings from docutils. 75 | "halt_level": 2, 76 | 77 | # Output math blocks as LaTeX that can be interpreted by MathJax for 78 | # a prettier display of Math formulas. 79 | # Pass a dummy path to supress docutils warning and emit HTML. 80 | "math_output": "MathJax /dummy.js", 81 | 82 | # Disable raw html as enabling it is a security risk, we do not want 83 | # people to be able to include any old HTML in the final output. 84 | "raw_enabled": False, 85 | 86 | # Disable all system messages from being reported. 87 | "report_level": 5, 88 | 89 | # Use typographic quotes, and transform --, ---, and ... into their 90 | # typographic counterparts. 91 | "smart_quotes": True, 92 | 93 | # Strip all comments from the rendered output. 94 | "strip_comments": True, 95 | 96 | # PATCH FOR FLIT ---------------------------------- 97 | # Disable syntax highlighting so we don't need Pygments installed. 98 | "syntax_highlight": "none", 99 | # ------------------------------------------------- 100 | 101 | # Maximum width (in characters) for one-column field names. 102 | # 0 means "no limit" 103 | "field_name_limit": 0, 104 | } 105 | 106 | 107 | def render( 108 | raw: str, 109 | stream: Optional[IO[str]] = None, 110 | **kwargs: Any 111 | ) -> Optional[str]: 112 | if stream is None: 113 | # Use a io.StringIO as the warning stream to prevent warnings from 114 | # being printed to sys.stderr. 115 | stream = io.StringIO() 116 | 117 | settings = SETTINGS.copy() 118 | settings["warning_stream"] = stream 119 | 120 | writer = Writer() 121 | writer.translator_class = ReadMeHTMLTranslator 122 | 123 | try: 124 | parts = publish_parts(raw, writer=writer, settings_overrides=settings) 125 | except SystemMessage: 126 | rendered = None 127 | else: 128 | rendered = parts.get("docinfo", "") + parts.get("fragment", "") 129 | 130 | if rendered: 131 | return clean(rendered) 132 | else: 133 | # If the warnings stream is empty, docutils had none, so add ours. 134 | if not stream.tell(): 135 | stream.write("No content rendered from RST source.") 136 | return None 137 | -------------------------------------------------------------------------------- /tests/test_upload.py: -------------------------------------------------------------------------------- 1 | from contextlib import contextmanager 2 | from tempfile import NamedTemporaryFile 3 | import os 4 | import io 5 | import pathlib 6 | 7 | import pytest 8 | import responses 9 | from testpath import modified_env 10 | from unittest.mock import patch 11 | 12 | from flit import upload 13 | from flit.build import ALL_FORMATS 14 | from flit.upload import get_repository, RepoDetails 15 | 16 | samples_dir = pathlib.Path(__file__).parent / 'samples' 17 | 18 | repo_settings = upload.RepoDetails( 19 | url=upload.PYPI, username='user', password='pw' 20 | ) 21 | 22 | pypirc1 = """ 23 | [distutils] 24 | index-servers = 25 | pypi 26 | 27 | [pypi] 28 | username: fred 29 | password: s3cret 30 | """ 31 | # That's not a real password. Well, hopefully not. 32 | 33 | @contextmanager 34 | def temp_pypirc(content): 35 | temp_file = NamedTemporaryFile("w+", delete=False) 36 | try: 37 | temp_file.write(content) 38 | temp_file.close() 39 | yield temp_file.name 40 | finally: 41 | os.unlink(temp_file.name) 42 | 43 | 44 | @responses.activate 45 | def test_upload(copy_sample): 46 | responses.add(responses.POST, upload.PYPI, status=200) 47 | td = copy_sample('module1_toml') 48 | 49 | with temp_pypirc(pypirc1) as pypirc, \ 50 | patch('flit.upload.get_repository', return_value=repo_settings): 51 | upload.main(td / 'pyproject.toml', repo_name='pypi', pypirc_path=pypirc) 52 | 53 | assert len(responses.calls) == 2 54 | 55 | def test_get_repository(): 56 | with temp_pypirc(pypirc1) as pypirc: 57 | repo = upload.get_repository(pypirc_path=pypirc, project_name='foo') 58 | assert repo.url == upload.PYPI 59 | assert repo.username == 'fred' 60 | assert repo.password == 's3cret' 61 | 62 | def test_get_repository_env(): 63 | with temp_pypirc(pypirc1) as pypirc, \ 64 | modified_env({ 65 | 'FLIT_INDEX_URL': 'https://pypi.example.com', 66 | 'FLIT_USERNAME': 'alice', 67 | 'FLIT_PASSWORD': 'p4ssword', # Also not a real password 68 | }): 69 | repo = upload.get_repository(pypirc_path=pypirc, project_name='foo') 70 | # Because we haven't specified a repo name, environment variables should 71 | # have higher priority than the config file. 72 | assert repo.url == 'https://pypi.example.com' 73 | assert repo.username == 'alice' 74 | assert repo.password == 'p4ssword' 75 | 76 | @contextmanager 77 | def _fake_keyring(d): 78 | class FakeKeyring: 79 | @staticmethod 80 | def get_password(service_name, username): 81 | return d.get(service_name, {}).get(username, None) 82 | 83 | class FakeKeyringErrMod: 84 | class KeyringError(Exception): 85 | pass 86 | 87 | with patch.dict('sys.modules', { 88 | 'keyring': FakeKeyring(), 'keyring.errors': FakeKeyringErrMod(), 89 | }): 90 | yield 91 | 92 | pypirc2 = """ 93 | [distutils] 94 | index-servers = 95 | pypi 96 | 97 | [pypi] 98 | username: fred 99 | """ 100 | 101 | def test_get_repository_keyring(monkeypatch): 102 | monkeypatch.delenv('FLIT_PASSWORD', raising=False) 103 | with _fake_keyring({upload.PYPI: {'fred': 'tops3cret'}}): 104 | repo = get_repository(pypirc_path=io.StringIO(pypirc2), project_name='foo') 105 | assert repo == RepoDetails(upload.PYPI, username='fred', password='tops3cret') 106 | 107 | for token_key in ['pypi_token:project:foo', 'pypi_token:user:fred']: 108 | with _fake_keyring({upload.PYPI: {token_key: 'xyz'}}): 109 | repo = get_repository(pypirc_path=io.StringIO(pypirc2), project_name='foo') 110 | assert repo == RepoDetails(upload.PYPI, username='__token__', password='xyz') 111 | 112 | 113 | pypirc3_repo = "https://invalid-repo.inv" 114 | pypirc3_user = "test" 115 | pypirc3_pass = "not_a_real_password" 116 | pypirc3 = f""" 117 | [distutils] = 118 | index-servers = 119 | test123 120 | 121 | [test123] 122 | repository: {pypirc3_repo} 123 | username: {pypirc3_user} 124 | password: {pypirc3_pass} 125 | """ 126 | 127 | 128 | def test_upload_pypirc_file(copy_sample): 129 | with temp_pypirc(pypirc3) as pypirc, \ 130 | patch("flit.upload.upload_file") as upload_file: 131 | td = copy_sample("module1_toml") 132 | formats = list(ALL_FORMATS)[:1] 133 | upload.main( 134 | td / "pyproject.toml", 135 | formats=set(formats), 136 | repo_name="test123", 137 | pypirc_path=pypirc, 138 | ) 139 | _, _, repo = upload_file.call_args[0] 140 | 141 | assert repo.url == pypirc3_repo 142 | assert repo.username == pypirc3_user 143 | assert repo.password == pypirc3_pass 144 | 145 | 146 | def test_upload_invalid_pypirc_file(copy_sample): 147 | with patch("flit.upload.upload_file"): 148 | td = copy_sample("module1_toml") 149 | formats = list(ALL_FORMATS)[:1] 150 | with pytest.raises(FileNotFoundError): 151 | upload.main( 152 | td / "pyproject.toml", 153 | formats=set(formats), 154 | repo_name="test123", 155 | pypirc_path="./file.invalid", 156 | ) 157 | -------------------------------------------------------------------------------- /flit_core/flit_core/versionno.py: -------------------------------------------------------------------------------- 1 | """Normalise version number according to PEP 440""" 2 | import logging 3 | import os 4 | import re 5 | 6 | log = logging.getLogger(__name__) 7 | 8 | # Regex below from packaging, via PEP 440. BSD License: 9 | # Copyright (c) Donald Stufft and individual contributors. 10 | # All rights reserved. 11 | # 12 | # Redistribution and use in source and binary forms, with or without 13 | # modification, are permitted provided that the following conditions are met: 14 | # 15 | # 1. Redistributions of source code must retain the above copyright notice, 16 | # this list of conditions and the following disclaimer. 17 | # 18 | # 2. Redistributions in binary form must reproduce the above copyright 19 | # notice, this list of conditions and the following disclaimer in the 20 | # documentation and/or other materials provided with the distribution. 21 | # 22 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | VERSION_PERMISSIVE = re.compile(r""" 33 | \s*v? 34 | (?: 35 | (?:(?P[0-9]+)!)? # epoch 36 | (?P[0-9]+(?:\.[0-9]+)*) # release segment 37 | (?P
                                          # pre-release
 38 |             [-_\.]?
 39 |             (?P(a|b|c|rc|alpha|beta|pre|preview))
 40 |             [-_\.]?
 41 |             (?P[0-9]+)?
 42 |         )?
 43 |         (?P                                         # post release
 44 |             (?:-(?P[0-9]+))
 45 |             |
 46 |             (?:
 47 |                 [-_\.]?
 48 |                 (?Ppost|rev|r)
 49 |                 [-_\.]?
 50 |                 (?P[0-9]+)?
 51 |             )
 52 |         )?
 53 |         (?P                                          # dev release
 54 |             [-_\.]?
 55 |             (?Pdev)
 56 |             [-_\.]?
 57 |             (?P[0-9]+)?
 58 |         )?
 59 |     )
 60 |     (?:\+(?P[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?       # local version
 61 | \s*$""", re.VERBOSE)
 62 | 
 63 | pre_spellings = {
 64 |     'a': 'a', 'alpha': 'a',
 65 |     'b': 'b', 'beta': 'b',
 66 |     'rc': 'rc', 'c': 'rc', 'pre': 'rc', 'preview': 'rc',
 67 | }
 68 | 
 69 | def normalise_version(orig_version):
 70 |     """Normalise version number according to rules in PEP 440
 71 | 
 72 |     Raises InvalidVersion if the version does not match PEP 440. This can be
 73 |     overridden with the FLIT_ALLOW_INVALID environment variable.
 74 | 
 75 |     https://www.python.org/dev/peps/pep-0440/#normalization
 76 |     """
 77 |     version = orig_version.lower()
 78 |     m = VERSION_PERMISSIVE.match(version)
 79 |     if not m:
 80 |         if os.environ.get('FLIT_ALLOW_INVALID'):
 81 |             log.warning("Invalid version number %r allowed by FLIT_ALLOW_INVALID",
 82 |                         orig_version)
 83 |             return version
 84 |         else:
 85 |             from .common import InvalidVersion
 86 |             raise InvalidVersion(f"Version number {orig_version!r} does not match PEP 440 rules")
 87 | 
 88 |     components = []
 89 |     add = components.append
 90 | 
 91 |     epoch, release = m.group('epoch', 'release')
 92 |     if epoch is not None:
 93 |         add(f'{int(epoch)}!')
 94 |     add('.'.join(str(int(rp)) for rp in release.split('.')))
 95 | 
 96 |     pre_l, pre_n = m.group('pre_l', 'pre_n')
 97 |     if pre_l is not None:
 98 |         pre_l = pre_spellings[pre_l]
 99 |         pre_n = '0' if pre_n is None else str(int(pre_n))
100 |         add(pre_l + pre_n)
101 | 
102 |     post_n1, post_l, post_n2 = m.group('post_n1', 'post_l', 'post_n2')
103 |     if post_n1 is not None:
104 |         add('.post' + str(int(post_n1)))
105 |     elif post_l is not None:
106 |         post_n = '0' if post_n2 is None else str(int(post_n2))
107 |         add('.post' + str(int(post_n)))
108 | 
109 |     dev_l, dev_n = m.group('dev_l', 'dev_n')
110 |     if dev_l is not None:
111 |         dev_n = '0' if dev_n is None else str(int(dev_n))
112 |         add('.dev' + dev_n)
113 | 
114 |     local = m.group('local')
115 |     if local is not None:
116 |         local = local.replace('-', '.').replace('_', '.')
117 |         l = [str(int(c)) if c.isdigit() else c
118 |              for c in local.split('.')]
119 |         add('+' + '.'.join(l))
120 | 
121 |     version = ''.join(components)
122 |     if version != orig_version:
123 |         log.warning("Version number normalised: %r -> %r (see PEP 440)",
124 |                     orig_version, version)
125 |     return version
126 | 


--------------------------------------------------------------------------------
/flit/sdist.py:
--------------------------------------------------------------------------------
  1 | from collections import defaultdict
  2 | import io
  3 | import logging
  4 | import os
  5 | from pathlib import Path
  6 | from posixpath import join as pjoin
  7 | from pprint import pformat
  8 | import tarfile
  9 | 
 10 | from flit_core.sdist import SdistBuilder as SdistBuilderCore
 11 | from flit_core.common import Module, VCSError
 12 | from flit.vcs import identify_vcs
 13 | 
 14 | log = logging.getLogger(__name__)
 15 | 
 16 | # Our generated setup.py deliberately loads distutils, not setuptools, to
 17 | # discourage running it directly and getting a setuptools mess. Tools like pip
 18 | # handle this correctly - loading setuptools anyway but avoiding its issues.
 19 | 
 20 | SETUP = """\
 21 | #!/usr/bin/env python
 22 | # setup.py generated by flit for tools that don't yet use PEP 517
 23 | 
 24 | from distutils.core import setup
 25 | 
 26 | {before}
 27 | setup(name={name!r},
 28 |       version={version!r},
 29 |       description={description!r},
 30 |       author={author!r},
 31 |       author_email={author_email!r},
 32 |       url={url!r},
 33 |       {extra}
 34 |      )
 35 | """
 36 | 
 37 | 
 38 | 
 39 | 
 40 | def namespace_packages(module: Module):
 41 |     """Get parent package names"""
 42 |     name_parts = []
 43 |     for part in module.namespace_package_name.split('.'):
 44 |         name_parts.append(part)
 45 |         yield '.'.join(name_parts)
 46 | 
 47 | 
 48 | def auto_packages(module: Module):
 49 |     """Discover subpackages and package_data"""
 50 |     pkgdir = os.path.normpath(str(module.path))
 51 |     pkg_name = module.name
 52 | 
 53 |     packages = []
 54 |     if module.in_namespace_package:
 55 |         packages.extend(namespace_packages(module))
 56 |     packages.append(pkg_name)
 57 | 
 58 |     pkg_data = defaultdict(list)
 59 |     # Undocumented distutils feature: the empty string matches all package names
 60 |     pkg_data[''].append('*')
 61 | 
 62 |     subpkg_paths = set()
 63 | 
 64 |     def find_nearest_pkg(rel_path):
 65 |         parts = rel_path.split(os.sep)
 66 |         for i in reversed(range(1, len(parts))):
 67 |             ancestor = '/'.join(parts[:i])
 68 |             if ancestor in subpkg_paths:
 69 |                 pkg = '.'.join([pkg_name] + parts[:i])
 70 |                 return pkg, '/'.join(parts[i:])
 71 | 
 72 |         # Relative to the top-level package
 73 |         return pkg_name, rel_path
 74 | 
 75 |     for path, dirnames, filenames in os.walk(pkgdir, topdown=True):
 76 |         if os.path.basename(path) == '__pycache__':
 77 |             continue
 78 | 
 79 |         from_top_level = os.path.relpath(path, pkgdir)
 80 |         if from_top_level == '.':
 81 |             continue
 82 | 
 83 |         is_subpkg = '__init__.py' in filenames
 84 |         if is_subpkg:
 85 |             subpkg_paths.add(from_top_level)
 86 |             parts = from_top_level.split(os.sep)
 87 |             packages.append('.'.join([pkg_name] + parts))
 88 |         else:
 89 |             pkg, from_nearest_pkg = find_nearest_pkg(from_top_level)
 90 |             pkg_data[pkg].append(pjoin(from_nearest_pkg, '*'))
 91 | 
 92 |     # Sort values in pkg_data
 93 |     pkg_data = {k: sorted(v) for (k, v) in pkg_data.items()}
 94 | 
 95 |     return sorted(packages), pkg_data
 96 | 
 97 | 
 98 | def include_path(p):
 99 |     return not (p.startswith('dist' + os.sep)
100 |                 or (os.sep+'__pycache__' in p)
101 |                 or p.endswith('.pyc'))
102 | 
103 | 
104 | def _parse_req(requires_dist):
105 |     """Parse "Foo (v); python_version == '2.x'" from Requires-Dist
106 | 
107 |     Returns pip-style appropriate for requirements.txt.
108 |     """
109 |     if ';' in requires_dist:
110 |         name_version, env_mark = requires_dist.split(';', 1)
111 |         env_mark = env_mark.strip()
112 |     else:
113 |         name_version, env_mark = requires_dist, None
114 | 
115 |     if '(' in name_version:
116 |         # turn 'name (X)' and 'name ('):
122 |             version = '==' + version
123 |         name_version = name + version
124 | 
125 |     return name_version, env_mark
126 | 
127 | 
128 | def convert_requires(reqs_by_extra):
129 |     """Regroup requirements by (extra, env_mark)"""
130 |     grouping = defaultdict(list)
131 |     for extra, reqs in reqs_by_extra.items():
132 |         for req in reqs:
133 |             name_version, env_mark = _parse_req(req)
134 |             grouping[(extra, env_mark)].append(name_version)
135 | 
136 |     install_reqs = grouping.pop(('.none',  None), [])
137 |     extra_reqs = {}
138 |     for (extra, env_mark), reqs in grouping.items():
139 |         if extra == '.none':
140 |             extra = ''
141 |         if env_mark is None:
142 |             extra_reqs[extra] = reqs
143 |         else:
144 |             extra_reqs[extra + ':' + env_mark] = reqs
145 | 
146 |     return install_reqs, extra_reqs
147 | 
148 | 
149 | class SdistBuilder(SdistBuilderCore):
150 |     """Build a complete sdist
151 | 
152 |     This extends the minimal sdist-building in flit_core:
153 | 
154 |     - Include any files tracked in version control, such as docs sources and
155 |       tests.
156 |     - Add a generated setup.py for compatibility with tools which don't yet know
157 |       about PEP 517.
158 |     """
159 |     use_vcs = True
160 | 
161 |     @classmethod
162 |     def from_ini_path(cls, ini_path: Path, use_vcs=True):
163 |         inst = super().from_ini_path(ini_path)
164 |         inst.use_vcs = use_vcs
165 |         return inst
166 | 
167 |     def select_files(self):
168 |         if not self.use_vcs:
169 |             return super().select_files()
170 | 
171 |         vcs_mod = identify_vcs(self.cfgdir)
172 |         if vcs_mod is not None:
173 |             untracked_deleted = vcs_mod.list_untracked_deleted_files(self.cfgdir)
174 |             if any(include_path(p) and not self.excludes.match_file(p)
175 |                    for p in untracked_deleted):
176 |                 raise VCSError(
177 |                     "Untracked or deleted files in the source directory. "
178 |                     "Commit, undo or ignore these files in your VCS.",
179 |                     self.cfgdir)
180 | 
181 |             files = [os.path.normpath(p)
182 |                      for p in vcs_mod.list_tracked_files(self.cfgdir)]
183 |             files = sorted(filter(include_path, files))
184 |             log.info("Found %d files tracked in %s", len(files), vcs_mod.name)
185 |         else:
186 |             files = super().select_files()
187 | 
188 |         return files
189 | 


--------------------------------------------------------------------------------
/doc/Makefile:
--------------------------------------------------------------------------------
  1 | # Makefile for Sphinx documentation
  2 | #
  3 | 
  4 | # You can set these variables from the command line.
  5 | SPHINXOPTS    =
  6 | SPHINXBUILD   = sphinx-build
  7 | PAPER         =
  8 | BUILDDIR      = _build
  9 | 
 10 | # User-friendly check for sphinx-build
 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
 12 | $(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/)
 13 | endif
 14 | 
 15 | # Internal variables.
 16 | PAPEROPT_a4     = -D latex_paper_size=a4
 17 | PAPEROPT_letter = -D latex_paper_size=letter
 18 | ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 19 | # the i18n builder cannot share the environment and doctrees with the others
 20 | I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
 21 | 
 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
 23 | 
 24 | help:
 25 | 	@echo "Please use \`make ' where  is one of"
 26 | 	@echo "  html       to make standalone HTML files"
 27 | 	@echo "  dirhtml    to make HTML files named index.html in directories"
 28 | 	@echo "  singlehtml to make a single large HTML file"
 29 | 	@echo "  pickle     to make pickle files"
 30 | 	@echo "  json       to make JSON files"
 31 | 	@echo "  htmlhelp   to make HTML files and a HTML help project"
 32 | 	@echo "  qthelp     to make HTML files and a qthelp project"
 33 | 	@echo "  devhelp    to make HTML files and a Devhelp project"
 34 | 	@echo "  epub       to make an epub"
 35 | 	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
 36 | 	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
 37 | 	@echo "  latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
 38 | 	@echo "  text       to make text files"
 39 | 	@echo "  man        to make manual pages"
 40 | 	@echo "  texinfo    to make Texinfo files"
 41 | 	@echo "  info       to make Texinfo files and run them through makeinfo"
 42 | 	@echo "  gettext    to make PO message catalogs"
 43 | 	@echo "  changes    to make an overview of all changed/added/deprecated items"
 44 | 	@echo "  xml        to make Docutils-native XML files"
 45 | 	@echo "  pseudoxml  to make pseudoxml-XML files for display purposes"
 46 | 	@echo "  linkcheck  to check all external links for integrity"
 47 | 	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
 48 | 
 49 | clean:
 50 | 	rm -rf $(BUILDDIR)/*
 51 | 
 52 | html:
 53 | 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 54 | 	@echo
 55 | 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 56 | 
 57 | dirhtml:
 58 | 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 59 | 	@echo
 60 | 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
 61 | 
 62 | singlehtml:
 63 | 	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
 64 | 	@echo
 65 | 	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
 66 | 
 67 | pickle:
 68 | 	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
 69 | 	@echo
 70 | 	@echo "Build finished; now you can process the pickle files."
 71 | 
 72 | json:
 73 | 	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
 74 | 	@echo
 75 | 	@echo "Build finished; now you can process the JSON files."
 76 | 
 77 | htmlhelp:
 78 | 	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
 79 | 	@echo
 80 | 	@echo "Build finished; now you can run HTML Help Workshop with the" \
 81 | 	      ".hhp project file in $(BUILDDIR)/htmlhelp."
 82 | 
 83 | qthelp:
 84 | 	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
 85 | 	@echo
 86 | 	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
 87 | 	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
 88 | 	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Flit.qhcp"
 89 | 	@echo "To view the help file:"
 90 | 	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Flit.qhc"
 91 | 
 92 | devhelp:
 93 | 	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
 94 | 	@echo
 95 | 	@echo "Build finished."
 96 | 	@echo "To view the help file:"
 97 | 	@echo "# mkdir -p $$HOME/.local/share/devhelp/Flit"
 98 | 	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Flit"
 99 | 	@echo "# devhelp"
100 | 
101 | epub:
102 | 	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | 	@echo
104 | 	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 | 
106 | latex:
107 | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | 	@echo
109 | 	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | 	@echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | 	      "(use \`make latexpdf' here to do that automatically)."
112 | 
113 | latexpdf:
114 | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | 	@echo "Running LaTeX files through pdflatex..."
116 | 	$(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 | 
119 | latexpdfja:
120 | 	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | 	@echo "Running LaTeX files through platex and dvipdfmx..."
122 | 	$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | 	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 | 
125 | text:
126 | 	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | 	@echo
128 | 	@echo "Build finished. The text files are in $(BUILDDIR)/text."
129 | 
130 | man:
131 | 	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | 	@echo
133 | 	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 | 
135 | texinfo:
136 | 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | 	@echo
138 | 	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | 	@echo "Run \`make' in that directory to run these through makeinfo" \
140 | 	      "(use \`make info' here to do that automatically)."
141 | 
142 | info:
143 | 	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | 	@echo "Running Texinfo files through makeinfo..."
145 | 	make -C $(BUILDDIR)/texinfo info
146 | 	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 | 
148 | gettext:
149 | 	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | 	@echo
151 | 	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 | 
153 | changes:
154 | 	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | 	@echo
156 | 	@echo "The overview file is in $(BUILDDIR)/changes."
157 | 
158 | linkcheck:
159 | 	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | 	@echo
161 | 	@echo "Link check complete; look for any errors in the above output " \
162 | 	      "or in $(BUILDDIR)/linkcheck/output.txt."
163 | 
164 | doctest:
165 | 	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | 	@echo "Testing of doctests in the sources finished, look at the " \
167 | 	      "results in $(BUILDDIR)/doctest/output.txt."
168 | 
169 | xml:
170 | 	$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | 	@echo
172 | 	@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 | 
174 | pseudoxml:
175 | 	$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | 	@echo
177 | 	@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 | 


--------------------------------------------------------------------------------
/doc/make.bat:
--------------------------------------------------------------------------------
  1 | @ECHO OFF
  2 | 
  3 | REM Command file for Sphinx documentation
  4 | 
  5 | if "%SPHINXBUILD%" == "" (
  6 | 	set SPHINXBUILD=sphinx-build
  7 | )
  8 | set BUILDDIR=_build
  9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
 10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
 11 | if NOT "%PAPER%" == "" (
 12 | 	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
 13 | 	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
 14 | )
 15 | 
 16 | if "%1" == "" goto help
 17 | 
 18 | if "%1" == "help" (
 19 | 	:help
 20 | 	echo.Please use `make ^` where ^ is one of
 21 | 	echo.  html       to make standalone HTML files
 22 | 	echo.  dirhtml    to make HTML files named index.html in directories
 23 | 	echo.  singlehtml to make a single large HTML file
 24 | 	echo.  pickle     to make pickle files
 25 | 	echo.  json       to make JSON files
 26 | 	echo.  htmlhelp   to make HTML files and a HTML help project
 27 | 	echo.  qthelp     to make HTML files and a qthelp project
 28 | 	echo.  devhelp    to make HTML files and a Devhelp project
 29 | 	echo.  epub       to make an epub
 30 | 	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
 31 | 	echo.  text       to make text files
 32 | 	echo.  man        to make manual pages
 33 | 	echo.  texinfo    to make Texinfo files
 34 | 	echo.  gettext    to make PO message catalogs
 35 | 	echo.  changes    to make an overview over all changed/added/deprecated items
 36 | 	echo.  xml        to make Docutils-native XML files
 37 | 	echo.  pseudoxml  to make pseudoxml-XML files for display purposes
 38 | 	echo.  linkcheck  to check all external links for integrity
 39 | 	echo.  doctest    to run all doctests embedded in the documentation if enabled
 40 | 	goto end
 41 | )
 42 | 
 43 | if "%1" == "clean" (
 44 | 	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
 45 | 	del /q /s %BUILDDIR%\*
 46 | 	goto end
 47 | )
 48 | 
 49 | 
 50 | %SPHINXBUILD% 2> nul
 51 | if errorlevel 9009 (
 52 | 	echo.
 53 | 	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
 54 | 	echo.installed, then set the SPHINXBUILD environment variable to point
 55 | 	echo.to the full path of the 'sphinx-build' executable. Alternatively you
 56 | 	echo.may add the Sphinx directory to PATH.
 57 | 	echo.
 58 | 	echo.If you don't have Sphinx installed, grab it from
 59 | 	echo.http://sphinx-doc.org/
 60 | 	exit /b 1
 61 | )
 62 | 
 63 | if "%1" == "html" (
 64 | 	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
 65 | 	if errorlevel 1 exit /b 1
 66 | 	echo.
 67 | 	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
 68 | 	goto end
 69 | )
 70 | 
 71 | if "%1" == "dirhtml" (
 72 | 	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
 73 | 	if errorlevel 1 exit /b 1
 74 | 	echo.
 75 | 	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
 76 | 	goto end
 77 | )
 78 | 
 79 | if "%1" == "singlehtml" (
 80 | 	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
 81 | 	if errorlevel 1 exit /b 1
 82 | 	echo.
 83 | 	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
 84 | 	goto end
 85 | )
 86 | 
 87 | if "%1" == "pickle" (
 88 | 	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
 89 | 	if errorlevel 1 exit /b 1
 90 | 	echo.
 91 | 	echo.Build finished; now you can process the pickle files.
 92 | 	goto end
 93 | )
 94 | 
 95 | if "%1" == "json" (
 96 | 	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
 97 | 	if errorlevel 1 exit /b 1
 98 | 	echo.
 99 | 	echo.Build finished; now you can process the JSON files.
100 | 	goto end
101 | )
102 | 
103 | if "%1" == "htmlhelp" (
104 | 	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | 	if errorlevel 1 exit /b 1
106 | 	echo.
107 | 	echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | 	goto end
110 | )
111 | 
112 | if "%1" == "qthelp" (
113 | 	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | 	if errorlevel 1 exit /b 1
115 | 	echo.
116 | 	echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | 	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Flit.qhcp
119 | 	echo.To view the help file:
120 | 	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Flit.ghc
121 | 	goto end
122 | )
123 | 
124 | if "%1" == "devhelp" (
125 | 	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | 	if errorlevel 1 exit /b 1
127 | 	echo.
128 | 	echo.Build finished.
129 | 	goto end
130 | )
131 | 
132 | if "%1" == "epub" (
133 | 	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | 	if errorlevel 1 exit /b 1
135 | 	echo.
136 | 	echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | 	goto end
138 | )
139 | 
140 | if "%1" == "latex" (
141 | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | 	if errorlevel 1 exit /b 1
143 | 	echo.
144 | 	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | 	goto end
146 | )
147 | 
148 | if "%1" == "latexpdf" (
149 | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | 	cd %BUILDDIR%/latex
151 | 	make all-pdf
152 | 	cd %BUILDDIR%/..
153 | 	echo.
154 | 	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | 	goto end
156 | )
157 | 
158 | if "%1" == "latexpdfja" (
159 | 	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | 	cd %BUILDDIR%/latex
161 | 	make all-pdf-ja
162 | 	cd %BUILDDIR%/..
163 | 	echo.
164 | 	echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | 	goto end
166 | )
167 | 
168 | if "%1" == "text" (
169 | 	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | 	if errorlevel 1 exit /b 1
171 | 	echo.
172 | 	echo.Build finished. The text files are in %BUILDDIR%/text.
173 | 	goto end
174 | )
175 | 
176 | if "%1" == "man" (
177 | 	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | 	if errorlevel 1 exit /b 1
179 | 	echo.
180 | 	echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | 	goto end
182 | )
183 | 
184 | if "%1" == "texinfo" (
185 | 	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | 	if errorlevel 1 exit /b 1
187 | 	echo.
188 | 	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | 	goto end
190 | )
191 | 
192 | if "%1" == "gettext" (
193 | 	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | 	if errorlevel 1 exit /b 1
195 | 	echo.
196 | 	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | 	goto end
198 | )
199 | 
200 | if "%1" == "changes" (
201 | 	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | 	if errorlevel 1 exit /b 1
203 | 	echo.
204 | 	echo.The overview file is in %BUILDDIR%/changes.
205 | 	goto end
206 | )
207 | 
208 | if "%1" == "linkcheck" (
209 | 	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | 	if errorlevel 1 exit /b 1
211 | 	echo.
212 | 	echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | 	goto end
215 | )
216 | 
217 | if "%1" == "doctest" (
218 | 	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | 	if errorlevel 1 exit /b 1
220 | 	echo.
221 | 	echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | 	goto end
224 | )
225 | 
226 | if "%1" == "xml" (
227 | 	%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | 	if errorlevel 1 exit /b 1
229 | 	echo.
230 | 	echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | 	goto end
232 | )
233 | 
234 | if "%1" == "pseudoxml" (
235 | 	%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | 	if errorlevel 1 exit /b 1
237 | 	echo.
238 | 	echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | 	goto end
240 | )
241 | 
242 | :end
243 | 


--------------------------------------------------------------------------------
/flit_core/flit_core/sdist.py:
--------------------------------------------------------------------------------
  1 | from collections import defaultdict
  2 | from copy import copy
  3 | from glob import glob
  4 | from gzip import GzipFile
  5 | import io
  6 | import logging
  7 | import os
  8 | import os.path as osp
  9 | from pathlib import Path
 10 | from posixpath import join as pjoin
 11 | import tarfile
 12 | 
 13 | from . import common
 14 | 
 15 | log = logging.getLogger(__name__)
 16 | 
 17 | 
 18 | def clean_tarinfo(ti, mtime=None):
 19 |     """Clean metadata from a TarInfo object to make it more reproducible.
 20 | 
 21 |     - Set uid & gid to 0
 22 |     - Set uname and gname to ""
 23 |     - Normalise permissions to 644 or 755
 24 |     - Set mtime if not None
 25 |     """
 26 |     ti = copy(ti)
 27 |     ti.uid = 0
 28 |     ti.gid = 0
 29 |     ti.uname = ''
 30 |     ti.gname = ''
 31 |     ti.mode = common.normalize_file_permissions(ti.mode)
 32 |     if mtime is not None:
 33 |         ti.mtime = mtime
 34 |     return ti
 35 | 
 36 | 
 37 | class FilePatterns:
 38 |     """Manage a set of file inclusion/exclusion patterns relative to basedir"""
 39 |     def __init__(self, patterns, basedir):
 40 |         self.basedir = basedir
 41 | 
 42 |         self.dirs = set()
 43 |         self.files = set()
 44 | 
 45 |         for pattern in patterns:
 46 |             for path in sorted(glob(osp.join(basedir, pattern), recursive=True)):
 47 |                 rel = osp.relpath(path, basedir)
 48 |                 if osp.isdir(path):
 49 |                     self.dirs.add(rel)
 50 |                 else:
 51 |                     self.files.add(rel)
 52 | 
 53 |     def match_file(self, rel_path):
 54 |         if rel_path in self.files:
 55 |             return True
 56 | 
 57 |         return any(rel_path.startswith(d + os.sep) for d in self.dirs)
 58 | 
 59 |     def match_dir(self, rel_path):
 60 |         if rel_path in self.dirs:
 61 |             return True
 62 | 
 63 |         # Check if it's a subdirectory of any directory in the list
 64 |         return any(rel_path.startswith(d + os.sep) for d in self.dirs)
 65 | 
 66 | 
 67 | class SdistBuilder:
 68 |     """Builds a minimal sdist
 69 | 
 70 |     These minimal sdists should work for PEP 517.
 71 |     The class is extended in flit.sdist to make a more 'full fat' sdist,
 72 |     which is what should normally be published to PyPI.
 73 |     """
 74 |     def __init__(self, module, metadata, cfgdir, reqs_by_extra, entrypoints,
 75 |                  extra_files, data_directory, include_patterns=(), exclude_patterns=()):
 76 |         self.module = module
 77 |         self.metadata = metadata
 78 |         self.cfgdir = cfgdir
 79 |         self.reqs_by_extra = reqs_by_extra
 80 |         self.entrypoints = entrypoints
 81 |         self.extra_files = extra_files
 82 |         self.data_directory = data_directory
 83 |         self.includes = FilePatterns(include_patterns, str(cfgdir))
 84 |         self.excludes = FilePatterns(exclude_patterns, str(cfgdir))
 85 | 
 86 |     @classmethod
 87 |     def from_ini_path(cls, ini_path: Path):
 88 |         # Local import so bootstrapping doesn't try to load toml
 89 |         from .config import read_flit_config
 90 |         ini_info = read_flit_config(ini_path)
 91 |         srcdir = ini_path.parent
 92 |         module = common.Module(ini_info.module, srcdir)
 93 |         metadata = common.make_metadata(module, ini_info)
 94 |         extra_files = [ini_path.name, *map(osp.normpath, ini_info.referenced_files)]
 95 |         return cls(
 96 |             module, metadata, srcdir, ini_info.reqs_by_extra,
 97 |             ini_info.entrypoints, extra_files, ini_info.data_directory,
 98 |             ini_info.sdist_include_patterns, ini_info.sdist_exclude_patterns,
 99 |         )
100 | 
101 |     def prep_entry_points(self):
102 |         # Reformat entry points from dict-of-dicts to dict-of-lists
103 |         res = defaultdict(list)
104 |         for groupname, group in self.entrypoints.items():
105 |             for name, ep in sorted(group.items()):
106 |                 res[groupname].append(f'{name} = {ep}')
107 | 
108 |         return dict(res)
109 | 
110 |     def select_files(self):
111 |         """Pick which files from the source tree will be included in the sdist
112 | 
113 |         This is overridden in flit itself to use information from a VCS to
114 |         include tests, docs, etc. for a 'gold standard' sdist.
115 |         """
116 |         cfgdir_s = str(self.cfgdir)
117 |         return [
118 |             osp.relpath(p, cfgdir_s) for p in self.module.iter_files()
119 |         ] + [
120 |             osp.relpath(p, cfgdir_s) for p in common.walk_data_dir(self.data_directory)
121 |         ] + self.extra_files
122 | 
123 |     def apply_includes_excludes(self, files):
124 |         cfgdir_s = str(self.cfgdir)
125 |         files = {f for f in files if not self.excludes.match_file(f)}
126 | 
127 |         for f_rel in self.includes.files:
128 |             if not self.excludes.match_file(f_rel):
129 |                 files.add(f_rel)
130 | 
131 |         for rel_d in self.includes.dirs:
132 |             for dirpath, dirs, dfiles in os.walk(osp.join(cfgdir_s, rel_d)):
133 |                 for file in dfiles:
134 |                     f_abs = osp.join(dirpath, file)
135 |                     f_rel = osp.relpath(f_abs, cfgdir_s)
136 |                     if not self.excludes.match_file(f_rel):
137 |                         files.add(f_rel)
138 | 
139 |                 # Filter subdirectories before os.walk scans them
140 |                 dirs[:] = [d for d in dirs if not self.excludes.match_dir(
141 |                     osp.relpath(osp.join(dirpath, d), cfgdir_s)
142 |                 )]
143 | 
144 |         crucial_files = set(self.extra_files)
145 |         if not self.module.is_stub_pkg:
146 |             crucial_files.add(str(self.module.file.relative_to(self.cfgdir)))
147 |         missing_crucial = crucial_files - files
148 |         if missing_crucial:
149 |             raise Exception("Crucial files were excluded from the sdist: {}"
150 |                             .format(", ".join(missing_crucial)))
151 | 
152 |         return sorted(files)
153 | 
154 |     @property
155 |     def dir_name(self):
156 |         return common.normalize_dist_name(self.metadata.name, self.metadata.version)
157 | 
158 |     def build(self, target_dir):
159 |         os.makedirs(str(target_dir), exist_ok=True)
160 |         target = target_dir / f'{self.dir_name}.tar.gz'
161 |         source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH', '')
162 |         mtime = int(source_date_epoch) if source_date_epoch else None
163 |         # For the gzip timestamp, default to 2016-1-1 00:00 (UTC)
164 |         # This makes the sdist reproducible even without SOURCE_DATE_EPOCH,
165 |         # if the source file mtimes don't change, i.e. from the same checkout.
166 |         gz = GzipFile(str(target), mode='wb', mtime=(mtime or 1451606400))
167 |         tf = tarfile.TarFile(str(target), mode='w', fileobj=gz,
168 |                              format=tarfile.PAX_FORMAT)
169 | 
170 |         try:
171 |             files_to_add = self.apply_includes_excludes(self.select_files())
172 | 
173 |             for relpath in files_to_add:
174 |                 path = str(self.cfgdir / relpath)
175 |                 ti = tf.gettarinfo(path, arcname=pjoin(self.dir_name, relpath))
176 |                 ti = clean_tarinfo(ti, mtime)
177 | 
178 |                 if ti.isreg():
179 |                     with open(path, 'rb') as f:
180 |                         tf.addfile(ti, f)
181 |                 else:
182 |                     tf.addfile(ti)  # Symlinks & ?
183 | 
184 |             stream = io.StringIO()
185 |             self.metadata.write_metadata_file(stream)
186 |             pkg_info = stream.getvalue().encode()
187 |             ti = tarfile.TarInfo(pjoin(self.dir_name, 'PKG-INFO'))
188 |             ti.size = len(pkg_info)
189 |             tf.addfile(ti, io.BytesIO(pkg_info))
190 | 
191 |         finally:
192 |             tf.close()
193 |             gz.close()
194 | 
195 |         log.info("Built sdist: %s", target)
196 |         return target
197 | 


--------------------------------------------------------------------------------
/tests/test_validate.py:
--------------------------------------------------------------------------------
  1 | import errno
  2 | import pytest
  3 | import responses
  4 | 
  5 | from flit import validate as fv
  6 | 
  7 | def test_validate_entrypoints():
  8 |     assert fv.validate_entrypoints(
  9 |         {'console_scripts': {'flit': 'flit:main'}}) == []
 10 |     assert fv.validate_entrypoints(
 11 |         {'some.group': {'flit': 'flit.buildapi'}}) == []
 12 | 
 13 |     res = fv.validate_entrypoints({'some.group': {'flit': 'a:b:c'}})
 14 |     assert len(res) == 1
 15 | 
 16 | def test_validate_name():
 17 |     def check(name):
 18 |         return fv.validate_name({'name': name})
 19 | 
 20 |     assert check('foo.bar_baz') == []
 21 |     assert check('5minus6') == []
 22 | 
 23 |     assert len(check('_foo')) == 1  # Must start with alphanumeric
 24 |     assert len(check('foo.')) == 1  # Must end with alphanumeric
 25 |     assert len(check('Bücher')) == 1 # ASCII only
 26 | 
 27 | def test_validate_requires_python():
 28 |     assert fv.validate_requires_python({}) == []  # Not required
 29 | 
 30 |     def check(spec):
 31 |         return fv.validate_requires_python({'requires_python': spec})
 32 | 
 33 |     assert check('>=3') == []
 34 |     assert check('>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*') == []
 35 | 
 36 |     assert len(check('3')) == 1
 37 |     assert len(check('@12')) == 1
 38 |     assert len(check('>=2.7; !=3.0.*')) == 1  # Comma separated, not semicolon
 39 | 
 40 | def test_validate_requires_dist():
 41 |     assert fv.validate_requires_dist({}) == []  # Not required
 42 | 
 43 |     def check(spec):
 44 |         return fv.validate_requires_dist({'requires_dist': [spec]})
 45 | 
 46 |     assert check('requests') == []
 47 |     assert check('requests[extra-foo]') == []
 48 |     assert check('requests (>=2.14)') == []  # parentheses allowed but not recommended
 49 |     assert check('requests >=2.14') == []
 50 |     assert check('pexpect; sys_platform == "win32"') == []
 51 |     # Altogether now
 52 |     assert check('requests[extra-foo] >=2.14; python_version < "3.0"') == []
 53 | 
 54 |     # URL specifier
 55 |     assert check('requests @ https://example.com/requests.tar.gz') == []
 56 |     assert check(
 57 |         'requests @ https://example.com/requests.tar.gz ; python_version < "3.8"'
 58 |     ) == []
 59 | 
 60 |     # Problems
 61 |     assert len(check('Bücher')) == 1
 62 |     assert len(check('requests 2.14')) == 1
 63 |     assert len(check('pexpect; sys.platform == "win32"')) == 1  # '.' -> '_'
 64 |     assert len(check('requests >=2.14 @ https://example.com/requests.tar.gz')) == 1
 65 |     # Several problems in one requirement
 66 |     assert len(check('pexpect[_foo] =3; sys.platform == "win32"')) == 3
 67 | 
 68 | def test_validate_environment_marker():
 69 |     vem = fv.validate_environment_marker
 70 | 
 71 |     assert vem('python_version >= "3" and os_name == \'posix\'') == []
 72 | 
 73 |     res = vem('python_version >= "3')  # Unclosed string
 74 |     assert len(res) == 1
 75 |     assert res[0].startswith("Invalid string")
 76 | 
 77 |     res = vem('python_verson >= "3"')  # Misspelled name
 78 |     assert len(res) == 1
 79 |     assert res[0].startswith("Invalid variable")
 80 | 
 81 |     res = vem("os_name is 'posix'")  # No 'is' comparisons
 82 |     assert len(res) == 1
 83 |     assert res[0].startswith("Invalid expression")
 84 | 
 85 |     res = vem("'2' < python_version < '4'")  # No chained comparisons
 86 |     assert len(res) == 1
 87 |     assert res[0].startswith("Invalid expression")
 88 | 
 89 |     assert len(vem('os.name == "linux\'')) == 2
 90 | 
 91 | def test_validate_url():
 92 |     vurl = fv.validate_url
 93 |     assert vurl("https://github.com/pypa/flit") == []
 94 | 
 95 |     assert len(vurl("github.com/pypa/flit")) == 1
 96 |     assert len(vurl("https://")) == 1
 97 | 
 98 | 
 99 | def test_validate_project_urls():
100 |     vpu = fv.validate_project_urls
101 | 
102 |     def check(prurl):
103 |         return vpu({'project_urls': [prurl]})
104 |     assert vpu({}) == []   # Not required
105 |     assert check('Documentation, https://flit.readthedocs.io/') == []
106 | 
107 |     # Missing https://
108 |     assert len(check('Documentation, flit.readthedocs.io')) == 1
109 |     # Double comma
110 |     assert len(check('A, B, flit.readthedocs.io')) == 1
111 |     # No name
112 |     assert len(check(', https://flit.readthedocs.io/')) == 1
113 |     # Name longer than 32 chars
114 |     assert len(check('Supercalifragilisticexpialidocious, https://flit.readthedocs.io/')) == 1
115 | 
116 | 
117 | def test_read_classifiers_cached(monkeypatch, tmp_path):
118 | 
119 |     def mock_get_cache_dir():
120 |         tmp_file = tmp_path / "classifiers.lst"
121 |         with tmp_file.open("w") as fh:
122 |             fh.write("A\nB\nC")
123 |         return tmp_path
124 | 
125 |     monkeypatch.setattr(fv, "get_cache_dir", mock_get_cache_dir)
126 | 
127 |     classifiers = fv._read_classifiers_cached()
128 | 
129 |     assert classifiers == {'A', 'B', 'C'}
130 | 
131 | 
132 | @responses.activate
133 | def test_download_and_cache_classifiers(monkeypatch, tmp_path):
134 |     responses.add(
135 |         responses.GET,
136 |         'https://pypi.org/pypi?%3Aaction=list_classifiers',
137 |         body="A\nB\nC")
138 | 
139 |     def mock_get_cache_dir():
140 |         return tmp_path
141 | 
142 |     monkeypatch.setattr(fv, "get_cache_dir", mock_get_cache_dir)
143 | 
144 |     classifiers = fv._download_and_cache_classifiers()
145 | 
146 |     assert classifiers == {"A", "B", "C"}
147 | 
148 | 
149 | def test_validate_classifiers_private(monkeypatch):
150 |     """
151 |     Test that `Private :: Do Not Upload` considered a valid classifier.
152 |     This is a special case because it is not listed in a trove classifier
153 |     but it is a way to make sure that a private package is not get uploaded
154 |     on PyPI by accident.
155 | 
156 |     Implementation on PyPI side:
157 |         https://github.com/pypa/warehouse/pull/5440
158 |     Issue about officially documenting the trick:
159 |         https://github.com/pypa/packaging.python.org/issues/643
160 |     """
161 |     monkeypatch.setattr(fv, "_read_classifiers_cached", set)
162 | 
163 |     actual = fv.validate_classifiers({'invalid'})
164 |     assert actual == ["Unrecognised classifier: 'invalid'"]
165 | 
166 |     assert fv.validate_classifiers({'Private :: Do Not Upload'}) == []
167 | 
168 | 
169 | @responses.activate
170 | @pytest.mark.parametrize("error", [PermissionError, OSError(errno.EROFS, "")])
171 | def test_download_and_cache_classifiers_with_unacessible_dir(monkeypatch, error):
172 |     responses.add(
173 |         responses.GET,
174 |         'https://pypi.org/pypi?%3Aaction=list_classifiers',
175 |         body="A\nB\nC")
176 | 
177 |     class MockCacheDir:
178 |         def mkdir(self, parents):
179 |             raise error
180 |         def __truediv__(self, other):
181 |             raise error
182 | 
183 |     monkeypatch.setattr(fv, "get_cache_dir", MockCacheDir)
184 | 
185 |     classifiers = fv._download_and_cache_classifiers()
186 | 
187 |     assert classifiers == {"A", "B", "C"}
188 | 
189 | 
190 | def test_verify_classifiers_valid_classifiers():
191 |     classifiers = {"A"}
192 |     valid_classifiers = {"A", "B"}
193 | 
194 |     problems = fv._verify_classifiers(classifiers, valid_classifiers)
195 | 
196 |     assert problems == []
197 | 
198 | def test_verify_classifiers_invalid_classifiers():
199 |     classifiers = {"A", "B"}
200 |     valid_classifiers = {"A"}
201 | 
202 |     problems = fv._verify_classifiers(classifiers, valid_classifiers)
203 | 
204 |     assert problems == ["Unrecognised classifier: 'B'"]
205 | 
206 | def test_validate_readme_rst():
207 |     metadata = {
208 |         'description_content_type': 'text/x-rst',
209 |         'description': "Invalid ``rst'",
210 |     }
211 |     problems = fv.validate_readme_rst(metadata)
212 | 
213 |     assert len(problems) == 2  # 1 message that rst is invalid + 1 with details
214 |     assert "valid rst" in problems[0]
215 | 
216 |     # Markdown should be ignored
217 |     metadata = {
218 |         'description_content_type': 'text/markdown',
219 |         'description': "Invalid `rst'",
220 |     }
221 |     problems = fv.validate_readme_rst(metadata)
222 | 
223 |     assert problems == []
224 | 
225 | RST_WITH_CODE = """
226 | Code snippet:
227 | 
228 | .. code-block:: python
229 | 
230 |    a = [i ** 2 for i in range(5)]
231 | """
232 | 
233 | def test_validate_readme_rst_code():
234 |     # Syntax highlighting shouldn't require pygments
235 |     metadata = {
236 |         'description_content_type': 'text/x-rst',
237 |         'description': RST_WITH_CODE,
238 |     }
239 |     problems = fv.validate_readme_rst(metadata)
240 |     for p in problems:
241 |         print(p)
242 | 
243 |     assert problems == []
244 | 


--------------------------------------------------------------------------------
/flit/__init__.py:
--------------------------------------------------------------------------------
  1 | """A simple packaging tool for simple packages."""
  2 | import argparse
  3 | import logging
  4 | import os
  5 | import pathlib
  6 | import shutil
  7 | import subprocess
  8 | import sys
  9 | from typing import Optional
 10 | 
 11 | from flit_core import common
 12 | from .config import ConfigError
 13 | from .log import enable_colourful_output
 14 | 
 15 | __version__ = '3.12.0'
 16 | 
 17 | log = logging.getLogger(__name__)
 18 | 
 19 | 
 20 | class PythonNotFoundError(FileNotFoundError): pass
 21 | 
 22 | 
 23 | def find_python_executable(python: Optional[str] = None) -> str:
 24 |     """Returns an absolute filepath to the executable of Python to use."""
 25 |     if not python:
 26 |         python = os.environ.get("FLIT_INSTALL_PYTHON")
 27 |     if not python:
 28 |         return sys.executable
 29 |     if os.path.isdir(python):
 30 |         # Assume it's a virtual environment and look for the environment's
 31 |         # Python executable.  This is the same behavior used by pip.
 32 |         #
 33 |         # Try both Unix and Windows paths in case of odd cases like cygwin.
 34 |         for exe in ("bin/python", "Scripts/python.exe"):
 35 |             py = os.path.join(python, exe)
 36 |             if os.path.exists(py):
 37 |                 return os.path.abspath(py)
 38 |     if os.path.isabs(python):  # sys.executable is absolute too
 39 |         return python
 40 |     # get absolute filepath of {python}
 41 |     # shutil.which may give a different result to the raw subprocess call
 42 |     # see https://github.com/pypa/flit/pull/300 and https://bugs.python.org/issue38905
 43 |     resolved_python = shutil.which(python)
 44 |     if resolved_python is None:
 45 |         raise PythonNotFoundError(f"Unable to resolve Python executable {python!r}")
 46 |     try:
 47 |         return subprocess.check_output(
 48 |             [resolved_python, "-c", "import sys; print(sys.executable)"],
 49 |             universal_newlines=True,
 50 |         ).strip()
 51 |     except Exception as e:
 52 |         raise PythonNotFoundError(
 53 |             f"{e.__class__.__name__} occurred trying to find the absolute filepath "
 54 |             f"of Python executable {python!r} ({resolved_python!r})"
 55 |         ) from e
 56 | 
 57 | 
 58 | def add_shared_install_options(parser: argparse.ArgumentParser):
 59 |     parser.add_argument('--user', action='store_true', default=None,
 60 |         help="Do a user-local install (default if site.ENABLE_USER_SITE is True)"
 61 |     )
 62 |     parser.add_argument('--env', action='store_false', dest='user',
 63 |         help="Install into sys.prefix (default if site.ENABLE_USER_SITE is False, i.e. in virtualenvs)"
 64 |     )
 65 |     parser.add_argument('--python',
 66 |         help="Target Python executable, if different from the one running flit"
 67 |     )
 68 |     parser.add_argument('--deps', choices=['all', 'production', 'develop', 'none'], default='all',
 69 |         help="Which set of dependencies to install. If --deps=develop, the extras dev, doc, and test are installed"
 70 |     )
 71 |     parser.add_argument('--only-deps', action='store_true',
 72 |         help="Install only dependencies of this package, and not the package itself"
 73 |     )
 74 |     parser.add_argument('--extras', default=(), type=lambda l: l.split(',') if l else (),
 75 |         help="Install the dependencies of these (comma separated) extras additionally to the ones implied by --deps. "
 76 |              "--extras=all can be useful in combination with --deps=production, --deps=none precludes using --extras"
 77 |     )
 78 | 
 79 | 
 80 | def add_shared_build_options(parser: argparse.ArgumentParser):
 81 |     parser.add_argument('--format', action='append',
 82 |         help="Select a format to publish. Options: 'wheel', 'sdist'"
 83 |     )
 84 | 
 85 |     vcs_grp = parser.add_mutually_exclusive_group()
 86 | 
 87 |     vcs_grp.add_argument('--use-vcs', action='store_true',
 88 |         help=("Choose which files to include in the sdist using git or hg. "
 89 |               "This is a convenient way to include all checked-in files, like "
 90 |               "tests and doc source files, in your sdist, but requires that git "
 91 |               "or hg is available on the command line."
 92 |              )
 93 |     )
 94 | 
 95 |     vcs_grp.add_argument('--no-use-vcs', action='store_true',
 96 |         help=("Select the files to include in the sdist without using git or hg. "
 97 |               "This should include all essential files to install and use your "
 98 |               "package; see the documentation for precisely what is included. "
 99 |               "This is the default for Flit version 4 and above."
100 |              )
101 |     )
102 | 
103 | 
104 | def main(argv=None):
105 |     ap = argparse.ArgumentParser()
106 |     ap.add_argument('-f', '--ini-file', type=pathlib.Path, default='pyproject.toml')
107 |     ap.add_argument('-V', '--version', action='version', version='Flit '+__version__)
108 |     # --repository now belongs on 'flit publish' - it's still here for
109 |     # compatibility with scripts passing it before the subcommand.
110 |     ap.add_argument('--repository', dest='deprecated_repository', help=argparse.SUPPRESS)
111 |     ap.add_argument('--debug', action='store_true', help=argparse.SUPPRESS)
112 |     ap.add_argument('--logo', action='store_true', help=argparse.SUPPRESS)
113 |     subparsers = ap.add_subparsers(title='subcommands', dest='subcmd')
114 | 
115 |     # flit build --------------------------------------------
116 |     parser_build = subparsers.add_parser('build',
117 |         help="Build wheel and sdist",
118 |     )
119 | 
120 |     add_shared_build_options(parser_build)
121 | 
122 |     # flit publish --------------------------------------------
123 |     parser_publish = subparsers.add_parser('publish',
124 |         help="Upload wheel and sdist",
125 |     )
126 | 
127 |     add_shared_build_options(parser_publish)
128 | 
129 |     parser_publish.add_argument('--pypirc',
130 |         help="The .pypirc config file to be used. DEFAULT = \"~/.pypirc\""
131 |     )
132 | 
133 |     parser_publish.add_argument('--repository',
134 |         help="Name of the repository to upload to (must be in the specified .pypirc file)"
135 |     )
136 | 
137 |     # flit install --------------------------------------------
138 |     parser_install = subparsers.add_parser('install',
139 |         help="Install the package",
140 |     )
141 |     parser_install.add_argument('-s', '--symlink', action='store_true',
142 |         help="Symlink the module/package into site packages instead of copying it"
143 |     )
144 |     parser_install.add_argument('--pth-file', action='store_true',
145 |         help="Add .pth file for the module/package to site packages instead of copying it"
146 |     )
147 |     add_shared_install_options(parser_install)
148 | 
149 |     # flit init --------------------------------------------
150 |     parser_init = subparsers.add_parser('init',
151 |         help="Prepare pyproject.toml for a new package"
152 |     )
153 | 
154 |     args = ap.parse_args(argv)
155 | 
156 |     if args.ini_file.suffix == '.ini':
157 |         sys.exit("flit.ini format is no longer supported. You can use "
158 |                  "'python3 -m flit.tomlify' to convert it to pyproject.toml")
159 | 
160 |     if args.subcmd not in {'init'} and not args.ini_file.is_file():
161 |         sys.exit(f'Config file {args.ini_file} does not exist')
162 | 
163 |     enable_colourful_output(logging.DEBUG if args.debug else logging.INFO)
164 | 
165 |     log.debug("Parsed arguments %r", args)
166 | 
167 |     if args.logo:
168 |         from .logo import clogo
169 |         print(clogo.format(version=__version__))
170 |         sys.exit(0)
171 | 
172 |     if args.subcmd == 'build':
173 |         from .build import main
174 |         try:
175 |             main(args.ini_file, formats=set(args.format or []),
176 |                  use_vcs=args.use_vcs)
177 |         except(common.NoDocstringError, common.VCSError, common.NoVersionError) as e:
178 |             sys.exit(e.args[0])
179 |     elif args.subcmd == 'publish':
180 |         if args.deprecated_repository:
181 |             log.warning("Passing --repository before the 'upload' subcommand is deprecated: pass it after")
182 |         repository = args.repository or args.deprecated_repository
183 |         from .upload import main
184 |         main(args.ini_file, repository, args.pypirc, formats=set(args.format or []),
185 |              use_vcs=args.use_vcs)
186 | 
187 |     elif args.subcmd == 'install':
188 |         from .install import Installer
189 |         try:
190 |             python = find_python_executable(args.python)
191 |             installer = Installer.from_ini_path(
192 |                 args.ini_file,
193 |                 user=args.user,
194 |                 python=python,
195 |                 symlink=args.symlink,
196 |                 deps=args.deps,
197 |                 extras=args.extras,
198 |                 pth=args.pth_file
199 |             )
200 |             if args.only_deps:
201 |                 installer.install_requirements()
202 |             else:
203 |                 installer.install()
204 |         except (ConfigError, PythonNotFoundError, common.NoDocstringError, common.NoVersionError) as e:
205 |             sys.exit(e.args[0])
206 | 
207 |     elif args.subcmd == 'init':
208 |         from .init import TerminalIniter
209 |         TerminalIniter().initialise()
210 |     else:
211 |         ap.print_help()
212 |         sys.exit(1)
213 | 


--------------------------------------------------------------------------------
/doc/_static/flit_logo_nobg.svg:
--------------------------------------------------------------------------------
  1 | 
  2 | 
  3 | 
  4 | 
 19 |   
 21 |     
 31 |     
 34 |       
 38 |       
 42 |     
 43 |     
 52 |     
 61 |     
 70 |     
 79 |   
 80 |   
102 |   
104 |     
105 |       
107 |         image/svg+xml
108 |         
110 |         
111 |       
112 |     
113 |   
114 |   
119 |     
122 |       
127 |       
131 |         
136 |         
141 |         
146 |       
147 |     
148 |   
149 | 
150 | 


--------------------------------------------------------------------------------
/doc/conf.py:
--------------------------------------------------------------------------------
  1 | # Flit documentation build configuration file, created by
  2 | # sphinx-quickstart on Sun Mar 15 19:16:41 2015.
  3 | #
  4 | # This file is execfile()d with the current directory set to its
  5 | # containing dir.
  6 | #
  7 | # Note that not all possible configuration values are present in this
  8 | # autogenerated file.
  9 | #
 10 | # All configuration values have a default; values that are commented out
 11 | # serve to show the default.
 12 | 
 13 | # If extensions (or modules to document with autodoc) are in another directory,
 14 | # add these directories to sys.path here. If the directory is relative to the
 15 | # documentation root, use os.path.abspath to make it absolute, like shown here.
 16 | #sys.path.insert(0, os.path.abspath('.'))
 17 | 
 18 | # -- General configuration ------------------------------------------------
 19 | 
 20 | # If your documentation needs a minimal Sphinx version, state it here.
 21 | #needs_sphinx = '1.0'
 22 | 
 23 | # Add any Sphinx extension module names here, as strings. They can be
 24 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
 25 | # ones.
 26 | extensions = [
 27 |     'sphinx.ext.autodoc',
 28 |     'sphinxcontrib_github_alt',
 29 |     'sphinx_rtd_theme',
 30 | ]
 31 | 
 32 | github_project_url = "https://github.com/pypa/flit"
 33 | 
 34 | # Add any paths that contain templates here, relative to this directory.
 35 | templates_path = ['_templates']
 36 | 
 37 | # The suffix of source filenames.
 38 | source_suffix = {".rst": "restructuredtext"}
 39 | 
 40 | # The encoding of source files.
 41 | #source_encoding = 'utf-8-sig'
 42 | 
 43 | # The master toctree document.
 44 | master_doc = 'index'
 45 | 
 46 | # General information about the project.
 47 | project = 'Flit'
 48 | copyright = '2015, Thomas Kluyver'
 49 | 
 50 | # The version info for the project you're documenting, acts as replacement for
 51 | # |version| and |release|, also used in various other places throughout the
 52 | # built documents.
 53 | #
 54 | # The short X.Y version.
 55 | version = '3.12.0'
 56 | # The full version, including alpha/beta/rc tags.
 57 | release = version #+ '.1'
 58 | 
 59 | # The language for content autogenerated by Sphinx. Refer to documentation
 60 | # for a list of supported languages.
 61 | #language = None
 62 | 
 63 | # There are two options for replacing |today|: either, you set today to some
 64 | # non-false value, then it is used:
 65 | #today = ''
 66 | # Else, today_fmt is used as the format for a strftime call.
 67 | #today_fmt = '%B %d, %Y'
 68 | 
 69 | # List of patterns, relative to source directory, that match files and
 70 | # directories to ignore when looking for source files.
 71 | exclude_patterns = ['_build']
 72 | 
 73 | # The reST default role (used for this markup: `text`) to use for all
 74 | # documents.
 75 | #default_role = None
 76 | 
 77 | # If true, '()' will be appended to :func: etc. cross-reference text.
 78 | #add_function_parentheses = True
 79 | 
 80 | # If true, the current module name will be prepended to all description
 81 | # unit titles (such as .. function::).
 82 | #add_module_names = True
 83 | 
 84 | # If true, sectionauthor and moduleauthor directives will be shown in the
 85 | # output. They are ignored by default.
 86 | #show_authors = False
 87 | 
 88 | # The name of the Pygments (syntax highlighting) style to use.
 89 | pygments_style = 'sphinx'
 90 | 
 91 | # A list of ignored prefixes for module index sorting.
 92 | #modindex_common_prefix = []
 93 | 
 94 | # If true, keep warnings as "system message" paragraphs in the built documents.
 95 | #keep_warnings = False
 96 | 
 97 | 
 98 | # -- Options for HTML output ----------------------------------------------
 99 | 
100 | # The theme to use for HTML and HTML Help pages.  See the documentation for
101 | # a list of builtin themes.
102 | html_theme = 'sphinx_rtd_theme'
103 | 
104 | # Theme options are theme-specific and customize the look and feel of a theme
105 | # further.  For a list of options available for each theme, see the
106 | # documentation.
107 | #html_theme_options = {}
108 | 
109 | # Add any paths that contain custom themes here, relative to this directory.
110 | #html_theme_path = []
111 | 
112 | # The name for this set of Sphinx documents.  If None, it defaults to
113 | # " v documentation".
114 | #html_title = None
115 | 
116 | # A shorter title for the navigation bar.  Default is the same as html_title.
117 | #html_short_title = None
118 | 
119 | # The name of an image file (relative to this directory) to place at the top
120 | # of the sidebar.
121 | #html_logo = '_static/flit_logo_nobg_cropped.svg'
122 | 
123 | # The name of an image file (within the static path) to use as favicon of the
124 | # docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
125 | # pixels large.
126 | #html_favicon = None
127 | 
128 | # Add any paths that contain custom static files (such as style sheets) here,
129 | # relative to this directory. They are copied after the builtin static files,
130 | # so a file named "default.css" will overwrite the builtin "default.css".
131 | html_static_path = ['_static']
132 | 
133 | # Add any extra paths that contain custom files (such as robots.txt or
134 | # .htaccess) here, relative to this directory. These files are copied
135 | # directly to the root of the documentation.
136 | #html_extra_path = []
137 | 
138 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
139 | # using the given strftime format.
140 | #html_last_updated_fmt = '%b %d, %Y'
141 | 
142 | # If true, SmartyPants will be used to convert quotes and dashes to
143 | # typographically correct entities.
144 | #html_use_smartypants = True
145 | 
146 | # Custom sidebar templates, maps document names to template names.
147 | #html_sidebars = {}
148 | 
149 | # Additional templates that should be rendered to pages, maps page names to
150 | # template names.
151 | #html_additional_pages = {}
152 | 
153 | # If false, no module index is generated.
154 | #html_domain_indices = True
155 | 
156 | # If false, no index is generated.
157 | #html_use_index = True
158 | 
159 | # If true, the index is split into individual pages for each letter.
160 | #html_split_index = False
161 | 
162 | # If true, links to the reST sources are added to the pages.
163 | #html_show_sourcelink = True
164 | 
165 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
166 | #html_show_sphinx = True
167 | 
168 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
169 | #html_show_copyright = True
170 | 
171 | # If true, an OpenSearch description file will be output, and all pages will
172 | # contain a  tag referring to it.  The value of this option must be the
173 | # base URL from which the finished HTML is served.
174 | #html_use_opensearch = ''
175 | 
176 | # This is the file name suffix for HTML files (e.g. ".xhtml").
177 | #html_file_suffix = None
178 | 
179 | # Output file base name for HTML help builder.
180 | htmlhelp_basename = 'Flitdoc'
181 | 
182 | 
183 | # -- Options for LaTeX output ---------------------------------------------
184 | 
185 | latex_elements = {
186 | # The paper size ('letterpaper' or 'a4paper').
187 | #'papersize': 'letterpaper',
188 | 
189 | # The font size ('10pt', '11pt' or '12pt').
190 | #'pointsize': '10pt',
191 | 
192 | # Additional stuff for the LaTeX preamble.
193 | #'preamble': '',
194 | }
195 | 
196 | # Grouping the document tree into LaTeX files. List of tuples
197 | # (source start file, target name, title,
198 | #  author, documentclass [howto, manual, or own class]).
199 | latex_documents = [
200 |   ('index', 'Flit.tex', 'Flit Documentation',
201 |    'Thomas Kluyver', 'manual'),
202 | ]
203 | 
204 | # The name of an image file (relative to this directory) to place at the top of
205 | # the title page.
206 | #latex_logo = None
207 | 
208 | # For "manual" documents, if this is true, then toplevel headings are parts,
209 | # not chapters.
210 | #latex_use_parts = False
211 | 
212 | # If true, show page references after internal links.
213 | #latex_show_pagerefs = False
214 | 
215 | # If true, show URL addresses after external links.
216 | #latex_show_urls = False
217 | 
218 | # Documents to append as an appendix to all manuals.
219 | #latex_appendices = []
220 | 
221 | # If false, no module index is generated.
222 | #latex_domain_indices = True
223 | 
224 | 
225 | # -- Options for manual page output ---------------------------------------
226 | 
227 | # One entry per manual page. List of tuples
228 | # (source start file, name, description, authors, manual section).
229 | man_pages = [
230 |     ('index', 'flit', 'Flit Documentation',
231 |      ['Thomas Kluyver'], 1)
232 | ]
233 | 
234 | # If true, show URL addresses after external links.
235 | #man_show_urls = False
236 | 
237 | 
238 | # -- Options for Texinfo output -------------------------------------------
239 | 
240 | # Grouping the document tree into Texinfo files. List of tuples
241 | # (source start file, target name, title, author,
242 | #  dir menu entry, description, category)
243 | texinfo_documents = [
244 |   ('index', 'Flit', 'Flit Documentation',
245 |    'Thomas Kluyver', 'Flit', 'One line description of project.',
246 |    'Miscellaneous'),
247 | ]
248 | 
249 | # Documents to append as an appendix to all manuals.
250 | #texinfo_appendices = []
251 | 
252 | # If false, no module index is generated.
253 | #texinfo_domain_indices = True
254 | 
255 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
256 | #texinfo_show_urls = 'footnote'
257 | 
258 | # If true, do not generate a @detailmenu in the "Top" node's menu.
259 | #texinfo_no_detailmenu = False
260 | 


--------------------------------------------------------------------------------