Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch to PEP 420 (Implicit namespace packages) #194

Open
2 of 12 tasks
icemac opened this issue Mar 8, 2023 · 47 comments
Open
2 of 12 tasks

Switch to PEP 420 (Implicit namespace packages) #194

icemac opened this issue Mar 8, 2023 · 47 comments
Labels
enhancement New feature or request question Further information is requested

Comments

@icemac
Copy link
Member

icemac commented Mar 8, 2023

Currently we use pkg_resources-style namespace packages. Using pkg_resources is deprecated (see https://setuptools.pypa.io/en/latest/pkg_resources.html), it seems not yet clear when it is removed from setuptools.

The current future are PEP 420: Implicit namespace packages.

This requires:

  • all the packages sharing a namespace to switch over to PEP 420.
  • removing the __init__.py files existing to create a namespace
  • removing namespace_packages declaration from setup.py
  • maybe removal of the dependency on setuptools? (Maybe we could even switch to pyproject.toml.)

zopefoundation hosts the following namespaces:

  • zope, zope.app – 117 repos
  • z3c, z3c.recipe, z3c.layer, z3c.formwidget, z3c.menu – 43 repos
  • zc, zc.recipe – 22 repos
  • Products – 22 repos
  • keas (probably only some packages of that namespace) – 1 repo
  • refline (probably only some packages of that namespace) - 1 repo
  • grokcore – 17 repos
  • grokui – all repos archived
  • hurry – 1 repos (hurry.query)
  • cipher – 2 repos
  • five – 4 repos
  • megrok: 1 repos (Switch to implicit (PEP 420) namespace. megrok.strictrequire#8)

See the discussion in pypa/setuptools#3434 for some more details.

Suggestion:

  • Try out on a small namespace where probably all packages are hosted by zopefoundation (grokcore?, z3c?, zope.app?)
  • If this works fine (especially in zc.buildout), adapt the other namespaces.
    • This will require coordination with the Plone folks.
    • This would also require a big warning sign that other packages hosted outside zopefoundation but using the same namespaces also have to switch to PEP 420.

Cc-ing: @dataflake @mauritsvanrees @gforcada @mgedmin @jensens

See also https://packaging.python.org/en/latest/guides/packaging-namespace-packages/

@icemac icemac added enhancement New feature or request question Further information is requested labels Mar 8, 2023
@dataflake
Copy link
Member

This looks like a ton of work... but if we could really get rid of setuptools it would be worth it. Setuptools is nothing but trouble at this point.

@mauritsvanrees
Copy link
Member

I left two comments in the setuptools discussion, with some numbers. Let's see if we can get a timeline for when setuptools will drop support.

Zope and Plone can only move to native namespaces in a major release. Since Plone 6 was only released a few months ago, I expect it will take two to three years before we have Plone 7, which would need a Zope 6. We may need to speed that up. But I hope we can let this rest for another year.

If we know that for example setuptools 70 is going to drop support, I suppose we could add setuptools<70 in install_requires to prevent installation of incompatible packages side by side.

@jensens
Copy link
Member

jensens commented Mar 8, 2023

+1 to start the process of modernization here. Some remarks:

  • I agree, this change is necessary at some point.
  • I agree with @mauritsvanrees that this is a breaking change at some point and would need a new major Zope release.
  • I think a good idea is to introduce this change incrementally:
    • first all controlled zope.* packages can be changed relatively easy, probably the same is valid for five.*, z3c.* and zc.* (and maybe some other in the list above I do not know/use). This might go into the Zope 5 release series.
    • With the Products.* namespace it gets difficult, this is a bunch of packages, including CMF, Plone and all add-ons. It needs tight coordination.
    • If we switch everything to PEP420 in Plone this would be possible only in a Plone 7.
  • if this topic is touched I would switch to full to pyproject.yml w/o any setup.* along with it. For my company projects I use already PEP420 style packages along with pyproject.toml w/o any problems.
  • ad getting rid of Setuptools: What is the idea behind this?
    • Setuptools is settled, widely used, has lots of tooling available and is relatively fast.
    • What is the alternative we want? We need some PEP 518 [build-system]. I am not that a fan of Poetry, did not try Flit or PDM.

@dataflake
Copy link
Member

The idea of getting rid of setuptools was not based on any knowledge of replacements, but on some comments in that setuptools discussion on GitHub. It may not be possible. Where I come from is endless issues with the Zope packages that use C-code in conjunction with macOS and the different builds that exist for it (x86_64, aarch64 and universal2). I described that at #181.

@icemac
Copy link
Member Author

icemac commented Mar 9, 2023

I am not aware of an alternative to setuptools which can handle C code. But I am open to learn if there is one.

@mgedmin
Copy link
Member

mgedmin commented Mar 9, 2023

Request for clarification: is it possible to mix and match PEP-420 namespace packages with pkg_resources namespace packages from the same namespace in the same virtualenv?

Are we talking about a flag day here, with major version bumps and coordinated releases for every zope.* package at the same time? (And similar efforts for zc.*, z3c.*, grok.*, collective.* namespaces)?

@d-maurer
Copy link

d-maurer commented Mar 9, 2023 via email

@d-maurer
Copy link

d-maurer commented Mar 9, 2023 via email

@jensens
Copy link
Member

jensens commented Mar 9, 2023

Request for clarification: is it possible to mix and match PEP-420 namespace packages with pkg_resources namespace packages from the same namespace in the same virtualenv?

I did not read the PEPs in detail nor the code about it, but about a year ago I just tried it and it just did not work. Ago then, I found some source-on-the-internet stating exact: it does not mix (but currently I can not find it again).

@mauritsvanrees
Copy link
Member

mauritsvanrees commented Mar 9, 2023

That would be the Python Packaging User Guide, which says:

Warning: While native namespace packages and pkgutil-style namespace packages are largely compatible, pkg_resources-style namespace packages are not compatible with the other methods. It’s inadvisable to use different methods in different distributions that provide packages to the same namespace.

It also gives one specific reason for using pkg_resources-style namespace packages:
"This method is recommended if you need compatibility with packages already using this method or if your package needs to be zip-safe."

Do we need that for some reason?
I guess not: in a lot of Zope and Plone package I see zip_safe=False, so the other way around.

@jensens
Copy link
Member

jensens commented Mar 10, 2023

"This method is recommended if you need compatibility with packages already using this method or if your package needs to be zip-safe."

Do we need that for some reason?

"It is very unlikely that the values of zip_safe will affect modern deployments that use pip for installing packages. Moreover, new users of setuptools should not attempt to create egg files using the deprecated build_egg command. Therefore, this flag is considered obsolete." https://setuptools.pypa.io/en/latest/deprecated/zip_safe.html

@graingert
Copy link

graingert commented Mar 15, 2023

for the jaraco. namespace @jaraco was able to do a staged migration to pkgutil namespace packages see pypa/setuptools#3434 (comment)

I think the packaging tutorial docs may be incorrect as there's a test for pkg_resources and pkgutil namespaces in one virtual environment https://github.com/pypa/sample-namespace-packages/blob/df7530eeb8fa0cb7dbb8ecb28363e8e36bfa2f45/noxfile.py#L79-L88 which shows a staged migration is possible

@graingert
Copy link

There's now work to test staged migration of namespace packages in pypa/sample-namespace-packages#22 referring to the table it looks like as long as you don't mind about not supporting pip install -e . a staged migration to pep 420 is supported

@mauritsvanrees
Copy link
Member

Let's see if I understand it correctly.

So "staged migration" means: migrate some packages in a namespace to pep420 while some others still use pkg_resources?

For Zope/Plone in that table the lines near the bottom with "cross_pkg_resources_pep420" are the relevant ones. I don't see a difference in behavior for Python versions, so that is good.
And you can use either a normal or editable install for old pkg_resources packages, but the pep420 ones then only work with normal installs.
So editable installs can be tricky, but a standard pip install Zope or Plone will just work, regardless of which type of namespaces are used.

That is better than I had hoped for!

It does make it a bit tricky in the transition period. Say you want to start with the Products namespace and start working on the first package. You do an editable install of the old code, and it works. Then you edit setup.py to move to native namespaces. Now you can no longer do an editable install. That makes it harder to check that it actually works before making a release.
Well, pip install . should still work, so it is not the end of the world. But when you want to fix an actual bug in the code, then after editing your files you need to explicitly run pip install . again, which is a pain.
So we should still try to keep the transition period short.

@icemac
Copy link
Member Author

icemac commented Jan 25, 2024

Heads up: zope.testrunner currently does not support implicit namespaces, see zopefoundation/zope.testrunner#160

@jensens
Copy link
Member

jensens commented Jan 29, 2024

Btw., I wrote I small blog about the mix of old and new style packages in one namespace problem last year, while confronted with this problem while working on a different project: https://yenzenz.com/blog/2023/mixed_python_pep420/

@malthe
Copy link

malthe commented Jan 31, 2024

Here's a vote for pip install -e. – I use that all the time!

jaraco added a commit to jaraco/jaraco.net that referenced this issue Feb 18, 2024
@d-maurer
Copy link

d-maurer commented Jun 8, 2024 via email

@npilon
Copy link

npilon commented Jun 8, 2024

That’s actually exactly what I ran into trying to tinker together a simple solution for one zope subpackage yesterday so I think I have some idea about what’s going on; I’m hoping that starting with zc.buildout I can bypass that.

@d-maurer
Copy link

d-maurer commented Jun 8, 2024 via email

@mauritsvanrees
Copy link
Member

Does anyone already have a timeline in mind for when to starting work on moving all repos to native namespaces?

With my Plone hat on, where we obviously need to do the same, I am inclined to say it is too early now. But somewhere between autumn 2025 and spring 2026 would sound good. That would be in time for a Plone 7 release in the second half of 2026. But this timeline involves looking into a crystal ball, and I am not sure mine is working. ;-)

@icemac
Copy link
Member Author

icemac commented Dec 19, 2024

@mauritsvanrees After resolving the issue in our company that z3c.autoinclude does not support implicit namespaces (just by no longer using that library at all) – I'd be keen to start moving to implicit namespaces sooner than end of next year.

I am tired of the many deprecation warnings and want to do something about it.
I'd probably start with the smaller namespaces like hurry or grokcore.

As this change is backwards in-compatible we might need to backport necessary code changes to the previous versions for a while. (with "we == the people who need specific backports") I think this approach would make it possible to get progress now for "early" adopters and the majority still can stay on the previous releases.

@dataflake
Copy link
Member

Would it make sense to come up with a plan or a priority-based list of package names?

I'm not afraid of the work, that's just simple drudgery. I am afraid if these updates are not coordinated well and all of a sudden things start breaking (like Zope) because some dependencies from the same namespace are converted and some are not. It feels like once you start on a namespace all of its packages should be completed in very quick succession.

Also, we need to brace ourselves when complaints come flooding in from package maintainers that use e.g. Products that their package starts failing without warning.

@mauritsvanrees
Copy link
Member

Doing one namespace completely in short succession seems best. But as far as I know, the only real problem is still when you want to use a checkout or editable install of both a native and non-native package within the same namespace. For packages that have been released, it does not seem to matter. So it is more a problem for people developing on a namespace, than on integrators who just install a final package. But that is still hear-say, so that may need to be checked, both in pip and buildout.

Since Plone also uses the Products namespace, it would be good to do that one last.

@npilon
Copy link

npilon commented Dec 19, 2024

For packages that have been released, it does not seem to matter.

This matches my experience - but note that it's going to be a problem for CI suites because zc.buildout makes basically everything an editable install: #194 (comment)

@icemac
Copy link
Member Author

icemac commented Dec 19, 2024

This matches my experience - but note that it's going to be a problem for CI suites because zc.buildout makes basically everything an editable install: #194 (comment)

This could be the reason why I saw this problem with sphinxcontrib packages which were installed via buildout.

@icemac
Copy link
Member Author

icemac commented Dec 19, 2024

@mauritsvanrees Yes we should do one namespace at a time. Especially the shared ones like Product are tricky, there will even be packages out of our reach which are using this namespace. That's why I'd like to start with the smaller and lesser used ones. Doing zope.* will probably require a shared effort to get it done in a reasonable timeframe.

@dataflake
Copy link
Member

This could be the reason why I saw this problem with sphinxcontrib packages which were installed via buildout.

You're right, I have run into this so many times and always wondered why. On all buildouts that still have some [docs] stanza to create sphinx-build I have ripped it out and rely on tox -edocs instead.

@dataflake
Copy link
Member

That's why I'd like to start with the smaller and lesser used ones. Doing zope.* will probably require a shared effort to get it done in a reasonable timeframe.

I'm game. As @npilon mentioned, we may have broken CI runs, but if we stick with just the minimum needed change set for each package and push/publish even if CI fails we should be able to get it done reasonably quickly.

The C-based packages may present a special problem because the PyPI publishing requires the package to build and the tests to succeed.

@gforcada
Copy link
Member

I'm also interested, and would like to help as well, though I'm a bit unreliable as of late due to personal/family circumstances...

@graingert
Copy link

My understanding is that the standard python whl files, as you would upload to PyPI are fully backwards compatible with the legacy pkg_resources build. Could you default to pkg_resources mode and support py3+ namespace packages by using an environment variable to choose the mode. Then in the near future build all wheels with the new mode opted in, and in the long future default to opted in, and in the far future remove the compat pkg_resources mode?

@dataflake
Copy link
Member

I have never heard of anyone making that package configuration depend on an environment variable. How would that even look like in practice? And why would that be in any way advantageous?

@graingert
Copy link

graingert commented Dec 20, 2024

It would mean anyone relying on building in editable mode from sdist or git would be unaffected

@dataflake
Copy link
Member

The developers here have enough control over the namespaces to convert them quickly enough. There is no need for extra complication. We cannot prevent all problems, that is true, but IMHO the extra effort for such a selective switch is not worth the effort. The effort isn't clear, anyway, because you didn't answer my question how that would look like in practice (code!).

@graingert
Copy link

Ah I didn't understand your question, I assumed you meant how it would appear from a user perspective

@graingert
Copy link

Also looking at the migration table, you wouldn't need an environment variable at all - you can just check the current type of namespace package in use and use that, and for wheels on PyPI use pep420

@dataflake
Copy link
Member

This is all hand waving. From what I know the change involves removing an __init__.py file at the namespace level (e.g. zope.foobar/src/zope/__init__py) which contained a pkg_resources invocation and a change in the arguments to the setup function in setup.py - please corect me if I am wrong. I am looking for specific code that shows me as a package maintainer how you would put these changes behind a check, be it environment variable or something else.

@graingert
Copy link

graingert commented Dec 20, 2024

so in your setup.py you'd have something like (untested psuedo code):

import importlib.resources
import setuptools.commands.build_py
import pathlib
import os

NAMESPACE = "zope"

def _check():
    if os.environ.get("ZOPE_USE_PEP_420", ""):
        return False

    default_init = importlib.resources.files(NAMESPACE) / "__init__.py"

    try:
        if b"pkg_resources" in default_init.read_bytes():
            return True
    except FileNotFoundError:
        return False

    raise RuntimeError(
        "pkgutil ns package in use"
    )  # probably possible to handle this too


USE_PKG_RESOURCES = _check()


def maybe_create_init_py():
    if USE_PKG_RESOURCES:
        (pathlib.Path(NAMESPACE) / "__init__.py").write_bytes(
            b"__import__('pkg_resources').declare_namespace(__name__)"
        )


class BuildPy(setuptools.commands.build_py.build_py):
    def run(self):
        # Base class code
        if self.py_modules:
            self.build_modules()
        if self.packages:
            self.build_packages()
            self.build_package_data()

        # Our modification!
        maybe_create_init_py()

        # Remaining base class code
        self.byte_compile(self.get_outputs(include_bytecode=0))


extra_kwargs = (
    {"cmdclass": {"build_py": BuildPy}, "namespace_packages": NAMESPACE}
    if USE_PKG_RESOURCES
    else {}
)

setuptools.setup(
    ...,
    packages=setuptools.find_namespace_packages(...),
    **extra_kwargs,
)

then you'd set ZOPE_USE_PEP_420 in your github actions to build your whl that gets uploaded to pypi

@icemac
Copy link
Member Author

icemac commented Dec 20, 2024

As an alternative we could set maximum version numbers for the dependencies to the current major release, cut a release. Afterwards the migration is done including requiring the next major version of the dependencies.
This forces to use PEP-420 for everything or nothing.

code example for zope.schema before migration:

REQUIRES = [
    'setuptools',
    'zope.interface >= 5.0.0, <8',
    'zope.event < 6',
]


TESTS_REQUIRE = [
    'zope.i18nmessageid < 8',
    'zope.testing < 6',
    'zope.testrunner < 7',
]


setup(
    name='zope.schema',
    version='7.1.dev0',
...
    namespace_packages=['zope', ],
    install_requires=REQUIRES,
...
    extras_require={
        'docs': [
            'Sphinx',
            'repoze.sphinx.autointerface',
        ],
        'test': TESTS_REQUIRE,
    },
)

These changes then get released as a feature release.

after migration to PEP-420:

REQUIRES = [
    'setuptools',
    'zope.interface >= 8',
    'zope.event >= 6',
]


TESTS_REQUIRE = [
    'zope.i18nmessageid >= 8',
    'zope.testing >= 6',
    'zope.testrunner >= 7',
]


setup(
    name='zope.schema',
    version='8.0.dev0',
...
    install_requires=REQUIRES,
...
    extras_require={
        'docs': [
            'Sphinx',
            'repoze.sphinx.autointerface',
        ],
        'test': TESTS_REQUIRE,
    },
)

This would be more effort but allows to do the migration even without merging after broken tests runs (besides circular dependencies).

@icemac
Copy link
Member Author

icemac commented Dec 20, 2024

Setting the max versions could be started at any time as it does not break anything. Maybe it could even be (semi-)automated.

@dataflake
Copy link
Member

With all due respect, that is a whole bunch of code required for exactly what gain? Like I said, except for Products the conversion for each namespace will not take long because most of the packages are in the zopefoundation organisation and under the control of the developers who have agreed to help. The Products namespace encompasses quite a few third parties and their development sandboxes may break, that is true. But honestly, after 25 years of experience I know that extra effort and additional wait time and asking people to please change their own code will not help much. People will only jump into action if they have to. I am willing to endure the complaints and asking them to change 2-3 lines of their code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is requested
Development

No branches or pull requests

10 participants