Skip to content
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
60 changes: 60 additions & 0 deletions .ci/release-uv
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env python3
'''
Deploys Python package onto [[https://pypi.org][PyPi]] or [[https://test.pypi.org][test PyPi]].

- running manually

You'll need =UV_PUBLISH_TOKEN= env variable

- running on Github Actions

Instead of env variable, relies on configuring github as Trusted publisher (https://docs.pypi.org/trusted-publishers/) -- both for test and regular pypi

It's running as =pypi= job in [[file:.github/workflows/main.yml][Github Actions config]].
Packages are deployed on:
- every master commit, onto test pypi
- every new tag, onto production pypi
'''

UV_PUBLISH_TOKEN = 'UV_PUBLISH_TOKEN'

import argparse
import os
import shutil
from pathlib import Path
from subprocess import check_call

is_ci = os.environ.get('CI') is not None

def main() -> None:
p = argparse.ArgumentParser()
p.add_argument('--use-test-pypi', action='store_true')
args = p.parse_args()

publish_url = ['--publish-url', 'https://test.pypi.org/legacy/'] if args.use_test_pypi else []

root = Path(__file__).absolute().parent.parent
os.chdir(root) # just in case

if is_ci:
# see https://github.com/actions/checkout/issues/217
check_call('git fetch --prune --unshallow'.split())

# TODO ok, for now uv won't remove dist dir if it already exists
# https://github.com/astral-sh/uv/issues/10293
dist = root / 'dist'
if dist.exists():
shutil.rmtree(dist)

# todo what is --force-pep517?
check_call(['uv', 'build'])

if not is_ci:
# CI relies on trusted publishers so doesn't need env variable
assert UV_PUBLISH_TOKEN in os.environ, f'no {UV_PUBLISH_TOKEN} passed'

check_call(['uv', 'publish', *publish_url])


if __name__ == '__main__':
main()
16 changes: 6 additions & 10 deletions .ci/run
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ if ! command -v sudo; then
}
fi

# --parallel-live to show outputs while it's running
tox_cmd='run-parallel --parallel-live'
if [ -n "${CI-}" ]; then
# install OS specific stuff here
case "$OSTYPE" in
Expand All @@ -20,7 +22,8 @@ if [ -n "${CI-}" ]; then
;;
cygwin* | msys* | win*)
# windows
:
# ugh. parallel stuff seems super flaky under windows, some random failures, "file used by other process" and crap like that
tox_cmd='run'
;;
*)
# must be linux?
Expand All @@ -29,12 +32,5 @@ if [ -n "${CI-}" ]; then
esac
fi


PY_BIN="python3"
# some systems might have python pointing to python3
if ! command -v python3 &> /dev/null; then
PY_BIN="python"
fi

"$PY_BIN" -m pip install --user tox
"$PY_BIN" -m tox --parallel --parallel-live "$@"
# NOTE: expects uv installed
uv tool run --with tox-uv tox $tox_cmd "$@"
46 changes: 28 additions & 18 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
branches: '*'
tags: 'v[0-9]+.*' # only trigger on 'release' tags for PyPi
# Ideally I would put this in the pypi job... but github syntax doesn't allow for regexes there :shrug:
# P.S. fuck made up yaml DSLs.
pull_request: # needed to trigger on others' PRs
# Note that people who fork it need to go to "Actions" tab on their fork and click "I understand my workflows, go ahead and enable them".
workflow_dispatch: # needed to trigger workflows manually
Expand All @@ -25,23 +24,31 @@ jobs:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
# vvv just an example of excluding stuff from matrix
# exclude: [{platform: macos-latest, python-version: '3.6'}]

runs-on: ${{ matrix.platform }}

# useful for 'optional' pipelines
# continue-on-error: ${{ matrix.platform == 'windows-latest' }}

steps:
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- uses: actions/setup-python@v4
- uses: actions/checkout@v4
with:
python-version: ${{ matrix.python-version }}
submodules: recursive
fetch-depth: 0 # nicer to have all git history when debugging/for tests

- uses: actions/checkout@v3
- uses: actions/setup-python@v5
with:
submodules: recursive
python-version: ${{ matrix.python-version }}

- uses: astral-sh/setup-uv@v5
with:
enable-cache: false # we don't have lock files, so can't use them as cache key

- uses: mxschmitt/action-tmate@v3
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
Expand All @@ -50,39 +57,42 @@ jobs:
- run: bash .ci/run

- if: matrix.platform == 'ubuntu-latest' # no need to compute coverage for other platforms
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
include-hidden-files: true
name: .coverage.mypy_${{ matrix.platform }}_${{ matrix.python-version }}
path: .coverage.mypy/


pypi:
runs-on: ubuntu-latest
needs: [build] # add all other jobs here

permissions:
# necessary for Trusted Publishing
id-token: write
steps:
# ugh https://github.com/actions/toolkit/blob/main/docs/commands.md#path-manipulation
- run: echo "$HOME/.local/bin" >> $GITHUB_PATH

- uses: actions/setup-python@v4
- uses: actions/checkout@v4
with:
python-version: '3.8'
submodules: recursive

