> ## Documentation Index
> Fetch the complete documentation index at: https://repost.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Python Quickstart

> Generate a typed Python client from your schema and publish your first event with stable idempotency and delivery outcomes.

export const RepostHighlight = () => {
  useEffect(() => {
    const colors = {
      comment: ['#116329', '#6A9955'],
      constant: ['#0550AE', '#9CDCFE'],
      keyword: ['#CF222E', '#569CD6'],
      name: ['#953800', '#4EC9B0'],
      string: ['#0A3069', '#CE9178'],
      type: ['#0550AE', '#4EC9B0'],
      variable: ['#1F2328', '#9CDCFE']
    };
    const tokenPattern = /"[^"\n]*"|@[A-Za-z_]\w*|\b(?:String|Int|Float|Boolean|DateTime|Json)\b|\b(?:true|false)\b|\b\d+(?:\.\d+)?\b|\b(?:now|uuid|cuid)(?=\s*\()|\b[A-Z]\w*\b|\b[A-Za-z_]\w*\b/g;
    const append = (line, text, color) => {
      if (!text) return;
      if (!color) {
        line.append(text);
        return;
      }
      const span = document.createElement('span');
      span.textContent = text;
      span.style.color = color[0];
      span.style.setProperty('--shiki-dark', color[1]);
      line.append(span);
    };
    const commentStart = text => {
      let quoted = false;
      for (let index = 0; index < text.length - 1; index += 1) {
        if (text[index] === '"' && text[index - 1] !== '\\') quoted = !quoted;
        if (!quoted && text[index] === '/' && text[index + 1] === '/') return index;
      }
      return -1;
    };
    const highlightCode = code => {
      if (code.dataset.repostHighlighted !== undefined || code.querySelector('span[style*="--shiki-dark"]')) {
        return;
      }
      code.dataset.repostHighlighted = '';
      const source = code.textContent ?? '';
      const fragment = document.createDocumentFragment();
      for (const text of source.split('\n')) {
        const line = document.createElement('span');
        line.className = 'line';
        const declaration = text.match(/^(\s*)(generator|model|enum|type|event)(\s+)([A-Za-z_]\w*)(.*)$/);
        if (declaration) {
          append(line, declaration[1]);
          append(line, declaration[2], colors.keyword);
          append(line, declaration[3]);
          append(line, declaration[4], colors.name);
          append(line, declaration[5]);
        } else {
          const start = commentStart(text);
          const codeText = start === -1 ? text : text.slice(0, start);
          const leading = codeText.match(/^(\s*)([A-Za-z_]\w*)(?=\s+(?:@|[A-Z]))/);
          const bare = codeText.match(/^(\s*)([A-Za-z_]\w*)(\s*)$/);
          let offset = 0;
          for (const match of codeText.matchAll(tokenPattern)) {
            append(line, codeText.slice(offset, match.index));
            const token = match[0];
            let color = colors.constant;
            if (token.startsWith('"')) color = colors.string; else if (token.startsWith('@') || (/^(now|uuid|cuid)$/).test(token)) color = colors.name; else if ((/^(String|Int|Float|Boolean|DateTime|Json)$/).test(token)) color = colors.type; else if (leading && match.index === leading[1].length) color = colors.variable; else if (bare && match.index === bare[1].length) color = colors.constant; else if ((/^[A-Z]/).test(token)) color = colors.name; else if ((/^[A-Za-z_]/).test(token)) color = colors.variable;
            append(line, token, color);
            offset = match.index + token.length;
          }
          append(line, codeText.slice(offset));
          if (start !== -1) append(line, text.slice(start), colors.comment);
        }
        fragment.append(line, '\n');
      }
      code.replaceChildren(fragment);
    };
    const highlightRepostBlocks = () => {
      for (const block of document.querySelectorAll('.code-block')) {
        const code = block.querySelector('pre code');
        const filename = block.querySelector('[data-component-part="code-block-header-filename"] [title$=".repost"]');
        const looksLikeRepost = (/^\s*(generator|model|enum|type|event)\s+[A-Za-z_]\w*\s*\{/m).test(code?.textContent ?? '');
        if (code && (filename || looksLikeRepost)) highlightCode(code);
      }
    };
    let frame;
    const schedule = () => {
      if (frame !== undefined) return;
      frame = requestAnimationFrame(() => {
        frame = undefined;
        highlightRepostBlocks();
      });
    };
    const observer = new MutationObserver(schedule);
    observer.observe(document.documentElement, {
      childList: true,
      subtree: true
    });
    schedule();
    return () => {
      observer.disconnect();
      if (frame !== undefined) cancelAnimationFrame(frame);
    };
  }, []);
  return null;
};

