Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions docs/reference-guide/native-namespaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# Native namespace

This document explains the steps needed to convert a python distribution from `pkg_resources` namespace to native namespaces.

## Background

Python, since Python 3.3, added support for native namespaces, see [PEP 420](https://peps.python.org/pep-0420/).

Plone has been using `pkg_resources`-style namespaces, but they are deprecated in `setuptools`.

`setuptools` is planning to remove `pkg_resources`'s namespaces support by end of 2025.

[PLIP 3928](https://github.com/plone/Products.CMFPlone/issues/3928) is tracking the changes needed for Plone to adapt to the _new_ native namespaces.

## Steps

To convert a given (`$package`) python distribution to use native namespaces follow these steps.

### Create maintenance branch

!!! note
Only relevant for Plone core packages

Clone the repository or ensure you are at latest changes:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add a line of explanation:

Suggested change
Clone the repository or ensure you are at latest changes:
This part is only needed when the `main` or `master` branch is used on multiple versions of the Plone core development buildout.
Clone the repository or ensure you are at latest changes:


```shell
git clone git@github.com:plone/$package
cd $package
# or update
git fetch -p
git checkout main # or master
git rebase
```

Find out what's the last release and create a branch for it:

```shell
# list all tags
git for-each-ref --sort=taggerdate --format '%(tag)'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not used the for-each-ref command before, thanks for sharing.
When I try it, I get some empty lines as well, for references in refs/heads and refs/remotes. We can restrict to only tags:

Suggested change
git for-each-ref --sort=taggerdate --format '%(tag)'
git for-each-ref --sort=taggerdate --format '%(tag) refs/tags

# get the last tag's major number
MAJOR=`git for-each-ref --sort=taggerdate --format '%(tag)' | tail -n1 | cut -d"." -f1`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here for good measure, only tags:

Suggested change
MAJOR=`git for-each-ref --sort=taggerdate --format '%(tag)' | tail -n1 | cut -d"." -f1`
MAJOR=`git for-each-ref --sort=taggerdate --format '%(tag)' refs/tags | tail -n1 | cut -d"." -f1`

# create a branch for it
git checkout -b $MAJOR.x
# push the newly created branch
git push $MAJOR.x
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This complains that for example 4.x is not a remote. This seems enough:

Suggested change
git push $MAJOR.x
git push

```

### Update buildout.coredev

!!! note
Only relevant for Plone core packages

Update `buildout.coredev`'s branch 6.1 to use the newly created branch

```shell
# go to the repository or clone it
cd buildout.coredev
# ensure you are at latest changes
git fetch -p
git checkout 6.1
git rebase
# update the branch being used by the python distribution
sed -i "s/$package.git branch=master/$package.git branch=$MAJOR.x/" sources.cfg
# add the changes, commit and push
git add sources.cfg
git commit -m"chore: use branch $MAJOR.x for $package"
git push
```

!!! note
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
!!! note
Now check if you need to do the same on the 6.0 branch of `buildout.coredev`.
!!! note

To lower the amount of builds in Jenkins, either do a few at a time or add a `[ci-skip]` on the commit message

### Numbers before

One risk of changing to the native namespaces is that some files, or tests, might not be left behind.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the main risk of this was actually already when moving to a src-layout.


To ensure all tests and files are kept after the switch, gather the numbers before the change:

Get the list of tests:

```shell
tox run -e test -- --list-tests
```

!!! note
Adapt to whichever way you are using to run the tests.
The above is meant for repositories that follow `plone.meta` conventions.
The `--list-tests` comes from `zope.testrunner`, if you are using that but not `plone.meta` you can use that as well.

Create a distribution to get a listing of how many files are currently packaged:

```shell
uvx --from build pyproject-build
```

A `dist` folder is created with two archives in it: a `.tar.gz` and a `.whl`.

To get the number of files on them run the following commands:

```shell
python -c "import tarfile; print(len(tarfile.open('dist/$package.tar.gz', 'r:gz').getnames()))"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
python -c "import tarfile; print(len(tarfile.open('dist/$package.tar.gz', 'r:gz').getnames()))"
python -c "import glob; import tarfile; print(len(tarfile.open(glob.glob('dist/*.tar.gz')[0], 'r:gz').getnames()))"

python -c "from zipfile import ZipFile; print(len(ZipFile('dist/$package.whl').namelist()))"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
python -c "from zipfile import ZipFile; print(len(ZipFile('dist/$package.whl').namelist()))"
python -c "import glob; import zipfile; print(len(zipfile.ZipFile(glob.glob('dist/*.whl')[0]).namelist()))"

```

Keep these numbers around for later.

### Build backend

To ensure the python continues to build, ensure that `setuptools` is defined as its build backend.

For that inspect the `pyproject.toml`, it should have these lines:

```toml
[build-system]
requires = ["setuptools>=68.2,<80", "wheel"]
```

If they are not there, add them, commit and push the changes.

### Convert to native namespace

Use `plone.meta`'s `switch-to-pep420` script:

```shell
cd $package
uvx --from plone.meta switch-to-pep420 .
```

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Note that this will also bump the version to a new major release.
If the `main` or `master` branch is already an alpha version that is only used in Plone 6.2, you can specify that you don't want this version bump by adding the `--no-breaking` option.

I should probably add this logic to the bumpversion command from zest.releaser.

### Update the test matrix

!!! note
Only relevant for Plone core packages

As the switch to the native namespace has to be coordinated, all python distributions need to be only for the same Plone version, in this case it was decided to do it for the Plone 6.2 version.

Thus, we need to ensure that the test matrix, only tests it against this Plone version.

For that, update `.meta.toml` with the following changes:

```toml
[tox]
test_matrix = {"6.2" = ["*"]}
```

And update the scaffolding files with `plone.meta`:

```shell
uvx --from plone.meta config-package .
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my initial comment in plone/meta#297 I first run config-package passing a specific branch name matching the one used by the pep-420 script, then update the test matrix, run config-package again, and finally run the pep-420 script.

You run the pep-420 script first, which I think makes sense. But then we should tell config-package to use the current branch:

Suggested change
uvx --from plone.meta config-package .
uvx --from plone.meta config-package --branch current .

```

Review the changes and ensure all changes are sound.

!!! note
If the diff is quite big, run `config-package` before all the changes, get that on a Pull Request, approved and merged and then do a follow up Pull Request to move to native namespace.

### Compare numbers

Get the list of tests, like before and compare the lists to ensure that the same amount of tests are found.

```shell
tox run -e test -- --list-tests
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can just get the number of lines:

Suggested change
tox run -e test -- --list-tests
tox run -e test -- --list-tests | wc -l

```

Likewise, create the distribution files and compare the numbers with the previous run:

```shell
python -c "import tarfile; print(len(tarfile.open('dist/$package.tar.gz', 'r:gz').getnames()))"
python -c "from zipfile import ZipFile; print(len(ZipFile('dist/$package.whl').namelist()))"
```

If all numbers match, review the changes once more, push the branch with the changes for others to review it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There will be a few less files in the new distributions.

Suggested change
If all numbers match, review the changes once more, push the branch with the changes for others to review it.
It is okay if the numbers are slightly lower.
For obvious reasons the old source distribution will have one or more extra `__init__.py` files.
Both old distributions are expected to have an extra `namespace_packages.txt` file and possible an `x-nspkg.pth` file.
If you lose more than a few files though, something is wrong.
If the numbers are close enough, review the changes once more, and push the branch with the changes for others to review it.