dustbunny


Namedustbunny JSON
Version 1.3.1 PyPI version JSON
download
home_pagehttps://github.com/Teamworksapp/dustbunny
SummaryGenerate database records for fuzz testing using SQLAlchemy
upload_time2017-10-12 19:26:46
maintainer
docs_urlNone
authorJefferson Heard
requires_python
licenseMIT
keywords pytest hypothesis sqlalchemy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Dustbunny

* **Author:** Jefferson Heard 
* **Version:** 0.0.1

## Introduction

Dustbunny creates fuzz records for SQL databases. It's based on [Hypothesis](http://hypothesis.works/) and uses it to 
generate random values for data columns. The difference between using Dustbunny vs. Hypothesis directly is that it's 
easier to generate values that are relative to an already generated value, and it's easier to structure the generation 
of relationships between tables, such as foreign keys. 

Dustbunny also records the records it generates, so you can remove them from the database when you 
are done with them.

## Working with Dustbunny

1. Create a Generate instance with the sqla session and ORM model that you want to generate.
2. Change the creation function (will use the default model create if you don't)
3. Set the number of records you want to generate total 
4. Set-up parent records using `for_some` or `for all`
4. Set fixed and random values
5. Set relative value functions
6. Call the `.execute()` function
7. Commit to the database. Generally your creation function should just create, not commit, for the sake of speed.


## Example:

The following generates a random set of appointments with start times distributed throughout the day, and with amounts 
of time distributed between even increments of fifteen minutes, across a predetermined sample of attendees (defined 
elsewhere), and across a predetermined sample of dates.  It builds 50 records using the model's controller class 
attribute to create the record. It exercises the facility for generating fixed values, random values from strategies,
 and relative values based on the previous two:

```python
from app.extensions import db
import importlib
from hypothesis import strategies as st, settings, given
from dustbunny.hyp.strategies import *
from dustbunny import Generate
from dustbunny.hyp.strategies import gfywords, gfycodes, words

import_upon_configure(db.Model, here)

# ...
# initialize the database ...
# ...

use_controller = lambda M, **kwargs: M.controller_class.create(_commit=False, **kwargs)

# ... do stuff ,,,

# Grab some initial records we're goin gto use in our fixed values
org = Org.query.first()
appointment_type = AppointmentType.query.first()

gen = Generate(db, Appointment)\
    .by_method(use_controller)\  # this is called instead of create
    .num(50)\   # generate 50 records
    .with_fixed_values_for(  # use fixed values for the following record attributes
        calendar_sync_google=False,  
        is_private=False,
        is_all_day=False,
        enable_notifications=False,
        organization=org,
        appointment_type_id=appointment_type.pk,
    ).using(  # use hypothesis strategies for generating the following attributes
        appointment_registration_type_id=st.sampled_from([x.pk for x in AppointmentRegistrationType.query.all()]),
        title=gfywords(),  # a dustbunny strategy for generating adj-adj-noun triplets that are unique
        location=gfywords(),
        appt_date=st.sampled_from(date_range),
        notes=words(),  # generate random words
        wage_minutes=st.sampled_from((15, 30, 45, 60, 90)),
        wage_code_id=st.sampled_from([x.pk for x in WageCode.query.all()]),
        members = st.lists(st.sampled_from(workers), average_size=5, min_size=1, max_size=25)
    ).with_relative_values_for(
        start_hour=lambda **k: k['appt_date'].hour,
        end_hour=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).hour,
        start_minute=lambda **k: k['appt_date'].minute,
        end_minute=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).minute,
        wage_start_hour=lambda **k: k['appt_date'].hour,
        wage_end_hour=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).hour,
        wage_start_minute=lambda **k: k['appt_date'].minute,
        wage_end_minute=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).minute,
        is_mandatory=lambda **k: k['wage_minutes'] > 0,
        start_date=lambda **k: k['appt_date'],
        end_date=lambda **k: k['appt_date'] + timedelta(minutes=k['wage_minutes']),
    )
    
worker_appointments.extend(gen.execute())  # actually create the records

db.session.commit()  # we don't commit during the generation because it takes forever.
```

A second example, using `for_every` to run the generator once for every parent:

```python
db = # ... the orm instance
Log = # ... a db model.
use_controller = # ... a function.
gen = Generate(db, Log)\
    .by_method(use_controller)\
    .num(n=1)\
    .for_every( # for each permutation of
        ('config', Config.query.all()),  # every TmpActivityLogConfig record that exists
        ('log_start_date', date_range)  # for 52 weeks
    ).with_fixed_values_for(  # set these attributes of the record to fixed values
        log_stage_seq=0,
        log_review_date=None
    ).with_relative_values_for(  # set these attributes of the record to values based on everything that came before
        log_end_date=lambda **kwargs: kwargs['log_start_date'] + timedelta(days=7)  # make the end date 1 week after the given config
    )
    
logs = gen.execute()  # execute the insert
```
            

Raw data

            {
    "_id": null,
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "cheesecake_code_kwalitee_id": null,
    "keywords": "pytest hypothesis sqlalchemy",
    "upload_time": "2017-10-12 19:26:46",
    "author": "Jefferson Heard",
    "home_page": "https://github.com/Teamworksapp/dustbunny",
    "github_user": "Teamworksapp",
    "download_url": "https://pypi.python.org/packages/4a/9c/90b47967a3a3377ecc69ecc1fc30604814edfdc497c8b1845a121baf98f8/dustbunny-1.3.1.macosx-10.12-x86_64.tar.gz",
    "platform": "",
    "version": "1.3.1",
    "cheesecake_documentation_id": null,
    "description": "# Dustbunny\n\n* **Author:** Jefferson Heard \n* **Version:** 0.0.1\n\n## Introduction\n\nDustbunny creates fuzz records for SQL databases. It's based on [Hypothesis](http://hypothesis.works/) and uses it to \ngenerate random values for data columns. The difference between using Dustbunny vs. Hypothesis directly is that it's \neasier to generate values that are relative to an already generated value, and it's easier to structure the generation \nof relationships between tables, such as foreign keys. \n\nDustbunny also records the records it generates, so you can remove them from the database when you \nare done with them.\n\n## Working with Dustbunny\n\n1. Create a Generate instance with the sqla session and ORM model that you want to generate.\n2. Change the creation function (will use the default model create if you don't)\n3. Set the number of records you want to generate total \n4. Set-up parent records using `for_some` or `for all`\n4. Set fixed and random values\n5. Set relative value functions\n6. Call the `.execute()` function\n7. Commit to the database. Generally your creation function should just create, not commit, for the sake of speed.\n\n\n## Example:\n\nThe following generates a random set of appointments with start times distributed throughout the day, and with amounts \nof time distributed between even increments of fifteen minutes, across a predetermined sample of attendees (defined \nelsewhere), and across a predetermined sample of dates.  It builds 50 records using the model's controller class \nattribute to create the record. It exercises the facility for generating fixed values, random values from strategies,\n and relative values based on the previous two:\n\n```python\nfrom app.extensions import db\nimport importlib\nfrom hypothesis import strategies as st, settings, given\nfrom dustbunny.hyp.strategies import *\nfrom dustbunny import Generate\nfrom dustbunny.hyp.strategies import gfywords, gfycodes, words\n\nimport_upon_configure(db.Model, here)\n\n# ...\n# initialize the database ...\n# ...\n\nuse_controller = lambda M, **kwargs: M.controller_class.create(_commit=False, **kwargs)\n\n# ... do stuff ,,,\n\n# Grab some initial records we're goin gto use in our fixed values\norg = Org.query.first()\nappointment_type = AppointmentType.query.first()\n\ngen = Generate(db, Appointment)\\\n    .by_method(use_controller)\\  # this is called instead of create\n    .num(50)\\   # generate 50 records\n    .with_fixed_values_for(  # use fixed values for the following record attributes\n        calendar_sync_google=False,  \n        is_private=False,\n        is_all_day=False,\n        enable_notifications=False,\n        organization=org,\n        appointment_type_id=appointment_type.pk,\n    ).using(  # use hypothesis strategies for generating the following attributes\n        appointment_registration_type_id=st.sampled_from([x.pk for x in AppointmentRegistrationType.query.all()]),\n        title=gfywords(),  # a dustbunny strategy for generating adj-adj-noun triplets that are unique\n        location=gfywords(),\n        appt_date=st.sampled_from(date_range),\n        notes=words(),  # generate random words\n        wage_minutes=st.sampled_from((15, 30, 45, 60, 90)),\n        wage_code_id=st.sampled_from([x.pk for x in WageCode.query.all()]),\n        members = st.lists(st.sampled_from(workers), average_size=5, min_size=1, max_size=25)\n    ).with_relative_values_for(\n        start_hour=lambda **k: k['appt_date'].hour,\n        end_hour=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).hour,\n        start_minute=lambda **k: k['appt_date'].minute,\n        end_minute=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).minute,\n        wage_start_hour=lambda **k: k['appt_date'].hour,\n        wage_end_hour=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).hour,\n        wage_start_minute=lambda **k: k['appt_date'].minute,\n        wage_end_minute=lambda **k: (k['appt_date'] + timedelta(minutes=k['wage_minutes'])).minute,\n        is_mandatory=lambda **k: k['wage_minutes'] > 0,\n        start_date=lambda **k: k['appt_date'],\n        end_date=lambda **k: k['appt_date'] + timedelta(minutes=k['wage_minutes']),\n    )\n    \nworker_appointments.extend(gen.execute())  # actually create the records\n\ndb.session.commit()  # we don't commit during the generation because it takes forever.\n```\n\nA second example, using `for_every` to run the generator once for every parent:\n\n```python\ndb = # ... the orm instance\nLog = # ... a db model.\nuse_controller = # ... a function.\ngen = Generate(db, Log)\\\n    .by_method(use_controller)\\\n    .num(n=1)\\\n    .for_every( # for each permutation of\n        ('config', Config.query.all()),  # every TmpActivityLogConfig record that exists\n        ('log_start_date', date_range)  # for 52 weeks\n    ).with_fixed_values_for(  # set these attributes of the record to fixed values\n        log_stage_seq=0,\n        log_review_date=None\n    ).with_relative_values_for(  # set these attributes of the record to values based on everything that came before\n        log_end_date=lambda **kwargs: kwargs['log_start_date'] + timedelta(days=7)  # make the end date 1 week after the given config\n    )\n    \nlogs = gen.execute()  # execute the insert\n```",
    "lcname": "dustbunny",
    "name": "dustbunny",
    "github": true,
    "coveralls": false,
    "bugtrack_url": null,
    "license": "MIT",
    "travis_ci": false,
    "github_project": "dustbunny",
    "summary": "Generate database records for fuzz testing using SQLAlchemy",
    "split_keywords": [
        "pytest",
        "hypothesis",
        "sqlalchemy"
    ],
    "author_email": "jheard@teamworks.com",
    "urls": [
        {
            "has_sig": false,
            "upload_time": "2017-10-12T19:29:15",
            "comment_text": "",
            "python_version": "py3",
            "url": "https://pypi.python.org/packages/f6/79/f6cdb7ce895be72e87bd907cbda7dc56ec848f6721bc4c5b952c10fa4372/dustbunny-1.3.1-py3-none-any.whl",
            "md5_digest": "a3294cba337aa82ea55d9acf3a62f511",
            "downloads": 0,
            "filename": "dustbunny-1.3.1-py3-none-any.whl",
            "packagetype": "bdist_wheel",
            "path": "f6/79/f6cdb7ce895be72e87bd907cbda7dc56ec848f6721bc4c5b952c10fa4372/dustbunny-1.3.1-py3-none-any.whl",
            "size": 38499
        },
        {
            "has_sig": false,
            "upload_time": "2017-10-12T19:26:46",
            "comment_text": "",
            "python_version": "source",
            "url": "https://pypi.python.org/packages/4a/9c/90b47967a3a3377ecc69ecc1fc30604814edfdc497c8b1845a121baf98f8/dustbunny-1.3.1.macosx-10.12-x86_64.tar.gz",
            "md5_digest": "997d22ae6b635d502d8b2a2453293d04",
            "downloads": 0,
            "filename": "dustbunny-1.3.1.macosx-10.12-x86_64.tar.gz",
            "packagetype": "sdist",
            "path": "4a/9c/90b47967a3a3377ecc69ecc1fc30604814edfdc497c8b1845a121baf98f8/dustbunny-1.3.1.macosx-10.12-x86_64.tar.gz",
            "size": 42493
        }
    ],
    "cheesecake_installability_id": null
}
        
Elapsed time: 0.08030s