import logging
import time
from thingy import Thingy
from appnexus.client import AppNexusClient, client, services_list
from appnexus.utils import classproperty, normalize_service_name
logger = logging.getLogger("appnexus-client")
[docs]class Model(Thingy):
"""Generic model for AppNexus data"""
_update_on_save = True
client = client
[docs] @classmethod
def connect(cls, username, password):
cls.client = AppNexusClient(username, password)
return cls.client
[docs] @classmethod
def find(cls, **kwargs):
representation = (kwargs.pop("representation", None)
or cls.client.representation
or cls.constructor)
return cls.client.find(cls.service_name, representation=representation,
**kwargs)
[docs] @classmethod
def find_one(cls, **kwargs):
return cls.find(**kwargs).first
[docs] @classmethod
def count(cls, **kwargs):
return cls.find(**kwargs).count()
@classproperty
def service_name(cls):
return normalize_service_name(cls.__name__)
[docs] @classmethod
def create(cls, payload, **kwargs):
payload = {cls.service_name: payload}
return cls.client.create(cls.service_name, payload, **kwargs)
[docs] @classmethod
def delete(cls, *args, **kwargs):
return cls.client.delete(cls.service_name, *args, **kwargs)
[docs] @classmethod
def modify(cls, payload, **kwargs):
payload = {cls.service_name: payload}
return cls.client.modify(cls.service_name, payload, **kwargs)
[docs] @classmethod
def constructor(cls, client, service_name, obj):
cls.client = client
cls.service_name = service_name
return cls(obj)
[docs] def save(self, **kwargs):
payload = self.__dict__
if "id" not in self.__dict__:
logger.info("creating a {}".format(self.service_name))
result = self.create(payload, **kwargs)
else:
result = self.modify(payload, id=self.id, **kwargs)
if self._update_on_save:
self.update(result)
return self
class AlphaModel(Model):
_update_on_save = False
_modifiable_fields = ()
def __setattr__(self, attr, value):
if self._modifiable_fields and attr not in self._modifiable_fields:
raise AttributeError("'{}' can't be modified".format(attr))
super(AlphaModel, self).__setattr__(attr, value)
@classmethod
def find(cls, **kwargs):
raise NotImplementedError("Can't get multiple objects on '{}' service"
.format(cls.service_name))
@classmethod
def find_one(cls, id, **kwargs):
representation = (kwargs.pop("representation", None)
or cls.client.representation
or cls.constructor)
response = cls.client.get(cls.service_name, id=id, **kwargs)
if representation:
return representation(cls.client, cls.service_name, response)
return response
@classmethod
def modify(cls, payload, **kwargs):
non_modifiable_fields = set(payload) - set(cls._modifiable_fields)
for field in non_modifiable_fields:
del payload[field]
return super(AlphaModel, cls).modify(payload, **kwargs)
[docs]class CustomModelHash(AlphaModel):
_modifiable_fields = ("coefficients",)
[docs]class CustomModelLogit(AlphaModel):
_modifiable_fields = ("beta0", "active", "predictors" "scale", "min",
"max", "name", "offset", "member_id")
[docs]class CustomModelLUT(AlphaModel):
_modifiable_fields = ("coefficients",)
[docs]class LineItemModel(AlphaModel):
pass
[docs]class Report(Model):
[docs] def download(self, retry_count=3, **kwargs):
while not self.is_ready and retry_count > 0:
retry_count -= 1
time.sleep(1)
return self.client.get("report-download", id=self.report_id)
@property
def is_ready(self):
status = Report.find_one(id=self.report_id).execution_status
return (status == "ready")
class BudgetSplitterMixin():
@property
def budget_splitter(self):
return BudgetSplitter.find_one(id=self.id) # noqa: F821
class ChangeLogMixin():
@property
def changelog(self):
return ChangeLog.find(service=self.service_name, # noqa: F821
resource_id=self.id)
class ProfileMixin():
@property
def profile(self):
return Profile.find_one(id=self.profile_id) # noqa: F821
def create_models(services_list):
for service_name in services_list:
ancestors = [Model]
if service_name in ("LineItem"):
ancestors.append(BudgetSplitterMixin)
if service_name in ("Campaign", "InsertionOrder", "LineItem",
"Profile"):
ancestors.append(ChangeLogMixin)
if service_name in ("AdQualityRule", "Advertiser", "Campaign",
"Creative", "LineItem", "PaymentRule"):
ancestors.append(ProfileMixin)
model = type(service_name, tuple(ancestors), {})
globals().setdefault(service_name, model)
create_models(services_list)
__all__ = ["Model", "services_list"] + services_list