def get_file_encoding(file_path, encoding):
def get_extension(file_path):
def make_temp_file_path_from_url(temp_dir_path, url): class SourceType:
    TEXT = "text"
    FILE = "file"
    URL = "url"
    OBJECT = "object" class TableNameTemplate:
    __FORMAT = "%({:s})s"
    DEFAULT = __FORMAT.format("default")
    FILENAME = __FORMAT.format("filename")
    FORMAT_NAME = __FORMAT.format("format_name")
    FORMAT_ID = __FORMAT.format("format_id")
    GLOBAL_ID = __FORMAT.format("global_id")
    KEY = __FORMAT.format("key")
    TITLE = __FORMAT.format("title")
    SHEET = __FORMAT.format("sheet") class FileSourceLogger(BaseLogger):
    def _get_load_message(self):
        message = "loading {:s}: format={:s}, path={}".format(
            self._loader.source_type, self._loader.format_name, self._loader.source
        )

        try:
            message += f", encoding={self._loader.encoding}"
        except AttributeError:
            pass

        if self._loader.type_hints:
            message += f", type-hints=({typehints_to_str(self._loader.type_hints)})"

        return message class TextSourceLogger(BaseLogger):
    def _get_load_message(self):
        message = "loading {:s}: format={:s}".format(
            self._loader.source_type, self._loader.format_name
        )

        try:
            message += f", len={len(self._loader.source)}"
        except TypeError:
            pass

        try:
            message += f", encoding={self._loader.encoding}"
        except AttributeError:
            pass

        if self._loader.type_hints:
            message += f", type-hints=({typehints_to_str(self._loader.type_hints)})"

        return message class FileValidator(BaseValidator):
    """
    Validator class for file data source.
    """

    @property
    def source_type(self):
        return SourceType.FILE

    def validate(self):
        try:
            pv.validate_filepath(self.source, platform="auto")
        except pv.ValidationError as e:
            raise InvalidFilePathError(e)

        if os.path.isfile(self.source) or is_fifo(self.source):
            return

        raise OSError("file not found") class TextValidator(BaseValidator):
    """
    Validator class for text object data source.
    """

    @property
    def source_type(self):
        return SourceType.TEXT

    def validate(self):
        if typepy.is_null_string(self.source):
            raise DataError("data source is empty") class ValidationError(Exception):
    """
    Exception raised when data is not properly formatted.
    """ class AbstractTableReader(TableLoaderInterface, metaclass=abc.ABCMeta):
    """
    The abstract class of table data file loader.

    .. py:attribute:: table_name

        Table name string.

    .. py:attribute:: source

        Table data source to load.
    """

    __table_count_lock = threading.Lock()
    __global_table_count = 0
    __format_table_count = {}

    @property
    def source_type(self):
        return self._validator.source_type

    @property
    def quoting_flags(self):
        return self.__quoting_flags

    @property
    def dp_extractor(self):
        return self.__dp_extractor

    def __init__(self, source, quoting_flags, type_hints, type_hint_rules=None):
        self.table_name = tnt.DEFAULT
        self.source = source
        self.__quoting_flags = quoting_flags
        self.type_hints = type_hints
        self.type_hint_rules = type_hint_rules
        self._validator = None
        self._logger = None

        self.__dp_extractor = DataPropertyExtractor()
        self.__dp_extractor.quoting_flags = self.quoting_flags
        self.__dp_extractor.update_strict_level_map({typepy.Typecode.BOOL: 1})

    def get_format_key(self):
        return f"{self.format_name:s}{self.__get_format_table_count():d}"

    def make_table_name(self):
        return self._make_table_name()

    def inc_table_count(self):
        with self.__table_count_lock:
            self.__global_table_count += 1
            self.__format_table_count[self.format_name] = self.__get_format_table_count() + 1

    @abc.abstractmethod
    def _get_default_table_name_template(self):  # pragma: no cover
        pass

    def _validate(self):
        self._validate_table_name()
        self._validate_source()

    def _validate_table_name(self):
        try:
            if typepy.is_null_string(self.table_name):
                raise ValueError("table name is empty")
        except (TypeError, AttributeError):
            raise TypeError("table_name must be a string")

    def _validate_source(self):
        self._validator.validate()

    def __get_format_table_count(self):
        return self.__format_table_count.get(self.format_name, 0)

    def _get_filename_tablename_mapping(self):
        filename = ""
        if all([self.source_type == SourceType.FILE, typepy.is_not_null_string(self.source)]):
            filename = path.Path(self.source).stem

        return (tnt.FILENAME, filename)

    def _get_basic_tablename_keyvalue_mapping(self):
        from collections import OrderedDict

        return OrderedDict(
            [
                (tnt.DEFAULT, self._get_default_table_name_template()),
                (tnt.FORMAT_NAME, self.format_name),
                (tnt.FORMAT_ID, str(self.__get_format_table_count())),
                (tnt.GLOBAL_ID, str(self.__global_table_count)),
                self._get_filename_tablename_mapping(),
            ]
        )

    def _expand_table_name_format(self, table_name_kv_mapping):
        self._validate_table_name()

        table_name = self.table_name
        for template, value in table_name_kv_mapping.items():
            table_name = table_name.replace(template, value)

        return self._sanitize_table_name(table_name)

    def _make_table_name(self):
        self._validate_table_name()

        return self._expand_table_name_format(self._get_basic_tablename_keyvalue_mapping())

    @staticmethod
    def _sanitize_table_name(table_name):
        if typepy.is_null_string(table_name):
            raise InvalidTableNameError("table name is empty after the template replacement")

        return table_name.strip("_")

    @classmethod
    def clear_table_count(cls):
        with cls.__table_count_lock:
            cls.__global_table_count = 0
            cls.__format_table_count = {} class JsonLinesTableFormatter(TableFormatter):
    def to_table_data(self):
        converter = FlatJsonTableConverter(self._source_data)
        converter.accept(self._loader)

        return converter.to_table_data()
