Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-editables for
openSUSE:Factory checked in at 2023-07-12 17:26:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-editables (Old)
and /work/SRC/openSUSE:Factory/.python-editables.new.8922 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-editables"
Wed Jul 12 17:26:17 2023 rev:3 rq:1098095 version:0.4
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-editables/python-editables.changes
2023-04-22 22:02:13.781851114 +0200
+++
/work/SRC/openSUSE:Factory/.python-editables.new.8922/python-editables.changes
2023-07-12 17:26:21.970149806 +0200
@@ -1,0 +2,18 @@
+Tue Jul 11 10:38:18 UTC 2023 - Markéta Machová <[email protected]>
+
+- Convert to multibuild to break a build cycle
+
+-------------------------------------------------------------------
+Mon Jul 10 09:59:29 UTC 2023 - Benoît Monin <[email protected]>
+
+- update to version 0.4:
+ * Add a new add_to_subpackage method
+ * Add type annotations
+ * Internal admin: Switch to nox for automation
+ * Internal admin: Switch to ruff for linting
+ * Internal admin: Switch from setuptools to flit_core
+- drop skip_python2 and require python >= 3.7
+- drop setuptools and requires pip and flit-core to build the rpm
+- add pytest as a build requirement and run the tests
+
+-------------------------------------------------------------------
Old:
----
editables-0.3.tar.gz
New:
----
_multibuild
editables-0.4.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-editables.spec ++++++
--- /var/tmp/diff_new_pack.KQ8O4L/_old 2023-07-12 17:26:23.234157174 +0200
+++ /var/tmp/diff_new_pack.KQ8O4L/_new 2023-07-12 17:26:23.278157431 +0200
@@ -1,5 +1,5 @@
#
-# spec file for package python-editables
+# spec file
#
# Copyright (c) 2023 SUSE LLC
#
@@ -16,20 +16,32 @@
#
-%define skip_python2 1
-%{?!python_module:%define python_module() python-%{**} python3-%{**}}
+%global flavor @BUILD_FLAVOR@%{nil}
+%if "%{flavor}" == "test"
+%define psuffix -%{flavor}
+%bcond_without test
+%else
+%define psuffix %{nil}
+%bcond_with test
+%endif
%{?sle15_python_module_pythons}
-Name: python-editables
-Version: 0.3
+Name: python-editables%{psuffix}
+Version: 0.4
Release: 0
Summary: Editable installations
License: MIT
URL: https://github.com/pfmoore/editables
Source:
https://files.pythonhosted.org/packages/source/e/editables/editables-%{version}.tar.gz
-BuildRequires: %{python_module setuptools}
+BuildRequires: %{python_module base >= 3.7}
+BuildRequires: %{python_module flit-core >= 3.3}
+BuildRequires: %{python_module pip}
BuildRequires: dos2unix
BuildRequires: fdupes
BuildRequires: python-rpm-macros
+%if %{with test}
+BuildRequires: %{python_module editables = %{version}}
+BuildRequires: %{python_module pytest}
+%endif
BuildArch: noarch
%python_subpackages
@@ -48,16 +60,27 @@
dos2unix -c ascii README.md
%build
-%python_build
+%if !%{with test}
+%pyproject_wheel
+%endif
%install
-%python_install
+%if !%{with test}
+%pyproject_install
%python_expand %fdupes %{buildroot}%{$python_sitelib}
+%endif
+%check
+%if %{with test}
+%pytest
+%endif
+
+%if !%{with test}
%files %{python_files}
%doc README.md
%license LICENSE.txt
%{python_sitelib}/editables
%{python_sitelib}/editables-%{version}*-info
+%endif
%changelog
++++++ _multibuild ++++++
<multibuild>
<package>test</package>
</multibuild>
++++++ editables-0.3.tar.gz -> editables-0.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/PKG-INFO new/editables-0.4/PKG-INFO
--- old/editables-0.3/PKG-INFO 2022-04-10 17:33:58.421759600 +0200
+++ new/editables-0.4/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,97 +1,90 @@
-Metadata-Version: 2.1
-Name: editables
-Version: 0.3
-Summary: Editable installations
-Home-page: https://github.com/pfmoore/editables
-Author: Paul Moore
-Author-email: [email protected]
-Maintainer: Paul Moore
-Maintainer-email: [email protected]
-License: MIT
-Project-URL: Source, https://github.com/pfmoore/editables
-Project-URL: Tracker, https://github.com/pfmoore/editables/issues
-Project-URL: Documentation, https://editables.readthedocs.io
-Keywords: packaging,editables
-Platform: any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Topic :: Software Development :: Libraries
-Classifier: Topic :: Utilities
-Requires-Python: >=3.7
-Description-Content-Type: text/markdown
-License-File: LICENSE.txt
-
-# A Python library for creating "editable wheels"
-
-This library supports the building of wheels which, when installed, will
-expose packages in a local directory on `sys.path` in "editable mode". In
-other words, changes to the package source will be reflected in the package
-visible to Python, without needing a reinstall.
-
-## Usage
-
-Suppose you want to build a wheel for your project `foo`. Your project is
-located in the directory `/path/to/foo`. Under that directory, you have a
-`src` directory containing your project, which is a package called `foo`
-and a Python module called `bar.py`. So your directory structure looks like
-this:
-
-```
-/path/to/foo
-|
-+-- src
-| +-- foo
-| | +-- __init__.py
-| +-- bar.py
-|
-+-- setup.py
-+-- other files
-```
-
-Build your wheel as follows:
-
-```python
-from editables import EditableProject
-
-my_project = EditableProject("foo", "/path/to/foo")
-my_project.add_to_path("src")
-
-# Build a wheel however you prefer...
-wheel = BuildAWheel()
-
-# Add files to the wheel
-for name, content in my_project.files():
- wheel.add_file(name, content)
-
-# Record any runtime dependencies
-for dep in my_project.dependencies():
- wheel.metadata.dependencies.add(dep)
-```
-
-The resulting wheel will, when installed, put the project `src` directory on
-`sys.path` so that editing the original source will take effect without needing
-a reinstall (i.e., as "editable" packages). The project is exposed on
`sys.path`
-by adding a single `.pth` file, named after the project, into the wheel.
-
-For more details, including how to control what gets exposed more precisely,
see
-[the documentation](https://editables.readthedocs.io/en/latest/).
-
-Note that this project doesn't build wheels directly. That's the responsibility
-of the calling code.
-
-## Python Compatibility
-
-This project supports the same versions of Python as pip does. Currently
-that is Python 3.7 and later, and PyPy3 (although we don't test against
-PyPy).
-
-
+Metadata-Version: 2.1
+Name: editables
+Version: 0.4
+Summary: Editable installations
+Author-email: Paul Moore <[email protected]>
+Requires-Python: >=3.7
+Description-Content-Type: text/markdown
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Topic :: Utilities
+Project-URL: Documentation, https://editables.readthedocs.io
+Project-URL: Source, https://github.com/pfmoore/editables
+Project-URL: Tracker, https://github.com/pfmoore/editables/issues
+
+# A Python library for creating "editable wheels"
+
+This library supports the building of wheels which, when installed, will
+expose packages in a local directory on `sys.path` in "editable mode". In
+other words, changes to the package source will be reflected in the package
+visible to Python, without needing a reinstall.
+
+## Usage
+
+Suppose you want to build a wheel for your project `foo`. Your project is
+located in the directory `/path/to/foo`. Under that directory, you have a
+`src` directory containing your project, which is a package called `foo`
+and a Python module called `bar.py`. So your directory structure looks like
+this:
+
+```
+/path/to/foo
+|
++-- src
+| +-- foo
+| | +-- __init__.py
+| +-- bar.py
+|
++-- setup.py
++-- other files
+```
+
+Build your wheel as follows:
+
+```python
+from editables import EditableProject
+
+my_project = EditableProject("foo", "/path/to/foo")
+my_project.add_to_path("src")
+
+# Build a wheel however you prefer...
+wheel = BuildAWheel()
+
+# Add files to the wheel
+for name, content in my_project.files():
+ wheel.add_file(name, content)
+
+# Record any runtime dependencies
+for dep in my_project.dependencies():
+ wheel.metadata.dependencies.add(dep)
+```
+
+The resulting wheel will, when installed, put the project `src` directory on
+`sys.path` so that editing the original source will take effect without needing
+a reinstall (i.e., as "editable" packages). The project is exposed on
`sys.path`
+by adding a single `.pth` file, named after the project, into the wheel.
+
+For more details, including how to control what gets exposed more precisely,
see
+[the documentation](https://editables.readthedocs.io/en/latest/).
+
+Note that this project doesn't build wheels directly. That's the responsibility
+of the calling code.
+
+## Python Compatibility
+
+This project supports the same versions of Python as pip does. Currently
+that is Python 3.7 and later, and PyPy3 (although we don't test against
+PyPy).
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/Makefile
new/editables-0.4/docs/Makefile
--- old/editables-0.3/docs/Makefile 1970-01-01 01:00:00.000000000 +0100
+++ new/editables-0.4/docs/Makefile 2022-04-09 17:49:08.691217400 +0200
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/make.bat
new/editables-0.4/docs/make.bat
--- old/editables-0.3/docs/make.bat 1970-01-01 01:00:00.000000000 +0100
+++ new/editables-0.4/docs/make.bat 2022-04-09 17:49:08.692220700 +0200
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively
you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.http://sphinx-doc.org/
+ exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/requirements.txt
new/editables-0.4/docs/requirements.txt
--- old/editables-0.3/docs/requirements.txt 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/docs/requirements.txt 2022-04-09 17:49:08.692220700
+0200
@@ -0,0 +1,3 @@
+sphinx
+myst_parser
+furo
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/source/conf.py
new/editables-0.4/docs/source/conf.py
--- old/editables-0.3/docs/source/conf.py 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/docs/source/conf.py 2023-07-06 16:58:12.743793700
+0200
@@ -0,0 +1,56 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = "editables"
+copyright = "2021, Paul Moore"
+author = "Paul Moore"
+
+# The full version, including alpha/beta/rc tags
+release = "0.4"
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+ "myst_parser",
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ["_templates"]
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = []
+
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. See the documentation for
+# a list of builtin themes.
+#
+html_theme = "furo"
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ["_static"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/source/implementation.md
new/editables-0.4/docs/source/implementation.md
--- old/editables-0.3/docs/source/implementation.md 1970-01-01
01:00:00.000000000 +0100
+++ new/editables-0.4/docs/source/implementation.md 2023-06-29
17:09:33.531062800 +0200
@@ -0,0 +1,117 @@
+# Implementation Details
+
+The key feature of a project that is installed in "editable mode" is that the
+code for the project remains in the project's working directory, and what gets
+installed into the user's Python installation is simply a "pointer" to that
+code. The implication of this is that the user can continue to edit the project
+source, and expect to see the changes reflected immediately in the Python
+interpreter, without needing to reinstall.
+
+The exact details of how such a "pointer" works, and indeed precisely how much
+of the project is exposed to Python, are generally considered to be
+implementation details, and users should not concern themselves too much with
+how things work "under the hood". However, there are practical implications
+which users of this library (typically build backend developers) should be
aware
+of.
+
+The basic import machinery in Python works by scanning a list of directories
+recorded in `sys.path` and looking for Python modules and packages in these
+directories. (There's a *lot* more complexity behind the scenes, and interested
+readers are directed to [the Python documentation](https://docs.python.org) for
+more details). The initial value of `sys.path` is set by the interpreter, but
+there are various ways of influencing this.
+
+As part of startup, Python checks various "site directories" on `sys.path` for
+files called `*.pth`. In their simplest form, `.pth` files contain a list of
+directory names, which are *added* to `sys.path`. In addition, for more
advanced
+cases, `.pth` files can also run executable code (typically, to set up import
+hooks to further configure the import machinery).
+
+## Editables using `.pth` entries
+
+The simplest way of setting up an editable project is to install a `.pth` file
+containing a single line specifying the project directory. This will cause the
+project directory to be added to `sys.path` at interpreter startup, making it
+available to Python in "editable" form.
+
+This is the approach which has been used by setuptools for many years, as part
+of the `setup.py develop` command, and subsequently exposed by pip under the
+name "editable installs", via the command `pip install --editable
<project_dir>`.
+
+In general, this is an extremely effective and low-cost approach to
implementing
+editable installs. It does, however, have one major disadvantage, in that it
does
+*not* necessarily expose the same packages as a normal install would do. If the
+project is not laid out with this in mind, an editable install may expose
importable
+files that were not intended. For example, if the project root directory is
added
+directly to the `.pth` file, `import setup` could end up running the project's
+`setup.py`! However, the recommended project layout, putting the Python source
in
+a `src` subdirectory (with the `src` directory then being what gets added to
+`sys.path`) reduces the risk of such issues significantly.
+
+The `editables` project implements this approach using the `add_to_path`
method.
+
+## Package-specific paths
+
+If a package sets the `__path__` variable to a list of those directories, the
+import system will search those directories when looking for subpackages or
+submodules. This allows the user to "graft" a directory into an existing
package,
+simply by setting an appropriate `__path__` value.
+
+The `editables` project implements this approach using the `add_to_subpackage`
method.
+
+## Import hooks
+
+Python's import machinery includes an "import hook" mechanism which in theory
+allows almost any means of exposing a package to Python. Import hooks have been
+used to implement importing from zip files, for example. It is possible,
therefore,
+to write an import hook that exposes a project in editable form.
+
+The `editables` project implements an import hook that redirects the import of
a
+package to a filesystem location specifically designated as where that
package's
+code is located. By using this import hook, it is possible to exercise precise
+control over what is exposed to Python. For details of how the hook works,
+readers should investigate the source of the `editables.redirector` module,
part
+of the `editables` package.
+
+The `editables` project implements this approach for the `map` method. The
+`.pth` file that gets written loads the redirector and calls a method on it
+to add the requested mappings to it.
+
+There are two downsides to this approach, as compared to the simple `.pth` file
+mechanism - lack of support for implicit namespace packages, and the need for
+runtime support code.
+
+The first issue (lack of support for implicit namespace packages) is
+unfortunate, but inherent in how Python (currently) implements the feature.
+Implicit namespace package support is handled as part of how the core import
+machinery does directory scans, and does not interact properly with the import
+hook mechanisms. As a result, the `editables` import hook does not support
+implicit namespace packages, and will probably never be able to do so without
+help from the core Python implementation[^1].
+
+The second issue (the need for runtime support) is more of an inconvenience
than
+a major problem. Because the implementation of the import hook is non-trivial,
+it should be shared between all editable installs, to avoid conflicts between
+import hooks, and performance issues from having unnecessary numbers of
+identical hooks running. As a consequence, projects installed in this manner
+will have a runtime dependency on the hook implementation (currently
distributed
+as part of `editables`, although it could be split out into an independent
+project).
+
+## Reserved Names
+
+The `editables` project uses the following file names when building an editable
+wheel. These should be considered reserved. While backends would not normally
+add extra files to wheels generated using this library, they are allowed to do
+so, as long as those files don't use any of the reserved names.
+
+1. `<project_name>.pth`
+2. `_editable_impl_<project_name>*.py`
+
+Here, `<project_name>` is the name supplied to the `EditableProject`
constructor,
+normalised as described in [PEP
503](https://peps.python.org/pep-0503/#normalized-names),
+with dashes replaced by underscores.
+
+[^1]: The issue is related to how the same namespace can be present in multiple
+ `sys.path` entries, and must be dynamically recomputed if the filesystem
+ changes while the interpreter is running.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/source/index.md
new/editables-0.4/docs/source/index.md
--- old/editables-0.3/docs/source/index.md 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/docs/source/index.md 2023-07-06 16:57:18.057853000
+0200
@@ -0,0 +1,22 @@
+% editables documentation master file, created by
+% sphinx-quickstart on Sun Apr 25 10:00:23 2021.
+% You can adapt this file completely to your liking, but it should at least
+% contain the root `toctree` directive.
+
+# Building editable wheels
+
+```{toctree}
+---
+maxdepth: 2
+caption: Contents
+---
+usage
+implementation
+use-cases
+```
+
+# Indices and tables
+
+* {ref}`genindex`
+* {ref}`modindex`
+* {ref}`search`
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/source/usage.md
new/editables-0.4/docs/source/usage.md
--- old/editables-0.3/docs/source/usage.md 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/docs/source/usage.md 2023-07-06 16:57:18.106888300
+0200
@@ -0,0 +1,119 @@
+# Basic workflow
+
+The `editables` project is designed to support *build backends*, allowing them
+to declare what they wish to expose as "editable", and returning a list of
+support files that need to be included in the wheel generated by the
+`build_editable` [backend
hook](https://peps.python.org/pep-0660/#build-editable).
+Note that the `editables` library does not build wheel files directly - it
+returns the content that needs to be added to the wheel, but it is the build
+backend's responsibility to actually create the wheel from that data.
+
+## Create a project
+
+The first step is for the backend to create an "editable project". The project
+name must follow the normal rules for Python project names from
+[PEP 426](https://peps.python.org/pep-0426/#name).
+
+```python
+project = EditableProject("myproject")
+```
+
+## Specify what to expose
+
+Once the project has been created, the backend can specify which files should
be
+exposed when the editable install is done. There are two mechanisms currently
+implemented for this.
+
+### Adding a directory to `sys.path`
+
+To add a particular directory (typically the project's "src" directory) to
+`sys.path` at runtime, simply call the `add_to_path` method
+
+```python
+project.add_to_path("src")
+```
+
+This will simply write the given directory into the `.pth` file added to the
+wheel. See the "Implementation Details" section for more information. Note that
+this method requires no runtime support.
+
+### Adding a directory as package content
+
+To expose a directory as a package on `sys.path`, call the `add_to_subpackage`
+method, giving the package name to use, and the path to the directory
containing
+the contents of that package.
+
+For example, if the directory `src` contains a package `my_pkg`, which you want
+to expose to the target interpreter as `some.package.my_pkg`, run the
following:
+
+```python
+project.add_to_subpackage("some.package", "src")
+```
+
+Note that everything in the source directory will be available under the given
+package name, and the source directory should *not* contain an `__init__.py`
+file (if it does, that file will simply be ignored).
+
+Also, the target (`some.package` here) must *not* be an existing package that
+is already part of the editable wheel. This is because its `__init__.py` file
+will be overwritten by the one created by this method.
+
+# Mapping individual files/packages
+
+To expose a single `.py` file as a module, call the `map` method, giving the
+name by which the module can be imported, and the path to the implementation
+`.py` file. It *is* possible to give the module a name that is not the same as
+the implementation filename, although this is expected to be extremely
uncommon.
+
+```python
+project.map("module", "src/module.py")
+```
+
+To expose a directory with an `__init__.py` file as a package, the `map`
+method is used in precisely the same way, but with the directory name:
+
+```python
+project.map("mypackage", "src/mypackage")
+```
+
+The directory *must* be a Python package - i.e., it must contain an
`__init__.py`
+file, and the target package name must be a top-level name, not a dotted name.
+
+Using the `map` method does require a runtime support module.
+
+## Build the wheel
+
+### Files to add
+
+Once all of the content to expose is specified, the backend can start building
+the wheel. To determine what files to write to the wheel, the `files` method
+should be used. This returns a sequence of pairs, each of which specifies a
+filename, and the content to write to that file. Both the name and the content
+are strings, and so should be encoded appropriately (i.e., in UTF-8) when
+writing to the wheel.
+
+```python
+for name, content in my_project.files():
+ wheel.add_file(name, content)
+```
+
+Note that the files to be added must be included unchanged - it is *not*
+supported for the caller to modify the returned content. Also, it is the
+caller's responsibility to ensure that none of the generated files clash with
+files that the caller is adding to the wheel as part of its own processes.
+
+### Runtime dependencies
+
+If the `map` method is used, the resulting wheel will require that the runtime
+support module is installed. To ensure that is the case, dependency metadata
+must be added to the wheel. The `dependencies` method provides the required
+metadata.
+
+```python
+for dep in my_project.dependencies():
+ wheel.metadata.dependencies.add(dep)
+```
+
+Note that if the backend only uses the `add_to_path` method, no runtime support
+is needed, so the `dependencies` method will return an empty list. For safety,
+and to protect against future changes, it should still be called, though.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/docs/source/use-cases.md
new/editables-0.4/docs/source/use-cases.md
--- old/editables-0.3/docs/source/use-cases.md 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/docs/source/use-cases.md 2023-07-06 16:57:18.111892200
+0200
@@ -0,0 +1,120 @@
+# Use Cases
+
+We will cover here the main supported use cases for editable installs,
+including the recommended approaches for exposing the files to the
+import system.
+
+## Project directory installed "as is"
+
+A key example of this is the recommended "`src` layout" for a project,
+where a single directory (typically named `src`) is copied unchanged
+into the target site-packages.
+
+For this use case, the `project.add_to_path` method is ideal, making
+the project directory available to the import system directly.
+
+There are almost no downsides to this approach, as it is using core
+import system mechanisms to manage `sys.path`. Furthermore, the method
+is implemented using `.pth` files, which are recognised by static analysis
+tools such as type checkers, and so editable installs created using this
+method will be visible in such tools.
+
+## Project directory installed under an explicit package name
+
+This is essentially the same as the previous use case, but rather than
+installing the project directory directly into site-packages, it is
+installed under a partocular package name. So, for example, if the
+project has a `src` directory containing a package `foo` and a module
+`bar.py`, the requirement is to install the contents of `src` as
+`my.namespace.foo` and `my.namespace.bar`.
+
+For this use case, the `project.add_to_subpackage` method is available.
+This method creates the `my.namespace` package (by installing an `__init__.py`
+file for it into site-packages) and gives that package a `__path__` attribute
+pointing to the source directory to be installed under that package name.
+
+Again, this approach uses core import system mechanisms, and so will have
+few or no downsides at runtime. However, because this approach relies on
+*runtime* manipulation of `sys.path`, it will not be recognised by static
+analysis tools.
+
+## Installing part of a source directory
+
+The most common case for this is a "flat" project layout, where the
+package and module files to be installed are stored alongside project
+files such as `pyproject.toml`. This layout is typically *not* recommended,
+particularly for new projects, although older projects may be using this
+type of layout for historical reasons.
+
+The core import machinery does not provide a "native" approach supporting
+excluding part of a directory like this, so custom import hooks are needed
+to implement it. At the time of writing, all such custom hook implementations
+have limitations, and should be considered experimental. As a result, build
+backends should *always* prefer one of the other implementation methods when
+available.
+
+The `project.map` method allows mapping of either a single Python file, or
+a Python package directory, to an explicit top-level name in the import system.
+It does this by installing a `.pth` file and a Python module. The `.pth` file
+simply runs the Python module, and the module installs the requested set of
+mappings using an import hook exported by the `editables` module.
+
+Downsides of this approach are:
+
+1. The approach depends on the ability to run executable code from a `.pth`
+ file. While this is a supported capability of `.pth` files, it is
+ considered a risk, and there have been proposals to remove it. If that
+ were to happen, this mechanism would no longer work.
+2. It adds a *runtime* dependency on the `editables` module, rather than
+ just a build-time dependency.
+3. The import hook has known limitations when used with implicit namespace
+ packages - there is [a CPython
issue](https://github.com/python/cpython/issues/92054)
+ discussing some of the problems.
+
+## Unsupported use cases
+
+In addition to the above there are a number of use cases which are explicitly
+**not** supported by this library. That is not to say that editable installs
+cannot do these things, simply that the build backend will need to provide
+its own support.
+
+### Metadata changes
+
+This library does not support dynamically changing installed project metadata
+when the project source changes. Typically, a reinstall is needed in those
+cases. A significant example of a metadata change is a change to the script
+entry points, which affects what command-line executables are installed.
+
+### Binary extensions
+
+Binary extensions require a build step when the source code is changed. This
+library does not support any sort of automatic rebuilding, nor does it
+support automatic reinstallation of binaries.
+
+The build backend may choose to expose the "working" version of the built
+binary, for example by placing a symbolic link to the binary in a directory
+that is visible to the import system as a result of `project.add_to_path`,
+but that would need to be implemented by the backend.
+
+### Mapping non-Python directories or files
+
+The methods of an editable project are all intended explicitly for exposing
+*Python code* to the import system. Other types of resource, such as data
+files, are *not* supported, except in the form of package data physically
+located in a Python package directory in the source.
+
+### Combining arbitrary code into a package
+
+The library assumes that a typical project layout, at least roughly, matches
+the installed layout - and in particular that Python package directories are
+"intact" in the source. Build backends can support more complex structures,
+but in order to expose them as editable installs, they need to create some
+form of "live" reflection of the final layout in a local directory (for
+example by using symbolic links) and create the editable install using that
+shadow copy of the source.
+
+It is possible that a future version of this library may add support for
+more complex mappings of this form, but that would likely require a
+significant enhancement to the import hook mechanism being used, and would
+be a major, backward incompatible, change. There are currently no plans for
+such a feature, though.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/pyproject.toml
new/editables-0.4/pyproject.toml
--- old/editables-0.3/pyproject.toml 2022-04-09 17:13:03.000000000 +0200
+++ new/editables-0.4/pyproject.toml 2023-07-06 16:58:12.742811700 +0200
@@ -1,3 +1,38 @@
[build-system]
-requires = ["setuptools>=42"]
-build-backend = "setuptools.build_meta"
+requires = ["flit_core >=3.3"]
+build-backend = "flit_core.buildapi"
+
+[project]
+name = "editables"
+version = "0.4"
+description = "Editable installations"
+readme = "README.md"
+requires-python = ">=3.7"
+authors = [{name = "Paul Moore", email = "[email protected]"}]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: MIT License",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.7",
+ "Programming Language :: Python :: 3.8",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Utilities",
+]
+dependencies = []
+
+[project.urls]
+Documentation = "https://editables.readthedocs.io"
+Source = "https://github.com/pfmoore/editables"
+Tracker = "https://github.com/pfmoore/editables/issues"
+
+[tool.flit.sdist]
+include = ["LICENSE*", "tests/", "docs/"]
+exclude = ["docs/build", "tests/__pycache__"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/setup.cfg new/editables-0.4/setup.cfg
--- old/editables-0.3/setup.cfg 2022-04-10 17:33:58.428794000 +0200
+++ new/editables-0.4/setup.cfg 1970-01-01 01:00:00.000000000 +0100
@@ -1,62 +0,0 @@
-[metadata]
-name = editables
-version = 0.3
-description = Editable installations
-long_description = file: README.md
-long_description_content_type = text/markdown
-url = https://github.com/pfmoore/editables
-author = Paul Moore
-author_email = [email protected]
-license = MIT
-license_file = LICENSE.txt
-platforms = any
-classifiers =
- Development Status :: 5 - Production/Stable
- Intended Audience :: Developers
- License :: OSI Approved :: MIT License
- Programming Language :: Python :: 3
- Programming Language :: Python :: 3 :: Only
- Programming Language :: Python :: 3.7
- Programming Language :: Python :: 3.8
- Programming Language :: Python :: 3.9
- Programming Language :: Python :: 3.10
- Programming Language :: Python :: Implementation :: CPython
- Programming Language :: Python :: Implementation :: PyPy
- Topic :: Software Development :: Libraries
- Topic :: Utilities
-keywords = packaging, editables
-maintainer = Paul Moore
-maintainer_email = [email protected]
-project_urls =
- Source=https://github.com/pfmoore/editables
- Tracker=https://github.com/pfmoore/editables/issues
- Documentation=https://editables.readthedocs.io
-
-[options]
-packages = find:
-python_requires = >=3.7
-package_dir =
- =src
-
-[options.packages.find]
-where = src
-
-[sdist]
-formats = gztar
-
-[isort]
-multi_line_output = 3
-include_trailing_comma = True
-force_grid_wrap = 0
-line_length = 88
-known_first_party = editables
-known_third_party = pytest,setuptools,virtualenv
-
-[flake8]
-max-line-length = 88
-ignore = E203, W503
-
-[egg_info]
-tag_build =
-tag_date = 0
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/setup.py new/editables-0.4/setup.py
--- old/editables-0.3/setup.py 2021-04-08 18:24:47.000000000 +0200
+++ new/editables-0.4/setup.py 1970-01-01 01:00:00.000000000 +0100
@@ -1,3 +0,0 @@
-from setuptools import setup
-
-setup()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/src/editables/__init__.py
new/editables-0.4/src/editables/__init__.py
--- old/editables-0.3/src/editables/__init__.py 2022-04-10 17:33:25.000000000
+0200
+++ new/editables-0.4/src/editables/__init__.py 2023-07-06 16:58:12.742811700
+0200
@@ -1,17 +1,19 @@
+import os
import re
from pathlib import Path
+from typing import Dict, Iterable, List, Tuple, Union
__all__ = (
"EditableProject",
"__version__",
)
-__version__ = "0.3"
+__version__ = "0.4"
# Check if a project name is valid, based on PEP 426:
# https://peps.python.org/pep-0426/#name
-def is_valid(name):
+def is_valid(name: str) -> bool:
return (
re.match(r"^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$", name,
re.IGNORECASE)
is not None
@@ -22,7 +24,7 @@
# https://peps.python.org/pep-0503/#normalized-names
# This version uses underscore, so that the result is more
# likely to be a valid import name
-def normalize(name):
+def normalize(name: str) -> str:
return re.sub(r"[-_.]+", "_", name).lower()
@@ -31,19 +33,20 @@
class EditableProject:
- def __init__(self, project_name, project_dir):
+ def __init__(self, project_name: str, project_dir: Union[str,
os.PathLike]) -> None:
if not is_valid(project_name):
raise ValueError(f"Project name {project_name} is not valid")
self.project_name = normalize(project_name)
self.bootstrap = f"_editable_impl_{self.project_name}"
self.project_dir = Path(project_dir)
- self.redirections = {}
- self.path_entries = []
+ self.redirections: Dict[str, str] = {}
+ self.path_entries: List[Path] = []
+ self.subpackages: Dict[str, Path] = {}
- def make_absolute(self, path):
+ def make_absolute(self, path: Union[str, os.PathLike]) -> Path:
return (self.project_dir / path).resolve()
- def map(self, name, target):
+ def map(self, name: str, target: Union[str, os.PathLike]) -> None:
if "." in name:
raise EditableException(
f"Cannot map {name} as it is not a top-level package"
@@ -56,21 +59,27 @@
else:
raise EditableException(f"{target} is not a valid Python package
or module")
- def add_to_path(self, dirname):
+ def add_to_path(self, dirname: Union[str, os.PathLike]) -> None:
self.path_entries.append(self.make_absolute(dirname))
- def files(self):
+ def add_to_subpackage(self, package: str, dirname: Union[str,
os.PathLike]) -> None:
+ self.subpackages[package] = self.make_absolute(dirname)
+
+ def files(self) -> Iterable[Tuple[str, str]]:
yield f"{self.project_name}.pth", self.pth_file()
+ if self.subpackages:
+ for package, location in self.subpackages.items():
+ yield self.package_redirection(package, location)
if self.redirections:
yield f"{self.bootstrap}.py", self.bootstrap_file()
- def dependencies(self):
+ def dependencies(self) -> List[str]:
deps = []
if self.redirections:
deps.append("editables")
return deps
- def pth_file(self):
+ def pth_file(self) -> str:
lines = []
if self.redirections:
lines.append(f"import {self.bootstrap}")
@@ -78,7 +87,12 @@
lines.append(str(entry))
return "\n".join(lines)
- def bootstrap_file(self):
+ def package_redirection(self, package: str, location: Path) -> Tuple[str,
str]:
+ init_py = package.replace(".", "/") + "/__init__.py"
+ content = f"__path__ = [{str(location)!r}]"
+ return init_py, content
+
+ def bootstrap_file(self) -> str:
bootstrap = [
"from editables.redirector import RedirectingFinder as F",
"F.install()",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/src/editables/redirector.py
new/editables-0.4/src/editables/redirector.py
--- old/editables-0.3/src/editables/redirector.py 2021-06-12
13:40:39.000000000 +0200
+++ new/editables-0.4/src/editables/redirector.py 2023-06-28
18:25:44.327179200 +0200
@@ -1,16 +1,24 @@
+import importlib.abc
+import importlib.machinery
import importlib.util
import sys
+from types import ModuleType
+from typing import Dict, Optional, Sequence, Union
+ModulePath = Optional[Sequence[Union[bytes, str]]]
-class RedirectingFinder:
- _redirections = {}
+
+class RedirectingFinder(importlib.abc.MetaPathFinder):
+ _redirections: Dict[str, str] = {}
@classmethod
- def map_module(cls, name, path):
+ def map_module(cls, name: str, path: str) -> None:
cls._redirections[name] = path
@classmethod
- def find_spec(cls, fullname, path=None, target=None):
+ def find_spec(
+ cls, fullname: str, path: ModulePath = None, target:
Optional[ModuleType] = None
+ ) -> Optional[importlib.machinery.ModuleSpec]:
if "." in fullname:
return None
if path is not None:
@@ -23,7 +31,7 @@
return spec
@classmethod
- def install(cls):
+ def install(cls) -> None:
for f in sys.meta_path:
if f == cls:
break
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/src/editables.egg-info/PKG-INFO
new/editables-0.4/src/editables.egg-info/PKG-INFO
--- old/editables-0.3/src/editables.egg-info/PKG-INFO 2022-04-10
17:33:57.000000000 +0200
+++ new/editables-0.4/src/editables.egg-info/PKG-INFO 1970-01-01
01:00:00.000000000 +0100
@@ -1,97 +0,0 @@
-Metadata-Version: 2.1
-Name: editables
-Version: 0.3
-Summary: Editable installations
-Home-page: https://github.com/pfmoore/editables
-Author: Paul Moore
-Author-email: [email protected]
-Maintainer: Paul Moore
-Maintainer-email: [email protected]
-License: MIT
-Project-URL: Source, https://github.com/pfmoore/editables
-Project-URL: Tracker, https://github.com/pfmoore/editables/issues
-Project-URL: Documentation, https://editables.readthedocs.io
-Keywords: packaging,editables
-Platform: any
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: MIT License
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3 :: Only
-Classifier: Programming Language :: Python :: 3.7
-Classifier: Programming Language :: Python :: 3.8
-Classifier: Programming Language :: Python :: 3.9
-Classifier: Programming Language :: Python :: 3.10
-Classifier: Programming Language :: Python :: Implementation :: CPython
-Classifier: Programming Language :: Python :: Implementation :: PyPy
-Classifier: Topic :: Software Development :: Libraries
-Classifier: Topic :: Utilities
-Requires-Python: >=3.7
-Description-Content-Type: text/markdown
-License-File: LICENSE.txt
-
-# A Python library for creating "editable wheels"
-
-This library supports the building of wheels which, when installed, will
-expose packages in a local directory on `sys.path` in "editable mode". In
-other words, changes to the package source will be reflected in the package
-visible to Python, without needing a reinstall.
-
-## Usage
-
-Suppose you want to build a wheel for your project `foo`. Your project is
-located in the directory `/path/to/foo`. Under that directory, you have a
-`src` directory containing your project, which is a package called `foo`
-and a Python module called `bar.py`. So your directory structure looks like
-this:
-
-```
-/path/to/foo
-|
-+-- src
-| +-- foo
-| | +-- __init__.py
-| +-- bar.py
-|
-+-- setup.py
-+-- other files
-```
-
-Build your wheel as follows:
-
-```python
-from editables import EditableProject
-
-my_project = EditableProject("foo", "/path/to/foo")
-my_project.add_to_path("src")
-
-# Build a wheel however you prefer...
-wheel = BuildAWheel()
-
-# Add files to the wheel
-for name, content in my_project.files():
- wheel.add_file(name, content)
-
-# Record any runtime dependencies
-for dep in my_project.dependencies():
- wheel.metadata.dependencies.add(dep)
-```
-
-The resulting wheel will, when installed, put the project `src` directory on
-`sys.path` so that editing the original source will take effect without needing
-a reinstall (i.e., as "editable" packages). The project is exposed on
`sys.path`
-by adding a single `.pth` file, named after the project, into the wheel.
-
-For more details, including how to control what gets exposed more precisely,
see
-[the documentation](https://editables.readthedocs.io/en/latest/).
-
-Note that this project doesn't build wheels directly. That's the responsibility
-of the calling code.
-
-## Python Compatibility
-
-This project supports the same versions of Python as pip does. Currently
-that is Python 3.7 and later, and PyPy3 (although we don't test against
-PyPy).
-
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/src/editables.egg-info/SOURCES.txt
new/editables-0.4/src/editables.egg-info/SOURCES.txt
--- old/editables-0.3/src/editables.egg-info/SOURCES.txt 2022-04-10
17:33:58.000000000 +0200
+++ new/editables-0.4/src/editables.egg-info/SOURCES.txt 1970-01-01
01:00:00.000000000 +0100
@@ -1,11 +0,0 @@
-LICENSE.txt
-README.md
-pyproject.toml
-setup.cfg
-setup.py
-src/editables/__init__.py
-src/editables/redirector.py
-src/editables.egg-info/PKG-INFO
-src/editables.egg-info/SOURCES.txt
-src/editables.egg-info/dependency_links.txt
-src/editables.egg-info/top_level.txt
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/editables-0.3/src/editables.egg-info/dependency_links.txt
new/editables-0.4/src/editables.egg-info/dependency_links.txt
--- old/editables-0.3/src/editables.egg-info/dependency_links.txt
2022-04-10 17:33:58.000000000 +0200
+++ new/editables-0.4/src/editables.egg-info/dependency_links.txt
1970-01-01 01:00:00.000000000 +0100
@@ -1 +0,0 @@
-
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/src/editables.egg-info/top_level.txt
new/editables-0.4/src/editables.egg-info/top_level.txt
--- old/editables-0.3/src/editables.egg-info/top_level.txt 2022-04-10
17:33:58.000000000 +0200
+++ new/editables-0.4/src/editables.egg-info/top_level.txt 1970-01-01
01:00:00.000000000 +0100
@@ -1 +0,0 @@
-editables
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/tests/requirements.txt
new/editables-0.4/tests/requirements.txt
--- old/editables-0.3/tests/requirements.txt 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/tests/requirements.txt 2023-06-28 14:32:00.757014800
+0200
@@ -0,0 +1,5 @@
+pip >= 20.1
+coverage >= 5
+pytest-coverage
+pytest >= 4
+virtualenv >= 20
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/tests/test_editable.py
new/editables-0.4/tests/test_editable.py
--- old/editables-0.3/tests/test_editable.py 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/tests/test_editable.py 2023-06-29 17:09:33.532063500
+0200
@@ -0,0 +1,162 @@
+import contextlib
+import os
+import site
+import sys
+from pathlib import Path
+
+import pytest
+
+from editables import EditableException, EditableProject
+
+# Use a project name that is not a valid Python identifier,
+# to test that it gets normalised correctly
+PROJECT_NAME = "my-project"
+
+
+def build_project(target, structure):
+ target.mkdir(exist_ok=True, parents=True)
+ for name, content in structure.items():
+ path = target / name
+ if isinstance(content, str):
+ # If the name contains slashes, create any
+ # required parent directories
+ path.parent.mkdir(exist_ok=True, parents=True)
+ path.write_text(content, encoding="utf-8")
+ else:
+ build_project(path, content)
+
+
+# to test in-process:
+# Put stuff in somedir
+# sys.path.append("somedir")
+# site.addsitedir("somedir")
+# Check stuff is visible
[email protected]
+def import_state(extra_site=None):
+ extra_site = os.fspath(extra_site)
+ orig_modules = set(sys.modules.keys())
+ orig_path = list(sys.path)
+ orig_meta_path = list(sys.meta_path)
+ orig_path_hooks = list(sys.path_hooks)
+ orig_path_importer_cache = sys.path_importer_cache
+ if extra_site:
+ sys.path.append(extra_site)
+ site.addsitedir(extra_site)
+ try:
+ yield
+ finally:
+ remove = [key for key in sys.modules if key not in orig_modules]
+ for key in remove:
+ del sys.modules[key]
+ sys.path[:] = orig_path
+ sys.meta_path[:] = orig_meta_path
+ sys.path_hooks[:] = orig_path_hooks
+ sys.path_importer_cache.clear()
+ sys.path_importer_cache.update(orig_path_importer_cache)
+
+
[email protected]
+def project(tmp_path):
+ project = tmp_path / "project"
+ structure = {
+ "foo": {
+ "__init__.py": "print('foo')",
+ "bar": {"__init__.py": "print('foo.bar')"},
+ "baz": {"__init__.py": "print('foo.baz')"},
+ }
+ }
+ build_project(project, structure)
+ yield project
+
+
+def test_invalid_project():
+ with pytest.raises(ValueError):
+ _ = EditableProject("a$b", "")
+
+
+def test_nonexistent_module(project):
+ p = EditableProject(PROJECT_NAME, project)
+ with pytest.raises(EditableException):
+ p.map("foo", "xxx")
+
+
+def test_not_toplevel(project):
+ p = EditableProject(PROJECT_NAME, project)
+ with pytest.raises(EditableException):
+ p.map("foo.bar", "foo/bar")
+
+
[email protected](
+ "name,expected",
+ [
+ ("_invalid", None),
+ ("invalid_", None),
+ ("invalid%character", None),
+ ("project", "project.pth"),
+ ("Project", "project.pth"),
+ ("project_1", "project_1.pth"),
+ ("project-1", "project_1.pth"),
+ ("project.1", "project_1.pth"),
+ ("project---1", "project_1.pth"),
+ ("project-._1", "project_1.pth"),
+ ("0leading_digit_ok", "0leading_digit_ok.pth"),
+ ],
+)
+def test_project_names_normalised(name, expected):
+ try:
+ # Tricky here. We create a dummy project, add
+ # an empty directory name to the path,
+ # then get the list of files generated.
+ # The .pth file should always be the first one,
+ # and we only care about the first item (the name)
+ p = EditableProject(name, "")
+ p.add_to_path("")
+ pth = next(p.files())[0]
+ except ValueError:
+ # If the project name isn't valid, we don't
+ # expect a pth file
+ pth = None
+ assert pth == expected
+
+
+def test_dependencies(project):
+ p = EditableProject(PROJECT_NAME, project)
+ assert len(p.dependencies()) == 0
+ p.map("foo", "foo")
+ assert len(p.dependencies()) == 1
+
+
+def test_simple_pth(tmp_path, project):
+ p = EditableProject(PROJECT_NAME, project)
+ p.add_to_path(".")
+ structure = {name: content for name, content in p.files()}
+ site_packages = tmp_path / "site-packages"
+ build_project(site_packages, structure)
+ with import_state(extra_site=site_packages):
+ import foo
+
+ assert Path(foo.__file__) == project / "foo/__init__.py"
+
+
+def test_make_project(project, tmp_path):
+ p = EditableProject(PROJECT_NAME, project)
+ p.map("foo", "foo")
+ structure = {name: content for name, content in p.files()}
+ site_packages = tmp_path / "site-packages"
+ build_project(site_packages, structure)
+ with import_state(extra_site=site_packages):
+ import foo
+
+ assert Path(foo.__file__) == project / "foo/__init__.py"
+
+
+def test_subpackage_pth(tmp_path, project):
+ p = EditableProject(PROJECT_NAME, project)
+ p.add_to_subpackage("a.b", ".")
+ structure = {name: content for name, content in p.files()}
+ site_packages = tmp_path / "site-packages"
+ build_project(site_packages, structure)
+ with import_state(extra_site=site_packages):
+ import a.b.foo
+
+ assert Path(a.b.foo.__file__) == project / "foo/__init__.py"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/editables-0.3/tests/test_redirects.py
new/editables-0.4/tests/test_redirects.py
--- old/editables-0.3/tests/test_redirects.py 1970-01-01 01:00:00.000000000
+0100
+++ new/editables-0.4/tests/test_redirects.py 2021-06-12 13:40:39.885457500
+0200
@@ -0,0 +1,83 @@
+import contextlib
+import sys
+
+from editables.redirector import RedirectingFinder as F
+
+
[email protected]
+def save_import_state():
+ orig_modules = set(sys.modules.keys())
+ orig_path = list(sys.path)
+ orig_meta_path = list(sys.meta_path)
+ orig_path_hooks = list(sys.path_hooks)
+ orig_path_importer_cache = sys.path_importer_cache
+ try:
+ yield
+ finally:
+ remove = [key for key in sys.modules if key not in orig_modules]
+ for key in remove:
+ del sys.modules[key]
+ sys.path[:] = orig_path
+ sys.meta_path[:] = orig_meta_path
+ sys.path_hooks[:] = orig_path_hooks
+ sys.path_importer_cache.clear()
+ sys.path_importer_cache.update(orig_path_importer_cache)
+ # HACK
+ F._redirections = {}
+
+
+def build(target, structure):
+ target.mkdir(exist_ok=True, parents=True)
+ for name, content in structure.items():
+ path = target / name
+ if isinstance(content, str):
+ path.write_text(content, encoding="utf-8")
+ else:
+ build(path, content)
+
+
+def test_double_install():
+ with save_import_state():
+ old_len = len(sys.meta_path)
+ F.install()
+ F.install()
+ assert len(sys.meta_path) == old_len + 1
+
+
+def test_toplevel_only():
+ assert F.find_spec("foo.bar") is None
+
+
+def test_no_path():
+ assert F.find_spec("foo", path=[]) is None
+
+
+def test_no_map_returns_none():
+ assert F.find_spec("foo") is None
+
+
+def test_redirects(tmp_path):
+ project = tmp_path / "project"
+ project_files = {
+ "mod.py": "val = 42",
+ "pkg": {
+ "__init__.py": "val = 42",
+ "sub.py": "val = 42",
+ },
+ }
+ build(project, project_files)
+
+ with save_import_state():
+ F.install()
+ F.map_module("mod", project / "mod.py")
+ F.map_module("pkg", project / "pkg/__init__.py")
+
+ import mod
+
+ assert mod.val == 42
+ import pkg
+
+ assert pkg.val == 42
+ import pkg.sub
+
+ assert pkg.sub.val == 42