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
5 changes: 2 additions & 3 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<!-- Make sure all items below are checked before submitting the Pull Request. -->

- [ ] I have installed `prek` on this project (for instance with the `make install-dev` command)
**before** creating any commit, or I have run successfully the `make format-lint` command on my
changes.
- [ ] I have run successfully the `make test` command on my changes.
**before** creating any commit, or I have run successfully the `make lint` command on my changes.
- [ ] I have run successfully the `make typecheck test` command on my changes.
- [ ] I have updated the `README.md` if my changes affected it.
21 changes: 20 additions & 1 deletion .github/workflows/test.yml → .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
name: Test
name: Pull Request

on:
pull_request:
branches: ["main"]
types: [opened, synchronize, reopened, ready_for_review]

jobs:
lint:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5

- name: Install uv
uses: astral-sh/setup-uv@v6

- name: Install dependencies
run: make install

- name: Lint
run: make lint

test:
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false
runs-on: ubuntu-latest
Expand All @@ -24,5 +40,8 @@ jobs:
- name: Install dependencies
run: make install

- name: Type check
run: make typecheck

- name: Test
run: make test
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ wheels/
# MacOS stuff
.DS_Store

# Coding agents stuff
.rtk/

playground.py
23 changes: 16 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,28 @@ This repository contains the public Python SDK for the Linkup API.

## Goal

Keep the SDK aligned with the current public, stable Linkup API while preserving a Pythonic public interface.
Keep the SDK aligned with the current public, stable Linkup API while preserving a Pythonic public
interface.

## Working Rules

- Read this file before making changes.
- Prefer minimal diffs focused on the public API change being synchronized.
- Do not expose internal, beta, deprecated, or undocumented API behavior unless explicitly requested.
- Do not expose internal, beta, deprecated, or undocumented API behavior unless explicitly
requested.
- Preserve the repo's public Python conventions:
- use snake_case in the SDK public surface;
- convert to API wire-format only at the request boundary.
- Keep sync and async client methods aligned when a capability exists in both forms.
- Avoid unnecessary breaking changes. If a change would be breaking or ambiguous, stop and explain instead of guessing.
- If code generation exists for a given area, use the generation command instead of manually editing generated output.
- Avoid unnecessary breaking changes. If a change would be breaking or ambiguous, stop and explain
instead of guessing.
- If code generation exists for a given area, use the generation command instead of manually editing
generated output.

## When Updating the SDK

When adding or changing a public API capability, update the relevant pieces together:

- client method signatures,
- request/response typing and models,
- sync and async behavior when applicable,
Expand All @@ -30,12 +35,15 @@ When adding or changing a public API capability, update the relevant pieces toge
## Validation

Before opening a PR, run the narrowest relevant checks:
- `make format-lint`

- `make lint`
- `make typecheck`
- `make test`

## Non-Goals

- Do not change package version, release config, or publish settings unless the task explicitly asks for it.
- Do not change package version, release config, or publish settings unless the task explicitly asks
for it.
- Do not refactor unrelated code while performing API synchronization.

## Sync Decisions
Expand All @@ -45,4 +53,5 @@ Add durable exceptions here when a proposed sync should not be repeated.
- Do not expose API capabilities that are not clearly public and stable.
- Do not implement `/credits/balance` in this SDK unless explicitly requested.
- Do not implement `/responses` in this SDK unless explicitly requested.
- If a capability was intentionally rejected for product/design reasons, do not propose it again until this file is updated.
- If a capability was intentionally rejected for product/design reasons, do not propose it again
until this file is updated.
20 changes: 6 additions & 14 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,15 @@ install-dev:
@$(MAKE) install
uv run prek install

format-lint:
lint:
SKIP=no-commit-to-branch uv run prek run --all-files
format-lint-unsafe:
uv run --with ruff ruff check --fix --unsafe-fixes .
@echo
@$(MAKE) format-lint

test-mypy:
@# Avoid running mypy on the whole directory ("./") to avoid potential conflicts with files with the same name (e.g. between different types of tests)
uv run mypy ./src/
uv run mypy ./tests/unit/
test-pytest:
uv run pytest --cov=src/linkup/ ./tests/unit/
typecheck:
@# Ignore pyright-python warnings (only warn when a new pyright version is available)
PYRIGHT_PYTHON_IGNORE_WARNINGS=1 uv run pyright

test:
@$(MAKE) test-mypy
@echo
@$(MAKE) test-pytest
uv run pytest --cov=src/linkup/ ./tests/unit/

update-dependencies:
uv lock --upgrade
Expand Down
33 changes: 29 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ Tracker = "https://github.com/LinkupPlatform/linkup-python-sdk/issues"

