def test_add_with_field(): data = {} session = Session(data) ex1 = Example(pk=1, name="Paul") session.add(ex1) session.flush() assert data == {"examples/1": {"name": "Paul"}}
def test_non_nullable_fields_cannot_be_saved_with_null_values(): instance = Example(pk=1) instance.defaulted_field = None session = Session({}) session.add(instance) with pytest.raises(ValueError): session.flush()
def test_add(): data = {} session = Session(data) ex1 = Example(pk=1) session.add(ex1) session.flush() assert data == {"examples/1": {}}
def test_remove_is_idempotent(): data = {"examples/1": {"name": "Paula"}} session = Session(data) ex1 = session.get(Example, 1) session.remove(ex1) session.remove(ex1) session.flush()
def test_get_then_remove_entity(): data = {"examples/1": {"name": "Paula"}} session = Session(data) ex1 = session.get(Example, 1) session.remove(ex1) session.flush() assert data == {}
def test_get_then_edit_entity(): data = {"examples/1": {"name": "Paula"}} session = Session(data) ex1 = session.get(Example, 1) ex1.name = "Paul" session.flush() assert data == {"examples/1": {"name": "Paul"}}
def test_sessions_are_not_global(): instance_session1 = Example(pk=1) session = Session({}) session.add(instance_session1) session.flush() session2 = Session({}) with pytest.raises(KeyError): instance_session2 = session2.get(Example, 1)
def test_add_then_remove_entity_has_no_effect(): data = {} session = Session(data) ex1 = Example(pk=1, name="Horse") session.add(ex1) session.remove(ex1) session.flush() assert data == {}
def release(store, name, constraints, branches): """ Release a given configuration. This is the main utility for launching experiments. `store` is the storage transaction mapping. `name` is a free-form name (generally an experiment ID), `constraints` is the constraints covering this release, and `branches` is an iterable of (branch ID, num buckets, settings) triples. The utility will select buckets which are not already covering the given settings, which allows for partial rollout before running a test. """ session = Session(store) # Branches is a list of (name, n_buckets, settings) tuples all_buckets = [ session.get(Bucket, x, default=CREATE) for x in range(NUM_BUCKETS) ] edited_settings = set.union(*[set(x[2].keys()) for x in branches]) conflicting_experiments = set() valid_bucket_indices = [] for idx, bucket in enumerate(all_buckets): if is_valid_bucket(bucket, edited_settings, constraints): valid_bucket_indices.append(idx) else: for entry in bucket.entries: # Determine if this entry is a potential conflict if set(entry.settings.keys()).isdisjoint(edited_settings): continue conflicting_experiment_id, _ = entry.key conflicting_experiments.add(conflicting_experiment_id) random.shuffle(valid_bucket_indices) for branch_name, n_buckets, settings in branches: key = [name, branch_name] bucket_indices = valid_bucket_indices[:n_buckets] if len(bucket_indices) < n_buckets: raise NotEnoughBucketsException(conflicts=conflicting_experiments) valid_bucket_indices = valid_bucket_indices[n_buckets:] for bucket_idx in bucket_indices: bucket = all_buckets[bucket_idx] bucket.add(key, settings, constraints) session.flush()
def close(store, name, constraints, branches): """ Close a given configuration. This is the main utility for ending experiments. `store` is the storage transaction mapping. `name` is a free-form name (generally an experiment ID), `constraints` is the constraints covering this release, and `branches` is an iterable of (branch ID, num buckets, settings) triples. Deliberately looks like `release` and works to counteract its effects. """ session = Session(store) keys = [[name, x[0]] for x in branches] for idx in range(NUM_BUCKETS): bucket = session.get(Bucket, idx, default=None) if bucket is not None: for key in keys: bucket.remove(key) session.flush()