<commit_before>"""ThreatConnect API V3 Base Model."""
# standard library
import datetime
import hashlib
import json
import logging
from abc import ABC
from json import JSONEncoder
from typing import Any, Optional

# third-party
from pydantic import BaseModel, PrivateAttr

# get tcex logger
logger = logging.getLogger('tcex')


class CustomJSONEncoder(JSONEncoder):
    """Format object in JSON data."""

    def default(self, o: Any) -> str:
        """Format object"""
        if isinstance(o, (datetime.date, datetime.datetime)):
            return o.isoformat()
        return o


class V3ModelABC(BaseModel, ABC):
    """V3 Base Model"""

    _associated_type = PrivateAttr(False)
    _cm_type = PrivateAttr(False)
    _dict_hash: str = PrivateAttr()
    _log = logger
    _shared_type = PrivateAttr(False)
    _staged = PrivateAttr(False)
    id: int = None

    def __init__(self, **kwargs):
        """Initialize class properties."""
        super().__init__(**kwargs)

        # when "id" field is present it indicates that the data was returned from the
        # API, otherwise the assumption is that the developer staged the data during
        # instantiation of the object.
        if kwargs and hasattr(self, 'id') and self.id is None:  # pylint: disable=no-member
            self._staged = True

        # store initial dict hash of model
        self._dict_hash = self.gen_model_hash(self.json(sort_keys=True))

    def _calculate_field_inclusion(
        self, field: str, method: str, mode: str, nested: bool, property_: dict, value: Any
    ) -> str:
        """Return True if the field is calculated to be included."""

        # INDICATOR ID RULE: If an indicator has a ID set, then the indicator fields
        #     cannot be provided (updateable is false) or the API request will fail.
        if self.id is not None and field in [
            'address',
            'file',
            'hostName',
            'ip',
            'md5',
            'sha1',
            'sha256',
            'text',
            'url',
            'value1',
            'value2',
            'value3',
        ]:
            return False

        # MODE DELETE RULE: The "id" should be the only field included when delete mode
        #     is enabled.
        if mode == 'delete' and nested is True and field not in ['id', 'name']:
            return False

        # ID RULE: The "id" should not be included for ANY HTTP method on parent object,
        #     but for nested objects the "id" field should be included when available.
        #     PLAT-4074 - updating nested object using "replace"
        if field == 'id' and nested is True and value:
            return True

        # NESTED RULE: For nested objects the body should use the valid POST fields
        #    instead of the PUT fields if id is not available. This handles including
        #    artifact type, attributes and other fields that are needed on the nested object.<commit_msg>Develop (#149)<commit_after>"""ThreatConnect API V3 Base Model."""
# standard library
import datetime
import hashlib
import json
import logging
from abc import ABC
from json import JSONEncoder
from typing import Any, Optional

# third-party
from pydantic import BaseModel, PrivateAttr

# get tcex logger

logger = logging.getLogger('tcex')


class CustomJSONEncoder(JSONEncoder):
    """Format object in JSON data."""

    def default(self, o: Any) -> str:
        """Format object"""
        if isinstance(o, (datetime.date, datetime.datetime)):
            return o.isoformat()
        return o


class V3ModelABC(BaseModel, ABC):
    """V3 Base Model"""

    _associated_type = PrivateAttr(False)
    _cm_type = PrivateAttr(False)
    _dict_hash: str = PrivateAttr()
    _log = logger
    _shared_type = PrivateAttr(False)
    _staged = PrivateAttr(False)
    id: int = None

    def __init__(self, **kwargs):
        """Initialize class properties."""
        super().__init__(**kwargs)

        # when "id" field is present it indicates that the data was returned from the
        # API, otherwise the assumption is that the developer staged the data during
        # instantiation of the object.
        if kwargs and hasattr(self, 'id') and self.id is None:  # pylint: disable=no-member
            self._staged = True

        # store initial dict hash of model
        self._dict_hash = self.gen_model_hash(self.json(sort_keys=True))

    def _calculate_field_inclusion(
        self, field: str, method: str, mode: str, nested: bool, property_: dict, value: Any
    ) -> str:
        """Return True if the field is calculated to be included."""

        # INDICATOR ID RULE: If an indicator has a ID set, then the indicator fields
        #     cannot be provided (updateable is false) or the API request will fail.
        #     Since Note model CAN update the `text` field we need to specifically state
        #     that this test is for the `Indicator Model` only.
        if (
            self.__config__.title == 'Indicator Model'
            and self.id is not None
            and field
            in [
                'address',
                'file',
                'hostName',
                'ip',
                'md5',
                'sha1',
                'sha256',
                'text',
                'url',
                'value1',
                'value2',
                'value3',
            ]
        ):
            return False

        # MODE DELETE RULE: The "id" should be the only field included when delete mode
        #     is enabled.
        if mode == 'delete' and nested is True and field not in ['id', 'name']:
            return False

        # ID RULE: The "id" should not be included for ANY HTTP method on parent object,
        #     but for nested objects the "id" field should be included when available.
        #     PLAT-4074 - updating nested object using "replace"
        if field == 'id' and nested is True and value:
            return True

        # NESTED RULE: For nested objects the body should use the valid POST fields
        #    instead of the PUT fields if id is not available. This handles including
        #    artifact type, attributes and other fields that are needed on the nested object.
{'fraction_non_alphanumeric': 0.08336005129849311, 'fraction_numerical': 0.00529015710163514, 'mean_word_length': 2.1621895590471363, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1339381', 'n_tokens_mistral': 1680, 'n_tokens_neox': 1616, 'n_words': 686}