import abc
from collections import OrderedDict
from .._common import get_file_encoding, json
from .._constant import SourceType
from .._constant import TableNameTemplate as tnt
from .._logger import FileSourceLogger, TextSourceLogger
from .._validator import FileValidator, TextValidator
from ..error import ValidationError
from ..interface import AbstractTableReader
from .formatter import JsonLinesTableFormatter


class JsonLinesTableLoader(AbstractTableReader, metaclass=abc.ABCMeta):
    """
    An abstract class of JSON table loaders.
    """

    @property
    def format_name(self):
        return "json_lines"

    @abc.abstractmethod
    def load_dict(self):  # pragma: no cover
        pass


class JsonLinesTableFileLoader(JsonLinesTableLoader):
    """
    A file loader class to extract tabular data from Line-delimited JSON files.

    :param str file_path: Path to the loading Line-delimited JSON file.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(filename)s_%(key)s``.
    """

    def __init__(self, file_path=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(file_path, quoting_flags, type_hints, type_hint_rules)

        self.encoding = None

        self._validator = FileValidator(file_path)
        self._logger = FileSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a Line-delimited JSON file.
        |load_source_desc_file|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

        :rtype: |TableData| iterator
        :raises pytablereader.DataError:
            If the data is invalid Line-delimited JSON.
        :raises pytablereader.error.ValidationError:
            If the data is not acceptable Line-delimited JSON format.
        """

        formatter = JsonLinesTableFormatter(self.load_dict())
        formatter.accept(self)

        return formatter.to_table_data()

    def load_dict(self):
        self._validate()
        self._logger.logging_load()
        self.encoding = get_file_encoding(self.source, self.encoding)

        buffer = []
        with open(self.source, encoding=self.encoding) as fp:
            for line_idx, line in enumerate(fp):
                line = line.strip()
                if not line:
                    continue

                try:
                    buffer.append(json.loads(line, object_pairs_hook=OrderedDict))
                except json.JSONDecodeError as e:
                    raise ValidationError(
                        "line {line_idx}: {msg}: {value}".format(
                            line_idx=line_idx + 1, msg=e, value=line
                        )
                    )

        return buffer

    def _get_default_table_name_template(self):
        return f"{tnt.FILENAME:s}_{tnt.KEY:s}"


class JsonLinesTableTextLoader(JsonLinesTableLoader):
    """
    A text loader class to extract tabular data from Line-delimited JSON text data.

    :param str text: Line-delimited JSON text to load.

    .. py:attribute:: table_name

        Table name string. Defaults to ``%(key)s``.
    """

    @property
    def source_type(self):
        return SourceType.TEXT

    def __init__(self, text=None, quoting_flags=None, type_hints=None, type_hint_rules=None):
        super().__init__(text, quoting_flags, type_hints)

        self._validator = TextValidator(text)
        self._logger = TextSourceLogger(self)

    def load(self):
        """
        Extract tabular data as |TableData| instances from a Line-delimited JSON text object.
        |load_source_desc_text|

        :return:
            Loaded table data iterator.
            |load_table_name_desc|

        :rtype: |TableData| iterator

        .. seealso::

            :py:meth:`.JsonLinesTableFileLoader.load()`
        """


        formatter = JsonLinesTableFormatter(self.load_dict())