Skip to content
Open
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
13 changes: 13 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,19 @@ pub struct SomeRequest { ... }
- `ruby` feature — enables `magnus` dependency (optional); wrapping is done in the Ruby crate rather than via macros on core types
- `rust` feature — `bon` builder pattern for ergonomic Rust usage

### Python `__repr__` and `to_dict`

Every `#[pyclass]` type exposed to Python must expose:
- `__repr__()` — delegates to Rust `Debug` so output is readable in the REPL, logs, and Jupyter (replaces `<builtins.Endpoint object at 0x...>`).
- `to_dict()` — returns a native Python `dict` produced via `pythonize` from the type's `serde::Serialize` impl, suitable for `json.dumps(obj.to_dict())`.

The `python_repr_dict!(TypeName)` macro (defined in `crates/core/src/python_macros.rs`) emits both methods. Each module that defines pyclass types has a `#[cfg(feature = "python")] mod python_repr_impls { ... }` block at the bottom that lists one macro call per type. When adding a new pyclass type, add a `crate::python_repr_dict!(NewType);` line to that block. The workspace `pyo3` dependency enables the `multiple-pymethods` feature so the macro can attach its own `#[pymethods]` impl alongside any existing one without a "conflicting implementations" error.

Sensitive types — those whose `Debug` impl is manually written to redact credentials (currently `SdkFullConfig.api_key`, `EndpointToken.token`, `EndpointJwt.public_key`) — skip the macro and define a hand-rolled `#[pymethods]` block that mirrors the redaction in `to_dict` by overwriting the sensitive key with `"[redacted]"` after pythonize. When adding a new field that holds credential material:
1. Replace the derived `Debug` with a manual impl that prints `[redacted]` for the sensitive field (see `crates/core/src/lib.rs::SdkConfig` for the pattern).
2. Do not add the type to the `python_repr_impls` block. Instead write a hand-rolled `#[pymethods]` impl with `__repr__` (`format!("{self:?}")`) and `to_dict` (pythonize + `dict.set_item("field", "[redacted]")`).
3. Add a unit test confirming `format!("{:?}", instance)` does not contain the raw credential value.

### Error Handling
`SdkError` (`crates/core/src/errors.rs`) uses `thiserror` with five variants:
- `Http` — wraps `reqwest::Error` (further classified via `SdkError::http_kind()` → `HttpKind::{Timeout, Connect, Other}`)
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ inefficient_to_string = "warn"
needless_pass_by_value = "warn"

[workspace.dependencies]
pyo3 = { version = "0.27", features = ["experimental-async"] }
pyo3 = { version = "0.27", features = ["experimental-async", "multiple-pymethods"] }
pyo3-async-runtimes = { version = "0.27", features = ["tokio-runtime"] }
napi = { version = "3", features = ["async", "tokio_rt", "compat-mode"] }
napi-derive = { version = "3", features = ["compat-mode"] }
Expand Down
3 changes: 2 additions & 1 deletion crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ categories = ["api-bindings", "web-programming"]
workspace = true