<RepostHighlight />

The Repost CLI turns your `.repost` schema into a typed Python package: keyword-only dataclasses, string enums, a `webhooks.<type>.<member>()` method tree, and descriptors used by the runtime. The `repost-client` runtime handles validation, serialization, HTTP, retries, idempotency, cancellation, and delivery outcomes. Python 3.10 or later is required.

The client is for trusted server-side code. It holds a publish credential, so do not ship it in desktop, mobile, browser, or other untrusted applications.

<Steps>
  <Step title="Install the CLI and runtime">
    Install the CLI and runtime:

    <CodeGroup>
      ```bash CLI install script theme={"languages":{"custom":["/languages/repost.json"]}}
      curl -fsSL https://repost.sh/install | sh
      ```

      ```bash CLI from npm theme={"languages":{"custom":["/languages/repost.json"]}}
      npm install -g @repost/cli
      ```

      ```bash Runtime from PyPI theme={"languages":{"custom":["/languages/repost.json"]}}
      python -m pip install repost-client
      ```
    </CodeGroup>
  </Step>

  <Step title="Define the schema">
    `repost schema init --language python --output ./repost_sdk` creates a `repost/` workspace, a `.env` file for your key, and this starter schema:

    ```repost repost/schema.repost theme={"languages":{"custom":["/languages/repost.json"]}}
    generator sdk {
      language = "python"
      output   = "../repost_sdk"
    }

    model User {
      id    String
      email String
    }

    type User {
      created
    }

    event UserCreated {
      type      @type(User.created)
      data      User
      timestamp DateTime
    }
    ```

    Python requires an explicit `output`. The path is relative to `repost/schema.repost`, and its final directory name becomes the importable package name—`repost_sdk` here.
  </Step>

  <Step title="Record the schema and generate">
    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost schema migrate dev --name init
    ```

    This records the first migration and generates the Python package. Commit both directories. [Code generation](/docs/send/python/generation) explains every generated file and the CI drift check.
  </Step>

  <Step title="Connect an environment">
    Create an environment in the [dashboard](https://app.repost.sh), copy its publish API key into `.env`, then sign in and deploy the migration:

    ```bash theme={"languages":{"custom":["/languages/repost.json"]}}
    repost auth login
    repost schema migrate deploy
    ```

    Load `.env` through your framework or process manager. The runtime reads `REPOST_SEND_API_KEY`, then `REPOST_TOKEN`.
  </Step>
</Steps>

## Your first send

```python theme={"languages":{"custom":["/languages/repost.json"]}}
from repost_sdk import RepostClient, User

with RepostClient() as repost:
    result = repost.webhooks.user.created(
        customer_id="customer_123",
        data=User(id="user_123", email="ada@example.com"),
        idempotency_key="user_123:created",
    )
    print(result.id)  # msg_...
```

`customer_id` identifies the customer receiving the event. Generated methods and models catch unknown members, missing fields, and incompatible values in your editor and type checker. The runtime validates and serializes the model again before opening a connection.

The idempotency key is optional. When you omit it, the runtime generates one key and reuses it across that operation's attempts. Pass a business-stable key when your queue or process may repeat the logical send. [Reliability](/docs/send/python/reliability) explains how to handle ambiguous outcomes.

Create one client for a process or application container and close it during shutdown. A context manager is convenient for scripts and jobs; long-running applications should use their framework lifecycle. A complete generated example lives under `examples/python` in the repository.

## Continue

<Columns cols={3} className="gap-y-4">
  <Card title="Code generation" icon="cog" href="/docs/send/python/generation" cta="Generation" arrow="true">
    Generated package contents, regeneration, output ownership, and version checks.
  </Card>

  <Card title="Configuration" icon="sliders-horizontal" href="/docs/send/python/configuration" cta="Configure" arrow="true">
    Credentials, deadlines, capacity, proxy, TLS, DNS, and HTTP/2.
  </Card>

  <Card title="Reliability" icon="shield-check" href="/docs/send/python/reliability" cta="Outcomes" arrow="true">
    Idempotency, delivery states, cancellation, retries, and backpressure.
  </Card>
</Columns>
