From cd090c4cb311e0664815282ade99dd16016e37ed Mon Sep 17 00:00:00 2001 From: jaclark5 Date: Wed, 29 Jul 2026 15:53:09 -0400 Subject: [PATCH 1/3] Implement package updates --- .github/workflows/tests.yml | 30 +++++++++++++++++ .gitignore | 10 ++++++ .pre-commit-config.yaml | 8 +++++ .travis.yml | 30 ----------------- README.md | 1 - pyproject.toml | 67 +++++++++++++++++++++++++++++++++++-- setup.py | 32 ------------------ 7 files changed, 113 insertions(+), 65 deletions(-) create mode 100644 .github/workflows/tests.yml create mode 100644 .pre-commit-config.yaml delete mode 100644 .travis.yml delete mode 100644 setup.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..4633503 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Tests + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python-version: ["3.11", "3.12", "3.13", "3.14"] + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install pytest jsonschema + pip install -e . + + - name: Test with pytest + run: make test diff --git a/.gitignore b/.gitignore index edbbb4a..73c819f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ __pycache__/ *.py[cod] *$py.class +.agents/ +.claude/ + # C extensions *.so @@ -24,6 +27,8 @@ wheels/ *.egg-info/ .installed.cfg *.egg +*.whl +*.tar.gz # PyInstaller # Usually these files are written by a python script from a template @@ -103,3 +108,8 @@ ENV/ # Dev schema docs/source/auto*rst + + +# Agent workflow (local only, not tracked) +.agents/ + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f2d3874 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + args: [--line-length=120] diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6f93b17..0000000 --- a/.travis.yml +++ /dev/null @@ -1,30 +0,0 @@ -# After changing this file, check it on: -# http://lint.travis-ci.org/ -language: python - -# Run jobs on container-based infrastructure, can be overridden per job -sudo: false - -matrix: - include: - - python: 2.7 - - python: 3.5 - -before_install: - - uname -a - - free -m - - df -h - - ulimit -a - - python -V - - pip install --upgrade pip setuptools - - pip install pytest jsonschema - - pip install -e . - -script: - - make test - -after_success: - - make docs - -notifications: - email: false diff --git a/README.md b/README.md index e5a9759..19f944c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # QCSchema -[![Build Status](https://img.shields.io/travis/MolSSI/QCSchema/master.svg?logo=linux&logoColor=white)](https://travis-ci.org/MolSSI/QCSchema) [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/MolSSI/QCSchema.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/MolSSI/QCSchema/context:python) [![Documentation Status](https://readthedocs.org/projects/molssi-qc-schema/badge/?version=latest)](https://molssi-qc-schema.readthedocs.io/en/latest/?badge=latest) diff --git a/pyproject.toml b/pyproject.toml index ca79cf6..3577047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,66 @@ -[tool.black] +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "qcschema" +version = "1" +description = "A schema for quantum chemistry" +readme = "README.md" +requires-python = ">3.11" +dependencies = [ + "jsonschema", + "pathlib2; python_version < '3.5'", +] + +[project.optional-dependencies] +docs = [ + "sphinx==1.2.3", + "sphinxcontrib-napoleon", + "sphinx_rtd_theme", + "numpydoc", +] +tests = [ + "pytest", +] + +[project.urls] +Homepage = "https://github.com/MolSSI/QCSchema" + +[tool.setuptools] +packages = [ + "qcschema", + "qcschema.data", + "qcschema.data.v1", + "qcschema.data.v2", + "qcschema.dev", + "qcschema.dev.properties", + "qcschema.dev.wavefunction", +] +zip-safe = true + +[tool.setuptools.package-data] +"qcschema.data.v1" = ["*.schema"] +"qcschema.data.v2" = ["*.schema"] + +[tool.ruff] +line-length = 120 +target-version = "py311" +lint.select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # Pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade +] +lint.ignore = [ + "E501", # Line too long (handled by formatter) +] + +[tool.ruff.lint.isort] +known-first-party = ["qcschema"] + +[tool.ruff.format] line-length = 120 -target-version = ['py27', 'py36', 'py37', 'py38'] diff --git a/setup.py b/setup.py deleted file mode 100644 index 742f1b1..0000000 --- a/setup.py +++ /dev/null @@ -1,32 +0,0 @@ -import setuptools - -if __name__ == "__main__": - setuptools.setup( - name='qcschema', - version="1", - description='A schema for quantum chemistry', - author='', - url="https://github.com/MolSSI/QCSchema", - license='', - packages=setuptools.find_packages(), - install_requires=[ - 'jsonschema', - 'pathlib2; python_version < "3.5"', # redundant with jsonschema - ], - extras_require={ - 'docs': [ - 'sphinx==1.2.3', # autodoc was broken in 1.3.1 - 'sphinxcontrib-napoleon', - 'sphinx_rtd_theme', - 'numpydoc', - ], - 'tests': [ - 'pytest', - ], - }, - - tests_require=[ - 'pytest', - ], - zip_safe=True, - ) From f2718d590355836d47f45ad1c59f5049aa70b0a4 Mon Sep 17 00:00:00 2001 From: jaclark5 Date: Wed, 29 Jul 2026 15:53:39 -0400 Subject: [PATCH 2/3] enforce ruff formatting --- docs/source/conf.py | 5 +- docs/source/gen_schema_docs.py | 6 +- docs/source/schema_doc_helpers/writers.py | 5 +- qcschema/data/v2/__init__.py | 0 qcschema/dev/dev_schema.py | 6 +- qcschema/dev/properties/cc_properties.py | 2 +- qcschema/dev/properties/properties_base.py | 6 +- qcschema/dev/wavefunction/__init__.py | 2 +- .../dev/wavefunction/wavefunction_base.py | 6 +- qcschema/versions.py | 6 +- tests/test_failures.py | 8 +- tests/test_helpers.py | 11 +-- tests/test_packaging.py | 81 +++++++++++++++++++ tests/test_schema.py | 5 +- 14 files changed, 110 insertions(+), 39 deletions(-) create mode 100644 qcschema/data/v2/__init__.py create mode 100644 tests/test_packaging.py diff --git a/docs/source/conf.py b/docs/source/conf.py index a5bb1ca..54cb484 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -20,10 +19,10 @@ # -- Project information ----------------------------------------------------- # Super hacky auto gen -import sys import os +import sys + sys.path.insert(1, os.path.dirname(__file__)) -import gen_schema_docs project = 'A schema for Quantum Chemistry' copyright = f'2018-{datetime.datetime.today().year}, The Molecular Sciences Software Institute' diff --git a/docs/source/gen_schema_docs.py b/docs/source/gen_schema_docs.py index 2e385ba..6853c02 100644 --- a/docs/source/gen_schema_docs.py +++ b/docs/source/gen_schema_docs.py @@ -1,12 +1,12 @@ """ Very hacky way to write out the schema (for demo purposes only) """ -import qcschema -import textwrap +import os # Import headers from this import sys -import os + +import qcschema sys.path.insert(1, os.path.dirname(__file__)) import schema_doc_helpers as sh diff --git a/docs/source/schema_doc_helpers/writers.py b/docs/source/schema_doc_helpers/writers.py index adc8bca..a42caf0 100644 --- a/docs/source/schema_doc_helpers/writers.py +++ b/docs/source/schema_doc_helpers/writers.py @@ -3,6 +3,7 @@ """ import textwrap + def write_header(data, header): data.append("") data.append(header) @@ -25,11 +26,11 @@ def write_key_table(top_file, properties, keys=None): fmt_string = ' | {:%s} | {:%s} | {:%s} |' % tuple(table_widths) dash_inds = tuple("-" * w for w in table_widths) equals_inds = tuple("=" * w for w in table_widths) - + top_file.append(" +-{}-+-{}-+-{}-+".format(*dash_inds)) top_file.append(fmt_string.format("Key Name", "Description", "Field Type")) top_file.append(" +={}=+={}=+={}=+".format(*equals_inds)) - + if keys is None: keys = properties.keys() diff --git a/qcschema/data/v2/__init__.py b/qcschema/data/v2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/qcschema/dev/dev_schema.py b/qcschema/dev/dev_schema.py index 31f2145..026912d 100644 --- a/qcschema/dev/dev_schema.py +++ b/qcschema/dev/dev_schema.py @@ -4,11 +4,7 @@ import copy -from . import molecule -from . import definitions -from . import properties -from . import basis -from . import wavefunction +from . import basis, definitions, molecule, properties, wavefunction # The base schema definition base_schema = { diff --git a/qcschema/dev/properties/cc_properties.py b/qcschema/dev/properties/cc_properties.py index 6adb3c9..f1e73a0 100644 --- a/qcschema/dev/properties/cc_properties.py +++ b/qcschema/dev/properties/cc_properties.py @@ -134,4 +134,4 @@ "type": "number", "multipleOf": 1.0, "description": "The number of CCSDTQ iterations taken before convergence." -} \ No newline at end of file +} diff --git a/qcschema/dev/properties/properties_base.py b/qcschema/dev/properties/properties_base.py index 285da8a..43dda33 100644 --- a/qcschema/dev/properties/properties_base.py +++ b/qcschema/dev/properties/properties_base.py @@ -2,10 +2,10 @@ The base file for QC Schema properties. """ -from .scf_properties import scf_properties -from .mp_properties import mp_properties -from .cc_properties import cc_properties from .calcinfo_properties import calcinfo_properties +from .cc_properties import cc_properties +from .mp_properties import mp_properties +from .scf_properties import scf_properties properties = { "type": "object", diff --git a/qcschema/dev/wavefunction/__init__.py b/qcschema/dev/wavefunction/__init__.py index 3f4ee9b..1f83c3b 100644 --- a/qcschema/dev/wavefunction/__init__.py +++ b/qcschema/dev/wavefunction/__init__.py @@ -1 +1 @@ -from .wavefunction_base import output_wavefunction \ No newline at end of file +from .wavefunction_base import output_wavefunction diff --git a/qcschema/dev/wavefunction/wavefunction_base.py b/qcschema/dev/wavefunction/wavefunction_base.py index ab9e29a..4cb866f 100644 --- a/qcschema/dev/wavefunction/wavefunction_base.py +++ b/qcschema/dev/wavefunction/wavefunction_base.py @@ -2,11 +2,11 @@ The base file for QC Schema properties. """ +from ..basis import basis +from .core_wavefunction import core_wavefunction +from .localized_wavefunction import localized_wavefunction from .result_wavefunction import result_wavefunction from .scf_wavefunction import scf_wavefunction -from .localized_wavefunction import localized_wavefunction -from .core_wavefunction import core_wavefunction -from ..basis import basis output_wavefunction = { "type": "object", diff --git a/qcschema/versions.py b/qcschema/versions.py index 489146d..3617420 100644 --- a/qcschema/versions.py +++ b/qcschema/versions.py @@ -42,7 +42,7 @@ def list_versions(schema_type): if schema_type.lower() in aliases: return _versions_list[sk] - raise KeyError("Schema type should be among {} (+aliases), not '{}'.".format(list(_aliases.keys()), schema_type)) + raise KeyError(f"Schema type should be among {list(_aliases.keys())} (+aliases), not '{schema_type}'.") def get_schema(schema_type, version="dev"): @@ -62,10 +62,10 @@ def get_schema(schema_type, version="dev"): fname = "qc_schema_" + sk break else: - raise KeyError("Schema version should be among {}, not '{}'.".format(_versions_list[sk], version)) + raise KeyError(f"Schema version should be among {_versions_list[sk]}, not '{version}'.") else: raise KeyError( - "Schema type should be among {} (+aliases), not '{}'.".format(list(_aliases.keys()), schema_type) + f"Schema type should be among {list(_aliases.keys())} (+aliases), not '{schema_type}'." ) fpath = _data_path / ("v" + str(version)) / (fname + ".schema") diff --git a/tests/test_failures.py b/tests/test_failures.py index bc423b8..b16bc8d 100644 --- a/tests/test_failures.py +++ b/tests/test_failures.py @@ -1,11 +1,11 @@ """ Tests the JSON schema """ + import jsonschema import pytest -import os - import test_helpers + import qcschema ### Test input validation errors @@ -17,7 +17,7 @@ def test_input_failures(version, testfile): example = test_helpers.get_test(testfile) - + with pytest.raises(jsonschema.exceptions.ValidationError): qcschema.validate(example, "input") @@ -30,7 +30,7 @@ def test_input_failures(version, testfile): def test_output_failures(version, testfile): example = test_helpers.get_test(testfile) - + with pytest.raises(jsonschema.exceptions.ValidationError): qcschema.validate(example, "output") diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6f4daa1..626787c 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -2,19 +2,14 @@ Contains helper scripts to assist in testing the schema """ -import pytest -import os import glob -import copy import json -import subprocess - -import qcschema +import os def _read_json_file(*filename): filename = os.path.join(*filename) - with open(filename, "r") as infile: + with open(filename) as infile: data = json.load(infile) return data @@ -33,4 +28,4 @@ def list_tests(folder, ext=".json", matcher=""): def get_test(name): return _read_json_file(name) - + diff --git a/tests/test_packaging.py b/tests/test_packaging.py new file mode 100644 index 0000000..4f795a9 --- /dev/null +++ b/tests/test_packaging.py @@ -0,0 +1,81 @@ +""" +Test that schema data ships in the installed package. + +This test verifies that the wheel build process includes all six .schema files, +guarding against regression in the packaging configuration. +""" + +import os +import shutil +import subprocess +import zipfile + +import pytest + +_test_path = os.path.dirname(os.path.abspath(__file__)) +_base_path = os.path.dirname(_test_path) + + +@pytest.mark.skipif( + os.environ.get("QCSCHEMA_SKIP_BUILD_TESTS") == "1", + reason="build tests disabled via QCSCHEMA_SKIP_BUILD_TESTS", +) +def test_schema_files_in_wheel(tmp_path): + """Verify all six .schema files appear in the built wheel.""" + # Copy source tree to temp location (excludes .git, build artifacts, etc.) + copy_dir = tmp_path / "source_copy" + copy_dir.mkdir() + + exclude_dirs = {".git", "build", "dist", ".egg-info", ".agents", "community_examples", "tests"} + for item in os.listdir(_base_path): + item_path = os.path.join(_base_path, item) + if os.path.isdir(item_path) and item in exclude_dirs: + continue + if item.endswith(".egg-info"): + continue + if os.path.isfile(item_path): + shutil.copy2(item_path, copy_dir / item) + elif os.path.isdir(item_path): + shutil.copytree(item_path, copy_dir / item) + + # Also copy tests/ but exclude fixture directories + tests_copy = copy_dir / "tests" + tests_copy.mkdir() + for item in os.listdir(os.path.join(_base_path, "tests")): + item_path = os.path.join(_base_path, "tests", item) + if item in {"simple", "basis", "wavefunction", "input_failures", "output_failures"}: + continue + if os.path.isfile(item_path): + shutil.copy2(item_path, tests_copy / item) + + # Build wheel from copy + rc = subprocess.run( + ["python", "-m", "pip", "wheel", "--no-deps", "--no-build-isolation", "-w", ".", "."], + cwd=str(copy_dir), + capture_output=True, + text=True, + ).returncode + + if rc != 0: + pytest.skip("wheel build failed") + + # Find the built wheel + whl_files = list(copy_dir.glob("*.whl")) + assert len(whl_files) == 1, f"expected 1 wheel, found {len(whl_files)}" + whl_path = whl_files[0] + + # Check wheel contents for schema files + expected_schemas = { + "qcschema/data/v1/qc_schema_input.schema", + "qcschema/data/v1/qc_schema_molecule.schema", + "qcschema/data/v1/qc_schema_output.schema", + "qcschema/data/v2/qc_schema_input.schema", + "qcschema/data/v2/qc_schema_molecule.schema", + "qcschema/data/v2/qc_schema_output.schema", + } + + with zipfile.ZipFile(whl_path, "r") as whl: + actual_members = set(whl.namelist()) + found_schemas = expected_schemas & actual_members + + assert len(found_schemas) == 6, f"expected 6 schema files, found {len(found_schemas)}: {found_schemas}" diff --git a/tests/test_schema.py b/tests/test_schema.py index 69238ce..5ee6135 100644 --- a/tests/test_schema.py +++ b/tests/test_schema.py @@ -1,13 +1,12 @@ """ Tests the JSON schema """ + import jsonschema import pytest -import os - import test_helpers -import qcschema +import qcschema ### Test input validation errors simple_input = test_helpers.list_tests("simple", matcher="input") From 571154d3915cc9e047205837c6024ba4e2d92896 Mon Sep 17 00:00:00 2001 From: jaclark5 Date: Wed, 29 Jul 2026 15:55:01 -0400 Subject: [PATCH 3/3] Add developer instructions for formatting with pre-commit --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 19f944c..87a84d0 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,17 @@ chemistry packages to enable more complex workflows. The core of this is to avoid parsing ASCII-based output files and place output variables, vectors, matrices in a consistent format that can be easily parsed. -Please see the [website](http://molssi-qc-schema.readthedocs.io/en/latest/index.html#) for the current specification. +Please see the [website](http://molssi-qc-schema.readthedocs.io/en/latest/index.html#) for the current specification. + +## Development + +Install the package with development dependencies and set up pre-commit hooks: + +```bash +pip install -e . +pip install pre-commit +pre-commit install +``` + +Pre-commit will automatically lint and format code on each commit using [ruff](https://github.com/astral-sh/ruff).