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
6 changes: 6 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@



# -- Substack --
# Optional: publication URL used by Substack live integration tests.
# SUBSTACK_TEST_PUBLICATION_URL=
# Optional: post slug used by Substack get_post live integration tests.
# SUBSTACK_TEST_POST_SLUG=

# -- Supadata --
# SUPADATA_API_KEY=

Expand Down
14 changes: 7 additions & 7 deletions substack/config.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "Substack",
"display_name": "Substack",
"version": "1.0.0",
"version": "2.0.0",
"description": "Search Substack publications, read posts and comments. No authentication required.",
"entry_point": "substack.py",
"actions": {
Expand Down Expand Up @@ -246,12 +246,12 @@
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"body": {"type": "string"},
"date": {"type": "string"},
"author_name": {"type": "string"},
"author_id": {"type": "integer"},
"like_count": {"type": "integer"},
"id": {"type": ["integer", "null"]},
"body": {"type": ["string", "null"]},
"date": {"type": ["string", "null"]},
"author_name": {"type": ["string", "null"]},
"author_id": {"type": ["integer", "null"]},
"like_count": {"type": ["integer", "null"]},
"children": {"type": "array"}
}
}
Expand Down
2 changes: 1 addition & 1 deletion substack/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
autohive-integrations-sdk~=1.0.2
autohive-integrations-sdk~=2.0.0
aiohttp>=3.9.0
19 changes: 11 additions & 8 deletions substack/substack.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
if inputs.get("search"):
params["search"] = inputs["search"]

posts_raw = await context.fetch(
response = await context.fetch(
f"{base_url}/api/v1/archive",
method="GET",
params=params,
headers=headers,
)
posts = [_format_post(p) for p in (posts_raw or [])]
posts = [_format_post(p) for p in (response.data or [])]
return ActionResult(data={"posts": posts, "count": len(posts)}, cost_usd=0.0)


Expand All @@ -99,11 +99,12 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
slug = inputs["slug"]
headers = _build_headers()

post = await context.fetch(
response = await context.fetch(
f"{base_url}/api/v1/posts/{slug}",
method="GET",
headers=headers,
)
post = response.data
result = _drop_none(
{
"id": post.get("id"),
Expand Down Expand Up @@ -144,7 +145,8 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
params=params,
headers=headers,
)
pubs_raw = response.get("publications", []) if isinstance(response, dict) else response
body = response.data
pubs_raw = body.get("publications", []) if isinstance(body, dict) else body
pubs = [
_drop_none(
{
Expand All @@ -159,7 +161,7 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
)
for p in pubs_raw
]
more = response.get("more", False) if isinstance(response, dict) else False
more = body.get("more", False) if isinstance(body, dict) else False
return ActionResult(data={"publications": pubs, "more": more}, cost_usd=0.0)


Expand All @@ -178,13 +180,13 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
"limit": min(inputs.get("limit", 10), 50),
}

posts_raw = await context.fetch(
response = await context.fetch(
f"{base_url}/api/v1/archive",
method="GET",
params=params,
headers=headers,
)
posts = [_format_post(p) for p in (posts_raw or [])]
posts = [_format_post(p) for p in (response.data or [])]
return ActionResult(data={"posts": posts, "count": len(posts)}, cost_usd=0.0)


Expand All @@ -207,5 +209,6 @@ async def execute(self, inputs: Dict[str, Any], context: ExecutionContext) -> Ac
params=params,
headers=headers,
)
comments = response.get("comments", []) if isinstance(response, dict) else []
body = response.data
comments = body.get("comments", []) if isinstance(body, dict) else []
return ActionResult(data={"comments": comments, "count": len(comments)}, cost_usd=0.0)
11 changes: 11 additions & 0 deletions substack/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Test configuration for the Substack integration.

Shared fixtures (``mock_context``, etc.) come from the repository-root
``conftest.py``. Tests import the integration via the package path
(``from substack.substack import ...``), which resolves from the repo root
that pytest puts on ``sys.path``.

Do NOT insert the integration directory onto ``sys.path`` here: that makes
``substack.py`` importable as a top-level module and shadows the ``substack``
package, breaking the package-style imports during collection.
"""
8 changes: 0 additions & 8 deletions substack/tests/context.py

This file was deleted.

Loading
Loading