def add_detached_bank_member_signal(bank_id=None) -> BankMemberSignal: """ Add a virtual bank_member (without any associated media) and a corresponding signal. Requires JSON object with following fields: - signal_type: ["pdq"|"pdq_ocr","photo_md5"] -> anything from threatexchange.content_type.meta.get_signal_types_by_name()'s keys - content_type: ["photo"|"video"] to get the content_type for the virtual member. - signal_value: the hash to store against this signal. Will automatically de-dupe against existing signals. """ content_type = get_content_type_for_name( bottle.request.json["content_type"]) signal_type = get_signal_types_by_name()[ bottle.request.json["signal_type"]] signal_value = bottle.request.json["signal_value"] return bank_ops.add_detached_bank_member_signal( banks_table=table_manager, bank_id=bank_id, content_type=content_type, signal_type=signal_type, signal_value=signal_value, )
def from_sqs_message(cls, d: dict) -> "PipelineHashRecord": return cls( content_id=d["ContentId"], signal_type=get_signal_types_by_name()[d["SignalType"]], content_hash=d["ContentHash"], signal_specific_attributes=d["SignalSpecificAttributes"], updated_at=datetime.datetime.fromisoformat(d["UpdatedAt"]), )
def from_dynamodb_item(cls, item: t.Dict) -> "BankMemberSignal": return cls( bank_id=item["BankId"], bank_member_id=item["BankMemberId"], signal_id=item["SignalId"], signal_type=get_signal_types_by_name()[item["SignalType"]], signal_value=item["SignalValue"], updated_at=datetime.fromisoformat(item["UpdatedAt"]), )
def _result_item_to_metadata( cls, item: t.Dict, ) -> "ThreatExchangeSignalMetadata": return ThreatExchangeSignalMetadata( signal_id=cls.remove_signal_key_prefix(item["PK"], item["SignalSource"]), privacy_group_id=item["PrivacyGroup"], updated_at=datetime.datetime.fromisoformat(item["UpdatedAt"]), signal_type=get_signal_types_by_name()[item["SignalType"]], signal_hash=item["SignalHash"], tags=item["Tags"], pending_opinion_change=PendingThreatExchangeOpinionChange( item.get( "PendingThreatExchangeOpinionChange", PendingThreatExchangeOpinionChange.NONE.value, ) ), )
def _result_items_to_records( cls, items: t.List[t.Dict], ) -> t.List["MatchRecord"]: return [ MatchRecord( content_id=cls.remove_content_key_prefix(item["PK"]), content_hash=item["ContentHash"], updated_at=datetime.datetime.fromisoformat(item["UpdatedAt"]), signal_type=get_signal_types_by_name()[item["SignalType"]], signal_id=cls.remove_signal_key_prefix(item["SK"], item["SignalSource"]), signal_source=item["SignalSource"], signal_hash=item["SignalHash"], signal_specific_attributes=cls. deserialize_signal_specific_attributes(item), ) for item in items ]
def _result_items_to_records( cls, items: t.List[t.Dict], ) -> t.List["PipelineHashRecord"]: """ Get a paginated list of recent hash records. Subsequent calls must use `return_value.last_evaluated_key`. """ return [ PipelineHashRecord( content_id=item["PK"][len(cls.CONTENT_KEY_PREFIX):], signal_type=get_signal_types_by_name()[item["SignalType"]], content_hash=item["ContentHash"], updated_at=datetime.datetime.fromisoformat(item["UpdatedAt"]), signal_specific_attributes=cls. deserialize_signal_specific_attributes(item), ) for item in items ]
def get_unique(cls, table: Table, signal_type: t.Type[SignalType], signal_value: str) -> "BankedSignalEntry": """ Write to the table if PK / SK does not exist. In either case (exists, not exists), return the current unique entry. This is a special use-case for BankedSignalEntry. If this is useful to other models, we can move it to a mixin or to dynamodb item. If trying to generify, note how the update_item query needs a custom update query based on what you are trying to write. Generifying may be harder than it seems. """ result = table.update_item( Key={ "PK": cls.get_pk(signal_type), "SK": cls.get_sk(signal_value), }, UpdateExpression= "SET SignalId = if_not_exists(SignalId, :signal_id), SignalType = :signal_type, SignalValue = :signal_value", ExpressionAttributeValues={ # Note we are generating a new uuid even though we don't always # expect it to get written. AFAIK, uuids are inexhaustible, and # generation performance is good enough to not worry about it. ":signal_id": str(uuid.uuid4()), ":signal_type": signal_type.get_name(), ":signal_value": signal_value, }, ReturnValues="ALL_NEW", ).get("Attributes") assert result is not None return BankedSignalEntry( signal_type=get_signal_types_by_name()[result["SignalType"]], signal_value=t.cast(str, result["SignalValue"]), signal_id=t.cast(str, result["SignalId"]), )
def from_dict(cls, d): base = super().from_dict(d) base.signal_type = get_signal_types_by_name()[base.signal_type] return base
def from_dict(cls, d): base = cls( **{f.name: d.get(f.name, None) for f in dataclasses.fields(cls)}) base.signal_type = get_signal_types_by_name()[base.signal_type] return base