Skip to content

Commit

Permalink
Add new command-line option --citation for installed version (#228)
Browse files Browse the repository at this point in the history
* add `--citation` flag to print out CITATION.cff

* generate different citation formats at build-time:  apalike, bibtex, endnote, ris, codemeta, zenodo, schema.org

* allow this to be chosen at runtime, default to simple `apalike`: fallback to top-level `CITATION.cff`

* use customized fork of `cffconvert`

* Remove `cffconvert` from build-system requires in pyproject.toml: generate citations outside `cibuildwheel`
  • Loading branch information
alexlancaster authored Nov 18, 2024
1 parent f1e9512 commit 5319688
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 46 deletions.
39 changes: 36 additions & 3 deletions .github/workflows/build_wheels.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ on:
- '.github/workflows/documentation.yaml'
- '.github/workflows/buildjet_arm64.yml'
- '.github/workflows/release-drafter.yml'
- '.github/workflows/codeql.yml'
- '.github/workflows/codeql.yml'
- '.gitattributes'
push:
paths-ignore:
Expand All @@ -34,7 +34,7 @@ on:
- '.github/workflows/documentation.yaml'
- '.github/workflows/buildjet_arm64.yml'
- '.github/workflows/release-drafter.yml'
- '.github/workflows/codeql.yml'
- '.github/workflows/codeql.yml'
- '.gitattributes'
release:
types:
Expand Down Expand Up @@ -178,12 +178,45 @@ jobs:
if: runner.os == 'Linux'
uses: docker/setup-qemu-action@v3
with:
platforms: all
platforms: all
- name: Install a recent stable Python to handle Python deps
uses: actions/setup-python@v4
with:
python-version: 3.12
- name: Query version with setuptools_scm
id: version
shell: bash
run: |
python -m pip install setuptools_scm
VERSION=$(python -c "from src.PyPop import __version_scheme__; import setuptools_scm; print(setuptools_scm.get_version(version_scheme=__version_scheme__))")
echo "VERSION=${VERSION}"
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- name: Install toml and remove cffconvert from pyproject.toml
run: |
python -m pip install toml
python -c "
import toml
with open('pyproject.toml', 'r') as f:
config = toml.load(f)
if 'build-system' in config and 'requires' in config['build-system']:
config['build-system']['requires'] = [
dep for dep in config['build-system']['requires'] if 'cffconvert' not in dep.lower()
]
with open('pyproject.toml', 'w') as f:
toml.dump(config, f)
"
- name: Generate citation formats
run: |
python --version
python -m pip install git+https://github.com/alexlancaster/cffconvert.git@combine_features#egg=cffconvert
python src/PyPop/citation.py
- name: Build and test wheels
uses: pypa/cibuildwheel@v2.21.3
env:
# FIXME: only run the slow tests when doing regular pushes, or manual - not for PRs
CIBW_TEST_COMMAND: "pytest -v {package}/tests ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && '--runslow' || '' }}"
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ env.VERSION }}
CIBW_ENVIRONMENT_PASS_LINUX: SETUPTOOLS_SCM_PRETEND_VERSION
with:
only: ${{ matrix.only }}
package-dir: .
Expand Down
6 changes: 3 additions & 3 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ url: http://pypop.org/
repository-artifact: https://pypi.org/project/pypop-genomics/
repository-code: https://github.com/alexlancaster/pypop
type: software
license: GPL-2.0-or-later
version: v1.1.1
doi: 10.5281/zenodo.13742984
keywords:
- population genetics
- population genomics
Expand All @@ -83,6 +86,3 @@ keywords:
- Major histocompatibility complex
- HLA
- MHC
license: GPL-2.0-or-later
version: v1.1.1
doi: 10.5281/zenodo.13742984
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ include DEV_NOTES.md
include LICENSE
include NEWS.rst
include MANIFEST.in
include CITATION.cff
prune .github
prune website
prune data/*
Expand Down
74 changes: 49 additions & 25 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,55 @@ If you write a paper that uses PyPop in your analysis, please cite
`10.3389/fimmu.2024.1378512
<https://doi.org/10.3389/fimmu.2024.1378512>`__

* **and** the `Zenodo record <https://zenodo.org/records/10080667>`__
for the software. To cite the correct version, follow these steps:

1) First visit the DOI for the overall Zenodo record:
`10.5281/zenodo.10080667
<https://zenodo.org/doi/10.5281/zenodo.10080667>`__. This DOI
represents **all versions**, and will always resolve to the
latest one.

2) When you are viewing the record, look for the **Versions** box
in the right-sidebar. Here are listed all versions (including
older versions).

3) Select and click the version-specific DOI that matches the
specific version of PyPop that you used for your analysis.

4) Once you are visiting the Zenodo record for the specific version,
under the **Citation** box in the right-sidebar, select the
citation format you wish to use and click to copy the citation.
It will contain link to the version-specific DOI, and be of the
form:

Lancaster, AK et al. (YYYY) "PyPop: Python for Population
Genomics" (Version X.Y.Z) [Computer
software]. Zenodo. https://doi.org/10.5281/zenodo.XXXXX
* **and** a citation to the `Zenodo record
<https://zenodo.org/records/10080667>`__ which includes a DOI for
the version of the software you used in your analyses. Citing this
record and DOI supports reproducibility by allowing researchers to
to determine the exact version of PyPop used in any particular
analysis. In addition, it allows retrieval of long-term software
source-code archives, independent of the original developers.

Here's how to cite the correct version:

* If you have PyPop version 1.1.2 or later, currently installed, you
can run:

.. code-block:: shell
pypop --citation
which outputs the Zenodo record citation in the simple "APA"
format (you can also choose from BibTeX, EndNote, RIS and other
formats, see the section on `command-line interfaces
<http://pypop.org/docs/guide-chapter-usage.html#command-line-interfaces>`_
in the *User Guide* for more details).

* If you do not have PyPop installed, have a release of PyPop
earlier than 1.1.2, or otherwise want to obtain the DOI and
citation for specific versions, follow these steps:

1) First visit the DOI for the overall Zenodo record:
`10.5281/zenodo.10080667
<https://zenodo.org/doi/10.5281/zenodo.10080667>`__. This DOI
represents **all versions**, and will always resolve to the
latest one.

2) When you are viewing the record, look for the **Versions** box
in the right-sidebar. Here are listed all versions (including
older versions).

3) Select and click the version-specific DOI that matches the
specific version of PyPop that you used for your analysis.

4) Once you are visiting the Zenodo record for the specific version,
under the **Citation** box in the right-sidebar, select the
citation format you wish to use and click to copy the citation.
It will contain link to the version-specific DOI, and be of the
form:

Lancaster, AK et al. (YYYY) "PyPop: Python for Population
Genomics" (Version X.Y.Z) [Computer
software]. Zenodo. https://doi.org/10.5281/zenodo.XXXXX

Note that citation metadata for the current Zenodo record is also
stored in `CITATION.cff
Expand Down
23 changes: 15 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ skip = ["*-win32", "*_i686", # skip 32-bit builds
"cp313-musllinux_x86_64", # problem with this version
"cp36-musllinux_*", "cp37-musllinux_*", "cp38-musllinux_*"] # older musllinux missing numpy wheels
test-extras = ["test"]
test-command = "pytest -v {package}/tests"
# FIXME: add below test-command unit tests need to be saved
# "&& echo {package} && ls && tar zcvf unit_tests_output.tar.gz run_test_* && cp unit_tests_output.tar.gz {package}/wheelhouse/"

# Skip trying to test arm64 builds on Intel Macs as per
# https://cibuildwheel.readthedocs.io/en/stable/faq/#apple-silicon
# test-skip = "*-macosx_arm64 *-macosx_universal2:arm64"
# don't try and install pypi packages and build from source

# FIXME: can add "test-command" that would allow unit test output to be saved
# "pytest -v {package}/tests && echo {package} && ls && tar zcvf unit_tests_output.tar.gz run_test_* && cp unit_tests_output.tar.gz {package}/wheelhouse/"

# don't try and install pypi packages that need build from source
# this is mainly import during the testing phase
environment = { PIP_ONLY_BINARY=":all:" }

# use pip and override the PIP_ONLY_BINARY=:all: during wheel generation
# so that certain source-only build deps (like cffconvert) install
build-frontend = { name = "pip", args = ["--only-binary=:none:"] }

[[tool.cibuildwheel.overrides]]
# for latest CPython use newer manylinux image
select = "cp312-*linux*"
Expand Down Expand Up @@ -93,10 +95,15 @@ select ="*-win_*"
inherit.environment="append"
environment = { CPATH="gsl-msvc14-x64.2.3.0.2779\\\\build\\\\native", LIBRARY_PATH="gsl-msvc14-x64.2.3.0.2779\\\\build\\\\native\\\\static" }

[tool.setuptools_scm]
write_to = "src/PyPop/_version.py" # matches the path where version will be written

[build-system]
build-backend = "setuptools.build_meta:__legacy__"
requires = ["setuptools>=42",
"setuptools_scm[toml]>=6.2",
"cffconvert @ git+https://github.com/alexlancaster/cffconvert.git@combine_features#egg=cffconvert",
"importlib-metadata; python_version <= '3.8'"
]


41 changes: 36 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
from glob import glob
from setuptools import setup
from setuptools.extension import Extension
from setuptools.command.build_py import build_py as _build_py
from setuptools.command.install import install as _install
from distutils.command import clean
from sysconfig import _PREFIX, get_config_vars, get_config_var
from src.PyPop import __pkgname__, __version_scheme__
Expand Down Expand Up @@ -210,10 +212,36 @@ def path_to_src(source_path_list):
# don't include HWEEnum
# extensions.append(ext_HweEnum)

data_file_paths = []
xslt_data_file_paths = []
# xslt files are in a subdirectory
xslt_files = [f + '.xsl' for f in ['text', 'html', 'lib', 'common', 'filter', 'hardyweinberg', 'homozygosity', 'emhaplofreq', 'meta-to-tsv', 'sort-by-locus', 'haplolist-by-group', 'phylip-allele', 'phylip-haplo']]
data_file_paths.extend(xslt_files)
xslt_data_file_paths.extend(xslt_files)

citation_data_file_paths = []
# citation files are in a subdirectory of PyPop, but not a separate module
from src.PyPop.citation import citation_output_formats, convert_citation_formats
citation_files = [os.path.join("citation", 'CITATION.' + suffix) for suffix in citation_output_formats]
citation_data_file_paths.extend(citation_files)

# currently disabled (these are built in a github action)
class CustomBuildPy(_build_py):
def run(self):

# do standard build process
super().run()

# if not running from a CIBUILDWHEEL environment variable
# we need to create the citations
if os.environ.get('CIBUILDWHEEL') != '1':

# source citation path (single-source of truth)
citation_path = "CITATION.cff"

# then copy CITATION.cff to temp build directory
# use setuptools' temp build directory
build_lib = self.get_finalized_command('build').build_lib

convert_citation_formats(build_lib, citation_path)

# read the contents of your README file
from pathlib import Path
Expand Down Expand Up @@ -250,7 +278,8 @@ def path_to_src(source_path_list):
],
package_dir = {"": src_dir},
packages = ["PyPop", "PyPop.xslt"],
package_data={"PyPop.xslt": data_file_paths},
package_data = {"PyPop.xslt": xslt_data_file_paths,
"PyPop": citation_data_file_paths},
install_requires = ["numpy <= 2.1.3",
"lxml <= 5.3.0",
"importlib-resources; python_version <= '3.8'",
Expand All @@ -265,6 +294,8 @@ def path_to_src(source_path_list):
'pypop-interactive=PyPop.pypop:main_interactive']
},
ext_modules=extensions,
cmdclass={'clean': CleanCommand,},
cmdclass={'clean': CleanCommand,
# enable the custom build
'build_py': CustomBuildPy,
},
)

38 changes: 36 additions & 2 deletions src/PyPop/CommandLineInterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@
# UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

import os, sys
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, RawDescriptionHelpFormatter, FileType
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, RawDescriptionHelpFormatter, FileType, Action
from pathlib import Path
from PyPop import platform_info # global info
from PyPop import platform_info # global info
from PyPop.citation import citation_output_formats # and citation formats

"""Command-line interface for PyPop scripts
"""
Expand All @@ -45,13 +46,46 @@
class PyPopFormatter(ArgumentDefaultsHelpFormatter, RawDescriptionHelpFormatter):
pass

class CitationAction(Action):

def __call__(self, parser, namespace, values, option_string=None):

citation_format = values or 'apalike'
citation_file_name = f'citation/CITATION.{citation_format}'

try: # looking in installed package
from importlib.resources import files
citation_file = files('PyPop').joinpath(citation_file_name)
citation_text = citation_file.read_text()
except (ModuleNotFoundError, ImportError, FileNotFoundError): # fallback to using backport if not found
try:
from importlib_resources import files
citation_file = files('PyPop').joinpath(citation_file_name)
citation_text = citation_file.read_text()
except (ModuleNotFoundError, ImportError, FileNotFoundError): # fallback to looking in top-level directory if running from repo
top_level_dir = Path(__file__).resolve().parent.parent.parent
citation_file = top_level_dir / 'CITATION.cff' # only output CFF

if citation_file.exists():
print("only CITATION.cff is available")
print()
citation_text = citation_file.read_text()
else:
print("could not locate the specified citation format.")
parser.exit()

print(citation_text)
parser.exit() # exit after printing the file

def get_parent_cli(version="", copyright_message=""):
# options common to both scripts
parent_parser = ArgumentParser(add_help=False)

# define function arguments as signatures - need to be added in child parser as part of the selection logic
common_args = [
(["-h", "--help"], {'action': "help", 'help': "show this help message and exit"}),
(["--citation"], {'help': "generate citation to PyPop for this version of PyPop",
'action': CitationAction, 'nargs':'?', 'choices': citation_output_formats, 'default':'apalike'}),
(["-o", "--outputdir"], {'help':"put output in directory OUTPUTDIR",
'required':False, 'type':Path, 'default':None}),
(["-V", "--version"], {'action':'version',
Expand Down
Loading

0 comments on commit 5319688

Please sign in to comment.