[dependency-groups]
dev = [
"mypy>=1.16.1",
"prek>=0.3.5",
"pyright>=1.1.409",
"pytest-asyncio>=1.0.0",
"pytest-cov>=6.2.1",
"pytest-mock>=3.14.1",
Expand All @@ -40,9 +40,14 @@ dev = [
"x402[httpx,evm]>=2.0.0",
]

[tool.mypy]
strict = true
warn_unreachable = true
[tool.pyright]
include = ["./src/", "./tests/"]
reportMissingParameterType = false # Redundant with Ruff ANN001
reportPrivateUsage = false # Redundant with Ruff SLF001
reportUnnecessaryIsInstance = false # Incompatible with the `if: … else: typing.assert_never(…)` pattern
reportUnusedImport = false # Redundant with Ruff F401
reportUnusedVariable = false # Redundant with Ruff F841
typeCheckingMode = "strict"

[tool.pytest.ini_options]
asyncio_default_fixture_loop_scope = "function"
Expand All @@ -55,10 +60,15 @@ skip_covered = true
[tool.ruff]
line-length = 100
target-version = "py310"
unsafe-fixes = true

[tool.ruff.lint]
explicit-preview-rules = true
extend-ignore = ["D107"]
preview = true
pydocstyle = { convention = "google" }

# See rules documentation at: https://docs.astral.sh/ruff/rules/
select = [
"A", # flake8-builtins: avoid shadowing built-in names
"ANN", # flake8-annotations: check for missing type annotations
Expand All @@ -74,6 +84,10 @@ select = [
"ISC", # flake8-implicit-str-concat: check for invalid implicit or explicit string concatenation
"N", # pep8-naming: check for naming convention violations
"PERF", # perflint: check for performance anti-patterns
# pylint (PL): check for errors, enforce coding standards, looks for code smells, and make refactoring suggestions
"PLC", # pylint conventions
"PLE", # pylint errors
"PLW", # pylint warnings
"PT", # flake8-pytest-style: check common style issues and inconsistencies in pytest-based tests
"PTH", # flake8-use-pathlib: enforce usage of pathlib for path manipulations instead of os.path
"Q", # flake8-quotes: enforce consistent string quote usage
Expand All @@ -93,6 +107,17 @@ select = [
"src/linkup/__init__.py" = ["D104"]
"tests/**/*test.py" = ["D", "S101"]

ignore = [
"PTH123", # open() should be replaced by Path.open()
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
"TD001", # Invalid TODO tag
"TD002", # Missing author in TODO
"TD003", # Missing issue link in TODO
]

[tool.ruff.lint.flake8-type-checking]
runtime-evaluated-base-classes = ["pydantic.BaseModel"]

[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]
Expand Down
6 changes: 3 additions & 3 deletions src/linkup/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,10 +532,10 @@ def _raise_linkup_error(self, response: httpx.Response) -> None:
details = error.get("details", [])

if details and isinstance(details, list):
for detail in details:
for detail in details: # pyright: ignore[reportUnknownVariableType]
if isinstance(detail, dict):
field = detail.get("field", "")
field_message = detail.get("message", "")
field = detail.get("field", "") # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
field_message = detail.get("message", "") # pyright: ignore[reportUnknownVariableType,reportUnknownMemberType]
error_msg += f" {field}: {field_message}"

if response.status_code == 402:
Expand Down
12 changes: 5 additions & 7 deletions src/linkup/_types.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""Input and output types for Linkup functions."""

# ruff: noqa: FA100 (pydantic models don't play well with future annotations)

from typing import Any, Literal, Optional, Union
from typing import Any, Literal

from pydantic import BaseModel, ConfigDict, Field

Expand Down Expand Up @@ -46,7 +44,7 @@ class LinkupSearchResults(BaseModel):
results: The results of the Linkup search.
"""

results: list[Union[LinkupSearchTextResult, LinkupSearchImageResult]]
results: list[LinkupSearchTextResult | LinkupSearchImageResult]


class LinkupSource(BaseModel):
Expand Down Expand Up @@ -86,7 +84,7 @@ class LinkupSearchStructuredResponse(BaseModel):
"""

data: Any
sources: list[Union[LinkupSearchTextResult, LinkupSearchImageResult]]
sources: list[LinkupSearchTextResult | LinkupSearchImageResult]


class LinkupFetchImageExtraction(BaseModel):
Expand All @@ -113,5 +111,5 @@ class LinkupFetchResponse(BaseModel):
model_config = ConfigDict(populate_by_name=True)

markdown: str
raw_html: Optional[str] = Field(default=None, validation_alias="rawHtml")
images: Optional[list[LinkupFetchImageExtraction]] = Field(default=None)
raw_html: str | None = Field(default=None, validation_alias="rawHtml")
images: list[LinkupFetchImageExtraction] | None = Field(default=None)
12 changes: 8 additions & 4 deletions src/linkup/x402/_signer.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ async def async_create_payment_headers(
class _DefaultX402Signer:
def __init__(self, account: LocalAccount) -> None:
try:
from x402 import x402Client, x402ClientSync
from x402.http import x402HTTPClient, x402HTTPClientSync
from x402.mechanisms.evm import EthAccountSigner
from x402.mechanisms.evm.exact.register import register_exact_evm_client
from x402 import x402Client, x402ClientSync # noqa: PLC0415
from x402.http import x402HTTPClient, x402HTTPClientSync # noqa: PLC0415
from x402.mechanisms.evm import ( # noqa: PLC0415 # pyright: ignore[reportMissingTypeStubs]
EthAccountSigner,
)
from x402.mechanisms.evm.exact.register import ( # noqa: PLC0415 # pyright: ignore[reportMissingTypeStubs]
register_exact_evm_client, # pyright: ignore[reportUnknownVariableType]
)
except ImportError as e:
raise ImportError(
"The x402 optional dependencies are required to use x402 payment. "
Expand Down
Loading
Loading