Skip to main content

Arcjet Python SDK. Bot detection, rate limiting, email validation, attack protection for Python applications.

Project description

Arcjet Logo

Arcjet - Python SDK

PyPI badge

Arcjet helps developers protect their apps in just a few lines of code. Bot detection. Rate limiting. Email validation. Attack protection. A developer-first approach to security.

This is the monorepo containing various Arcjet open source packages for Python.

Features

Arcjet security features for protecting Python apps:

  • 🤖 Bot protection - manage traffic by automated clients and bots.
  • 🛑 Rate limiting - limit the number of requests a client can make.
  • 🛡️ Shield WAF - protect your application against common attacks.
  • 📧 Email validation - prevent users from signing up with fake email addresses.
  • 📝 Signup form protection - combines rate limiting, bot protection, and email validation to protect your signup forms.

[!NOTE] The Arcjet Python SDK currently doesn't support sensitive information detection or request filters.

Get help

Join our Discord server or reach out for support.

Usage

Read the docs at docs.arcjet.com

Quick start example

This example implements Arcjet bot protection, rate limiting, email validation, and Shield WAF in a FastAPI application. Requests from bots not in the allow list will be blocked with a 403 Forbidden response.

The example email is invalid so an error will be returned - change the email to see different results.

FastAPI

An asynchronous example using FastAPI with the Arcjet async client.

# main.py
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

from arcjet import (
    arcjet,
    shield,
    detect_bot,
    token_bucket,
    is_spoofed_bot,
    validate_email,
    Mode,
    BotCategory,
    EmailType,
)

app = FastAPI()

aj = arcjet(
    key=os.environ["ARCJET_KEY"],
    rules=[
        shield(mode=Mode.LIVE),
        detect_bot(
            mode=Mode.LIVE, allow=[BotCategory.SEARCH_ENGINE, "OPENAI_CRAWLER_SEARCH"]
        ),
        token_bucket(mode=Mode.LIVE, refill_rate=5, interval=10, capacity=10),
        validate_email(
            mode=Mode.LIVE,
            deny=[EmailType.DISPOSABLE, EmailType.INVALID, EmailType.NO_MX_RECORDS],
        ),
    ],
)


@app.get("/")
async def hello(request: Request):
    # requested is optional; only relevant for token bucket rules (default: 1)
    # email is only required if validate_email() is configured
    decision = await aj.protect(request, requested=1, email="example@arcjet.ai")

    if decision.is_denied():
        status = 429 if decision.reason.is_rate_limit() else 403
        return JSONResponse(
            {"error": "Denied", "reason": decision.reason.to_dict()},
            status_code=status,
        )

    if decision.ip.is_hosting():
        return JSONResponse({"error": "Hosting IP blocked"}, status_code=403)

    if any(is_spoofed_bot(r) for r in decision.results):
        return JSONResponse({"error": "Spoofed bot"}, status_code=403)

    return {"message": "Hello world", "decision": decision.to_dict()}

Flask

A synchronous example using Flask with the sync client.

# main.py
from flask import Flask, request, jsonify
import os

from arcjet import (
  arcjet_sync,
  shield,
  detect_bot,
  token_bucket,
  validate_email,
  is_spoofed_bot,
  Mode,
  BotCategory,
  EmailType,
)

app = Flask(__name__)

aj = arcjet_sync(
    key=os.environ["ARCJET_KEY"],
    rules=[
        shield(mode=Mode.LIVE),
        detect_bot(
            mode=Mode.LIVE, allow=[BotCategory.SEARCH_ENGINE, "OPENAI_CRAWLER_SEARCH"]
        ),
        token_bucket(mode=Mode.LIVE, refill_rate=5, interval=10, capacity=10),
        validate_email(
            mode=Mode.LIVE,
            deny=[EmailType.DISPOSABLE, EmailType.INVALID, EmailType.NO_MX_RECORDS],
        ),
    ],
)

