Skip to content
Draft
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
31 changes: 29 additions & 2 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ permissions:
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
deploy-docs:
uses: CitrineInformatics/common-gh-actions/.github/workflows/deploy-docs.yml@v1
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
- name: Build Docs
# uv run puts the project environment, and so sphinx-build, on PATH for make
run: |
uv run --locked --group docs make -C docs/ html
touch docs/_build/html/.nojekyll
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: 'docs/_build/html'
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
3 changes: 2 additions & 1 deletion .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ on:

jobs:
pr-checks:
uses: CitrineInformatics/common-gh-actions/.github/workflows/repo-checks.yml@v1
uses: CitrineInformatics/common-gh-actions/.github/workflows/repo-checks.yml@v3
with:
src: gemd
check_version_bump: ${{ github.base_ref == 'main' }}
4 changes: 1 addition & 3 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ on:

jobs:
pr-tests:
uses: CitrineInformatics/common-gh-actions/.github/workflows/run-tests.yml@v1.1
uses: CitrineInformatics/common-gh-actions/.github/workflows/matrix-tests.yml@v3
with:
src: gemd
skip_38: true
include_313: true
42 changes: 17 additions & 25 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@

Documentation is built using [Sphinx](https://www.sphinx-doc.org/en/master/) with the
[autodoc](https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#module-sphinx.ext.autodoc)
extension. In order to build the documentation, you can install the necessary packages and then use make:
extension. Build the documentation with [uv](https://docs.astral.sh/uv/):
```shell
pip install -r doc_requirements.txt
make make -C docs/ html
uv run --group docs make -C docs/ html
```

All documentation source files and configuration are in the `docs/source` directory.
Expand All @@ -17,32 +16,25 @@ Additional reference material, such as tutorials, should be referenced in `index
## Testing

Changes are gated on:
* Passing unit tests with minimum supported library versions
* Passing unit tests with the lowest supported versions of direct dependencies
* 100% test coverage
* PEP8 style compliance, with some exceptions in the [tox file](tox.ini)
* Incrementing the package version number in [setup.py](setup.py)
* Passing `ruff check` and `ruff format --check`, configured in [pyproject.toml](pyproject.toml)
* Removing code whose deprecation has reached its `removed_in` version
* A [uv.lock](uv.lock) that matches [pyproject.toml](pyproject.toml)
* Incrementing the package version number in [gemd/\_\_version\_\_.py](gemd/__version__.py), for PRs into `main`

Additionally, PRs are checked against (but not blocked by):
* Passing unit tests with latest nominally supported versions of all dependencies
* Documentation builds without warnings
CI also runs the unit tests against the latest versions of all dependencies. Failures there do not block a PR.

In order to run tests locally, you'll need to install additional packages:
Run the same checks locally with uv:
```shell
pip install -r test_requirements.txt
uv run ruff check gemd tests
uv run ruff format --check gemd tests
uv run pytest --cov=gemd --cov-report term-missing:skip-covered --cov-fail-under=100 tests/
```

A test runner is available in `scripts/run_tests.sh`, which gives a convenient one-line invocation for testing.

As it can be easy to forget to verify these prior to pushing,
it's possible to use [git hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
to enforce compliance during normal workflows.
Consider editing `.git/hooks/pre-commit` or `.git/hooks/pre-push`
(or adding them and marking them as executable: `chmod +x <file>`).
For example, you could set your local `.git/hooks/pre-commit` to be
```shell
scripts/run_tests.sh --quiet --exitfirst
```
to make sure you're not on the `main` branch, you've incremented the package version, you pass the linter and you have complete, passing tests.
To run these checks before every commit, add them to a
[git hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) such as `.git/hooks/pre-commit`,
and mark the hook executable with `chmod +x`.

## Coding Style
This project follows [PEP8](https://www.python.org/dev/peps/pep-0008/), with the following exception:
Expand Down Expand Up @@ -79,10 +71,10 @@ To create a release:
* You can use `git diff` as well, if you prefer
* In another tab, navigate to the [GitHub release creation page](https://github.com/CitrineInformatics/gemd-python/releases/new)
* Set the `Target` to the commit you want to deploy, typically `main`
* Set the `Tag version` to `v{x.y.z}` where `x.y.z` is the version in [setup.py](setup.py), e.g. `v1.2.3`
* Set the `Tag version` to `v{x.y.z}` where `x.y.z` is the version in [gemd/\_\_version\_\_.py](gemd/__version__.py), e.g. `v1.2.3`
* Set the `Release title` to "GEMD v{x.y.z} is released!"
* Populate the release notes with a 1 or 2 sentence summary and `What's New`, `Improvements`, `Fixes`, and `Deprecated` sections, as appropriate

Travis will trigger the deploy due to the addition of the tag.
Publishing the release triggers GitHub Actions to deploy the package to PyPI and the documentation to GitHub Pages.
Only commits on the **main** or backports branches can be released, but it need not be the most recent commit on the branch.
The tests contained within this repository are sufficient to verify a release.
3 changes: 0 additions & 3 deletions doc_requirements.txt

This file was deleted.

13 changes: 13 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import gemd
import inspect
import os
import sys
sys.path.insert(0, os.path.abspath('../../gemd'))
Expand Down Expand Up @@ -89,3 +90,15 @@
'sticky_navigation': False,
'logo_only': True
}


def _skip_builtin_members(app, what, name, obj, skip, options):
"""Skip members implemented in C, such as int.to_bytes inherited by IntEnum classes."""
implemented_in_c = (
inspect.isbuiltin(obj) or inspect.ismethoddescriptor(obj) or inspect.isgetsetdescriptor(obj)
)
return True if implemented_in_c else None


def setup(app):
app.connect("autodoc-skip-member", _skip_builtin_members)
2 changes: 1 addition & 1 deletion gemd/__version__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "2.4.0"
__version__ = "3.0.0"
27 changes: 13 additions & 14 deletions gemd/builders/impl.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Tools to help build GEMD objects
from typing import List, Union

from gemd.entity.attribute import Condition, Parameter, Property, PropertyAndConditions
from gemd.entity.attribute.base_attribute import BaseAttribute
Expand Down Expand Up @@ -104,10 +103,10 @@ def add_edge(
output_material: MaterialRun,
*,
name: str = None,
mass_fraction: Union[float, ContinuousValue] = None,
number_fraction: Union[float, ContinuousValue] = None,
volume_fraction: Union[float, ContinuousValue] = None,
absolute_quantity: Union[int, float, ContinuousValue] = None,
mass_fraction: float | ContinuousValue = None,
number_fraction: float | ContinuousValue = None,
volume_fraction: float | ContinuousValue = None,
absolute_quantity: int | float | ContinuousValue = None,
absolute_units: str = None,
) -> IngredientRun:
"""Connect two material-process spec-run quadruples with ingredients.
Expand Down Expand Up @@ -209,7 +208,7 @@ def add_measurement(
*,
name: str = None,
template: MeasurementTemplate = None,
attributes: List[BaseAttribute] = None,
attributes: list[BaseAttribute] = None,
) -> MeasurementRun:
"""Add a measurement run-spec set to a :class:`~gemd.entity.object.material_run.MaterialRun`.

Expand Down Expand Up @@ -264,10 +263,10 @@ def add_measurement(


def add_attribute(
target: Union[HasProperties, HasConditions, HasParameters],
template: Union[PropertyTemplate, ConditionTemplate, ParameterTemplate],
value: Union[BaseValue, str, float, int],
) -> Union[Property, Condition, Parameter]:
target: HasProperties | HasConditions | HasParameters,
template: PropertyTemplate | ConditionTemplate | ParameterTemplate,
value: BaseValue | str | float | int,
) -> Property | Condition | Parameter:
"""Generate an attribute, and then add it attribute to a GEMD object.

Parameters
Expand Down Expand Up @@ -314,9 +313,9 @@ def add_attribute(


def make_attribute(
template: Union[PropertyTemplate, ConditionTemplate, ParameterTemplate],
value: Union[BaseValue, str, float, int],
) -> Union[Property, Condition, Parameter]:
template: PropertyTemplate | ConditionTemplate | ParameterTemplate,
value: BaseValue | str | float | int,
) -> Property | Condition | Parameter:
"""Generate an Attribute and the contained Value.

Parameters
Expand Down Expand Up @@ -350,7 +349,7 @@ def make_attribute(
return attribute


def make_value(value: Union[str, float, int], bounds: BaseBounds) -> BaseValue:
def make_value(value: str | float | int, bounds: BaseBounds) -> BaseValue:
"""Generate a Value object based upon a number or string and a particular bounds.

Parameters
Expand Down
2 changes: 1 addition & 1 deletion gemd/demo/strehlow_and_cook.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Demo representing Strehlow & Cook bandgap data with data concepts."""

from typing import Iterable
from collections.abc import Iterable

from gemd.entity.attribute.condition import Condition
from gemd.entity.attribute.property import Property
Expand Down
20 changes: 10 additions & 10 deletions gemd/entity/attribute/base_attribute.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from abc import abstractmethod
from typing import Iterable, List, Optional, Type, Union
from collections.abc import Iterable

from gemd.entity.bounds_validation import WarningLevel, get_validation_level
from gemd.entity.dict_serializable import DictSerializable, logger
Expand Down Expand Up @@ -37,11 +37,11 @@ def __init__(
self,
name: str,
*,
template: Union[AttributeTemplate, LinkByUID, None] = None,
origin: Union[Origin, str] = Origin.UNKNOWN,
template: AttributeTemplate | LinkByUID | None = None,
origin: Origin | str = Origin.UNKNOWN,
value: BaseValue = None,
notes: str = None,
file_links: Optional[Union[Iterable[FileLink], FileLink]] = None,
file_links: Iterable[FileLink] | FileLink | None = None,
):
self.name = name
self.notes = notes
Expand Down Expand Up @@ -85,12 +85,12 @@ def value(self, value: BaseValue):
raise TypeError(f"value must be a BaseValue: {value}")

@property
def template(self) -> Optional[Union[AttributeTemplate, LinkByUID]]:
def template(self) -> AttributeTemplate | LinkByUID | None:
"""Get the template."""
return self._template

@template.setter
def template(self, template: Optional[Union[AttributeTemplate, LinkByUID]]):
def template(self, template: AttributeTemplate | LinkByUID | None):
"""Set the template."""
if template is None:
self._template = None
Expand All @@ -103,7 +103,7 @@ def template(self, template: Optional[Union[AttributeTemplate, LinkByUID]]):

@staticmethod
@abstractmethod
def _template_type() -> Type:
def _template_type() -> type:
"""Get the expected type of template for this object (property of child)."""

@property
Expand All @@ -112,18 +112,18 @@ def origin(self) -> Origin:
return self._origin

@origin.setter
def origin(self, origin: Union[Origin, str]):
def origin(self, origin: Origin | str):
"""Set the origin."""
if origin is None:
raise ValueError("origin must be specified (but may be `unknown`)")
self._origin = Origin.from_str(origin, exception=True)

@property
def file_links(self) -> List[FileLink]:
def file_links(self) -> list[FileLink]:
"""Get the file links."""
return self._file_links

@file_links.setter
def file_links(self, file_links: Optional[Union[Iterable[FileLink], FileLink]]):
def file_links(self, file_links: Iterable[FileLink] | FileLink | None):
"""Set the file links."""
self._file_links = validate_list(file_links, FileLink)
4 changes: 1 addition & 3 deletions gemd/entity/attribute/condition.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Type

from gemd.entity.attribute.base_attribute import BaseAttribute
from gemd.entity.template import ConditionTemplate

Expand Down Expand Up @@ -32,5 +30,5 @@ class Condition(BaseAttribute, typ="condition"):
"""

@staticmethod
def _template_type() -> Type:
def _template_type() -> type:
return ConditionTemplate
4 changes: 1 addition & 3 deletions gemd/entity/attribute/parameter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Type

from gemd.entity.attribute.base_attribute import BaseAttribute
from gemd.entity.template import ParameterTemplate

Expand Down Expand Up @@ -33,5 +31,5 @@ class Parameter(BaseAttribute, typ="parameter"):
"""

@staticmethod
def _template_type() -> Type:
def _template_type() -> type:
return ParameterTemplate
4 changes: 1 addition & 3 deletions gemd/entity/attribute/property.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Type

from gemd.entity.attribute.base_attribute import BaseAttribute
from gemd.entity.template import PropertyTemplate

Expand Down Expand Up @@ -32,5 +30,5 @@ class Property(BaseAttribute, typ="property"):
"""

@staticmethod
def _template_type() -> Type:
def _template_type() -> type:
return PropertyTemplate
8 changes: 4 additions & 4 deletions gemd/entity/attribute/property_and_conditions.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Iterable, List, Optional, Union
from collections.abc import Iterable

from gemd.entity.attribute.condition import Condition
from gemd.entity.attribute.property import Property
Expand Down Expand Up @@ -26,15 +26,15 @@ class PropertyAndConditions(DictSerializable, typ="property_and_conditions"):
"""

def __init__(
self, property: Property = None, conditions: Union[Iterable[Condition], Condition] = None
self, property: Property = None, conditions: Iterable[Condition] | Condition = None
):
self._property = None
self.property = property
self._conditions = None
self.conditions = conditions

@property
def conditions(self) -> List[Condition]:
def conditions(self) -> list[Condition]:
"""Get conditions."""
return self._conditions

Expand All @@ -49,7 +49,7 @@ def name(self) -> str:
return self.property.name

@property
def template(self) -> Optional[Union[PropertyTemplate, LinkByUID]]:
def template(self) -> PropertyTemplate | LinkByUID | None:
"""Get template of attribute (use template of property)."""
return self.property.template

Expand Down
Loading
Loading