[features]
python = ["pyo3", "pyo3-async-runtimes", "pyo3-stub-gen"]
python = ["pyo3", "pyo3-async-runtimes", "pyo3-stub-gen", "pythonize"]
node = ["napi", "napi-derive"]
ruby = ["magnus"]
rust = ["bon"]
Expand All @@ -29,6 +29,7 @@ pyo3-async-runtimes = { workspace = true, optional = true, features = ["tokio-ru
napi = { workspace = true, optional = true, features = ["async", "tokio_rt"] }
napi-derive = { workspace = true, optional = true }
pyo3-stub-gen = { workspace = true, optional = true }
pythonize = { version = "0.27", optional = true }
magnus = { workspace = true, optional = true }
bon = { version = "3.9", optional = true }
config = "0.15"
Expand Down
15 changes: 15 additions & 0 deletions crates/core/src/admin/billing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,18 @@ pub struct ListPaymentsData {
#[serde(default)]
pub payments: Vec<Payment>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(InvoiceLine);
crate::python_repr_dict!(Invoice);
crate::python_repr_dict!(ListInvoicesResponse);
crate::python_repr_dict!(ListInvoicesData);
crate::python_repr_dict!(Payment);
crate::python_repr_dict!(ListPaymentsResponse);
crate::python_repr_dict!(ListPaymentsData);
}
19 changes: 19 additions & 0 deletions crates/core/src/admin/bulk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,22 @@ pub struct BulkRemoveTagResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(BulkUpdateEndpointStatusRequest);
crate::python_repr_dict!(BulkOperationResult);
crate::python_repr_dict!(BulkUpdateEndpointStatusData);
crate::python_repr_dict!(BulkUpdateEndpointStatusResponse);
crate::python_repr_dict!(BulkAddTagRequest);
crate::python_repr_dict!(BulkTag);
crate::python_repr_dict!(BulkAddTagData);
crate::python_repr_dict!(BulkAddTagResponse);
crate::python_repr_dict!(BulkRemoveTagRequest);
crate::python_repr_dict!(BulkRemoveTagData);
crate::python_repr_dict!(BulkRemoveTagResponse);
}
11 changes: 11 additions & 0 deletions crates/core/src/admin/chains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,14 @@ pub struct ListChainsResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(ChainNetwork);
crate::python_repr_dict!(Chain);
crate::python_repr_dict!(ListChainsResponse);
}
13 changes: 13 additions & 0 deletions crates/core/src/admin/endpoint_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,16 @@ mod tests {
.contains("expected string or array of strings for tag"));
}
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(GetEndpointMetricsRequest);
crate::python_repr_dict!(GetAccountMetricsRequest);
crate::python_repr_dict!(EndpointMetric);
crate::python_repr_dict!(GetEndpointMetricsResponse);
crate::python_repr_dict!(GetAccountMetricsResponse);
}
20 changes: 20 additions & 0 deletions crates/core/src/admin/endpoint_rate_limits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,23 @@ pub struct GetRateLimitsResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(MethodRateLimiter);
crate::python_repr_dict!(GetMethodRateLimitsData);
crate::python_repr_dict!(GetMethodRateLimitsResponse);
crate::python_repr_dict!(CreateMethodRateLimitRequest);
crate::python_repr_dict!(CreateMethodRateLimitResponse);
crate::python_repr_dict!(UpdateMethodRateLimitRequest);
crate::python_repr_dict!(UpdateMethodRateLimitResponse);
crate::python_repr_dict!(RateLimitSettings);
crate::python_repr_dict!(UpdateRateLimitsRequest);
crate::python_repr_dict!(RateLimitEntry);
crate::python_repr_dict!(GetRateLimitsData);
crate::python_repr_dict!(GetRateLimitsResponse);
}
25 changes: 25 additions & 0 deletions crates/core/src/admin/endpoint_security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,28 @@ pub struct DeleteBoolResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(SecurityOption);
crate::python_repr_dict!(GetSecurityOptionsResponse);
crate::python_repr_dict!(SecurityOptionsUpdate);
crate::python_repr_dict!(UpdateSecurityOptionsRequest);
crate::python_repr_dict!(UpdateSecurityOptionsResponse);
crate::python_repr_dict!(CreateReferrerRequest);
crate::python_repr_dict!(CreateIpRequest);
crate::python_repr_dict!(CreateDomainMaskRequest);
crate::python_repr_dict!(CreateJwtRequest);
crate::python_repr_dict!(CreateRequestFilterRequest);
crate::python_repr_dict!(CreateRequestFilterResponse);
crate::python_repr_dict!(CreateRequestFilterData);
crate::python_repr_dict!(UpdateRequestFilterRequest);
crate::python_repr_dict!(CreateOrUpdateIpCustomHeaderRequest);
crate::python_repr_dict!(IpCustomHeaderData);
crate::python_repr_dict!(CreateOrUpdateIpCustomHeaderResponse);
crate::python_repr_dict!(DeleteBoolResponse);
}
11 changes: 11 additions & 0 deletions crates/core/src/admin/endpoint_urls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,14 @@ pub struct GetEndpointUrlsResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(EndpointUrl);
crate::python_repr_dict!(GetEndpointUrlsData);
crate::python_repr_dict!(GetEndpointUrlsResponse);
}
69 changes: 69 additions & 0 deletions crates/core/src/admin/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,72 @@ pub struct GetEndpointSecurityResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(GetEndpointsRequest);
crate::python_repr_dict!(GetEndpointsResponse);
crate::python_repr_dict!(Pagination);
crate::python_repr_dict!(Endpoint);
crate::python_repr_dict!(EndpointTag);
crate::python_repr_dict!(CreateEndpointRequest);
crate::python_repr_dict!(CreateEndpointResponse);
crate::python_repr_dict!(SingleEndpoint);
crate::python_repr_dict!(EndpointRateLimits);
crate::python_repr_dict!(EndpointSecurity);
crate::python_repr_dict!(EndpointSecurityOptions);
crate::python_repr_dict!(EndpointIpCustomHeaderOption);
crate::python_repr_dict!(EndpointReferrer);
crate::python_repr_dict!(EndpointDomainMask);
crate::python_repr_dict!(EndpointIp);
crate::python_repr_dict!(EndpointRequestFilter);
crate::python_repr_dict!(ShowEndpointResponse);
crate::python_repr_dict!(UpdateEndpointRequest);
crate::python_repr_dict!(UpdateEndpointStatusRequest);
crate::python_repr_dict!(UpdateEndpointStatusResponse);
crate::python_repr_dict!(CreateTagRequest);
crate::python_repr_dict!(GetEndpointSecurityResponse);
}

