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

Pyproject support #34

Merged
merged 39 commits into from
Feb 2, 2023
Merged

Pyproject support #34

merged 39 commits into from
Feb 2, 2023

Conversation

gpongelli
Copy link
Contributor

@gpongelli gpongelli commented Jan 24, 2023

Thanks for this awesome package, I need it for my Poetry-managed projects ;) !

This is a possible support for pyproject.toml file managed with Poetry, many methods are reused from setup.py source.

Extra package to be installed is pytomlpp to manage toml files.

closes #21 .

EDIT: added management for setuptools and flit pyproject files

Copy link
Owner

@mgedmin mgedmin left a comment

Choose a reason for hiding this comment

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

Thank you very much! I like most of the changes.

The Windows build failures are a bit of a blocker at the moment. And I suspect some of the unit tests are unnecessary, as they look like they were adapted from setup.py support routines that try to handle dynamic code instead of declarative values. Does poetry really support Python list comprehensions inside the pyproject.toml file?

setup.py Outdated
@@ -68,6 +68,6 @@
'check-python-versions = check_python_versions.cli:main',
],
},
install_requires=['pyyaml'],
install_requires=['pyyaml', 'pytomlpp'],
Copy link
Owner

@mgedmin mgedmin Jan 24, 2023

Choose a reason for hiding this comment

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

I'd rather use tomli; python_version < 3.11 (except with the correct syntax for the python version check).

EDIT: unless pytomlpp is required for formatting-preserving write support or something like that.

Copy link
Owner

Choose a reason for hiding this comment

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

Why are Appveyor builds failing to build pytomlpp? I see Windows wheels on PyPI, why is pip install not using them?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's not clear for me why pytomlpp is building on windows even if wheel exists, I've never used appveyor so I've no idea of what's happening.

I've no problems to change TOML package, even if I saw pytomlpp is faster, I use it just for this reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tomlkit seems to be the only library that preserves comments.

tomli has an explicit FAQ answer about comments.

pyproject.toml Outdated
@@ -0,0 +1,38 @@
[tool.poetry]
name = "check-python-versions"
version = "1.0.0"
Copy link
Owner

Choose a reason for hiding this comment

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

I don't want to manually keep two sets of metadata in sync. All my current maintenance tooling supports reading/updating the version number in src/check_python_versions/__init__.py.

I've never used Poetry and I'm not sure I want to switch to it at this time.

Copy link
Owner

Choose a reason for hiding this comment

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

If you need it for the test suite, I suggest moving it inside tests/ somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I put version metadata there to mimic the setup.py content, not to be used to build the package.

Into my packages, I usually have version number into __init__.py and into pyproject.toml, kept in sync with commitizen tool, FYI .

let me know if you prefer removing the version here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Version removed.

