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
75 changes: 65 additions & 10 deletions docs/competition_creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ competition you host.
**Usage:**

```bash
kaggle competitions pages create <competition> --name <page-name> -f <path> \
kaggle competitions pages create <competition> --page-name <page-name> -f <path> \
[--mime-type <type>] [--post-title "<title>"] [--publish]
```

Expand All @@ -178,7 +178,7 @@ kaggle competitions pages create <competition> --name <page-name> -f <path> \

**Options:**

- `--name <page-name>` (required): Page name (e.g. `description`, `rules`,
- `--page-name <page-name>` (required): Page name (e.g. `description`, `rules`,
`evaluation`, `data-description`, `prizes`). Conventional names are
recognized by the competition page UI; new names are allowed but won't be
shown in the standard tabs.
Expand All @@ -195,20 +195,75 @@ kaggle competitions pages create <competition> --name <page-name> -f <path> \
**Example:**

```bash
# Stage a draft of the rules page.
kaggle competitions pages create my-comp --name rules -f ./rules.md \
# Create the rules page in a staged (not-yet-published) state.
kaggle competitions pages create my-comp --page-name rules -f ./rules.md \
Comment thread
bovard marked this conversation as resolved.
--mime-type text/markdown --post-title "Competition Rules"

# Replace with a published version once you're happy.
kaggle competitions pages create my-comp --name rules -f ./rules-final.md --publish
# When you're ready to make it visible to participants:
kaggle competitions pages update my-comp --page-name rules --publish
```

**Note:** `pages create` does not update an existing page in place — it creates
a new page. Use [`kaggle competitions pages delete`](#kaggle-competitions-pages-delete)
to remove a page.
Each page exists as a single record; `--publish` / `--unpublish` toggles its
visibility rather than creating separate draft and live copies. To swap in new
content later, use
[`kaggle competitions pages update`](#kaggle-competitions-pages-update) — a
second `create` for the same page name will be rejected.

You can list and inspect existing pages with `kaggle competitions pages`
(or the explicit `kaggle competitions pages list`).
(or the explicit `kaggle competitions pages list`), modify one in place with
[`kaggle competitions pages update`](#kaggle-competitions-pages-update), or
remove one with [`kaggle competitions pages delete`](#kaggle-competitions-pages-delete).

---

## `kaggle competitions pages update`

Updates fields on an existing competition page. Only the flags you supply are
sent (the FieldMask is built from which arguments are non-default), so this is
also how you publish or unpublish a page in place.

**Usage:**

```bash
kaggle competitions pages update <competition> --page-name <current-name> \
[-f <path>] [--new-name <name>] [--mime-type <type>] \
[--post-title "<title>"] [--publish | --unpublish]
```

**Arguments:**

- `<competition>`: The competition slug.

**Options:**

- `--page-name <current-name>` (required): The page's current name (used as the
identifier; rename via `--new-name`).
- `-f, --file <path>` (optional): Path to a file with the new page body.
- `--new-name <name>` (optional): Rename the page.
- `--mime-type <type>` (optional): New MIME type of the content.
- `--post-title "<title>"` (optional): New title shown above the page content.
- `--publish` / `--unpublish` (optional, mutually exclusive): Publish or
unpublish the page.

At least one update flag is required.

**Examples:**

```bash
# Publish a staged page without changing its content.
kaggle competitions pages update my-comp --page-name rules --publish

# Swap in new content and update the visible title in one call.
kaggle competitions pages update my-comp --page-name rules \
-f ./rules-v2.md --post-title "Competition Rules (v2)"

# Rename a page.
Comment thread
bovard marked this conversation as resolved.
kaggle competitions pages update my-comp --page-name evaluation \
--new-name scoring
```

**Note:** a small set of pages is reserved by the backend and cannot be
renamed; attempting to rename one returns an error from the server.

---

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ keywords = ["Kaggle", "API"]
requires-python = ">= 3.11"
dependencies = [
"bleach",
"kagglesdk >= 0.1.32, < 1.0", # sync with kagglehub
"kagglesdk >= 0.1.33, < 1.0", # sync with kagglehub
"python-slugify",
"requests",
"python-dateutil",
Expand Down
109 changes: 109 additions & 0 deletions src/kaggle/api/kaggle_api_extended.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@
ApiListCompetitionPagesResponse,
ApiCreateCompetitionPageRequest,
ApiDeleteCompetitionPageRequest,
ApiUpdateCompetitionPageRequest,
ApiCompetitionPage,
ApiCreateCompetitionRequest,
ApiCreateCompetitionResponse,
Expand Down Expand Up @@ -2431,6 +2432,114 @@ def competition_create_page_cli(
status = "published" if page.is_published else "staged (unpublished)"
print(f'Page "{page.name}" created on competition "{competition_name}" — {status}.')

def competition_update_page(
self,
competition_name: str,
page_name: str,
content_path: Optional[str] = None,
new_name: Optional[str] = None,
mime_type: Optional[str] = None,
post_title: Optional[str] = None,
is_published: Optional[bool] = None,
) -> ApiCompetitionPage:
"""Update fields on an existing competition page.

Only fields supplied here are sent to the server (the FieldMask is built
from which arguments are non-None). Pass ``new_name`` to rename the page.

Args:
competition_name (str): The competition name (slug).
page_name (str): Current page name (identifier).
content_path (Optional[str]): Path to a file whose contents become
the new page body.
new_name (Optional[str]): New page name (renames the page).
mime_type (Optional[str]): New MIME type for the content.
post_title (Optional[str]): New title shown above the content.
is_published (Optional[bool]): True publishes the page, False
unpublishes it.

Returns:
ApiCompetitionPage: the updated page.
"""
page = ApiCompetitionPage()
paths: List[str] = []

if content_path is not None:
if not os.path.isfile(content_path):
raise ValueError("Content file not found: " + content_path)
with open(content_path, "r", encoding="utf-8") as f:
page.content = f.read()
paths.append("content")
if new_name is not None:
page.name = new_name
paths.append("name")
if mime_type is not None:
page.mime_type = mime_type
paths.append("mime_type")
if post_title is not None:
page.post_title = post_title
paths.append("post_title")
if is_published is not None:
page.is_published = is_published
paths.append("is_published")

if not paths:
raise ValueError(
"Nothing to update — pass at least one of -f, --new-name, "
"--mime-type, --post-title, --publish/--unpublish."
)

with self.build_kaggle_client() as kaggle:
request = ApiUpdateCompetitionPageRequest()
request.competition_name = competition_name
request.page_name = page_name
request.page = page
request.update_mask = field_mask_pb2.FieldMask(paths=paths)
return kaggle.competitions.competition_api_client.update_competition_page(request)

def competition_update_page_cli(
self,
competition=None,
competition_opt=None,
page_name=None,
file_path=None,
new_name=None,
mime_type=None,
post_title=None,
publish=False,
unpublish=False,
quiet=False,
):
"""CLI wrapper for competition_update_page."""
competition_name = competition or competition_opt
if competition_name is None:
competition_name = self.get_config_value(self.CONFIG_NAME_COMPETITION)
if competition_name is not None and not quiet:
print("Using competition: " + competition_name)
if competition_name is None:
raise ValueError("No competition specified")
if not page_name:
raise ValueError("--page-name is required")
if publish and unpublish:
raise ValueError("--publish and --unpublish are mutually exclusive")

is_published: Optional[bool] = None
if publish:
is_published = True
elif unpublish:
is_published = False

page = self.competition_update_page(
competition_name=competition_name,
page_name=page_name,
content_path=file_path,
new_name=new_name,
mime_type=mime_type,
post_title=post_title,
is_published=is_published,
)
print(f'Page "{page.name}" updated on competition "{competition_name}".')

def competition_delete_page(
self,
competition_name: str,
Expand Down
62 changes: 61 additions & 1 deletion src/kaggle/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,65 @@ def parse_competitions(subparsers) -> None:
parser_competitions_pages_create._action_groups.append(parser_competitions_pages_create_optional)
parser_competitions_pages_create.set_defaults(func=api.competition_create_page_cli)

# Competitions pages update
parser_competitions_pages_update = subparsers_competitions_pages.add_parser(
"update",
formatter_class=argparse.RawTextHelpFormatter,
help=Help.command_competitions_pages_update,
)
parser_competitions_pages_update_optional = parser_competitions_pages_update._action_groups.pop()
parser_competitions_pages_update_optional.add_argument(
"competition", nargs="?", default=None, help=Help.param_competition
)
parser_competitions_pages_update_optional.add_argument(
"-c", "--competition", dest="competition_opt", required=False, help=argparse.SUPPRESS
)
parser_competitions_pages_update_optional.add_argument(
"--page-name",
dest="page_name",
required=True,
help="Current page name (identifier).",
)
parser_competitions_pages_update_optional.add_argument(
"-f", "--file", dest="file_path", required=False, help="Path to a file with the new page body."
)
parser_competitions_pages_update_optional.add_argument(
"--new-name",
dest="new_name",
required=False,
help="Rename the page to this value.",
)
parser_competitions_pages_update_optional.add_argument(
"--mime-type",
dest="mime_type",
required=False,
help='New MIME type of the content (e.g. "text/markdown").',
)
parser_competitions_pages_update_optional.add_argument(
"--post-title",
dest="post_title",
required=False,
help="New title shown above the page content.",
)
publish_group = parser_competitions_pages_update_optional.add_mutually_exclusive_group()
publish_group.add_argument(
"--publish",
dest="publish",
action="store_true",
help="Publish the page.",
)
publish_group.add_argument(
"--unpublish",
dest="unpublish",
action="store_true",
help="Unpublish the page (stage it).",
)
parser_competitions_pages_update_optional.add_argument(
"-q", "--quiet", dest="quiet", action="store_true", help=Help.param_quiet
)
parser_competitions_pages_update._action_groups.append(parser_competitions_pages_update_optional)
parser_competitions_pages_update.set_defaults(func=api.competition_update_page_cli)

# Competitions pages delete
parser_competitions_pages_delete = subparsers_competitions_pages.add_parser(
"delete",
Expand Down Expand Up @@ -2122,7 +2181,7 @@ class Help(object):
forums_choices = ["list", "topics"]
forums_topics_choices = ["list", "show"]
entity_topics_choices = ["list", "show"]
entity_pages_choices = ["list", "create", "delete"]
entity_pages_choices = ["list", "create", "update", "delete"]
config_choices = ["view", "set", "unset"]
auth_choices = ["login", "print-access-token", "revoke"]

Expand Down Expand Up @@ -2196,6 +2255,7 @@ class Help(object):
command_competitions_episode_logs = "Download agent logs for a simulation episode"
command_competitions_pages = "List pages for a competition"
command_competitions_pages_create = "Create a new page on a competition you host"
command_competitions_pages_update = "Update fields on an existing competition page"
command_competitions_pages_delete = "Delete a page from a competition you host"
command_competitions_launch = "Launch a competition you host, optionally at a future UTC time"
command_competitions_init = "Initialize folder with a competition-metadata.json template"
Expand Down
1 change: 0 additions & 1 deletion tests/unit/test_competition_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from kagglesdk.competitions.types.competition_enums import CompetitionPrivacy
from kagglesdk.competitions.types.competition import RewardTypeId


_MINIMAL_META = {
"title": "My Awesome Comp",
"slug": "my-awesome-comp",
Expand Down
Loading
Loading