Skip to main content

Polars plugin exposing PlHashSet for efficient filtering with persistent sets across LazyFrames

Project description

polars-hashfilter

A Polars plugin exposing PlHashSet for efficient filtering with persistent sets across LazyFrames.

Features

  • Zero-copy StringHashSet: A Python-accessible wrapper around Polars' PlHashSet<String> that can be shared between Python and Rust without copying the underlying data
  • Persistent filtering: Use the same hashset across multiple LazyFrames for deduplication
  • Streaming compatible: All expressions work in streaming mode with engine="streaming"
  • Seven filtering/update expressions:
    • is_in: Check if values exist in the set (read-only)
    • not_in: Check if values do NOT exist in the set (read-only)
    • not_in_and_update: Check if values are NOT in the set, then add them (batch semantics)
    • not_in_and_update_rowwise: Same as above but with row-by-row semantics
    • update: Bulk insert all values into the set (returns null)
    • update_chain: Bulk insert all values, return original Series (for chaining)
    • update_bool: Insert values row-by-row, return bool indicating if newly inserted

Installation

# From source with uv
uv pip install .

# Development mode
just dev

Usage

Basic Example

import polars as pl
from polars_hashfilter import StringHashSet

# Create a persistent set
seen = StringHashSet.from_values(["alice", "bob"])

# Filter using the set
df = pl.DataFrame({"user": ["alice", "charlie", "bob", "dave"]})

# Using standalone functions
from polars_hashfilter import is_in_hashset, not_in_hashset

df.filter(is_in_hashset(pl.col("user"), seen))
# shape: (2, 1)
# ┌───────┐
# │ user  │
# │ ---   │
# │ str   │
# ╞═══════╡
# │ alice │
# │ bob   │
# └───────┘

# Using expression namespace
df.filter(pl.col("user").hashfilter.not_in(seen))
# shape: (2, 1)
# ┌─────────┐
# │ user    │
# │ ---     │
# │ str     │
# ╞═════════╡
# │ charlie │
# │ dave    │
# └─────────┘

Deduplication Across Multiple LazyFrames (Anti-Join Pattern)

This is the primary use case - efficiently deduplicate records across many large LazyFrames:

import polars as pl
from polars_hashfilter import StringHashSet

# Create a persistent set to track seen IDs
seen = StringHashSet()

# Process multiple LazyFrames, keeping only new records
lazy_frames = [
    pl.LazyFrame({"id": ["a", "b"], "value": [1, 2]}),
    pl.LazyFrame({"id": ["b", "c"], "value": [3, 4]}),
    pl.LazyFrame({"id": ["c", "d"], "value": [5, 6]}),
]

for lf in lazy_frames:
    # Keep only rows we haven't seen before, and remember them
    df = lf.filter(pl.col("id").hashfilter.not_in_and_update(seen)).collect()
    print(df)
    # First:  id=["a", "b"], value=[1, 2]
    # Second: id=["c"],      value=[4]      (b already seen)
    # Third:  id=["d"],      value=[6]      (c already seen)

# The set now contains all unique IDs
print(seen.to_list())  # ["a", "b", "c", "d"]

Batch vs Rowwise Semantics

IMPORTANT: When processing data with duplicates, you must choose the right semantics:

Batch Semantics (not_in_and_update - default)

All rows are evaluated against the initial set state, then new values are inserted. Duplicates within the same batch all return True.

df = pl.DataFrame({"id": ["a", "b", "a", "c", "b"]})

seen = StringHashSet()
result = df.filter(pl.col("id").hashfilter.not_in_and_update(seen))

print(result["id"].to_list())
# Output: ["a", "b", "a", "c", "b"]  <- All instances kept!
print(seen.to_list())
# Output: ["a", "b", "c"]  <- Only unique values stored

Use batch semantics when: You want to keep all instances of new values in the first occurrence batch, or when processing separate LazyFrames.

Rowwise Semantics (not_in_and_update_rowwise)

Rows are evaluated sequentially. First occurrence returns True, subsequent duplicates return False.

df = pl.DataFrame({"id": ["a", "b", "a", "c", "b"]})

seen = StringHashSet()
result = df.filter(pl.col("id").hashfilter.not_in_and_update_rowwise(seen))

print(result["id"].to_list())
# Output: ["a", "b", "c"]  <- Only first occurrence kept!
print(seen.to_list())
# Output: ["a", "b", "c"]  <- Only unique values stored

Use rowwise semantics when: You explicitly want to deduplicate within the same batch/DataFrame, keeping only the first occurrence.

⚠️ Streaming Mode Warning

In streaming mode (collect(engine="streaming")), Polars processes data in chunks. Each chunk triggers a separate expression call, which means:

  • Batch semantics only apply WITHIN each chunk
  • Across chunk boundaries, values from previous chunks are already in the set
  • For large data split across chunks, not_in_and_update effectively behaves like not_in_and_update_rowwise at chunk boundaries

