diff --git a/grand_challenge_forge/forge.py b/grand_challenge_forge/forge.py index 8844d7f..41e4e9b 100644 --- a/grand_challenge_forge/forge.py +++ b/grand_challenge_forge/forge.py @@ -14,7 +14,6 @@ from grand_challenge_forge.generation_utils import ( ci_to_civ, create_civ_stub_file, - extract_slug, ) from grand_challenge_forge.schemas import validate_pack_context from grand_challenge_forge.utils import cookiecutter_context as cc @@ -105,11 +104,6 @@ def generate_upload_to_archive_script( ): context = deepcopy(context) - # Cannot always use filters in directory names so generate it here - context["phase"]["archive"]["slug"] = extract_slug( - context["phase"]["archive"]["url"] - ) - script_dir = ( output_directory / f"upload-to-archive-{context['phase']['archive']['slug']}" diff --git a/grand_challenge_forge/generation_utils.py b/grand_challenge_forge/generation_utils.py index 8ff7929..8099a84 100644 --- a/grand_challenge_forge/generation_utils.py +++ b/grand_challenge_forge/generation_utils.py @@ -1,5 +1,4 @@ import os -import re import shutil import uuid from pathlib import Path @@ -22,22 +21,6 @@ def is_file(component_interface): ] == "File" and not component_interface["relative_path"].endswith(".json") -def extract_slug(url): - # Define a regex pattern to match the slug in the URL - pattern = r"/([^/]+)/*$" - - # Use re.search to find the match - match = re.search(pattern, url) - - # If a match is found, extract and return the slug - if match: - slug = match.group(1) - return slug - else: - # Return None or handle the case where no match is found - return None - - def create_civ_stub_file(*, target_dir, component_interface): """Creates a stub based on a component interface""" target_dir.parent.mkdir(parents=True, exist_ok=True) diff --git a/grand_challenge_forge/partials/filters.py b/grand_challenge_forge/partials/filters.py index bd63558..208ca17 100644 --- a/grand_challenge_forge/partials/filters.py +++ b/grand_challenge_forge/partials/filters.py @@ -13,11 +13,6 @@ def register_simple_filter(func): return func -@register_simple_filter -def extract_slug(url): - return generation_utils.extract_slug(url) - - @register_simple_filter def is_json(arg): return generation_utils.is_json(arg) diff --git a/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/.gitattributes b/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/.gitattributes new file mode 100644 index 0000000..c5ae1ed --- /dev/null +++ b/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/.gitattributes @@ -0,0 +1,3 @@ +# sh (and their template counter parts) should always be lf their line endings +*.sh text=lf eol=lf +*.sh.j2 text=lf eol=lf diff --git a/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/README.md.j2 b/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/README.md.j2 index 7f0ccf6..fa3ff1c 100644 --- a/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/README.md.j2 +++ b/grand_challenge_forge/partials/pack-readme/{{cookiecutter.pack_dir_name}}/README.md.j2 @@ -1,14 +1,14 @@ # 📦 {{cookiecutter.challenge.slug}} challenge pack -Thank you for hosting your challenge on Grand-Challenge.org. We appreciate it! +Thank you for hosting your challenge on Grand-Challenge.org, we appreciate it! ## Content This Challenge Pack is a collection of challenge-tailored examples to help you on your -way to hosting your {{cookiecutter.challenge.slug}} challenge. +way to host your {{cookiecutter.challenge.slug}} challenge. It contains the following: * ️🦾 An example script to _automate uploading_ data to an archive -* 🦿 An example _submission algorithm_ that can be uploaded to run as a submission in a challenge-phase +* 🦿 An example _submission algorithm_ that can be uploaded to run as a submission in a challenge phase * 🧮 An example _evaluation method_ that evaluates algorithm submissions and generates performance metrics for ranking @@ -23,8 +23,8 @@ Challenge phases pull their data from archives to test submissions on. Your challenge has the following archives: -{% for phase in cookiecutter.challenge.phases -%} - * {{phase.archive.url}} +{% for archive in cookiecutter.challenge.archives -%} + * {{archive.url}} {% endfor %} --- diff --git a/grand_challenge_forge/schemas.py b/grand_challenge_forge/schemas.py index 1b0792a..f20c566 100644 --- a/grand_challenge_forge/schemas.py +++ b/grand_challenge_forge/schemas.py @@ -7,6 +7,31 @@ logger = logging.getLogger(__name__) +ARCHIVE_SCHEMA = { + "type": "object", + "properties": { + "slug": {"type": "string"}, + "url": {"type": "string"}, + }, + "required": ["slug", "url"], +} + +COMPONENT_INTERFACE_SCHEMA = { + "type": "object", + "properties": { + "slug": {"type": "string"}, + "relative_path": {"type": "string"}, + "kind": {"type": "string"}, + "super_kind": {"type": "string"}, + }, + "required": [ + "slug", + "relative_path", + "kind", + "super_kind", + ], +} + PACK_CONTEXT_SCHEMA = { "type": "object", @@ -15,52 +40,21 @@ "type": "object", "properties": { "slug": {"type": "string"}, + "archives": {"type": "array", "items": ARCHIVE_SCHEMA}, "phases": { "type": "array", "items": { "type": "object", "properties": { "slug": {"type": "string"}, - "archive": { - "type": "object", - "properties": {"url": {"type": "string"}}, - "required": ["url"], - }, + "archive": ARCHIVE_SCHEMA, "inputs": { "type": "array", - "items": { - "type": "object", - "properties": { - "slug": {"type": "string"}, - "relative_path": {"type": "string"}, - "kind": {"type": "string"}, - "super_kind": {"type": "string"}, - }, - "required": [ - "slug", - "relative_path", - "kind", - "super_kind", - ], - }, + "items": COMPONENT_INTERFACE_SCHEMA, }, "outputs": { "type": "array", - "items": { - "type": "object", - "properties": { - "slug": {"type": "string"}, - "relative_path": {"type": "string"}, - "kind": {"type": "string"}, - "super_kind": {"type": "string"}, - }, - "required": [ - "slug", - "relative_path", - "kind", - "super_kind", - ], - }, + "items": COMPONENT_INTERFACE_SCHEMA, }, }, "required": ["slug", "archive", "inputs", "outputs"], @@ -68,7 +62,7 @@ }, }, }, - "required": ["slug", "phases"], + "required": ["slug", "phases", "archives"], }, }, "required": ["challenge"], diff --git a/tests/test_pack_generation.py b/tests/test_pack_generation.py index bf6901d..b0ebe16 100644 --- a/tests/test_pack_generation.py +++ b/tests/test_pack_generation.py @@ -1,5 +1,4 @@ from grand_challenge_forge.forge import generate_challenge_pack -from grand_challenge_forge.generation_utils import extract_slug from tests.utils import pack_context_factory @@ -24,5 +23,5 @@ def test_for_pack_content(tmp_path): assert ( pack_dir / phase["slug"] - / f"upload-to-archive-{extract_slug(phase['archive']['url'])}" + / f"upload-to-archive-{phase['archive']['slug']}" ).exists diff --git a/tests/utils.py b/tests/utils.py index 040f369..6c1e2f5 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -6,10 +6,25 @@ DEFAULT_PACK_CONTEXT_STUB = { "challenge": { "slug": "challenge-slug", + "archives": [ + { + "slug": "archive-slug", + "url": "https://grand-challenge.org/archives/archive-slug/" + }, + { + "slug": "another-archive-slug", + "url": "https://grand-challenge.org/archives/another-archive-slug/" + }, + { + "slug": "yet-another-archive-slug", + "url": "https://grand-challenge.org/archives/yet-another-archive-slug/" + } + ], "phases": [ { "slug": "phase-slug", "archive": { + "slug": "archive-slug", "url": "https://grand-challenge.org/archives/archive-slug/" }, "inputs": [ @@ -68,6 +83,7 @@ { "slug": "another-phase-slug", "archive": { + "slug": "another-archive-slug", "url": "https://grand-challenge.org/archives/another-archive-slug/" }, "inputs": [