Skip to content

Commit

Permalink
Merge pull request #287 from SCM-NV/linting
Browse files Browse the repository at this point in the history
TST: Move linting checks to their own dedicated workflow
  • Loading branch information
BvB93 committed Feb 14, 2022
2 parents ce3106a + 2300930 commit 0e2ea97
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 99 deletions.
33 changes: 32 additions & 1 deletion .github/workflows/pythonapp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
pull_request:

jobs:
build:
Test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
Expand Down Expand Up @@ -126,3 +126,34 @@ jobs:
with:
file: ./coverage.xml
name: codecov-umbrella

Linting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Set up Python 3.10 on ubuntu-latest
uses: actions/setup-python@v2
with:
python-version: "3.10"

- name: Install linters
run: pip install pydocstyle pycodestyle mypy "numpy>=1.21" pytest more_itertools types-PyYAML types-setuptools

- name: Python info
run: |
which python
python --version
- name: Installed packages
run: pip list

- name: Run pycodestyle
run: pycodestyle src test

- name: Run pydocstyle
run: pydocstyle src

- name: Run mypy
run: mypy src
continue-on-error: true
27 changes: 27 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[tool.mypy]
plugins = "numpy.typing.mypy_plugin"
show_error_codes = true

[[tool.mypy.overrides]]
module = "scm.plams.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "scm.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "noodles.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "rdkit.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "pandas.*"
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = "pyparsing.*"
ignore_missing_imports = true
4 changes: 2 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ max-line-length = 100

[tool:pytest]
testpaths = src test
addopts = --pycodestyle --pydocstyle --tb=short --cov --doctest-modules --cov-report xml --cov-report term --cov-report html
addopts = --tb=short --cov --doctest-modules --cov-report xml --cov-report term --cov-report html
markers = slow: A marker for slow tests requiring external quantum-chemical packages.

# Define `python setup.py build_sphinx`
Expand All @@ -29,5 +29,5 @@ builder = html

[pydocstyle]
convention = numpy
add-ignore = D401 # First line should be in imperative mood
add-ignore = D401,D105 # First line should be in imperative mood
ignore-decorators = overload
9 changes: 1 addition & 8 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,10 @@ def readme():
'pytest>=5.4',
'pytest-cov',
'pytest-mock',
'pytest-pycodestyle',
'pytest-pydocstyle>=2.1',
'typing_extensions'
]

tests_require = [
'mypy',
'types-PyYAML',
'types-setuptools',
]
tests_require += tests_no_optional_require
tests_require = tests_no_optional_require.copy()
tests_require += docs_require

