Repository: cert-orangecyberdefense/datalake (language: python)

Issue:
v2.6.5
## Added new feature: 
#### Retrieve sightings related to a threat
- From hashkey
- From atom value


Discussion / hints:

Fix patch:
diff --git a/datalake/__init__.py b/datalake/__init__.py
index a1e494d..c48a059 100644
--- a/datalake/__init__.py
+++ b/datalake/__init__.py
@@ -1,6 +1,42 @@
-from .api_objects.bulk_search_task import BulkSearchTask, BulkSearchTaskState, BulkSearchFailedError, BulkSearchNotFound
-from .common.atom import AtomType, ThreatType, OverrideType, SightingType, Visibility, SightingRelation
-from .common.atom_type import Atom, Hashes, FileAtom, AndroidApp, ApkAtom, AsAtom, CcAtom, CryptoAtom, CveAtom, Jarm, DomainAtom, EmailFlow, EmailAtom, FqdnAtom, IbanAtom, IpService, IpAtom, IpRangeAtom, PasteAtom, PhoneNumberAtom, RegKeyAtom, SslAtom, UrlAtom
+from .api_objects.bulk_search_task import (
+    BulkSearchTask,
+    BulkSearchTaskState,
+    BulkSearchFailedError,
+    BulkSearchNotFound,
+)
+from .common.atom import (
+    AtomType,
+    ThreatType,
+    OverrideType,
+    SightingType,
+    Visibility,
+    SightingRelation,
+)
+from .common.atom_type import (
+    Atom,
+    Hashes,
+    FileAtom,
+    AndroidApp,
+    ApkAtom,
+    AsAtom,
+    CcAtom,
+    CryptoAtom,
+    CveAtom,
+    Jarm,
+    DomainAtom,
+    EmailFlow,
+    EmailAtom,
+    FqdnAtom,
+    IbanAtom,
+    IpService,
+    IpAtom,
+    IpRangeAtom,
+    PasteAtom,
+    PhoneNumberAtom,
+    RegKeyAtom,
+    SslAtom,
+    UrlAtom,
+)
 from .common.ouput import Output
 
 from .datalake import Datalake
diff --git a/datalake/api_objects/bulk_search_task.py b/datalake/api_objects/bulk_search_task.py
index 5a146c8..652a76f 100644
--- a/datalake/api_objects/bulk_search_task.py
+++ b/datalake/api_objects/bulk_search_task.py
@@ -8,19 +8,19 @@
 
 
 class BulkSearchTaskState(Enum):
-    NEW = 'NEW'
-    QUEUED = 'QUEUED'
-    IN_PROGRESS = 'IN_PROGRESS'
-    DONE = 'DONE'
-    CANCELLED = 'CANCELLED'
-    FAILED_ERROR = 'FAILED_ERROR'
-    FAILED_TIMEOUT = 'FAILED_TIMEOUT'
+    NEW = "NEW"
+    QUEUED = "QUEUED"
+    IN_PROGRESS = "IN_PROGRESS"
+    DONE = "DONE"
+    CANCELLED = "CANCELLED"
+    FAILED_ERROR = "FAILED_ERROR"
+    FAILED_TIMEOUT = "FAILED_TIMEOUT"
 
 
 BULK_SEARCH_FAILED_STATE = {
     BulkSearchTaskState.CANCELLED,
     BulkSearchTaskState.FAILED_TIMEOUT,
-    BulkSearchTaskState.FAILED_ERROR
+    BulkSearchTaskState.FAILED_ERROR,
 }
 
 
@@ -40,33 +40,33 @@ class BulkSearchTask:
     This class is a thin wrapper around information returned by the API
     """
 
-    REQUEST_INTERVAL = float(os.getenv('OCD_DTL_MAX_BACK_OFF_TIME', 10))
+    REQUEST_INTERVAL = float(os.getenv("OCD_DTL_MAX_BACK_OFF_TIME", 10))
     STREAM_CHUNK_SIZE = 4096
 
     def __init__(
-            self,
-            endpoint: "BulkSearch",
-            bulk_search: dict,
-            bulk_search_hash: str,
-            created_at: str,
-            eta: str,
-            file_delete_after: str,
-            file_deleted: bool,
-            file_size: int,
-            finished_at: str,
-            progress: int,
-            queue_position: int,
-            results: int,
-            started_at: str,
-            state: str,
-            uuid: str,
-            user: dict,
+        self,
+        endpoint: "BulkSearch",
+        bulk_search: dict,
+        bulk_search_hash: str,
+        created_at: str,
+        eta: str,
+        file_delete_after: str,
+        file_deleted: bool,
+        file_size: int,
+        finished_at: str,
+        progress: int,
+        queue_position: int,
+        results: int,
+        started_at: str,
+        state: str,
+        uuid: str,
+        user: dict,
     ):
         """Do not call this method directly, use BulkSearch.create_task instead"""
         self._endpoint = endpoint
         # flatten bulk_search field
-        self.advanced_query_hash = bulk_search['advanced_query_hash']
-        self.query_fields = bulk_search['query_fields']
+        self.advanced_query_hash = bulk_search["advanced_query_hash"]
+        self.query_fields = bulk_search["query_fields"]
         self.bulk_search_hash = bulk_search_hash
         self.created_at = parse_api_timestamp(created_at)
         self.eta = parse_api_timestamp(eta)
@@ -113,13 +113,15 @@ def download_sync(self, output=Output.JSON, timeout=15 * 60, stream=False):
         """Blocking version of download_async, easier to use but doesn't allow parallelization"""
         return asyncio.run(self.download_async(output, timeout, stream=stream))
 
-    def download_sync_stream_to_file(self, output_path, output=Output.JSON, timeout=15 * 60):
+    def download_sync_stream_to_file(
+        self, output_path, output=Output.JSON, timeout=15 * 60
+    ):
         """
         Wrapper around download_sync that output the result to a file directly (reducing the memory usage).
         output_path should be an absolute path.
         """
         raw_response = self.download_sync(output=output, timeout=timeout, stream=True)
-        with open(output_path, 'wb') as output_file:  # Binary is required for zip types
+        with open(output_path, "wb") as output_file:  # Binary is required for zip types
             for chunk in raw_response.iter_content(chunk_size=self.STREAM_CHUNK_SIZE):
                 output_file.write(chunk)
 
diff --git a/datalake/common/atom.py b/datalake/common/atom.py
index 3caa136..abf7a10 100644
--- a/datalake/common/atom.py
+++ b/datalake/common/atom.py
@@ -6,38 +6,40 @@
 
 
 class AtomType(Enum):
-    APK = 'apk'
-    AS = 'as'
-    CC = 'cc'
-    CRYPTO = 'crypto'
-    CVE = 'cve'
-    DOMAIN = 'domain'
-    EMAIL = 'email'
-    FILE = 'file'
-    FQDN = 'fqdn'
-    IBAN = 'iban'
-    IP = 'ip'
-    IP_RANGE = 'ip_range'
-    PASTE = 'paste'
-    PHONE_NUMBER = 'phone_number'
-    REGKEY = 'regkey'
-    SSL = 'ssl'
-    URL = 'url'
+    APK = "apk"
+    AS = "as"
+    CC = "cc"
+    CRYPTO = "crypto"
+    CVE = "cve"
+    DOMAIN = "domain"
+    EMAIL = "email"
+    FILE = "file"
+    FQDN = "fqdn"
+    IBAN = "iban"
+    IP = "ip"
+    IP_RANGE = "ip_range"
+    PASTE = "paste"
+    PHONE_NUMBER = "phone_number"
+    REGKEY = "regkey"
+    SSL = "ssl"
+    URL = "url"
 
 
 class ThreatType(Enum):
-    DDOS = 'ddos'
-    FRAUD = 'fraud'
-    HACK = 'hack'
-    LEAK = 'leak'
-    MALWARE = 'malware'
-    PHISHING = 'phishing'
-    SCAM = 'scam'
-    SCAN = 'scan'
-    SPAM = 'spam'
+    DDOS = "ddos"
+    FRAUD = "fraud"
+    HACK = "hack"
+    LEAK = "leak"
+    MALWARE = "malware"
+    PHISHING = "phishing"
+    SCAM = "scam"
+    SCAN = "scan"
+    SPAM = "spam"
 
 
-ScoreMap = Dict[str, Union[int, ThreatType]]  # Should be replaced by a TypedDict when we drop python 3.7 support
+ScoreMap = Dict[
+    str, Union[int, ThreatType]
+]  # Should be replaced by a TypedDict when we drop python 3.7 support
 """
 Group a threat type with a score using the following keys:
 score -> int (from 0 to 100 included)
@@ -46,22 +48,22 @@ class ThreatType(Enum):
 
 
 class OverrideType(Enum):
-    PERMANENT = 'permanent'
-    TEMPORARY = 'temporary'
-    LOCK = 'lock'
+    PERMANENT = "permanent"
+    TEMPORARY = "temporary"
+    LOCK = "lock"
 
 
 class SightingType(Enum):
-    POSITIVE = 'positive'
-    NEGATIVE = 'negative'
-    NEUTRAL = 'neutral'
+    POSITIVE = "positive"
+    NEGATIVE = "negative"
+    NEUTRAL = "neutral"
 
 
 class Visibility(Enum):
-    PUBLIC = 'PUBLIC'
-    ORGANIZATION = 'ORGANIZATION'
+    PUBLIC = "PUBLIC"
+    ORGANIZATION = "ORGANIZATION"
 
 
 class SightingRelation(Enum):
-    SIGHTING = 'sighting'
-    DNS = 'dns'
+    SIGHTING = "sighting"
+    DNS = "dns"
diff --git a/datalake/common/atom_type.py b/datalake/common/atom_type.py
index 792daef..bf378a5 100644
--- a/datalake/common/atom_type.py
+++ b/datalake/common/atom_type.py
@@ -9,6 +9,7 @@ class Atom:
     """
     Base class for atom types.
     """
+
     pass
 
     def _factory(self, data):
@@ -33,9 +34,11 @@ def _sightings_factory(self, data):
                 else:
                     removed = True
         if removed:
-            Warn.warning("Some keys aren't allowed for sightings and thus will be removed if you have set them. Check "
-                         "the classes for information on which keys are allowed. To stop this warning from showing, "
-                         "please set the IGNORE_SIGHTING_BUILDER_WARNING environment variable to True")
+            Warn.warning(
+                "Some keys aren't allowed for sightings and thus will be removed if you have set them. Check "
+                "the classes for information on which keys are allowed. To stop this warning from showing, "
+                "please set the IGNORE_SIGHTING_BUILDER_WARNING environment variable to True"
+            )
         return fact_dict
 
     def generate_atom_json(self, for_sightings=False):
@@ -44,8 +47,10 @@ def generate_atom_json(self, for_sightings=False):
         """
         prefix = self._get_sightings_prefix()
         if for_sightings:
-            return {f'{prefix}_list': [asdict(self, dict_factory=self._sightings_factory)]}
-        return {f'{prefix}_content': asdict(self, dict_factory=self._factory)}
+            return {
+                f"{prefix}_list": [asdict(self, dict_factory=self._sightings_factory)]
+            }
+        return {f"{prefix}_content": asdict(self, dict_factory=self._factory)}
 
 
 @dataclass
@@ -53,6 +58,7 @@ class Hashes:
     """
     At least one value is required
     """
+
     md5: str = None
     sha1: str = None
     sha256: str = None
@@ -65,6 +71,7 @@ class FileAtom(Atom):
     """
     Allowed sighting key: ['hashes', 'md5', 'sha1', 'sha256', 'sha512']
     """
+
     hashes: Hashes
     external_analysis_link: List[str] = None
     filesize: int = None
@@ -75,10 +82,10 @@ class FileAtom(Atom):
     filepath: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['hashes', 'md5', 'sha1', 'sha256', 'sha512']
+        return ["hashes", "md5", "sha1", "sha256", "sha512"]
 
     def _get_sightings_prefix(self):
-        return 'file'
+        return "file"
 
 
 @dataclass
@@ -95,6 +102,7 @@ class ApkAtom(Atom):
     """
     Allowed sighting key: ['android', 'package_name', 'version_name', 'hashes', 'md5', 'sha1', 'sha256', 'sha512']
     """
+
     android: AndroidApp
     hashes: Hashes
     external_analysis_link: List[str] = None
@@ -106,10 +114,19 @@ class ApkAtom(Atom):
     filepath: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['android', 'package_name', 'version_name', 'hashes', 'md5', 'sha1', 'sha256', 'sha512']
+        return [
+            "android",
+            "package_name",
+            "version_name",
+            "hashes",
+            "md5",
+            "sha1",
+            "sha256",
+            "sha512",
+        ]
 
     def _get_sightings_prefix(self):
-        return 'apk'
+        return "apk"
 
 
 @dataclass
@@ -117,6 +134,7 @@ class AsAtom(Atom):
     """
     Allowed sighting key: ['asn']
     """
+
     asn: int
     external_analysis_link: List[str] = None
     allocation_date: str = None
@@ -126,10 +144,10 @@ class AsAtom(Atom):
     registry: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['asn']
+        return ["asn"]
 
     def _get_sightings_prefix(self):
-        return 'as'
+        return "as"
 
 
 @dataclass
@@ -138,6 +156,7 @@ class CcAtom(Atom):
     Allowed sighting key: ['number']
     number: minLength = 8  maxLength = 19
     """
+
     number: str
     external_analysis_link: List[str] = None
     bin: int = None
@@ -147,10 +166,10 @@ class CcAtom(Atom):
     expiry_date: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['number']
+        return ["number"]
 
     def _get_sightings_prefix(self):
-        return 'cc'
+        return "cc"
 
 
 @dataclass
@@ -158,6 +177,7 @@ class CryptoAtom(Atom):
     """
     Allowed sighting key: ['crypto_address', 'crypto_network']
     """
+
     """
     first_used and last_used expect the following datetime format: '%Y-%m-%dT%H:%M:%SZ'
     """
@@ -171,10 +191,10 @@ class CryptoAtom(Atom):
     last_used: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['crypto_address', 'crypto_network']
+        return ["crypto_address", "crypto_network"]
 
     def _get_sightings_prefix(self):
-        return 'crypto'
+        return "crypto"
 
 
 @dataclass
@@ -182,6 +202,7 @@ class CveAtom(Atom):
     """
     Allowed sighting key: ['cve_id']
     """
+
     """
     published_at expect the following datetime format: '%Y-%m-%dT%H:%M:%SZ'
     """
@@ -192,10 +213,10 @@ class CveAtom(Atom):
     published_at: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['cve_id']
+        return ["cve_id"]
 
     def _get_sightings_prefix(self):
-        return 'cve'
+        return "cve"
 
 
 @dataclass
@@ -211,21 +232,22 @@ class DomainAtom(Atom):
     """
     Allowed sighting key: ['domain']
     """
+
     domain: str
     external_analysis_link: List[str] = None
     malware_family: str = None
     jarm: Jarm = None
 
     def _get_sightings_allowed_keys(self):
-        return ['domain']
+        return ["domain"]
 
     def _get_sightings_prefix(self):
-        return 'domain'
+        return "domain"
 
 
 class EmailFlow:
-    TO = 'to'
-    FROM = 'from'
+    TO = "to"
+    FROM = "from"
 
 
 @dataclass
@@ -233,15 +255,16 @@ class EmailAtom(Atom):
     """
     Allowed sighting key: ['email']
     """
+
     email: str
     email_flow: EmailFlow = None
     external_analysis_link: List[str] = None
 
     def _get_sightings_allowed_keys(self):
-        return ['email']
+        return ["email"]
 
     def _get_sightings_prefix(self):
-        return 'email'
+        return "email"
 
 
 @dataclass
@@ -249,6 +272,7 @@ class FqdnAtom(Atom):
     """
     Allowed sighting key: ['fqdn']
     """
+
     fqdn: str
     jarm: Jarm = None
     malware_family: str = None
@@ -257,10 +281,10 @@ class FqdnAtom(Atom):
     external_analysis_link: List[str] = None
 
     def _get_sightings_allowed_keys(self):
-        return ['fqdn']
+        return ["fqdn"]
 
     def _get_sightings_prefix(self):
-        return 'fqdn'
+        return "fqdn"
 
 
 @dataclass
@@ -268,6 +292,7 @@ class IbanAtom(Atom):
     """
     Allowed sighting key: ['iban']
     """
+
     iban: str
     holder_name: str = None
     holder_address: str = None
@@ -277,10 +302,10 @@ class IbanAtom(Atom):
     bank_address: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['iban']
+        return ["iban"]
 
     def _get_sightings_prefix(self):
-        return 'iban'
+        return "iban"
 
 
 @dataclass
@@ -296,6 +321,7 @@ class IpAtom(Atom):
     """
     Allowed sighting key: ['ip_address']
     """
+
     ip_address: str
     external_analysis_link: List[str] = None
     hostname: str = None
@@ -307,10 +333,10 @@ class IpAtom(Atom):
     services: List[IpService] = None
 
     def _get_sightings_allowed_keys(self):
-        return ['ip_address']
+        return ["ip_address"]
 
     def _get_sightings_prefix(self):
-        return 'ip'
+        return "ip"
 
 
 @dataclass
@@ -318,6 +344,7 @@ class IpRangeAtom(Atom):
     """
     Allowed sighting key: ['cidr']
     """
+
     cidr: str
     country: str = None
     allocation_date: str = None
@@ -327,10 +354,10 @@ class IpRangeAtom(Atom):
     registry: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['cidr']
+        return ["cidr"]
 
     def _get_sightings_prefix(self):
-        return 'ip_range'
+        return "ip_range"
 
 
 @dataclass
@@ -338,6 +365,7 @@ class PasteAtom(Atom):
     """
     Allowed sighting key: ['url']
     """
+
     url: str
     author: str = None
     title: str = None
@@ -345,10 +373,10 @@ class PasteAtom(Atom):
     external_analysis_link: List[str] = None
 
     def _get_sightings_allowed_keys(self):
-        return ['url']
+        return ["url"]
 
     def _get_sightings_prefix(self):
-        return 'paste'
+        return "paste"
 
 
 @dataclass
@@ -356,6 +384,7 @@ class PhoneNumberAtom(Atom):
     """
     Allowed sighting key: ['international_phone_number', 'national_phone_number']
     """
+
     """
     country: minLength = 2  maxLength = 2
     """
@@ -366,10 +395,10 @@ class PhoneNumberAtom(Atom):
     national_phone_number: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['international_phone_number', 'national_phone_number']
+        return ["international_phone_number", "national_phone_number"]
 
     def _get_sightings_prefix(self):
-        return 'phone_number'
+        return "phone_number"
 
 
 @dataclass
@@ -377,16 +406,17 @@ class RegKeyAtom(Atom):
     """
     Allowed sighting key: ['path']
     """
+
     path: str
     regkey_value: str = None
     hive: str = None
     external_analysis_link: List[str] = None
 
     def _get_sightings_allowed_keys(self):
-        return ['path']
+        return ["path"]
 
     def _get_sightings_prefix(self):
-        return 'regkey'
+        return "regkey"
 
 
 @dataclass
@@ -394,6 +424,7 @@ class SslAtom(Atom):
     """
     Allowed sighting key: ['hashes', 'md5', 'sha1', 'sha256', 'sha512']
     """
+
     hashes: Hashes
     issuer: str = None
     public_key: str = None
@@ -406,10 +437,10 @@ class SslAtom(Atom):
     external_analysis_link: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['hashes', 'md5', 'sha1', 'sha256', 'sha512']
+        return ["hashes", "md5", "sha1", "sha256", "sha512"]
 
     def _get_sightings_prefix(self):
-        return 'ssl'
+        return "ssl"
 
 
 @dataclass
@@ -417,6 +448,7 @@ class UrlAtom(Atom):
     """
     Allowed sighting key: ['url']
     """
+
     url: str
     malware_family: str = None
     jarm: Jarm = None
@@ -426,7 +458,7 @@ class UrlAtom(Atom):
     reason: str = None
 
     def _get_sightings_allowed_keys(self):
-        return ['url']
+        return ["url"]
 
     def _get_sightings_prefix(self):
-        return 'url'
+        return "url"
diff --git a/datalake/common/config.py b/datalake/common/config.py
index b1870bc..54dd00d 100644
--- a/datalake/common/config.py
+++ b/datalake/common/config.py
@@ -6,7 +6,9 @@
 
 class Config:
 
-    _CONFIG_ENDPOINTS = os.path.join(FOLDER_ABSOLUTE_PATH, '..', 'config', 'endpoints.json')
+    _CONFIG_ENDPOINTS = os.path.join(
+        FOLDER_ABSOLUTE_PATH, "..", "config", "endpoints.json"
+    )
 
     def load_config(self):
         """
@@ -14,5 +16,5 @@ def load_config(self):
 
         :return (dict, str, list<str, str>)
         """
-        with open(self._CONFIG_ENDPOINTS, 'r') as config:
+        with open(self._CONFIG_ENDPOINTS, "r") as config:
             return json.load(config)
diff --git a/datalake/common/logger.py b/datalake/common/logger.py
index f60b5ee..e2a158d 100644
--- a/datalake/common/logger.py
+++ b/datalake/common/logger.py
@@ -2,8 +2,8 @@
 
 logger = logging.getLogger("OCD_DTL")
 
-DEBUG_FORMAT = '%(name)s:%(levelname)-4s %(message)s'
-INFO_FORMAT = '%(message)s'
+DEBUG_FORMAT = "%(name)s:%(levelname)-4s %(message)s"
+INFO_FORMAT = "%(message)s"
 
 
 def configure_logging(loglevel: int):
diff --git a/datalake/common/ouput.py b/datalake/common/ouput.py
index 31a9da6..e2f8f76 100644
--- a/datalake/common/ouput.py
+++ b/datalake/common/ouput.py
@@ -5,13 +5,13 @@
 
 
 class Output(Enum):
-    JSON = 'application/json'
-    CSV = 'text/csv'
-    MISP = 'application/x-misp+json'
-    STIX = 'application/stix+json'
-    JSON_ZIP = 'application/zip'
-    CSV_ZIP = 'text/x-csv-zip'
-    STIX_ZIP = 'text/x-stix-zip'
+    JSON = "application/json"
+    CSV = "text/csv"
+    MISP = "application/x-misp+json"
+    STIX = "application/stix+json"
+    JSON_ZIP = "application/zip"
+    CSV_ZIP = "text/x-csv-zip"
+    STIX_ZIP = "text/x-stix-zip"
 
     def __str__(self):
         return self.name
@@ -22,24 +22,32 @@ def __repr__(self):
 
 def parse_response(response: Response) -> Union[str, dict]:
     """Parse a Request.Response depending of the Content-Type returned"""
-    content_type = response.headers.get('Content-Type', Output.JSON.value)
-    content_type = content_type.split(';')[0]  # we don't care about extra info on the content
-    if content_type in {output.value for output in [Output.CSV, Output.CSV_ZIP, Output.JSON_ZIP]}:
+    content_type = response.headers.get("Content-Type", Output.JSON.value)
+    content_type = content_type.split(";")[
+        0
+    ]  # we don't care about extra info on the content
+    if content_type in {
+        output.value for output in [Output.CSV, Output.CSV_ZIP, Output.JSON_ZIP]
+    }:
         return response.text
     else:
         return response.json()
 
 
 def display_outputs(outputs):
-    return ', '.join(sorted([str(output) for output in outputs]))
+    return ", ".join(sorted([str(output) for output in outputs]))
 
 
 def output_supported(outputs: Set[Output]):
     def inner_decorator(function):
         def wrapper(*args, **kwargs):
-            if kwargs.get('output') and kwargs['output'] not in outputs:
-                raise ValueError(f'{kwargs["output"]} output type is not supported. '
-                                 f'Outputs supported are: {display_outputs(outputs)}')
+            if kwargs.get("output") and kwargs["output"] not in outputs:
+                raise ValueError(
+                    f'{kwargs["output"]} output type is not supported. '
+                    f"Outputs supported are: {display_outputs(outputs)}"
+                )
             return function(*args, **kwargs)
+
         return wrapper
+
     return inner_decorator
diff --git a/datalake/common/throttler.py b/datalake/common/throttler.py
index 69775b4..5703325 100644
--- a/datalake/common/throttler.py
+++ b/datalake/common/throttler.py
@@ -23,8 +23,10 @@ def wrapped(*args, **kwargs):
                 previous_call.pop(0)
             # Check if the number of call for the period don't allow the function to be called
             if len(previous_call) >= call_per_period:
-                time_since_first_call = (call_time - previous_call[0])
-                sleep(max(period - time_since_first_call, 0.1))  # Wait until a call has been made 'period' ago
+                time_since_first_call = call_time - previous_call[0]
+                sleep(
+                    max(period - time_since_first_call, 0.1)
+                )  # Wait until a call has been made 'period' ago
                 assert previous_call[0] + period <= time()
                 previous_call.pop(0)
             previous_call.append(time())
@@ -37,7 +39,7 @@ def wrapped(*args, **kwargs):
     return inner_decorator
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     """Example on how to use the throttle wrapper"""
     random.seed(0)
     PERIOD = 1
@@ -48,9 +50,8 @@ def f():
         sleep(random.randint(0, 2))
         return "hello world"
 
-
     start = time()
     total_calls = 10
     for i in range(total_calls):
-        print(f'{f()} {i} {time()}')
+        print(f"{f()} {i} {time()}")
     assert time() > start + ((total_calls / CALL_PER_PERIOD) - 1) * PERIOD
diff --git a/datalake/common/token_manager.py b/datalake/common/token_manager.py
index 430d093..8d27929 100644
--- a/datalake/common/token_manager.py
+++ b/datalake/common/token_manager.py
@@ -17,13 +17,19 @@ class TokenManager:
     Use it to generate token access to the API
     """
 
-    def __init__(self, endpoint_config: dict, *, environment: str, username=None, password=None):
+    def __init__(
+        self, endpoint_config: dict, *, environment: str, username=None, password=None
+    ):
         """environment can be either prod or preprod"""
-        base_url = urljoin(endpoint_config['main'][environment], endpoint_config['api_version'])
-        endpoints = endpoint_config['endpoints']
+        base_url = urljoin(
+            endpoint_config["main"][environment], endpoint_config["api_version"]
+        )
+        endpoints = endpoint_config["endpoints"]
 
-        self.url_token = urljoin(base_url, endpoints['token'], allow_fragments=True)
-        self.url_refresh = urljoin(base_url, endpoints['refresh_token'], allow_fragments=True)
+        self.url_token = urljoin(base_url, endpoints["token"], allow_fragments=True)
+        self.url_refresh = urljoin(
+            base_url, endpoints["refresh_token"], allow_fragments=True
+        )
 
         self.username = username
         self.password = password
@@ -35,10 +41,12 @@ def get_token(self):
         """
         Generate token from user input, with email and password
         """
-        self.username = self.username or os.getenv('OCD_DTL_USERNAME') or input('Email: ')
-        self.password = self.password or os.getenv('OCD_DTL_PASSWORD') or getpass()
+        self.username = (
+            self.username or os.getenv("OCD_DTL_USERNAME") or input("Email: ")
+        )
+        self.password = self.password or os.getenv("OCD_DTL_PASSWORD") or getpass()
         print()
-        data = {'email': self.username, 'password': self.password}
+        data = {"email": self.username, "password": self.password}
 
         response = requests.post(url=self.url_token, json=data)
         json_response = json.loads(response.text)
@@ -46,26 +54,33 @@ def get_token(self):
             self.access_token = f'Token {json_response["access_token"]}'
             self.refresh_token = f'Token {json_response["refresh_token"]}'
         except KeyError:
-            logger.error(f'An error occurred while retrieving an access token, for URL: {self.url_token}\n'
-                         f'response of the API: {response.text}')
-            raise ValueError(f'Could not login: {response.text}')
+            logger.error(
+                f"An error occurred while retrieving an access token, for URL: {self.url_token}\n"
+                f"response of the API: {response.text}"
+            )
+            raise ValueError(f"Could not login: {response.text}")
 
     def fetch_new_token(self):
-        logger.debug('Token will be refreshed')
-        headers = {'Authorization': self.refresh_token}
+        logger.debug("Token will be refreshed")
+        headers = {"Authorization": self.refresh_token}
         response = requests.post(url=self.url_refresh, headers=headers)
 
         json_response = response.json()
-        if response.status_code == 401 and json_response.get('msg') == 'Token has expired':
-            logger.info('Refreshing the refresh token')
+        if (
+            response.status_code == 401
+            and json_response.get("msg") == "Token has expired"
+        ):
+            logger.info("Refreshing the refresh token")
             # Refresh token is also expired, we need to restart the authentication from scratch
             self.get_token()
-        elif 'access_token' in json_response:
+        elif "access_token" in json_response:
             self.access_token = f'Token {json_response["access_token"]}'
         else:  # an error occurred
-            logger.error(f'An error occurred while refreshing the refresh token, for URL: {self.url_refresh}\n'
-                         f'response of the API: {response.text}')
-            raise ValueError(f'Could not refresh the token: {response.text}')
+            logger.error(
+                f"An error occurred while refreshing the refresh token, for URL: {self.url_refresh}\n"
+                f"response of the API: {response.text}"
+            )
+            raise ValueError(f"Could not refresh the token: {response.text}")
 
     def process_auth_error(self, json_resp: dict):
         """
@@ -73,12 +88,12 @@ def process_auth_error(self, json_resp: dict):
         """
         error_msg = get_error_message(json_resp)
         if error_msg in (
-                'Missing Authorization Header',
-                "Bad Authorization header. Expected value 'Token <JWT>'",
-                "Missing 'Token' type in 'Authorization' header. Expected 'Authorization: Token <JWT>'",
+            "Missing Authorization Header",
+            "Bad Authorization header. Expected value 'Token <JWT>'",
+            "Missing 'Token' type in 'Authorization' header. Expected 'Authorization: Token <JWT>'",
         ):
             self.get_token()
-        elif error_msg == 'Token has expired':
+        elif error_msg == "Token has expired":
             self.fetch_new_token()
         else:
-            raise ValueError(f'Unexpected msg: {error_msg}')
+            raise ValueError(f"Unexpected msg: {error_msg}")
diff --git a/datalake/common/utils.py b/datalake/common/utils.py
index d21bd48..0acdd55 100644
--- a/datalake/common/utils.py
+++ b/datalake/common/utils.py
@@ -21,7 +21,7 @@ def aggregate_csv_or_json_api_response(aggregated_response, response):
     if isinstance(response, dict):  # json response
         aggregated_response = join_dicts(aggregated_response, response)
     else:  # csv response
-        csv_lines = response.strip().split('\n')
+        csv_lines = response.strip().split("\n")
         if not aggregated_response:
             aggregated_response = [csv_lines[0]]
         aggregated_response += csv_lines[1:]
@@ -30,14 +30,16 @@ def aggregate_csv_or_json_api_response(aggregated_response, response):
 
 def parse_api_timestamp(timestamp: str) -> Optional[datetime.datetime]:
     if timestamp:
-        timestamp = timestamp.split('+')[0]  # Discard the offset as it should be 0 and is not supported by python 3.6
-        return datetime.datetime.strptime(timestamp, '%Y-%m-%dT%H:%M:%S.%f')
+        timestamp = timestamp.split("+")[
+            0
+        ]  # Discard the offset as it should be 0 and is not supported by python 3.6
+        return datetime.datetime.strptime(timestamp, "%Y-%m-%dT%H:%M:%S.%f")
     return None
 
 
 def split_list(list_to_split: list, slice_size: int) -> Generator[list, None, None]:
     for i in range(0, len(list_to_split), slice_size):
-        yield list_to_split[i:i + slice_size]
+        yield list_to_split[i : i + slice_size]
 
 
 def get_error_message(resp_body: dict):
diff --git a/datalake/common/warn.py b/datalake/common/warn.py
index 6ef0899..210af97 100644
--- a/datalake/common/warn.py
+++ b/datalake/common/warn.py
@@ -5,5 +5,9 @@
 class Warn:
     @staticmethod
     def warning(message):
-        if not os.environ.get('IGNORE_SIGHTING_BUILDER_WARNING', 'False').lower() in ('true', '1', 't'):
+        if not os.environ.get("IGNORE_SIGHTING_BUILDER_WARNING", "False").lower() in (
+            "true",
+            "1",
+            "t",
+        ):
             warnings.warn(message)
diff --git a/datalake/datalake.py b/datalake/datalake.py
index a927e5c..4bdd55e 100644
--- a/datalake/datalake.py
+++ b/datalake/datalake.py
@@ -1,6 +1,6 @@
 import logging
 
-from datalake import AtomType
+from datalake import AtomType, SightingType, Visibility, Output
 from datalake.common.config import Config
 from datalake.common.logger import configure_logging
 from datalake.common.token_manager import TokenManager
@@ -12,20 +12,79 @@
 
 
 class Datalake:
