Skip to main content

stapel_core

CI codecov PyPI

Shared Python library for Stapel services. Provides JWT authentication, captcha verification, event bus, notifications, and Django utilities used across all backend services.

Part of the Stapel framework.

Quick start for a new Django service

pip install -e ../iron-common-python

Add to INSTALLED_APPS:

INSTALLED_APPS = [
    ...
    'stapel_core.django',
    'stapel_core.django.users',   # if using the shared User model
]

Modules

stapel_core.captcha — Pluggable captcha verification

Backend-agnostic captcha interface. Supports Cloudflare Turnstile, Google reCAPTCHA v2, hCaptcha, and custom backends.

Settings (per service, in settings/base.py):

STAPEL_CAPTCHA = {
    'BACKEND': env.str('CAPTCHA_BACKEND', 'turnstile'),
    'SECRET': env.str('CAPTCHA_SECRET', None),  # absent → disabled
}

Auto-disable: if the secret is None or empty, build_verifier returns NoopVerifier regardless of backend. No separate toggle needed.

DRF integration (add mixin to any serializer):

from stapel_core.django.captcha import CaptchaMixin

class MySerializer(CaptchaMixin, serializers.Serializer):
    captcha_token = serializers.CharField(required=False, allow_blank=True)

    def validate(self, attrs):
        self._require_captcha_if_configured(attrs)
        return attrs

Custom backend — subclass CaptchaVerifier and point to it via a dotted import path:

from stapel_core.captcha import CaptchaVerifier

class MyCaptchaVerifier(CaptchaVerifier):
    def verify(self, token: str, ip: str | None = None, *, level: str | None = None) -> bool:
        return my_service.check(token, self.secret)
# settings.py
STAPEL_CAPTCHA = {'BACKEND': 'myapp.captcha.MyCaptchaVerifier', 'SECRET': 'my-secret'}

Tiered challenge policy — instead of a binary on/off, protect a view with a strictness level derived from the client's network class (via stapel_core.netintel):

from stapel_core.django.captcha import captcha_protected

class RegisterView(APIView):
    @captcha_protected(action="register")
    def post(self, request): ...

Levels: none < invisible < interactive < interactive+ratelimit < block. The default matrix (overridable via STAPEL_CAPTCHA["CHALLENGE_MATRIX"], merged over the defaults) maps residential/unknown → invisible, datacenter/vpn → interactive, tor → interactive+ratelimit. Per-action overrides: STAPEL_CAPTCHA["ACTION_OVERRIDES"] = {"register": "+1"} (bump one level) or {"payout": {"vpn": "block"}}. The whole policy is swappable via STAPEL_CAPTCHA["CHALLENGE_POLICY"] (dotted path to a ChallengePolicy). block returns 403 error.403.network_blocked; rate limiting is not done here — middleware reads request.stapel_challenge_level. With no netintel provider configured every request classifies as unknown → behavior is identical to the classic binary captcha.


stapel_core.netintel — IP intelligence (network class + geo)

classify_ip(ip) -> IpProfile{kind, asn, asn_org, country, confidence} and country_of(ip). Kind vocabulary: residential | datacenter | vpn | tor | unknown. Results are cached in the Django cache; provider errors fail open to unknown and never raise.

STAPEL_NETINTEL = {
    # dotted path / class / instance of a NetIntelProvider (replace seam)
    "PROVIDER": "stapel_core.netintel.providers.MaxMindProvider",
    "MAXMIND_ASN_DB": "/var/geoip/GeoLite2-ASN.mmdb",
    "MAXMIND_COUNTRY_DB": "/var/geoip/GeoLite2-Country.mmdb",
    "MAXMIND_ANONYMOUS_DB": "/var/geoip/GeoIP2-Anonymous-IP.mmdb",
}

Built-in providers: NullProvider (default — always unknown), MaxMindProvider (offline mmdb, pip install stapel-core[netintel-maxmind]), HttpJsonProvider (ipinfo/IPQS-style HTTP APIs via HTTP_URL_TEMPLATE / HTTP_API_KEY / HTTP_RESPONSE_MAPPER). client_ip(request) honors TRUSTED_PROXY_HEADER (default: REMOTE_ADDR only — proxy headers are spoofable unless your edge overwrites them).


stapel_core.django.jwt — JWT authentication

Unified JWT provider (singleton). Supports HS256 and RS256.

from stapel_core.django.jwt.provider import jwt_provider

access, refresh = jwt_provider.create_tokens(user)
payload = jwt_provider.validate_token(access_token)

Settings:

JWT_ALGORITHM    = 'HS256'           # or 'RS256'
JWT_SECRET_KEY   = 'your-secret'     # HS256
JWT_PRIVATE_KEY  = '...'             # RS256
JWT_PUBLIC_KEY   = '...'             # RS256
JWT_ISSUER       = 'https://yourapp.com'
JWT_AUDIENCE     = None
JWT_ACCESS_TOKEN_LIFETIME  = 900     # seconds
JWT_REFRESH_TOKEN_LIFETIME = 604800  # seconds

stapel_core.django.jwt.authentication — JWT cookie auth

JWTCookieAuthentication reads JWT from access_token cookie or Authorization: Bearer <token> header.

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'stapel_core.django.jwt.authentication.JWTCookieAuthentication',
    ],
}

stapel_core.django.api — DRF utilities