- uses: actions/checkout@v3
- uses: actions/setup-python@v5
with:
submodules: recursive
python-version: '3.10'

- uses: astral-sh/setup-uv@v5
with:
enable-cache: false # we don't have lock files, so can't use them as cache key

- name: 'release to test pypi'
# always deploy merged master to test pypi
if: github.event_name != 'pull_request' && github.event.ref == 'refs/heads/master'
env:
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD_TEST }}
run: pip3 install --user --upgrade build twine && .ci/release --test
run: .ci/release-uv --use-test-pypi

- name: 'release to pypi'
# always deploy tags to release pypi
# NOTE: release tags are guarded by on: push: tags on the top
if: github.event_name != 'pull_request' && startsWith(github.event.ref, 'refs/tags')
env:
TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }}
run: pip3 install --user --upgrade build twine && .ci/release
run: .ci/release-uv
11 changes: 10 additions & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# without it, pytest can't discover the package root for some reason
# also see https://github.com/karlicoss/pytest_namespace_pkgs for more

import os
import pathlib
from typing import Optional

Expand All @@ -24,6 +25,10 @@ def resolve_package_path(path: pathlib.Path) -> Optional[pathlib.Path]:
for parent in result.parents:
if str(parent) in namespace_pkg_dirs:
return parent
if os.name == 'nt':
# ??? for some reason on windows it is trying to call this against conftest? but not on linux/osx
if path.name == 'conftest.py':
return resolve_pkg_path_orig(path)
raise RuntimeError("Couldn't determine path for ", path)
_pytest.pathlib.resolve_package_path = resolve_package_path

Expand All @@ -34,5 +39,9 @@ def resolve_package_path(path: pathlib.Path) -> Optional[pathlib.Path]:
# not sure what are the consequences.. maybe it wouldn't be able to run against installed packages? not sure..
search_pypath_orig = _pytest.main.search_pypath
def search_pypath(module_name: str) -> str:
return str(root_dir)
mpath = root_dir / module_name.replace('.', os.sep)
if not mpath.is_dir():
mpath = mpath.with_suffix('.py')
assert mpath.exists(), mpath # just in case
return str(mpath)
_pytest.main.search_pypath = search_pypath
11 changes: 7 additions & 4 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
[mypy]
namespace_packages = True
pretty = True
show_error_context = True
show_error_codes = True
show_column_numbers = True
show_error_end = True
warn_unused_ignores = True

check_untyped_defs = True
enable_error_code = possibly-undefined

# see https://mypy.readthedocs.io/en/stable/error_code_list2.html
warn_redundant_casts = True
strict_equality = True
warn_unused_ignores = True
enable_error_code = deprecated,redundant-expr,possibly-undefined,truthy-bool,truthy-iterable,ignore-without-code,unused-awaitable


# an example of suppressing
# [mypy-my.config.repos.pdfannots.pdfannots]
Expand Down
39 changes: 17 additions & 22 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
[project]
dynamic = ["version"] # version is managed by setuptools_scm
dynamic = ["version"] # version is managed by build backend
name = "orgparse"
dependencies = [
]
requires-python = ">=3.9"
description = "orgparse - Emacs org-mode parser in Python"
license = {file = "LICENSE"}
authors = [
Expand All @@ -24,37 +27,29 @@ classifiers = [
Homepage = "https://github.com/karlicoss/orgparse"

[project.optional-dependencies]
[dependency-groups]
testing = [
"pytest",
]
linting = [
"pytest",
"ruff",
"mypy",
"lxml", # for mypy html coverage
]


[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]
version_scheme = "python-simplified-semver"
local_scheme = "dirty-tag"
# workaround for error during uv publishing
# see https://github.com/astral-sh/uv/issues/9513#issuecomment-2519527822
[tool.setuptools]
license-files = []


# nice things about pyproject.toml
# - zip_safe=False isn't neccessary anymore
# - correctly discovers namespace packages by defuilt?
# - correctly handles py.typed by default?
# - handles src layout automatically https://setuptools.pypa.io/en/latest/userguide/package_discovery.html#src-layout
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

# things I'm not sure about yet
# - avoiding dupliation/variable reuse?
# - file/git dependencies?
# - unclear how to specify namespace package order https://github.com/seanbreckenridge/reorder_editable/issues/2
# unfortunately have to duplicate project name here atm, see https://github.com/pypa/hatch/issues/1894
[tool.hatch.build.targets.wheel]
packages = ["src/orgparse"]

# TODO
# - maybe it has a nicer pypi upload system? not sure
# e.g. possibly use hatch/flit/pdb/poetry -- but not sure what's the benefit tbh
[tool.hatch.version]
source = "vcs"
7 changes: 7 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
[pytest]
# discover files that don't follow test_ naming. Useful to keep tests along with the source code
python_files = *.py

# this setting only impacts package/module naming under pytest, not the discovery
consider_namespace_packages = true

addopts =
# prevent pytest cache from being created... it craps into project dir and I never use it anyway
-p no:cacheprovider

# -rap to print tests summary even when they are successful
-rap
--verbose
Expand Down
Loading