-    """ Entrypoint to the Datalake library
+    """Entrypoint to the Datalake library
 
     Usage:
     >>> dtl = Datalake(username='some username', password='some password')
     >>> dtl.Threats.lookup(atom_value='mayoclinic.org', atom_type=AtomType.DOMAIN, hashkey_only=False)
     """
 
-    def __init__(self, username: str = None, password: str = None, env='prod', log_level=logging.WARNING):
+    def __init__(
+        self,
+        username: str = None,
+        password: str = None,
+        env="prod",
+        log_level=logging.WARNING,
+    ):
         configure_logging(log_level)
         endpoint_config = Config().load_config()
-        token_manager = TokenManager(endpoint_config, environment=env, username=username, password=password)
+        token_manager = TokenManager(
+            endpoint_config, environment=env, username=username, password=password
+        )
 
         self.Threats = Threats(endpoint_config, env, token_manager)
         self.BulkSearch = BulkSearch(endpoint_config, env, token_manager)
         self.Tags = Tags(endpoint_config, env, token_manager)
         self.AdvancedSearch = AdvancedSearch(endpoint_config, env, token_manager)
         self.Sightings = Sightings(endpoint_config, env, token_manager)
+
+    def sightings_filtered_from_atom_value(
+        self,
+        atom_value: str,
+        limit: int = None,
+        offset: int = None,
+        ordering: str = None,
+        organization_id: int = None,
+        organization_name: str = None,
+        start_timestamp_date: str = None,
+        end_timestamp_date: str = None,
+        tags: list = None,
+        sighting_type: SightingType = None,
+        visibility: Visibility = None,
+    ):
+
+        atom_extract = self.Threats.atom_values_extract([atom_value])
+
+        if atom_extract["found"] == 0:
+            raise ValueError(f"No atom found for atom value: {atom_value}")
+        if atom_extract["found"] > 1:
+            raise ValueError(f"Multiple atoms found for atom value: {atom_value}")
+
+        atom_detail = {}
+        for k, v in atom_extract["results"].items():
+            atom_detail["atom_type"] = AtomType(k)
+            atom_detail["atom_value"] = v[0]
+        resp = self.Threats.bulk_lookup(
+            atom_values=[atom_detail["atom_value"]],
+            atom_type=atom_detail["atom_type"],
+            hashkey_only=True,
+            output=Output.JSON,
+            return_search_hashkey=False,
+        )
+        if not resp[atom_detail["atom_type"].value][0]["threat_found"]:
+            raise ValueError(f"No threat found for atom value: {atom_value}")
+        hashkey = resp[atom_detail["atom_type"].value][0]["hashkey"]
+
+        return self.Sightings.sightings_filtered(
+            hashkey,
+            limit,
+            offset,
+            ordering,
+            organization_id,
+            organization_name,
+            start_timestamp_date,
+            end_timestamp_date,
+            tags,
+            sighting_type,
+            visibility,
+        )
diff --git a/datalake/endpoints/advanced_search.py b/datalake/endpoints/advanced_search.py
index dc45cbc..9c4ad24 100644
--- a/datalake/endpoints/advanced_search.py
+++ b/datalake/endpoints/advanced_search.py
@@ -5,12 +5,26 @@
 
 
 class AdvancedSearch(Endpoint):
-    ordering_list = ['first_seen', '-first_seen', 'last_updated', '-last_updated', 'events_count', '-events_count',
-                     'sources_count', '-sources_count']
+    ordering_list = [
+        "first_seen",
+        "-first_seen",
+        "last_updated",
+        "-last_updated",
+        "events_count",
+        "-events_count",
+        "sources_count",
+        "-sources_count",
+    ]
 
     @output_supported({Output.JSON, Output.STIX, Output.MISP, Output.CSV})
-    def advanced_search_from_query_body(self, query_body: dict, limit: int = 20, offset: int = 0, 
-                                        ordering: List[str] = None, output=Output.JSON):
+    def advanced_search_from_query_body(
+        self,
+        query_body: dict,
+        limit: int = 20,
+        offset: int = 0,
+        ordering: List[str] = None,
+        output=Output.JSON,
+    ):
         if not query_body:
             raise ValueError("query_body is required")
         if ordering and not set(ordering).issubset(self.ordering_list):
@@ -18,31 +32,43 @@ def advanced_search_from_query_body(self, query_body: dict, limit: int = 20, off
                 f'ordering needs to be a list of at least one of the following str : {", ".join(self.ordering_list)}'
             )
         body = {
-            'query_body': query_body,
-            'limit': limit,
-            'offset': offset,
+            "query_body": query_body,
+            "limit": limit,
+            "offset": offset,
         }
         if ordering:
-            body['ordering'] = ordering
-        url = self._build_url_for_endpoint('advanced-search')
-        response = self.datalake_requests(url, 'post', post_body=body, headers=self._post_headers(output=output))
+            body["ordering"] = ordering
+        url = self._build_url_for_endpoint("advanced-search")
+        response = self.datalake_requests(
+            url, "post", post_body=body, headers=self._post_headers(output=output)
+        )
         return parse_response(response)
 
     @output_supported({Output.JSON, Output.STIX, Output.MISP, Output.CSV})
-    def advanced_search_from_query_hash(self, query_hash, limit: int = 20, offset: int = 0,
-                                        ordering: List[str] = None, output=Output.JSON):
+    def advanced_search_from_query_hash(
+        self,
+        query_hash,
+        limit: int = 20,
+        offset: int = 0,
+        ordering: List[str] = None,
+        output=Output.JSON,
+    ):
         if not query_hash:
             raise ValueError("query_hash is required")
         if ordering and not set(ordering).issubset(self.ordering_list):
             raise ValueError(
                 f'ordering needs to be a list of at least one of the following str : {", ".join(self.ordering_list)}'
             )
-        url = self._build_url_for_endpoint('advanced-search-hash').format(query_hash=query_hash)
-        params = {'limit': limit, 'offset': offset}
+        url = self._build_url_for_endpoint("advanced-search-hash").format(
+            query_hash=query_hash
+        )
+        params = {"limit": limit, "offset": offset}
         if ordering:
-            params['ordering'] = ordering
+            params["ordering"] = ordering
         req = PreparedRequest()
         req.prepare_url(url, params)
         url = req.url
-        response = self.datalake_requests(url, 'get', headers=self._get_headers(output=output))
+        response = self.datalake_requests(
+            url, "get", headers=self._get_headers(output=output)
+        )
         return parse_response(response)
diff --git a/datalake/endpoints/bulk_search.py b/datalake/endpoints/bulk_search.py
index b8c7d63..a907421 100644
--- a/datalake/endpoints/bulk_search.py
+++ b/datalake/endpoints/bulk_search.py
@@ -9,50 +9,64 @@
 
 
 class BulkSearch(Endpoint):
-
     def create_task(
-            self,
-            for_stix_export: bool = False,
-            query_body: dict = None,
-            query_hash: str = None,
-            query_fields: list = None
+        self,
+        for_stix_export: bool = False,
+        query_body: dict = None,
+        query_hash: str = None,
+        query_fields: list = None,
     ) -> BulkSearchTask:
         if not query_body and not query_hash:
             raise ValueError("Either a query_body or query_hash is required")
 
         body = {"query_fields": query_fields} if query_fields else {}
         if query_body:
-            body['query_body'] = query_body
+            body["query_body"] = query_body
         else:
-            body['query_hash'] = query_hash
+            body["query_hash"] = query_hash
         if for_stix_export:
-            body['for_stix_export'] = for_stix_export
+            body["for_stix_export"] = for_stix_export
 
-        url = self._build_url_for_endpoint('bulk-search')
-        response = self.datalake_requests(url, 'post', post_body=body, headers=self._post_headers()).json()
-        return self.get_task(response['task_uuid'])
+        url = self._build_url_for_endpoint("bulk-search")
+        response = self.datalake_requests(
+            url, "post", post_body=body, headers=self._post_headers()
+        ).json()
+        return self.get_task(response["task_uuid"])
 
     def get_task(self, task_uuid) -> BulkSearchTask:
         url = self._build_url_for_endpoint("bulk-search-task")
         body = {"task_uuid": task_uuid}
-        response = self.datalake_requests(url, 'post', post_body=body, headers=self._post_headers()).json()
-        results = response['results']
+        response = self.datalake_requests(
+            url, "post", post_body=body, headers=self._post_headers()
+        ).json()
+        results = response["results"]
         if len(results) != 1:
             raise BulkSearchNotFound
         bs_as_json = results[0]
         return BulkSearchTask(endpoint=self, **bs_as_json)
 
-    @output_supported({Output.JSON, Output.JSON_ZIP, Output.STIX, Output.STIX_ZIP, Output.CSV, Output.CSV_ZIP})
+    @output_supported(
+        {
+            Output.JSON,
+            Output.JSON_ZIP,
+            Output.STIX,
+            Output.STIX_ZIP,
+            Output.CSV,
+            Output.CSV_ZIP,
+        }
+    )
     def download(self, task_uuid, output=Output.JSON, stream=False):
         """
         Download the bulk search task with the given uuid.
         Stream parameter enables the raw stream to be returned, allowing it to be processed by chunks.
         """
-        url = self._build_url_for_endpoint('retrieve-bulk-search')
+        url = self._build_url_for_endpoint("retrieve-bulk-search")
         url = url.format(task_uuid=task_uuid)
-        response: Response = self.datalake_requests(url, 'get', headers=self._get_headers(output=output), stream=stream)
+        response: Response = self.datalake_requests(
+            url, "get", headers=self._get_headers(output=output), stream=stream
+        )
         if response.status_code == 202:
-            raise ResponseNotReady(response.json().get('message', ''))
+            raise ResponseNotReady(response.json().get("message", ""))
         if stream:
             return response  # Return the raw response
         return parse_response(response)
diff --git a/datalake/endpoints/endpoint.py b/datalake/endpoints/endpoint.py
index 89610a3..94ee181 100644
--- a/datalake/endpoints/endpoint.py
+++ b/datalake/endpoints/endpoint.py
@@ -17,13 +17,19 @@
 
 
 class Endpoint:
-    OCD_DTL_QUOTA_TIME = int(os.getenv('OCD_DTL_QUOTA_TIME', 1))
-    OCD_DTL_REQUESTS_PER_QUOTA_TIME = int(os.getenv('OCD_DTL_REQUESTS_PER_QUOTA_TIME', 5))
-    logger.debug(f'Throttle selected: {OCD_DTL_REQUESTS_PER_QUOTA_TIME} queries per {OCD_DTL_QUOTA_TIME}s')
+    OCD_DTL_QUOTA_TIME = int(os.getenv("OCD_DTL_QUOTA_TIME", 1))
+    OCD_DTL_REQUESTS_PER_QUOTA_TIME = int(
+        os.getenv("OCD_DTL_REQUESTS_PER_QUOTA_TIME", 5)
+    )
+    logger.debug(
+        f"Throttle selected: {OCD_DTL_REQUESTS_PER_QUOTA_TIME} queries per {OCD_DTL_QUOTA_TIME}s"
+    )
 
     SET_MAX_RETRY = 3
 
-    def __init__(self, endpoint_config: dict, environment: str, token_manager: TokenManager):
+    def __init__(
+        self, endpoint_config: dict, environment: str, token_manager: TokenManager
+    ):
         self.endpoint_config = endpoint_config
         self.environment = environment
         self.terminal_size = self._get_terminal_size()
@@ -46,12 +52,12 @@ def _get_terminal_size() -> int:
         call_per_period=OCD_DTL_REQUESTS_PER_QUOTA_TIME,
     )
     def datalake_requests(
-            self,
-            url: str,
-            method: str,
-            headers: dict,
-            post_body: dict = None,
-            stream=False,
+        self,
+        url: str,
+        method: str,
+        headers: dict,
+        post_body: dict = None,
+        stream=False,
     ) -> Response:
         """
         Use it to request the API
