Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apiobject subclass mapping #756

Merged
merged 5 commits into from
May 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Betsy Ross (Unreleased)

### Changes

- Use Mapping instead of dict as the base class for APIObject ([#756](https:/fishtown-analytics/dbt/pull/756))

## dbt 0.10.1 (Unreleased)

This release focuses on achieving functional parity between all of dbt's adapters. With this release, most dbt functionality should work on every adapter except where noted [here](https://docs.getdbt.com/v0.10/docs/supported-databases#section-caveats).
Expand Down
55 changes: 39 additions & 16 deletions dbt/api/object.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import copy
from collections import Mapping
from jsonschema import Draft4Validator

from dbt.exceptions import ValidationException
from dbt.utils import deep_merge


class APIObject(dict):
class APIObject(Mapping):
"""
A serializable / deserializable object intended for
use in a future dbt API.
Expand All @@ -24,17 +25,15 @@ class APIObject(dict):

DEFAULTS = {}

def __init__(self, *args, **kwargs):
def __init__(self, **kwargs):
"""
Create and validate an instance. Note that it's
not a good idea to override this.
Create and validate an instance. Note that if you override this, you
will want to do so by modifying kwargs and only then calling
super(NewClass, self).__init__(**kwargs).
"""
defaults = copy.deepcopy(self.DEFAULTS)
settings = copy.deepcopy(kwargs)

d = deep_merge(defaults, settings)
super(APIObject, self).__init__(*args, **d)
self.__dict__ = self
super(APIObject, self).__init__()
# note: deep_merge does a deep copy on its arguments.
self._contents = deep_merge(self.DEFAULTS, kwargs)
self.validate()

def incorporate(self, **kwargs):
Expand All @@ -43,15 +42,13 @@ def incorporate(self, **kwargs):
into a new copy of this instance, and return the new
instance after validating.
"""
existing = copy.deepcopy(dict(self))
updates = copy.deepcopy(kwargs)
return type(self)(**deep_merge(existing, updates))
return type(self)(**deep_merge(self._contents, kwargs))

def serialize(self):
"""
Return a dict representation of this object.
"""
return dict(self)
return copy.deepcopy(self._contents)

@classmethod
def deserialize(cls, settings):
Expand All @@ -72,11 +69,37 @@ def validate(self):
errors = []

for error in validator.iter_errors(self.serialize()):
errors.append('property "{}", {}'.format(
".".join(error.path), error.message))
errors.append('.'.join(
list(map(str, error.path)) + [error.message])
)

if errors:
raise ValidationException(
'Invalid arguments passed to "{}" instance: {}'
.format(type(self).__name__,
", ".join(errors)))

# implement the Mapping protocol:
# https://docs.python.org/3/library/collections.abc.html
def __getitem__(self, key):
return self._contents[key]

def __iter__(self):
return self._contents.__iter__()

def __len__(self):
return self._contents.__len__()

# implement this because everyone always expects it.
def get(self, key, default=None):
return self._contents.get(key, default)

# most users of APIObject also expect the attributes to be available via
# dot-notation because the previous implementation assigned to __dict__.
# we should consider removing this if we fix all uses to have properties.
def __getattr__(self, name):
if name in self._contents:
return self._contents[name]
raise AttributeError((
"'{}' object has no attribute '{}'"
).format(type(self).__name__, name))
5 changes: 3 additions & 2 deletions dbt/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import hashlib
import itertools
import collections
import copy
import functools

import dbt.exceptions
Expand Down Expand Up @@ -240,10 +241,10 @@ def deep_merge(*args):
return None

if len(args) == 1:
return args[0]
return copy.deepcopy(args[0])

lst = list(args)
last = lst.pop(len(lst)-1)
last = copy.deepcopy(lst.pop(len(lst)-1))

return _deep_merge(deep_merge(*lst), last)

Expand Down