class TestModel(Model):
    class Meta:
        table_name = 'test-table'
        host = os.environ['AWS_DYNAMODB_HOST']
        read_capacity_units = 10
        write_capacity_units = 10

    unicode_attr = attributes.UnicodeAttribute(hash_key=True)
    binary_attr = attributes.BinaryAttribute()
    binary_set_attr = attributes.BinarySetAttribute()
    boolean_attr = attributes.BooleanAttribute()
class PageEntityBaseMixin:
    page_id = attributes.UnicodeAttribute(hash_key=True, attr_name='pid')

    bol = attributes.UTCDateTimeAttribute(null=True)
    eol = attributes.UTCDateTimeAttribute(null=True)

    is_accessible = attributes.BooleanAttribute(default=True,
                                                null=True,
                                                attr_name='is_accessible')

    entity_type = None  # will be overridden in subclass
    _additional_fields = {'entity_type'}
class PageEntity(ConsoleEntityMixin, BaseModel):
    """
    Represents a single facebook page entity
    """

    Meta = EntityBaseMeta(dynamodb_config.PAGE_ENTITY_TABLE)

    scope = attributes.UnicodeAttribute(hash_key=True, attr_name='scope')
    page_id = attributes.UnicodeAttribute(range_key=True, attr_name='pid')

    # copied indicator of activity from Console DB per each sync
    # (alternative to deletion. To be discussed later if deletion is better)
    is_active = attributes.BooleanAttribute(default=False, attr_name='a')

    is_accessible = attributes.BooleanAttribute(default=True,
                                                attr_name='is_accessible')

    # utilized by logic that prunes out Ad Accounts
    # that are switched to "inactive" on Console
    # Expectation is that after a long-running update job
    # there is a task at the end that goes back and marks
    # all AA records with non-last-sweep_id as "inactive"
    # See https://operam.atlassian.net/browse/PROD-1825 for context
    updated_by_sweep_id = attributes.UnicodeAttribute(null=True, attr_name='u')

    entity_type = Entity.Page

    _additional_fields = {'entity_type'}
    _default_bol = True

    @classmethod
    def upsert_entity_from_console(cls, job_scope: JobScope, entity: Any,
                                   is_accessible: bool):
        cls.upsert(
            job_scope.entity_id,  # scope ID
            entity['ad_account_id'],
            is_active=entity.get('active', True),
            updated_by_sweep_id=job_scope.sweep_id,
            is_accessible=is_accessible,
        )
Beispiel #4
0
class AuditModel(Model):
    class Meta:
        abstract = True

    is_removed = attributes.BooleanAttribute(default=False)
    created_at = attributes.NumberAttribute(default=get_timestamp())
    modified_at = attributes.NumberAttribute(default=get_timestamp())

    def save(self, condition=None) -> t.Dict[str, t.Any]:
        self.modified_at = get_timestamp()
        return super().save(condition)

    def safe_delete(self):
        self.is_removed = True
        return self.save()
class EntityBaseMixin:
    """
    Use this mixin for describing Facebook entity existence tables
    """

    # Note that each Entity is keyed by, effectively, a compound key: ad_account_id+entity_id
    # This allows us to issue queries like "Get all objects per ad_account_id" rather quickly
    ad_account_id = attributes.UnicodeAttribute(hash_key=True,
                                                attr_name='aaid')

    # Primary Keys

    # Hash Key (old name) == Primary Key (new name)
    # Range Key (old name) == Sort Key (new name) [ == Secondary Key (Cassandra term, used by Daniel D) ]
    # See https://aws.amazon.com/blogs/database/choosing-the-right-dynamodb-partition-key/

    # do NOT set an index on secondary keys (unless you really really need it)
    # In DynamoDB this limits the table size to 10GB
    # Without secondary key index, table size is unbounded.
    # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Limits.html
    # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.ItemCollections.SizeLimit
    entity_id = attributes.UnicodeAttribute(range_key=True, attr_name='eid')

    bol = attributes.UTCDateTimeAttribute(null=True)
    eol = attributes.UTCDateTimeAttribute(null=True)

    is_accessible = attributes.BooleanAttribute(default=True,
                                                null=True,
                                                attr_name='is_accessible')

    # Since we use UpdateItem for inserting records, we must have at least
    # one attribute specified on each model. Normally that would be created_time
    # but FB doesn't have that on all models, so we setup a default
    _default_bol = False

    entity_type = None  # will be overridden in subclass
    _additional_fields = {'entity_type'}
