From 00babef38f153cb453f978b947629546307e5616 Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 25 Jul 2026 13:20:10 -0400 Subject: [PATCH 1/3] fix(release): repair the publish workflow after the setup.py removal setup.py was deleted when the project moved to pyproject.toml, but the release workflow still referenced it in two places, so it could not have produced a release: the version-bump step would raise FileNotFoundError and the build step called `python setup.py sdist bdist_wheel`. The migration landed after v1.18, so the workflow has never run against a setup.py-less tree -- PyPI 1.18 still advertises `requires_python: >=3.7`, the value from the old setup.py. Build with `python -m build` and bump the version in pyproject.toml instead. update_version.py silently did nothing to pyproject.toml. Its patterns matched `version="x"` (setup.py) and `version = 'x'` (meta.yaml), but pyproject declares `version = "x"` -- spaces and double quotes -- so it rewrote the file unchanged and exited 0. A release would have been tagged and uploaded under the previous version number. It now matches all three declaration styles and exits non-zero when a file declares no version at all, so a future rename fails the run rather than shipping a stale number. Also: - `readme` was inline text, so the PyPI project page showed a single sentence instead of README.md. Point it at the file; its images and badges already use absolute URLs, so it renders off-GitHub. Verified README.md ships in the sdist, which is what a conda-forge recipe would build from. - Require setuptools>=77. `license = "Apache-2.0"` as a bare string is PEP 639, which older setuptools rejects, and both the conda recipe and conda-forge build with --no-build-isolation against whatever setuptools the host provides. - Add `twine check dist/*` before upload. - The conda recipe declared `license_family: MIT` for an Apache-2.0 package, and the README's license badge linked to LGPL-2.0. - Refresh Update_version.md, which documented the setup.py flow. Verified end to end: sdist and wheel build without isolation, metadata carries Description-Content-Type text/markdown with the full README, and the package installs from the sdist and imports. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/update_version.py | 52 +++++++++++++++++++++++----- .github/workflows/python-publish.yml | 11 +++--- README.md | 2 +- Update_version.md | 39 +++++++++++++-------- conda-recipe/meta.yaml | 4 +-- pyproject.toml | 4 +-- 6 files changed, 79 insertions(+), 33 deletions(-) diff --git a/.github/update_version.py b/.github/update_version.py index 0b045308..42179f1b 100644 --- a/.github/update_version.py +++ b/.github/update_version.py @@ -1,13 +1,47 @@ +"""Set the version number in one of the files that declare it. + +Usage: python update_version.py + +Each pattern below targets exactly one declaration style. A file that matches +none of them is treated as an error rather than rewritten unchanged, so a +renamed key or a reformatted file fails the release run instead of shipping a +stale version number. +""" import re import sys -file_i = sys.argv[1] -version = sys.argv[2] +# (pattern, flags) -- group 1 is the prefix up to the opening quote, group 2 the +# closing quote, so the version itself is whatever sits between them. +PATTERNS = ( + # pyproject.toml version = "1.19" + (r'^(version\s*=\s*")[^"]*(")', re.M), + # docs/source/conf.py release = "1.19" + (r'^(release\s*=\s*")[^"]*(")', re.M), + # conda-recipe/meta.yaml {% set version = '1.19' %} + (r"(\{%\s*set\s+version\s*=\s*')[^']*(')", 0), +) + + +def main(): + if len(sys.argv) != 3: + sys.exit(__doc__) + path, version = sys.argv[1], sys.argv[2] + + with open(path, 'r') as f: + content = f.read() + + total = 0 + for pattern, flags in PATTERNS: + content, count = re.subn(pattern, r'\g<1>' + version + r'\g<2>', content, flags=flags) + total += count + + if not total: + sys.exit(f'{path}: no version declaration matched, version NOT updated') + + with open(path, 'w') as f: + f.write(content) + print(f'{path}: set version to {version} ({total} occurrence(s))') + -with open(file_i, 'r') as f: - content = f.read() - content_new = re.sub(r"(?<=version\=\").*?(?=\")", str(version), content, flags=re.M) - content_new = re.sub(r"(?<=version \= ').*?(?=')", str(version), content_new, flags=re.M) - content_new = re.sub(r"(?<=release \= \").*?(?=\")", str(version), content_new, flags=re.M) -with open(file_i, 'w') as f: - f.write(content_new) +if __name__ == '__main__': + main() diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 02ea77dc..2cf9fdc4 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -29,7 +29,7 @@ jobs: python-version: '3.x' - name: update version number run: | - python ./.github/update_version.py setup.py ${{ github.event.inputs.version }} + python ./.github/update_version.py pyproject.toml ${{ github.event.inputs.version }} python ./.github/update_version.py conda-recipe/meta.yaml ${{ github.event.inputs.version }} python ./.github/update_version.py docs/source/conf.py ${{ github.event.inputs.version }} - name: install yapf @@ -59,14 +59,17 @@ jobs: gh release create "v${{ github.event.inputs.version }}" \ --title "v${{ github.event.inputs.version }}" \ --notes "${{ github.event.inputs.description }}" - - name: Install setuptools, wheel and twine + - name: Install build and twine run: | - pip install build setuptools wheel twine + pip install --upgrade build twine - name: Clean PyPi build directories run: rm -rf dist - name: Build for PyPi run: | - python setup.py sdist bdist_wheel + python -m build + - name: Check the built distributions + run: | + twine check dist/* - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: diff --git a/README.md b/README.md index b8ba4897..a05beeb2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Documentation Status](https://readthedocs.org/projects/straindesign/badge/?version=latest)](https://readthedocs.org/projects/straindesign/builds/) [![Supported Python Versions](https://img.shields.io/pypi/pyversions/straindesign.svg)](https://pypi.org/project/straindesign/) [![CI-test Status](https://github.com/klamt-lab/straindesign/workflows/CI-test/badge.svg)](https://github.com/klamt-lab/straindesign/actions/workflows/CI-test.yml) -[![License](https://img.shields.io/pypi/l/straindesign.svg)](https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html) +[![License](https://img.shields.io/pypi/l/straindesign.svg)](https://github.com/klamt-lab/straindesign/blob/main/LICENSE) [![Code style: YAPF](https://img.shields.io/badge/code%20style-yapf-blue)](https://github.com/google/yapf) ## A COBRApy[1]-based package for computational design of metabolic networks diff --git a/Update_version.md b/Update_version.md index 1cd75be2..d47ec370 100644 --- a/Update_version.md +++ b/Update_version.md @@ -1,33 +1,42 @@ -This is only needed for manual version upgrading. A versioning script is availabe in GitHub actions. +Releases are normally cut by the `Build and Upload Python Package` GitHub action, +which is triggered manually and takes the new version number as its input. It +bumps the version, formats the repository, creates the tag and release, and +uploads to PyPI and the `cnapy` Anaconda channel. The steps below are the manual +equivalent, for the case where the action cannot be used. ## In any case: -1. Update version number in `conda-recipe/meta.yaml`. Update dependencies/versions and description if necessary -2. Update version number in `setup.py`. Update dependencies/versions and description and `requirements.txt` if necessary. -3. Create a new tag/release on GitHub with matching version number (e.g. `v0.1`) +1. Update the version number in `pyproject.toml`, `conda-recipe/meta.yaml` and + `docs/source/conf.py`. `python .github/update_version.py ` + does this per file and fails if the file declares no version, so a silent + miss is not possible. Update dependencies/versions and the description in + `pyproject.toml` and `requirements.txt` if necessary. +2. Create a new tag/release on GitHub with a matching version number (e.g. `v0.1`) -## Building PyPi package +## Building the PyPI package ### Prerequisites -1. Install newest version of pip (`python -m pip install --upgrade pip` or `conda update pip`) -2. Install twain (`pip install twain` or `conda install twain`) +1. Install the newest version of pip (`python -m pip install --upgrade pip` or `conda update pip`) +2. Install the build frontend and twine (`pip install --upgrade build twine`) ### Build and upload package -1. Navigate to package folder and build package with `python setup.py sdist bdist_wheel` -2. Clean up dist folder (remove old version builds) -3. Upload package source and wheel to PyPi via `twine upload dist/*` (Use PyPi credencials) +1. Clean the dist folder (remove old version builds) +2. Navigate to the package folder and build with `python -m build`, which produces + both the source distribution and the wheel +3. Verify the metadata renders on PyPI with `twine check dist/*` +4. Upload the source distribution and wheel via `twine upload dist/*` (use PyPI credentials) -## Building Conda package +## Building the Conda package ### Prerequisites 1. Create a conda environment to build the package (e.g. `conda create -n straindesign-build`) -2. Activate environment (`conda activate straindesign-build`) -3. Install requirements (`conda install anaconda-client conda-build`) +2. Activate the environment (`conda activate straindesign-build`) +3. Install the requirements (`conda install anaconda-client conda-build`) ### Build and upload package -1. Navigate to package folder and build package with `conda-build conda-recipe/. -c conda-forge --croot conda-bld` -2. Clean up conda-bld folder (remove old version builds) +1. Navigate to the package folder and build with `conda-build conda-recipe/. -c conda-forge --croot conda-bld` +2. Clean up the conda-bld folder (remove old version builds) 3. `anaconda login` 4. `anaconda upload -u cnapy conda-bld/noarch/straindesign*` 5. Clean up with conda diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index a13e4a7c..7f214cb4 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -33,8 +33,8 @@ test: about: home: https://github.com/klamt-lab/straindesign - license: Apache 2 - license_family: MIT + license: Apache-2.0 + license_family: Apache license_file: LICENSE summary: Computational strain design package for the COBRApy framework. doc_url: https://github.com/klamt-lab/straindesign diff --git a/pyproject.toml b/pyproject.toml index d4265de7..87b9f06d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["setuptools>=64", "wheel"] +requires = ["setuptools>=77", "wheel"] build-backend = "setuptools.build_meta" [project] name = "straindesign" version = "1.18" description = "Computational strain design package for the COBRApy framework" -readme = {text = "Computational strain design package for the COBRApy framework, offering standard and advanced tools for the analysis and redesign of biological networks", content-type = "text/plain"} +readme = "README.md" license = "Apache-2.0" requires-python = ">=3.10" authors = [ From 72a8d05c3867fae5c968892719113d60d743f927 Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 25 Jul 2026 16:34:20 -0400 Subject: [PATCH 2/3] fix(deps): declare the imports we rely on, and ship a solver that fits the formulation Four packages were imported directly but declared nowhere: numpy, pandas, optlang and swiglpk. They resolved only because cobra happens to require all four. That works today, but it means a cobra dependency change breaks `import straindesign` with no change on our side, and the traceback points at cobra rather than at us. They are listed unpinned on purpose. Contributing no version constraint leaves cobra the sole authority on which versions are resolved, so the declaration records what we import without taking a position on versions we have no opinion about. None of them adds install weight -- cobra pulls them in either way. Nothing is dropped: fva() and speedy_fva are annotated `-> DataFrame` and return 'minimum'/'maximum' frames to match cobra's flux_variability_analysis, names.py takes its status constants from optlang.interface so they stay comparable with cobra's, networktools needs optlang's Container for the solver stub on suppressed model copies, and swiglpk is the GLPK binding itself. pyscipopt becomes a dependency. GLPK cannot express indicator constraints and translates them into big-M, which is documented to give incorrect results -- glpk_interface.py:38 says so, strainDesignProblem.py:125 warns about it at runtime, and test_07_compression.py already skips GLPK with "GLPK gives incorrect results" while preferring SCIP as the strongest free backend. Until now the only solver guaranteed to be present was the one that cannot state the problem correctly. SCIP supports indicator constraints natively, is MIT/Apache licensed, ships self-contained wheels, is on conda-forge, and is installed on every leg of the CI matrix -- broader coverage than CPLEX, which is excluded on macOS and 3.13. Verified: the e_coli_core gene-MCS gate on SCIP returns 455 in 7.8 s, and the KO sets are identical to four stored gurobi references, not merely equal in count. Also correct the license headers: "Insitute" -> "Institute" and extend the year to 2022-2026, across the 17 modules that carry one. compression.py and speedy_fva.py have no header and are left alone -- who holds copyright on those is a question for their authors, not one to settle with a sweep. Co-Authored-By: Claude Opus 4.8 (1M context) --- conda-recipe/meta.yaml | 9 +++++++-- environment.yml | 7 ++++++- pyproject.toml | 14 ++++++++++++++ requirements.txt | 5 +++++ straindesign/__init__.py | 2 +- straindesign/compute_strain_designs.py | 2 +- straindesign/cplex_interface.py | 2 +- straindesign/glpk_interface.py | 2 +- straindesign/gurobi_interface.py | 2 +- straindesign/indicatorConstraints.py | 2 +- straindesign/lptools.py | 2 +- straindesign/names.py | 2 +- straindesign/networktools.py | 2 +- straindesign/parse_constr.py | 2 +- straindesign/pool.py | 2 +- straindesign/scip_interface.py | 2 +- straindesign/solver_interface.py | 2 +- straindesign/strainDesignMILP.py | 2 +- straindesign/strainDesignModule.py | 2 +- straindesign/strainDesignProblem.py | 2 +- straindesign/strainDesignSolutions.py | 2 +- 21 files changed, 49 insertions(+), 20 deletions(-) diff --git a/conda-recipe/meta.yaml b/conda-recipe/meta.yaml index 7f214cb4..b8245de8 100644 --- a/conda-recipe/meta.yaml +++ b/conda-recipe/meta.yaml @@ -13,13 +13,18 @@ requirements: build: - python - pip - - setuptools >=64 + - setuptools >=77 - wheel run: - python - scipy - - matplotlib + - matplotlib-base - psutil + - numpy + - pandas + - optlang + - swiglpk + - pyscipopt - conda-forge:cobra build: diff --git a/environment.yml b/environment.yml index 0f703389..4e894c38 100644 --- a/environment.yml +++ b/environment.yml @@ -5,6 +5,11 @@ channels: dependencies: - python - scipy - - matplotlib + - matplotlib-base - psutil + - numpy + - pandas + - optlang + - swiglpk + - pyscipopt - conda-forge::cobra diff --git a/pyproject.toml b/pyproject.toml index 87b9f06d..eaec3921 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,20 @@ dependencies = [ "scipy", "matplotlib", "psutil", + # Imported directly by this package, but every one of them is already a hard + # requirement of cobra. They are listed so the imports are declared, and left + # deliberately unpinned: contributing no version constraint leaves cobra the + # sole authority on which versions get resolved. Do not add bounds here -- + # that would start a fight with cobra over versions we have no opinion on. + "numpy", + "pandas", + "optlang", + "swiglpk", + # The only free solver that handles indicator constraints natively. GLPK has + # to translate them into big-M, which is documented to give incorrect results + # (see glpk_interface.py and test_07_compression.py), so without this a + # default install would have no solver able to express the formulation. + "pyscipopt", ] [project.urls] diff --git a/requirements.txt b/requirements.txt index c74b88bc..168b0c91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,8 @@ cobra scipy matplotlib psutil +numpy +pandas +optlang +swiglpk +pyscipopt diff --git a/straindesign/__init__.py b/straindesign/__init__.py index ac79c2f6..60c4f927 100644 --- a/straindesign/__init__.py +++ b/straindesign/__init__.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/compute_strain_designs.py b/straindesign/compute_strain_designs.py index 5cfd2588..6db62c6e 100644 --- a/straindesign/compute_strain_designs.py +++ b/straindesign/compute_strain_designs.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/cplex_interface.py b/straindesign/cplex_interface.py index d652e178..11453d4e 100644 --- a/straindesign/cplex_interface.py +++ b/straindesign/cplex_interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/glpk_interface.py b/straindesign/glpk_interface.py index 2fd6a12f..90ebaab8 100644 --- a/straindesign/glpk_interface.py +++ b/straindesign/glpk_interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/gurobi_interface.py b/straindesign/gurobi_interface.py index 32623206..a9aa730a 100644 --- a/straindesign/gurobi_interface.py +++ b/straindesign/gurobi_interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/indicatorConstraints.py b/straindesign/indicatorConstraints.py index cfa24010..13ff1326 100644 --- a/straindesign/indicatorConstraints.py +++ b/straindesign/indicatorConstraints.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/lptools.py b/straindesign/lptools.py index 663869f5..1ba04fec 100644 --- a/straindesign/lptools.py +++ b/straindesign/lptools.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/names.py b/straindesign/names.py index eabdfdc5..64dd5f81 100644 --- a/straindesign/names.py +++ b/straindesign/names.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/networktools.py b/straindesign/networktools.py index f7b0356a..060dc88f 100644 --- a/straindesign/networktools.py +++ b/straindesign/networktools.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/parse_constr.py b/straindesign/parse_constr.py index cb7b89eb..ce628e2c 100644 --- a/straindesign/parse_constr.py +++ b/straindesign/parse_constr.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/pool.py b/straindesign/pool.py index 12535122..c9095476 100644 --- a/straindesign/pool.py +++ b/straindesign/pool.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/scip_interface.py b/straindesign/scip_interface.py index 4123d887..87989188 100644 --- a/straindesign/scip_interface.py +++ b/straindesign/scip_interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/solver_interface.py b/straindesign/solver_interface.py index 4096530c..b89a074e 100644 --- a/straindesign/solver_interface.py +++ b/straindesign/solver_interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/strainDesignMILP.py b/straindesign/strainDesignMILP.py index 60d93d68..325e77e6 100644 --- a/straindesign/strainDesignMILP.py +++ b/straindesign/strainDesignMILP.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/strainDesignModule.py b/straindesign/strainDesignModule.py index 478a4673..df24d3f4 100644 --- a/straindesign/strainDesignModule.py +++ b/straindesign/strainDesignModule.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/strainDesignProblem.py b/straindesign/strainDesignProblem.py index c7031d9c..d994c5e0 100644 --- a/straindesign/strainDesignProblem.py +++ b/straindesign/strainDesignProblem.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/straindesign/strainDesignSolutions.py b/straindesign/strainDesignSolutions.py index e2b871ad..2d86e518 100644 --- a/straindesign/strainDesignSolutions.py +++ b/straindesign/strainDesignSolutions.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright 2022 Max Planck Insitute Magdeburg +# Copyright 2022-2026 Max Planck Institute Magdeburg # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From 4dddaf7315c3caed6c0d808e70b416cfea35fb96 Mon Sep 17 00:00:00 2001 From: Phil Date: Sat, 25 Jul 2026 16:45:53 -0400 Subject: [PATCH 3/3] docs(license): add the Apache header to the two modules that lacked one compression.py and speedy_fva.py were the only modules in the package without the boilerplate notice. The years are the ones the files actually date from -- compression.py first appeared 2025-12-12, speedy_fva.py 2026-03-04 -- rather than the package-wide 2022-2026, since a copyright year that predates the file is just wrong. The header is a recommendation in the Apache appendix, not a condition: the top-level LICENSE plus the License-Expression in the wheel metadata is what satisfies the licence. These are here so the notice travels with a file that gets copied out of the repo on its own. Copyright holder matches the rest of the package. If either module is not MPI work, that is a correction to make deliberately rather than by sweep. Co-Authored-By: Claude Opus 4.8 (1M context) --- straindesign/compression.py | 16 ++++++++++++++++ straindesign/speedy_fva.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/straindesign/compression.py b/straindesign/compression.py index 8d450c9a..5c086921 100644 --- a/straindesign/compression.py +++ b/straindesign/compression.py @@ -1,3 +1,19 @@ +#!/usr/bin/env python3 +# +# Copyright 2025-2026 Max Planck Institute Magdeburg +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# """ Metabolic network compression using nullspace-based coupling detection. diff --git a/straindesign/speedy_fva.py b/straindesign/speedy_fva.py index 7c14dfb7..50be8f09 100644 --- a/straindesign/speedy_fva.py +++ b/straindesign/speedy_fva.py @@ -1,3 +1,19 @@ +#!/usr/bin/env python3 +# +# Copyright 2026 Max Planck Institute Magdeburg +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# """Accelerated FVA using scan LPs and bound scanning. Standard FVA solves 2*n independent LPs (max and min for each reaction).