Symbol Purpose
StapelDataclassSerializer Serializer that maps @dataclass fields
StapelResponse(serializer) Wraps .data automatically
StapelErrorResponse(status, ERR_KEY) Structured error response
StapelValidationError(ERR_KEY) Raises DRF validation error with error key
register_service_errors(dict) Registers error messages for a service
AnchorPagination / CreatedAtAnchorPagination Cursor-style paginators

stapel_core.bus — Event bus

Transport-agnostic event bus: in-memory backend for tests/dev, Kafka, NATS JetStream, or Redis Streams for production — pick one via STAPEL_BUS_BACKEND (or bring your own BusBackend subclass).

Publish (sync, fire-and-forget):

from stapel_core.bus import publish, Event

publish('user.created', Event(
    event_type='user.created',
    service='auth',
    payload={'user_id': '...'},
))

Consume by subclassing the management-command base:

from stapel_core.bus import BaseBusConsumerCommand, Event

class ConsumeUsers(BaseBusConsumerCommand):
    topics = ['user.created']
    consumer_group = 'notifications'

    def handle_event(self, event: Event) -> None:
        ...

Backend is selected via the STAPEL_BUS_BACKEND env var or Django setting (shorthand memory / kafka / nats / redis_streams, or any dotted path). Default is memory (stapel_core.bus.backends.memory.MemoryBus); production picks one of stapel_core.bus.backends.kafka.KafkaBus, stapel_core.bus.backends.nats.NatsJetStreamBus, or stapel_core.bus.backends.redis_streams.RedisStreamsBus (needs pip install 'stapel-core[kafka]' / [nats] / [redis-bus] respectively — see MODULE.md for connection settings and delivery semantics).


stapel_core.notifications — Push notifications

from stapel_core.notifications import request_notification

request_notification(
    notification_type='welcome',
    user_id=str(user.id),
    email=user.email,
    variables={'name': user.username},
    source_service='auth',
)

stapel_core.oauth — OAuth provider registry

Provider classes (GoogleProvider, GitHubProvider, etc.) and registry for OAuth consumer flows (when your service accepts OAuth logins from external providers).


stapel_core.gdpr — GDPR utilities

Account closure requests, data export, re-registration hashes.


Running tests

cd iron-common-python
pip install -e '.[dev]'
pytest stapel_core/tests/ -v

Download files

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

Source Distribution

stapel_core-0.12.3.tar.gz (513.8 kB view details)

Uploaded Source

Built Distribution

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

stapel_core-0.12.3-py3-none-any.whl (443.5 kB view details)

Uploaded Python 3

File details

Details for the file stapel_core-0.12.3.tar.gz.

File metadata

  • Download URL: stapel_core-0.12.3.tar.gz
  • Upload date:
  • Size: 513.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stapel_core-0.12.3.tar.gz
Algorithm Hash digest
SHA256 3e8f0f44656d77f9b9d4a5041f6a14f7f05af0f5c5f5bb0c0512919b6416e207
MD5 94eccd2d35ee87f91dd5e685a8b37efb
BLAKE2b-256 dadfcf9ab69827098b983c8a4a2032dd7231717d35c98812ebaa9482c3d0206f

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_core-0.12.3.tar.gz:

Publisher: publish.yml on usestapel/stapel-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file stapel_core-0.12.3-py3-none-any.whl.

File metadata

  • Download URL: stapel_core-0.12.3-py3-none-any.whl
  • Upload date:
  • Size: 443.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for stapel_core-0.12.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ef962aeff349c3caf23a0c44d3aba770a99a4192c71b25c30c1df5a0a171a9be
MD5 fd3135ece22bf0b853f7976235755e2e
BLAKE2b-256 afbdceb8544edd49fd90e0df125ac1325ae271ab942c3d207a6ab9bb26464ec9

See more details on using hashes here.

Provenance

The following attestation bundles were made for stapel_core-0.12.3-py3-none-any.whl:

Publisher: publish.yml on usestapel/stapel-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.34.0

2 files

0.33.2

2 files

0.33.1

2 files

0.33.0

2 files

0.32.0

2 files

0.31.0

2 files

0.30.1

2 files

0.29.0

2 files

0.28.0

2 files

0.27.0

2 files

0.26.0

2 files

0.25.0

2 files

0.24.1

2 files

0.24.0

2 files

0.23.1

2 files

0.22.0

2 files

0.21.0

2 files

0.20.2

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.1

2 files

0.17.0

2 files

0.16.1

2 files

0.16.0

2 files

0.15.12

2 files

0.15.10

2 files

0.15.9

2 files

0.15.8

2 files

0.15.7

2 files

0.15.6

2 files

0.15.5

2 files

0.15.4

2 files

0.15.3

2 files

0.15.2

2 files

0.15.1

2 files

0.14.2

2 files

0.14.1

2 files

0.14.0

2 files

0.13.0

2 files

0.12.4

2 files

This release

0.12.3 This release

2 files

0.12.2

2 files

0.12.1

2 files

0.12.0

2 files

0.11.2

2 files

0.11.1

2 files

0.11.0

2 files

0.10.4

2 files

0.10.3

2 files

0.10.2

2 files

0.10.1

2 files

0.10.0

2 files

0.8.0

2 files

0.3.2

2 files

0.3.1

1 file

Supported by

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