setup(
Expand Down
22 changes: 22 additions & 0 deletions src/qmflows/packages/SCM.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,19 @@ class ADF_Result(Result):

prop_mapping: ClassVar[_Settings] = load_properties('ADF', prefix='properties')

# Attributes accessed via `__getattr__`
charges: "None | Any"
dipole: "None | Any"
energy: "None | Any"
enthalpy: "None | Any"
free_energy: "None | Any"
frequencies: "None | Any"
hessian: "None | Any"
homo: "None | Any"
lumo: "None | Any"
optcycles: "None | Any"
runtime: "None | Any"

def __init__(self, settings: Optional[Settings],
molecule: Optional[plams.Molecule],
job_name: str,
Expand Down Expand Up @@ -151,6 +164,15 @@ class DFTB_Result(Result):

prop_mapping: ClassVar[_Settings] = load_properties('DFTB', prefix='properties')

# Attributes accessed via `__getattr__`
charges: "None | Any"
dipole: "None | Any"
energy: "None | Any"
enthalpy: "None | Any"
free_energy: "None | Any"
frequencies: "None | Any"
hessian: "None | Any"

def __init__(self, settings: Optional[Settings],
molecule: Optional[plams.Molecule],
job_name: str,
Expand Down
21 changes: 20 additions & 1 deletion src/qmflows/packages/cp2k_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import os
from os.path import join
from typing import Any, Dict, Iterable, ClassVar, Type
from typing import Any, Dict, Iterable, ClassVar, Type, TYPE_CHECKING
from warnings import warn

import numpy as np
Expand All @@ -14,6 +14,11 @@
from ..settings import Settings
from ..warnings_qmflows import cp2k_warnings, Key_Warning
from ..type_hints import Final, _Settings, Generic2Special
from ..common import InfoMO

if TYPE_CHECKING:
from numpy.typing import NDArray
from numpy import float64 as f8

__all__ = ['cp2k']

Expand Down Expand Up @@ -131,6 +136,20 @@ class CP2K_Result(Result):

prop_mapping: ClassVar[_Settings] = load_properties('CP2K', prefix='properties')

# Attributes accessed via `__getattr__`
energy: "None | float"
frequencies: "None | NDArray[f8]"
geometry: "None | plams.Molecule"
enthalpy: "None | float"
free_energy: "None | float"
orbitals: "None | InfoMO | tuple[InfoMO, InfoMO]"
forces: "None | NDArray[f8]"
coordinates: "None | NDArray[f8]"
temperature: "None | NDArray[f8]"
volume: "None | NDArray[f8]"
lattice: "None | NDArray[f8]"
pressure: "None | NDArray[f8]"

@property
def molecule(self) -> "None | plams.Molecule":
"""Return the current geometry.
Expand Down
20 changes: 19 additions & 1 deletion src/qmflows/packages/orca.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import os
from os.path import join
from warnings import warn
from typing import Any, Union, Optional, ClassVar, List, Type
from typing import Any, Union, Optional, ClassVar, List, Type, TYPE_CHECKING

import numpy as np
from scm import plams
Expand All @@ -15,6 +15,11 @@
from ..type_hints import Final, _Settings
from ..utils import get_tmpfile_name
from ..warnings_qmflows import Key_Warning
from ..common import InfoMO

if TYPE_CHECKING:
from numpy.typing import NDArray
from numpy import float64 as f8

# ============================= Orca ==========================================

Expand All @@ -24,6 +29,19 @@ class ORCA_Result(Result):

prop_mapping: ClassVar[_Settings] = load_properties('ORCA', prefix='properties')

# Attributes accessed via `__getattr__`
charges: "None | list[float]"
dipole: "None | list[float]"
energy: "None | float"
enthalpy: "None | float"
free_energy: "None | float"
frequencies: "None | NDArray[f8]"
hessian: "None | NDArray[f8]"
normal_modes: "None | NDArray[f8]"
optcycles: "None | int"
orbitals: "None | InfoMO"
runtime: "None | float"

@property
def molecule(self) -> Optional[plams.Molecule]:
"""Retrieve the molecule from the output file."""
Expand Down
7 changes: 6 additions & 1 deletion src/qmflows/packages/package_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@

import os
from os.path import join
from typing import Type, TypeVar, Union, ClassVar, Any, Dict, Optional, TypeVar, Generic, Tuple
from typing import (
Type, TypeVar, Union, ClassVar, Any, Dict, Optional, TypeVar, Generic, Tuple, TYPE_CHECKING
)
from warnings import warn

from scm import plams
Expand Down Expand Up @@ -187,6 +189,9 @@ class PackageWrapper(Package, Generic[JT]):
result_type: ClassVar[Type[ResultWrapper]] = ResultWrapper
job_type: Type[JT]

if TYPE_CHECKING:
def __getattr__(self, name: str) -> Any: ...

def __init__(self, job_type: Type[JT], name: Optional[str] = None) -> None:
"""Initialize this instance.
Expand Down
77 changes: 40 additions & 37 deletions src/qmflows/packages/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from os.path import join
from warnings import warn
from typing import (
Any, Callable, Optional, Union, ClassVar, Mapping, Iterator, Type, Dict, TypeVar, Tuple, List
Any, Callable, Optional, Union, ClassVar, Mapping, Iterator,
Type, Dict, TypeVar, Tuple, List, TYPE_CHECKING,
)

import numpy as np
Expand Down Expand Up @@ -109,48 +110,50 @@ def __deepcopy__(self, memo: Optional[Dict[int, Any]] = None) -> 'Result':
copy_instance.__dict__ = self.__dict__.copy()
return copy_instance

def __getattr__(self, prop: str) -> Any:
"""Return a section of the results.
# Hide `__getattr__` from the type checker and use explicit attribute annotations
if not TYPE_CHECKING:
def __getattr__(self, prop: str) -> Any:
"""Return a section of the results.
For example:
For example:
..code:: python
..code:: python
>>> from qmflows.packages.packages import Result
>>> from qmflows.packages.packages import Result
>>> result = Result(...) # doctest: +SKIP
>>> dipole = result.dipole # doctest: +SKIP
>>> result = Result(...) # doctest: +SKIP
>>> dipole = result.dipole # doctest: +SKIP
"""
is_private = prop.startswith('__') and prop.endswith('__')
has_crashed = self.status in {'failed', 'crashed'}

if not has_crashed and prop in self.prop_mapping:
return self.get_property(prop)

elif not (has_crashed or is_private or prop in self.prop_mapping):
if self._results_open:
warn(f"Generic property {prop!r} not defined",
category=QMFlows_Warning, stacklevel=2)

# Do not issue this warning if the Results object is still pickled
else: # Unpickle the Results instance and try again
self._unpack_results()
try:
return vars(self)[prop] # Avoid recursive `getattr` calls
except KeyError:
warn(f"Generic property {prop!r} not defined",
category=QMFlows_Warning, stacklevel=2)
"""
is_private = prop.startswith('__') and prop.endswith('__')
has_crashed = self.status in {'failed', 'crashed'}

elif has_crashed and not is_private:
warn(f"""
It is not possible to retrieve property: {prop!r}
Because Job: {self.job_name!r} has {self.status}. Check the output.\n
Are you sure that you have the package installed or
you have loaded the package in the cluster. For example:
`module load AwesomeQuantumPackage/3.141592`
""", category=QMFlows_Warning, stacklevel=2)
return None
if not has_crashed and prop in self.prop_mapping:
return self.get_property(prop)

elif not (has_crashed or is_private or prop in self.prop_mapping):
if self._results_open:
warn(f"Generic property {prop!r} not defined",
category=QMFlows_Warning, stacklevel=2)

# Do not issue this warning if the Results object is still pickled
else: # Unpickle the Results instance and try again
self._unpack_results()
try:
return vars(self)[prop] # Avoid recursive `getattr` calls
except KeyError:
warn(f"Generic property {prop!r} not defined",
category=QMFlows_Warning, stacklevel=2)

elif has_crashed and not is_private:
warn(f"""
It is not possible to retrieve property: {prop!r}
Because Job: {self.job_name!r} has {self.status}. Check the output.\n
Are you sure that you have the package installed or
you have loaded the package in the cluster. For example:
`module load AwesomeQuantumPackage/3.141592`
""", category=QMFlows_Warning, stacklevel=2)
return None

def __dir__(self) -> List[str]:
"""Implement ``dir(self)``."""
Expand Down
48 changes: 0 additions & 48 deletions test/test_mypy.py

This file was deleted.

0 comments on commit 0e2ea97

Please sign in to comment.