// EndpointToken and EndpointJwt skip the macro: their serde Serialize emits
// raw credential material (token / public_key), so to_dict needs to redact
// those fields to match the manual Debug impls above.
#[cfg(feature = "python")]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
#[pyo3::pymethods]
impl EndpointToken {
fn __repr__(&self) -> String {
format!("{self:?}")
}

fn to_dict<'py>(&self, py: pyo3::Python<'py>) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
use pyo3::types::{PyAnyMethods, PyDict};
let obj = pythonize::pythonize(py, self)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
let dict = obj.cast::<PyDict>()?;
dict.set_item("token", "[redacted]")?;
Ok(obj)
}
}

#[cfg(feature = "python")]
#[pyo3_stub_gen::derive::gen_stub_pymethods]
#[pyo3::pymethods]
impl EndpointJwt {
fn __repr__(&self) -> String {
format!("{self:?}")
}

fn to_dict<'py>(&self, py: pyo3::Python<'py>) -> pyo3::PyResult<pyo3::Bound<'py, pyo3::PyAny>> {
use pyo3::types::{PyAnyMethods, PyDict};
let obj = pythonize::pythonize(py, self)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?;
let dict = obj.cast::<PyDict>()?;
dict.set_item("public_key", "[redacted]")?;
Ok(obj)
}
}
13 changes: 13 additions & 0 deletions crates/core/src/admin/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,16 @@ pub struct GetLogDetailsResponse {
/// Raw request and response payloads for the log entry.
pub data: Option<LogDetails>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(GetEndpointLogsRequest);
crate::python_repr_dict!(LogDetails);
crate::python_repr_dict!(EndpointLog);
crate::python_repr_dict!(GetEndpointLogsResponse);
crate::python_repr_dict!(GetLogDetailsResponse);
}
15 changes: 15 additions & 0 deletions crates/core/src/admin/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,18 @@ pub struct DeleteAccountTagResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(AccountTag);
crate::python_repr_dict!(ListTagsData);
crate::python_repr_dict!(ListTagsResponse);
crate::python_repr_dict!(RenameTagRequest);
crate::python_repr_dict!(RenameTagResponse);
crate::python_repr_dict!(DeleteAccountTagData);
crate::python_repr_dict!(DeleteAccountTagResponse);
}
29 changes: 29 additions & 0 deletions crates/core/src/admin/teams.rs
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,32 @@ pub struct ResendTeamInviteResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(TeamUser);
crate::python_repr_dict!(TeamSummary);
crate::python_repr_dict!(TeamDetail);
crate::python_repr_dict!(ListTeamsResponse);
crate::python_repr_dict!(CreateTeamRequest);
crate::python_repr_dict!(CreateTeamData);
crate::python_repr_dict!(CreateTeamResponse);
crate::python_repr_dict!(GetTeamResponse);
crate::python_repr_dict!(DeleteTeamData);
crate::python_repr_dict!(DeleteTeamResponse);
crate::python_repr_dict!(TeamEndpoint);
crate::python_repr_dict!(ListTeamEndpointsResponse);
crate::python_repr_dict!(UpdateTeamEndpointsRequest);
crate::python_repr_dict!(UpdateTeamEndpointsData);
crate::python_repr_dict!(UpdateTeamEndpointsResponse);
crate::python_repr_dict!(InviteTeamMemberRequest);
crate::python_repr_dict!(InviteTeamMemberResponse);
crate::python_repr_dict!(RemoveTeamMemberRequest);
crate::python_repr_dict!(TeamMessageData);
crate::python_repr_dict!(RemoveTeamMemberResponse);
crate::python_repr_dict!(ResendTeamInviteResponse);
}
23 changes: 23 additions & 0 deletions crates/core/src/admin/usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,3 +234,26 @@ pub struct GetUsageByTagResponse {
/// Error message when the request did not succeed.
pub error: Option<String>,
}

// ── Python conveniences (__repr__, to_dict) ───────────────────────────────
// Generated by the `python_repr_dict!` macro (see crates/core/src/python_macros.rs).

#[cfg(feature = "python")]
mod python_repr_impls {
use super::*;
crate::python_repr_dict!(GetUsageRequest);
crate::python_repr_dict!(UsageData);
crate::python_repr_dict!(GetUsageResponse);
crate::python_repr_dict!(EndpointUsage);
crate::python_repr_dict!(MethodUsage);
crate::python_repr_dict!(ChainUsage);
crate::python_repr_dict!(UsageByEndpointData);
crate::python_repr_dict!(GetUsageByEndpointResponse);
crate::python_repr_dict!(UsageByMethodData);
crate::python_repr_dict!(GetUsageByMethodResponse);
crate::python_repr_dict!(UsageByChainData);
crate::python_repr_dict!(GetUsageByChainResponse);
crate::python_repr_dict!(TagUsage);
crate::python_repr_dict!(UsageByTagData);
crate::python_repr_dict!(GetUsageByTagResponse);
}
Loading
Loading