diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 4a59aeb..f3855d1 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -53,6 +53,13 @@ doesn't have to be used in the readme, but ideally should be similar to a readme 10. Run through the appropriate [Language Checklist](#language-checklist) 11. Do a Pull Request +## Updating an Existing Example + +If you change the code or documentation of an example in a way that would matter to readers, remember +to update the `updated_date` field in the `article.yml` file. + +This is used by the Zaber website to display a last modified date in addition to the creation date. + ## Language Checklist Here are the checklists for the various languages: diff --git a/examples/_template/article.yml.template b/examples/_template/article.yml.template index 3a92788..17315ac 100644 --- a/examples/_template/article.yml.template +++ b/examples/_template/article.yml.template @@ -2,7 +2,8 @@ # then edit the content of the metatdata in this YAML file for rendering on https://software.zaber.com/examples authors: - Andrew Lau -date: 2024-05-01 +date: 2024-05-01 # This should stay at the original publication date of the article. +updated_date: 2025-06-02 # Update this if you change the content (code or documentation) but not infrastructure. category: Microscopy tags: - Python diff --git a/examples/calibration_2d/article.yml b/examples/calibration_2d/article.yml index c2e2892..161ee1a 100644 --- a/examples/calibration_2d/article.yml +++ b/examples/calibration_2d/article.yml @@ -1,6 +1,7 @@ authors: - Andrew Lau date: 2023-09-07 +updated_date: 2023-09-27 category: Calibration tags: - Python diff --git a/examples/gui_pyqt6/article.yml b/examples/gui_pyqt6/article.yml index 56c7663..f532e26 100644 --- a/examples/gui_pyqt6/article.yml +++ b/examples/gui_pyqt6/article.yml @@ -1,6 +1,7 @@ authors: - Nathan Paolini date: 2023-06-01 +updated_date: 2023-06-01 category: Graphical User Interface tags: - Qt diff --git a/examples/gui_raspberrypi_touch/article.yml b/examples/gui_raspberrypi_touch/article.yml index 5f13332..252f654 100644 --- a/examples/gui_raspberrypi_touch/article.yml +++ b/examples/gui_raspberrypi_touch/article.yml @@ -1,6 +1,7 @@ authors: - Martin Zak date: 2023-06-02 +updated_date: 2023-06-02 category: Graphical User Interface tags: - Raspberry PI diff --git a/examples/gui_swift_ui/article.yml b/examples/gui_swift_ui/article.yml index de15dbd..bd715fc 100644 --- a/examples/gui_swift_ui/article.yml +++ b/examples/gui_swift_ui/article.yml @@ -1,6 +1,7 @@ authors: - Silviu Toderita date: 2025-04-21 +updated_date: 2025-04-21 category: Graphical User Interface tags: - Swift diff --git a/examples/gui_textual/article.yml b/examples/gui_textual/article.yml index c4a5bd8..5357962 100644 --- a/examples/gui_textual/article.yml +++ b/examples/gui_textual/article.yml @@ -1,6 +1,7 @@ authors: - Andrew Lau date: 2023-06-03 +updated_date: 2023-06-03 category: Graphical User Interface tags: - Python diff --git a/examples/hid_joystick/article.yml b/examples/hid_joystick/article.yml index df490a1..9f3d2f1 100644 --- a/examples/hid_joystick/article.yml +++ b/examples/hid_joystick/article.yml @@ -1,6 +1,7 @@ authors: - Soleil Lapierre date: 2023-06-04 +updated_date: 2023-06-04 category: User Interaction tags: - Joystick diff --git a/examples/microplate_scanning_basic/article.yml b/examples/microplate_scanning_basic/article.yml index da40eaa..a25a051 100644 --- a/examples/microplate_scanning_basic/article.yml +++ b/examples/microplate_scanning_basic/article.yml @@ -2,6 +2,7 @@ authors: - Andrew Lau - Mike Fussell date: 2023-06-12 +updated_date: 2023-06-12 category: Microscopy tags: - Microplate diff --git a/examples/microscope_autofocus/article.yml b/examples/microscope_autofocus/article.yml index 1ce5c81..8b4ce4f 100644 --- a/examples/microscope_autofocus/article.yml +++ b/examples/microscope_autofocus/article.yml @@ -1,6 +1,7 @@ authors: - Skyler Olson date: 2023-06-15 +updated_date: 2023-06-15 category: Microscopy tags: - Computer Vision diff --git a/examples/microscope_filter_cube/article.yml b/examples/microscope_filter_cube/article.yml index 380547b..a70fea4 100644 --- a/examples/microscope_filter_cube/article.yml +++ b/examples/microscope_filter_cube/article.yml @@ -1,6 +1,7 @@ authors: - Stefan Martin date: 2023-06-10 +updated_date: 2023-06-10 category: Microscopy tags: - Filter Cube diff --git a/examples/microscope_focus_map/article.yml b/examples/microscope_focus_map/article.yml index eb51eba..8270a18 100644 --- a/examples/microscope_focus_map/article.yml +++ b/examples/microscope_focus_map/article.yml @@ -2,6 +2,7 @@ authors: - Andrew Lau - Mike Fussell date: 2023-07-31 +updated_date: 2023-07-31 category: Microscopy tags: - Python diff --git a/examples/microscope_high_throughput_scanning/article.yml b/examples/microscope_high_throughput_scanning/article.yml index f2f3ca4..29aca39 100644 --- a/examples/microscope_high_throughput_scanning/article.yml +++ b/examples/microscope_high_throughput_scanning/article.yml @@ -1,6 +1,7 @@ authors: - Stefan Martin date: 2023-06-15 +updated_date: 2024-07-24 category: Microscopy tags: - Python diff --git a/examples/microscope_illuminator/article.yml b/examples/microscope_illuminator/article.yml index 5234fe1..905b11d 100644 --- a/examples/microscope_illuminator/article.yml +++ b/examples/microscope_illuminator/article.yml @@ -1,6 +1,7 @@ authors: - Stefan Martin date: 2023-06-07 +updated_date: 2023-06-07 category: Microscopy tags: - Illuminator diff --git a/examples/microscope_tiling_basler_camera/article.yml b/examples/microscope_tiling_basler_camera/article.yml index e8f8887..0203c06 100644 --- a/examples/microscope_tiling_basler_camera/article.yml +++ b/examples/microscope_tiling_basler_camera/article.yml @@ -1,6 +1,7 @@ authors: - Colby Sparks date: 2024-04-29 +updated_date: 2024-07-02 category: Microscopy tags: - Tiling Automation diff --git a/examples/microscope_well_plate_loader/article.yml b/examples/microscope_well_plate_loader/article.yml index 68a8041..0c2370f 100644 --- a/examples/microscope_well_plate_loader/article.yml +++ b/examples/microscope_well_plate_loader/article.yml @@ -1,6 +1,7 @@ authors: - Alex Canan and Albert David date: 2025-02-20 +updated_date: 2025-02-25 category: Microscopy tags: - Well Plate diff --git a/examples/motion_input_shaping/article.yml b/examples/motion_input_shaping/article.yml index dd20947..58b02a7 100644 --- a/examples/motion_input_shaping/article.yml +++ b/examples/motion_input_shaping/article.yml @@ -1,6 +1,7 @@ authors: - Graham Kerr date: 2023-06-08 +updated_date: 2023-06-08 category: Motion tags: - Python diff --git a/examples/motion_pvt_sequence_generation/article.yml b/examples/motion_pvt_sequence_generation/article.yml index 5b2fdbb..4a97258 100644 --- a/examples/motion_pvt_sequence_generation/article.yml +++ b/examples/motion_pvt_sequence_generation/article.yml @@ -1,6 +1,7 @@ authors: - Jeff Homer date: 2023-12-12 +updated_date: 2023-12-12 category: Motion tags: - Python diff --git a/examples/motion_tracking/article.yml b/examples/motion_tracking/article.yml index e21fa24..5b5cf5d 100644 --- a/examples/motion_tracking/article.yml +++ b/examples/motion_tracking/article.yml @@ -1,6 +1,7 @@ authors: - Martin Zak date: 2024-12-05 +updated_date: 2024-12-05 category: Motion tags: - PID diff --git a/examples/self_test_direct_encoder_stage/article.yml b/examples/self_test_direct_encoder_stage/article.yml index b3a7f83..a6d9513 100644 --- a/examples/self_test_direct_encoder_stage/article.yml +++ b/examples/self_test_direct_encoder_stage/article.yml @@ -1,6 +1,7 @@ authors: - Nathan Paolini date: 2023-06-10 +updated_date: 2023-06-10 category: Diagnostics tags: - Python diff --git a/examples/util_com_port_scan/article.yml b/examples/util_com_port_scan/article.yml index 0738235..2951617 100644 --- a/examples/util_com_port_scan/article.yml +++ b/examples/util_com_port_scan/article.yml @@ -1,6 +1,7 @@ authors: - Nathan Paolini date: 2023-06-04 +updated_date: 2023-06-04 category: Utility tags: - COM Port diff --git a/examples/util_pyinstaller/article.yml b/examples/util_pyinstaller/article.yml index 4cb8bff..10ab9e0 100644 --- a/examples/util_pyinstaller/article.yml +++ b/examples/util_pyinstaller/article.yml @@ -1,6 +1,7 @@ authors: - Software Team date: 2024-03-18 +updated_date: 2024-12-09 category: Utility tags: - PyInstaller diff --git a/tools/check_examples/pdm.lock b/tools/check_examples/pdm.lock index df4e5dc..efd2e39 100644 --- a/tools/check_examples/pdm.lock +++ b/tools/check_examples/pdm.lock @@ -4,8 +4,11 @@ [metadata] groups = ["default", "dev"] strategy = ["cross_platform", "inherit_metadata"] -lock_version = "4.4.2" -content_hash = "sha256:1c24566ae66b511c62852c3809727433d0be1de9b721a81bd90af7feddf7d834" +lock_version = "4.5.0" +content_hash = "sha256:cea8da11b331af35e830c957ec19e9f75fdc15ce9803f8f4661456667d18b7a1" + +[[metadata.targets]] +requires_python = "==3.12.*" [[package]] name = "astroid" @@ -13,6 +16,9 @@ version = "3.2.2" requires_python = ">=3.8.0" summary = "An abstract syntax tree for Python with inference support." groups = ["dev"] +dependencies = [ + "typing-extensions>=4.0.0; python_version < \"3.11\"", +] files = [ {file = "astroid-3.2.2-py3-none-any.whl", hash = "sha256:e8a0083b4bb28fcffb6207a3bfc9e5d0a68be951dd7e336d5dcf639c682388c0"}, {file = "astroid-3.2.2.tar.gz", hash = "sha256:8ead48e31b92b2e217b6c9733a21afafe479d52d6e164dd25fb1a770c7c3cf94"}, @@ -30,6 +36,8 @@ dependencies = [ "packaging>=22.0", "pathspec>=0.9.0", "platformdirs>=2", + "tomli>=1.1.0; python_version < \"3.11\"", + "typing-extensions>=4.0.1; python_version < \"3.11\"", ] files = [ {file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"}, @@ -48,6 +56,7 @@ summary = "Composable command line interface toolkit" groups = ["dev"] dependencies = [ "colorama; platform_system == \"Windows\"", + "importlib-metadata; python_version < \"3.8\"", ] files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, @@ -119,6 +128,7 @@ summary = "Optional static typing for Python" groups = ["dev"] dependencies = [ "mypy-extensions>=1.0.0", + "tomli>=1.1.0; python_version < \"3.11\"", "typing-extensions>=4.1.0", ] files = [ @@ -182,6 +192,7 @@ requires_python = ">=3.6" summary = "Python docstring style checker" groups = ["dev"] dependencies = [ + "importlib-metadata<5.0.0,>=2.0.0; python_version < \"3.8\"", "snowballstemmer>=2.2.0", ] files = [ @@ -198,18 +209,40 @@ groups = ["dev"] dependencies = [ "astroid<=3.3.0-dev0,>=3.2.2", "colorama>=0.4.5; sys_platform == \"win32\"", + "dill>=0.2; python_version < \"3.11\"", "dill>=0.3.6; python_version >= \"3.11\"", "dill>=0.3.7; python_version >= \"3.12\"", "isort!=5.13.0,<6,>=4.2.5", "mccabe<0.8,>=0.6", "platformdirs>=2.2.0", + "tomli>=1.1.0; python_version < \"3.11\"", "tomlkit>=0.10.1", + "typing-extensions>=3.10.0; python_version < \"3.10\"", ] files = [ {file = "pylint-3.2.5-py3-none-any.whl", hash = "sha256:32cd6c042b5004b8e857d727708720c54a676d1e22917cf1a2df9b4d4868abd6"}, {file = "pylint-3.2.5.tar.gz", hash = "sha256:e9b7171e242dcc6ebd0aaa7540481d1a72860748a0a7816b8fe6cf6c80a6fe7e"}, ] +[[package]] +name = "pyyaml" +version = "6.0.2" +requires_python = ">=3.8" +summary = "YAML parser and emitter for Python" +groups = ["default"] +files = [ + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + [[package]] name = "snowballstemmer" version = "2.2.0" @@ -231,6 +264,17 @@ files = [ {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] +[[package]] +name = "types-pyyaml" +version = "6.0.12.20250402" +requires_python = ">=3.9" +summary = "Typing stubs for PyYAML" +groups = ["dev"] +files = [ + {file = "types_pyyaml-6.0.12.20250402-py3-none-any.whl", hash = "sha256:652348fa9e7a203d4b0d21066dfb00760d3cbd5a15ebb7cf8d33c88a49546681"}, + {file = "types_pyyaml-6.0.12.20250402.tar.gz", hash = "sha256:d7c13c3e6d335b6af4b0122a01ff1d270aba84ab96d1a1a1063ecba3e13ec075"}, +] + [[package]] name = "typing-extensions" version = "4.11.0" diff --git a/tools/check_examples/pyproject.toml b/tools/check_examples/pyproject.toml index 0185f25..cce513d 100644 --- a/tools/check_examples/pyproject.toml +++ b/tools/check_examples/pyproject.toml @@ -7,6 +7,7 @@ authors = [ ] dependencies = [ "docopt-ng>=0.9.0", + "pyyaml>=6.0.2", ] requires-python = "==3.12.*" readme = "README.md" @@ -18,10 +19,12 @@ distribution = false [tool.pdm.scripts] check = {call="src.check_examples.check:main"} -[tool.pdm.dev-dependencies] + +[dependency-groups] dev = [ "black>=24.4.2", "pylint>=3.2.5", "pydocstyle>=6.3.0", "mypy>=1.10.0", + "types-PyYAML>=6.0.12.20250402", ] diff --git a/tools/check_examples/src/check_examples/check.py b/tools/check_examples/src/check_examples/check.py index 499dfb3..f1d3ee4 100644 --- a/tools/check_examples/src/check_examples/check.py +++ b/tools/check_examples/src/check_examples/check.py @@ -27,7 +27,7 @@ from .common import filter_not_ignored, load_ignore, get_git_root_directory from .terminal_utils import iprint, iprint_pass, iprint_fail, iprint_info, match_string from .check_python import check_python -from .check_basic import check_basic, check_markdown +from .check_basic import check_basic, check_markdown, validate_article_metadata EXAMPLE_DIR = "examples" TOOLS_DIR = "tools" @@ -222,6 +222,7 @@ def check_example(directory: Path, fix: bool) -> int: return_code = 0 return_code |= check_basic(directory) return_code |= check_markdown(directory) + return_code |= validate_article_metadata(directory) return_code |= check_python(directory, fix) # Can add other languages here such as check_csharp(), check_html(), etc. return return_code diff --git a/tools/check_examples/src/check_examples/check_basic.py b/tools/check_examples/src/check_examples/check_basic.py index 051d0ea..dfd0826 100644 --- a/tools/check_examples/src/check_examples/check_basic.py +++ b/tools/check_examples/src/check_examples/check_basic.py @@ -1,6 +1,10 @@ """Check for basic requirements of example repositories.""" +from datetime import date from pathlib import Path + +import yaml + from .common import file_exists, list_files_of_suffix, execute, get_git_root_directory from .terminal_utils import iprint_fail, iprint_pass from .markdown_links import check_links_in_markdown @@ -32,3 +36,26 @@ def check_markdown(directory: Path, recurse: bool = True) -> int: ) return_code |= check_links_in_markdown(file) return return_code + + +def validate_article_metadata(directory: Path) -> int: + """Check that the required yaml fields exist.""" + with open(directory.joinpath("article.yml"), "r", encoding="utf-8") as stream: + meta = yaml.safe_load(stream) + + required_fields = { + "date": date, + "updated_date": date, + "category": str, + "picture": str, + } + + for field, field_type in required_fields.items(): + if field not in meta: + iprint_fail(f"article.yml does not contain the {field} field.", 1) + return 1 + if not isinstance(meta[field], field_type): + iprint_fail(f"article.yml {field} field is not of type {field_type.__name__}.", 1) + return 1 + + return 0