@@ -0,0 +1,197 @@
"""
Support for pyproject.toml.
Copy link
Owner

Choose a reason for hiding this comment

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

But AFAIU Poetry is not the only build tool that supports pyproject.toml? There are also things like flit, and I think even setuptools? (I'm not sure about setuptools -- I know there's a way to use declarative configuration for setuptools with an empty/minimal setup.py, but I don't know if it goes into setup.cfg, or if it can also go into pyproject.toml.)

Copy link
Owner

Choose a reason for hiding this comment

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

I think it can be made to work fine if you make the extract() callback of the new Source check the value of [build-system] build-backend and return None if it doesn't match poetry.whatever. Then multiple sources can declare that they support pyproject.toml and they should all cooperate nicely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But AFAIU Poetry is not the only build tool that supports pyproject.toml? There are also things like flit, and I think even setuptools? (I'm not sure about setuptools -- I know there's a way to use declarative configuration for setuptools with an empty/minimal setup.py, but I don't know if it goes into setup.cfg, or if it can also go into pyproject.toml.)

yes, recently many tools are starting support pyproject.toml instead of setup.py, but the content could differ (see setuptools as example, it does not start with [tool.poetry] section but with [project] )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it can be made to work fine if you make the extract() callback of the new Source check the value of [build-system] build-backend and return None if it doesn't match poetry.whatever. Then multiple sources can declare that they support pyproject.toml and they should all cooperate nicely.

surely it can be improved with other tools that are using pyproject.toml, I've to "study" your suggestion 😄

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it can be made to work fine if you make the extract() callback of the new Source check the value of [build-system] build-backend and return None if it doesn't match poetry.whatever. Then multiple sources can declare that they support pyproject.toml and they should all cooperate nicely.

Actually it's done, there are a plenty of utility methods to check which is the tools that handles the pyproject.toml file, with their tests.

some part are left unimplemented (even if the call is commented, e.g. this point and this and this) because I don't know the pyproject.toml structure used by setuptools and flit.



def _get_pyproject_toml_classifiers(
filename: FileOrFilename = PYPROJECT_TOML
Copy link
Owner

Choose a reason for hiding this comment

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

This is a bit over-indented.



def _get_pyproject_toml_python_requires(
filename: FileOrFilename = PYPROJECT_TOML
Copy link
Owner

Choose a reason for hiding this comment

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

Likewise.


table['tool']['poetry']['classifiers'] = new_value

return pytomlpp.dumps(table).split('\n')
Copy link
Owner

Choose a reason for hiding this comment

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

Ah! Does this preserve formatting/comments? This would be a sufficiently good reason not to use tomli.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've done a test with my pyproject, and the dumped content is different than original one.
I've searched pytomlpp and seems there's no way to keep formatting/comments (and also the original toml style).

that dumps is used to create a big string with the content of entire file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched to tomlkit that keeps the original style and comments.


def _update_pyproject_toml_python_requires(
filename: FileOrFilename,
new_value: Union[str, List[str]]
Copy link
Owner

Choose a reason for hiding this comment

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

This is overindented, and also I would like a trailing comma here.

[tool.poetry.dependencies]
python = ', '.join([
'>=2.7',
'!=3.0.*',
Copy link
Owner

Choose a reason for hiding this comment

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

Is this actually valid TOML syntax, supported by Poetry?

Copy link
Contributor Author

@gpongelli gpongelli Jan 24, 2023

Choose a reason for hiding this comment

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

it's not a valid syntax

name='foo',
classifiers=[
'Programming Language :: Python :: %s' % v
for v in ['2.7', '3.7']
Copy link
Owner

Choose a reason for hiding this comment

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

Is this actually valid syntax?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this actually valid syntax?

no, I'm going to remove the tests that uses this declaration way

assert (
"Did not understand python_requires formatting in python dependency"
in capsys.readouterr().err
)
Copy link
Owner

Choose a reason for hiding this comment

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

I would like to see a unit test that modifying the toml file leaves comments in place.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added dedicated test

@gpongelli
Copy link
Contributor Author

Thank you very much! I like most of the changes.

The Windows build failures are a bit of a blocker at the moment. And I suspect some of the unit tests are unnecessary, as they look like they were adapted from setup.py support routines that try to handle dynamic code instead of declarative values. Does poetry really support Python list comprehensions inside the pyproject.toml file?

Removed the tests with dynamic code. Actually there is a minimum set of working tests and the build pass because of different toml library.

@gpongelli gpongelli changed the title Poetry Pyproject support Pyproject support Jan 25, 2023
@gpongelli
Copy link
Contributor Author

added implementation&tests for setuptools and flit tools

Copy link
Owner

@mgedmin mgedmin left a comment

Choose a reason for hiding this comment

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

No time to do a full review today, but here are a few comments

pyproject.toml Outdated
@@ -1,6 +1,5 @@
[tool.poetry]
name = "check-python-versions"
version = "1.0.0"
Copy link
Owner

Choose a reason for hiding this comment

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

It's not the version that's a problem, it's that this project (check-python-versions) doesn't use Poetry and therefore it shouldn't have Poetry configuration in its top-level pyproject.toml.

Please move this file into tests/sources/data/ or a similarly named subdirectory if it's needed by the test suite.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's clear that this project doesn't use poetry to build it.

I've made the file to do a live check with check-python-versions.py , so it could be related to test_cli.py , I've to look at it.

@@ -38,11 +39,29 @@
PYPROJECT_TOML = 'pyproject.toml'


def _load_toml(filename: FileOrFilename):
Copy link
Owner

Choose a reason for hiding this comment

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

Would be nice if there was a type annotation for the return type. I suppose it's hard to be more specific than a generic Dict[str, Any].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

seems this row is outdated, also the file was renamed to "pyproject.py" because it handles all the tools that use pyproject.toml

with open_file(filename) as fp:
table = load(fp)
if isinstance(filename, TextIO):
table = load(filename)
Copy link
Owner

Choose a reason for hiding this comment

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

There's a check_python_version.utils.open_file() context manager that could simplify this.

@@ -11,7 +11,8 @@
check-python-versions supports both.
"""

import pytomlpp
from tomlkit import dumps
from tomlkit import parse, load
Copy link
Owner

Choose a reason for hiding this comment

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

flake8 tells me tomlkit.parse is imported but unused.

def get_toml_content(
filename: FileOrFilename = PYPROJECT_TOML
) -> FileLines:
"""Utility method to see if TOML library keeps style and comments."""
Copy link
Owner

Choose a reason for hiding this comment

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

If it's only used by the test suite, it should be moved into test_pyproject.py.