Recommended patterns for streaming:

  • Use not_in_and_update_rowwise for consistent row-by-row deduplication
  • OR use not_in (read-only lookup) + update (explicit insert) for full control:
seen = StringHashSet()

# Pattern: Separate lookup from update
filtered = lf.filter(pl.col("id").hashfilter.not_in(seen))
filtered.select(pl.col("id").hashfilter.update(seen))  # Explicit update

Update Functions

For explicit control over when values are added to the set:

# update: Bulk insert, returns null (side-effect only)
df.select(pl.col("id").hashfilter.update(seen))

# update_chain: Bulk insert, returns original Series (for chaining)
df.select(
    pl.col("id")
    .hashfilter.update_chain(seen)
    .str.to_uppercase()  # Can chain other operations
)

# update_bool: Row-by-row insert, returns bool (True if newly inserted)
result = df.select(pl.col("id").hashfilter.update_bool(seen))
# First occurrence: True, duplicates: False

StringHashSet API

from polars_hashfilter import StringHashSet

# Creation
s = StringHashSet()                      # Empty set
s = StringHashSet.with_capacity(1000)    # Pre-allocated
s = StringHashSet.from_values(["a", "b"])  # From iterable

# Operations
s.insert("value")      # Insert, returns True if new
s.contains("value")    # Check membership
s.remove("value")      # Remove, returns True if existed
s.extend(["x", "y"])   # Bulk insert
s.clear()              # Remove all elements

# Inspection
len(s)                 # Number of elements
s.is_empty()           # Check if empty
s.to_list()            # Export as Python list (copies data)

# Debug
s._ptr()               # Memory address (for verifying zero-copy)

Zero-Copy Guarantee

The StringHashSet is stored behind Arc<RwLock>, meaning:

  1. No copies when passing to expressions: The set's memory address remains stable
  2. Thread-safe: Multiple readers OR one writer at a time
  3. Copies only when necessary:
    • StringHashSet.from_values() - copying Python strings to Rust
    • StringHashSet.extend() - copying Python strings to Rust
    • StringHashSet.to_list() - copying Rust strings to Python

You can verify zero-copy behavior:

seen = StringHashSet()
ptr1 = seen._ptr()

# Use in many expressions...
df.filter(pl.col("id").hashfilter.not_in_and_update(seen))

ptr2 = seen._ptr()
assert ptr1 == ptr2  # Same memory address

Development

# Setup
just venv

# Build (debug)
just dev

# Build (release)
just release

# Test
just test

# Format
just fmt

# Lint
just lint

License

MIT

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

polars_hashfilter-0.1.1.tar.gz (59.5 kB view details)

Uploaded Source

Built Distributions

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

polars_hashfilter-0.1.1-cp314-cp314-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.14Windows x86-64

polars_hashfilter-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.17+ x86-64

polars_hashfilter-0.1.1-cp314-cp314-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded CPython 3.14macOS 11.0+ ARM64

polars_hashfilter-0.1.1-cp313-cp313-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.13Windows x86-64

polars_hashfilter-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

polars_hashfilter-0.1.1-cp313-cp313-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

polars_hashfilter-0.1.1-cp312-cp312-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.12Windows x86-64

polars_hashfilter-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

polars_hashfilter-0.1.1-cp312-cp312-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

polars_hashfilter-0.1.1-cp311-cp311-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.11Windows x86-64

polars_hashfilter-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.17+ x86-64

polars_hashfilter-0.1.1-cp311-cp311-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

polars_hashfilter-0.1.1-cp310-cp310-win_amd64.whl (4.4 MB view details)

Uploaded CPython 3.10Windows x86-64