class AdAccountEntity(ConsoleEntityMixin, BaseModel):
    """
    Represents a single facebook ad account entity
    """

    Meta = BaseMeta(dynamodb_config.AD_ACCOUNT_ENTITY_TABLE)

    _additional_fields = {'entity_type'}

    # scope is an ephemeral scoping element
    # Imagine "operam business manager system user" being one of the scope's values.
    # This is here mostly just to simplify iterating
    # through AAs per given known source.
    # (even if originally we will have only one scope in entire system)
    # At first, scope will be pegged one-to-one to
    # one token to be used for all AAs,
    # (Later this relationship may need to be inverted to
    # allow multiple tokens per AA)
    scope = attributes.UnicodeAttribute(hash_key=True, attr_name='scope')

    ad_account_id = attributes.UnicodeAttribute(range_key=True,
                                                attr_name='aaid')

    # copied indicator of activity from Console DB per each sync
    # (alternative to deletion. To be discussed later if deletion is better)
    is_active = attributes.BooleanAttribute(default=False, attr_name='a')

    is_accessible = attributes.BooleanAttribute(default=True,
                                                attr_name='is_accessible')

    # Provides an option to manually disable accounts from syncing, even if they are imported as active from console.
    manually_disabled = attributes.BooleanAttribute(default=False,
                                                    attr_name='man_dis')

    # utilized by logic that prunes out Ad Accounts
    # that are switched to "inactive" on Console
    # Expectation is that after a long-running update job
    # there is a task at the end that goes back and marks
    # all AA records with non-last-sweep_id as "inactive"
    # See https://operam.atlassian.net/browse/PROD-1825 for context
    updated_by_sweep_id = attributes.UnicodeAttribute(null=True, attr_name='u')

    # Each AdAccount on FB side can be set to a particular timezone
    # A lot of reporting on FB is pegged to a "day" that is interpreted
    # in that AdAccount's timezone (not UTC).
    timezone = attributes.UnicodeAttribute(attr_name='tz')

    # manually controlled through Dynamo UI. Here we just read it
    # does not have to be set for majority of records.
    score_multiplier = attributes.NumberAttribute(null=True,
                                                  attr_name='score_skew')
    refresh_if_older_than = attributes.UTCDateTimeAttribute(
        null=True, attr_name='refresh_to')

    entity_type = Entity.AdAccount

    @property
    @memoized_property
    def scope_model(self):
        from common.store.scope import AssetScope

        return AssetScope.get(self.scope)

    def to_fb_sdk_ad_account(self, api=None):
        """
        Returns an instance of Facebook Ads SDK AdAccount model
        with ID matching this DB model's ID

        :param api: FB Ads SDK Api instance with token baked in.
        """
        from facebook_business.api import FacebookAdsApi, FacebookSession
        from facebook_business.adobjects.adaccount import AdAccount

        if not api:
            # This is very expensive call. Takes 2 DB hits to get token value
            # Try to pass api value at all times in prod code to avoid using this.
            # Allowing to omit the API value to simplify use of this API in
            # testing in console.
            api = FacebookAdsApi(
                FacebookSession(access_token=self.scope_model.platform_token))

        return AdAccount(fbid=f'act_{self.ad_account_id}', api=api)

    @classmethod
    def upsert_entity_from_console(cls, job_scope: JobScope, entity: Any,
                                   is_accessible: bool):
        cls.upsert(
            job_scope.entity_id,  # scope ID
            entity['ad_account_id'],
            is_active=entity.get('active', True),
            updated_by_sweep_id=job_scope.sweep_id,
            is_accessible=is_accessible,
        )