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

🔧 Move to flit for PEP 621 compliant package build #5312

Merged
merged 14 commits into from
Jan 17, 2022
Merged
Show file tree
Hide file tree
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
27 changes: 21 additions & 6 deletions .github/workflows/check_release_tag.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
# -*- coding: utf-8 -*-
"""Check that the GitHub release tag matches the package version."""
import argparse
import json
import ast
from pathlib import Path


def get_version_from_module(content: str) -> str:
"""Get the __version__ value from a module."""
Copy link
Contributor

Choose a reason for hiding this comment

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

I take it that you employ this (convoluted) way of reading the version number because you cannot import it normally? Instead of this approach, is there no simple way to make the package importable so we can just do

import aiida
version = aiida.__version__

At the very least, I think it would be useful to have a comment here explaining why this approach is necessary and the import way doesn't work or is not desirable

Copy link
Member Author

Choose a reason for hiding this comment

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

I wouldn't call this approach particularly convoluted; I would say having to go through the whole process of installing/importing the package is convoluted, just to read the version from a file 🤷

Copy link
Member Author

@chrisjsewell chrisjsewell Jan 17, 2022

Choose a reason for hiding this comment

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

Feel free to create a suggestion for what the docstring should be (and I'll happily merge), but I'm not adding one myself, because I don't feel it needs it, since this IMO is the canonical way to retrieve the __version__ attribute (it is how both setuptools and flit do it)

Copy link
Contributor

Choose a reason for hiding this comment

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

If generating an abstract syntax tree, parsing it element by element just to find the one string that defines the version number is not convoluted, then I don't know what is. I can see the point that having to install it is likewise a lot of work just to get the version, but at least it is very understandable and readable. One would think that there should be an easier way to do this... But in the absence of that, guess this is fine to keep

Copy link
Contributor

Choose a reason for hiding this comment

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

@sphuber It is convoluted, but to my understanding, one main motivation for the overall change is to make the project metadata and build definition more declarative, so needing to install and import the package runs exactly counter that. I believe that we can reduce complexity by either parsing the module directly with a regex or by moving to a different version specification like I suggested in my comment. My recommendation would be to move forward and avoid bikeshedding on this issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

I already agreed to keeping this and move forward. Still, the suggestion to add a comment explaining why this is being done doesn't seem like a big or weird ask. Even if this may be the standard, I doubt many developers are aware of this and they will almost certainly wonder why it has to be (or look) so complicated.

Copy link
Member Author

Choose a reason for hiding this comment

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

I would note, that on the next line, it says: # adapted from setuptools/config.py, but yeh honestly this is just a very simple check (that wasn't even here a year ago) to make sure the release tag is consistent with the package version

# adapted from setuptools/config.py
try:
module = ast.parse(content)
except SyntaxError as exc:
raise IOError(f'Unable to parse module: {exc}')
try:
return next(
ast.literal_eval(statement.value) for statement in module.body if isinstance(statement, ast.Assign)
for target in statement.targets if isinstance(target, ast.Name) and target.id == '__version__'
)
except StopIteration:
raise IOError('Unable to find __version__ in module')
csadorf marked this conversation as resolved.
Show resolved Hide resolved


if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('GITHUB_REF', help='The GITHUB_REF environmental variable')
parser.add_argument('SETUP_PATH', help='Path to the setup.json')
args = parser.parse_args()
assert args.GITHUB_REF.startswith('refs/tags/v'), f'GITHUB_REF should start with "refs/tags/v": {args.GITHUB_REF}'
tag_version = args.GITHUB_REF[11:]
with open(args.SETUP_PATH, encoding='utf8') as handle:
data = json.load(handle)
pypi_version = data['version']
assert tag_version == pypi_version, f'The tag version {tag_version} != {pypi_version} specified in `setup.json`'
pypi_version = get_version_from_module(Path('aiida/__init__.py').read_text(encoding='utf-8'))
assert tag_version == pypi_version, f'The tag version {tag_version} != {pypi_version} specified in `pyproject.toml`'
2 changes: 1 addition & 1 deletion .github/workflows/ci-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
if: failure() && steps.check_reqs.outputs.error
uses: peter-evans/commit-comment@v1
with:
path: setup.json
path: pyproject.toml
body: |
${{ steps.check_reqs.outputs.error }}

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci-style.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:

- name: Install python dependencies
run: |
pip install --upgrade pip
pip install -r requirements/requirements-py-3.8.txt
pip install -e .[pre-commit]
pip freeze
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/docs-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ jobs:
python-version: '3.8'
- name: Install python dependencies
run: |
pip install -e .[docs,tests]
pip install --upgrade pip
pip install -e .[docs,tests,rest,atomic_tools]
- name: Build HTML docs
id: linkcheck
run: |
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/post-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ jobs:

- name: Install python dependencies
run: |
pip install --upgrade pip
pip install transifex-client sphinx-intl
pip install -e .[docs,tests]
pip install -e .[docs,tests,rest,atomic_tools]

- name: Build pot files
env:
Expand Down
24 changes: 14 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: '3.8'
- run: python .github/workflows/check_release_tag.py $GITHUB_REF setup.json
- run: python .github/workflows/check_release_tag.py $GITHUB_REF

pre-commit:

Expand All @@ -43,7 +43,11 @@ jobs:
sudo apt update
sudo apt install libkrb5-dev ruby ruby-dev
- name: Install python dependencies
run: pip install -e .[all]
run: |
pip install --upgrade pip
pip install -r requirements/requirements-py-3.8.txt
pip install -e .[pre-commit]
pip freeze
- name: Run pre-commit
run: pre-commit run --all-files || ( git status --short ; git diff ; exit 1 )

Expand Down Expand Up @@ -126,12 +130,12 @@ jobs:
uses: actions/setup-python@v2
with:
python-version: '3.8'
- name: Build package
- name: install flit
run: |
pip install wheel
python setup.py sdist bdist_wheel
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.1.0
with:
user: __token__
password: ${{ secrets.PYPI_KEY }}
pip install flit~=3.4
- name: Build and publish
run: |
flit publish
env:
FLIT_USERNAME: __token__
FLIT_PASSWORD: ${{ secrets.PYPI_KEY }}
13 changes: 6 additions & 7 deletions .github/workflows/test-install.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ name: test-install
on:
pull_request:
paths:
- 'setup.*'
- 'environment.yml'
- '**/requirements*.txt'
- 'pyproject.toml'
Expand Down Expand Up @@ -251,14 +250,14 @@ jobs:

# Add python-version specific requirements/ file to the requirements.txt artifact.
# This artifact can be used in the next step to automatically create a pull request
# updating the requirements (in case they are inconsistent with the setup.json file).
# updating the requirements (in case they are inconsistent with the pyproject.toml file).
- uses: actions/upload-artifact@v1
if: matrix.backend == 'django' # The requirements are identical between backends.
with:
name: requirements.txt
path: requirements-py-${{ matrix.python-version }}.txt

# Check whether the requirements/ files are consistent with the dependency specification in the setup.json file.
# Check whether the requirements/ files are consistent with the dependency specification in the pyproject.toml file.
# If the check fails, warn the user via a comment and try to automatically create a pull request to update the files
# (does not work on pull requests from forks).

Expand Down Expand Up @@ -293,7 +292,7 @@ jobs:
uses: peter-evans/commit-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
path: setup.json
path: pyproject.toml
body: |
The requirements/ files are inconsistent!

Expand Down Expand Up @@ -328,7 +327,7 @@ jobs:
title: "Update requirements/ files."
body: |
Update requirements files to ensure that they are consistent
with the dependencies specified in the 'setup.json' file.
with the dependencies specified in the 'pyproject.toml' file.

Please note, that this pull request was likely created to
resolve the inconsistency for a specific dependency, however
Expand All @@ -344,7 +343,7 @@ jobs:
issue-number: ${{ github.event.number }}
body: |
I automatically created a pull request (#${{ steps.create_update_requirements_pr.outputs.pr_number }}) that adapts the
requirements/ files according to the dependencies specified in the 'setup.json' file.
requirements/ files according to the dependencies specified in the 'pyproject.toml' file.

- name: Create PR comment on failure
if: steps.create_update_requirements_pr.outcome == 'Failure'
Expand All @@ -353,4 +352,4 @@ jobs:
issue-number: ${{ github.event.number }}
body: |
Please update the requirements/ files to ensure that they
are consistent with the dependencies specified in the 'setup.json' file.
are consistent with the dependencies specified in the 'pyproject.toml' file.
20 changes: 3 additions & 17 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ci:
autoupdate_schedule: monthly
autofix_prs: true
skip: [mypy, pylint, dm-generate-all, dependencies, verdi-autodocs, version-number]
skip: [mypy, pylint, dm-generate-all, dependencies, verdi-autodocs]

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
Expand Down Expand Up @@ -123,8 +123,7 @@ repos:
pass_filenames: false
files: >-
(?x)^(
setup.py|
setup.json|
pyproject.toml|
utils/dependency_management.py
)$

Expand All @@ -135,8 +134,7 @@ repos:
pass_filenames: false
files: >-
(?x)^(
setup.json|
setup.py|
pyproject.toml|
utils/dependency_management.py|
environment.yml|
)$
Expand All @@ -153,15 +151,3 @@ repos:
aiida/cmdline/params/types/.*|
utils/validate_consistency.py|
)$

- id: version-number
name: Check version numbers
entry: python ./utils/validate_consistency.py version
language: system
pass_filenames: false
files: >-
(?x)^(
setup.json|
utils/validate_consistency.py|
aiida/__init__.py
)$
2 changes: 2 additions & 0 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ python:
extra_requirements:
- docs
- tests
- rest
- atomic_tools

# Let the build fail if there are any warnings
sphinx:
Expand Down
7 changes: 0 additions & 7 deletions MANIFEST.in

This file was deleted.

2 changes: 1 addition & 1 deletion aiida/calculations/diff_tutorial/calculations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
Calculations provided by aiida_diff tutorial plugin.

Register calculations via the "aiida.calculations" entry point in the setup.json file.
Register calculations via the "aiida.calculations" entry point in the pyproject.toml file.
"""
from aiida.common import datastructures
from aiida.engine import CalcJob
Expand Down
2 changes: 1 addition & 1 deletion aiida/parsers/plugins/diff_tutorial/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"""
Parsers for DiffCalculation of plugin tutorial.

Register parsers via the "aiida.parsers" entry point in the setup.json file.
Register parsers via the "aiida.parsers" entry point in the pyproject.toml file.
"""
# START PARSER HEAD
from aiida.engine import ExitCode
Expand Down
26 changes: 8 additions & 18 deletions docs/source/howto/plugins_develop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,15 @@ Here is an example of a folder structure for an AiiDA plugin, illustrating diffe
LICENSE - license of your plugin
MANIFEST.in - lists non-python files to be installed, such as LICENSE
README.md - project description for github and PyPI
setup.json - plugin metadata: installation requirements, author, entry points, etc.
setup.py - PyPI installation script, parses setup.json and README.md
pyproject.toml - plugin metadata: installation requirements, author, entry points, etc.
...

A minimal plugin package instead might look like::

aiida-minimal/
aiida_minimal/
__init__.py
setup.py
setup.json
pyproject.toml

.. _how-to:plugins-develop:entrypoints:

Expand All @@ -111,14 +109,11 @@ Adding a new entry point consists of the following steps:
#. Finding the right entry point group. You can list the entry point groups defined by AiiDA via ``verdi plugin list``.
For a documentation of the groups, see :ref:`topics:plugins:entrypointgroups`.

#. Adding the entry point to the ``entry_points`` field in the ``setup.json`` file::
#. Adding the entry point to the ``entry_points`` field in the ``pyproject.toml`` file::

...
entry_points={
"aiida.calculations": [
"mycode.<something> = aiida_mycode.calcs.some:MysomethingCalculation"
]
}
[project.entry-points."aiida.calculations"]
"mycode.<something>" = "aiida_mycode.calcs.some:MysomethingCalculation"
...

Your new entry point should now show up in ``verdi plugin list aiida.calculations``.
Expand Down Expand Up @@ -227,7 +222,7 @@ Since the source code of most AiiDA plugins is hosted on GitHub, the first conta

* Make sure to have a useful ``README.md``, describing what your plugin does and how to install it.
* Leaving a contact email and adding a license is also a good idea.
* Make sure the information in the ``setup.json`` file is correct and up to date (in particular the version number), since this information is used to advertise your package on the AiiDA plugin registry.
* Make sure the information in the ``pyproject.toml`` file is correct and up to date (in particular the version number), since this information is used to advertise your package on the AiiDA plugin registry.

Source-code-level documentation
-------------------------------
Expand Down Expand Up @@ -279,8 +274,7 @@ AiiDA plugin packages are published on the `AiiDA plugin registry <registry_>`_

Before publishing your plugin, make sure your plugin comes with:

* a ``setup.json`` file with the plugin metadata
* a ``setup.py`` file for installing your plugin via ``pip``
* a ``pyproject.toml`` file with the plugin metadata and for installing your plugin via ``pip``
* a license

For examples of these files, see the `aiida-diff demo plugin <aiida-diff_>`_.
Expand All @@ -297,7 +291,7 @@ In order to register your plugin package, simply go to the `plugin registry <reg

.. note::

The plugin registry reads the metadata of your plugin from the ``setup.json`` file in your plugin repository.
The plugin registry reads the metadata of your plugin from the ``pyproject.toml`` file in your plugin repository.


We encourage you to **get your plugin package listed as soon as possible**, both in order to reserve the plugin name and to inform others of the ongoing development.
Expand All @@ -308,10 +302,6 @@ Publishing on PyPI
For distributing AiiDA plugin packages, we recommend to follow the `guidelines for packaging python projects <packaging_>`_, which include making the plugin available on the `python package index <PyPI_>`_.
This makes it possible for users to simply ``pip install aiida-myplugin``.

.. note::
When updating the version of your plugin, don't forget to update the version number both in the ``setup.json`` and in ``aiida_mycode/__init__.py``.


.. _plugin-cutter: https://github.com/aiidateam/aiida-plugin-cutter
.. _aiida-diff: https://github.com/aiidateam/aiida-diff
.. _pytest: https://pytest.org
Expand Down
Loading