@@ -94,7 +116,7 @@ def get_python_requires(
if python_requires is None:
return None
if not isinstance(python_requires, str):
warn('The value passed to python is not a string')
warn('The value passed to python dependency is not a string')
Copy link
Owner

Choose a reason for hiding this comment

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

Likewise.

@@ -111,7 +133,7 @@ def update_supported_python_versions(
if classifiers is None:
return None
if not isinstance(classifiers, (list, tuple)):
warn('The value passed to setup(classifiers=...) is not a list')
warn('The value passed to classifiers is not a list')
Copy link
Owner

Choose a reason for hiding this comment

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

Likewise.

@@ -175,7 +195,7 @@ def _update_pyproject_toml_python_requires(
return []

table['tool']['poetry']['dependencies']['python'] = new_value
return pytomlpp.dumps(table).split('\n')
return dumps(table).split('\n')
Copy link
Owner

Choose a reason for hiding this comment

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

Note to self: investigate what tomlkit.dumps() returns and whether this should be using .splitlines() instead.

Note to self: make sure the unit tests verify that modifying a .toml file doesn't add or remove any trailing newlines at the end of the FILE.

' \'Programming Language :: Python :: 3.6\',',
' \'Programming Language :: Python :: 3.10\',',
' ]',
'']
Copy link
Owner

Choose a reason for hiding this comment

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

This would read better without the excessive indentation. Look at how other unit tests make assertions about the output. Here is one example:

assert "".join(result) == textwrap.dedent("""\
jobs:
build:
strategy:
matrix:
config:
# [Python version, tox env]
- ["3.8", "lint"]
- ["2.7", "py27"]
- ["3.8", "py38"]
- ["3.9", "py39"]
- ["pypy2", "pypy"]
- ["pypy3", "pypy3"]
- ["3.8", "coverage"]
steps:
- ...
""")

# assert (
# 'The value passed to classifiers is not a list'
# in capsys.readouterr().err
# )
Copy link
Owner

Choose a reason for hiding this comment

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

Please do not leave commented-out tests in the file. Either remove entirely, or fix to make them pass, if it's a useful test.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's an outdated state, latest commit have changed the file name to test_pyproject_poetry.py

@mgedmin
Copy link
Owner

mgedmin commented Jan 26, 2023

Do tell me when you want me to take another look. I see there are still test failures, so I was thinking of waiting a bit.

@gpongelli
Copy link
Contributor Author

Do tell me when you want me to take another look. I see there are still test failures, so I was thinking of waiting a bit.

They’re failing because coverage is less than 100% .

An improvement: is it possible to run both appveyor and GitHub action on all pushes? Because fixing issues from GitHub actions (that does not run always) after had fixed appveyor ones is a little bit demoralising for whom would contribute.

@mgedmin
Copy link
Owner

mgedmin commented Jan 26, 2023

Looks like GitHub requires me to approve the action run every time ("for first-time contributors"). I don't know if there's a way to avoid that.

Oh how I hate cryptocurrency scammers that started abusing free CI and caused every CI provider to adopt these countermeasures.

@gpongelli
Copy link
Contributor Author

Looks like GitHub requires me to approve the action run every time ("for first-time contributors"). I don't know if there's a way to avoid that.

Is it something you can configure into settings?

Oh how I hate cryptocurrency scammers that started abusing free CI and caused every CI provider to adopt these countermeasures.

Didn’t know about that.

anyway, just reached again 100% of coverage.

@gpongelli
Copy link
Contributor Author

Do tell me when you want me to take another look. I see there are still test failures, so I was thinking of waiting a bit.

@mgedmin you can do it now, I've run all the checks locally and they all pass.

Copy link
Owner

@mgedmin mgedmin left a comment

Choose a reason for hiding this comment

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

Sorry for the delay, I was mostly offline for personal reasons.

I found a setting that relaxes the approval requirement for pull request to first-time GitHub users. Thank you for suggesting that there ought to be a setting for that somewhere!

There are some style concerns I have, but they're not critical. I might as well just merge this and fix things up more to my liking in git master.

Thank you!

The most important missing bit is describing the new feature in the changelog and crediting you.

@mgedmin mgedmin merged commit 925b32c into mgedmin:master Feb 2, 2023
if PYTHON not in \
table[TOOL][POETRY][DEPENDENCIES]:
return None
return cast(str, table[TOOL][POETRY][DEPENDENCIES][PYTHON])
Copy link
Owner

Choose a reason for hiding this comment

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

Um, I checked Poetry's documentation and tools.poetry.dependencies.python uses a different syntax (^3.8 instead of >= 3.8), so this will not work right.

"""))
fp.name = "pyproject.toml"
result = update_python_requires(fp, v(['2.7', '3.2']))
assert result == ['[project]',
Copy link
Owner

Choose a reason for hiding this comment

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

This is wrong: the FileLines returned by update_... functions are supposed to include trailing newlines.

@mgedmin
Copy link
Owner

mgedmin commented Feb 7, 2023

mypy 1.0 was released today (or yesterday? timezones, eh) and started complaining a lot about pyproject.py. While trying to fix those errors I ended up rewriting most of the code. I fixed some bugs that I noticed, but I'm afraid I might have introduced new ones, so some testing with actual Poetry-using projects would be appreciated.

@mgedmin
Copy link
Owner

mgedmin commented Feb 7, 2023

(None of the above would've happened if you hadn't opened this PR, for which I would once again like to thank you!)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support for pyproject.toml
2 participants