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

Reply via email to