Skip to content

Commit 0796f8b

Browse files
Bump kagglesdk floor and inline shipped hackathon APIs
Per review: the right fix for "kagglesdk doesn't have these yet" is to bump the floor, not work around it. Bumped to >= 0.1.23 (latest) and dropped the lazy-import + getattr plumbing for the two endpoints that ship there, so `hackathon_get_overview` and `hackathon_list_writeups` now use top-of-file imports and the tests stub the SDK client directly instead of mocking helper indirection. The CSV-export and resolve-links endpoints still aren't in any released kagglesdk, so those wrappers keep a small lazy import (with a TODO) and the test fixture stubs those two modules in `sys.modules`. Co-authored-by: kaggle-agent <kaggle-agent@users.noreply.github.com>
1 parent 5c8d811 commit 0796f8b

3 files changed

Lines changed: 50 additions & 118 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ keywords = ["Kaggle", "API"]
2424
requires-python = ">= 3.11"
2525
dependencies = [
2626
"bleach",
27-
"kagglesdk >= 0.1.21, < 1.0", # sync with kagglehub
27+
"kagglesdk >= 0.1.23, < 1.0", # sync with kagglehub
2828
"python-slugify",
2929
"requests",
3030
"python-dateutil",

src/kaggle/api/kaggle_api_extended.py

Lines changed: 26 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
ApiListCompetitionTopicsResponse,
9898
ApiListTopicMessagesRequest,
9999
ApiListTopicMessagesResponse,
100+
ApiGetHackathonOverviewRequest,
101+
ApiListHackathonWriteUpsRequest,
100102
)
101103
from kagglesdk.discussions.types.discussions_api_service import (
102104
ApiDiscussionComment,
@@ -2652,81 +2654,17 @@ def entity_topic_show_cli(
26522654
hackathon_overview_page_fields = ["name"]
26532655
hackathon_writeup_link_fields = ["url", "type", "title"]
26542656

2655-
def _require_sdk_method(self, client, method_name: str, hint: str):
2656-
"""Return ``client.method_name`` or raise a clear error.
2657-
2658-
The hackathon endpoints landed in kagglesdk after this CLI release
2659-
was cut, so older SDK installs may not have them yet.
2660-
"""
2661-
method = getattr(client, method_name, None)
2662-
if method is None:
2663-
raise ValueError(
2664-
f"This command requires a newer kagglesdk that exposes " f"`{hint}`. Please upgrade kagglesdk."
2665-
)
2666-
return method
2667-
2668-
def _build_hackathon_overview_request(self, competition: str):
2669-
try:
2670-
from kagglesdk.competitions.types.competition_api_service import ( # type: ignore
2671-
ApiGetHackathonOverviewRequest,
2672-
)
2673-
except ImportError as exc:
2674-
raise ValueError("kagglesdk is missing ApiGetHackathonOverviewRequest; please upgrade kagglesdk.") from exc
2675-
request = ApiGetHackathonOverviewRequest()
2676-
request.competition_name = competition
2677-
return request
2678-
2679-
def _build_list_hackathon_writeups_request(self, competition: str):
2680-
try:
2681-
from kagglesdk.competitions.types.competition_api_service import ( # type: ignore
2682-
ApiListHackathonWriteUpsRequest,
2683-
)
2684-
except ImportError as exc:
2685-
raise ValueError("kagglesdk is missing ApiListHackathonWriteUpsRequest; please upgrade kagglesdk.") from exc
2686-
request = ApiListHackathonWriteUpsRequest()
2687-
request.competition_name = competition
2688-
return request
2689-
2690-
def _build_export_hackathon_writeups_csv_request(self, competition: str):
2691-
try:
2692-
from kagglesdk.competitions.types.hackathon_service import ( # type: ignore
2693-
ExportHackathonWriteUpsCsvRequest,
2694-
)
2695-
except ImportError as exc:
2696-
raise ValueError(
2697-
"kagglesdk is missing ExportHackathonWriteUpsCsvRequest; please upgrade kagglesdk."
2698-
) from exc
2699-
request = ExportHackathonWriteUpsCsvRequest()
2700-
request.competition_name = competition
2701-
return request
2702-
2703-
def _build_get_resolved_writeup_links_request(self, write_up_id: int):
2704-
try:
2705-
from kagglesdk.discussions.types.writeups_service import ( # type: ignore
2706-
GetResolvedWriteUpLinksRequest,
2707-
)
2708-
except ImportError as exc:
2709-
raise ValueError("kagglesdk is missing GetResolvedWriteUpLinksRequest; please upgrade kagglesdk.") from exc
2710-
request = GetResolvedWriteUpLinksRequest()
2711-
request.write_up_id = write_up_id
2712-
return request
2713-
27142657
def hackathon_get_overview(self, competition: str):
27152658
"""Get the overview page content for a hackathon competition.
27162659
27172660
Mirrors the ``get_hackathon_overview`` MCP tool.
27182661
"""
27192662
if not competition:
27202663
raise ValueError("No competition specified")
2664+
request = ApiGetHackathonOverviewRequest()
2665+
request.competition_name = competition
27212666
with self.build_kaggle_client() as kaggle:
2722-
request = self._build_hackathon_overview_request(competition)
2723-
client = kaggle.competitions.competition_api_client
2724-
method = self._require_sdk_method(
2725-
client,
2726-
"get_hackathon_overview",
2727-
"CompetitionApiClient.get_hackathon_overview",
2728-
)
2729-
return method(request)
2667+
return kaggle.competitions.competition_api_client.get_hackathon_overview(request)
27302668

27312669
def hackathon_get_overview_cli(self, competition=None, csv_display=False, quiet=False):
27322670
"""CLI wrapper for ``kaggle hackathons get <competition>``."""
@@ -2757,15 +2695,10 @@ def hackathon_list_writeups(self, competition: str):
27572695
"""
27582696
if not competition:
27592697
raise ValueError("No competition specified")
2698+
request = ApiListHackathonWriteUpsRequest()
2699+
request.competition_name = competition
27602700
with self.build_kaggle_client() as kaggle:
2761-
request = self._build_list_hackathon_writeups_request(competition)
2762-
client = kaggle.competitions.competition_api_client
2763-
method = self._require_sdk_method(
2764-
client,
2765-
"list_hackathon_write_ups",
2766-
"CompetitionApiClient.list_hackathon_write_ups",
2767-
)
2768-
return method(request)
2701+
return kaggle.competitions.competition_api_client.list_hackathon_write_ups(request)
27692702

27702703
def hackathon_list_writeups_cli(self, competition=None, csv_display=False, quiet=False):
27712704
"""CLI wrapper for ``kaggle hackathons writeups list <competition>``."""
@@ -2815,15 +2748,16 @@ def hackathon_download_writeups(self, competition: str, path: Optional[str] = No
28152748
"""
28162749
if not competition:
28172750
raise ValueError("No competition specified")
2751+
# Not yet exported by any released kagglesdk; import lazily so the
2752+
# rest of the module still loads. Drop this once kagglesdk ships it.
2753+
from kagglesdk.competitions.types.hackathon_service import ( # type: ignore
2754+
ExportHackathonWriteUpsCsvRequest,
2755+
)
2756+
2757+
request = ExportHackathonWriteUpsCsvRequest()
2758+
request.competition_name = competition
28182759
with self.build_kaggle_client() as kaggle:
2819-
request = self._build_export_hackathon_writeups_csv_request(competition)
2820-
client = kaggle.competitions.hackathon_client
2821-
method = self._require_sdk_method(
2822-
client,
2823-
"export_hackathon_write_ups_csv",
2824-
"HackathonClient.export_hackathon_write_ups_csv",
2825-
)
2826-
response = method(request)
2760+
response = kaggle.competitions.hackathon_client.export_hackathon_write_ups_csv(request)
28272761

28282762
csv_body = (
28292763
getattr(response, "csv", None)
@@ -2869,19 +2803,21 @@ def hackathon_resolve_writeup_links(self, write_up_id: int):
28692803
"""
28702804
if write_up_id is None:
28712805
raise ValueError("No write_up_id specified")
2806+
# Not yet exported by any released kagglesdk; import lazily so the
2807+
# rest of the module still loads. Drop this once kagglesdk ships it.
2808+
from kagglesdk.discussions.types.writeups_service import ( # type: ignore
2809+
GetResolvedWriteUpLinksRequest,
2810+
)
2811+
2812+
request = GetResolvedWriteUpLinksRequest()
2813+
request.write_up_id = int(write_up_id)
28722814
with self.build_kaggle_client() as kaggle:
2873-
request = self._build_get_resolved_writeup_links_request(int(write_up_id))
28742815
client = getattr(kaggle.discussions, "writeups_client", None) or getattr(
28752816
kaggle.discussions, "write_ups_client", None
28762817
)
28772818
if client is None:
28782819
raise ValueError("kagglesdk is missing the WriteUpsClient; please upgrade kagglesdk.")
2879-
method = self._require_sdk_method(
2880-
client,
2881-
"get_resolved_writeup_links",
2882-
"WriteUpsClient.get_resolved_writeup_links",
2883-
)
2884-
return method(request)
2820+
return client.get_resolved_writeup_links(request)
28852821

28862822
def hackathon_resolve_writeup_links_cli(self, writeup_id=None, csv_display=False, quiet=False):
28872823
"""CLI wrapper for ``kaggle hackathons writeups resolve-links <writeup_id>``."""

src/kaggle/test/test_hackathons_cli.py

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
11
"""Tests for ``kaggle hackathons`` CLI commands.
22
3-
The hackathon endpoints (``get_hackathon_overview``,
4-
``list_hackathon_write_ups``, ``export_hackathon_write_ups_csv``,
5-
``get_resolved_writeup_links``) may not yet exist on the installed
6-
``kagglesdk``. The CLI wrappers build their request objects via lazy
7-
imports and look up the SDK methods with ``getattr``, so the tests here
8-
mock ``KaggleApi.build_kaggle_client`` and the lazy ``_build_*`` request
9-
helpers — no SDK methods need to actually exist for the tests to run.
3+
The two CSV-export and resolve-links endpoints are not yet shipped by any
4+
released ``kagglesdk``, so those wrappers still import their request types
5+
lazily. The fixture pins a ``WriteUpsClient`` onto the mocked
6+
``kaggle.discussions`` so the resolve-links code path resolves cleanly.
107
"""
118

129
import argparse
10+
import sys
11+
import types
1312
from unittest.mock import MagicMock, patch
1413

1514
import pytest
1615

1716
from kaggle.api.kaggle_api_extended import KaggleApi
1817
from kaggle import cli as kaggle_cli
1918

19+
20+
def _install_missing_sdk_modules():
21+
"""Inject stub modules for the two request types kagglesdk hasn't shipped yet."""
22+
hackathon_mod = "kagglesdk.competitions.types.hackathon_service"
23+
if not hasattr(sys.modules.get(hackathon_mod), "ExportHackathonWriteUpsCsvRequest"):
24+
mod = sys.modules.get(hackathon_mod) or types.ModuleType(hackathon_mod)
25+
mod.ExportHackathonWriteUpsCsvRequest = type("ExportHackathonWriteUpsCsvRequest", (), {})
26+
sys.modules[hackathon_mod] = mod
27+
writeups_mod = "kagglesdk.discussions.types.writeups_service"
28+
if writeups_mod not in sys.modules:
29+
mod = types.ModuleType(writeups_mod)
30+
mod.GetResolvedWriteUpLinksRequest = type("GetResolvedWriteUpLinksRequest", (), {})
31+
sys.modules[writeups_mod] = mod
32+
33+
34+
_install_missing_sdk_modules()
35+
2036
# ---- Fixtures & helpers ----
2137

2238

@@ -27,28 +43,15 @@ def api():
2743
mock_client = MagicMock()
2844
a.build_kaggle_client = MagicMock()
2945
a.build_kaggle_client.return_value.__enter__.return_value = mock_client
30-
a._mock_client = mock_client
3146
a._mock_competitions = mock_client.competitions.competition_api_client
3247
a._mock_hackathon = mock_client.competitions.hackathon_client
3348
# The current SDK has no WriteUpsClient — pin one onto the mock so the
3449
# production lookup (`getattr(...)`) finds it.
3550
a._mock_writeups = MagicMock()
3651
mock_client.discussions.writeups_client = a._mock_writeups
37-
# Stub the lazy request builders so the tests don't depend on the SDK
38-
# exposing the new request types.
39-
a._build_hackathon_overview_request = MagicMock(side_effect=lambda c: _Req(c))
40-
a._build_list_hackathon_writeups_request = MagicMock(side_effect=lambda c: _Req(c))
41-
a._build_export_hackathon_writeups_csv_request = MagicMock(side_effect=lambda c: _Req(c))
42-
a._build_get_resolved_writeup_links_request = MagicMock(side_effect=lambda wid: _Req(write_up_id=wid))
4352
return a
4453

4554

46-
class _Req:
47-
def __init__(self, competition_name=None, write_up_id=None):
48-
self.competition_name = competition_name
49-
self.write_up_id = write_up_id
50-
51-
5255
def _make_overview_response(pages):
5356
r = MagicMock()
5457
r.pages = pages
@@ -132,13 +135,6 @@ def test_missing_competition(self, api):
132135
with pytest.raises(ValueError, match="No competition specified"):
133136
api.hackathon_get_overview_cli(None)
134137

135-
def test_missing_sdk_method(self, api):
136-
# Strip the method off the client to force the missing-method path.
137-
del api._mock_competitions.get_hackathon_overview
138-
api._mock_competitions.mock_add_spec(["other_method"])
139-
with pytest.raises(ValueError, match="newer kagglesdk"):
140-
api.hackathon_get_overview("titanic")
141-
142138

143139
# ---- hackathons writeups list ----
144140

0 commit comments

Comments
 (0)