Skip to main content

No project description provided

Project description

Saboteur

PyPI version

A simple and extensible data mutation library for Chaos Engineering in Python.

🤔 What is Saboteur?

Saboteur is a lightweight Python library designed to test the robustness of your data processing logic. It helps you practice Chaos Engineering by intentionally and randomly injecting faulty or unexpected data into your system.

By "attacking" your data with various mutation strategies, Saboteur helps you uncover hidden bugs, handle edge cases gracefully, and build more resilient applications.

✨ Key Features

  • Simple API: Get started in seconds with the intuitive .mutate() method.
  • Randomized Mutations: Automatically selects a random field and applies a random, applicable mutation to simulate real-world unpredictability.
  • Extensible: Easily create and add your own custom mutation strategies to fit your specific needs.
  • Lightweighted Dependencies: A pure Python library that can be dropped into any project without extra baggage.

💾 Installation

Install Saboteur directly from PyPI:

# pip
pip install saboteur

# poetry
pip install poetry && poetry add saboteur

🚀 Quick Start

Using Saboteur is straightforward. Import the Saboteur class and the desired strategies, then call the mutate method on your data.

from saboteur.application.facade import Saboteur
from saboteur.infrastructure.mutation.strategies.injections import NullInjectionStrategy
from saboteur.infrastructure.mutation.strategies.flippings import TypeFlipStrategy
from saboteur.domain.mutation.configs import MutationConfig
from saboteur.domain.mutation.runner import MutationRunner

# 0. Prepare your data
mock_data = {
    "user_id": 12345,
    "username": "test_user",
    "is_active": True,
    "score": 987
}

# 1. Define the strategies you want to use
strategies = [
    NullInjectionStrategy(),
    TypeFlipStrategy(),
]

# 2. Set configuration what you want
config = MutationConfig(
    strategies=strategies,
    apply_all_strategies=True,
    original_data=mock_data,
)

# 3. Initialize runners with corresponding configuration within Sabeteur Facade.
saboteur = Saboteur(
    runners=[
        MutationRunner(
            config=config,
        )
    ]
)

# 4. Mutate the data!
# Saboteur will randomly pick one key (e.g., "user_id") and apply one
# applicable strategy (e.g., TypeFlipStrategy).
results = saboteur.run()

# 5. Each runner's results is returned!
# Example output: {4332166016: MutationResult(result={'age': 25, 'name': 'John', 'active': None, 'score': None, 'nested': {'height': 175.5, 'weight': 70, 'hobbies': ['reading', 'gaming'], 'nested_level_2': {'city': 'New York', 'country': 'USA'}}}, created_at='2026-03-02T07:47:15.115957+09:00', elapsed_time=3.724999260157347e-05)}
print(results)

🛠️ Available Strategies

Saboteur comes with a set of built-in strategies to get you started.

Strategies for MutationRunner

NullInjectionStrategy

Replaces the original value of a field with None. This is useful for testing how your code handles missing or null data.

  • Applicable when: The original value is not None.
  • Mutation: original_value -> None

TypeFlipStrategy

Changes the data type of a field. Currently supports int to str and str to int conversions. This helps test for TypeError exceptions and weak typing issues.

  • Applicable when: The original value is an int or a str.
  • Mutation:
    • int -> str (e.g., 123 -> '123')
    • str -> int (e.g., '456' -> 456). If the string is not a digit, it returns -1.

RandomizationStrategy

Replaces a value with a randomized value of the same type. This is useful for testing how your system handles a wide range of valid but unexpected inputs. Saboteur provides several randomization strategies based on data type.

  • IntegerRandomizationStrategy(from_value: int = -1000, to_value: int = 1000)

    • Applicable when: The original value is an int.
    • Mutation: Replaces the value with a random integer within the specified range (from_value to to_value).
  • FloatRandomizationStrategy(from_value: float = -1000.0, to_value: float = 1000.0)

    • Applicable when: The original value is a float.
    • Mutation: Replaces the value with a random float within the specified range.
  • StringRandomizationStrategy(length: int = 10)

    • Applicable when: The original value is a str.
    • Mutation: Replaces the value with a random alphanumeric string of the specified length.
  • BooleanRandomizationStrategy

    • Applicable when: The original value is a bool.
    • Mutation: Replaces the value with a randomly chosen True or False.
  • ListRandomizationStrategy

    • Applicable when: The original value is a list.
    • Mutation: Creates a new list of the same length by randomly sampling elements from the original list (with replacement).
  • DictRandomizationStrategy

    • Applicable when: The original value is a dict.
    • Mutation: Replaces the dictionary with a new one where the order of keys is shuffled.

✍️ Creating a Custom Strategy

You can easily create your own strategies by inheriting from MutationStrategy and implementing two methods: is_applicable and apply.

Here's an example of a BooleanFlipStrategy that flips True to False and vice-versa.

# custom_strategies.py
from typing import Any
from saboteur.domain.mutation.strategies import MutationStrategy
from saboteur.domain.mutation.contexts import MutationContext
from saboteur.domain.mutation.configs import MutationConfig


class BooleanFlipStrategy(MutationStrategy):
    """Flips a boolean value."""
    def is_applicable(self, context: MutationContext) -> bool:
        # This strategy only applies to boolean types
        return isinstance(context.original_value, bool)

    def apply(self, context: MutationContext) -> Any:
        # The mutation logic is simple: flip the boolean
        return not context.original_value

# You can then use it with Saboteur:
strategies = [
    BooleanFlipStrategy(),
    # ... other strategies
]

config = MutationConfig(
    strategies=strategies,
    ... # other options
)

🤝 Contributing

Contributions are welcome! Whether it's adding new strategies, improving documentation, or fixing bugs, please feel free to open an issue or submit a pull request.

  1. Fork the repository.
  2. Create your feature branch (git checkout -b feature/AmazingFeature).
  3. Commit your changes (git commit -m 'Add some AmazingFeature').
  4. Push to the branch (git push origin feature/AmazingFeature).
  5. Open a Pull Request.

🗺️ Roadmap

Saboteur is currently focused on data mutation, but we plan to expand its capabilities. Future versions will include logic for API load testing, allowing you to simulate heavy traffic and test the performance and stability of your endpoints under stress.

📄 License

This project is licensed under the MIT License. See the LICENSE file for details.

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

saboteur-0.1.6.tar.gz (10.6 kB view details)

Uploaded Source

Built Distribution

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

saboteur-0.1.6-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file saboteur-0.1.6.tar.gz.

File metadata

  • Download URL: saboteur-0.1.6.tar.gz
  • Upload date:
  • Size: 10.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.13.12 Linux/6.14.0-1017-azure

File hashes

Hashes for saboteur-0.1.6.tar.gz
Algorithm Hash digest
SHA256 ff4280de8b5a1aef582364772dd8b3f8f486ec508f2c750583bb0ce97e3a595a
MD5 5b6aba09173e4579256837e5c8f96b55
BLAKE2b-256 8f47aee9e9e7c6dea1240c4b9065f7bb79ec7723a331ebe5e666a32b713df97d

See more details on using hashes here.

File details

Details for the file saboteur-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: saboteur-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.13.12 Linux/6.14.0-1017-azure

File hashes

Hashes for saboteur-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 043bfeae890fcd0df1e3e0ad31806fc05db14e78dea50faebf644b0860d4c127
MD5 0a75bedb688fa1a37d00bdb32d4661cc
BLAKE2b-256 fc94b7e6685f6c21618e3dd612e5ce0fba3f810e417a9c5c7b3992a0b86d1812

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