@app.route("/")
def hello():
  # requested is optional; only relevant for token bucket rules (default: 1)
  # email is only required if validate_email() is configured
  decision = aj.protect(request, requested=1, email="example@arcjet.com")

  if decision.is_denied():
    status = 429 if decision.reason.is_rate_limit() else 403
    return jsonify(error="Denied", reason=decision.reason.to_dict()), status

  if decision.ip.is_hosting():
    return jsonify(error="Hosting IP blocked"), 403

  if any(is_spoofed_bot(r) for r in decision.results):
    return jsonify(error="Spoofed bot"), 403

  return jsonify(message="Hello world", decision=decision.to_dict())

if __name__ == "__main__":
  app.run(debug=True)

Custom characteristics

Each client is tracked by IP address by default. To customize client fingerprinting you can configure custom characteristics:

# main.py
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

from arcjet import (
    arcjet,
    shield,
    token_bucket,
    Mode,
    BotCategory,
)

app = FastAPI()

aj = arcjet(
    key=os.environ["ARCJET_KEY"],
    rules=[
        shield(mode=Mode.LIVE),
        token_bucket(
            mode=Mode.LIVE,
            refill_rate=5,
            interval=10,
            capacity=10,
            # track clients by userId characteristic
            characteristics=["userId"],
        ),
    ],
)


@app.get("/")
async def hello(request: Request):
    decision = await aj.protect(
      # pass userId characteristic for this request
        request, requested=1, characteristics={"userId": "user-1234"}
    )

    if decision.is_denied():
        status = 429 if decision.reason.is_rate_limit() else 403
        return JSONResponse(
            {"error": "Denied", "reason": decision.reason.to_dict()},
            status_code=status,
        )

    return {"message": "Hello world", "decision": decision.to_dict()}

Trusted proxies

When your app runs behind one or more reverse proxies or a load balancer, pass their IPs or CIDR ranges so Arcjet can correctly resolve the real client IP from X-Forwarded-For and similar headers.

from arcjet import arcjet

aj = arcjet(
    key=os.environ["ARCJET_KEY"],
    rules=[...],
    proxies=["10.0.0.0/8", "192.168.0.1"],
)

Only globally routable IPs are accepted for client identification; private, loopback, link-local, and addresses matching proxies are ignored during IP extraction.

Logging

Enable debug logging to troubleshoot issues with Arcjet integration.

import logging
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)s %(name)s %(message)s"
)

Arcjet logging can be controlled directly by setting the ARCJET_LOG_LEVEL environment variable e.g. export ARCJET_LOG_LEVEL=debug.

Tests

Run the unit tests locally with uv and pytest:

uv run pytest -q
  • Tests stub the Decide API protobufs and clients, so no network access is required.
  • Set ARCJET_LOG_LEVEL=debug to see detailed debug logs during development.

Support

This repository follows the Arcjet Support Policy.

Security

This repository follows the Arcjet Security Policy.

Compatibility

Packages maintained in this repository are compatible with Python 3.10 and above.

License

Licensed under the Apache License, Version 2.0.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

arcjet-0.2.0.tar.gz (21.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

arcjet-0.2.0-py3-none-any.whl (24.7 kB view details)

Uploaded Python 3

File details

Details for the file arcjet-0.2.0.tar.gz.

File metadata

  • Download URL: arcjet-0.2.0.tar.gz
  • Upload date:
  • Size: 21.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for arcjet-0.2.0.tar.gz
Algorithm Hash digest
SHA256 bf45bedef02f4ec94e528d9ecc4dbd2eb5f4d34eaf1f26ed6d293f2779fd09d8
MD5 5deefea42fcf79e563308abaf78423e5
BLAKE2b-256 5dfc7dbf74c10ac3e1d3e1ac4e562c9a99919374ae91ba018e4d8aa1f85189ef

See more details on using hashes here.

File details

Details for the file arcjet-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: arcjet-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 24.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for arcjet-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2189b69e04f77436d6bf41a731cce78bbe373372dfb89e70762c9ed25851838c
MD5 9c8daf1229398cb58b34856cba947f4b
BLAKE2b-256 5ad138cfdae47de974ed2c2b3e0b203ae13e00c3490befef2aa4942df0ca57b8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page