polars_hashfilter-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (4.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.17+ x86-64

polars_hashfilter-0.1.1-cp310-cp310-macosx_11_0_arm64.whl (4.0 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file polars_hashfilter-0.1.1.tar.gz.

File metadata

  • Download URL: polars_hashfilter-0.1.1.tar.gz
  • Upload date:
  • Size: 59.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for polars_hashfilter-0.1.1.tar.gz
Algorithm Hash digest
SHA256 20c68a776a8ebd0a6ee3262e031ba54dba5b96b545d68dea0169d11d523e388e
MD5 b09c74cf81bf121e28f6fa067370b8a7
BLAKE2b-256 6f3e6a35dd427418f1a3596d57ff26ebb162fbc5c2ae5dd582175ca9c30e2f8e

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1.tar.gz:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp314-cp314-win_amd64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 a2a1edb586ec4581a6b2af6309561723f8f59a9ab5cd3f507885bb58386dfac4
MD5 5aa1d9ee4810b858c06f3fdd58aafeb8
BLAKE2b-256 6b985f4fa892e8b7aa438024dd2c2e09f0b1ef6d3db09cb4203998e311af0aff

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp314-cp314-win_amd64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 7cac894fab0eab87aae38417d5feb19313528595ce8cf3d7770bc3f8ad878c10
MD5 23f4dd76bccc6ccfc64b578304a0434d
BLAKE2b-256 81f8fe894ba947a6d6c887926766aea935805372e3ca4d01f684a641fecff94c

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp314-cp314-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp314-cp314-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f84c02ac10d7c9971bceea29653454088ef2beebdc7a87e989abcc84d9df8841
MD5 39dc30b351bd1a109cbc601406a32fc7
BLAKE2b-256 e4577702f7016140b113691cc0818302ab6231851c40d22de0f47e839998ccc6

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp314-cp314-macosx_11_0_arm64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp313-cp313-win_amd64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 25c9123c128cd14b0e0924c310a756b543c8088ebc3640bde14c345b64112bfd
MD5 acb958cbfdad246aa184b15da5fc3071
BLAKE2b-256 d4ec014a8795bf3df22ddb83cc8c081fd08ddda9c2259c7d00f8eda9629dc8a5

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp313-cp313-win_amd64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 19d52fe43403137bba42678cf15e407b4b960195ddca69c02b0d324efec47166
MD5 ee1e6e88f4cb44fd5da775a000b501b2
BLAKE2b-256 df56e05bf35301050d601273faa9d6dda9630f0827fadd26fee85c4e8f3c6aea

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 563b29c431072c876f6a8d7adf6aa457710404129d0c4611d6cf560706ec5a94
MD5 fadf71f60abba4fcd0f91c39e4b5c7d8
BLAKE2b-256 85cc98938621a325f74b383c28ad1365d9ee629593c11f9e29ebadc67fbb60e6

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp312-cp312-win_amd64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 e0b35fb916557b577d891d6faae92a5fadde44efc0951eae6a9e9188b3d7c9a3
MD5 4d77973b6dd503bdf01e5fe6762c3cc7
BLAKE2b-256 ba826fe704e6502e4e0e79d67df6c4aec929bbed4c339703d67a0447aec05280

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp312-cp312-win_amd64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 dfde7f8be6ed14845803236c6ab6e89233e42adb9981f707664cb939e1f6315e
MD5 0380153766075e93891504e1d2e0d186
BLAKE2b-256 7684b75288848681fef77675ea91ed692897bd245ada2329cf943c62c98b4fe4

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 b8c3af3fd3eb71f156ca185c4ed761a5b294af0261499159142f642669f4ec9c
MD5 936bec28a88da05ee3ea636346669922
BLAKE2b-256 63556f3b18104f89d78abde527e91e0bd6fc11ad4691c6cb7ee099c0c5638fb9

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp311-cp311-win_amd64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 6eaf1effa5d8580016a70c49fbf71bb4264b9bbc1eb588fa555de302da48fab0
MD5 bf6d9616d9fe6964d8a4470bb30c98c5
BLAKE2b-256 032be6cd75a03d441defe65667bb46488d728508e6169155743acb41163a94b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp311-cp311-win_amd64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 b14ac176807fb91a9d255429189c3bd805c162fb0f2a90884b7c724466f5cf82
MD5 90606c15a30e1dc25b0c86b6986f4f7c
BLAKE2b-256 bafdc40316e6b3dd892444cea854b7d7b2ddcbd24e9c31cbdc602c2bd74d830b

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 77b3a87fd2373e9247435c359e1e7e2309ff06c39fd669e9862e7dccd69a119e
MD5 5fbc6d5dca7f577f683f9daf8d901df9
BLAKE2b-256 3dd3cc01a919f05c129b3a0ad5c3555a0f21e134a923a5dddf07e0af0bb523fa

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp310-cp310-win_amd64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 7892493816f332bf2f4dd0c2388787a1878f8c1c1fea90ac95c3f323129b9a09
MD5 8c69e676f81809fc9108e749421d6d70
BLAKE2b-256 1fcbd21840e460e95a20bc8f3ee0acbc96055702a1b2797368b37d84f2e7bb15

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp310-cp310-win_amd64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a6651208b4724c6d28f05b8c095af209934c133d6738f50e1350c6c67caf75dd
MD5 e17fa246d952da0f9e64310fa6a5016c
BLAKE2b-256 d750531f918f3ee0d86404c084826873d03b8f5df01842f70465c7dc88dbf94e

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

File details

Details for the file polars_hashfilter-0.1.1-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polars_hashfilter-0.1.1-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 ec4c45983770165bdb9cff39b7474634eb7229a3287baf90b605d514f8737d1c
MD5 e23a4de132bd499be7b0cb00a2e8be90
BLAKE2b-256 69d7d77795d67f1d512f7ec164e57c6012427e528a7f84d701400b0ee1d78daa

See more details on using hashes here.

Provenance

The following attestation bundles were made for polars_hashfilter-0.1.1-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: release.yml on jpfeuffer/polars-hashfilter

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

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