@@ -59,16 +65,20 @@ def datalake_requests(
         tries_left = self.SET_MAX_RETRY
 
         while tries_left > 0:
-            headers['Authorization'] = self.token_manager.access_token
+            headers["Authorization"] = self.token_manager.access_token
             logger.debug(self._pretty_debug_request(url, method, post_body, headers))
 
-            response = self._send_request(url, method, headers, post_body, stream=stream)
+            response = self._send_request(
+                url, method, headers, post_body, stream=stream
+            )
 
             if logger.isEnabledFor(logging.DEBUG):
                 # Don't compute response.text var if not needed, especially for streaming response
-                logger.debug('API response:\n%s', response.text)
+                logger.debug("API response:\n%s", response.text)
             if response.status_code == 401:
-                logger.warning('Token expired or Missing authorization header. Updating token')
+                logger.warning(
+                    "Token expired or Missing authorization header. Updating token"
+                )
                 self.token_manager.process_auth_error(response.json())
             elif response.status_code == 422:
                 json_resp = response.json()
@@ -76,67 +86,76 @@ def datalake_requests(
                     error_msg = get_error_message(json_resp)
                 except ValueError:
                     error_msg = response.text
-                raise ValueError(f'422 HTTP code: {error_msg}')
+                raise ValueError(f"422 HTTP code: {error_msg}")
             elif response.status_code < 200 or response.status_code > 299:
                 logger.error(
-                    f'API returned non 2xx response code : {response.status_code}\n{response.text}\n Retrying'
+                    f"API returned non 2xx response code : {response.status_code}\n{response.text}\n Retrying"
                 )
             else:
                 return response
             tries_left -= 1
-        logger.error('Request failed')
-        raise ValueError(f'{response.status_code}: {response.text.strip()}')
+        logger.error("Request failed")
+        raise ValueError(f"{response.status_code}: {response.text.strip()}")
 
     @staticmethod
     def _post_headers(output=Output.JSON) -> dict:
         """headers for POST endpoints"""
-        return {'Accept': output.value, 'Content-Type': 'application/json'}
+        return {"Accept": output.value, "Content-Type": "application/json"}
 
     @staticmethod
     def _get_headers(output=Output.JSON) -> dict:
         """headers for GET endpoints"""
-        return {'Accept': output.value}
+        return {"Accept": output.value}
 
     @staticmethod
-    def _send_request(url: str, method: str, headers: dict, data: dict, stream=False) -> Response:
+    def _send_request(
+        url: str, method: str, headers: dict, data: dict, stream=False
+    ) -> Response:
         """
         Send the correct http request to url from method [get, post, delete, patch, put].
         Raise a TypeError 'Unknown method to requests {method}' when the method is not one of the above.
         """
         common_kwargs = {
-            'url': url,
-            'headers': headers,
-            'stream': stream,
+            "url": url,
+            "headers": headers,
+            "stream": stream,
         }
 
-        if method == 'get':
+        if method == "get":
             api_response = requests.get(**common_kwargs)
-        elif method == 'post':
+        elif method == "post":
             api_response = requests.post(**common_kwargs, data=json.dumps(data))
-        elif method == 'delete':
+        elif method == "delete":
             api_response = requests.delete(**common_kwargs, data=json.dumps(data))
-        elif method == 'patch':
+        elif method == "patch":
             api_response = requests.patch(**common_kwargs, data=json.dumps(data))
-        elif method == 'put':
+        elif method == "put":
             api_response = requests.put(**common_kwargs, data=json.dumps(data))
         else:
-            logger.debug('ERROR : Wrong requests, please only do [get, post, put, patch, delete] method')
-            raise TypeError('Unknown method to requests %s', method)
+            logger.debug(
+                "ERROR : Wrong requests, please only do [get, post, put, patch, delete] method"
+            )
+            raise TypeError("Unknown method to requests %s", method)
         return api_response
 
     def _pretty_debug_request(self, url: str, method: str, data: dict, headers: dict):
-        debug = ('-' * self.terminal_size +
-                 'DEBUG - datalake_requests:\n' +
-                 f' - url: \n{url}\n' +
-                 f' - method: \n{method}\n' +
-                 f' - headers: \n{headers}\n' +
-                 f' - data: \n{data}\n' +
-                 f' - token: \n{self.token_manager.access_token}\n' +
-                 f' - refresh_token: \n{self.token_manager.refresh_token}\n' +
-                 '-' * self.terminal_size)
+        debug = (
+            "-" * self.terminal_size
+            + "DEBUG - datalake_requests:\n"
+            + f" - url: \n{url}\n"
+            + f" - method: \n{method}\n"
+            + f" - headers: \n{headers}\n"
+            + f" - data: \n{data}\n"
+            + f" - token: \n{self.token_manager.access_token}\n"
+            + f" - refresh_token: \n{self.token_manager.refresh_token}\n"
+            + "-" * self.terminal_size
+        )
         return debug
 
     def _build_url_for_endpoint(self, endpoint_name):
-        base_url = urljoin(self.endpoint_config['main'][self.environment], self.endpoint_config['api_version'])
-        enpoints = self.endpoint_config['endpoints']
+        base_url = urljoin(
+            self.endpoint_config["main"][self.environment],
+            self.endpoint_config["api_version"],
+        )
+        enpoints = self.endpoint_config["endpoints"]
         return urljoin(base_url, enpoints[endpoint_name], allow_fragments=True)
diff --git a/datalake/endpoints/sightings.py b/datalake/endpoints/sightings.py
index 3d667f5..2ac40dd 100644
--- a/datalake/endpoints/sightings.py
+++ b/datalake/endpoints/sightings.py
@@ -6,11 +6,21 @@
 
 
 class Sightings(Endpoint):
-    def submit_sighting(self, start_timestamp: datetime, end_timestamp: datetime, sighting_type: SightingType,
-                        visibility: Visibility, count: int, threat_types: List[ThreatType] = None,
-                        tags: List[str] = None, description: str = None,
-                        atoms: List[Atom] = None, hashkeys: List[str] = None, relation: SightingRelation = None,
-                        editable: bool = None):
+    def submit_sighting(
+        self,
+        start_timestamp: datetime,
+        end_timestamp: datetime,
+        sighting_type: SightingType,
+        visibility: Visibility,
+        count: int,
+        threat_types: List[ThreatType] = None,
+        tags: List[str] = None,
+        description: str = None,
+        atoms: List[Atom] = None,
+        hashkeys: List[str] = None,
+        relation: SightingRelation = None,
+        editable: bool = None,
+    ):
         """
         Submit a list of sightings.
         Either threat hashkeys or list of atom objects is required.
@@ -18,39 +28,78 @@ def submit_sighting(self, start_timestamp: datetime, end_timestamp: datetime, si
         field is required. End date timestamp should always be in the past.
         relation field requires specific permission for the user.
         """
-        payload = self._prepare_sightings_payload(atoms, hashkeys, start_timestamp, end_timestamp, sighting_type,
-                                                  visibility, count, threat_types, tags, description, relation, editable)
-        url = self._build_url_for_endpoint('submit-sightings')
-        res = self.datalake_requests(url, 'post', self._post_headers(), payload).json()
+        payload = self._prepare_sightings_payload(
+            atoms,
+            hashkeys,
+            start_timestamp,
+            end_timestamp,
+            sighting_type,
+            visibility,
+            count,
+            threat_types,
+            tags,
+            description,
+            relation,
+            editable,
+        )
+        url = self._build_url_for_endpoint("submit-sightings")
+        res = self.datalake_requests(url, "post", self._post_headers(), payload).json()
         return res
 
     @staticmethod
-    def _check_sightings_payload_parameters(atoms, hashkeys, sighting_type, visibility, count, threat_types, relation):
+    def _check_sightings_payload_parameters(
+        atoms, hashkeys, sighting_type, visibility, count, threat_types, relation
+    ):
         if not atoms and not hashkeys:
-            raise ValueError('Either threat hashkeys or list of atom objects is required.')
+            raise ValueError(
+                "Either threat hashkeys or list of atom objects is required."
+            )
         if count < 1:
-            raise ValueError('count value minimum: 1')
+            raise ValueError("count value minimum: 1")
         if not isinstance(sighting_type, SightingType):
-            raise ValueError('sighting_type has to be an instance of the SightingType class.')
+            raise ValueError(
+                "sighting_type has to be an instance of the SightingType class."
+            )
         if sighting_type in (SightingType.POSITIVE, SightingType.NEGATIVE):
-            if not threat_types or not all(isinstance(threat_type, ThreatType) for threat_type in threat_types):
-                raise ValueError('For POSITIVE and NEGATIVE sightings "threat_types" field is required and has to be '
-                                 'an instance of the Visibility class')
+            if not threat_types or not all(
+                isinstance(threat_type, ThreatType) for threat_type in threat_types
+            ):
+                raise ValueError(
+                    'For POSITIVE and NEGATIVE sightings "threat_types" field is required and has to be '
+                    "an instance of the Visibility class"
+                )
         elif threat_types:
             raise ValueError("For NEUTRAL sightings, threat_types can't be passed.")
         if not isinstance(visibility, Visibility):
-            raise ValueError('visibility has to be an instance of the Visibility class.')
+            raise ValueError(
+                "visibility has to be an instance of the Visibility class."
+            )
         if relation and not isinstance(relation, SightingRelation):
-            raise ValueError('relation has to be an instance of the SightingRelation class.')
+            raise ValueError(
+                "relation has to be an instance of the SightingRelation class."
+            )
 
-    def _prepare_sightings_payload(self, atoms, hashkeys, start_timestamp, end_timestamp, sighting_type: SightingType,
-                                   visibility, count, threat_types=None, tags=None, description=None, relation=None,
-                                   editable=None):
+    def _prepare_sightings_payload(
+        self,
+        atoms,
+        hashkeys,
+        start_timestamp,
+        end_timestamp,
+        sighting_type: SightingType,
+        visibility,
+        count,
+        threat_types=None,
+        tags=None,
+        description=None,
+        relation=None,
+        editable=None,
+    ):
         """
         Internal function to prepare a list of Atoms for sighting submission to the format the API expects.
         """
-        self._check_sightings_payload_parameters(atoms, hashkeys, sighting_type, visibility, count, threat_types,
-                                                 relation)
+        self._check_sightings_payload_parameters(
+            atoms, hashkeys, sighting_type, visibility, count, threat_types, relation
+        )
         payload = {}
         # atoms and hashkeys can both be None
         if atoms:
@@ -63,28 +112,94 @@ def _prepare_sightings_payload(self, atoms, hashkeys, start_timestamp, end_times
                 else:
                     payload = {
                         key: payload.get(key, []) + atom_dict.get(key, [])
-                        for key in set(
-                            list(payload.keys()) + list(atom_dict.keys())
-                        )
+                        for key in set(list(payload.keys()) + list(atom_dict.keys()))
                     }
 
         if hashkeys:
-            payload['hashkeys'] = hashkeys
+            payload["hashkeys"] = hashkeys
 
-        payload['start_timestamp'] = start_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
-        payload['end_timestamp'] = end_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
-        payload['visibility'] = visibility.value
-        payload['type'] = sighting_type.value
-        payload['count'] = count
+        payload["start_timestamp"] = start_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
+        payload["end_timestamp"] = end_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
+        payload["visibility"] = visibility.value
+        payload["type"] = sighting_type.value
+        payload["count"] = count
 
         if relation:
-            payload['relation_type'] = relation.value
+            payload["relation_type"] = relation.value
         if sighting_type in (SightingType.POSITIVE, SightingType.NEGATIVE):
-            payload['threat_types'] = [threat_type.value for threat_type in threat_types]
+            payload["threat_types"] = [
+                threat_type.value for threat_type in threat_types
+            ]
         if tags:
-            payload['tags'] = tags
+            payload["tags"] = tags
         if description:
-            payload['description'] = description
+            payload["description"] = description
         if editable is not None:
-            payload['editable'] = editable
+            payload["editable"] = editable
         return payload
+
+    def sightings_filtered(
+        self,
+        threat_hashkey: str = None,
+        limit: int = None,
+        offset: int = None,
+        ordering: str = None,
+        organization_id: int = None,
+        organization_name: str = None,
+        start_timestamp_date: str = None,
+        end_timestamp_date: str = None,
+        tags: list = None,
+        sighting_type: SightingType = None,
+        visibility: Visibility = None,
+    ):
+        """
+        Retrieve a list of filtered sightings.
+        """
+        ordering_list = [
+            "start_timestamp",
+            "-start_timestamp",
+            "end_timestamp",
+            "-end_timestamp",
+            "timestamp_created",
+            "-timestamp_created",
+            "count",
+            "-count",
+        ]
+        if ordering is not None and ordering not in ordering_list:
+            raise ValueError(
+                'ordering has to be one of the following: "start_timestamp", "-start_timestamp", "end_timestamp", "-end_timestamp", "timestamp_created", "-timestamp_created", "count", "-count"'
+            )
+        if (
+            sighting_type is not None
+            and sighting_type
+            and not isinstance(sighting_type, SightingType)
+        ):
+            raise ValueError(
+                "sighting_type has to be an instance of the SightingType class."
+            )
+        if (
+            visibility is not None
+            and visibility
+            and not isinstance(visibility, Visibility)
+        ):
+            raise ValueError(
+                "visibility has to be an instance of the Visibility class."
+            )
+
+        payload = {
+            "threat_hashkey": threat_hashkey,
+            "limit": limit,
+            "offset": offset,
+            "ordering": ordering,
+            "organization_id": organization_id,
+            "organization_name": organization_name,
+            "start_timestamp_date": start_timestamp_date,
+            "end_timestamp_date": end_timestamp_date,
+            "tags": tags,
+            "type": sighting_type.value if sighting_type is not None else None,
+            "visibility": visibility.value if visibility is not None else None,
+        }
+        payload = {k: v for k, v in payload.items() if v is not None}
+        url = self._build_url_for_endpoint("sighting-filtered")
+        res = self.datalake_requests(url, "post", self._post_headers(), payload).json()
+        return res
diff --git a/datalake/endpoints/tags.py b/datalake/endpoints/tags.py
index 819de51..6e53451 100644
--- a/datalake/endpoints/tags.py
+++ b/datalake/endpoints/tags.py
@@ -3,12 +3,7 @@
 
 
 class Tags(Endpoint):
-    def add_to_threat(
-            self,
-            hashkey: str,
-            tags: list,
-            public=False
-    ):
+    def add_to_threat(self, hashkey: str, tags: list, public=False):
         """
         Adds tag(s) to a threat.
         """
@@ -16,19 +11,19 @@ def add_to_threat(
             raise ValueError("Tags has to be a list of string")
         if all(type(tag) is not str or not tag for tag in tags):
             raise ValueError("Tags has to be a list of string")
-        
-        visibility = 'public' if public else 'organization'
+
+        visibility = "public" if public else "organization"
         tags_payload = []
         for tag in tags:
             tags_payload.append(
                 {
-                    'name': tag,
-                    'visibility': visibility,
+                    "name": tag,
+                    "visibility": visibility,
                 }
             )
         payload = {
-            'tags': tags_payload,
+            "tags": tags_payload,
         }
-        url = self._build_url_for_endpoint('tag').format(hashkey=hashkey)
-        response = self.datalake_requests(url, 'post', self._post_headers(), payload)
+        url = self._build_url_for_endpoint("tag").format(hashkey=hashkey)
+        response = self.datalake_requests(url, "post", self._post_headers(), payload)
         return parse_response(response)
diff --git a/datalake/endpoints/threats.py b/datalake/endpoints/threats.py
index ae5b55b..b25f2f2 100644
--- a/datalake/endpoints/threats.py
+++ b/datalake/endpoints/threats.py
@@ -13,47 +13,53 @@
 
 class Threats(Endpoint):
     _NB_ATOMS_PER_BULK_LOOKUP = 100
-    OCD_DTL_MAX_BULK_THREATS_TIME = int(os.getenv('OCD_DTL_MAX_BULK_THREATS_TIME', 600))
-    OCD_DTL_MAX_BULK_THREATS_IN_FLIGHT = int(os.getenv('OCD_DTL_MAX_BULK_THREATS_IN_FLIGHT', 10))
-    OCD_DTL_MAX_BACK_OFF_TIME = float(os.getenv('OCD_DTL_MAX_BACK_OFF_TIME', 30))
-    OCD_DTL_MAX_EDIT_SCORE_HASHKEYS = int(os.getenv('OCD_DTL_MAX_EDIT_SCORE_HASHKEYS', 100))
+    OCD_DTL_MAX_BULK_THREATS_TIME = int(os.getenv("OCD_DTL_MAX_BULK_THREATS_TIME", 600))
+    OCD_DTL_MAX_BULK_THREATS_IN_FLIGHT = int(
+        os.getenv("OCD_DTL_MAX_BULK_THREATS_IN_FLIGHT", 10)
+    )
+    OCD_DTL_MAX_BACK_OFF_TIME = float(os.getenv("OCD_DTL_MAX_BACK_OFF_TIME", 30))
+    OCD_DTL_MAX_EDIT_SCORE_HASHKEYS = int(
+        os.getenv("OCD_DTL_MAX_EDIT_SCORE_HASHKEYS", 100)
+    )
 
     def _bulk_lookup_batch(
-            self,
-            atom_values: list,
-            atom_type: AtomType = None,
-            hashkey_only=False,
-            output=Output.JSON,
-            return_search_hashkey=False,
+        self,
+        atom_values: list,
+        atom_type: AtomType = None,
+        hashkey_only=False,
+        output=Output.JSON,
+        return_search_hashkey=False,
     ) -> dict:
         """Bulk lookup done on maximum _NB_ATOMS_PER_BULK_LOOKUP atoms"""
         typed_atoms = {}
         if not atom_type:
             atoms_values_extractor_response = self.atom_values_extract(atom_values)
-            if atoms_values_extractor_response['found'] > 0:
-                typed_atoms = atoms_values_extractor_response['results']
+            if atoms_values_extractor_response["found"] > 0:
+                typed_atoms = atoms_values_extractor_response["results"]
             else:
-                raise ValueError('none of your atoms could be typed')
+                raise ValueError("none of your atoms could be typed")
         elif not isinstance(atom_type, AtomType):
-            raise ValueError(f'{atom_type} atom_type could not be treated')
+            raise ValueError(f"{atom_type} atom_type could not be treated")
         else:
             typed_atoms[atom_type.value] = atom_values
 
         body: dict = typed_atoms
-        body['hashkey_only'] = hashkey_only
-        body['return_search_hashkey'] = return_search_hashkey
-        url = self._build_url_for_endpoint('threats-bulk-lookup')
-        response = self.datalake_requests(url, 'post', self._post_headers(output=output), body)
+        body["hashkey_only"] = hashkey_only
+        body["return_search_hashkey"] = return_search_hashkey
+        url = self._build_url_for_endpoint("threats-bulk-lookup")
+        response = self.datalake_requests(
+            url, "post", self._post_headers(output=output), body
+        )
         return parse_response(response)
 
     @output_supported({Output.JSON, Output.CSV})
     def bulk_lookup(
-            self,
-            atom_values: list,
-            atom_type: AtomType = None,
-            hashkey_only=False,
-            output=Output.JSON,
-            return_search_hashkey=False,
+        self,
+        atom_values: list,
+        atom_type: AtomType = None,
+        hashkey_only=False,
+        output=Output.JSON,
+        return_search_hashkey=False,
     ) -> Union[dict, str]:
         """
         Look up multiple threats at once in the API, returning their ids and if they are present in Datalake.
@@ -63,26 +69,38 @@ def bulk_lookup(
         """
         aggregated_response = [] if output is Output.CSV else {}
         search_hashkey_list = []
-        for atom_values_batch in split_list(atom_values, self._NB_ATOMS_PER_BULK_LOOKUP):
-            batch_result = self._bulk_lookup_batch(atom_values_batch,
-                                                   atom_type,
-                                                   hashkey_only,
-                                                   output,
-                                                   return_search_hashkey)
-            if 'search_hashkey' in batch_result:
-                search_hashkey_list.append(batch_result.pop('search_hashkey'))
+        for atom_values_batch in split_list(
+            atom_values, self._NB_ATOMS_PER_BULK_LOOKUP
+        ):
+            batch_result = self._bulk_lookup_batch(
+                atom_values_batch,
+                atom_type,
+                hashkey_only,
+                output,
+                return_search_hashkey,
+            )
+            if "search_hashkey" in batch_result:
+                search_hashkey_list.append(batch_result.pop("search_hashkey"))
             aggregated_response = aggregate_csv_or_json_api_response(
                 aggregated_response,
                 batch_result,
             )
         if search_hashkey_list:
-            aggregated_response['search_hashkey'] = search_hashkey_list
+            aggregated_response["search_hashkey"] = search_hashkey_list
         if output is Output.CSV:
-            aggregated_response = '\n'.join(aggregated_response)  # a string is expected for CSV output
+            aggregated_response = "\n".join(
+                aggregated_response
+            )  # a string is expected for CSV output
         return aggregated_response
 
     @output_supported({Output.JSON, Output.CSV, Output.MISP, Output.STIX})
-    def lookup(self, atom_value, atom_type: AtomType = None, hashkey_only=False, output=Output.JSON) -> dict:
+    def lookup(
+        self,
+        atom_value,
+        atom_type: AtomType = None,
+        hashkey_only=False,
+        output=Output.JSON,
+    ) -> dict:
         """
         Look up a threat in the API, returning its id (called threat's hashkey) and if it is present in Datalake.
 
@@ -94,113 +112,132 @@ def lookup(self, atom_value, atom_type: AtomType = None, hashkey_only=False, out
         if not atom_type:
             threats = [atom_value]
             atoms_values_extractor_response = self.atom_values_extract(threats)
-            if atoms_values_extractor_response['found'] > 0:
-                atom_type_str = list(atoms_values_extractor_response['results'].keys())[0]
+            if atoms_values_extractor_response["found"] > 0:
+                atom_type_str = list(atoms_values_extractor_response["results"].keys())[
+                    0
+                ]
             else:
-                raise ValueError('atom could not be typed')
+                raise ValueError("atom could not be typed")
         elif not isinstance(atom_type, AtomType):
-            raise ValueError(f'{atom_type} atom_type could not be treated')
+            raise ValueError(f"{atom_type} atom_type could not be treated")
         else:  # atom_type is a valid enum passed by the user
             atom_type_str = atom_type.value
 
-        url = self._build_url_for_endpoint('lookup')
-        params = {'atom_value': atom_value, 'atom_type': atom_type_str, 'hashkey_only': hashkey_only}
+        url = self._build_url_for_endpoint("lookup")
+        params = {
+            "atom_value": atom_value,
+            "atom_type": atom_type_str,
+            "hashkey_only": hashkey_only,
+        }
         req = PreparedRequest()
         req.prepare_url(url, params)
-        response = self.datalake_requests(req.url, 'get', self._get_headers(output=output))
+        response = self.datalake_requests(
+            req.url, "get", self._get_headers(output=output)
+        )
         return parse_response(response)
 
-    def atom_values_extract(self, untyped_atoms: List[str], treat_hashes_like=AtomType.FILE) -> dict:
+    def atom_values_extract(
+        self, untyped_atoms: List[str], treat_hashes_like=AtomType.FILE
+    ) -> dict:
         """
         Determine the types of atoms passed.
 
         values that are believed to be hashes will be returned as <treat_hashes_like>
         """
-        url = self._build_url_for_endpoint('atom-values-extract')
+        url = self._build_url_for_endpoint("atom-values-extract")
         payload = {
-            'content': ' '.join(untyped_atoms),
-            'treat_hashes_like': treat_hashes_like.value
+            "content": " ".join(untyped_atoms),
+            "treat_hashes_like": treat_hashes_like.value,
         }
-        return self.datalake_requests(url, 'post', self._post_headers(), payload).json()
+        return self.datalake_requests(url, "post", self._post_headers(), payload).json()
 
-    def edit_score_by_hashkeys(self, hashkeys, scores_list, override_type: OverrideType = OverrideType.TEMPORARY):
+    def edit_score_by_hashkeys(
+        self,
+        hashkeys,
+        scores_list,
+        override_type: OverrideType = OverrideType.TEMPORARY,
+    ):
         """
-        Edit the score of a list of threats using the API. Default is 100. This function will receive a list of 
+        Edit the score of a list of threats using the API. Default is 100. This function will receive a list of
         hashkey to edit, a list of dictionaries defining the score the set and an override type. Can only process a
-        limited number of hashkeys at one time. Can be modified with the environment variable 
+        limited number of hashkeys at one time. Can be modified with the environment variable
         OCD_DTL_MAX_EDIT_SCORE_HASHKEYS.
         """
         if type(hashkeys) is not list or not hashkeys:
-            raise ValueError('Hashkeys has to be a list of string')
+            raise ValueError("Hashkeys has to be a list of string")
         if len(hashkeys) > self.OCD_DTL_MAX_EDIT_SCORE_HASHKEYS:
-            raise ValueError(f'Can\'t process more than {self.OCD_DTL_MAX_EDIT_SCORE_HASHKEYS} hashkeys at one time.')
+            raise ValueError(
+                f"Can't process more than {self.OCD_DTL_MAX_EDIT_SCORE_HASHKEYS} hashkeys at one time."
+            )
         if any(type(hashkey) is not str or not hashkey for hashkey in hashkeys):
-            raise ValueError('Hashkeys has to be a list of string')
+            raise ValueError("Hashkeys has to be a list of string")
         if not isinstance(override_type, OverrideType):
-            raise ValueError('Invalid OverrideType input')
+            raise ValueError("Invalid OverrideType input")
         req_body = {
-            'override_type': override_type.value,
-            'hashkeys': hashkeys,
-            'scores': self._build_scores(scores_list)
+            "override_type": override_type.value,
+            "hashkeys": hashkeys,
+            "scores": self._build_scores(scores_list),
         }
-        url = self._build_url_for_endpoint('bulk-scoring-edits')
-        response = self.datalake_requests(url, 'post', self._post_headers(), req_body)
+        url = self._build_url_for_endpoint("bulk-scoring-edits")
+        response = self.datalake_requests(url, "post", self._post_headers(), req_body)
         return parse_response(response)
 
-    def edit_score_by_query_body_hash(self,
-                                      query_body_hash: str,
-                                      scores_list: List[ScoreMap],
-                                      override_type: OverrideType = OverrideType.TEMPORARY):
+    def edit_score_by_query_body_hash(
+        self,
+        query_body_hash: str,
+        scores_list: List[ScoreMap],
+        override_type: OverrideType = OverrideType.TEMPORARY,
+    ):
         """
         Edit the score of a list of threats using the API.
         This function will receive a query body hash,
         a list of dictonaries defining the score the set and an override type.
         """
         if not isinstance(override_type, OverrideType):
-            raise ValueError('Invalid OverrideType input')
+            raise ValueError("Invalid OverrideType input")
         req_body = {
-            'override_type': override_type.value,
-            'query_body_hash': query_body_hash,
-            'scores': self._build_scores(scores_list)
+            "override_type": override_type.value,
+            "query_body_hash": query_body_hash,
+            "scores": self._build_scores(scores_list),
         }
-        url = self._build_url_for_endpoint('bulk-scoring-edits')
-        response = self.datalake_requests(url, 'post', self._post_headers(), req_body)
+        url = self._build_url_for_endpoint("bulk-scoring-edits")
+        response = self.datalake_requests(url, "post", self._post_headers(), req_body)
         return parse_response(response)
 
     @staticmethod
     def _build_scores(scores_list: List[ScoreMap]):
         scores_body = []
         for score_dict in scores_list:
-            if not isinstance(score_dict['threat_type'], ThreatType):
-                raise ValueError('Invalid threat_type input')
-            if score_dict['score'] > 100 or score_dict['score'] < 0:
-                raise ValueError('Invalid score input, min: 0, max: 100')
+            if not isinstance(score_dict["threat_type"], ThreatType):
+                raise ValueError("Invalid threat_type input")
+            if score_dict["score"] > 100 or score_dict["score"] < 0:
+                raise ValueError("Invalid score input, min: 0, max: 100")
 
-            scores_body.append({
-                'score': {
-                    'risk': score_dict['score']
-                },
-                'threat_type': score_dict['threat_type'].value
-            })
+            scores_body.append(
+                {
+                    "score": {"risk": score_dict["score"]},
+                    "threat_type": score_dict["threat_type"].value,
+                }
+            )
         return scores_body
 
     @staticmethod
     def _build_whitelist_scores():
         scores = []
         for threat in ThreatType:
-            scores.append({'score': {'risk': 0}, 'threat_type': threat.value})
+            scores.append({"score": {"risk": 0}, "threat_type": threat.value})
         return scores
 
     def add_threats(
-            self,
-            atom_list: List[str],
-            atom_type: AtomType,
-            threat_types: List[ScoreMap] = None,
-            override_type: OverrideType = OverrideType.TEMPORARY,
-            whitelist: bool = False,
-            public: bool = True,
-            tags: List = None,
-            external_analysis_link: List = None,
+        self,
+        atom_list: List[str],
+        atom_type: AtomType,
+        threat_types: List[ScoreMap] = None,
+        override_type: OverrideType = OverrideType.TEMPORARY,
+        whitelist: bool = False,
+        public: bool = True,
+        tags: List = None,
+        external_analysis_link: List = None,
     ):
         """
         Add a list of threats to datalake using the API.
@@ -208,43 +245,44 @@ def add_threats(
         """
         self.check_add_threats_params(atom_list, override_type, threat_types, whitelist)
         tags = tags or []  # API requires a tag field, default to an empty list
-        payload = {
-            'override_type': override_type.value,
-            'public': public
-        }
+        payload = {"override_type": override_type.value, "public": public}
         if whitelist:
             scores = self._build_whitelist_scores()
         else:
             scores = self._build_scores(threat_types)
-        return self._bulk_add_threat(atom_list, atom_type, payload, tags, scores, external_analysis_link)
+        return self._bulk_add_threat(
+            atom_list, atom_type, payload, tags, scores, external_analysis_link
+        )
 
     @staticmethod
     def check_add_threats_params(atom_list, override_type, threat_types, whitelist):
         if not threat_types and not whitelist:
-            raise ValueError('threat_types is required if the atom is not for whitelisting')
+            raise ValueError(
+                "threat_types is required if the atom is not for whitelisting"
+            )
         if not isinstance(override_type, OverrideType):
-            raise ValueError('Invalid OverrideType input')
+            raise ValueError("Invalid OverrideType input")
         if any(len(atom) < 1 for atom in atom_list):
-            raise ValueError('Empty atom in atom_list')
+            raise ValueError("Empty atom in atom_list")
 
     def _bulk_add_threat(
-            self,
-            atom_list: List,
-            atom_type: AtomType,
-            payload: Dict,
-            tags: List,
-            scores: List[Dict],
-            external_analysis_link: List = None
+        self,
+        atom_list: List,
+        atom_type: AtomType,
+        payload: Dict,
+        tags: List,
+        scores: List[Dict],
+        external_analysis_link: List = None,
     ):
-        url = self._build_url_for_endpoint('threats-manual-bulk')
-        payload['atom_type'] = atom_type.value
-        payload['scores'] = scores
-        payload['tags'] = tags
+        url = self._build_url_for_endpoint("threats-manual-bulk")
+        payload["atom_type"] = atom_type.value
+        payload["scores"] = scores
+        payload["tags"] = tags
         if external_analysis_link:
-            payload['threat_data'] = {
-                'content': {
-                    f'{atom_type.value}_content': {
-                        'external_analysis_link': external_analysis_link
+            payload["threat_data"] = {
+                "content": {
+                    f"{atom_type.value}_content": {
+                        "external_analysis_link": external_analysis_link
                     }
                 }
             }
@@ -258,26 +296,30 @@ def queue_bulk_threats(self, atom_list, payload, url):
         for batch in split_list(atom_list, 100):
             if len(bulk_in_flight) >= self.OCD_DTL_MAX_BULK_THREATS_IN_FLIGHT:
                 bulk_threat_task_uuid = bulk_in_flight.pop(0)
-                bulk_response.append(self.check_bulk_threats_added(bulk_threat_task_uuid))
+                bulk_response.append(
+                    self.check_bulk_threats_added(bulk_threat_task_uuid)
+                )
 
-            payload['atom_values'] = '\n'.join(batch)  # Raw csv expected
-            response = self.datalake_requests(url, 'post', self._post_headers(), payload)
+            payload["atom_values"] = "\n".join(batch)  # Raw csv expected
+            response = self.datalake_requests(
+                url, "post", self._post_headers(), payload
+            )
             response = parse_response(response)
-            task_uid = response.get('task_uuid')
+            task_uid = response.get("task_uuid")
             if task_uid:
-                bulk_in_flight.append(response['task_uuid'])
+                bulk_in_flight.append(response["task_uuid"])
             else:
                 failed_batch.append(batch)
         if failed_batch:
-            bulk_response.append({
-                'success': {
-                    'created_hashkeys': [],
-                    'created_atom_values': []
-                },
-                'failed': {
-                    'failed_hashkeys': [],
-                    'failed_atom_values': failed_batch}
-            })
+            bulk_response.append(
+                {
+                    "success": {"created_hashkeys": [], "created_atom_values": []},
+                    "failed": {
+                        "failed_hashkeys": [],
+                        "failed_atom_values": failed_batch,
+                    },
+                }
+            )
 
         # Finish to check the other bulk tasks
         for bulk_threat_task_uuid in bulk_in_flight:
@@ -288,7 +330,7 @@ def check_bulk_threats_added(self, bulk_threat_task_uuid) -> dict:
         """Check if the bulk manual threat submission completed successfully and if so return the hashkeys created"""
         success = []
         failed = []
-        url = self._build_url_for_endpoint('retrieve-threats-manual-bulk')
+        url = self._build_url_for_endpoint("retrieve-threats-manual-bulk")
 
         try:
             response = self._handle_bulk_task(
@@ -299,22 +341,29 @@ def check_bulk_threats_added(self, bulk_threat_task_uuid) -> dict:
         except TimeoutError:
             response = {}
 
-        hashkeys = response.get('hashkeys')
-        atom_values = response.get('atom_values')
-        if hashkeys and response.get('state', 'CANCELLED') == 'DONE':
+        hashkeys = response.get("hashkeys")
+        atom_values = response.get("atom_values")
+        if hashkeys and response.get("state", "CANCELLED") == "DONE":
             hashkey_created = []
             atom_val_created = []
             for hashkey in hashkeys:
                 hashkey_created.append(hashkey)
             for atom_value in atom_values:
                 atom_val_created.append(atom_value)
-            success.append({'created_hashkeys': hashkey_created, 'created_atom_values': atom_val_created})
+            success.append(
+                {
+                    "created_hashkeys": hashkey_created,
+                    "created_atom_values": atom_val_created,
+                }
+            )
         else:  # if the state is not DONE we consider the batch a failure
             # default values in case the json is missing some fields
-            hashkeys = hashkeys or ['<missing value>']
-            atom_values = atom_values or ['<missing value>']
-            failed.append({'failed_hashkeys': hashkeys, 'failed_atom_values': atom_values})
-        response_dict = {'success': success, 'failed': failed}
+            hashkeys = hashkeys or ["<missing value>"]
+            atom_values = atom_values or ["<missing value>"]
+            failed.append(
+                {"failed_hashkeys": hashkeys, "failed_atom_values": atom_values}
+            )
+        response_dict = {"success": success, "failed": failed}
         return response_dict
 
     def _handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, timeout):
@@ -323,10 +372,12 @@ def _handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, timeout):
         back_off_time = 1
         json_response = None
         while not json_response:
-            response = self.datalake_requests(retrieve_bulk_result_url, 'get', self._get_headers())
+            response = self.datalake_requests(
+                retrieve_bulk_result_url, "get", self._get_headers()
+            )
             if response.status_code == 200:
                 potential_json_response = response.json()
-                if not potential_json_response['state'] in ('DONE', 'CANCELLED'):
+                if not potential_json_response["state"] in ("DONE", "CANCELLED"):
                     continue
                 json_response = response.json()
             elif time() - start_time + back_off_time < timeout:
@@ -334,7 +385,7 @@ def _handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, timeout):
                 back_off_time = min(back_off_time * 2, self.OCD_DTL_MAX_BACK_OFF_TIME)
             else:
                 raise TimeoutError(
-                    f'No bulk result after waiting {timeout / 60:.0f} mins\n'
+                    f"No bulk result after waiting {timeout / 60:.0f} mins\n"
                     f'task_uuid: "{task_uuid}"'
                 )
         return json_response
@@ -342,20 +393,22 @@ def _handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, timeout):
     @staticmethod
     def check_add_threat_params(atom, override_type, threat_types, whitelist):
         if not threat_types and not whitelist:
-            raise ValueError('threat_types is required if the atom is not for whitelisting')
+            raise ValueError(
+                "threat_types is required if the atom is not for whitelisting"
+            )
         if not isinstance(override_type, OverrideType):
-            raise ValueError('Invalid OverrideType input')
+            raise ValueError("Invalid OverrideType input")
         if type(atom) == Atom or not isinstance(atom, Atom):
             raise TypeError("atom needs to be an Atom subclasses.")
 
     def add_threat(
-            self,
-            atom: Atom,
-            threat_types: List[ScoreMap] = None,
-            override_type: OverrideType = OverrideType.TEMPORARY,
-            whitelist: bool = False,
-            public: bool = True,
-            tags: List = None,
+        self,
+        atom: Atom,
+        threat_types: List[ScoreMap] = None,
+        override_type: OverrideType = OverrideType.TEMPORARY,
+        whitelist: bool = False,
+        public: bool = True,
+        tags: List = None,
     ):
         """
         Add a single threat to datalake using the API.
@@ -368,14 +421,14 @@ def add_threat(
         else:
             scores = self._build_scores(threat_types)
         payload = {
-            'override_type': override_type.value,
-            'public': public,
-            'threat_data': {
-                'scores': scores,
-                'tags': tags,
-                'content': atom.generate_atom_json(),
-            }
+            "override_type": override_type.value,
+            "public": public,
+            "threat_data": {
+                "scores": scores,
+                "tags": tags,
+                "content": atom.generate_atom_json(),
+            },
         }
-        url = self._build_url_for_endpoint('threats-manual')
-        response = self.datalake_requests(url, 'post', self._post_headers(), payload)
+        url = self._build_url_for_endpoint("threats-manual")
+        response = self.datalake_requests(url, "post", self._post_headers(), payload)
         return parse_response(response)
diff --git a/datalake_scripts/common/base_engine.py b/datalake_scripts/common/base_engine.py
index 5c42432..4bb81d6 100644
--- a/datalake_scripts/common/base_engine.py
+++ b/datalake_scripts/common/base_engine.py
@@ -21,17 +21,20 @@ class InvalidHeader(Exception):
 
 
 class BaseEngine:
-    ACCEPTED_HEADERS = {
-        'json': 'application/json',
-        'csv': 'text/csv'
-    }
-    OCD_DTL_QUOTA_TIME = int(os.getenv('OCD_DTL_QUOTA_TIME', 1))
-    OCD_DTL_REQUESTS_PER_QUOTA_TIME = int(os.getenv('OCD_DTL_REQUESTS_PER_QUOTA_TIME', 5))
-    logger.debug(f'Throttle selected: {OCD_DTL_REQUESTS_PER_QUOTA_TIME} queries per {OCD_DTL_QUOTA_TIME}s')
+    ACCEPTED_HEADERS = {"json": "application/json", "csv": "text/csv"}
+    OCD_DTL_QUOTA_TIME = int(os.getenv("OCD_DTL_QUOTA_TIME", 1))
+    OCD_DTL_REQUESTS_PER_QUOTA_TIME = int(
+        os.getenv("OCD_DTL_REQUESTS_PER_QUOTA_TIME", 5)
+    )
+    logger.debug(
+        f"Throttle selected: {OCD_DTL_REQUESTS_PER_QUOTA_TIME} queries per {OCD_DTL_QUOTA_TIME}s"
+    )
 
     Json = Union[dict, list]  # json like object that can be a dict or root level array
 
-    def __init__(self, endpoint_config: dict, environment: str, token_manager: TokenManager):
+    def __init__(
+        self, endpoint_config: dict, environment: str, token_manager: TokenManager
+    ):
         self.endpoint_config = endpoint_config
         self.environment = environment
         self.requests_ssl_verify = suppress_insecure_request_warns(environment)
@@ -39,23 +42,28 @@ def __init__(self, endpoint_config: dict, environment: str, token_manager: Token
         self.token_manager = token_manager
         self.endpoint = Endpoint(endpoint_config, environment, token_manager)
 
-    def datalake_requests(self, url: str, method: str, headers: dict, post_body: dict = None):
+    def datalake_requests(
+        self, url: str, method: str, headers: dict, post_body: dict = None
+    ):
         """
         Wrapper around the new datalake_requests to keep compatibility with old scrips
         """
         try:
             response = self.endpoint.datalake_requests(url, method, headers, post_body)
         except ValueError:
-            logger.error('Request failed: Will return nothing for this request')
+            logger.error("Request failed: Will return nothing for this request")
             return {}
-        if 'Content-Type' in response.headers and 'text/csv' in response.headers['Content-Type']:
+        if (
+            "Content-Type" in response.headers
+            and "text/csv" in response.headers["Content-Type"]
+        ):
             return response.text
         else:
             try:
                 dict_response = self._load_response(response)
                 return dict_response
             except JSONDecodeError:
-                logger.error('Request unexpectedly returned non dict value. Retrying')
+                logger.error("Request unexpectedly returned non dict value. Retrying")
 
     @staticmethod
     def output_type2header(value):
@@ -66,7 +74,9 @@ def output_type2header(value):
         """
         if value.lower() in BaseEngine.ACCEPTED_HEADERS:
             return BaseEngine.ACCEPTED_HEADERS[value.lower()]
-        raise InvalidHeader(f'{value.lower()} is not a valid. Use some of {BaseEngine.ACCEPTED_HEADERS.keys()}')
+        raise InvalidHeader(
+            f"{value.lower()} is not a valid. Use some of {BaseEngine.ACCEPTED_HEADERS.keys()}"
+        )
 
     @staticmethod
     def _load_response(api_response: Response):
@@ -80,9 +90,9 @@ def _load_response(api_response: Response):
         :param: api_response: dict
         :return: dict_response
         """
-        if api_response.text.startswith('[') and api_response.text.endswith(']\n'):
+        if api_response.text.startswith("[") and api_response.text.endswith("]\n"):
             # This condition is for the date-histogram endpoints
-            dict_response = {'response_length': len(api_response.text)}
+            dict_response = {"response_length": len(api_response.text)}
         else:
             dict_response = json.loads(api_response.text)
         return dict_response
@@ -92,6 +102,9 @@ def _build_url(self, endpoint_config: dict, environment: str):
         raise NotImplemented()
 
     def _build_url_for_endpoint(self, endpoint_name):
-        base_url = urljoin(self.endpoint_config['main'][self.environment], self.endpoint_config['api_version'])
-        enpoints = self.endpoint_config['endpoints']
+        base_url = urljoin(
+            self.endpoint_config["main"][self.environment],
+            self.endpoint_config["api_version"],
+        )
+        enpoints = self.endpoint_config["endpoints"]
         return urljoin(base_url, enpoints[endpoint_name], allow_fragments=True)
diff --git a/datalake_scripts/common/base_script.py b/datalake_scripts/common/base_script.py
index 402593f..2ce6a7f 100644
--- a/datalake_scripts/common/base_script.py
+++ b/datalake_scripts/common/base_script.py
@@ -14,12 +14,13 @@
 
 
 class BaseScripts:
-
     @staticmethod
-    def start(description: str, output_file_required: bool = False) -> argparse.ArgumentParser:
+    def start(
+        description: str, output_file_required: bool = False
+    ) -> argparse.ArgumentParser:
         """
-        Create a common parser for all the scripts. 
-            Parser will contain: 
+        Create a common parser for all the scripts.
+            Parser will contain:
                 --output,  name of the output file
                 --env,     name of the environment of scripting
                 --debug, if set, will show debug text
@@ -30,33 +31,39 @@ def start(description: str, output_file_required: bool = False) -> argparse.Argu
             description=description,
         )
         parser.add_argument(
-            '-o',
-            '--output',
-            help='Output FILE path from script',
+            "-o",
+            "--output",
+            help="Output FILE path from script",
             required=output_file_required,
         )
         parser.add_argument(
-            '-e',
-            '--env',
-            help='Execute on specified environment (Default: prod)',
-            choices=['prod', 'preprod'],
-            default='prod',
+            "-e",
+            "--env",
+            help="Execute on specified environment (Default: prod)",
+            choices=["prod", "preprod"],
+            default="prod",
         )
         parser.add_argument(
-            '--debug',
+            "--debug",
             help="Enable ",
-            action="store_const", dest="loglevel", const=logging.DEBUG,
+            action="store_const",
+            dest="loglevel",
+            const=logging.DEBUG,
             default=logging.INFO,
         )
         parser.add_argument(
-            '-q',
-            '--quiet',
-            help='Silence the output to only show warnings/errors',
-            action="store_const", dest="loglevel", const=logging.WARNING,
+            "-q",
+            "--quiet",
+            help="Silence the output to only show warnings/errors",
+            action="store_const",
+            dest="loglevel",
+            const=logging.WARNING,
         )
         return parser
 
-    def load_config(self, args, username=None, password=None) -> Tuple[dict, TokenManager]:
+    def load_config(
+        self, args, username=None, password=None
+    ) -> Tuple[dict, TokenManager]:
         """Load correct config and generate first tokens"""
         configure_logging(args.loglevel)
         endpoint_config = Config().load_config()
@@ -69,6 +76,8 @@ def load_config(self, args, username=None, password=None) -> Tuple[dict, TokenMa
         try:
             token_manager.get_token()
         except ValueError:
-            logger.error("Couldn't generate Tokens, please check the login/password provided")
+            logger.error(
+                "Couldn't generate Tokens, please check the login/password provided"
+            )
             exit()
         return endpoint_config, token_manager
diff --git a/datalake_scripts/common/mixins.py b/datalake_scripts/common/mixins.py
index 32040f9..ec2131c 100644
--- a/datalake_scripts/common/mixins.py
+++ b/datalake_scripts/common/mixins.py
@@ -12,13 +12,19 @@
 
 class HandleBulkTaskMixin(BaseEngine):
 
-    OCD_DTL_MAX_BACK_OFF_TIME = float(os.getenv('OCD_DTL_MAX_BACK_OFF_TIME', 30))
+    OCD_DTL_MAX_BACK_OFF_TIME = float(os.getenv("OCD_DTL_MAX_BACK_OFF_TIME", 30))
 
     Json = Dict
     Check = Callable[[Json], bool]
 
-    def handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, *, timeout, additional_checks: List[Check] = None) \
-            -> Json:
+    def handle_bulk_task(
+        self,
+        task_uuid,
+        retrieve_bulk_result_url,
+        *,
+        timeout,
+        additional_checks: List[Check] = None,
+    ) -> Json:
         """
         Handle a generic bulk task, blocking until the task is done or the timeout is up
 
@@ -32,7 +38,9 @@ def handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, *, timeout, addi
 
         spinner = None
         if logger.isEnabledFor(logging.INFO):
-            spinner = Halo(text=f'Waiting for bulk task {task_uuid} response', spinner='dots')
+            spinner = Halo(
+                text=f"Waiting for bulk task {task_uuid} response", spinner="dots"
+            )
             spinner.start()
 
         start_time = time()
@@ -40,27 +48,33 @@ def handle_bulk_task(self, task_uuid, retrieve_bulk_result_url, *, timeout, addi
 
         json_response = None
         while not json_response:
-            headers = {'Authorization': self.token_manager.access_token}
-            response = requests.get(url=retrieve_bulk_result_url, headers=headers, verify=self.requests_ssl_verify)
+            headers = {"Authorization": self.token_manager.access_token}
+            response = requests.get(
+                url=retrieve_bulk_result_url,
+                headers=headers,
+                verify=self.requests_ssl_verify,
+            )
             if response.status_code == 200:
                 potential_json_response = response.json()
-                if additional_checks and not all(check(potential_json_response) for check in additional_checks):
+                if additional_checks and not all(
+                    check(potential_json_response) for check in additional_checks
+                ):
                     continue  # the json isn't valid
                 if spinner:
-                    spinner.succeed(f'bulk task {task_uuid} done')
+                    spinner.succeed(f"bulk task {task_uuid} done")
                 json_response = potential_json_response
             elif response.status_code == 401:
-                logger.debug('Refreshing expired Token')
+                logger.debug("Refreshing expired Token")
                 self.token_manager.process_auth_error(response.json())
             elif time() - start_time + back_off_time < timeout:
                 sleep(back_off_time)
                 back_off_time = min(back_off_time * 2, self.OCD_DTL_MAX_BACK_OFF_TIME)
             else:
                 if spinner:
-                    spinner.fail(f'bulk task {task_uuid} timeout')
+                    spinner.fail(f"bulk task {task_uuid} timeout")
                 logger.error()
                 raise TimeoutError(
-                    f'No bulk result after waiting {timeout / 60:.0f} mins\n'
+                    f"No bulk result after waiting {timeout / 60:.0f} mins\n"
                     f'task_uuid: "{task_uuid}"'
                 )
 
diff --git a/datalake_scripts/engines/get_engine.py b/datalake_scripts/engines/get_engine.py
index 2a424b4..f08ef89 100644
--- a/datalake_scripts/engines/get_engine.py
+++ b/datalake_scripts/engines/get_engine.py
@@ -13,7 +13,7 @@ class GetEngine(BaseEngine):
     """
 
     def _get_headers(self) -> dict:
-        return {'accept': 'application/json'}
+        return {"accept": "application/json"}
 
 
 class ThreatsSearch(GetEngine):
@@ -22,7 +22,7 @@ class ThreatsSearch(GetEngine):
     """
 
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('threats')
+        return self._build_url_for_endpoint("threats")
 
     def get_json(self, list_threats: list):
         """
@@ -31,25 +31,31 @@ def get_json(self, list_threats: list):
         """
         terminal_size = Endpoint._get_terminal_size()
         total_hash = len(list_threats)
-        dict_threat = {'count': total_hash, 'results': []}
+        dict_threat = {"count": total_hash, "results": []}
         list_of_lost_hashes = []
         for index, threat in enumerate(list_threats):
             request_url = self.url + threat
             response_dict = self.datalake_requests(
                 request_url,
-                'get',
+                "get",
                 self._get_headers(),
                 None,
             )
             final_dict = response_dict
-            if not response_dict.get('hashkey'):
+            if not response_dict.get("hashkey"):
                 list_of_lost_hashes.append(threat)
-                logger.info(f'{str(index).ljust(5)}:{threat.ljust(terminal_size - 11)}\x1b[0;30;41mERROR\x1b[0m')
+                logger.info(
+                    f"{str(index).ljust(5)}:{threat.ljust(terminal_size - 11)}\x1b[0;30;41mERROR\x1b[0m"
+                )
             else:
-                logger.info(f'{str(index).ljust(5)}:{threat.ljust(terminal_size - 10)}\x1b[0;30;42m OK \x1b[0m')
-                response_dict = self.datalake_requests(f'{request_url}/comments', 'get', self._get_headers(), None)
-                final_dict['comments'] = response_dict
-                dict_threat['results'].append(final_dict)
+                logger.info(
+                    f"{str(index).ljust(5)}:{threat.ljust(terminal_size - 10)}\x1b[0;30;42m OK \x1b[0m"
+                )
+                response_dict = self.datalake_requests(
+                    f"{request_url}/comments", "get", self._get_headers(), None
+                )
+                final_dict["comments"] = response_dict
+                dict_threat["results"].append(final_dict)
         return dict_threat, list_of_lost_hashes
 
 
@@ -59,13 +65,15 @@ class Threats(GetEngine):
     """
 
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('advanced-search')
+        return self._build_url_for_endpoint("advanced-search")
 
-    def get_threats(self, query_hash: str, limit=10, response_format="application/json") -> dict:
+    def get_threats(
+        self, query_hash: str, limit=10, response_format="application/json"
+    ) -> dict:
         url = self.url + query_hash
-        params = {'limit': limit}
+        params = {"limit": limit}
         req = PreparedRequest()  # Adding parameters using requests' tool
         req.prepare_url(url, params)
-        headers = {'Accept': response_format}
-        response = self.datalake_requests(req.url, 'get', headers=headers)
+        headers = {"Accept": response_format}
+        response = self.datalake_requests(req.url, "get", headers=headers)
         return response
diff --git a/datalake_scripts/engines/post_engine.py b/datalake_scripts/engines/post_engine.py
index 15d5456..aa207dd 100755
--- a/datalake_scripts/engines/post_engine.py
+++ b/datalake_scripts/engines/post_engine.py
@@ -15,54 +15,55 @@ class PostEngine(BaseEngine):
     """
     Common engine for all the POST endpoints.
     """
+
     authorized_atom_value = [
-        'apk',
-        'as',
-        'cc',
-        'crypto',
-        'cve',
-        'domain',
-        'email',
-        'file',
-        'fqdn',
-        'iban',
-        'ip',
-        'ip_range',
-        'paste',
-        'phone_number',
-        'regkey',
-        'ssl',
-        'url',
+        "apk",
+        "as",
+        "cc",
+        "crypto",
+        "cve",
+        "domain",
+        "email",
+        "file",
+        "fqdn",
+        "iban",
+        "ip",
+        "ip_range",
+        "paste",
+        "phone_number",
+        "regkey",
+        "ssl",
+        "url",
     ]
 
     authorized_threats_value = [
-        'ddos',
-        'fraud',
-        'hack',
-        'leak',
-        'malware',
-        'phishing',
-        'scam',
-        'scan',
-        'spam',
+        "ddos",
+        "fraud",
+        "hack",
+        "leak",
+        "malware",
+        "phishing",
+        "scam",
+        "scan",
+        "spam",
     ]
 
     def _find_in_dict(self, dict_input: dict, key: str, value: str) -> dict:
         """
         Return dict result if dict key is = to value
         """
-        if value == 'None':
+        if value == "None":
             return {}
         for entry in dict_input:
             if entry[key] == value:
                 return entry
 
     def _post_headers(self) -> dict:
-        return {'Accept': 'application/json', 'Content-Type': 'application/json'}
+        return {"Accept": "application/json", "Content-Type": "application/json"}
 
     @staticmethod
     def build_full_query_body(query_body):
-        if not isinstance(query_body, dict) or 'AND' not in query_body:
+        if not isinstance(query_body, dict) or "AND" not in query_body:
             raise ValueError('Query body is not valid: top level "AND" is missing')
         return query_body
 
@@ -73,34 +74,36 @@ class CommentsPost(PostEngine):
     """
 
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('comment')
+        return self._build_url_for_endpoint("comment")
 
-    def _post_comment(self, hashkey: str, comment: str, visibility: str = 'organization') -> dict:
+    def _post_comment(
+        self, hashkey: str, comment: str, visibility: str = "organization"
+    ) -> dict:
         """
         Post comment on threat hashkey
         """
         payload = {
-            'content': comment,
-            'visibility': visibility,
+            "content": comment,
+            "visibility": visibility,
         }
         url = self.url.format(hashkey=hashkey)
         logger.debug(url)
-        return self.datalake_requests(url, 'post', self._post_headers(), payload)
+        return self.datalake_requests(url, "post", self._post_headers(), payload)
 
     def post_comments(self, hashkeys: Set[str], comment: str, *, public=True) -> list:
         """
         Post comments on threats hashkey
         """
-        visibility = 'public' if public else 'organization'
+        visibility = "public" if public else "organization"
         return_value = []
         for hashkey in hashkeys:
             response = self._post_comment(hashkey, comment, visibility)
             if not response:
-                logger.warning(f'\x1b[6;30;41m{hashkey}: KO\x1b[0m')
-                return_value.append(hashkey + ': KO')
+                logger.warning(f"\x1b[6;30;41m{hashkey}: KO\x1b[0m")
+                return_value.append(hashkey + ": KO")
             else:
-                return_value.append(hashkey + ': OK')
-                logger.info(f'\x1b[6;30;42m{hashkey}: OK\x1b[0m')
+                return_value.append(hashkey + ": OK")
+                logger.info(f"\x1b[6;30;42m{hashkey}: OK\x1b[0m")
         return return_value
 
 
@@ -109,50 +112,67 @@ class BulkSearch(PostEngine, HandleBulkTaskMixin):
     Bulk search engines
     """
 
-    OCD_DTL_MAX_BULK_SEARCH_TIME = int(os.getenv('OCD_DTL_MAX_BULK_SEARCH_TIME', 3600))
+    OCD_DTL_MAX_BULK_SEARCH_TIME = int(os.getenv("OCD_DTL_MAX_BULK_SEARCH_TIME", 3600))
 
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('bulk-search')
+        return self._build_url_for_endpoint("bulk-search")
 
     def _handle_bulk_search_task(self, task_uuid):
-        retrieve_bulk_result_url = self._build_url_for_endpoint('retrieve-bulk-search')
-        return self.handle_bulk_task(task_uuid, retrieve_bulk_result_url, timeout=self.OCD_DTL_MAX_BULK_SEARCH_TIME)
-
-    def get_threats(self, query_hash: str = None, query_body: BaseEngine.Json = None, query_fields: List[str] = None) \
-            -> dict:
+        retrieve_bulk_result_url = self._build_url_for_endpoint("retrieve-bulk-search")
+        return self.handle_bulk_task(
+            task_uuid,
+            retrieve_bulk_result_url,
+            timeout=self.OCD_DTL_MAX_BULK_SEARCH_TIME,
+        )
+
+    def get_threats(
+        self,
+        query_hash: str = None,
+        query_body: BaseEngine.Json = None,
+        query_fields: List[str] = None,
+    ) -> dict:
         body = {"query_fields": query_fields} if query_fields else {}
         if query_body:
-            body['query_body'] = self.build_full_query_body(query_body)
+            body["query_body"] = self.build_full_query_body(query_body)
         else:
-            body['query_hash'] = query_hash
+            body["query_hash"] = query_hash
 
-        response = self.datalake_requests(self.url, 'post', post_body=body, headers=self._post_headers())
+        response = self.datalake_requests(
+            self.url, "post", post_body=body, headers=self._post_headers()
+        )
         if not response:
-            logger.error('No bulk search created, is the query_hash valid as well as the query_fields ?')
+            logger.error(
+                "No bulk search created, is the query_hash valid as well as the query_fields ?"
+            )
             return {}
-        return self._handle_bulk_search_task(task_uuid=response['task_uuid'])
+        return self._handle_bulk_search_task(task_uuid=response["task_uuid"])
 
 
 class BulkLookupThreats(PostEngine):
     """Bulk Lookup Threats"""
 
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('threats-bulk-lookup')
+        return self._build_url_for_endpoint("threats-bulk-lookup")
 
-    def bulk_lookup_threats(self, threats: BaseEngine.Json, additional_headers, hashkey_only: bool = True) -> dict:
+    def bulk_lookup_threats(
+        self, threats: BaseEngine.Json, additional_headers, hashkey_only: bool = True
+    ) -> dict:
         body = threats
-        body['hashkey_only'] = hashkey_only
-        return self.datalake_requests(self.url, 'post', {**self._post_headers(), **additional_headers}, body)
+        body["hashkey_only"] = hashkey_only
+        return self.datalake_requests(
+            self.url, "post", {**self._post_headers(), **additional_headers}, body
+        )
 
 
 class AtomValuesExtractor(PostEngine):
-
     def _build_url(self, endpoint_config: dict, environment: str):
-        return self._build_url_for_endpoint('atom-values-extract')
+        return self._build_url_for_endpoint("atom-values-extract")
 
-    def atom_values_extract(self, untyped_atoms: List[str], treat_hashes_like='file') -> dict:
+    def atom_values_extract(
+        self, untyped_atoms: List[str], treat_hashes_like="file"
+    ) -> dict:
         payload = {
-            'content': ' '.join(untyped_atoms),
-            'treat_hashes_like': treat_hashes_like
+            "content": " ".join(untyped_atoms),
+            "treat_hashes_like": treat_hashes_like,
         }
-        return self.datalake_requests(self.url, 'post', self._post_headers(), payload)
+        return self.datalake_requests(self.url, "post", self._post_headers(), payload)
diff --git a/datalake_scripts/helper_scripts/output_builder.py b/datalake_scripts/helper_scripts/output_builder.py
index 5cfda21..fbd5904 100644
--- a/datalake_scripts/helper_scripts/output_builder.py
+++ b/datalake_scripts/helper_scripts/output_builder.py
@@ -1,47 +1,58 @@
-
 class CsvBuilder:
-
     @staticmethod
-    def create_look_up_csv(api_response: dict, atom_type: str, has_details=True) -> list:
+    def create_look_up_csv(
+        api_response: dict, atom_type: str, has_details=True
+    ) -> list:
         """
         :param api_response: the api lookup response aggregated by atom value
         :param atom_type: the atom type given for the look up
         :param has_details: indicate if the api_response includes details or only found + hashkey
         """
-        minimum_header = 'hashkey,atom_type,atom_value_best_matching,atom_found'
-        extended_header = 'events_number,first_seen,last_updated,threat_types,ddos.score.risk,' \
-                          'fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,' \
-                          'phishing.score.risk,scam.score.risk,spam.score.risk,sources,tags,href_graph,' \
-                          'href_history,href_threat,href_threat_webGUI'
+        minimum_header = "hashkey,atom_type,atom_value_best_matching,atom_found"
+        extended_header = (
+            "events_number,first_seen,last_updated,threat_types,ddos.score.risk,"
+            "fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,"
+            "phishing.score.risk,scam.score.risk,spam.score.risk,sources,tags,href_graph,"
+            "href_history,href_threat,href_threat_webGUI"
+        )
         complete_csv = []
         if has_details:
-            complete_csv.append(minimum_header + ',' + extended_header)
+            complete_csv.append(minimum_header + "," + extended_header)
         else:
             complete_csv.append(minimum_header)
 
         for threat in api_response.keys():
             response = api_response[threat]
-            threat_found = response.get('threat_found', True)
+            threat_found = response.get("threat_found", True)
             if not has_details:
                 line = f"{response['hashkey']},{atom_type},{threat},{threat_found}"
             elif threat_found:
-                threat_scores = CsvBuilder._load_scores(response['scores'])
-                threat_types = ','.join(response['threat_types']) if 'threat_types' in response else None
+                threat_scores = CsvBuilder._load_scores(response["scores"])
+                threat_types = (
+                    ",".join(response["threat_types"])
+                    if "threat_types" in response
+                    else None
+                )
                 if threat_types:
                     threat_types = f'"{threat_types}"'
 
-                line = f"{response['hashkey']},{atom_type},{threat},{True}," \
-                       f"{CsvBuilder._count_events(response['sources'])},{response['first_seen']}," \
-                       f"{response['last_updated']},{threat_types}," \
-                       f"{threat_scores.get('ddos')},{threat_scores.get('fraud')}," \
-                       f"{threat_scores.get('hack')},{threat_scores.get('leak')},{threat_scores.get('malware')}," \
-                       f"{threat_scores.get('phishing')},{threat_scores.get('scam')},{threat_scores.get('spam')}," \
-                       f"{CsvBuilder._load_sources(response['sources'])}," \
-                       f"{CsvBuilder._create_tags_list(response['tags'])}," \
-                       f"{response['href_graph']},{response['href_history']},{response['href_threat']}," \
-                       f"{response['href_threat']}"
+                line = (
+                    f"{response['hashkey']},{atom_type},{threat},{True},"
+                    f"{CsvBuilder._count_events(response['sources'])},{response['first_seen']},"
+                    f"{response['last_updated']},{threat_types},"
+                    f"{threat_scores.get('ddos')},{threat_scores.get('fraud')},"
+                    f"{threat_scores.get('hack')},{threat_scores.get('leak')},{threat_scores.get('malware')},"
+                    f"{threat_scores.get('phishing')},{threat_scores.get('scam')},{threat_scores.get('spam')},"
+                    f"{CsvBuilder._load_sources(response['sources'])},"
+                    f"{CsvBuilder._create_tags_list(response['tags'])},"
+                    f"{response['href_graph']},{response['href_history']},{response['href_threat']},"
+                    f"{response['href_threat']}"
+                )
             else:  # Threat not found with details
-                line = f"{response['hashkey']},{atom_type},{threat},{False}," + ','.join([f"{None}"] * 18)
+                line = (
+                    f"{response['hashkey']},{atom_type},{threat},{False},"
+                    + ",".join([f"{None}"] * 18)
+                )
 
             complete_csv.append(line)
 
@@ -51,14 +62,14 @@ def create_look_up_csv(api_response: dict, atom_type: str, has_details=True) ->
     def _count_events(sources):
         tot = 0
         for source in sources:
-            tot += int(source['count'])
+            tot += int(source["count"])
         return tot
 
     @staticmethod
     def _load_scores(scores):
         threat_scoring = {}
         for score in scores:
-            threat_scoring[score['threat_type']] = score['score']['risk']
+            threat_scoring[score["threat_type"]] = score["score"]["risk"]
         return threat_scoring
 
     @staticmethod
@@ -69,5 +80,5 @@ def _load_sources(sources):
     def _create_tags_list(tags):
         if not tags:
             return None
-        tags = ','.join([tag['name'] for tag in tags]).replace('"', '""')
-        return f"\"{tags}\""
+        tags = ",".join([tag["name"] for tag in tags]).replace('"', '""')
+        return f'"{tags}"'
diff --git a/datalake_scripts/helper_scripts/utils.py b/datalake_scripts/helper_scripts/utils.py
index d48e2cd..339f549 100644
--- a/datalake_scripts/helper_scripts/utils.py
+++ b/datalake_scripts/helper_scripts/utils.py
@@ -8,23 +8,25 @@
 
 def split_list(list_to_split: list, slice_size: int) -> Generator[list, None, None]:
     for i in range(0, len(list_to_split), slice_size):
-        yield list_to_split[i:i + slice_size]
+        yield list_to_split[i : i + slice_size]
 
 
-def load_csv(file_name: str, delimiter: str = ',', column: int = 0) -> List[str]:
+def load_csv(file_name: str, delimiter: str = ",", column: int = 0) -> List[str]:
     """
     Load a CSV file and return one column in a list
     """
     ret = []
     i = 0  # Keep current row number in case of exception
     try:
-        with open(file_name, 'r') as csvfile:
+        with open(file_name, "r") as csvfile:
             csv_reader = csv.reader(csvfile, delimiter=delimiter)
             for i, row in enumerate(csv_reader):
-                if row and not row[0].startswith('#'):  # We discard comments
+                if row and not row[0].startswith("#"):  # We discard comments
                     ret.append(row[column])
     except IndexError:
-        raise ValueError(f'Csv passed does not have enough columns on line {i} or the delimiter is incorrect')
+        raise ValueError(
+            f"Csv passed does not have enough columns on line {i} or the delimiter is incorrect"
+        )
     return ret
 
 
@@ -32,7 +34,7 @@ def load_list(file_name: str) -> list:
     """
     Load a file and retrieve each line in a list
     """
-    return [line.rstrip('\n') for line in open(file_name)]
+    return [line.rstrip("\n") for line in open(file_name)]
 
 
 def load_json(file_name: str) -> dict:
@@ -51,12 +53,12 @@ def save_output(file_name: str, data):
     If data is a list, file format will be txt.
     Else it will be saved as it comes.
     """
-    with open(file_name, 'w+') as file_to_write:
+    with open(file_name, "w+") as file_to_write:
         if isinstance(data, dict):
             file_to_write.write(json.dumps(data, sort_keys=True, indent=2))
         elif isinstance(data, list):
             for item in data:
-                file_to_write.write(f'{item}\n')
+                file_to_write.write(f"{item}\n")
         else:
             file_to_write.write(data)
 
@@ -66,8 +68,8 @@ def parse_atom_type_or_exit(atom_type: str) -> AtomType:
         return AtomType[atom_type.upper()]
     except KeyError:
         logger.fatal(
-            f'{atom_type} atom type is not supported, '
-            f'use one of {[atom_type_.name.lower() for atom_type_ in AtomType]}'
+            f"{atom_type} atom type is not supported, "
+            f"use one of {[atom_type_.name.lower() for atom_type_ in AtomType]}"
         )
         exit(1)
 
@@ -79,15 +81,19 @@ def parse_threat_types(threat_types: list) -> list:
         try:
             threat_type = ThreatType(threat_types[i])
         except ValueError:
-            raise ValueError(f'Unknow threat_types: {threat_types[i]} {score},'
-                             f' please use only value in {[e.value for e in ThreatType]}.')
+            raise ValueError(
+                f"Unknow threat_types: {threat_types[i]} {score},"
+                f" please use only value in {[e.value for e in ThreatType]}."
+            )
         if score < 0 or score > 100:
-            raise ValueError(f'Wrong score: {threat_type} {score}, '
-                             'please use only value in [0, 100].')
+            raise ValueError(
+                f"Wrong score: {threat_type} {score}, "
+                "please use only value in [0, 100]."
+            )
         threat_type_parsed[threat_type] = score
     threat_type_formatted = []
     for key, value in threat_type_parsed.items():
-        threat_type_formatted.append({'threat_type': key, 'score': value})
+        threat_type_formatted.append({"threat_type": key, "score": value})
     return threat_type_formatted
 
 
diff --git a/datalake_scripts/scripts/add_comments.py b/datalake_scripts/scripts/add_comments.py
index f2bfb2a..597c7f1 100644
--- a/datalake_scripts/scripts/add_comments.py
+++ b/datalake_scripts/scripts/add_comments.py
@@ -11,26 +11,28 @@
 def main(override_args=None):
     """Method to start the script"""
     # Load initial args
-    parser = BaseScripts.start('Add tags and/or comments to a specified list of hashkeys.')
+    parser = BaseScripts.start(
+        "Add tags and/or comments to a specified list of hashkeys."
+    )
     parser.add_argument(
-        'hashkeys',
-        help='hashkeys of the threat to add tags and/or the comment',
-        nargs='*',
+        "hashkeys",
+        help="hashkeys of the threat to add tags and/or the comment",
+        nargs="*",
     )
     parser.add_argument(
-        '-i',
-        '--input_file',
-        help='hashkey txt file, with one hashkey by line',
+        "-i",
+        "--input_file",
+        help="hashkey txt file, with one hashkey by line",
     )
     parser.add_argument(
-        '-p',
-        '--public',
-        help='set the visibility to public',
-        action='store_true',
+        "-p",
+        "--public",
+        help="set the visibility to public",
+        action="store_true",
     )
     parser.add_argument(
-        '--comment',
-        help='add the given comment',
+        "--comment",
+        help="add the given comment",
         required=True,
     )
 
@@ -60,16 +62,16 @@ def main(override_args=None):
 
     if args.output:
         save_output(args.output, response_dict)
-        logger.debug(f'Results saved in {args.output}\n')
-    logger.debug(f'END: add_comments.py')
+        logger.debug(f"Results saved in {args.output}\n")
+    logger.debug(f"END: add_comments.py")
 
 
 def retrieve_hashkeys_from_file(input_file, hashkeys):
-    with open(input_file, 'r') as input_file:
+    with open(input_file, "r") as input_file:
         for line in input_file:
             if line:
                 hashkeys.add(line.strip())
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/add_tags.py b/datalake_scripts/scripts/add_tags.py
index f2d7579..81c102d 100644
--- a/datalake_scripts/scripts/add_tags.py
+++ b/datalake_scripts/scripts/add_tags.py
@@ -10,27 +10,29 @@ def main(override_args=None):
     """Method to start the script"""
 
     # Load initial args
-    parser = BaseScripts.start('Add tags and/or comments to a specified list of hashkeys.')
+    parser = BaseScripts.start(
+        "Add tags and/or comments to a specified list of hashkeys."
+    )
     parser.add_argument(
-        'hashkeys',
-        help='hashkeys of the threat to add tags and/or the comment',
-        nargs='*',
+        "hashkeys",
+        help="hashkeys of the threat to add tags and/or the comment",
+        nargs="*",
     )
     parser.add_argument(
-        '-i',
-        '--input_file',
-        help='hashkey txt file, with one hashkey by line',
+        "-i",
+        "--input_file",
+        help="hashkey txt file, with one hashkey by line",
     )
     parser.add_argument(
-        '-p',
-        '--public',
-        help='set the visibility to public',
-        action='store_true',
+        "-p",
+        "--public",
+        help="set the visibility to public",
+        action="store_true",
     )
     parser.add_argument(
-        '--tags',
-        nargs='+',
-        help='add a list of tags',
+        "--tags",
+        nargs="+",
+        help="add a list of tags",
         required=True,
     )
 
@@ -48,17 +50,12 @@ def main(override_args=None):
         retrieve_hashkeys_from_file(args.input_file, hashkeys)
 
     dtl = Datalake(env=args.env, log_level=args.loglevel)
-    response_dict = post_tags(
-        hashkeys,
-        args.tags,
-        args.public,
-        dtl
-    )
+    response_dict = post_tags(hashkeys, args.tags, args.public, dtl)
 
     if args.output:
         save_output(args.output, response_dict)
-        logger.debug(f'Results saved in {args.output}\n')
-    logger.debug(f'END: add_tags.py')
+        logger.debug(f"Results saved in {args.output}\n")
+    logger.debug(f"END: add_tags.py")
 
 
 def post_tags(hashkeys, tags, public, dtl):
@@ -67,21 +64,21 @@ def post_tags(hashkeys, tags, public, dtl):
         try:
             dtl.Tags.add_to_threat(hashkey, tags, public)
         except ValueError as e:
-            logger.warning('\x1b[6;30;41m' + hashkey + ': FAILED\x1b[0m')
-            logger.debug('\x1b[6;30;41m' + hashkey + ': FAILED : ' + str(e) + '\x1b[0m')
-            return_value.append(hashkey + ': FAILED')
+            logger.warning("\x1b[6;30;41m" + hashkey + ": FAILED\x1b[0m")
+            logger.debug("\x1b[6;30;41m" + hashkey + ": FAILED : " + str(e) + "\x1b[0m")
+            return_value.append(hashkey + ": FAILED")
         else:
-            return_value.append(hashkey + ': OK')
-            logger.info('\x1b[6;30;42m' + hashkey + ': OK\x1b[0m')
+            return_value.append(hashkey + ": OK")
+            logger.info("\x1b[6;30;42m" + hashkey + ": OK\x1b[0m")
     return return_value
 
 
 def retrieve_hashkeys_from_file(input_file, hashkeys):
-    with open(input_file, 'r') as input_file:
+    with open(input_file, "r") as input_file:
         for line in input_file:
             if line:
                 hashkeys.add(line.strip())
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/add_threats.py b/datalake_scripts/scripts/add_threats.py
index 2c14601..3aa8f4f 100644
--- a/datalake_scripts/scripts/add_threats.py
+++ b/datalake_scripts/scripts/add_threats.py
@@ -9,97 +9,127 @@
 from datalake.common.logger import logger
 from datalake.endpoints import Endpoint
 from datalake_scripts.common.base_script import BaseScripts
-from datalake_scripts.helper_scripts.utils import load_csv, load_list, save_output, parse_threat_types, flatten_list
-from datalake import Hashes, FileAtom, AndroidApp, ApkAtom, AsAtom, CcAtom, CryptoAtom, CveAtom, Jarm, DomainAtom, EmailFlow, EmailAtom, FqdnAtom, IbanAtom, IpService, IpAtom, IpRangeAtom, PasteAtom, PhoneNumberAtom, RegKeyAtom, SslAtom, UrlAtom
+from datalake_scripts.helper_scripts.utils import (
+    load_csv,
+    load_list,
+    save_output,
+    parse_threat_types,
+    flatten_list,
+)
+from datalake import (
+    Hashes,
+    FileAtom,
+    AndroidApp,
+    ApkAtom,
+    AsAtom,
+    CcAtom,
+    CryptoAtom,
+    CveAtom,
+    Jarm,
+    DomainAtom,
+    EmailFlow,
+    EmailAtom,
+    FqdnAtom,
+    IbanAtom,
+    IpService,
+    IpAtom,
+    IpRangeAtom,
+    PasteAtom,
+    PhoneNumberAtom,
+    RegKeyAtom,
+    SslAtom,
+    UrlAtom,
+)
+
 
 def main(override_args=None):
     """Method to start the script"""
     # Load initial args
-    parser = BaseScripts.start('Submit a new threat to Datalake from a file')
-    required_named = parser.add_argument_group('required arguments')
-    csv_controle = parser.add_argument_group('CSV control arguments')
+    parser = BaseScripts.start("Submit a new threat to Datalake from a file")
+    required_named = parser.add_argument_group("required arguments")
+    csv_controle = parser.add_argument_group("CSV control arguments")
     required_named.add_argument(
-        '-i',
-        '--input',
-        help='read threats to add from FILE',
+        "-i",
+        "--input",
+        help="read threats to add from FILE",
         required=True,
     )
     required_named.add_argument(
-        '-a',
-        '--atom_type',
-        help='set it to define the atom type',
+        "-a",
+        "--atom_type",
+        help="set it to define the atom type",
         required=True,
     )
     csv_controle.add_argument(
-        '--is_csv',
-        help='set if the file input is a CSV',
-        action='store_true',
+        "--is_csv",
+        help="set if the file input is a CSV",
+        action="store_true",
     )
     csv_controle.add_argument(
-        '-d',
-        '--delimiter',
-        help='set the delimiter of the CSV file',
-        default=',',
+        "-d",
+        "--delimiter",
+        help="set the delimiter of the CSV file",
+        default=",",
     )
     csv_controle.add_argument(
-        '-c',
-        '--column',
-        help='select column of the CSV file, starting at 1',
+        "-c",
+        "--column",
+        help="select column of the CSV file, starting at 1",
         type=int,
         default=1,
     )
     parser.add_argument(
-        '-p',
-        '--public',
-        help='set the visibility to public',
-        action='store_true',
+        "-p",
+        "--public",
+        help="set the visibility to public",
+        action="store_true",
     )
     parser.add_argument(
-        '-w',
-        '--whitelist',
-        help='set it to define the added threats as whitelist',
-        action='store_true',
+        "-w",
+        "--whitelist",
+        help="set it to define the added threats as whitelist",
+        action="store_true",
     )
     parser.add_argument(
-        '-t',
-        '--threat_types',
-        nargs='+',
-        help='choose specific threat types and their score, like: ddos 50 scam 15',
+        "-t",
+        "--threat_types",
+        nargs="+",
+        help="choose specific threat types and their score, like: ddos 50 scam 15",
         default=[],
-        action='append',
+        action="append",
     )
     parser.add_argument(
-        '--tag',
-        nargs='+',
-        help='add a list of tags',
+        "--tag",
+        nargs="+",
+        help="add a list of tags",
         default=[],
     )
     parser.add_argument(
-        '--link',
-        help='add link as external_analysis_link',
-        nargs='+',
+        "--link",
+        help="add link as external_analysis_link",
+        nargs="+",
     )
     parser.add_argument(
-        '--permanent',
-        help='sets override_type to permanent. Scores won\'t be updated by the algorithm. Default is temporary',
-        action='store_true',
+        "--permanent",
+        help="sets override_type to permanent. Scores won't be updated by the algorithm. Default is temporary",
+        action="store_true",
     )
     parser.add_argument(
-        '--lock',
-        help='sets override_type to lock. Scores won\'t be updated by the algorithm for three months. Default is '
-             'temporary',
-        action='store_true',
+        "--lock",
+        help="sets override_type to lock. Scores won't be updated by the algorithm for three months. Default is "
+        "temporary",
+        action="store_true",
     )
     parser.add_argument(
-        '--no-bulk',
-        help='force an api call for each threats, useful to retrieve the details of threats created',
-        action='store_true',
+        "--no-bulk",
+        help="force an api call for each threats, useful to retrieve the details of threats created",
+        action="store_true",
     )
     if override_args:
         args = parser.parse_args(override_args)
     else:
         args = parser.parse_args()
-    logger.debug(f'START: add_new_threats.py')
+    logger.debug(f"START: add_new_threats.py")
 
     if not args.threat_types and not args.whitelist:
         parser.error("threat types is required if the atom is not for whitelisting")
@@ -123,9 +153,11 @@ def main(override_args=None):
     else:
         list_new_threats = load_list(args.input)
         if not list_new_threats:
-            raise parser.error('No atom found in the input file.')
+            raise parser.error("No atom found in the input file.")
     list_new_threats = defang_threats(list_new_threats, args.atom_type)
-    list_new_threats = list(OrderedDict.fromkeys(list_new_threats))  # removing duplicates while preserving order
+    list_new_threats = list(
+        OrderedDict.fromkeys(list_new_threats)
+    )  # removing duplicates while preserving order
     args.threat_types = flatten_list(args.threat_types)
     threat_types = parse_threat_types(args.threat_types)
     atom_type = AtomType[args.atom_type.upper()]
@@ -146,14 +178,24 @@ def main(override_args=None):
                     tags=args.tag,
                 )
                 threat_response.append(res)
-                logger.info(f'{threat.ljust(terminal_size - 6, " ")} \x1b[0;30;42m  OK  \x1b[0m')
+                logger.info(
+                    f'{threat.ljust(terminal_size - 6, " ")} \x1b[0;30;42m  OK  \x1b[0m'
+                )
             except ValueError as ve:  # Wrong atom type most likely
                 error_message = str(ve)
-                threat_response.append({"atom_value": threat, "failed": True, "error_message": error_message})
-                logger.info(f'{threat.ljust(terminal_size - 6, " ")} '
-                            f'\x1b[0;30;41m  KO  \x1b[0m')
+                threat_response.append(
+                    {
+                        "atom_value": threat,
+                        "failed": True,
+                        "error_message": error_message,
+                    }
+                )
+                logger.info(
+                    f'{threat.ljust(terminal_size - 6, " ")} '
+                    f"\x1b[0;30;41m  KO  \x1b[0m"
+                )
     else:
-        spinner = Halo(text=f'Creating threats', spinner='dots')
+        spinner = Halo(text=f"Creating threats", spinner="dots")
         spinner.start()
         threat_response = dtl.Threats.add_threats(
             atom_list=list_new_threats,
@@ -163,82 +205,96 @@ def main(override_args=None):
             whitelist=args.whitelist,
             public=args.public,
             tags=args.tag,
-            external_analysis_link=args.link
+            external_analysis_link=args.link,
         )
         spinner.succeed()
         failed = []
         failed_counter = 0
         created_counter = 0
         for batch_res in threat_response:
-            failed.extend(batch_res['failed'])
-            for success in batch_res['success']:
-                for val_created in success['created_atom_values']:
+            failed.extend(batch_res["failed"])
+            for success in batch_res["success"]:
+                for val_created in success["created_atom_values"]:
                     created_counter += 1
-                    logger.info(f'{val_created.ljust(terminal_size - 6, " ")} \x1b[0;30;42m  OK  \x1b[0m')
+                    logger.info(
+                        f'{val_created.ljust(terminal_size - 6, " ")} \x1b[0;30;42m  OK  \x1b[0m'
+                    )
         for failed_obj in failed:
-            for failed_atom_val in failed_obj['failed_atom_values']:
+            for failed_atom_val in failed_obj["failed_atom_values"]:
                 failed_counter += 1
-                logger.info(f'Creation failed for value {failed_atom_val.ljust(terminal_size - 6, " ")} \x1b[0;30;4\
-                1m  KO  \x1b[0m')
+                logger.info(
+                    f'Creation failed for value {failed_atom_val.ljust(terminal_size - 6, " ")} \x1b[0;30;4\
+                1m  KO  \x1b[0m'
+                )
         logger.info(
-            f'Number of batches: {len(threat_response)}\n'
-            f'Created threats: {created_counter}\n'
-            f'Failed threat creation: {failed_counter}'
+            f"Number of batches: {len(threat_response)}\n"
+            f"Created threats: {created_counter}\n"
+            f"Failed threat creation: {failed_counter}"
         )
 
     if args.output:
         save_output(args.output, json.dumps(threat_response))
-        logger.debug(f'Results saved in {args.output}\n')
-    logger.debug(f'END: add_new_threats.py')
+        logger.debug(f"Results saved in {args.output}\n")
+    logger.debug(f"END: add_new_threats.py")
 
 
 def defang_threats(threats, atom_type):
     defanged = []
     # matches urls like http://www.website.com:444/file.html
-    standard_url_regex = re.compile(r'^(https?://)[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$')
+    standard_url_regex = re.compile(
+        r"^(https?://)[a-z0-9]+([\-.][a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$"
+    )
     # matches urls like http://185.25.5.3:8080/result.php (ipv4 or ipv6)
-    ip_url_regex = re.compile(r'^(https?://)[0-9a-zA-Z]{1,4}([.:][0-9a-zA-Z]{1,4}){3,7}(:[0-9]{1,5})?(/.*)?$')
+    ip_url_regex = re.compile(
+        r"^(https?://)[0-9a-zA-Z]{1,4}([.:][0-9a-zA-Z]{1,4}){3,7}(:[0-9]{1,5})?(/.*)?$"
+    )
     for threat in threats:
         unmodified_threat = threat
-        threat = threat.replace('[.]', '.')
-        threat = threat.replace('(.)', '.')
-        if atom_type == 'url':
-            if not threat.startswith('http'):
-                if threat.startswith('hxxp'):
-                    threat = threat.replace('hxxp', 'http')
-                elif threat.startswith('ftp'):
-                    threat = threat.replace('ftp', 'http')
-                elif threat.startswith('sftp'):
-                    threat = threat.replace('sftp', 'https')
+        threat = threat.replace("[.]", ".")
+        threat = threat.replace("(.)", ".")
+        if atom_type == "url":
+            if not threat.startswith("http"):
+                if threat.startswith("hxxp"):
+                    threat = threat.replace("hxxp", "http")
+                elif threat.startswith("ftp"):
+                    threat = threat.replace("ftp", "http")
+                elif threat.startswith("sftp"):
+                    threat = threat.replace("sftp", "https")
                 else:
-                    threat = 'http://' + threat
+                    threat = "http://" + threat
             if not standard_url_regex.match(threat) and not ip_url_regex.match(threat):
-                logger.warning(f'\'{unmodified_threat}\' has been modified as \'{threat}\' but is still not recognized'
-                               f' as an url. Skipping this line')
+                logger.warning(
+                    f"'{unmodified_threat}' has been modified as '{threat}' but is still not recognized"
+                    f" as an url. Skipping this line"
+                )
                 continue
             if unmodified_threat != threat:
-                logger.info(f'\'{unmodified_threat}\' has been modified as \'{threat}\'')
+                logger.info(f"'{unmodified_threat}' has been modified as '{threat}'")
         defanged.append(threat)
     return defanged
 
 
 def hash_to_name(hash_):
-    hash_list = {32: 'md5', 40: 'sha1', 64: 'sha256', 128: 'sha512'}
-    return hash_list.get(len(hash_), 'ssdeep')
+    hash_list = {32: "md5", 40: "sha1", 64: "sha256", 128: "sha512"}
+    return hash_list.get(len(hash_), "ssdeep")
 
 
 def _build_threat_from_atom_type(value, atom_type, link=None):
     if atom_type == AtomType.APK:
-        package_name, apk_hash = value.split(',')
+        package_name, apk_hash = value.split(",")
         hashes = Hashes(**{hash_to_name(apk_hash): apk_hash})
-        atom = ApkAtom(external_analysis_link=link, android=AndroidApp(package_name), hashes=hashes)
+        atom = ApkAtom(
+            external_analysis_link=link, android=AndroidApp(package_name), hashes=hashes
+        )
     elif atom_type == AtomType.AS:
         atom = AsAtom(external_analysis_link=link, asn=value)
     elif atom_type == AtomType.CC:
         atom = CcAtom(external_analysis_link=link, number=value)
     elif atom_type == AtomType.CRYPTO:
         address, network = value.split()
-        atom = CryptoAtom(external_analysis_link=link, crypto_address=address, crypto_network=network)
+        atom = CryptoAtom(
+            external_analysis_link=link, crypto_address=address, crypto_network=network
+        )
     elif atom_type == AtomType.CVE:
         atom = CveAtom(external_analysis_link=link, cve_id=value)
     elif atom_type == AtomType.DOMAIN:
@@ -259,7 +315,11 @@ def _build_threat_from_atom_type(value, atom_type, link=None):
     elif atom_type == AtomType.PASTE:
         atom = PasteAtom(external_analysis_link=link, url=value)
     elif atom_type == AtomType.PHONE_NUMBER:
-        key = 'international_phone_number' if value.startswith('+') else 'national_phone_number'
+        key = (
+            "international_phone_number"
+            if value.startswith("+")
+            else "national_phone_number"
+        )
         atom = PhoneNumberAtom(external_analysis_link=link, **{key: value})
     elif atom_type == AtomType.REGKEY:
         atom = RegKeyAtom(external_analysis_link=link, path=value)
@@ -271,5 +331,5 @@ def _build_threat_from_atom_type(value, atom_type, link=None):
     return atom
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/advanced_search.py b/datalake_scripts/scripts/advanced_search.py
index 3f32c48..1964ddc 100644
--- a/datalake_scripts/scripts/advanced_search.py
+++ b/datalake_scripts/scripts/advanced_search.py
@@ -7,66 +7,75 @@
 
 
 def main(override_args=None):
-    parser = BaseScripts.start('Gets threats from given query body or query hash.', output_file_required=True)
-    parser.add_argument(
-        '-i',
-        '--input',
-        help='read query body from a json file'
+    parser = BaseScripts.start(
+        "Gets threats from given query body or query hash.", output_file_required=True
     )
+    parser.add_argument("-i", "--input", help="read query body from a json file")
     parser.add_argument(
-        '--query-hash',
-        help='sets the query hash for the advanced search'
+        "--query-hash", help="sets the query hash for the advanced search"
     )
     parser.add_argument(
-        '-l',
-        '--limit',
-        help='defines how many items will be returned in one page slice. Accepted values: 0 to 5000, default is 20',
+        "-l",
+        "--limit",
+        help="defines how many items will be returned in one page slice. Accepted values: 0 to 5000, default is 20",
         type=int,
-        default=20
+        default=20,
     )
     parser.add_argument(
-        '--offset',
-        help='defines an index of the first requested item. Accepted values: 0 and bigger, default is 0.',
+        "--offset",
+        help="defines an index of the first requested item. Accepted values: 0 and bigger, default is 0.",
         type=int,
-        default=0
+        default=0,
     )
     parser.add_argument(
-        '-ot',
-        '--output-type',
-        help='sets the output type desired {json, csv, stix, misp}. Default is json',
-        default='json'
+        "-ot",
+        "--output-type",
+        help="sets the output type desired {json, csv, stix, misp}. Default is json",
+        default="json",
     )
     parser.add_argument(
-        '--ordering',
+        "--ordering",
         help='threat field to filter on. To sort the results by relevance (if any "search" is applied), just skip '
-             'this field. To use the reversed order, use minus, i.e. --ordering="-last_updated" in your command line.'
+        'this field. To use the reversed order, use minus, i.e. --ordering="-last_updated" in your command line.',
     )
 
     if override_args:
         args = parser.parse_args(override_args)
     else:
         args = parser.parse_args()
-    logger.debug(f'START: advanced_search.py')
+    logger.debug(f"START: advanced_search.py")
     if bool(args.input) == bool(args.query_hash):
-        raise ValueError('Either an input file with a query body or a query hash needs to be provided.')
+        raise ValueError(
+            "Either an input file with a query body or a query hash needs to be provided."
+        )
     try:
         output_type = Output[args.output_type.upper()]
     except KeyError:
-        logger.error('Not supported output, please use json, stix, misp or csv')
+        logger.error("Not supported output, please use json, stix, misp or csv")
         exit(1)
 
     dtl = Datalake(env=args.env, log_level=args.loglevel)
     if args.input:
         query_body = load_json(args.input)
-        resp = dtl.AdvancedSearch.advanced_search_from_query_body(query_body, limit=args.limit, offset=args.offset,
-                                                                  output=output_type, ordering=[args.ordering])
+        resp = dtl.AdvancedSearch.advanced_search_from_query_body(
+            query_body,
+            limit=args.limit,
+            offset=args.offset,
+            output=output_type,
+            ordering=[args.ordering],
+        )
     else:
-        resp = dtl.AdvancedSearch.advanced_search_from_query_hash(args.query_hash, limit=args.limit, offset=args.offset,
-                                                                  output=output_type, ordering=[args.ordering])
+        resp = dtl.AdvancedSearch.advanced_search_from_query_hash(
+            args.query_hash,
+            limit=args.limit,
+            offset=args.offset,
+            output=output_type,
+            ordering=[args.ordering],
+        )
     save_output(args.output, resp)
-    logger.info(f'\x1b[0;30;42m OK: MATCHING THREATS SAVED IN {args.output} \x1b[0m')
-    logger.debug(f'END: advanced_search.py')
+    logger.info(f"\x1b[0;30;42m OK: MATCHING THREATS SAVED IN {args.output} \x1b[0m")
+    logger.debug(f"END: advanced_search.py")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/bulk_lookup_threats.py b/datalake_scripts/scripts/bulk_lookup_threats.py
index a533929..9c1299a 100755
--- a/datalake_scripts/scripts/bulk_lookup_threats.py
+++ b/datalake_scripts/scripts/bulk_lookup_threats.py
@@ -10,8 +10,8 @@
 from datalake_scripts.helper_scripts.utils import split_input_file, save_output
 from urllib.parse import urlparse, urlsplit, urlunsplit
 
-SUBCOMMAND_NAME = 'bulk_lookup_threats'
-UNTYPED_ATOM_TYPE = 'untyped'
+SUBCOMMAND_NAME = "bulk_lookup_threats"
+UNTYPED_ATOM_TYPE = "untyped"
 ATOM_TYPES_FLAGS = [atom_type.name.lower() for atom_type in AtomType]
 
 
@@ -19,52 +19,54 @@ def main(override_args=None):
     """Method to start the script"""
 
     # Load initial args
-    parser = BaseScripts.start('Gets threats or hashkeys from given atom types and atom values.')
-    supported_atom_types = parser.add_argument_group('Supported Atom Types')
+    parser = BaseScripts.start(
+        "Gets threats or hashkeys from given atom types and atom values."
+    )
+    supported_atom_types = parser.add_argument_group("Supported Atom Types")
 
     parser.add_argument(
-        'untyped_atoms',
-        help='untyped atom values to lookup. Useful when you do not know what is the atom type',
-        nargs='*',
+        "untyped_atoms",
+        help="untyped atom values to lookup. Useful when you do not know what is the atom type",
+        nargs="*",
     )
     for atom_type in ATOM_TYPES_FLAGS:
         supported_atom_types.add_argument(
-            f'--{atom_type}',
-            action='append',
-            help=f'set a single {atom_type} atom type with its value',
+            f"--{atom_type}",
+            action="append",
+            help=f"set a single {atom_type} atom type with its value",
         )
     parser.add_argument(
-        '-ad',
-        '--atom-details',
-        dest='hashkey_only',
+        "-ad",
+        "--atom-details",
+        dest="hashkey_only",
         default=True,
-        action='store_false',
-        help='returns threats full details',
+        action="store_false",
+        help="returns threats full details",
     )
     parser.add_argument(
-        '-i',
-        '--input',
-        action='append',
-        help='read threats to add from FILE. [atomtype:path/to/file.txt]',
+        "-i",
+        "--input",
+        action="append",
+        help="read threats to add from FILE. [atomtype:path/to/file.txt]",
     )
     parser.add_argument(
-        '-ot',
-        '--output-type',
-        help='set to the output type desired {json,csv}. Default is json',
+        "-ot",
+        "--output-type",
+        help="set to the output type desired {json,csv}. Default is json",
     )
 
     if override_args:
         args = parser.parse_args(override_args)
     else:
         args = parser.parse_args()
-    logger.debug(f'START: bulk_lookup_threats.py')
+    logger.debug(f"START: bulk_lookup_threats.py")
 
     output_type = Output.JSON
     if args.output_type:
         try:
             output_type = Output[args.output_type.upper()]
         except KeyError:
-            logger.error('Not supported output, please use either json or csv')
+            logger.error("Not supported output, please use either json or csv")
             exit(1)
     # to gather all typed atoms passed by arguments and input files
     typed_atoms = {}
@@ -78,14 +80,18 @@ def main(override_args=None):
             typed_atoms[flag] = atom_values
             has_typed_atoms = True
     # validate that at least there is one untyped atom or one atom or one input file
-    if (not has_typed_atoms and not has_file and not args.untyped_atoms) or (SUBCOMMAND_NAME in args.untyped_atoms):
-        parser.error("you must provide at least one of following: untyped atom, atom type, input file.")
+    if (not has_typed_atoms and not has_file and not args.untyped_atoms) or (
+        SUBCOMMAND_NAME in args.untyped_atoms
+    ):
+        parser.error(
+            "you must provide at least one of following: untyped atom, atom type, input file."
+        )
 
     # load api_endpoints and tokens
     dtl = Datalake(env=args.env, log_level=args.loglevel)
     spinner = None
     if logger.isEnabledFor(logging.INFO):
-        spinner = Halo(text=f'Parsing input...', spinner='dots')
+        spinner = Halo(text=f"Parsing input...", spinner="dots")
         spinner.start()
     hashkey_only = args.hashkey_only
     untyped_atoms = args.untyped_atoms or []
@@ -104,36 +110,40 @@ def main(override_args=None):
     if has_file:
         for input_file in args.input:
             file_atom_type, filename = get_atom_type_from_filename(input_file)
-            logger.debug(f'file {filename} was recognized as {file_atom_type}')
+            logger.debug(f"file {filename} was recognized as {file_atom_type}")
             if file_atom_type == UNTYPED_ATOM_TYPE:
                 for atom_chunk in split_input_file(filename, size_limit):
                     untyped_atoms = atom_chunk
-                    typed_atoms_to_look_up = join_dicts(lookup_atom_types(dtl, untyped_atoms), typed_atoms_to_look_up)
+                    typed_atoms_to_look_up = join_dicts(
+                        lookup_atom_types(dtl, untyped_atoms), typed_atoms_to_look_up
+                    )
             else:
                 for atom_chunk in split_input_file(filename, size_limit):
-                    typed_atoms_to_look_up = join_dicts({file_atom_type: atom_chunk}, typed_atoms_to_look_up)
+                    typed_atoms_to_look_up = join_dicts(
+                        {file_atom_type: atom_chunk}, typed_atoms_to_look_up
+                    )
 
     # Query each atom type
     full_response = {}
     if spinner:
-        spinner.text = f'Executing bulk search...'
+        spinner.text = f"Executing bulk search..."
     for atom_type, atoms in typed_atoms_to_look_up.items():
         response = dtl.Threats.bulk_lookup(
             atom_values=atoms,
             atom_type=AtomType[atom_type.upper()],
             hashkey_only=hashkey_only,
             output=output_type,
-            return_search_hashkey=True
+            return_search_hashkey=True,
         )
         full_response = aggregate_csv_or_json_api_response(full_response, response)
     if spinner:
-        spinner.succeed('Done.')
+        spinner.succeed("Done.")
     if args.output:
         save_output(args.output, full_response)
-        logger.debug(f'Results saved in {args.output}\n')
+        logger.debug(f"Results saved in {args.output}\n")
     else:
         pretty_print(full_response, args.output_type, args.env, dtl)
-    logger.debug(f'END: lookup_threats.py')
+    logger.debug(f"END: lookup_threats.py")
 
 
 def lookup_atom_types(dtl: Datalake, untyped_atoms):
@@ -141,18 +151,20 @@ def lookup_atom_types(dtl: Datalake, untyped_atoms):
         return {}
 
     response = dtl.Threats.atom_values_extract(untyped_atoms)
-    if response['found'] == 0:
-        logger.warning('none of your untyped atoms could be typed')
+    if response["found"] == 0:
+        logger.warning("none of your untyped atoms could be typed")
 
     # find out which atoms couldn't be typed to display them
-    failed_to_type_atoms = response['not_found']
+    failed_to_type_atoms = response["not_found"]
     if len(failed_to_type_atoms) > 0:
-        logger.warning(f'\x1b[46m{"#" * 60} FAILED TO IDENTIFY ATOMS {"#" * 36}\x1b[46m')
-        logger.warning('\n'.join(failed_to_type_atoms) + '\n')
-    return response['results']
+        logger.warning(
+            f'\x1b[46m{"#" * 60} FAILED TO IDENTIFY ATOMS {"#" * 36}\x1b[46m'
+        )
+        logger.warning("\n".join(failed_to_type_atoms) + "\n")
+    return response["results"]
 
 
-def get_atom_type_from_filename(filename, input_delimiter=':'):
+def get_atom_type_from_filename(filename, input_delimiter=":"):
     """
     parse filename for getting the atom type that it contains and the cleaned filename as a list as following
     ['type', cleaned_file]
@@ -168,51 +180,57 @@ def get_atom_type_from_filename(filename, input_delimiter=':'):
         return [UNTYPED_ATOM_TYPE, parts[0]]
 
     logger.error(
-        f'{filename} filename could not be treated `atomtype:path/to/file.txt`')
+        f"{filename} filename could not be treated `atomtype:path/to/file.txt`"
+    )
     exit(1)
 
 
 def pretty_print(raw_response, stdout_format, env, dtl):
     """
-     takes the API raw response and format it for be printed as stdout
-     stdout_format possible values {json, csv}
+    takes the API raw response and format it for be printed as stdout
+    stdout_format possible values {json, csv}
     """
-    if stdout_format == 'json':
+    if stdout_format == "json":
         logger.info(json.dumps(raw_response, indent=4, sort_keys=True))
         return
 
-    if stdout_format == 'csv':
+    if stdout_format == "csv":
         logger.info(raw_response)
         return
 
-    search_hashkeys = raw_response.pop('search_hashkey', None)
-    blue_bg = '\033[104m'
-    eol = '\x1b[0m'
+    search_hashkeys = raw_response.pop("search_hashkey", None)
+    blue_bg = "\033[104m"
+    eol = "\x1b[0m"
     boolean_to_text_and_color = {
-        True: ('FOUND', '\x1b[6;30;42m'),
-        False: ('NOT_FOUND', '\x1b[6;30;41m')
+        True: ("FOUND", "\x1b[6;30;42m"),
+        False: ("NOT_FOUND", "\x1b[6;30;41m"),
     }
-    parsed_url = urlparse(dtl.Threats.endpoint_config['main'][env])
-    base_url = f'{parsed_url.scheme}://{parsed_url.netloc}/gui/?query_hash='
+    parsed_url = urlparse(dtl.Threats.endpoint_config["main"][env])
+    base_url = f"{parsed_url.scheme}://{parsed_url.netloc}/gui/?query_hash="
     for atom_type in raw_response.keys():
         logger.info(
-            f'\x1b[6;30;41m{blue_bg}{"#" * 50} {atom_type.upper()} {"#" * (50 - len(atom_type))}{eol}\x1b[0m')
+            f'\x1b[6;30;41m{blue_bg}{"#" * 50} {atom_type.upper()} {"#" * (50 - len(atom_type))}{eol}\x1b[0m'
+        )
 
         for atom in raw_response[atom_type]:
-            found = atom.get('threat_found', False)
+            found = atom.get("threat_found", False)
             text, color = boolean_to_text_and_color[found]
             logger.info(
-                f'{atom_type : <11} {atom["atom_value"][:29] : <30} hashkey: {atom["hashkey"]} {color} {text :^15} {eol}')
+                f'{atom_type : <11} {atom["atom_value"][:29] : <30} hashkey: {atom["hashkey"]} {color} {text :^15} {eol}'
+            )
 
-        logger.info('')
+        logger.info("")
     if search_hashkeys and len(search_hashkeys) <= 10:
         for search_hashkey in search_hashkeys:
             url = base_url + search_hashkey
-            logger.info(f'Results available here : {url}')
+            logger.info(f"Results available here : {url}")
     elif search_hashkeys and len(search_hashkeys) > 10:
-        logger.info('Too many search hashkeys to display, check the output for the full list.')
+        logger.info(
+            "Too many search hashkeys to display, check the output for the full list."
+        )
     else:
-        logger.info('No search hashkeys available for this lookup.')
+        logger.info("No search hashkeys available for this lookup.")
+
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/edit_score.py b/datalake_scripts/scripts/edit_score.py
index 88effd3..df3ac8c 100644
--- a/datalake_scripts/scripts/edit_score.py
+++ b/datalake_scripts/scripts/edit_score.py
@@ -5,56 +5,61 @@
 from datalake import Datalake
 from datalake.common.logger import logger
 from datalake_scripts.common.base_script import BaseScripts
-from datalake_scripts.helper_scripts.utils import save_output, parse_threat_types, split_list, flatten_list
+from datalake_scripts.helper_scripts.utils import (
+    save_output,
+    parse_threat_types,
+    split_list,
+    flatten_list,
+)
 
 
 def main(override_args=None):
     """Method to start the script"""
     # Load initial args
-    parser = BaseScripts.start('Edit scores of a specified list of ids (hashkeys)')
+    parser = BaseScripts.start("Edit scores of a specified list of ids (hashkeys)")
     parser.add_argument(
-        'hashkeys',
-        help='hashkeys of the threat to edit score.',
-        nargs='*',
+        "hashkeys",
+        help="hashkeys of the threat to edit score.",
+        nargs="*",
     )
     parser.add_argument(
-        '-i',
-        '--input_file',
-        help='hashkey txt file, with one hashkey by line.',
+        "-i",
+        "--input_file",
+        help="hashkey txt file, with one hashkey by line.",
     )
     parser.add_argument(
-        '-t',
-        '--threat_types',
-        nargs='+',
-        help='choose specific threat types and their score, like: ddos 50 scam 15',
+        "-t",
+        "--threat_types",
+        nargs="+",
+        help="choose specific threat types and their score, like: ddos 50 scam 15",
         default=[],
-        action='append',
+        action="append",
     )
     parser.add_argument(
-        '-w',
-        '--whitelist',
-        help='Whitelist the input, equivalent to setting all threat types at 0.',
-        action='store_true',
+        "-w",
+        "--whitelist",
+        help="Whitelist the input, equivalent to setting all threat types at 0.",
+        action="store_true",
     )
     parser.add_argument(
-        '--permanent',
-        help='''Permanent: all values will override any values provided by both newer and
+        "--permanent",
+        help="""Permanent: all values will override any values provided by both newer and
             older IOCs. Newer IOCs with override_type permanent can still override old permanent changes.
             temporary: all values should override any values provided by older IOCs,
-            but not newer ones.''',
-        action='store_true',
+            but not newer ones.""",
+        action="store_true",
     )
     parser.add_argument(
-        '--lock',
-        help='sets override_type to lock. Scores won\'t be updated by the algorithm for three months. Default is '
-             'temporary',
-        action='store_true',
+        "--lock",
+        help="sets override_type to lock. Scores won't be updated by the algorithm for three months. Default is "
+        "temporary",
+        action="store_true",
     )
     if override_args:
         args = parser.parse_args(override_args)
     else:
         args = parser.parse_args()
-    logger.debug(f'START: edit_score.py')
+    logger.debug(f"START: edit_score.py")
 
     if not args.hashkeys and not args.input_file:
         parser.error("either a hashkey or an input_file is required")
@@ -81,42 +86,50 @@ def main(override_args=None):
     if args.input_file:
         retrieve_hashkeys_from_file(args.input_file, hashkeys)
         if not hashkeys:
-            raise parser.error('No hashkey found in the input file.')
-    hashkeys_chunks = list(split_list(list(OrderedDict.fromkeys(hashkeys)) if hashkeys else [], 100))
+            raise parser.error("No hashkey found in the input file.")
+    hashkeys_chunks = list(
+        split_list(list(OrderedDict.fromkeys(hashkeys)) if hashkeys else [], 100)
+    )
 
     dtl = Datalake(env=args.env, log_level=args.loglevel)
     response_list = []
     for index, hashkeys in enumerate(hashkeys_chunks):
         try:
-            dtl.Threats.edit_score_by_hashkeys(hashkeys, parsed_threat_type, override_type)
+            dtl.Threats.edit_score_by_hashkeys(
+                hashkeys, parsed_threat_type, override_type
+            )
         except ValueError as e:
-            logger.warning(f'\x1b[6;30;41mBATCH {str(index+1)}/{len(list(hashkeys_chunks))}: FAILED\x1b[0m')
+            logger.warning(
+                f"\x1b[6;30;41mBATCH {str(index+1)}/{len(list(hashkeys_chunks))}: FAILED\x1b[0m"
+            )
             for hashkey in hashkeys:
-                response_list.append(hashkey + ': FAILED')
-                logger.warning(f'\x1b[6;30;41m{hashkey} : FAILED\x1b[0m')
+                response_list.append(hashkey + ": FAILED")
+                logger.warning(f"\x1b[6;30;41m{hashkey} : FAILED\x1b[0m")
             logger.warning(e)
         else:
-            logger.info(f'\x1b[6;30;42mBATCH {str(index+1)}/{len(list(hashkeys_chunks))}: OK\x1b[0m')
+            logger.info(
+                f"\x1b[6;30;42mBATCH {str(index+1)}/{len(list(hashkeys_chunks))}: OK\x1b[0m"
+            )
             for hashkey in hashkeys:
-                response_list.append(hashkey + ': OK')
+                response_list.append(hashkey + ": OK")
 
     if args.output:
         save_output(args.output, response_list)
-        logger.info(f'Results saved in {args.output}\n')
-    logger.debug(f'END: edit_score.py')
+        logger.info(f"Results saved in {args.output}\n")
+    logger.debug(f"END: edit_score.py")
 
 
 def get_whitelist_threat_types():
-    return [{'threat_type': threat_type, 'score': 0} for threat_type in ThreatType]
+    return [{"threat_type": threat_type, "score": 0} for threat_type in ThreatType]
 
 
 def retrieve_hashkeys_from_file(input_file, hashkeys):
-    with open(input_file, 'r', encoding='utf-8') as input_file:
+    with open(input_file, "r", encoding="utf-8") as input_file:
         for line in input_file:
             line = line.strip()
             if line:
                 hashkeys.append(line)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/get_query_hash.py b/datalake_scripts/scripts/get_query_hash.py
index 1e3927e..41f9749 100644
--- a/datalake_scripts/scripts/get_query_hash.py
+++ b/datalake_scripts/scripts/get_query_hash.py
@@ -8,14 +8,16 @@
 
 
 def main(override_args=None):
-    logger.debug(f'START: get_query_hash.py')
+    logger.debug(f"START: get_query_hash.py")
 
     # Load initial args
-    parser = BaseScripts.start('Retrieve a query hash from a query body (a json used for the Advanced Search).')
-    required_named = parser.add_argument_group('required arguments')
+    parser = BaseScripts.start(
+        "Retrieve a query hash from a query body (a json used for the Advanced Search)."
+    )
+    required_named = parser.add_argument_group("required arguments")
     required_named.add_argument(
-        'query_body_path',
-        help='path to the json file containing the query body',
+        "query_body_path",
+        help="path to the json file containing the query body",
     )
     if override_args:
         args = parser.parse_args(override_args)
@@ -23,25 +25,26 @@ def main(override_args=None):
         args = parser.parse_args()
 
     # Load api_endpoints and tokens
-    with open(args.query_body_path, 'r') as query_body_file:
+    with open(args.query_body_path, "r") as query_body_file:
         query_body = json.load(query_body_file)
-    logger.debug(f'Retrieving query hash for query body: {query_body}')
+    logger.debug(f"Retrieving query hash for query body: {query_body}")
 
     dtl = Datalake(env=args.env, log_level=args.loglevel)
 
-    resp = dtl.AdvancedSearch.advanced_search_from_query_body(query_body, limit=0, offset=0,
-                                                              output=Output.JSON)
-    if not resp or 'query_hash' not in resp:
+    resp = dtl.AdvancedSearch.advanced_search_from_query_body(
+        query_body, limit=0, offset=0, output=Output.JSON
+    )
+    if not resp or "query_hash" not in resp:
         logger.error("Couldn't retrieve a query hash, is the query body valid ?")
         exit(1)
-    query_hash = resp['query_hash']
+    query_hash = resp["query_hash"]
     if args.output:
-        with open(args.output, 'w') as output:
+        with open(args.output, "w") as output:
             output.write(query_hash)
-        logger.info(f'Query hash saved in {args.output}')
+        logger.info(f"Query hash saved in {args.output}")
     else:
-        logger.info(f'Query hash associated: {query_hash}')
+        logger.info(f"Query hash associated: {query_hash}")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/get_threats_by_hashkey.py b/datalake_scripts/scripts/get_threats_by_hashkey.py
index c815d34..c935a3d 100644
--- a/datalake_scripts/scripts/get_threats_by_hashkey.py
+++ b/datalake_scripts/scripts/get_threats_by_hashkey.py
@@ -10,23 +10,25 @@
 
 def main(override_args=None):
     """Method to start the script"""
-    logger.debug(f'START: get_threats_by_hashkey.py')
+    logger.debug(f"START: get_threats_by_hashkey.py")
 
     # Load initial args
-    parser = BaseScripts.start('Retrieve threats (as Json) from a list of ids (hashkeys)')
+    parser = BaseScripts.start(
+        "Retrieve threats (as Json) from a list of ids (hashkeys)"
+    )
     parser.add_argument(
-        'hashkeys',
-        help='hashkeys of the threats to retreive',
-        nargs='*',
+        "hashkeys",
+        help="hashkeys of the threats to retreive",
+        nargs="*",
     )
     parser.add_argument(
-        '-i',
-        '--input_file',
-        help='list of threats ids (hashkeys) that need to be retrieved',
+        "-i",
+        "--input_file",
+        help="list of threats ids (hashkeys) that need to be retrieved",
     )
     parser.add_argument(
-        '--lost',
-        help='saved hashes that were not found',
+        "--lost",
+        help="saved hashes that were not found",
     )
     if override_args:
         args = parser.parse_args(override_args)
@@ -37,7 +39,7 @@ def main(override_args=None):
     if not args.hashkeys and not args.input_file:
         parser.error("either a hashkey or an input_file is required")
     threats_list = load_list(args.input_file) if args.input_file else args.hashkeys
-    logger.debug(f'TOTAL: {len(threats_list)} threats found')
+    logger.debug(f"TOTAL: {len(threats_list)} threats found")
 
     # Load api_endpoints and tokens
     endpoint_config = Config().load_config()
@@ -47,12 +49,12 @@ def main(override_args=None):
 
     if args.output:
         save_output(args.output, list_threats)
-        logger.debug(f'Threats JSON saved in {args.output}\n')
+        logger.debug(f"Threats JSON saved in {args.output}\n")
     if args.lost:
         save_output(args.lost, list_lost_hashes)
-        logger.debug(f'Threats lost saved in {args.lost}\n')
-    logger.debug(f'END: get_threats_by_hashkey.py')
+        logger.debug(f"Threats lost saved in {args.lost}\n")
+    logger.debug(f"END: get_threats_by_hashkey.py")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/get_threats_from_query_hash.py b/datalake_scripts/scripts/get_threats_from_query_hash.py
index fd5e0ff..80c920e 100644
--- a/datalake_scripts/scripts/get_threats_from_query_hash.py
+++ b/datalake_scripts/scripts/get_threats_from_query_hash.py
@@ -12,26 +12,26 @@
 
 def main(override_args=None):
     """Method to start the script"""
-    logger.debug(f'START: get_threats_from_query_hash.py')
+    logger.debug(f"START: get_threats_from_query_hash.py")
 
     # Load initial args
-    parser = BaseScripts.start('Retrieve a list of response from a given query hash.')
+    parser = BaseScripts.start("Retrieve a list of response from a given query hash.")
     parser.add_argument(
-        '--query_fields',
-        help='fields to be retrieved from the threat (default: only the hashkey)\n'
-             'If an atom detail isn\'t present in a particular atom, empty string is returned.',
-        nargs='+',
-        default=['threat_hashkey'],
+        "--query_fields",
+        help="fields to be retrieved from the threat (default: only the hashkey)\n"
+        "If an atom detail isn't present in a particular atom, empty string is returned.",
+        nargs="+",
+        default=["threat_hashkey"],
     )
     parser.add_argument(
-        '--list',
-        help='Turn the output in a list (require query_fields to be a single element)',
-        action='store_true',
+        "--list",
+        help="Turn the output in a list (require query_fields to be a single element)",
+        action="store_true",
     )
-    required_named = parser.add_argument_group('required arguments')
+    required_named = parser.add_argument_group("required arguments")
     required_named.add_argument(
-        'query_hash',
-        help='the query hash from which to retrieve the response hashkeys or a path to the query body json file',
+        "query_hash",
+        help="the query hash from which to retrieve the response hashkeys or a path to the query body json file",
     )
     if override_args:
         args = parser.parse_args(override_args)
@@ -40,58 +40,64 @@ def main(override_args=None):
     configure_logging(args.loglevel)
 
     if len(args.query_fields) > 1 and args.list:
-        parser.error("List output format is only available if a single element is queried (via query_fields)")
+        parser.error(
+            "List output format is only available if a single element is queried (via query_fields)"
+        )
 
     query_body = {}
     query_hash = args.query_hash
     if len(query_hash) != 32 or os.path.exists(query_hash):
         try:
-            with open(query_hash, 'r') as query_body_file:
+            with open(query_hash, "r") as query_body_file:
                 query_body = json.load(query_body_file)
         except FileNotFoundError:
-            logger.error(f"Couldn't understand the given value as a query hash or path to query body: {query_hash}")
+            logger.error(
+                f"Couldn't understand the given value as a query hash or path to query body: {query_hash}"
+            )
             exit(1)
 
     # Load api_endpoints and tokens
     dtl = Datalake(env=args.env, log_level=args.loglevel)
-    logger.debug(f'Start to search for threat from the query hash:{query_hash}')
+    logger.debug(f"Start to search for threat from the query hash:{query_hash}")
     spinner = None
     if logger.isEnabledFor(logging.INFO):
-        spinner = Halo(text=f'Creating bulk task', spinner='dots')
+        spinner = Halo(text=f"Creating bulk task", spinner="dots")
         spinner.start()
 
-    task = dtl.BulkSearch.create_task(query_body=query_body, query_hash=query_hash, query_fields=args.query_fields)
+    task = dtl.BulkSearch.create_task(
+        query_body=query_body, query_hash=query_hash, query_fields=args.query_fields
+    )
     if spinner:
-        spinner.text = f'Waiting for bulk task {task.uuid} response'
+        spinner.text = f"Waiting for bulk task {task.uuid} response"
     response = task.download_sync()
-    original_count = response.get('count', 0)
+    original_count = response.get("count", 0)
     if spinner:
         spinner.succeed()
-        spinner.info(f'Number of threat that have been retrieved: {original_count}')
+        spinner.info(f"Number of threat that have been retrieved: {original_count}")
 
     formatted_output = format_output(response, args.list)
     if args.output:
-        with open(args.output, 'w') as output:
+        with open(args.output, "w") as output:
             output.write(formatted_output)
     else:
         logger.info(formatted_output)
 
     if args.output:
-        logger.info(f'Threats saved in {args.output}')
+        logger.info(f"Threats saved in {args.output}")
     else:
-        logger.info('Done')
+        logger.info("Done")
 
 
 def format_output(response: dict, one_threat_per_line=False) -> str:
     if one_threat_per_line:
         threat_list = []
-        for result in response.get('results', []):
+        for result in response.get("results", []):
             if result:
                 threat_list += result
-        return '\n'.join(threat_list)
+        return "\n".join(threat_list)
     else:
         return json.dumps(response, sort_keys=True, indent=4)
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/datalake_scripts/scripts/lookup_threats.py b/datalake_scripts/scripts/lookup_threats.py
index c986c40..26721cb 100644
--- a/datalake_scripts/scripts/lookup_threats.py
+++ b/datalake_scripts/scripts/lookup_threats.py
@@ -6,63 +6,68 @@
 from datalake_scripts.common.base_engine import BaseEngine, InvalidHeader
 from datalake_scripts.common.base_script import BaseScripts
 from datalake_scripts.helper_scripts.output_builder import CsvBuilder
-from datalake_scripts.helper_scripts.utils import load_csv, load_list, save_output, parse_atom_type_or_exit
+from datalake_scripts.helper_scripts.utils import (
+    load_csv,
+    load_list,
+    save_output,
+    parse_atom_type_or_exit,
+)
 
 boolean_to_text_and_color = {
-    True: ('FOUND', '\x1b[6;30;42m'),
-    False: ('NOT_FOUND', '\x1b[6;30;41m')
+    True: ("FOUND", "\x1b[6;30;42m"),
+    False: ("NOT_FOUND", "\x1b[6;30;41m"),
 }
 
 
 def main(override_args=None):
     # Load initial args
-    parser = BaseScripts.start('Submit a new threat to Datalake from a file')
-    required_named = parser.add_argument_group('required arguments')
-    csv_control = parser.add_argument_group('CSV control arguments')
+    parser = BaseScripts.start("Submit a new threat to Datalake from a file")
+    required_named = parser.add_argument_group("required arguments")
+    csv_control = parser.add_argument_group("CSV control arguments")
 
     parser.add_argument(
-        'threats',
-        help='threats to lookup',
-        nargs='*',
+        "threats",
+        help="threats to lookup",
+        nargs="*",
     )
     parser.add_argument(
-        '-i',
-        '--input',
-        help='read threats to add from FILE',
+        "-i",
+        "--input",
+        help="read threats to add from FILE",
     )
     parser.add_argument(
-        '-td',
-        '--threat_details',
-        action='store_true',
-        help='set if you also want to have access to the threat details ',
+        "-td",
+        "--threat_details",
+        action="store_true",
+        help="set if you also want to have access to the threat details ",
     )
     parser.add_argument(
-        '-ot',
-        '--output_type',
-        default='json',
-        help='set to the output type desired {json,csv}. Default is json if not specified',
+        "-ot",
+        "--output_type",
+        default="json",
+        help="set to the output type desired {json,csv}. Default is json if not specified",
     )
     required_named.add_argument(
-        '-a',
-        '--atom_type',
-        help='set it to define the atom type',
+        "-a",
+        "--atom_type",
+        help="set it to define the atom type",
         required=True,
     )
     csv_control.add_argument(
-        '--is_csv',
-        help='set if the file input is a CSV',
-        action='store_true',
+        "--is_csv",
+        help="set if the file input is a CSV",
+        action="store_true",
     )
     csv_control.add_argument(
-        '-d',
-        '--delimiter',
-        help='set the delimiter of the CSV file',
-        default=',',
+        "-d",
+        "--delimiter",
+        help="set the delimiter of the CSV file",
+        default=",",
     )
     csv_control.add_argument(
-        '-c',
-        '--column',
-        help='select column of the CSV file, starting at 1',
+        "-c",
+        "--column",
+        help="select column of the CSV file, starting at 1",
         type=int,
         default=1,
     )
@@ -70,7 +75,7 @@ def main(override_args=None):
         args = parser.parse_args(override_args)
     else:
         args = parser.parse_args()
-    logger.debug(f'START: lookup_threats.py')
+    logger.debug(f"START: lookup_threats.py")
 
     if not args.threats and not args.input:
         parser.error("either a threat or an input_file is required")
@@ -79,7 +84,10 @@ def main(override_args=None):
         try:
             args.output_type = BaseEngine.output_type2header(args.output_type)
         except InvalidHeader as e:
-            logger.exception(f'Exception raised while getting output type from headers # {str(e)}', exc_info=False)
+            logger.exception(
+                f"Exception raised while getting output type from headers # {str(e)}",
+                exc_info=False,
+            )
             exit(1)
 
     hashkey_only = not args.threat_details
@@ -88,7 +96,9 @@ def main(override_args=None):
     if args.input:
         if args.is_csv:
             try:
-                list_threats = list_threats + load_csv(args.input, args.delimiter, args.column - 1)
+                list_threats = list_threats + load_csv(
+                    args.input, args.delimiter, args.column - 1
+                )
             except ValueError as ve:
                 logger.error(ve)
                 exit()
@@ -97,25 +107,31 @@ def main(override_args=None):
 
     full_response = {}
     atom_type = parse_atom_type_or_exit(args.atom_type)
-    list_threats = list(OrderedDict.fromkeys(list_threats))  # removing duplicates while preserving order
+    list_threats = list(
+        OrderedDict.fromkeys(list_threats)
+    )  # removing duplicates while preserving order
     for threat in list_threats:
-        response = dtl.Threats.lookup(threat, atom_type=atom_type, hashkey_only=hashkey_only)
-        found = response.get('threat_found', True)
+        response = dtl.Threats.lookup(
+            threat, atom_type=atom_type, hashkey_only=hashkey_only
+        )
+        found = response.get("threat_found", True)
         text, color = boolean_to_text_and_color[found]
-        logger.info('{}{} hashkey:{} {}\x1b[0m'.format(color, threat, response['hashkey'], text))
+        logger.info(
+            "{}{} hashkey:{} {}\x1b[0m".format(color, threat, response["hashkey"], text)
+        )
         full_response[threat] = response
 
     if args.output:
-        if args.output_type == 'text/csv':
+        if args.output_type == "text/csv":
             full_response = CsvBuilder.create_look_up_csv(
                 full_response,
                 args.atom_type,
                 has_details=args.threat_details,
             )
         save_output(args.output, full_response)
-        logger.debug(f'Results saved in {args.output}\n')
-    logger.debug(f'END: lookup_threats.py')
+        logger.debug(f"Results saved in {args.output}\n")
+    logger.debug(f"END: lookup_threats.py")
 
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     sys.exit(main())
diff --git a/setup.py b/setup.py
index 84045cc..fe7ce8f 100644
--- a/setup.py
+++ b/setup.py
@@ -10,47 +10,44 @@
     long_description = fh.read()
 
 setup(
-    name='datalake-scripts',
+    name="datalake-scripts",
     version=Cli.VERSION,
-    author='OCD',
-    author_email='cert-contact.ocd@orange.com',
-    description='A collection of scripts to easily use the API of OCD Datalake',
+    author="OCD",
+    author_email="cert-contact.ocd@orange.com",
+    description="A collection of scripts to easily use the API of OCD Datalake",
     long_description=long_description,
     long_description_content_type="text/markdown",
-    url='https://github.com/cert-orangecyberdefense/datalake/',
+    url="https://github.com/cert-orangecyberdefense/datalake/",
     package_dir={
-        '': '.',
+        "": ".",
     },
     packages=find_packages(),
-    install_requires=[
-        "requests",
-        "halo"
-    ],
-    test_suite='nose.collector',
+    install_requires=["requests", "halo"],
+    test_suite="nose.collector",
     entry_points={
-        'console_scripts': (
-            'get_users = datalake_scripts.scripts.get_users:main',
-            'get_threats_by_hashkey = datalake_scripts.scripts.get_threats_by_hashkey:main',
-            'add_new_threats = datalake_scripts.scripts.add_new_threats:main',
-            'add_new_comment_or_tags = datalake_scripts.scripts.add_new_comment_or_tags:main',
-            'edit_score = datalake_scripts.scripts.edit_score:main',
-            'get_threats_from_query_hash = datalake_scripts.scripts.get_threats_from_query_hash:main',
-            f'{Cli.CLI_NAME} = datalake_scripts.cli:main'
+        "console_scripts": (
+            "get_users = datalake_scripts.scripts.get_users:main",
+            "get_threats_by_hashkey = datalake_scripts.scripts.get_threats_by_hashkey:main",
+            "add_new_threats = datalake_scripts.scripts.add_new_threats:main",
+            "add_new_comment_or_tags = datalake_scripts.scripts.add_new_comment_or_tags:main",
+            "edit_score = datalake_scripts.scripts.edit_score:main",
+            "get_threats_from_query_hash = datalake_scripts.scripts.get_threats_from_query_hash:main",
+            f"{Cli.CLI_NAME} = datalake_scripts.cli:main",
         ),
     },
     classifiers=[
-        'Programming Language :: Python :: 3.7',
-        'Programming Language :: Python :: 3.8',
-        'Programming Language :: Python :: 3.9',
-        'Programming Language :: Python :: 3.10',
-        'Operating System :: POSIX :: Linux',
-        'Environment :: Console',
-        'Topic :: Security',
-        'Natural Language :: English',
+        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Operating System :: POSIX :: Linux",
+        "Environment :: Console",
+        "Topic :: Security",
+        "Natural Language :: English",
     ],
     include_package_data=True,
     package_data={
-        'datalake': ['config/endpoints.json'],
+        "datalake": ["config/endpoints.json"],
     },
     python_requires=">=3.6",
 )
diff --git a/tutorial.md b/tutorial.md
index 0811d12..7348480 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -52,6 +52,7 @@ Below are some examples to get you started
 - [Edit score](#edit-score)
 - [Advanced Search](#advanced-search)
 - [Sightings](#sightings)
+- [Search Sightings](#search-sightings)
 
 ### Lookup a threat
 
@@ -407,6 +408,40 @@ you with hints on the value expected for each atom_type, most of which aren't ma
 For sightings, we won't use most of the fields. You can verify the fields that are used for sighting in the docstrings
 of each class, inside your editor.
 
+### Search Sightings
+It is possible to search sightings from either a hashkey or an atom_value. All parameters are optional.
+
+From a hashkey:
+```python
+from datalake import Datalake, SightingType, Visibility
+
+dtl = Datalake(username='username', password='password')
+resp = dtl.Sightings.sightings_filtered(
+    "f39cbce3c4d30d61ccdc99c5fcb3bf6f", 
+    limit=100, 
+    offset=0,
+    sighting_type=SightingType.POSITIVE, 
+    visibility=Visibility.PUBLIC
+)
+```
+⚠️ Not providing a hashkey will return all sightings.
+
+
+From an atom value
+```python
+from datalake import Datalake, SightingType, Visibility
+
+dtl = Datalake(username='username', password='password')
+resp = dtl.sightings_filtered_from_atom_value(
+    "8.8.8.8", 
+    limit=100, 
+    offset=0, 
+    sighting_type=SightingType.POSITIVE, 
+    visibility=Visibility.PUBLIC
+)
+```
+
+See the API documentation below for a list of available options.
 ### API documentation
 
 For more information on the API used by this library,


Test patch:
diff --git a/tests/common/fixture.py b/tests/common/fixture.py
index 3d4fafa..620189f 100644
--- a/tests/common/fixture.py
+++ b/tests/common/fixture.py
@@ -6,49 +6,46 @@
 
 
 class TestData:
-    TEST_ENV = 'my_env'
+    TEST_ENV = "my_env"
     TEST_CONFIG = {
-        'main': {
-            TEST_ENV: 'https://datalake.com/api/'
+        "main": {TEST_ENV: "https://datalake.com/api/"},
+        "endpoints": {
+            "bulk-search": "mrti/bulk-search/",
+            "threats": "mrti/threats/",
+            "threats-manual": "mrti/threats-manual/",
+            "token": "auth/token/",
+            "advanced-search": "mrti/advanced-queries/threats/",
+            "advanced-search-hash": "mrti/advanced-queries/threats/{query_hash}/?limit={limit}&offset={offset}&ordering"
+            "={ordering}",
+            "refresh_token": "auth/refresh-token/",
+            "lookup": "mrti/threats/lookup/",
+            "comment": "mrti/threats/{hashkey}/comments/",
+            "tag": "mrti/threats/{hashkey}/tags/",
         },
-        'endpoints': {
-            'bulk-search': 'mrti/bulk-search/',
-            'threats': 'mrti/threats/',
-            'threats-manual': 'mrti/threats-manual/',
-            'token': 'auth/token/',
-            'advanced-search': 'mrti/advanced-queries/threats/',
-            'advanced-search-hash': 'mrti/advanced-queries/threats/{query_hash}/?limit={limit}&offset={offset}&ordering'\
-            '={ordering}',
-            'refresh_token': 'auth/refresh-token/',
-            'lookup': 'mrti/threats/lookup/',
-            'comment': 'mrti/threats/{hashkey}/comments/',
-            'tag': 'mrti/threats/{hashkey}/tags/',
-        },
-        'api_version': 'v42/'
+        "api_version": "v42/",
     }
 
 
 @pytest.fixture
 @responses.activate
 def token_manager():
-    url = 'https://datalake.com/api/v42/auth/token/'
-    auth_response = {
-        "access_token": "access_token",
-        "refresh_token": "refresh_token"
-    }
+    url = "https://datalake.com/api/v42/auth/token/"
+    auth_response = {"access_token": "access_token", "refresh_token": "refresh_token"}
     responses.add(responses.POST, url, json=auth_response, status=200)
-    return TokenManager(TestData.TEST_CONFIG, environment=TestData.TEST_ENV, username='username', password='password')
+    return TokenManager(
+        TestData.TEST_CONFIG,
+        environment=TestData.TEST_ENV,
+        username="username",
+        password="password",
+    )
 
 
 @pytest.fixture
 @responses.activate
 def datalake():
-    url = 'https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/'
+    url = "https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/"
 
-    auth_response = {
-        "access_token": "12345",
-        "refresh_token": "123456"
-    }
+    auth_response = {"access_token": "12345", "refresh_token": "123456"}
 
     responses.add(responses.POST, url, json=auth_response, status=200)
-    return Datalake(username='username', password='password')
+    return Datalake(username="username", password="password")
diff --git a/tests/conftest.py b/tests/conftest.py
index 99375ed..919cccd 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -2,5 +2,7 @@
 
 
 def pytest_sessionstart(session):
-    os.environ["OCD_DTL_REQUESTS_PER_QUOTA_TIME"] = "10000"  # Disable the throttler during tests
+    os.environ[
+        "OCD_DTL_REQUESTS_PER_QUOTA_TIME"
+    ] = "10000"  # Disable the throttler during tests
     os.environ["OCD_DTL_MAX_BACK_OFF_TIME"] = "0.01"  # waiting should be minimal
diff --git a/tests/lib/test_advanced_search.py b/tests/lib/test_advanced_search.py
index 4030092..67779f7 100644
--- a/tests/lib/test_advanced_search.py
+++ b/tests/lib/test_advanced_search.py
@@ -9,18 +9,10 @@
             "AND": [
                 {
                     "field": "atom_type",
-                    "multi_values": [
-                        "phone_number"
-                    ],
-                    "type": "filter"
+                    "multi_values": ["phone_number"],
+                    "type": "filter",
                 },
-                {
-                    "field": "risk",
-                    "range": {
-                        "gt": 90
-                    },
-                    "type": "filter"
-                }
+                {"field": "risk", "range": {"gt": 90}, "type": "filter"},
             ]
         }
     ]
@@ -39,36 +31,36 @@ def mock_api_resp():
             '66ec37c2032d1b/",
             "query_body": query_body,
             "query_hash": "8697fbe09069e882e2de169ad480c2bf",
-            "results": []
-        }
+            "results": [],
+        },
     )
     responses.add(
         responses.GET,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats'
-        '/8697fbe09069e882e2de169ad480c2bf/?limit=0&offset=0',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats"
+        "/8697fbe09069e882e2de169ad480c2bf/?limit=0&offset=0",
         status=200,
         json={
             "count": 1,
             "href_query": "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats"
-                          "/8697fbe09069e882e2de169ad480c2bf/66ec37c2032d1b/",
+            "/8697fbe09069e882e2de169ad480c2bf/66ec37c2032d1b/",
             "query_body": query_body,
             "query_hash": "8697fbe09069e882e2de169ad480c2bf",
-            "results": []
-        }
+            "results": [],
+        },
     )
     responses.add(
         responses.GET,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats'
-        '/8697fbe09069e882e2de169ad480c2bf/?limit=0&offset=0&ordering=first_seen',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats"
+        "/8697fbe09069e882e2de169ad480c2bf/?limit=0&offset=0&ordering=first_seen",
         status=200,
         json={
             "count": 1,
             "href_query": "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/advanced-queries/threats"
-                          "/8697fbe09069e882e2de169ad480c2bf/66ec37c2032d1b/",
+            "/8697fbe09069e882e2de169ad480c2bf/66ec37c2032d1b/",
             "query_body": query_body,
             "query_hash": "8697fbe09069e882e2de169ad480c2bf",
-            "results": []
-        }
+            "results": [],
+        },
     )
 
 
@@ -76,14 +68,14 @@ def mock_api_resp():
 def test_advanced_search_from_query_body(datalake: Datalake):
     mock_api_resp()
     resp = datalake.AdvancedSearch.advanced_search_from_query_body(query_body, limit=0)
-    assert resp['query_hash'] == query_hash
+    assert resp["query_hash"] == query_hash
 
 
 @responses.activate
 def test_advanced_search_from_query_hash(datalake: Datalake):
     mock_api_resp()
     resp = datalake.AdvancedSearch.advanced_search_from_query_hash(query_hash, limit=0)
-    assert resp['query_hash'] == query_hash
+    assert resp["query_hash"] == query_hash
 
 
 def test_advanced_search_from_query_body_no_body(datalake: Datalake):
@@ -94,28 +86,40 @@ def test_advanced_search_from_query_body_no_body(datalake: Datalake):
 
 def test_advanced_search_from_query_hash_no_hash(datalake: Datalake):
     with pytest.raises(ValueError) as exec_error:
-        datalake.AdvancedSearch.advanced_search_from_query_hash('', limit=0)
+        datalake.AdvancedSearch.advanced_search_from_query_hash("", limit=0)
     assert str(exec_error.value) == "query_hash is required"
 
 
 def test_advanced_search_from_query_hash_bad_ordering(datalake: Datalake):
     with pytest.raises(ValueError) as exec_error:
-        datalake.AdvancedSearch.advanced_search_from_query_hash(query_hash, limit=0, ordering=['badbad'])
-    assert str(exec_error.value) == "ordering needs to be a list of at least one of the following str : first_seen, " \
-                                    "-first_seen, last_updated, -last_updated, events_count, -events_count, " \
-                                    "sources_count, -sources_count"
+        datalake.AdvancedSearch.advanced_search_from_query_hash(
+            query_hash, limit=0, ordering=["badbad"]
+        )
+    assert (
+        str(exec_error.value)
+        == "ordering needs to be a list of at least one of the following str : first_seen, "
+        "-first_seen, last_updated, -last_updated, events_count, -events_count, "
+        "sources_count, -sources_count"
+    )
 
 
 def test_advanced_search_from_query_hash_ordering_not_list(datalake: Datalake):
     with pytest.raises(ValueError) as exec_error:
-        datalake.AdvancedSearch.advanced_search_from_query_hash(query_hash, limit=0, ordering='badbad')
-    assert str(exec_error.value) == "ordering needs to be a list of at least one of the following str : first_seen, " \
-                                    "-first_seen, last_updated, -last_updated, events_count, -events_count, " \
-                                    "sources_count, -sources_count"
+        datalake.AdvancedSearch.advanced_search_from_query_hash(
+            query_hash, limit=0, ordering="badbad"
+        )
+    assert (
+        str(exec_error.value)
+        == "ordering needs to be a list of at least one of the following str : first_seen, "
+        "-first_seen, last_updated, -last_updated, events_count, -events_count, "
+        "sources_count, -sources_count"
+    )
 
 
 @responses.activate
 def test_advanced_search_from_query_hash_ok_ordering(datalake: Datalake):
     mock_api_resp()
-    resp = datalake.AdvancedSearch.advanced_search_from_query_hash(query_hash, limit=0, ordering=['first_seen'])
-    assert resp['query_hash'] == query_hash
+    resp = datalake.AdvancedSearch.advanced_search_from_query_hash(
+        query_hash, limit=0, ordering=["first_seen"]
+    )
+    assert resp["query_hash"] == query_hash
diff --git a/tests/lib/test_atom_type.py b/tests/lib/test_atom_type.py
index 7bc98d1..be4880e 100644
--- a/tests/lib/test_atom_type.py
+++ b/tests/lib/test_atom_type.py
@@ -2,45 +2,44 @@
 from datalake import IpAtom, FileAtom, Hashes, Jarm, IpService
 import warnings
 
-jarm = Jarm('12/12/2012 12:12:12', 'some_fingerprint', False, 'some_malware')
-ip_service = IpService(port=78, service_name='some_service', application='some_application', protocol='some_protocol')
+jarm = Jarm("12/12/2012 12:12:12", "some_fingerprint", False, "some_malware")
+ip_service = IpService(
+    port=78,
+    service_name="some_service",
+    application="some_application",
+    protocol="some_protocol",
+)
 ip_atom = IpAtom(
-    ip_address='8.8.8.8',
-    external_analysis_link=['https://some_url.co'],
-    hostname='some_host',
+    ip_address="8.8.8.8",
+    external_analysis_link=["https://some_url.co"],
+    hostname="some_host",
     ip_version=4,
     jarm=jarm,
-    malware_family='some_malware',
-    owner='owmer',
+    malware_family="some_malware",
+    owner="owmer",
     peer_asns=[1, 2, 3],
-    services=ip_service
+    services=ip_service,
 )
 hashes = Hashes(
-    md5='d26351ba789fba3385d2382aa9d24908',
-    sha1='a61e243f25b8b08661011869a53315363697a0f4',
-    sha256='c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-    sha512='01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'
+    md5="d26351ba789fba3385d2382aa9d24908",
+    sha1="a61e243f25b8b08661011869a53315363697a0f4",
+    sha256="c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+    sha512="01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
 )
 file_atom = FileAtom(
     hashes=hashes,
-    filename='some_filename',
-    file_url='some_url',
-    external_analysis_link='some_external_url',
+    filename="some_filename",
+    file_url="some_url",
+    external_analysis_link="some_external_url",
     filesize=666,
-    filetype='jpg',
-    mimetype='some_mime',
-    filepath='some/path'
+    filetype="jpg",
+    mimetype="some_mime",
+    filepath="some/path",
 )
 
 
 def test_generate_atom_json_remove_unused_keys_for_sightings(datalake):
-    expect_output = {
-        'ip_list': [
-            {
-                'ip_address': '8.8.8.8'
-            }
-        ]
-    }
+    expect_output = {"ip_list": [{"ip_address": "8.8.8.8"}]}
     output = ip_atom.generate_atom_json(for_sightings=True)
 
     assert expect_output == output
@@ -48,26 +47,26 @@ def test_generate_atom_json_remove_unused_keys_for_sightings(datalake):
 
 def test_generate_atom_json(datalake):
     expect_output = {
-        'ip_content': {
-            'ip_address': '8.8.8.8',
-            'external_analysis_link': ['https://some_url.co'],
-            'hostname': 'some_host',
-            'ip_version': 4,
-            'jarm': {
-                'calculated_at': '12/12/2012 12:12:12',
-                'fingerprint': 'some_fingerprint',
-                'malicious': False,
-                'malware_family': 'some_malware'
+        "ip_content": {
+            "ip_address": "8.8.8.8",
+            "external_analysis_link": ["https://some_url.co"],
+            "hostname": "some_host",
+            "ip_version": 4,
+            "jarm": {
+                "calculated_at": "12/12/2012 12:12:12",
+                "fingerprint": "some_fingerprint",
+                "malicious": False,
+                "malware_family": "some_malware",
+            },
+            "malware_family": "some_malware",
+            "owner": "owmer",
+            "peer_asns": [1, 2, 3],
+            "services": {
+                "port": 78,
+                "service_name": "some_service",
+                "application": "some_application",
+                "protocol": "some_protocol",
             },
-            'malware_family': 'some_malware',
-            'owner': 'owmer',
-            'peer_asns': [1, 2, 3],
-            'services': {
-                'port': 78,
-                'service_name': 'some_service',
-                'application': 'some_application',
-                'protocol': 'some_protocol'
-            }
         }
     }
     output = ip_atom.generate_atom_json(for_sightings=False)
@@ -77,13 +76,13 @@ def test_generate_atom_json(datalake):
 
 def test_generate_atom_json_nested_remove_unused_keys_for_sightings(datalake):
     expected_output = {
-        'file_list': [
+        "file_list": [
             {
-                'hashes': {
-                    'md5': 'd26351ba789fba3385d2382aa9d24908',
-                    'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                    'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                    'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'
+                "hashes": {
+                    "md5": "d26351ba789fba3385d2382aa9d24908",
+                    "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                    "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                    "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
                 }
             }
         ]
@@ -95,20 +94,20 @@ def test_generate_atom_json_nested_remove_unused_keys_for_sightings(datalake):
 
 def test_generate_atom_json_nested(datalake):
     expected_output = {
-        'file_content': {
-            'hashes': {
-                'md5': 'd26351ba789fba3385d2382aa9d24908',
-                'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'
+        "file_content": {
+            "hashes": {
+                "md5": "d26351ba789fba3385d2382aa9d24908",
+                "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
             },
-            'external_analysis_link': 'some_external_url',
-            'filesize': 666,
-            'filetype': 'jpg',
-            'file_url': 'some_url',
-            'mimetype': 'some_mime',
-            'filename': 'some_filename',
-            'filepath': 'some/path'
+            "external_analysis_link": "some_external_url",
+            "filesize": 666,
+            "filetype": "jpg",
+            "file_url": "some_url",
+            "mimetype": "some_mime",
+            "filename": "some_filename",
+            "filepath": "some/path",
         }
     }
     output = file_atom.generate_atom_json(for_sightings=False)
diff --git a/tests/lib/test_auth_via_tokens.py b/tests/lib/test_auth_via_tokens.py
index 774c869..a2adf29 100644
--- a/tests/lib/test_auth_via_tokens.py
+++ b/tests/lib/test_auth_via_tokens.py
@@ -12,8 +12,8 @@
 @responses.activate
 def test_token_auth(datalake):
     expected_tokens = {
-        'access_token': '12345',
-        'refresh_token': '123456',
+        "access_token": "12345",
+        "refresh_token": "123456",
     }
 
     token_manager = datalake.Threats.token_manager
@@ -24,28 +24,34 @@ def test_token_auth(datalake):
 
 @responses.activate
 def test_invalid_credentials(caplog):
-    url = 'https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/'
+    url = "https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/"
 
-    api_error_msg = 'Wrong credentials provided'
-    api_response = {'message': api_error_msg}
+    api_error_msg = "Wrong credentials provided"
+    api_response = {"message": api_error_msg}
     responses.add(responses.POST, url, json=api_response, status=401)
     with caplog.at_level(logging.ERROR):
         with pytest.raises(ValueError) as ve:
-            Datalake(username='username@wow.com', password='password')
+            Datalake(username="username@wow.com", password="password")
     assert str(ve.value) == f'Could not login: {{"message": "{api_error_msg}"}}'
     assert caplog.messages == [
-        f'An error occurred while retrieving an access token, for URL: {url}\n'
+        f"An error occurred while retrieving an access token, for URL: {url}\n"
         f'response of the API: {{"message": "{api_error_msg}"}}'
     ]
 
 
-def lookup_callback(request, expired_token, valid_token, response_on_valid_token, response_on_expired_token=None):
+def lookup_callback(
+    request,
+    expired_token,
+    valid_token,
+    response_on_valid_token,
+    response_on_expired_token=None,
+):
     headers = {}
     if not response_on_expired_token:
-        response_on_expired_token = {'msg': 'Token has expired'}  # Default value
-    if request.headers['Authorization'] == f'Token {expired_token}':
+        response_on_expired_token = {"msg": "Token has expired"}  # Default value
+    if request.headers["Authorization"] == f"Token {expired_token}":
         return 401, headers, json.dumps(response_on_expired_token)
-    elif request.headers['Authorization'] == f'Token {valid_token}':
+    elif request.headers["Authorization"] == f"Token {valid_token}":
         return 200, headers, json.dumps(response_on_valid_token)
     else:
         raise Exception()
@@ -54,105 +60,122 @@ def lookup_callback(request, expired_token, valid_token, response_on_valid_token
 @responses.activate
 def test_access_token_expired(datalake, caplog):
     caplog.set_level(level=logging.INFO, logger="OCD_DTL")
-    expected_json = {'wow.com': 'bad'}
+    expected_json = {"wow.com": "bad"}
 
     responses.add_callback(
         responses.GET,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/",
         callback=partial(
             lookup_callback,
-            expired_token='12345',
-            valid_token='refreshed_token',
+            expired_token="12345",
+            valid_token="refreshed_token",
             response_on_valid_token=expected_json,
         ),
-        content_type='application/json',
+        content_type="application/json",
     )
 
     def refresh_token_callback(request):
         headers = {}
-        assert request.headers['Authorization'] == 'Token 123456', 'token passed is not the refresh token'
-        return 200, headers, json.dumps({'access_token': 'refreshed_token'})
+        assert (
+            request.headers["Authorization"] == "Token 123456"
+        ), "token passed is not the refresh token"
+        return 200, headers, json.dumps({"access_token": "refreshed_token"})
 
     responses.add_callback(
         responses.POST,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/auth/refresh-token/',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/auth/refresh-token/",
         callback=refresh_token_callback,
-        content_type='application/json',
+        content_type="application/json",
     )
 
-    assert datalake.Threats.lookup(atom_value='wow.com', atom_type=AtomType.DOMAIN) == expected_json
-    assert caplog.messages == ['Token expired or Missing authorization header. Updating token']
+    assert (
+        datalake.Threats.lookup(atom_value="wow.com", atom_type=AtomType.DOMAIN)
+        == expected_json
+    )
+    assert caplog.messages == [
+        "Token expired or Missing authorization header. Updating token"
+    ]
 
 
 @responses.activate
 def test_refresh_token_expired(datalake, caplog):
     caplog.set_level(level=logging.INFO, logger="OCD_DTL")
-    expected_json = {'wow.com': 'bad'}
+    expected_json = {"wow.com": "bad"}
 
     responses.add_callback(
         responses.GET,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/",
         callback=partial(
             lookup_callback,
-            expired_token='12345',
-            valid_token='new_token',
+            expired_token="12345",
+            valid_token="new_token",
             response_on_valid_token=expected_json,
         ),
-        content_type='application/json',
+        content_type="application/json",
     )
 
     def refresh_token_callback(request):
         headers = {}
-        assert request.headers['Authorization'] == 'Token 123456', 'token passed is not the refresh token'
-        return 401, headers, json.dumps({'msg': 'Token has expired'})
+        assert (
+            request.headers["Authorization"] == "Token 123456"
+        ), "token passed is not the refresh token"
+        return 401, headers, json.dumps({"msg": "Token has expired"})
 
     responses.add_callback(
         responses.POST,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/auth/refresh-token/',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/auth/refresh-token/",
         callback=refresh_token_callback,
-        content_type='application/json',
+        content_type="application/json",
     )
     responses.post(
-        url='https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/',
-        json={'access_token': 'new_token', 'refresh_token': ''},
+        url="https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/",
+        json={"access_token": "new_token", "refresh_token": ""},
         status=200,
     )
 
-    assert datalake.Threats.lookup(atom_value='wow.com', atom_type=AtomType.DOMAIN) == expected_json
+    assert (
+        datalake.Threats.lookup(atom_value="wow.com", atom_type=AtomType.DOMAIN)
+        == expected_json
+    )
     assert caplog.messages == [
-        'Token expired or Missing authorization header. Updating token',
-        'Refreshing the refresh token',
+        "Token expired or Missing authorization header. Updating token",
+        "Refreshing the refresh token",
     ]
 
 
 @responses.activate
 def test_invalid_token(datalake, caplog):
-    expected_json = {'wow.com': 'bad'}
-    invalid_access_token = 'not-valid-token'
-    valid_access_token = 'valid_token'
+    expected_json = {"wow.com": "bad"}
+    invalid_access_token = "not-valid-token"
+    valid_access_token = "valid_token"
 
     responses.add_callback(
         responses.GET,
-        'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/",
         callback=partial(
             lookup_callback,
             expired_token=invalid_access_token,
             valid_token=valid_access_token,
             response_on_valid_token=expected_json,
             response_on_expired_token={
-                'msg': "Missing 'Token' type in 'Authorization' header. Expected 'Authorization: Token <JWT>'"
-            }
+                "msg": "Missing 'Token' type in 'Authorization' header. Expected 'Authorization: Token <JWT>'"
+            },
         ),
-        content_type='application/json',
+        content_type="application/json",
     )
 
     responses.post(
-        'https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/',
-        json={'access_token': valid_access_token, 'refresh_token': ''},
-        content_type='application/json',
+        "https://datalake.cert.orangecyberdefense.com/api/v2/auth/token/",
+        json={"access_token": valid_access_token, "refresh_token": ""},
+        content_type="application/json",
     )
 
-    datalake.Threats.token_manager.access_token = f'Token {invalid_access_token}'
+    datalake.Threats.token_manager.access_token = f"Token {invalid_access_token}"
 
-    assert datalake.Threats.lookup(atom_value='wow.com', atom_type=AtomType.DOMAIN) == expected_json
-    assert caplog.messages == ['Token expired or Missing authorization header. Updating token']
+    assert (
+        datalake.Threats.lookup(atom_value="wow.com", atom_type=AtomType.DOMAIN)
+        == expected_json
+    )
+    assert caplog.messages == [
+        "Token expired or Missing authorization header. Updating token"
+    ]
diff --git a/tests/lib/test_bulk_search.py b/tests/lib/test_bulk_search.py
index da3999e..2a90c66 100644
--- a/tests/lib/test_bulk_search.py
+++ b/tests/lib/test_bulk_search.py
@@ -8,20 +8,21 @@
 import pytest
 import responses
 
-from datalake import Datalake, BulkSearchTaskState, BulkSearchTask, Output, BulkSearchFailedError, BulkSearchNotFound
+from datalake import (
+    Datalake,
+    BulkSearchTaskState,
+    BulkSearchTask,
+    Output,
+    BulkSearchFailedError,
+    BulkSearchNotFound,
+)
 from tests.common.fixture import datalake  # noqa needed fixture import
 
 bs_user = {
     "email": "123@mail.com",
     "full_name": "firstname lastname",
     "id": 12,
-    "organization": {
-        "id": 47,
-        "name": "my_org",
-        "path_names": [
-            "my_org"
-        ]
-    }
+    "organization": {"id": 47, "name": "my_org", "path_names": ["my_org"]},
 }
 
 # <editor-fold desc="bs_status_json">
@@ -31,10 +32,7 @@
         {
             "bulk_search": {
                 "advanced_query_hash": "de70393f1c250ae67566ec37c2032d1b",
-                "query_fields": [
-                    "threat_hashkey",
-                    "atom_value"
-                ]
+                "query_fields": ["threat_hashkey", "atom_value"],
             },
             "bulk_search_hash": "ff2d2dc27f17f115d85647dced7a3106",
             "created_at": "2021-09-21T14:19:26.872073+00:00",
@@ -49,30 +47,34 @@
             "started_at": "2021-09-21T14:19:58.040840+00:00",
             "state": "DONE",
             "user": bs_user,
-            "uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41"
+            "uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41",
         }
-    ]
+    ],
 }
 
 
 # </editor-fold>
 
+
 @pytest.fixture
 def bulk_search_task(datalake: Datalake):
-    bs_creation_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/'
+    bs_creation_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/"
+    )
     bs_creation_response = {
         "bulk_search_hash": "ff2d2dc27f17f115d85647dced7a3106",
-        "query_fields": [
-            "threat_hashkey",
-            "atom_value"
-        ],
+        "query_fields": ["threat_hashkey", "atom_value"],
         "query_hash": "de70393f1c250ae67566ec37c2032d1b",
-        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41"
+        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41",
     }
     with responses.RequestsMock() as response_context:
-        response_context.add(responses.POST, bs_creation_url, json=bs_creation_response, status=200)
-        bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
-        response_context.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
+        response_context.add(
+            responses.POST, bs_creation_url, json=bs_creation_response, status=200
+        )
+        bs_status_url = "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+        response_context.add(
+            responses.POST, bs_status_url, json=bs_status_json, status=200
+        )
         task = datalake.BulkSearch.create_task(query_hash="123")
     return task
 
@@ -86,25 +88,28 @@ def test_bulk_search_no_parameter(datalake: Datalake):
 
 @responses.activate
 def test_bulk_search_query_hash(datalake: Datalake):
-    bs_creation_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/'
+    bs_creation_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/"
+    )
     bs_creation_response = {
         "bulk_search_hash": "ff2d2dc27f17f115d85647dced7a3106",
-        "query_fields": [
-            "threat_hashkey",
-            "atom_value"
-        ],
+        "query_fields": ["threat_hashkey", "atom_value"],
         "query_hash": "de70393f1c250ae67566ec37c2032d1b",
-        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41"
+        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41",
     }
-    responses.add(responses.POST, bs_creation_url, json=bs_creation_response, status=200)
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+    responses.add(
+        responses.POST, bs_creation_url, json=bs_creation_response, status=200
+    )
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
 
     bs = datalake.BulkSearch.create_task(query_hash="123")
-    assert bs.bulk_search_hash == 'ff2d2dc27f17f115d85647dced7a3106'
-    assert bs.advanced_query_hash == 'de70393f1c250ae67566ec37c2032d1b'  # flatten field
+    assert bs.bulk_search_hash == "ff2d2dc27f17f115d85647dced7a3106"
+    assert bs.advanced_query_hash == "de70393f1c250ae67566ec37c2032d1b"  # flatten field
     assert bs.query_fields == ["threat_hashkey", "atom_value"]  # flatten field
-    assert bs.uuid == 'd9c00380-2784-4386-9bc3-aff35cfeeb41'
+    assert bs.uuid == "d9c00380-2784-4386-9bc3-aff35cfeeb41"
     assert bs.state == BulkSearchTaskState.DONE
     assert bs.user == bs_user  # field is not flatten as of now
     assert bs.created_at == datetime.datetime(2021, 9, 21, 14, 19, 26, 872073)
@@ -113,24 +118,19 @@ def test_bulk_search_query_hash(datalake: Datalake):
 
 @responses.activate
 def test_bulk_search_query_body(datalake: Datalake):
-    bs_creation_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/'
+    bs_creation_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/"
+    )
     bs_creation_response = {
         "bulk_search_hash": "ff2d2dc27f17f115d85647dced7a3106",
-        "query_fields": [
-            "threat_hashkey",
-            "atom_value"
-        ],
+        "query_fields": ["threat_hashkey", "atom_value"],
         "query_hash": "de70393f1c250ae67566ec37c2032d1b",
-        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41"
+        "task_uuid": "d9c00380-2784-4386-9bc3-aff35cfeeb41",
     }
     bs_query_body = {
-        "AND": [{
-            "AND": [{
-                "field": "metadata",
-                "type": "search",
-                "value": "some value"
-            }]
-        }]
+        "AND": [
+            {"AND": [{"field": "metadata", "type": "search", "value": "some value"}]}
+        ]
     }
 
     def bs_creation_callback(request):
@@ -145,26 +145,30 @@ def bs_creation_callback(request):
         responses.POST,
         bs_creation_url,
         callback=bs_creation_callback,
-        content_type='application/json',
+        content_type="application/json",
+    )
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
     )
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
     responses.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
 
     bs = datalake.BulkSearch.create_task(
         query_body=bs_query_body,
         query_fields=["atom detail 1", "atom detail 2"],
     )
-    assert bs.bulk_search_hash == 'ff2d2dc27f17f115d85647dced7a3106'
-    assert bs.uuid == 'd9c00380-2784-4386-9bc3-aff35cfeeb41'
+    assert bs.bulk_search_hash == "ff2d2dc27f17f115d85647dced7a3106"
+    assert bs.uuid == "d9c00380-2784-4386-9bc3-aff35cfeeb41"
 
 
 @responses.activate
 def test_bulk_search_task_update(bulk_search_task: BulkSearchTask):
     assert bulk_search_task.queue_position is None
     assert bulk_search_task.state == BulkSearchTaskState.DONE
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     bs_update_json = copy.deepcopy(bs_status_json)
-    bs_update_json['results'][0]['queue_position'] = 42
+    bs_update_json["results"][0]["queue_position"] = 42
     responses.add(responses.POST, bs_status_url, json=bs_update_json, status=200)
 
     bulk_search_task.update()
@@ -175,17 +179,19 @@ def test_bulk_search_task_update(bulk_search_task: BulkSearchTask):
 
 @responses.activate
 def test_bulk_search_task_not_found(datalake: Datalake):
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
-    responses.add(responses.POST, bs_status_url, json={'results': []}, status=200)
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
+    responses.add(responses.POST, bs_status_url, json={"results": []}, status=200)
 
     with pytest.raises(BulkSearchNotFound):
-        datalake.BulkSearch.get_task('non existing task_uuid')
+        datalake.BulkSearch.get_task("non existing task_uuid")
 
 
 @responses.activate
 def test_bulk_search_task_download(bulk_search_task: BulkSearchTask):
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     expected_result = "bulk search download"
     responses.add(responses.GET, bs_download_url, json=expected_result, status=200)
 
@@ -197,7 +203,7 @@ def test_bulk_search_task_download(bulk_search_task: BulkSearchTask):
 @responses.activate
 def test_bulk_search_task_download_not_ready(bulk_search_task: BulkSearchTask):
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     error_message = "Not ready yet"
     json_returned = {"message": ("%s" % error_message)}
     responses.add(responses.GET, bs_download_url, json=json_returned, status=202)
@@ -211,26 +217,29 @@ def test_bulk_search_task_download_not_ready(bulk_search_task: BulkSearchTask):
 def test_bulk_search_task_download_invalid_output(bulk_search_task: BulkSearchTask):
     with pytest.raises(ValueError) as err:
         bulk_search_task.download(Output.MISP)
-    assert str(err.value) == f'MISP output type is not supported. Outputs supported are: CSV, CSV_ZIP, JSON, JSON_ZIP,'\
-                             ' STIX, STIX_ZIP'
+    assert (
+        str(err.value)
+        == f"MISP output type is not supported. Outputs supported are: CSV, CSV_ZIP, JSON, JSON_ZIP,"
+        " STIX, STIX_ZIP"
+    )
 
 
 @responses.activate
 def test_bulk_search_task_download_zip_json_output(bulk_search_task: BulkSearchTask):
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
-    expected_result = 'zip json'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
+    expected_result = "zip json"
 
     def bs_download_callback(request):
-        assert request.headers['Accept'] == 'application/zip'
-        headers = {'Content-Type': 'application/zip'}
+        assert request.headers["Accept"] == "application/zip"
+        headers = {"Content-Type": "application/zip"}
         return 200, headers, expected_result
 
     responses.add_callback(
         responses.GET,
         bs_download_url,
         callback=bs_download_callback,
-        content_type='application/zip',
+        content_type="application/zip",
     )
 
     download_result = bulk_search_task.download(Output.JSON_ZIP)
@@ -241,7 +250,7 @@ def bs_download_callback(request):
 @responses.activate
 def test_bulk_search_task_download_async(bulk_search_task: BulkSearchTask):
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     expected_result = "bulk search download"
     responses.add(responses.GET, bs_download_url, json=expected_result, status=200)
 
@@ -252,11 +261,15 @@ def test_bulk_search_task_download_async(bulk_search_task: BulkSearchTask):
 
 @responses.activate
 def test_bulk_search_task_download_sync(bulk_search_task: BulkSearchTask):
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
-    bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS  # download is not ready yet
+    bulk_search_task.state = (
+        BulkSearchTaskState.IN_PROGRESS
+    )  # download is not ready yet
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     expected_result = "bulk search download"
     responses.add(responses.GET, bs_download_url, json=expected_result, status=200)
 
@@ -266,12 +279,18 @@ def test_bulk_search_task_download_sync(bulk_search_task: BulkSearchTask):
 
 
 @responses.activate
-def test_bulk_search_task_download_sync_stream_to_file(bulk_search_task: BulkSearchTask):
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+def test_bulk_search_task_download_sync_stream_to_file(
+    bulk_search_task: BulkSearchTask,
+):
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
-    bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS  # download is not ready yet
+    bulk_search_task.state = (
+        BulkSearchTaskState.IN_PROGRESS
+    )  # download is not ready yet
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     expected_result = "bulk search download saved in file"
     responses.add(responses.GET, bs_download_url, body=expected_result, status=200)
     temporary_file = tempfile.NamedTemporaryFile()
@@ -285,34 +304,50 @@ def test_bulk_search_task_download_sync_stream_to_file(bulk_search_task: BulkSea
 
 
 @responses.activate
-def test_bulk_search_task_download_sync_stream_to_file_zip(bulk_search_task: BulkSearchTask):
-    bs_status_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+def test_bulk_search_task_download_sync_stream_to_file_zip(
+    bulk_search_task: BulkSearchTask,
+):
+    bs_status_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_status_json, status=200)
-    bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS  # download is not ready yet
+    bulk_search_task.state = (
+        BulkSearchTaskState.IN_PROGRESS
+    )  # download is not ready yet
     task_uuid = bulk_search_task.uuid
-    bs_download_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/'
+    bs_download_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/task/{task_uuid}/"
     # Gzip content
-    expected_result = b'\x1f\x8b\x08\x00\xe3\xaf\x2c\x57\x00\x03\x4b\xcb\xcf\x4f\x4a\x2c' \
-                      b'\xe2\x02\x00\x47\x97\x2c\xb2\x07\x00\x00\x00'
+    expected_result = (
+        b"\x1f\x8b\x08\x00\xe3\xaf\x2c\x57\x00\x03\x4b\xcb\xcf\x4f\x4a\x2c"
+        b"\xe2\x02\x00\x47\x97\x2c\xb2\x07\x00\x00\x00"
+    )
     responses.add(responses.GET, bs_download_url, body=expected_result, status=200)
     temporary_file = tempfile.NamedTemporaryFile()
     output_path = temporary_file.name
 
-    bulk_search_task.download_sync_stream_to_file(output_path=output_path, output=Output.JSON_ZIP)
+    bulk_search_task.download_sync_stream_to_file(
+        output_path=output_path, output=Output.JSON_ZIP
+    )
 
-    with open(output_path, 'rb') as f:
+    with open(output_path, "rb") as f:
         download_result = f.read()
     assert download_result == expected_result
 
 
 @pytest.mark.asyncio
-async def test_bulk_search_task_download_async_timeout(bulk_search_task: BulkSearchTask):
+async def test_bulk_search_task_download_async_timeout(
+    bulk_search_task: BulkSearchTask,
+):
     with responses.RequestsMock() as response_context:  # pytest.mark.asyncio is not compatible with responses decorator
-        bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS  # download will never be ready
+        bulk_search_task.state = (
+            BulkSearchTaskState.IN_PROGRESS
+        )  # download will never be ready
         bs_update_json = copy.deepcopy(bs_status_json)
-        bs_update_json['results'][0]['state'] = 'IN_PROGRESS'
-        bs_status_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
-        response_context.add(responses.POST, bs_status_url, json=bs_update_json, status=200)
+        bs_update_json["results"][0]["state"] = "IN_PROGRESS"
+        bs_status_url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+        response_context.add(
+            responses.POST, bs_status_url, json=bs_update_json, status=200
+        )
 
         task = bulk_search_task.download_async(timeout=0.2)
         with pytest.raises(TimeoutError):
@@ -321,10 +356,14 @@ async def test_bulk_search_task_download_async_timeout(bulk_search_task: BulkSea
 
 @responses.activate
 def test_bulk_search_task_download_sync_timeout(bulk_search_task: BulkSearchTask):
-    bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS  # download will never be ready
+    bulk_search_task.state = (
+        BulkSearchTaskState.IN_PROGRESS
+    )  # download will never be ready
     bs_update_json = copy.deepcopy(bs_status_json)
-    bs_update_json['results'][0]['state'] = 'IN_PROGRESS'
-    bs_status_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+    bs_update_json["results"][0]["state"] = "IN_PROGRESS"
+    bs_status_url = (
+        f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_update_json, status=200)
 
     with pytest.raises(TimeoutError):
@@ -335,8 +374,10 @@ def test_bulk_search_task_download_sync_timeout(bulk_search_task: BulkSearchTask
 def test_bulk_search_task_download_sync_failed(bulk_search_task: BulkSearchTask):
     bulk_search_task.state = BulkSearchTaskState.IN_PROGRESS
     bs_update_json = copy.deepcopy(bs_status_json)
-    bs_update_json['results'][0]['state'] = 'CANCELLED'
-    bs_status_url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/'
+    bs_update_json["results"][0]["state"] = "CANCELLED"
+    bs_status_url = (
+        f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-search/tasks/"
+    )
     responses.add(responses.POST, bs_status_url, json=bs_update_json, status=200)
 
     with pytest.raises(BulkSearchFailedError) as err:
diff --git a/tests/lib/test_output.py b/tests/lib/test_output.py
index 4ee1b50..68df46e 100644
--- a/tests/lib/test_output.py
+++ b/tests/lib/test_output.py
@@ -8,24 +8,24 @@
 
 
 @pytest.mark.parametrize(
-    'method, content_type',
+    "method, content_type",
     [
-        ('json', None),
-        ('json', Output.JSON.value),
-        ('json', Output.MISP.value),
-        ('json', Output.STIX.value),
-        ('text', Output.CSV.value),
-        ('text', Output.JSON_ZIP.value),
-        ('text', Output.CSV_ZIP.value),
-    ]
+        ("json", None),
+        ("json", Output.JSON.value),
+        ("json", Output.MISP.value),
+        ("json", Output.STIX.value),
+        ("text", Output.CSV.value),
+        ("text", Output.JSON_ZIP.value),
+        ("text", Output.CSV_ZIP.value),
+    ],
 )
 def test_parse_response_default(method, content_type):
     mock: Mock = create_autospec(Response)
-    mock.headers = {'Content-Type': content_type} if content_type else {}
-    expected_res = 'json'
-    if method == 'json':
+    mock.headers = {"Content-Type": content_type} if content_type else {}
+    expected_res = "json"
+    if method == "json":
         mock.json.return_value = expected_res
-    elif method == 'text':
+    elif method == "text":
         mock.text = expected_res
 
     res = parse_response(mock)
diff --git a/tests/lib/test_sightings.py b/tests/lib/test_sightings.py
index 2437c14..a99b9a3 100644
--- a/tests/lib/test_sightings.py
+++ b/tests/lib/test_sightings.py
@@ -5,41 +5,50 @@
 from responses import matchers
 
 from tests.common.fixture import datalake  # noqa needed fixture import
-from datalake import IpAtom, FileAtom, Hashes, Jarm, IpService, SightingType, Visibility, ThreatType
+from datalake import (
+    IpAtom,
+    FileAtom,
+    Hashes,
+    Jarm,
+    IpService,
+    SightingType,
+    Visibility,
+    ThreatType,
+)
 
-jarm = Jarm('12/12/2012 12:12:12', 'some_fingerprint', True, 'some_malware')
-ip_service = IpService(77, 'some_service', 'some_application', 'some_protocol')
+jarm = Jarm("12/12/2012 12:12:12", "some_fingerprint", True, "some_malware")
+ip_service = IpService(77, "some_service", "some_application", "some_protocol")
 ip_atom = IpAtom(
-    '8.8.8.8',
-    ['https://some_url.co'],
-    'some_host',
+    "8.8.8.8",
+    ["https://some_url.co"],
+    "some_host",
     4,
     jarm,
-    'some_malware',
-    'owmer',
+    "some_malware",
+    "owmer",
     [1, 2, 3],
-    ip_service
+    ip_service,
 )
-ip_atom1 = IpAtom('9.9.9.9')
+ip_atom1 = IpAtom("9.9.9.9")
 hashes = Hashes(
-    md5='d26351ba789fba3385d2382aa9d24908',
-    sha1='a61e243f25b8b08661011869a53315363697a0f4',
-    sha256='c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-    sha512='01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'
+    md5="d26351ba789fba3385d2382aa9d24908",
+    sha1="a61e243f25b8b08661011869a53315363697a0f4",
+    sha256="c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+    sha512="01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
 )
 file_atom = FileAtom(
     hashes=hashes,
-    filename='some_filename',
-    file_url='some_url',
-    external_analysis_link=['some_external_url'],
+    filename="some_filename",
+    file_url="some_url",
+    external_analysis_link=["some_external_url"],
     filesize=666,
-    filetype='jpg',
-    mimetype='some_mime',
-    filepath='some/path'
+    filetype="jpg",
+    mimetype="some_mime",
+    filepath="some/path",
 )
 threat_types = [ThreatType.PHISHING, ThreatType.SCAM]
-start = datetime.strptime('2021-05-10T16:20:23Z', "%Y-%m-%dT%H:%M:%SZ")
-end = datetime.strptime('2021-05-11T16:20:23Z', "%Y-%m-%dT%H:%M:%SZ")
+start = datetime.strptime("2021-05-10T16:20:23Z", "%Y-%m-%dT%H:%M:%SZ")
+end = datetime.strptime("2021-05-11T16:20:23Z", "%Y-%m-%dT%H:%M:%SZ")
 
 
 def test_prepare_sightings_payload(datalake):
@@ -50,26 +59,24 @@ def test_prepare_sightings_payload(datalake):
     payload_threat_types = [ThreatType.SCAM]
 
     expected_payload = {
-        'ip_list': [
-            {'ip_address': '8.8.8.8'},
-            {'ip_address': '9.9.9.9'}
-        ],
-        'file_list': [
+        "ip_list": [{"ip_address": "8.8.8.8"}, {"ip_address": "9.9.9.9"}],
+        "file_list": [
             {
-                'hashes': {
-                    'md5': 'd26351ba789fba3385d2382aa9d24908',
-                    'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                    'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                    'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'}
+                "hashes": {
+                    "md5": "d26351ba789fba3385d2382aa9d24908",
+                    "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                    "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                    "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
+                }
             }
         ],
-        'start_timestamp': '2021-05-10T16:20:23Z',
-        'end_timestamp': '2021-05-11T16:20:23Z',
-        'visibility': 'PUBLIC',
-        'type': 'positive',
-        'count': 1,
-        'threat_types': ['scam'],
-        'editable': False,
+        "start_timestamp": "2021-05-10T16:20:23Z",
+        "end_timestamp": "2021-05-11T16:20:23Z",
+        "visibility": "PUBLIC",
+        "type": "positive",
+        "count": 1,
+        "threat_types": ["scam"],
+        "editable": False,
     }
 
     payload = datalake.Sightings._prepare_sightings_payload(
@@ -81,7 +88,7 @@ def test_prepare_sightings_payload(datalake):
         visibility,
         count,
         payload_threat_types,
-        editable=False
+        editable=False,
     )
 
     assert expected_payload == payload
@@ -95,25 +102,23 @@ def test_submit_sightings_without_editable(datalake):
     payload_threat_types = [ThreatType.SCAM]
 
     expected_payload = {
-        'ip_list': [
-            {'ip_address': '8.8.8.8'},
-            {'ip_address': '9.9.9.9'}
-        ],
-        'file_list': [
+        "ip_list": [{"ip_address": "8.8.8.8"}, {"ip_address": "9.9.9.9"}],
+        "file_list": [
             {
-                'hashes': {
-                    'md5': 'd26351ba789fba3385d2382aa9d24908',
-                    'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                    'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                    'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'}
+                "hashes": {
+                    "md5": "d26351ba789fba3385d2382aa9d24908",
+                    "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                    "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                    "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
+                }
             }
         ],
-        'start_timestamp': '2021-05-10T16:20:23Z',
-        'end_timestamp': '2021-05-11T16:20:23Z',
-        'visibility': 'PUBLIC',
-        'type': 'positive',
-        'count': 1,
-        'threat_types': ['scam'],
+        "start_timestamp": "2021-05-10T16:20:23Z",
+        "end_timestamp": "2021-05-11T16:20:23Z",
+        "visibility": "PUBLIC",
+        "type": "positive",
+        "count": 1,
+        "threat_types": ["scam"],
     }
 
     payload = datalake.Sightings._prepare_sightings_payload(
@@ -137,33 +142,26 @@ def test_prepare_sightings_payload_with_empty_tags(datalake):
     count = 1
 
     expected_payload = {
-        'file_list': [
+        "file_list": [
             {
-                'hashes': {
-                    'md5': 'd26351ba789fba3385d2382aa9d24908',
-                    'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                    'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                    'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de'
-                              '48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944',
+                "hashes": {
+                    "md5": "d26351ba789fba3385d2382aa9d24908",
+                    "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                    "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                    "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de"
+                    "48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
                 }
             }
         ],
-        'start_timestamp': '2021-05-10T16:20:23Z',
-        'end_timestamp': '2021-05-11T16:20:23Z',
-        'visibility': 'ORGANIZATION',
-        'type': 'neutral',
-        'count': 1,
+        "start_timestamp": "2021-05-10T16:20:23Z",
+        "end_timestamp": "2021-05-11T16:20:23Z",
+        "visibility": "ORGANIZATION",
+        "type": "neutral",
+        "count": 1,
     }
 
     payload = datalake.Sightings._prepare_sightings_payload(
-        atoms,
-        None,
-        start,
-        end,
-        sighting_type,
-        visibility,
-        count,
-        tags=[]
+        atoms, None, start, end, sighting_type, visibility, count, tags=[]
     )
 
     assert expected_payload == payload
@@ -171,59 +169,61 @@ def test_prepare_sightings_payload_with_empty_tags(datalake):
 
 @responses.activate
 def test_submit_sightings(datalake):
-    url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/sighting/'
+    url = "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/sighting/"
 
     expected_request = {
-        "file_list": [{"hashes":
-                           {"md5": "d26351ba789fba3385d2382aa9d24908",
-                            "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
-                            "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
-                            "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de"
-                                      "48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944"}}],
+        "file_list": [
+            {
+                "hashes": {
+                    "md5": "d26351ba789fba3385d2382aa9d24908",
+                    "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                    "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                    "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de"
+                    "48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
+                }
+            }
+        ],
         "ip_list": [{"ip_address": "8.8.8.8"}, {"ip_address": "9.9.9.9"}],
-        "start_timestamp": "2021-05-10T16:20:23Z", "end_timestamp": "2021-05-11T16:20:23Z",
-        "visibility": "PUBLIC", "type": "positive", "count": 1, "threat_types": ["phishing", "scam"],
-        'tags': ['tag1', 'tag2'], 'description': 'some description',
+        "start_timestamp": "2021-05-10T16:20:23Z",
+        "end_timestamp": "2021-05-11T16:20:23Z",
+        "visibility": "PUBLIC",
+        "type": "positive",
+        "count": 1,
+        "threat_types": ["phishing", "scam"],
+        "tags": ["tag1", "tag2"],
+        "description": "some description",
     }
     expected_res = {
-        'count': 1,
-        'end_timestamp': '2021-05-11T16:20:23Z',
-        'relation_type': 'sighting',
-        'reliability': 50,
-        'sighting_version': 1,
-        'sightings': {
-            'file_list': [
+        "count": 1,
+        "end_timestamp": "2021-05-11T16:20:23Z",
+        "relation_type": "sighting",
+        "reliability": 50,
+        "sighting_version": 1,
+        "sightings": {
+            "file_list": [
                 {
-                    'hashes': {
-                        'md5': 'd26351ba789fba3385d2382aa9d24908',
-                        'sha1': 'a61e243f25b8b08661011869a53315363697a0f4',
-                        'sha256': 'c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e',
-                        'sha512': '01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944'
+                    "hashes": {
+                        "md5": "d26351ba789fba3385d2382aa9d24908",
+                        "sha1": "a61e243f25b8b08661011869a53315363697a0f4",
+                        "sha256": "c056f9206143bc43a7f524f946149ad77c0c491ce816a2865feb6e5f2eaf521e",
+                        "sha512": "01525491943d324e928e4d30702fa731db20a2c82dc6b1d8bf7cf157227517de48f10be15053a63be598f75618ea0179c33f8726bde620e976c7ff5a4fbaa944",
                     }
                 }
             ],
-            'ip_list': [
-                {'ip_address': '8.8.8.8'},
-                {'ip_address': '9.9.9.9'}
-            ]
+            "ip_list": [{"ip_address": "8.8.8.8"}, {"ip_address": "9.9.9.9"}],
         },
-        'source_context': {
-            'source_id': 'org:45:public',
-            'source_policy': {
-                'source_uses': [
-                    'commercial',
-                    'internal',
-                    'notify',
-                    'sensitive'
-                ]
-            }
+        "source_context": {
+            "source_id": "org:45:public",
+            "source_policy": {
+                "source_uses": ["commercial", "internal", "notify", "sensitive"]
+            },
         },
-        'start_timestamp': '2021-05-10T16:20:23Z',
-        'tags': [],
-        'threat_types': ['phishing', 'scam'],
-        'timestamp_created': '2022-05-18T12:42:13Z',
-        'type': 'positive',
-        'uid': '1d67a120-8983-4909-a6f0-eec4a7673395'
+        "start_timestamp": "2021-05-10T16:20:23Z",
+        "tags": [],
+        "threat_types": ["phishing", "scam"],
+        "timestamp_created": "2022-05-18T12:42:13Z",
+        "type": "positive",
+        "uid": "1d67a120-8983-4909-a6f0-eec4a7673395",
     }
 
     responses.post(
@@ -240,9 +240,9 @@ def test_submit_sightings(datalake):
         Visibility.PUBLIC,
         1,
         threat_types,
-        tags=['tag1', 'tag2'],
-        description='some description',
-        atoms=[ip_atom, ip_atom1, file_atom]
+        tags=["tag1", "tag2"],
+        description="some description",
+        atoms=[ip_atom, ip_atom1, file_atom],
     )
 
     assert res == expected_res
@@ -251,14 +251,11 @@ def test_submit_sightings(datalake):
 def test_submit_sightings_no_atoms_no_hashkeys(datalake):
     with pytest.raises(ValueError) as err:
         datalake.Sightings.submit_sighting(
-            start,
-            end,
-            SightingType.POSITIVE,
-            Visibility.PUBLIC,
-            1,
-            threat_types
+            start, end, SightingType.POSITIVE, Visibility.PUBLIC, 1, threat_types
         )
-    assert str(err.value) == 'Either threat hashkeys or list of atom objects is required.'
+    assert (
+        str(err.value) == "Either threat hashkeys or list of atom objects is required."
+    )
 
 
 def test_submit_sightings_invalid_count(datalake):
@@ -270,9 +267,9 @@ def test_submit_sightings_invalid_count(datalake):
             Visibility.PUBLIC,
             0,
             threat_types,
-            atoms=[ip_atom, ip_atom1, file_atom]
+            atoms=[ip_atom, ip_atom1, file_atom],
         )
-    assert str(err.value) == 'count value minimum: 1'
+    assert str(err.value) == "count value minimum: 1"
 
 
 def test_submit_sightings_bad_sighting_type(datalake):
@@ -280,13 +277,16 @@ def test_submit_sightings_bad_sighting_type(datalake):
         datalake.Sightings.submit_sighting(
             start,
             end,
-            'positive',
+            "positive",
             Visibility.PUBLIC,
             1,
             threat_types,
-            atoms=[ip_atom, ip_atom1, file_atom]
+            atoms=[ip_atom, ip_atom1, file_atom],
         )
-    assert str(err.value) == 'sighting_type has to be an instance of the SightingType class.'
+    assert (
+        str(err.value)
+        == "sighting_type has to be an instance of the SightingType class."
+    )
 
 
 def test_submit_sightings_no_threat_types(datalake):
@@ -297,10 +297,13 @@ def test_submit_sightings_no_threat_types(datalake):
             SightingType.POSITIVE,
             Visibility.PUBLIC,
             1,
-            atoms=[ip_atom, ip_atom1, file_atom]
+            atoms=[ip_atom, ip_atom1, file_atom],
         )
-    assert str(err.value) == 'For POSITIVE and NEGATIVE sightings "threat_types" field is required and' \
-                             ' has to be an instance of the Visibility class'
+    assert (
+        str(err.value)
+        == 'For POSITIVE and NEGATIVE sightings "threat_types" field is required and'
+        " has to be an instance of the Visibility class"
+    )
 
 
 def test_submit_sightings_neutral_with_threat_types(datalake):
@@ -312,7 +315,7 @@ def test_submit_sightings_neutral_with_threat_types(datalake):
             Visibility.PUBLIC,
             1,
             threat_types,
-            atoms=[ip_atom, ip_atom1, file_atom]
+            atoms=[ip_atom, ip_atom1, file_atom],
         )
     assert str(err.value) == "For NEUTRAL sightings, threat_types can't be passed."
 
@@ -323,12 +326,12 @@ def test_submit_sightings_bad_visibility(datalake):
             start,
             end,
             SightingType.POSITIVE,
-            'public',
+            "public",
             1,
             threat_types,
-            atoms=[ip_atom, ip_atom1, file_atom]
+            atoms=[ip_atom, ip_atom1, file_atom],
         )
-    assert str(err.value) == 'visibility has to be an instance of the Visibility class.'
+    assert str(err.value) == "visibility has to be an instance of the Visibility class."
 
 
 def test_submit_sightings_bad_atom(datalake):
@@ -340,6 +343,71 @@ def test_submit_sightings_bad_atom(datalake):
             Visibility.PUBLIC,
             1,
             threat_types,
-            atoms=[ip_atom, ip_atom1, file_atom, 'not_an_atom']
+            atoms=[ip_atom, ip_atom1, file_atom, "not_an_atom"],
         )
-    assert str(err.value) == 'atoms needs to be a list of Atom subclasses.'
+    assert str(err.value) == "atoms needs to be a list of Atom subclasses."
+
+
+def test_sightings_filtered_bad_ordering(datalake):
+    with pytest.raises(ValueError) as err:
+        datalake.Sightings.sightings_filtered(ordering="bad_ordering")
+    assert (
+        str(err.value)
+        == 'ordering has to be one of the following: "start_timestamp", "-start_timestamp", "end_timestamp", "-end_timestamp", "timestamp_created", "-timestamp_created", "count", "-count"'
+    )
+
+
+def test_sightings_filtered_bad_type(datalake):
+    with pytest.raises(ValueError) as err:
+        datalake.Sightings.sightings_filtered(sighting_type="bad_sighting_type")
+    assert (
+        str(err.value)
+        == "sighting_type has to be an instance of the SightingType class."
+    )
+
+
+def test_sightings_filtered_bad_visibility(datalake):
+    with pytest.raises(ValueError) as err:
+        datalake.Sightings.sightings_filtered(visibility="bad_visibility")
+    assert str(err.value) == "visibility has to be an instance of the Visibility class."
+
+
+@responses.activate
+def test_sightings_filtered(datalake):
+    url = "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/sighting/filtered/"
+    expected_payload = {"threat_hashkey": "f39cbce3c4d30d61ccdc99c5fcb3bf6f"}
+    expected = {
+        "count": 1,
+        "results": [
+            {
+                "atom_type": "ip",
+                "content": {"ip_content": {"ip_address": "8.8.8.8"}},
+                "count": 1,
+                "end_timestamp": "2022-12-26T13:00:00Z",
+                "is_editable": False,
+                "reliability": 50,
+                "sighting_file_hashkey": "some_hashkey",
+                "sighting_hashkey": "another_hashkey",
+                "sighting_version": 1,
+                "source_context": {
+                    "source_id": "org: org_name",
+                    "source_policy": {"source_uses": ["commercial"]},
+                },
+                "start_timestamp": "2022-12-26T13:00:00Z",
+                "tags": [],
+                "threat_hashkey": "f39cbce3c4d30d61ccdc99c5fcb3bf6f",
+                "threat_types": [],
+                "timestamp_created": "2022-12-26T13:41:20Z",
+                "type": "neutral",
+                "visibility": "PUBLIC",
+            }
+        ],
+    }
+    responses.post(
+        url=url,
+        json=expected,
+        status=200,
+        match=[matchers.json_params_matcher(expected_payload)],
+    )
+    res = datalake.Sightings.sightings_filtered("f39cbce3c4d30d61ccdc99c5fcb3bf6f")
+    assert res == expected
diff --git a/tests/lib/test_tags.py b/tests/lib/test_tags.py
index ce94d13..8bfa7ba 100644
--- a/tests/lib/test_tags.py
+++ b/tests/lib/test_tags.py
@@ -6,17 +6,16 @@
 value_error_msg = "Tags has to be a list of string"
 
 
-
 @responses.activate
 def test_add_to_threat(datalake: Datalake):
     mock_api_resp()
-    resp = datalake.Tags.add_to_threat(hashkey="123456abcd", tags=['tag'])
-    assert resp[0]['author']['full_name'] == "string"
+    resp = datalake.Tags.add_to_threat(hashkey="123456abcd", tags=["tag"])
+    assert resp[0]["author"]["full_name"] == "string"
 
 
 def test_add_to_threat_bad_tags(datalake: Datalake):
     with pytest.raises(ValueError) as execinfo:
-        datalake.Tags.add_to_threat(hashkey="123456abcd", tags='[tag]')
+        datalake.Tags.add_to_threat(hashkey="123456abcd", tags="[tag]")
     assert str(execinfo.value) == value_error_msg
 
 
@@ -42,7 +41,7 @@ def test_add_to_threat_not_str_tags(datalake: Datalake):
 def test_add_to_threat_bad_hash(datalake: Datalake):
     mock_api_resp_404()
     with pytest.raises(ValueError) as execinfo:
-        datalake.Tags.add_to_threat(hashkey="789101112efghij", tags=['tag'])
+        datalake.Tags.add_to_threat(hashkey="789101112efghij", tags=["tag"])
     assert str(execinfo.value) == '404: {"message": "Threat does not exist."}'
 
 
@@ -53,24 +52,15 @@ def mock_api_resp():
         status=200,
         json=[
             {
-                "author": {
-                    "full_name": "string",
-                    "organization_id": 0,
-                    "user_id": 0
-                },
+                "author": {"full_name": "string", "organization_id": 0, "user_id": 0},
                 "name": "string",
                 "system_origin": {
-                    "source": {
-                        "source_id": "string",
-                        "source_uses": [
-                            "string"
-                        ]
-                    }
+                    "source": {"source_id": "string", "source_uses": ["string"]}
                 },
                 "timestamp_created": "2021-11-24T12:48:13.940Z",
-                "visibility": "organization"
+                "visibility": "organization",
             }
-        ]
+        ],
     )
 
 
@@ -79,7 +69,5 @@ def mock_api_resp_404():
         responses.POST,
         "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/789101112efghij/tags/",
         status=404,
-        json={
-            "message": "Threat does not exist."
-        }
+        json={"message": "Threat does not exist."},
     )
diff --git a/tests/lib/test_threats.py b/tests/lib/test_threats.py
index 1d889a8..1d051fe 100644
--- a/tests/lib/test_threats.py
+++ b/tests/lib/test_threats.py
@@ -6,73 +6,88 @@
 from datalake import Datalake, Output, AtomType, ThreatType, OverrideType, IpAtom
 from tests.common.fixture import datalake  # noqa needed fixture import
 
-atoms = [
-    'mayoclinic.org',
-    'commentcamarche.net',
-    'gawker.com'
-]
+atoms = ["mayoclinic.org", "commentcamarche.net", "gawker.com"]
 
-atom_values_extract_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/atom-values-extract/'
+atom_values_extract_url = "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/atom-values-extract/"
 
 
 @responses.activate
 def test_lookup_threat(datalake):
-    lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/' \
-                 '?atom_value=mayoclinic.org&atom_type=domain&hashkey_only=False'
+    lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/"
+        "?atom_value=mayoclinic.org&atom_type=domain&hashkey_only=False"
+    )
     # <editor-fold desc="resp_json">
-    resp_json = {'atom_type': 'domain',
-                 'content': {'domain_content': {'atom_value': 'mayoclinic.org',
-                                                'depth': 1,
-                                                'domain': 'mayoclinic.org',
-                                                'notify': True,
-                                                'tld': 'org'}},
-                 'first_seen': '2021-04-03T21:10:33Z',
-                 'hashkey': '13166b76877347b83ec060f44b847071',
-                 'href_graph': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/graph/',
-                 'href_history': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/13166b76877347b83ec060f44b847071/',
-                 'href_threat': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/',
-                 'last_updated': '2021-05-12T10:55:49Z',
-                 'metadata': {'virustotal_url_feed': {'last_analysis_stats': {'harmless': 80,
-                                                                              'malicious': 0,
-                                                                              'suspicious': 0,
-                                                                              'timeout': 0,
-                                                                              'undetected': 7},
-                                                      'permalink': 'https://www.virustotal.com/gui/url/af017a61fedd9c7002db06689a43b28fb14ef76d590f67694506bfc0815fd667',
-                                                      'positives': 0,
-                                                      'total': 87}},
-                 'scores': [{'score': {'reliability': 16, 'risk': 0}, 'threat_type': 'malware'},
-                            {'score': {'reliability': 16, 'risk': 0},
-                             'threat_type': 'phishing'},
-                            {'score': {'reliability': 16, 'risk': 0}, 'threat_type': 'spam'}],
-                 'sources': [{'count': 2,
-                              'first_seen': '2021-04-03T21:10:33Z',
-                              'last_updated': '2021-05-12T10:55:49Z',
-                              'max_depth': 1,
-                              'min_depth': 1,
-                              'source_id': 'virustotal_url_feed (notify)',
-                              'source_policy': {'source_categories': ['threatintell',
-                                                                      'reputation',
-                                                                      'antivirus'],
-                                                'source_conditions': 'yes',
-                                                'source_name_display': ['restricted_internal'],
-                                                'source_references_conditions': 'no resell',
-                                                'source_uses': ['notify']},
-                              'tlp': 'amber'}],
-                 'system_first_seen': '2021-04-05T22:02:33Z',
-                 'system_last_updated': '2021-05-12T11:56:24Z',
-                 'tags': []}
+    resp_json = {
+        "atom_type": "domain",
+        "content": {
+            "domain_content": {
+                "atom_value": "mayoclinic.org",
+                "depth": 1,
+                "domain": "mayoclinic.org",
+                "notify": True,
+                "tld": "org",
+            }
+        },
+        "first_seen": "2021-04-03T21:10:33Z",
+        "hashkey": "13166b76877347b83ec060f44b847071",
+        "href_graph": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/graph/",
+        "href_history": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/13166b76877347b83ec060f44b847071/",
+        "href_threat": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/",
+        "last_updated": "2021-05-12T10:55:49Z",
+        "metadata": {
+            "virustotal_url_feed": {
+                "last_analysis_stats": {
+                    "harmless": 80,
+                    "malicious": 0,
+                    "suspicious": 0,
+                    "timeout": 0,
+                    "undetected": 7,
+                },
+                "permalink": "https://www.virustotal.com/gui/url/af017a61fedd9c7002db06689a43b28fb14ef76d590f67694506bfc0815fd667",
+                "positives": 0,
+                "total": 87,
+            }
+        },
+        "scores": [
+            {"score": {"reliability": 16, "risk": 0}, "threat_type": "malware"},
+            {"score": {"reliability": 16, "risk": 0}, "threat_type": "phishing"},
+            {"score": {"reliability": 16, "risk": 0}, "threat_type": "spam"},
+        ],
+        "sources": [
+            {
+                "count": 2,
+                "first_seen": "2021-04-03T21:10:33Z",
+                "last_updated": "2021-05-12T10:55:49Z",
+                "max_depth": 1,
+                "min_depth": 1,
+                "source_id": "virustotal_url_feed (notify)",
+                "source_policy": {
+                    "source_categories": ["threatintell", "reputation", "antivirus"],
+                    "source_conditions": "yes",
+                    "source_name_display": ["restricted_internal"],
+                    "source_references_conditions": "no resell",
+                    "source_uses": ["notify"],
+                },
+                "tlp": "amber",
+            }
+        ],
+        "system_first_seen": "2021-04-05T22:02:33Z",
+        "system_last_updated": "2021-05-12T11:56:24Z",
+        "tags": [],
+    }
     # </editor-fold>
     extractor_response = {
         "found": 1,
         "not_found": 0,
-        "results": {
-            "domain": [
-                "mayoclinic.org"
-            ]
-        }
+        "results": {"domain": ["mayoclinic.org"]},
     }
-    responses.add(responses.POST, atom_values_extract_url, json=extractor_response, status=200)
-    responses.add(responses.GET, lookup_url, match_querystring=True, json=resp_json, status=200)
+    responses.add(
+        responses.POST, atom_values_extract_url, json=extractor_response, status=200
+    )
+    responses.add(
+        responses.GET, lookup_url, match_querystring=True, json=resp_json, status=200
+    )
 
     lookup_response = datalake.Threats.lookup(atoms[0])
 
@@ -84,27 +99,32 @@ def test_lookup_threat_invalid_output(datalake: Datalake):
     wrong_output = "123"
     with pytest.raises(ValueError) as err:
         datalake.Threats.lookup(atoms[0], output=wrong_output)
-    assert str(err.value) == f'{wrong_output} output type is not supported. ' \
-                             f'Outputs supported are: CSV, JSON, MISP, STIX'
+    assert (
+        str(err.value) == f"{wrong_output} output type is not supported. "
+        f"Outputs supported are: CSV, JSON, MISP, STIX"
+    )
 
 
 @responses.activate
 def test_lookup_threat_specific_output(datalake: Datalake):
-    lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/' \
-                 '?atom_value=domain.net&atom_type=domain&hashkey_only=True'
+    lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/lookup/"
+        "?atom_value=domain.net&atom_type=domain&hashkey_only=True"
+    )
     some_csv = "some csv"
 
     def request_callback(req):
-        assert req.headers['Accept'] == 'text/csv'
-        return 200, {'Content-Type': 'text/csv'}, some_csv
+        assert req.headers["Accept"] == "text/csv"
+        return 200, {"Content-Type": "text/csv"}, some_csv
 
     responses.add_callback(
-        responses.GET, lookup_url,
+        responses.GET,
+        lookup_url,
         callback=request_callback,
         match_querystring=True,
     )
     res = datalake.Threats.lookup(
-        'domain.net',
+        "domain.net",
         atom_type=AtomType.DOMAIN,
         hashkey_only=True,
         output=Output.CSV,
@@ -117,72 +137,100 @@ def test_bulk_lookup_threats(datalake):
     extractor_response = {
         "found": 1,
         "not_found": 0,
-        "results": {
-            "domain": [
-                "mayoclinic.org"
-            ]
-        }
+        "results": {"domain": ["mayoclinic.org"]},
     }
-    bulk_lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/'
-    responses.add(responses.POST, atom_values_extract_url, json=extractor_response, status=200)
+    bulk_lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/"
+    )
+    responses.add(
+        responses.POST, atom_values_extract_url, json=extractor_response, status=200
+    )
     # <editor-fold desc="bulk_resp">
-    bulk_resp = {'domain': [{'atom_value': 'mayoclinic.org',
-                             'hashkey': '13166b76877347b83ec060f44b847071',
-                             'threat_details': {'atom_type': 'domain',
-                                                'content': {'domain_content': {'atom_value': 'mayoclinic.org',
-                                                                               'depth': 1,
-                                                                               'domain': 'mayoclinic.org',
-                                                                               'notify': True,
-                                                                               'tld': 'org'}},
-                                                'first_seen': '2021-04-03T21:10:33Z',
-                                                'hashkey': '13166b76877347b83ec060f44b847071',
-                                                'href_graph': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/graph/',
-                                                'href_history': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/13166b76877347b83ec060f44b847071/',
-                                                'href_threat': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/',
-                                                'last_updated': '2021-05-12T10:55:49Z',
-                                                'metadata': {
-                                                    'virustotal_url_feed': {'last_analysis_stats': {'harmless': 80,
-                                                                                                    'malicious': 0,
-                                                                                                    'suspicious': 0,
-                                                                                                    'timeout': 0,
-                                                                                                    'undetected': 7},
-                                                                            'permalink': 'https://www.virustotal.com/gui/url/af017a61fedd9c7002db06689a43b28fb14ef76d590f67694506bfc0815fd667',
-                                                                            'positives': 0,
-                                                                            'total': 87}},
-                                                'scores': [{'score': {'reliability': 16,
-                                                                      'risk': 0},
-                                                            'threat_type': 'malware'},
-                                                           {'score': {'reliability': 16,
-                                                                      'risk': 0},
-                                                            'threat_type': 'phishing'},
-                                                           {'score': {'reliability': 16,
-                                                                      'risk': 0},
-                                                            'threat_type': 'spam'}],
-                                                'sources': [{'count': 2,
-                                                             'first_seen': '2021-04-03T21:10:33Z',
-                                                             'last_updated': '2021-05-12T10:55:49Z',
-                                                             'max_depth': 1,
-                                                             'min_depth': 1,
-                                                             'source_id': 'virustotal_url_feed '
-                                                                          '(notify)',
-                                                             'source_policy': {'source_categories': ['threatintell',
-                                                                                                     'reputation',
-                                                                                                     'antivirus'],
-                                                                               'source_conditions': 'yes',
-                                                                               'source_name_display': [
-                                                                                   'restricted_internal'],
-                                                                               'source_references_conditions': 'no '
-                                                                                                               'resell',
-                                                                               'source_uses': ['notify']},
-                                                             'tlp': 'amber'}],
-                                                'system_first_seen': '2021-04-05T22:02:33Z',
-                                                'system_last_updated': '2021-05-12T11:56:24Z',
-                                                'tags': []},
-                             'threat_found': True},
-                            {'atom_value': 'gawker.com',
-                             'hashkey': '664d2e13bff4ac355c94b4f62ac0b92a',
-                             'threat_found': False}
-                            ]}
+    bulk_resp = {
+        "domain": [
+            {
+                "atom_value": "mayoclinic.org",
+                "hashkey": "13166b76877347b83ec060f44b847071",
+                "threat_details": {
+                    "atom_type": "domain",
+                    "content": {
+                        "domain_content": {
+                            "atom_value": "mayoclinic.org",
+                            "depth": 1,
+                            "domain": "mayoclinic.org",
+                            "notify": True,
+                            "tld": "org",
+                        }
+                    },
+                    "first_seen": "2021-04-03T21:10:33Z",
+                    "hashkey": "13166b76877347b83ec060f44b847071",
+                    "href_graph": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/graph/",
+                    "href_history": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/13166b76877347b83ec060f44b847071/",
+                    "href_threat": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/13166b76877347b83ec060f44b847071/",
+                    "last_updated": "2021-05-12T10:55:49Z",
+                    "metadata": {
+                        "virustotal_url_feed": {
+                            "last_analysis_stats": {
+                                "harmless": 80,
+                                "malicious": 0,
+                                "suspicious": 0,
+                                "timeout": 0,
+                                "undetected": 7,
+                            },
+                            "permalink": "https://www.virustotal.com/gui/url/af017a61fedd9c7002db06689a43b28fb14ef76d590f67694506bfc0815fd667",
+                            "positives": 0,
+                            "total": 87,
+                        }
+                    },
+                    "scores": [
+                        {
+                            "score": {"reliability": 16, "risk": 0},
+                            "threat_type": "malware",
+                        },
+                        {
+                            "score": {"reliability": 16, "risk": 0},
+                            "threat_type": "phishing",
+                        },
+                        {
+                            "score": {"reliability": 16, "risk": 0},
+                            "threat_type": "spam",
+                        },
+                    ],
+                    "sources": [
+                        {
+                            "count": 2,
+                            "first_seen": "2021-04-03T21:10:33Z",
+                            "last_updated": "2021-05-12T10:55:49Z",
+                            "max_depth": 1,
+                            "min_depth": 1,
+                            "source_id": "virustotal_url_feed " "(notify)",
+                            "source_policy": {
+                                "source_categories": [
+                                    "threatintell",
+                                    "reputation",
+                                    "antivirus",
+                                ],
+                                "source_conditions": "yes",
+                                "source_name_display": ["restricted_internal"],
+                                "source_references_conditions": "no " "resell",
+                                "source_uses": ["notify"],
+                            },
+                            "tlp": "amber",
+                        }
+                    ],
+                    "system_first_seen": "2021-04-05T22:02:33Z",
+                    "system_last_updated": "2021-05-12T11:56:24Z",
+                    "tags": [],
+                },
+                "threat_found": True,
+            },
+            {
+                "atom_value": "gawker.com",
+                "hashkey": "664d2e13bff4ac355c94b4f62ac0b92a",
+                "threat_found": False,
+            },
+        ]
+    }
     # </editor-fold>
 
     responses.add(responses.POST, bulk_lookup_url, json=bulk_resp, status=200)
@@ -194,68 +242,84 @@ def test_bulk_lookup_return_search_hashkey(datalake):
     extractor_response = {
         "found": 1,
         "not_found": 0,
-        "results": {
-            "domain": [
-                "mayoclinic.org"
-            ]
-        }
+        "results": {"domain": ["mayoclinic.org"]},
     }
-    bulk_lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/'
-    responses.add(responses.POST, atom_values_extract_url, json=extractor_response, status=200)
+    bulk_lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/"
+    )
+    responses.add(
+        responses.POST, atom_values_extract_url, json=extractor_response, status=200
+    )
     # <editor-fold desc="bulk_resp">
     bulk_resp = {
-        'domain': [
+        "domain": [
             {
-                'access_permission': True,
-                'atom_value': 'mayoclinic.org',
-                'hashkey': '13166b76877347b83ec060f44b847071',
-                'threat_found': True
+                "access_permission": True,
+                "atom_value": "mayoclinic.org",
+                "hashkey": "13166b76877347b83ec060f44b847071",
+                "threat_found": True,
             }
         ],
-        'search_hashkey': '83fd935d302db70155cceddd09b15dfd'}  # </editor-fold>
+        "search_hashkey": "83fd935d302db70155cceddd09b15dfd",
+    }  # </editor-fold>
     expected_resp = {
-        'domain': [
+        "domain": [
             {
-                'access_permission': True,
-                'atom_value': 'mayoclinic.org',
-                'hashkey': '13166b76877347b83ec060f44b847071',
-                'threat_found': True
+                "access_permission": True,
+                "atom_value": "mayoclinic.org",
+                "hashkey": "13166b76877347b83ec060f44b847071",
+                "threat_found": True,
             }
         ],
-        'search_hashkey': ['83fd935d302db70155cceddd09b15dfd']}
+        "search_hashkey": ["83fd935d302db70155cceddd09b15dfd"],
+    }
     responses.add(responses.POST, bulk_lookup_url, json=bulk_resp, status=200)
-    assert datalake.Threats.bulk_lookup(atom_values=atoms, return_search_hashkey=True) == expected_resp
+    assert (
+        datalake.Threats.bulk_lookup(atom_values=atoms, return_search_hashkey=True)
+        == expected_resp
+    )
 
 
 @responses.activate
 def test_bulk_lookup_threats_on_typed_atoms(datalake):
-    bulk_lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/'
+    bulk_lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/"
+    )
 
-    bulk_resp = {'file': [{'uid': '123'}]}  # Only check the API response is returned as is
+    bulk_resp = {
+        "file": [{"uid": "123"}]
+    }  # Only check the API response is returned as is
 
     responses.add(responses.POST, bulk_lookup_url, json=bulk_resp, status=200)
-    assert datalake.Threats.bulk_lookup(atom_values=atoms, atom_type=AtomType.DOMAIN) == bulk_resp
+    assert (
+        datalake.Threats.bulk_lookup(atom_values=atoms, atom_type=AtomType.DOMAIN)
+        == bulk_resp
+    )
 
 
 @responses.activate
 def test_bulk_lookup_threats_on_big_chunk_json(datalake):
-    atom_values = [f'domain{i}.com' for i in range(10_000)]
-    bulk_lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/'
+    atom_values = [f"domain{i}.com" for i in range(10_000)]
+    bulk_lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/"
+    )
 
     def request_callback(req):
-        assert req.headers['Accept'] == 'application/json'
+        assert req.headers["Accept"] == "application/json"
         body = json.loads(req.body)
-        assert len(body['domain']) == 100
+        assert len(body["domain"]) == 100
 
         resp = {
-            'domain': [
-                {'atom_value': domain,
-                 'hashkey': '664d2e13bff4ac355c94b4f62ac0b92a',
-                 'threat_found': False}
-                for domain in body['domain']
+            "domain": [
+                {
+                    "atom_value": domain,
+                    "hashkey": "664d2e13bff4ac355c94b4f62ac0b92a",
+                    "threat_found": False,
+                }
+                for domain in body["domain"]
             ]
         }
-        return 200, {'Content-Type': 'application/json'}, json.dumps(resp)
+        return 200, {"Content-Type": "application/json"}, json.dumps(resp)
 
     responses.add_callback(
         responses.POST,
@@ -264,28 +328,37 @@ def request_callback(req):
         match_querystring=True,
     )
 
-    api_response = datalake.Threats.bulk_lookup(atom_values=atom_values, atom_type=AtomType.DOMAIN)
+    api_response = datalake.Threats.bulk_lookup(
+        atom_values=atom_values, atom_type=AtomType.DOMAIN
+    )
 
-    assert len(responses.calls) == 100, 'big chunk of atoms should be split in multiple query for bulk lookup'
-    assert len(api_response['domain']) == 10_000
+    assert (
+        len(responses.calls) == 100
+    ), "big chunk of atoms should be split in multiple query for bulk lookup"
+    assert len(api_response["domain"]) == 10_000
 
 
 @responses.activate
 def test_bulk_lookup_threats_on_big_chunk_csv(datalake):
-    atom_values = [f'domain{i}.com' for i in range(5_000)]
-    bulk_lookup_url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/'
-    header = 'hashkey,atom_type,atom_value,atom_value_best_matching,threat_found,events_number,first_seen,last_updated,threat_types,ddos.score.risk,fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,phishing.score.risk,scam.score.risk,scan.score.risk,spam.score.risk,sources,tags,href_graph,href_history,href_threat,href_threat_webGUI'
+    atom_values = [f"domain{i}.com" for i in range(5_000)]
+    bulk_lookup_url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats/bulk-lookup/"
+    )
+    header = "hashkey,atom_type,atom_value,atom_value_best_matching,threat_found,events_number,first_seen,last_updated,threat_types,ddos.score.risk,fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,phishing.score.risk,scam.score.risk,scan.score.risk,spam.score.risk,sources,tags,href_graph,href_history,href_threat,href_threat_webGUI"
 
     def request_callback(req):
-        assert req.headers['Accept'] == 'text/csv'
+        assert req.headers["Accept"] == "text/csv"
         body = json.loads(req.body)
-        assert len(body['domain']) == 100
+        assert len(body["domain"]) == 100
 
-        resp = '\n'.join(
-            [header] +
-            [f'02bd4baae2bb8142509984c3c7574512,domain,{domain},,False,,,,,,,,,,,,,,,,,,,' for domain in body['domain']]
+        resp = "\n".join(
+            [header]
+            + [
+                f"02bd4baae2bb8142509984c3c7574512,domain,{domain},,False,,,,,,,,,,,,,,,,,,,"
+                for domain in body["domain"]
+            ]
         )
-        return 200, {'Content-Type': 'text/csv'}, resp
+        return 200, {"Content-Type": "text/csv"}, resp
 
     responses.add_callback(
         responses.POST,
@@ -300,8 +373,10 @@ def request_callback(req):
         output=Output.CSV,
     )
 
-    assert len(responses.calls) == 50, 'big chunk of atoms should be split in multiple query for bulk lookup'
-    csv_lines = api_response.split('\n')
+    assert (
+        len(responses.calls) == 50
+    ), "big chunk of atoms should be split in multiple query for bulk lookup"
+    csv_lines = api_response.split("\n")
     assert len(csv_lines) == 5_001
     assert csv_lines[0] == header
 
@@ -311,144 +386,157 @@ def test_bulk_lookup_threat_invalid_output(datalake: Datalake):
     wrong_output = "123"
     with pytest.raises(ValueError) as err:
         datalake.Threats.bulk_lookup(atoms, output=wrong_output)
-    assert str(err.value) == f'{wrong_output} output type is not supported. Outputs supported are: CSV, JSON'
+    assert (
+        str(err.value)
+        == f"{wrong_output} output type is not supported. Outputs supported are: CSV, JSON"
+    )
 
 
 @responses.activate
 def test_bulk_lookup_threat_not_supported_output(datalake: Datalake):
     with pytest.raises(ValueError) as err:
         datalake.Threats.bulk_lookup(atoms, output=Output.MISP)
-    assert str(err.value) == f'MISP output type is not supported. Outputs supported are: CSV, JSON'
+    assert (
+        str(err.value)
+        == f"MISP output type is not supported. Outputs supported are: CSV, JSON"
+    )
 
 
 def test_edit_score_by_hashkeys_invalid_input(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        datalake.Threats.edit_score_by_hashkeys('not a list', None, None)
-    assert str(err.value) == 'Hashkeys has to be a list of string'
+        datalake.Threats.edit_score_by_hashkeys("not a list", None, None)
+    assert str(err.value) == "Hashkeys has to be a list of string"
 
 
 def test_edit_score_by_hashkeys_empty_input(datalake: Datalake):
     with pytest.raises(ValueError) as err:
         datalake.Threats.edit_score_by_hashkeys([], None, None)
-    assert str(err.value) == 'Hashkeys has to be a list of string'
+    assert str(err.value) == "Hashkeys has to be a list of string"
 
 
 def test_edit_score_by_hashkeys_invalid_list(datalake: Datalake):
     with pytest.raises(ValueError) as err:
         datalake.Threats.edit_score_by_hashkeys([1, 2, 3], None, None)
-    assert str(err.value) == 'Hashkeys has to be a list of string'
+    assert str(err.value) == "Hashkeys has to be a list of string"
 
 
 def test_edit_score_by_hashkeys_empty_list_element(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        datalake.Threats.edit_score_by_hashkeys(['xxx-some-hashkey-xxx', ''], None, None)
-    assert str(err.value) == 'Hashkeys has to be a list of string'
+        datalake.Threats.edit_score_by_hashkeys(
+            ["xxx-some-hashkey-xxx", ""], None, None
+        )
+    assert str(err.value) == "Hashkeys has to be a list of string"
 
 
 def test_edit_score_by_hashkeys_invalid_scores_threat_type(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        datalake.Threats.edit_score_by_hashkeys(['some_hashkey'], [{'threat_type': 'ddos'}])
-    assert str(err.value) == 'Invalid threat_type input'
+        datalake.Threats.edit_score_by_hashkeys(
+            ["some_hashkey"], [{"threat_type": "ddos"}]
+        )
+    assert str(err.value) == "Invalid threat_type input"
 
 
 def test_edit_score_by_hashkeys_invalid_scores_score(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        datalake.Threats.edit_score_by_hashkeys(['some_hashkey'], [{'threat_type': ThreatType.DDOS, 'score': 999}])
-    assert str(err.value) == 'Invalid score input, min: 0, max: 100'
+        datalake.Threats.edit_score_by_hashkeys(
+            ["some_hashkey"], [{"threat_type": ThreatType.DDOS, "score": 999}]
+        )
+    assert str(err.value) == "Invalid score input, min: 0, max: 100"
 
 
 def test_edit_score_bad_override_type(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        datalake.Threats.edit_score_by_hashkeys(['some_hashkey'], [{'threat_type': 'ddos'}], 'lock')
-    assert str(err.value) == 'Invalid OverrideType input'
+        datalake.Threats.edit_score_by_hashkeys(
+            ["some_hashkey"], [{"threat_type": "ddos"}], "lock"
+        )
+    assert str(err.value) == "Invalid OverrideType input"
 
 
 def test_add_threats_not_threat_types_not_whitelist(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        atom_list = ['100.100.100.1']
+        atom_list = ["100.100.100.1"]
         datalake.Threats.add_threats(atom_list, AtomType.IP)
-    assert str(err.value) == 'threat_types is required if the atom is not for whitelisting'
+    assert (
+        str(err.value) == "threat_types is required if the atom is not for whitelisting"
+    )
 
 
 def test_add_threats_bad_override_type(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        atom_list = ['100.100.100.1']
-        threat_types = [{'threat_type': ThreatType('ddos'), 'score': 5}]
-        datalake.Threats.add_threats(atom_list, AtomType.IP, threat_types, 'lock')
-    assert str(err.value) == 'Invalid OverrideType input'
+        atom_list = ["100.100.100.1"]
+        threat_types = [{"threat_type": ThreatType("ddos"), "score": 5}]
+        datalake.Threats.add_threats(atom_list, AtomType.IP, threat_types, "lock")
+    assert str(err.value) == "Invalid OverrideType input"
 
 
 def test_add_threats_bad_atom(datalake: Datalake):
     with pytest.raises(ValueError) as err:
-        atom_list = ['100.100.100.1', '']
-        threat_types = [{'threat_type': ThreatType('ddos'), 'score': 5}]
-        datalake.Threats.add_threats(atom_list, AtomType.IP, threat_types, OverrideType.TEMPORARY)
-    assert str(err.value) == 'Empty atom in atom_list'
+        atom_list = ["100.100.100.1", ""]
+        threat_types = [{"threat_type": ThreatType("ddos"), "score": 5}]
+        datalake.Threats.add_threats(
+            atom_list, AtomType.IP, threat_types, OverrideType.TEMPORARY
+        )
+    assert str(err.value) == "Empty atom in atom_list"
 
 
 @responses.activate
 def test_add_threats_no_bulk(datalake: Datalake):
-    url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats-manual/'
+    url = "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/threats-manual/"
     ip = IpAtom(
-        ip_address='11.11.111.1',
-        external_analysis_link=['https://somelink.co'],
-        hostname='hostname',
-        ip_version=4
+        ip_address="11.11.111.1",
+        external_analysis_link=["https://somelink.co"],
+        hostname="hostname",
+        ip_version=4,
     )
     resp = {
-        'atom_type': 'ip',
-        'atom_value': '11.11.111.1',
-        'delivery_timestamp': '2021-12-23T15:57:23.450107+00:00',
-        'hashkey': '3e3f43a23fadc97a5d4c72424d62f48a',
-        'override_type': 'lock',
-        'public': False,
-        'threat_data': {
-            'content': {
-                'ip_content': {
-                    'ip_address': '11.11.111.1',
-                    'ip_version': 4,
-                    'external_analysis_link': ['https://somelink.co'],
-                    'hostname': 'hostname'
+        "atom_type": "ip",
+        "atom_value": "11.11.111.1",
+        "delivery_timestamp": "2021-12-23T15:57:23.450107+00:00",
+        "hashkey": "3e3f43a23fadc97a5d4c72424d62f48a",
+        "override_type": "lock",
+        "public": False,
+        "threat_data": {
+            "content": {
+                "ip_content": {
+                    "ip_address": "11.11.111.1",
+                    "ip_version": 4,
+                    "external_analysis_link": ["https://somelink.co"],
+                    "hostname": "hostname",
                 }
             },
-            'scores': [
-                {
-                    'score': {
-                        'risk': 0
-                    },
-                    'threat_type': 'ddos'
-                }
-            ],
-            'tags': ['test_tag', 'ocd']
+            "scores": [{"score": {"risk": 0}, "threat_type": "ddos"}],
+            "tags": ["test_tag", "ocd"],
         },
-        'timestamp_created': '2021-12-23T15:57:23.306039+00:00',
-        'user': {
-            'email': 'user.user@email.com',
-            'full_name': 'User USER', 'id': 0,
-            'organization': {'id': 0, 'name': 'ORG', 'path_names': ['ORG']}
+        "timestamp_created": "2021-12-23T15:57:23.306039+00:00",
+        "user": {
+            "email": "user.user@email.com",
+            "full_name": "User USER",
+            "id": 0,
+            "organization": {"id": 0, "name": "ORG", "path_names": ["ORG"]},
         },
-        'uuid': '7447bbea-f9a8-44a4-8dca-fcdaa153d13b'
+        "uuid": "7447bbea-f9a8-44a4-8dca-fcdaa153d13b",
     }
     responses.add(responses.POST, url, json=resp, status=200)
 
-    threat_types = [{'threat_type': ThreatType('ddos'), 'score': 0}]
-    output = datalake.Threats.add_threat(atom=ip, threat_types=threat_types, override_type=OverrideType.TEMPORARY)
+    threat_types = [{"threat_type": ThreatType("ddos"), "score": 0}]
+    output = datalake.Threats.add_threat(
+        atom=ip, threat_types=threat_types, override_type=OverrideType.TEMPORARY
+    )
     assert output == resp
 
 
 @responses.activate
 def test_add_threats_bulk(datalake: Datalake):
-    url = 'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-manual-threats/'
-    atom_list = ['1.1.1.1', '1.1.1.2']
+    url = (
+        "https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-manual-threats/"
+    )
+    atom_list = ["1.1.1.1", "1.1.1.2"]
     task_uid = "5b127e18-b471-44ae-ae34-e616d74d63a9"
-    hashkeys = ['498838952afbf8a47b68563206fbfca4', '4c7711a6bdbcb8626e3f07d4aaa317aa']
-    post_resp = {
-        "hashkeys": hashkeys,
-        "task_uuid": task_uid
-    }
+    hashkeys = ["498838952afbf8a47b68563206fbfca4", "4c7711a6bdbcb8626e3f07d4aaa317aa"]
+    post_resp = {"hashkeys": hashkeys, "task_uuid": task_uid}
     responses.add(responses.POST, url, json=post_resp, status=202)
 
-    url = f'https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-manual-threats/task/{task_uid}/'
+    url = f"https://datalake.cert.orangecyberdefense.com/api/v2/mrti/bulk-manual-threats/task/{task_uid}/"
     get_resp = {  # Returns the DONE response right away
         "atom_type": "ip",
         "atom_values": atom_list,
@@ -464,21 +552,23 @@ def test_add_threats_bulk(datalake: Datalake):
             "email": "johnny.english@orange.com",
             "full_name": "johnny english",
             "id": 287,
-            "organization": {"id": 4, "name": "OCD", "path_names": ["OCD"]}
+            "organization": {"id": 4, "name": "OCD", "path_names": ["OCD"]},
         },
-        "uuid": task_uid
+        "uuid": task_uid,
     }
     responses.add(responses.GET, url, json=get_resp, status=200)
 
-    threat_types = [{'threat_type': ThreatType('ddos'), 'score': 0}]
-    assert datalake.Threats.add_threats(atom_list, AtomType.IP, threat_types, OverrideType.TEMPORARY) == [
+    threat_types = [{"threat_type": ThreatType("ddos"), "score": 0}]
+    assert datalake.Threats.add_threats(
+        atom_list, AtomType.IP, threat_types, OverrideType.TEMPORARY
+    ) == [
         {
-            'success': [
+            "success": [
                 {
-                    'created_hashkeys': hashkeys,
-                    'created_atom_values': ['1.1.1.1', '1.1.1.2']
+                    "created_hashkeys": hashkeys,
+                    "created_atom_values": ["1.1.1.1", "1.1.1.2"],
                 }
             ],
-            'failed': []
+            "failed": [],
         }
     ]
diff --git a/tests/scripts/test_add_new_threats.py b/tests/scripts/test_add_new_threats.py
index 9af46bb..3205cf9 100644
--- a/tests/scripts/test_add_new_threats.py
+++ b/tests/scripts/test_add_new_threats.py
@@ -1,19 +1,42 @@
-from datalake_scripts.scripts.add_threats import defang_threats, _build_threat_from_atom_type
+from datalake_scripts.scripts.add_threats import (
+    defang_threats,
+    _build_threat_from_atom_type,
+)
 from datalake import AtomType
 
+
 def test_defanging():
-    threats = ['https://threta.com', 'https://another.com', 'another.com', 'domain[.]com', 'www(.)domain(.)com',
-               'https://andanother.com', 'http://FE80:0000:0000:0000:0202:B3FF:FE1E:8',
-               'ftp://34.237.176.218/pub/daemon.php', 'sftp://34.237.176.218/pub/daemon.php',
-               'ftps://34.237.176.218/pub/daemon.php', 'dfsdfsdf', 'http://a la mer', 'a la mer', 'meràboire',
-               'çoulographe', '0.12.5.6']
-    defanged = defang_threats(threats, 'url')
+    threats = [
+        "https://threta.com",
+        "https://another.com",
+        "another.com",
+        "domain[.]com",
+        "www(.)domain(.)com",
+        "https://andanother.com",
+        "http://FE80:0000:0000:0000:0202:B3FF:FE1E:8",
+        "ftp://34.237.176.218/pub/daemon.php",
+        "sftp://34.237.176.218/pub/daemon.php",
+        "ftps://34.237.176.218/pub/daemon.php",
+        "dfsdfsdf",
+        "http://a la mer",
+        "a la mer",
+        "meràboire",
+        "çoulographe",
+        "0.12.5.6",
+    ]
+    defanged = defang_threats(threats, "url")
     planned_defanged = [
-        'https://threta.com', 'https://another.com', 'http://another.com', 'http://domain.com',
-        'http://www.domain.com', 'https://andanother.com',
-        'http://FE80:0000:0000:0000:0202:B3FF:FE1E:8',
-        'http://34.237.176.218/pub/daemon.php', 'https://34.237.176.218/pub/daemon.php',
-        'https://34.237.176.218/pub/daemon.php', 'http://0.12.5.6'
+        "https://threta.com",
+        "https://another.com",
+        "http://another.com",
+        "http://domain.com",
+        "http://www.domain.com",
+        "https://andanother.com",
+        "http://FE80:0000:0000:0000:0202:B3FF:FE1E:8",
+        "http://34.237.176.218/pub/daemon.php",
+        "https://34.237.176.218/pub/daemon.php",
+        "https://34.237.176.218/pub/daemon.php",
+        "http://0.12.5.6",
     ]
 
     assert defanged == planned_defanged
@@ -21,13 +44,13 @@ def test_defanging():
 
 def test_build_threat_from_atom_type():
     expected_file_atom = {
-        'file_content': {
-            'hashes': {
-                'md5': 'd26351ba789fba3385d2382aa9d24908'
-            },
-            'external_analysis_link': ['https://someurl.co']
+        "file_content": {
+            "hashes": {"md5": "d26351ba789fba3385d2382aa9d24908"},
+            "external_analysis_link": ["https://someurl.co"],
         }
     }
-    output = _build_threat_from_atom_type('d26351ba789fba3385d2382aa9d24908', AtomType.FILE, ['https://someurl.co']).generate_atom_json()
+    output = _build_threat_from_atom_type(
+        "d26351ba789fba3385d2382aa9d24908", AtomType.FILE, ["https://someurl.co"]
+    ).generate_atom_json()
 
-    assert output == expected_file_atom
\ No newline at end of file
+    assert output == expected_file_atom
diff --git a/tests/scripts/test_base_engine.py b/tests/scripts/test_base_engine.py
index 4bcdae4..997d057 100644
--- a/tests/scripts/test_base_engine.py
+++ b/tests/scripts/test_base_engine.py
@@ -2,16 +2,18 @@
 from datalake_scripts.common.base_engine import BaseEngine, InvalidHeader
 
 
-@pytest.mark.parametrize('value, expected', [
-    ('json', 'application/json'),
-    ('csv', 'text/csv')
-])
+@pytest.mark.parametrize(
+    "value, expected", [("json", "application/json"), ("csv", "text/csv")]
+)
 def test_output_type2header(value, expected):
     assert BaseEngine.output_type2header(value) == expected
 
 
 def test_output_type2header_parser_error():
-    bad_header = 'foo'
+    bad_header = "foo"
     with pytest.raises(InvalidHeader) as e:
         BaseEngine.output_type2header(bad_header)
-    assert str(e.value) == f'{bad_header} is not a valid. Use some of {BaseEngine.ACCEPTED_HEADERS.keys()}'
+    assert (
+        str(e.value)
+        == f"{bad_header} is not a valid. Use some of {BaseEngine.ACCEPTED_HEADERS.keys()}"
+    )
diff --git a/tests/scripts/test_csv_builder.py b/tests/scripts/test_csv_builder.py
index 8af6af4..a771cca 100644
--- a/tests/scripts/test_csv_builder.py
+++ b/tests/scripts/test_csv_builder.py
@@ -5,121 +5,179 @@
 
 @pytest.fixture
 def csv_header():
-    return 'hashkey,atom_type,atom_value_best_matching,atom_found'
+    return "hashkey,atom_type,atom_value_best_matching,atom_found"
 
 
 @pytest.fixture
 def csv_header_extended(csv_header):
-    return f'{csv_header},events_number,first_seen,last_updated,' \
-           'threat_types,ddos.score.risk,fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,' \
-           'phishing.score.risk,scam.score.risk,spam.score.risk,sources,tags,href_graph,href_history,href_threat,' \
-           'href_threat_webGUI'
+    return (
+        f"{csv_header},events_number,first_seen,last_updated,"
+        "threat_types,ddos.score.risk,fraud.score.risk,hack.score.risk,leak.score.risk,malware.score.risk,"
+        "phishing.score.risk,scam.score.risk,spam.score.risk,sources,tags,href_graph,href_history,href_threat,"
+        "href_threat_webGUI"
+    )
 
 
 def test_single_threat_no_details(csv_header):
-    threat_type = 'as'
-    api_response = {'48587': {'hashkey': '1b19ef78b0df6cd55fe894307d7fda8e', 'threat_found': True}}
-    expected_response = [
-        csv_header,
-        '1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True'
-    ]
+    threat_type = "as"
+    api_response = {
+        "48587": {"hashkey": "1b19ef78b0df6cd55fe894307d7fda8e", "threat_found": True}
+    }
+    expected_response = [csv_header, "1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True"]
 
-    assert CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=False) == expected_response
+    assert (
+        CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=False)
+        == expected_response
+    )
 
 
 def test_single_threat_with_details(csv_header_extended):
-    threat_type = 'as'
+    threat_type = "as"
     api_response = {
-        '48587': {'sources': [{'count': 1,
-                               'source_id': 'badips_apache',
-                               'first_seen': '2020-10-24T05:30:38Z',
-                               'last_updated': '2020-10-24T05:30:38Z'}, {
-                                  'count': 3,
-                                  'source_id': 'hybrid_analysis_public',
-                                  'first_seen': '2020-10-18T02:01:17Z',
-                                  'last_updated': '2020-10-18T02:10:24Z'}],
-                  'system_last_updated': '2020-10-24T05:30:38Z',
-                  'href_graph': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/',
-                  'href_threat': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/',
-                  'atom_type': 'as', 'system_first_seen': '2020-04-09T11:59:30Z', 'content': {
-                'as_content': {'allocation_date': '2008-12-12', 'owner': 'NET-0X2A-AS Datacentre _0x2a_, UA',
-                               'registry': 'ripencc', 'country': 'UA', 'atom_value': 48587, 'asn': 48587}
+        "48587": {
+            "sources": [
+                {
+                    "count": 1,
+                    "source_id": "badips_apache",
+                    "first_seen": "2020-10-24T05:30:38Z",
+                    "last_updated": "2020-10-24T05:30:38Z",
+                },
+                {
+                    "count": 3,
+                    "source_id": "hybrid_analysis_public",
+                    "first_seen": "2020-10-18T02:01:17Z",
+                    "last_updated": "2020-10-18T02:10:24Z",
+                },
+            ],
+            "system_last_updated": "2020-10-24T05:30:38Z",
+            "href_graph": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/",
+            "href_threat": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/",
+            "atom_type": "as",
+            "system_first_seen": "2020-04-09T11:59:30Z",
+            "content": {
+                "as_content": {
+                    "allocation_date": "2008-12-12",
+                    "owner": "NET-0X2A-AS Datacentre _0x2a_, UA",
+                    "registry": "ripencc",
+                    "country": "UA",
+                    "atom_value": 48587,
+                    "asn": 48587,
+                }
             },
-                  'tags': [],
-                  'href_history': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/',
-                  'metadata': {},
-                  'hashkey': '1b19ef78b0df6cd55fe894307d7fda8e',
-                  'scores': [{'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'ddos'},
-                             {'score': {'reliability': 8, 'risk': 0}, 'threat_type': 'phishing'},
-                             {'score': {'reliability': 8, 'risk': 0}, 'threat_type': 'spam'},
-                             {'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'scan'},
-                             {'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'hack'},
-                             {'score': {'reliability': 10, 'risk': 1}, 'threat_type': 'malware'}],
-                  'first_seen': '2013-03-13T14:37:35Z', 'last_updated': '2020-10-24T05:30:38Z'}
+            "tags": [],
+            "href_history": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/",
+            "metadata": {},
+            "hashkey": "1b19ef78b0df6cd55fe894307d7fda8e",
+            "scores": [
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "ddos"},
+                {"score": {"reliability": 8, "risk": 0}, "threat_type": "phishing"},
+                {"score": {"reliability": 8, "risk": 0}, "threat_type": "spam"},
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "scan"},
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "hack"},
+                {"score": {"reliability": 10, "risk": 1}, "threat_type": "malware"},
+            ],
+            "first_seen": "2013-03-13T14:37:35Z",
+            "last_updated": "2020-10-24T05:30:38Z",
+        }
     }
 
     expected_response = [
         csv_header_extended,
-        '1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True,4,2013-03-13T14:37:35Z,2020-10-24T05:30:38Z,None,0,None,'
+        "1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True,4,2013-03-13T14:37:35Z,2020-10-24T05:30:38Z,None,0,None,"
         '0,None,1,0,None,0,"badips_apache,hybrid_analysis_public",None,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/']
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/",
+    ]
 
-    assert CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=True) == expected_response
+    assert (
+        CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=True)
+        == expected_response
+    )
 
 
 def test_multiple_threats_no_details(csv_header):
-    threat_type = 'as'
-    api_response = {'48587': {'hashkey': '1b19ef78b0df6cd55fe894307d7fda8e', 'threat_found': True},
-                    '48588': {'hashkey': '49b565d2fddc6861e6c5c236f065504d', 'threat_found': False}}
+    threat_type = "as"
+    api_response = {
+        "48587": {"hashkey": "1b19ef78b0df6cd55fe894307d7fda8e", "threat_found": True},
+        "48588": {"hashkey": "49b565d2fddc6861e6c5c236f065504d", "threat_found": False},
+    }
     expected_response = [
         csv_header,
-        '1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True',
-        '49b565d2fddc6861e6c5c236f065504d,as,48588,False'
+        "1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True",
+        "49b565d2fddc6861e6c5c236f065504d,as,48588,False",
     ]
 
-    assert CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=False) == expected_response
+    assert (
+        CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=False)
+        == expected_response
+    )
 
 
 def test_multiple_threats_with_details(csv_header_extended):
-    threat_type = 'as'
+    threat_type = "as"
     api_response = {
-        '48587': {'sources': [{'count': 1, 'source_id': 'badips_apache',
-                               'first_seen': '2020-10-24T05:30:38Z',
-                               'last_updated': '2020-10-24T05:30:38Z'}, {
-                                  'count': 3, 'source_id': 'hybrid_analysis_public',
-                                  'first_seen': '2020-10-18T02:01:17Z',
-                                  'last_updated': '2020-10-18T02:10:24Z'}],
-                  'system_last_updated': '2020-10-24T05:30:38Z',
-                  'href_graph': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/',
-                  'href_threat': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/',
-                  'atom_type': 'as', 'system_first_seen': '2020-04-09T11:59:30Z', 'content': {
-                'as_content': {'allocation_date': '2008-12-12', 'owner': 'NET-0X2A-AS Datacentre _0x2a_, UA',
-                               'registry': 'ripencc', 'country': 'UA', 'atom_value': 48587, 'asn': 48587}}, 'tags': [],
-                  'href_history': 'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/',
-                  'metadata': {},
-                  'hashkey': '1b19ef78b0df6cd55fe894307d7fda8e',
-                  'scores': [{'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'ddos'},
-                             {'score': {'reliability': 8, 'risk': 0}, 'threat_type': 'phishing'},
-                             {'score': {'reliability': 8, 'risk': 0}, 'threat_type': 'spam'},
-                             {'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'scan'},
-                             {'score': {'reliability': 1, 'risk': 0}, 'threat_type': 'hack'},
-                             {'score': {'reliability': 10, 'risk': 1}, 'threat_type': 'malware'}],
-                  'first_seen': '2013-03-13T14:37:35Z', 'last_updated': '2020-10-24T05:30:38Z'},
-        '48588': {'hashkey': '49b565d2fddc6861e6c5c236f065504d', 'threat_found': False}
+        "48587": {
+            "sources": [
+                {
+                    "count": 1,
+                    "source_id": "badips_apache",
+                    "first_seen": "2020-10-24T05:30:38Z",
+                    "last_updated": "2020-10-24T05:30:38Z",
+                },
+                {
+                    "count": 3,
+                    "source_id": "hybrid_analysis_public",
+                    "first_seen": "2020-10-18T02:01:17Z",
+                    "last_updated": "2020-10-18T02:10:24Z",
+                },
+            ],
+            "system_last_updated": "2020-10-24T05:30:38Z",
+            "href_graph": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/",
+            "href_threat": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/",
+            "atom_type": "as",
+            "system_first_seen": "2020-04-09T11:59:30Z",
+            "content": {
+                "as_content": {
+                    "allocation_date": "2008-12-12",
+                    "owner": "NET-0X2A-AS Datacentre _0x2a_, UA",
+                    "registry": "ripencc",
+                    "country": "UA",
+                    "atom_value": 48587,
+                    "asn": 48587,
+                }
+            },
+            "tags": [],
+            "href_history": "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/",
+            "metadata": {},
+            "hashkey": "1b19ef78b0df6cd55fe894307d7fda8e",
+            "scores": [
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "ddos"},
+                {"score": {"reliability": 8, "risk": 0}, "threat_type": "phishing"},
+                {"score": {"reliability": 8, "risk": 0}, "threat_type": "spam"},
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "scan"},
+                {"score": {"reliability": 1, "risk": 0}, "threat_type": "hack"},
+                {"score": {"reliability": 10, "risk": 1}, "threat_type": "malware"},
+            ],
+            "first_seen": "2013-03-13T14:37:35Z",
+            "last_updated": "2020-10-24T05:30:38Z",
+        },
+        "48588": {"hashkey": "49b565d2fddc6861e6c5c236f065504d", "threat_found": False},
     }
     expected_response = [
         csv_header_extended,
-        '1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True,4,2013-03-13T14:37:35Z,2020-10-24T05:30:38Z,None,0,None,0,None,'
+        "1b19ef78b0df6cd55fe894307d7fda8e,as,48587,True,4,2013-03-13T14:37:35Z,2020-10-24T05:30:38Z,None,0,None,0,None,"
         '1,0,None,0,"badips_apache,hybrid_analysis_public",None,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/,'
-        'https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/',
-        '49b565d2fddc6861e6c5c236f065504d,as,48588,False,None,None,None,None,None,None,None,None,None,None,None,None,'
-        'None,None,None,None,None,None'
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/graph/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats-history/1b19ef78b0df6cd55fe894307d7fda8e/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/,"
+        "https://ti.extranet.mrti-center.com/api/v2/mrti/threats/1b19ef78b0df6cd55fe894307d7fda8e/",
+        "49b565d2fddc6861e6c5c236f065504d,as,48588,False,None,None,None,None,None,None,None,None,None,None,None,None,"
+        "None,None,None,None,None,None",
     ]
 
-    assert CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=True) == expected_response
+    assert (
+        CsvBuilder.create_look_up_csv(api_response, threat_type, has_details=True)
+        == expected_response
+    )
diff --git a/tests/scripts/test_endpoint_config.py b/tests/scripts/test_endpoint_config.py
index 22e0844..e37f01d 100644
--- a/tests/scripts/test_endpoint_config.py
+++ b/tests/scripts/test_endpoint_config.py
@@ -6,16 +6,21 @@
 
 
 def test_auth(token_manager):
-    assert token_manager.url_token == 'https://datalake.com/api/v42/auth/token/'
-    assert token_manager.access_token == 'Token access_token'
-    assert token_manager.refresh_token == 'Token refresh_token'
+    assert token_manager.url_token == "https://datalake.com/api/v42/auth/token/"
+    assert token_manager.access_token == "Token access_token"
+    assert token_manager.refresh_token == "Token refresh_token"
 
 
-@pytest.mark.parametrize("engine,expected_url", [
-    (ThreatsSearch, 'https://datalake.com/api/v42/mrti/threats/'),
-    (BulkSearch, 'https://datalake.com/api/v42/mrti/bulk-search/'),
-    (CommentsPost, 'https://datalake.com/api/v42/mrti/threats/{hashkey}/comments/'),
-])
+@pytest.mark.parametrize(
+    "engine,expected_url",
+    [
+        (ThreatsSearch, "https://datalake.com/api/v42/mrti/threats/"),
+        (BulkSearch, "https://datalake.com/api/v42/mrti/bulk-search/"),
+        (CommentsPost, "https://datalake.com/api/v42/mrti/threats/{hashkey}/comments/"),
+    ],
+)
 def test_engine(token_manager, engine, expected_url):
-    engine = engine(TestData.TEST_CONFIG, environment=TestData.TEST_ENV, token_manager=token_manager)
+    engine = engine(
+        TestData.TEST_CONFIG, environment=TestData.TEST_ENV, token_manager=token_manager
+    )
     assert engine.url == expected_url
diff --git a/tests/scripts/test_utils.py b/tests/scripts/test_utils.py
index f06140b..f1401c2 100644
--- a/tests/scripts/test_utils.py
+++ b/tests/scripts/test_utils.py
@@ -15,24 +15,23 @@ def test_split_list():
 
 def test_join_dicts():
     d1 = {
-        'k1': ['k1', 'k2', 'k3'],
-        'a1': ['a1', 'a2', 'a3'],
-        'b1': ['b1', 'b2'],
+        "k1": ["k1", "k2", "k3"],
+        "a1": ["a1", "a2", "a3"],
+        "b1": ["b1", "b2"],
     }
     d2 = {
-        'k1': ['k4', 'k5', 'k6'],
-        'a1': ['a4', 'a5', 'a6'],
+        "k1": ["k4", "k5", "k6"],
+        "a1": ["a4", "a5", "a6"],
     }
     d3 = {
-        'k1': ['k7', 'k8'],
-        'a1': ['a7', 'a8'],
-        'p1': ['p1', 'p2'],
+        "k1": ["k7", "k8"],
+        "a1": ["a7", "a8"],
+        "p1": ["p1", "p2"],
     }
 
     assert join_dicts(d1, d2, d3) == {
-        'k1': ['k1', 'k2', 'k3', 'k4', 'k5', 'k6', 'k7', 'k8'],
-        'a1': ['a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7', 'a8'],
-        'b1': ['b1', 'b2'],
-        'p1': ['p1', 'p2']
+        "k1": ["k1", "k2", "k3", "k4", "k5", "k6", "k7", "k8"],
+        "a1": ["a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8"],
+        "b1": ["b1", "b2"],
+        "p1": ["p1", "p2"],
     }
-