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
32 changes: 25 additions & 7 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,43 @@ on:
jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13"]
include:
- python-version: "3.12"
coverage: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version-file: ".python-version"
python-version: ${{ matrix.python-version }}

- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install repo
run: uv sync --all-extras --dev
# uv pip resolves & installs deps like pip (not an environment sync)
- name: Install package + test deps (uv pip)
run: uv pip install --system --group dev .

- name: Run tests
run: bash scripts/tests.sh
run: python -m pytest --cov-report=term-missing --cov-report html:htmlcov --cov-config=pyproject.toml --cov=qcio --cov=tests .

- name: Upload coverage HTML
if: ${{ matrix.coverage == true }}
uses: actions/upload-artifact@v4
with:
name: htmlcov
name: htmlcov-py${{ matrix.python-version }}
path: htmlcov

# To catch manifest issues, we build and install from sdist on one version
- name: Build sdist (only on 3.12)
if: ${{ matrix.coverage == true }}
run: python -m pip install build && python -m build --sdist

- name: Install from sdist (only on 3.12)
if: ${{ matrix.coverage == true }}
run: python -m pip install dist/*.tar.gz
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.9
3.10
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

### Removed

- 🚨Python 3.9 support. Minimum supported version is now 3.10. [#91](https://github.com/coltonbh/qcio/pull/91)
- All constants and periodic table data moved to [qcconst](https://github.com/coltonbh/qcconst).
- All cheminformatics methods, including those that used `rdkit` and `openbabel` such as `rmsd` and `align`. Placed these algorithms into [qcinf](https://github.com/coltonbh/qcinf) so that `qcio` can remain purely about data structures. All future algorithms will go into `qcinf`.

Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description = "Beautiful and user friendly data structures for quantum chemistry
readme = "README.md"
authors = [{ name = "Colton Hicks", email = "github@coltonhicks.com" }]
license = { file = "LICENSE" }
requires-python = ">=3.9"
requires-python = ">=3.10"
keywords = ["quantum-chemistry", "data-structures", "cheminformatics"]
dependencies = [
"pydantic>=2.0.0, !=2.0.1, !=2.1.0",
Expand All @@ -22,11 +22,11 @@ classifiers = [
"Intended Audience :: Science/Research",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering",
"Topic :: Software Development :: Libraries",
]
Expand Down
4 changes: 2 additions & 2 deletions src/qcio/helper_types.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from pathlib import Path
from typing import Annotated, Union
from typing import Annotated

import numpy as np
from pydantic import BeforeValidator, GetPydanticSchema, PlainSerializer, SkipValidation
from pydantic_core import core_schema

StrOrPath = Annotated[Union[str, Path], PlainSerializer(lambda x: str(x))]
StrOrPath = Annotated[str | Path, PlainSerializer(lambda x: str(x))]

# Create the annotated type for numpy array
SerializableNDArray = Annotated[
Expand Down
30 changes: 15 additions & 15 deletions src/qcio/models/base_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from base64 import b64decode, b64encode
from enum import Enum
from pathlib import Path
from typing import TYPE_CHECKING, Any, Optional, Union
from typing import TYPE_CHECKING, Any

import numpy as np
import toml
Expand Down Expand Up @@ -66,7 +66,7 @@ class QCIOBaseModel(BaseModel, ABC):
}

@classmethod
def open(cls, filepath: Union[Path, str]) -> Self:
def open(cls, filepath: Path | str) -> Self:
"""Instantiate an object from data saved to disk.

Args:
Expand All @@ -92,7 +92,7 @@ def open(cls, filepath: Union[Path, str]) -> Self:
return cls.model_validate_json(data)

@classmethod
def open_multi(cls, filepath: Union[Path, str]) -> list[Self]:
def open_multi(cls, filepath: Path | str) -> list[Self]:
"""Instantiate a list of objects from data saved to disk.

Args:
Expand All @@ -119,7 +119,7 @@ def open_multi(cls, filepath: Union[Path, str]) -> list[Self]:

def save(
self,
filepath: Union[Path, str],
filepath: Path | str,
exclude_none: bool = True,
exclude_unset: bool = True,
indent: int = 4,
Expand Down Expand Up @@ -227,7 +227,7 @@ class Files(QCIOBaseModel):
files: A dict mapping filename to str or bytes data.
"""

files: dict[str, Union[str, bytes]] = {}
files: dict[str, str | bytes] = {}

@field_validator("files")
def _convert_base64_to_bytes(cls, value):
Expand All @@ -250,7 +250,7 @@ def _serialize_files(self, files, _info) -> dict[str, str]:
}

def add_file(
self, filepath: Union[Path, str], relative_dir: Optional[Path] = None
self, filepath: Path | str, relative_dir: Path | None = None
) -> None:
"""Add a file to the object. The file will be added at to the `files` attribute
with the filename as the key and the file data as the value.
Expand All @@ -270,7 +270,7 @@ def add_file(
filepath = Path(filepath)
raw_bytes = filepath.read_bytes()
try:
data: Union[str, bytes] = raw_bytes.decode("utf-8") # str
data: str | bytes = raw_bytes.decode("utf-8") # str
except UnicodeDecodeError:
data = raw_bytes # bytes

Expand All @@ -288,7 +288,7 @@ def add_files(
self,
directory: StrOrPath,
recursive: bool = False,
exclude: Optional[list[str]] = None,
exclude: list[str] | None = None,
) -> None:
"""Add all files in a directory to the object.

Expand Down Expand Up @@ -355,12 +355,12 @@ class Provenance(QCIOBaseModel):
"""

program: str
program_version: Optional[str] = None
scratch_dir: Optional[Path] = None
wall_time: Optional[float] = None
hostname: Optional[str] = None
hostcpus: Optional[int] = None
hostmem: Optional[int] = None
program_version: str | None = None
scratch_dir: Path | None = None
wall_time: float | None = None
hostname: str | None = None
hostcpus: int | None = None
hostmem: int | None = None


class CalcType(str, Enum):
Expand Down Expand Up @@ -399,4 +399,4 @@ class Model(QCIOBaseModel):
"""

method: str
basis: Optional[str] = None
basis: str | None = None
2 changes: 1 addition & 1 deletion src/qcio/models/results.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def conformers_filtered(
self,
threshold: float = 1.0,
**rmsd_kwargs,
) -> tuple[list["Structure"], "SerializableNDArray"]:
) -> tuple[list[Structure], SerializableNDArray]:
"""
!!! warning "Moved since *qcio* 0.15.0"
This convenience method has moved to
Expand Down
8 changes: 4 additions & 4 deletions src/qcio/models/specs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

import warnings
from pathlib import Path
from typing import Any, Optional, Union
from typing import Any, TypeVar, Union

from pydantic import BaseModel, field_serializer, model_validator
from typing_extensions import Self, TypeVar
from typing_extensions import Self

from .base_models import CalcType, Files, Model
from .structure import Structure
Expand Down Expand Up @@ -41,7 +41,7 @@ class FileSpec(Files):
cmdline_args: list[str] = []

@classmethod
def from_directory(cls, directory: Union[Path, str], **kwargs) -> Self:
def from_directory(cls, directory: Path | str, **kwargs) -> Self:
"""Create a new FileSpec and collect all files in the directory."""
obj = cls(**kwargs)
directory = Path(directory)
Expand Down Expand Up @@ -130,7 +130,7 @@ class SubCalcSpec(FileSpec, _KeywordsMixin):
development and scratch space.
"""

model: Optional[Model] = None
model: Model | None = None
subprogram: str
subprogram_spec: CoreSpec

Expand Down
52 changes: 26 additions & 26 deletions src/qcio/models/structure.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import warnings
from collections import Counter
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar, Optional, Union
from typing import TYPE_CHECKING, Any, ClassVar

import numpy as np
from pydantic import field_serializer, model_validator
Expand Down Expand Up @@ -48,20 +48,20 @@ class Identifiers(QCIOBaseModel):
schema development and scratch space.
"""

name: Optional[str] = None
name_IUPAC: Optional[str] = None
smiles: Optional[str] = None
canonical_smiles: Optional[str] = None
canonical_smiles_program: Optional[str] = None
canonical_explicit_hydrogen_smiles: Optional[str] = None
canonical_isomeric_smiles: Optional[str] = None
canonical_isomeric_explicit_hydrogen_smiles: Optional[str] = None
canonical_isomeric_explicit_hydrogen_mapped_smiles: Optional[str] = None
inchi: Optional[str] = None
inchikey: Optional[str] = None
pubchem_cid: Optional[str] = None
pubchem_sid: Optional[str] = None
pubchem_conformerid: Optional[str] = None
name: str | None = None
name_IUPAC: str | None = None
smiles: str | None = None
canonical_smiles: str | None = None
canonical_smiles_program: str | None = None
canonical_explicit_hydrogen_smiles: str | None = None
canonical_isomeric_smiles: str | None = None
canonical_isomeric_explicit_hydrogen_smiles: str | None = None
canonical_isomeric_explicit_hydrogen_mapped_smiles: str | None = None
inchi: str | None = None
inchikey: str | None = None
pubchem_cid: str | None = None
pubchem_sid: str | None = None
pubchem_conformerid: str | None = None


class Structure(QCIOBaseModel):
Expand Down Expand Up @@ -128,9 +128,9 @@ def __init__(self, **data: Any):
@classmethod
def open(
cls,
filepath: Union[Path, str],
charge: Optional[int] = None,
multiplicity: Optional[int] = None,
filepath: Path | str,
charge: int | None = None,
multiplicity: int | None = None,
) -> Self:
"""Open a structure or structures from a file.

Expand Down Expand Up @@ -176,9 +176,9 @@ def open(
@classmethod
def open_multi(
cls,
filepath: Union[Path, str],
charge: Optional[int] = None,
multiplicity: Optional[int] = None,
filepath: Path | str,
charge: int | None = None,
multiplicity: int | None = None,
) -> list["Structure"]:
"""Open a multi-structure file and return a list of Structure objects.

Expand Down Expand Up @@ -226,7 +226,7 @@ def open_multi(

def save(
self,
filepath: Union[Path, str],
filepath: Path | str,
exclude_none: bool = True,
exclude_unset: bool = True,
indent: int = 4,
Expand Down Expand Up @@ -274,8 +274,8 @@ def from_xyz(
cls,
xyz_str: str,
*,
charge: Optional[int] = None,
multiplicity: Optional[int] = None,
charge: int | None = None,
multiplicity: int | None = None,
) -> Self:
"""Create a Structure from an XYZ file or string.

Expand Down Expand Up @@ -350,8 +350,8 @@ def from_xyz(
def from_xyz_multi(
cls,
xyz_str: str,
charge: Optional[int] = None,
multiplicity: Optional[int] = None,
charge: int | None = None,
multiplicity: int | None = None,
) -> list["Structure"]:
"""Parse a multi-structure XYZ file into a list of Structure objects.

Expand Down
3 changes: 2 additions & 1 deletion src/qcio/models/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Utility functions for the models module."""

import warnings
from typing import TYPE_CHECKING, Any, Iterable
from collections.abc import Iterable
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
from qcio import Structure
Expand Down
3 changes: 1 addition & 2 deletions src/qcio/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from __future__ import annotations

import json
from typing import Union

import numpy as np
from pydantic import BaseModel
Expand All @@ -28,7 +27,7 @@


def json_dumps(
obj: Union[BaseModel, list[BaseModel]],
obj: BaseModel | list[BaseModel],
exclude_unset: bool = True,
**model_dump_kwargs,
) -> str:
Expand Down
Loading