try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass
import json
import os
from typing import Optional, Dict, List
import requests
from validator_collection import validators, checkers
from highcharts_core import __version__ as highcharts_version
from highcharts_core import errors, constants
from highcharts_core.decorators import class_sensitive
from highcharts_core.metaclasses import HighchartsMeta
from highcharts_core.utility_classes.javascript_functions import CallbackFunction
from highcharts_core.options import HighchartsOptions
from highcharts_core.options.data import Data
[docs]class ExportServer(HighchartsMeta):
    """Class that provides methods for interacting with the Highcharts
    `Export Server <https://github.com/highcharts/node-export-server>`_.
    .. note::
      By default, the :class:`ExportServer` class operates using the Highcharts-provided
      export server. If you wish to use your own (or a custom) export server, you can
      configure the class using either the :meth:`url <ExportServer.url>`,
      :meth:`port <ExportServer.port>`, and
      :meth:`path <ExportServer.path>` properties explicitly or by setting
      the ``HIGHCHARTS_EXPORT_SERVER_DOMAIN``, ``HIGHCHARTS_EXPORT_SERVER_PORT``, or
      ``HIGHCHARTS_EXPORT_SERVER_PATH`` environment variables.
    """
    def __init__(self, **kwargs):
        self._url = None
        self._port = None
        self._path = None
        self._options = None
        self._format_ = None
        self._scale = None
        self._width = None
        self._height = None
        self._callback = None
        self._constructor = None
        self._use_base64 = None
        self._no_download = None
        self._async_rendering = None
        self._global_options = None
        self._data_options = None
        self._custom_code = None
        self._resources = None
        self._files = None
        self._css = None
        self._js = None
        self._referer = None
        self._user_agent = None
        self.protocol = kwargs.get(
            "protocol", os.getenv("HIGHCHARTS_EXPORT_SERVER_PROTOCOL", "https")
        )
        self.domain = kwargs.get(
            "domain",
            os.getenv("HIGHCHARTS_EXPORT_SERVER_DOMAIN", "export.highcharts.com"),
        )
        self.port = kwargs.get("port", os.getenv("HIGHCHARTS_EXPORT_SERVER_PORT", ""))
        self.path = kwargs.get("path", os.getenv("HIGHCHARTS_EXPORT_SERVER_PATH", ""))
        self.options = kwargs.get("options", None)
        self.format_ = kwargs.get("format_", kwargs.get("type", "png"))
        self.scale = kwargs.get("scale", 1)
        self.width = kwargs.get("width", None)
        self.height = kwargs.get("height", None)
        self.callback = kwargs.get("callback", None)
        self.constructor = kwargs.get("constructor", "chart")
        self.use_base64 = kwargs.get("use_base64", False)
        self.no_download = kwargs.get("no_download", False)
        self.async_rendering = kwargs.get("async_rendering", False)
        self.global_options = kwargs.get("global_options", None)
        self.data_options = kwargs.get("data_options", None)
        self.custom_code = kwargs.get("custom_code", None)
        files = kwargs.get("files", None)
        css = kwargs.get("css", None)
        js = kwargs.get("js", None)
        resources = kwargs.get("resources", None)
        self.referer = kwargs.get(
            "referer",
            os.getenv(
                "HIGHCHARTS_EXPORT_SERVER_REFERER", "https://www.highchartspython.com"
            ),
        )
        self.user_agent = kwargs.get(
            "user_agent", os.getenv("HIGHCHARTS_EXPORT_SERVER_USER_AGENT", None)
        )
        if resources:
            self.resources = kwargs.get("resources", None)
        else:
            self.files = files
            self.css = css
            self.js = js
        super().__init__(**kwargs)
    @property
    def referer(self) -> Optional[str]:
        """The referer to use when making requests to the export server. Defaults to the
        ``HIGHCHARTS_EXPORT_SERVER_REFERER`` environment variable if present, otherwise defaults to
        ``'https://www.highchartspython.com'``.
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        if not self._referer:
            return os.getenv(
                "HIGHCHARTS_EXPORT_SERVER_REFERER", "https://www.highchartspython.com"
            )
        return self._referer
    @referer.setter
    def referer(self, value):
        value = validators.url(value, allow_empty=True)
        self._referer = value
    @property
    def user_agent(self) -> Optional[str]:
        """The user agent to use when making requests to the export server. Defaults to the ``HIGHCHARTS_EXPORT_SERVER_USER_AGENT`` environment variable if present, otherwise defaults to
        ``Highcharts Core for Python / v.<VERSION NUMBER>``.
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        if self._user_agent:
            return self._user_agent
        user_agent = os.getenv(
            "HIGHCHARTS_EXPORT_SERVER_USER_AGENT",
            f"HighchartsCoreforPy/{highcharts_version.__version__}",
        )
        return user_agent
    @user_agent.setter
    def user_agent(self, value):
        value = validators.string(value, allow_empty=True)
        if not value:
            value = None
        self._user_agent = value
    @property
    def protocol(self) -> Optional[str]:
        """The protocol over which the Highcharts for Python library should communicate
        with the :term:`Export Server`. Accepts either ``'https'`` or ``'http'``. Defaults
        to the ``HIGHCHARTS_EXPORT_SERVER_PROTOCOL`` environment variable if present,
        otherwise falls back to default of ``'https'``.
        .. tip::
          This property is set automatically by the ``HIGHCHARTS_EXPORT_SERVER_PROTOCOL``
          environment variable, if present.
        .. warning::
          If set to :obj:`None <python:None>`, will fall back to the
          ``HIGHCHARTS_EXPORT_SERVER_PROTOCOL`` value if available, and the Highsoft-
          provided server (``'export.highcharts.com'``) if not.
        :rtype: :class:`str <python:str>`
        """
        return self._protocol
    @protocol.setter
    def protocol(self, value):
        value = validators.string(value, allow_empty=True)
        if not value:
            value = os.getenv("HIGHCHARTS_EXPORT_SERVER_PROTOCOL", "https")
        value = value.lower()
        if value not in ["https", "http"]:
            raise errors.HighchartsUnsupportedProtocolError(
                f"protocol expects either "
                f'"https" or "http". '
                f'Received: "{value}"'
            )
        self._protocol = value
        self._url = None
    @property
    def domain(self) -> Optional[str]:
        """The domain where the :term:`Export Server` can be found. Defaults to the
        Highsoft-provided Export Server at ``'export.highcharts.com'``, unless over-ridden
        by the ``HIGHCHARTS_EXPORT_SERVER_DOMAIN`` environment variable.
        .. tip::
          This property is set automatically by the ``HIGHCHARTS_EXPORT_SERVER_DOMAIN``
          environment variable, if present.
        .. warning::
          If set to :obj:`None <python:None>`, will fall back to the
          ``HIGHCHARTS_EXPORT_SERVER_DOMAIN`` value if available, and the Highsoft-
          provided server (``'export.highcharts.com'``) if not.
        :rtype: :class:`str <pythoon:str>`
        """
        return self._domain
    @domain.setter
    def domain(self, value):
        value = validators.domain(value, allow_empty=True)
        if not value:
            value = os.getenv(
                "HIGHCHARTS_EXPORT_SERVER_DOMAIN", "export.highcharts.com"
            )
        self._domain = value
        self._url = None
    @property
    def port(self) -> Optional[int]:
        """The port on which the :term:`Export Server` can be found. Defaults to
        :obj:`None <python:None>` (for the Highsoft-provided export server), unless
        over-ridden by the ``HIGHCHARTS_EXPORT_SERVER_PORT`` environment variable.
        .. tip::
          This property is set automatically by the ``HIGHCHARTS_EXPORT_SERVER_PORT``
          environment variable, if present.
        .. warning::
          If set to :obj:`None <python:None>`, will fall back to the
          ``HIGHCHARTS_EXPORT_SERVER_PORT`` value if available. If unavailable, will
          revert to :obj:`None <python:None>`.
        :rtype: :class:`str <pythoon:str>`
        """
        return self._port
    @port.setter
    def port(self, value):
        if value or value == 0:
            value = validators.integer(
                value, allow_empty=True, minimum=0, maximum=65536
            )
        else:
            value = os.getenv("HIGHCHARTS_EXPORT_SERVER_PORT", None)
        self._port = value
        self._url = None
    @property
    def path(self) -> Optional[str]:
        """The path (at the :meth:`ExportServer.url`) where the :term:`Export Server` can
        be reached. Defaults to :obj:`None <python:None>` (for the Highsoft-provided
        export server), unless over-ridden by the ``HIGHCHARTS_EXPORT_SERVER_PATH``
        environment variable.
        .. tip::
          This property is set automatically by the ``HIGHCHARTS_EXPORT_SERVER_PATH``
          environment variable, if present.
        .. warning::
          If set to :obj:`None <python:None>`, will fall back to the
          ``HIGHCHARTS_EXPORT_SERVER_PATH`` value if available. If unavailable, will
          revert to :obj:`None <python:None>`.
        :rtype: :class:`str <pythoon:str>`
        """
        return self._path
    @path.setter
    def path(self, value):
        value = validators.path(value, allow_empty=True)
        if value is None:
            value = os.getenv("HIGHCHARTS_EXPORT_SERVER_PATH", None)
        self._path = value
        self._url = None
    @property
    def url(self) -> Optional[str]:
        """The fully-formed URL for the :term:`Export Server`, consisting of a
        :meth:`protocol <ExportServer.protocol>`, a :meth:`domain <ExportServer.domain>`,
        and optional :meth:`port <ExportServer.port>` and
        :meth:`path <ExportServer.path>`.
        .. note::
          If explicitly set, will override the values in related properties:
            * :meth:`protocol <ExportServer.protocol>`,
            * :meth:`domain <ExportServer.domain>`,
            * :meth:`port <ExportServer.port>`, and
            * :meth:`path <ExportServer.path>`
        :rtype: :class:`str <python:str>`
        """
        if self._url:
            return self._url
        else:
            return_value = f"{self.protocol}://{self.domain}"
            if self.port is not None:
                return_value += f":{self.port}/"
            if self.path is not None:
                return_value += self.path
            return return_value
    @url.setter
    def url(self, value):
        value = validators.url(
            value,
            allow_empty=True,
            allow_special_ips=os.getenv("HCP_ALLOW_SPECIAL_IPS", False),
        )
        if not value:
            self.protocol = None
            self.domain = None
            self.port = None
            self.path = None
        else:
            original_value = value
            self.protocol = value[: value.index(":")]
            protocol = self.protocol + "://"
            value = value.replace(protocol, "")
            no_port = False
            try:
                end_of_domain = value.index(":")
                self.domain = value[:end_of_domain]
            except ValueError:
                no_port = True
                try:
                    end_of_domain = value.index("/")
                    self.domain = value[:end_of_domain]
                except ValueError:
                    self.domain = value
            domain = self.domain + "/"
            if domain in value:
                value = value.replace(domain, "")
            elif self.domain in value:
                value = value.replace(self.domain, "")
            if value and no_port:
                if value.startswith("/"):
                    self.path = value[1:]
                else:
                    self.path = value
            else:
                if value.startswith(":"):
                    start_of_port = 1
                else:
                    start_of_port = 0
                try:
                    end_of_port = value.index("/")
                except ValueError:
                    end_of_port = None
                if end_of_port:
                    self.port = value[start_of_port:end_of_port]
                else:
                    self.port = value[start_of_port:]
                port = f":{self.port}"
                value = value.replace(port, "")
                if value.startswith("/"):
                    self.path = value[1:]
                elif value:
                    self.path = value
                else:
                    self.path = None
            self._url = original_value
    @property
    def options(self) -> Optional[HighchartsOptions]:
        """The :class:`HighchartsOptions` which should be applied to render the exported
        chart. Defaults to :obj:`None <python:None>`.
        :rtype: :class:`HighchartsOptions` or :obj:`None <pythoN:None>`
        """
        return self._options
    @options.setter
    @class_sensitive(HighchartsOptions)
    def options(self, value):
        self._options = value
    @property
    def format_(self) -> Optional[str]:
        """The format in which the exported chart should be returned. Defaults to
        ``'png'``.
        Accepts:
          * ``'png'``
          * ``'jpeg'``
          * ``'pdf'``
          * ``'svg'``
          * ``'image/svg+xml'``
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        return self._format_
    @format_.setter
    def format_(self, value):
        value = validators.string(value, allow_empty=True)
        if not value:
            self._format_ = None
        else:
            value = value.lower()
            if value not in ["png", "jpeg", "pdf", "svg", "image/svg+xml"]:
                raise errors.HighchartsUnsupportedExportTypeError(
                    f"format_ expects either "
                    f'"png", "jpeg", "pdf", "svg", or '
                    f'"image/svg+xml". Received: {value}'
                )
            if value == "svg":
                value = "image/svg+xml"
            self._format_ = value
    @property
    def scale(self) -> Optional[int | float]:
        """The scale factor by which the exported chart image should be scaled. Defaults
        to ``1``.
        .. tip::
          Use this setting to improve resolution when exporting PNG or JPEG images. For
          example, setting ``.scale = 2`` on a chart whose width is 600px will produce
          an image with a width of 1200px.
        .. warning::
          If :meth:`width <ExportServer.width>` is explicitly set, this setting will be
          overridden.
        :rtype: numeric
        """
        return self._scale
    @scale.setter
    def scale(self, value):
        value = validators.numeric(value, allow_empty=True, minimum=0)
        if not value:
            value = 1
        self._scale = value
    @property
    def width(self) -> Optional[int | float]:
        """The width that the exported chart should have. Defaults to
        :obj:`None <python:None>`.
        .. warning::
          If explicitly set, this setting will override
          :meth:`scale <ExportServer.scale>`.
        :rtype: numeric or :obj:`None <python:None>`
        """
        return self._width
    @width.setter
    def width(self, value):
        value = validators.numeric(value, allow_empty=True, minimum=0)
        if not value:
            value = None
        self._width = value
    @property
    def height(self) -> Optional[int | float]:
        """The height that the exported chart should have. Defaults to
        :obj:`None <python:None>`.
        .. warning::
          If explicitly set, this setting will override
          :meth:`scale <ExportServer.scale>`.
        :rtype: numeric or :obj:`None <python:None>`
        """
        return self._height
    @height.setter
    def height(self, value):
        value = validators.numeric(value, allow_empty=True, minimum=0)
        if not value:
            value = None
        self._height = value
    @property
    def callback(self) -> Optional[CallbackFunction]:
        """A JavaScript function to execute in the (JavaScript) Highcharts constructor.
        .. note::
          This setting is equivalent to providing the :meth:`Chart.callback` setting.
        :rtype: :class:`CallbackFunction` or :obj:`None <pythoN:None>`
        """
        return self._callback
    @callback.setter
    @class_sensitive(CallbackFunction)
    def callback(self, value):
        self._callback = value
    @property
    def constructor(self) -> Optional[str]:
        """The (JavaScript) constructor to use when generating the exported chart.
        Defaults to :obj:`None <python:None>`.
        Accepts:
          * ``'Chart'``
          * ``'chart'``
          * ``'Stock'``
          * ``'stockChart'``
          * ``'Map'``
          * ``'mapChart'``
          * ``'Gantt'``
          * ``'ganttChart'``
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        return self._constructor
    @constructor.setter
    def constructor(self, value):
        value = validators.string(value, allow_empty=True)
        if not value:
            self._constructor = None
        else:
            if value not in [
                "Chart",
                "Stock",
                "Map",
                "Gantt",
                "chart",
                "stockChart",
                "mapChart",
                "ganttChart",
            ]:
                raise errors.HighchartsUnsupportedConstructorError(
                    f"constructor expects "
                    f'"Chart", "Stock", "Map", "Gantt", "chart", '
                    f'"stockChart",  "mapChart", or "ganttChart", but '
                    f'received: "{value}"'
                )
            if value == "Chart":
                value = "chart"
            elif value == "Stock":
                value = "stockChart"
            elif value == "Map":
                value = "mapChart"
            elif value == "Gantt":
                value = "ganttChart"
            self._constructor = value
    @property
    def use_base64(self) -> bool:
        """If ``True``, returns the exported chart in base64 encoding. If ``False``,
        returns the exported chart in binary. Defaults to ``False``.
        :rtype: :class:`bool <python:bool>`
        """
        return self._use_base64
    @use_base64.setter
    def use_base64(self, value):
        self._use_base64 = bool(value)
    @property
    def no_download(self) -> bool:
        """If ``True``, will not send attachment headers in the HTTP response when
        exporting a chart. Defaults to ``False``.
        :rtype: :class:`bool <python:bool>`
        """
        return self._no_download
    @no_download.setter
    def no_download(self, value):
        self._no_download = bool(value)
    @property
    def async_rendering(self) -> bool:
        """If ``True``, will delay the (server-side) rendering of the exported chart
        until all scripts, functions, and event handlers provided have been executed
        and the (JavaScript) method ``highexp.done()`` is called. Defaults to ``False``.
        :rtype: :class:`bool <python:bool>`
        """
        return self._async_rendering
    @async_rendering.setter
    def async_rendering(self, value):
        self._async_rendering = bool(value)
    @property
    def global_options(self) -> Optional[HighchartsOptions]:
        """The global options which will be passed to the (JavaScript)
        ``Highcharts.setOptions()`` method, and which will be applied to the exported
        chart. Defaults to :obj:`None <python:None>`.
        :rtype: :class:`HighchartsOptions`
        """
        return self._global_options
    @global_options.setter
    @class_sensitive(HighchartsOptions)
    def global_options(self, value):
        self._global_options = value
    @property
    def data_options(self) -> Optional[Data]:
        """Configuration of data options to add data to the chart from sources like CSV.
        Defaults to :obj:`None <python:None>`.
        :rtype: :class:`Data` or :obj:`None <python:None>`
        """
        return self._data_options
    @data_options.setter
    @class_sensitive(Data)
    def data_options(self, value):
        self._data_options = value
    @property
    def custom_code(self) -> Optional[CallbackFunction]:
        """When :meth:`data_options <ExportServer.data_options>` is not
        :obj:`None <python:None>`, this (JavaScript) callback function is executed after
        the data options are applied. The only argument it receives is the complete
        set of :class:`HighchartsOptions` (as a JS literal object), which will be passed
        to the Highcharts constructor on return. Defaults to :obj:`None <python:None>`.
        :rtype: :class:`CallbackFunction` or :obj:`None <python:None>`
        """
        return self._custom_code
    @custom_code.setter
    @class_sensitive(CallbackFunction)
    def custom_code(self, value):
        self._custom_code = value
    @property
    def resources(self) -> Optional[Dict]:
        """A dictionary of resources to be used in the export server.
        Expects to contain up to three keys:
          * ``files`` which contains an array of JS filenames
          * ``js`` which contains a string representation of JS code
          * ``css`` which contains a string representation of CSS code that will
            applied to the chart on export
        Defaults to :obj:`None <python:None>`.
        :rtype: :class:`dict <python:dict>` or :obj:`None <python:None>`
        """
        resources = {}
        if self.files:
            resources["files"] = self.files
        if self.js:
            resources["js"] = self.js
        if self.css:
            resources["css"] = self.css
        if not resources:
            return None
        return resources
    @resources.setter
    def resources(self, value):
        if not value:
            self.files = None
            self.js = None
            self.css = None
        elif not isinstance(value, dict):
            raise errors.HighchartsValueError(
                'resources expects a dictionary with keys "files", "js", and "css"'
            )
        else:
            self.files = value.get("files", None)
            self.js = value.get("js", None)
            self.css = value.get("css", None)
    @property
    def files(self) -> Optional[List[str]]:
        """Collection of files that will be loaded into context for the export.
        Defaults to :obj:`None <python:None>`.
        :rtype: :class:`list <python:list>` of :class:`str <python:str>` or
          :obj:`None <python:None>`
        """
        return self._files
    @files.setter
    def files(self, value):
        if not value:
            self._files = None
        else:
            if isinstance(value, str):
                value = [value]
            elif not checkers.is_iterable(value):
                raise errors.HighchartsValueError("files expects a list of strings")
            self._files = value
    @property
    def js(self) -> Optional[str]:
        """JavaScript code that will be loaded into context for the exported chart.
        Defaults to :obj:`None <python:None>`.
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        return self._js
    @js.setter
    def js(self, value):
        if not value:
            self._js = None
        else:
            if not isinstance(value, str):
                raise errors.HighchartsValueError("js expects a string")
            self._js = value
    @property
    def css(self) -> Optional[str]:
        """CSS code that will be loaded into context for the exported chart.
        Defaults to :obj:`None <python:None>`.
        :rtype: :class:`str <python:str>` or :obj:`None <python:None>`
        """
        return self._css
    @css.setter
    def css(self, value):
        if not value:
            self._css = None
        else:
            if not isinstance(value, str):
                raise errors.HighchartsValueError("css expects a string")
            self._css = value
[docs]    @classmethod
    def is_export_supported(cls, options) -> bool:
        """Evaluates whether the Highcharts Export Server supports exporting the series types in ``options``.
        :rtype: :class:`bool <python:bool>`
        """
        if not isinstance(options, HighchartsOptions):
            return False
        if not options.series:
            return True
        series_types = [x.type for x in options.series]
        for item in series_types:
            if item in constants.EXPORT_SERVER_UNSUPPORTED_SERIES_TYPES:
                return False
        return True 
    @classmethod
    def _get_kwargs_from_dict(cls, as_dict):
        url = as_dict.get("url", None)
        protocol = None
        domain = None
        port = None
        path = None
        if not url:
            protocol = as_dict.get("protocol", None)
            domain = as_dict.get("domain", None)
            port = as_dict.get("port", None)
            path = as_dict.get("path", None)
        kwargs = {
            "options": as_dict.get("options", None),
            "format_": as_dict.get("type", as_dict.get("format_", "png")),
            "scale": as_dict.get("scale", 1),
            "width": as_dict.get("width", None),
            "callback": as_dict.get("callback", None),
            "constructor": as_dict.get("constructor", None)
            or as_dict.get("constr", None),
            "use_base64": as_dict.get("use_base64", None) or as_dict.get("b64", False),
            "no_download": as_dict.get("noDownload", None)
            or as_dict.get("no_download", None),
            "async_rendering": as_dict.get("asyncRendering", False)
            or as_dict.get("async_rendering", False),
            "global_options": as_dict.get("global_options", None)
            or as_dict.get("globalOptions", None),
            "data_options": as_dict.get("data_options", None)
            or as_dict.get("dataOptions", None),
            "custom_code": as_dict.get("custom_code", None)
            or as_dict.get("customCode", None),
        }
        if url:
            kwargs["url"] = url
        if protocol:
            kwargs["protocol"] = protocol
        if domain:
            kwargs["domain"] = domain
        if port:
            kwargs["port"] = port
        if path:
            kwargs["path"] = path
        return kwargs
    def _to_untrimmed_dict(self, in_cls=None) -> dict:
        untrimmed = {
            "url": self.url,
            "options": self.options,
            "type": self.format_,
            "scale": self.scale,
            "width": self.width,
            "callback": self.callback,
            "constr": self.constructor,
            "b64": self.use_base64,
            "noDownload": self.no_download,
            "asyncRendering": self.async_rendering,
            "globalOptions": self.global_options,
            "dataOptions": self.data_options,
            "customCode": self.custom_code,
            "resources": self.resources,
        }
        return untrimmed
[docs]    def request_chart(
        self, filename=None, auth_user=None, auth_password=None, timeout=3, **kwargs
    ):
        """Execute a request against the export server based on the configuration in the
        instance.
        :param filename: The name of the file where the exported chart should (optionally)
          be persisted. Defaults to :obj:`None <python:None>`.
        :type filename: Path-like or :obj:`None <python:None>`
        :param auth_user: The username to use to authenticate against the
          Export Server, using :term:`basic authentication`. Defaults to
          :obj:`None <python:None>`.
        :type auth_user: :class:`str <python:str>` or :obj:`None <python:None>`
        :param auth_password: The password to use to authenticate against the Export
          Server (using :term:`basic authentication`). Defaults to
          :obj:`None <python:None>`.
        :type auth_password: :class:`str <python:str>` or :obj:`None <python:None>`
        :param timeout: The number of seconds to wait before issuing a timeout error.
          The timeout check is passed if bytes have been received on the socket in less
          than the ``timeout`` value. Defaults to ``3``.
        :type timeout: numeric or :obj:`None <python:None>`
        .. note::
          All other keyword arguments are as per the :class:`ExportServer` constructor
          :meth:`ExportServer.__init__() <highcharts_core.headless_export.ExportServer.__init__>`
        :returns: The exported chart image, either as a :class:`bytes <python:bytes>`
          binary object or as a base-64 encoded string (depending on the
          :meth:`use_base64 <ExportServer.use_base64>` property).
        :rtype: :class:`bytes <python:bytes>` or :class:`str <python:str>`
        """
        self.options = kwargs.get("options", self.options)
        self.format_ = kwargs.get("format_", kwargs.get("type", self.format_))
        self.scale = kwargs.get("scale", self.scale)
        self.width = kwargs.get("width", self.width)
        self.callback = kwargs.get("callback", self.callback)
        self.constructor = kwargs.get("constructor", self.constructor)
        self.use_base64 = kwargs.get("use_base64", self.use_base64)
        self.no_download = kwargs.get("no_download", self.no_download)
        self.async_rendering = kwargs.get("async_rendering", self.async_rendering)
        self.global_options = kwargs.get("global_options", self.global_options)
        self.custom_code = kwargs.get("custom_code", self.custom_code)
        missing_details = []
        if not self.options:
            missing_details.append("options")
        if not self.format_:
            missing_details.append("format_")
        if not self.constructor:
            missing_details.append("constructor")
        if not self.url:
            missing_details.append("url")
        if missing_details:
            raise errors.HighchartsMissingExportSettingsError(
                f"Unable to export a chart."
                f"ExportServer was missing "
                f" following settings: "
                f"{missing_details}"
            )
        basic_auth = None
        if auth_user and auth_password:
            basic_auth = requests.HTTPBasicAuth(auth_user, auth_password)
        payload = {
            "infile": "HIGHCHARTS FOR PYTHON: REPLACE WITH OPTIONS",
            "type": self.format_,
            "scale": self.scale,
            "constr": self.constructor,
            "b64": self.use_base64,
            "noDownload": self.no_download,
        }
        if self.width:
            payload["width"] = self.width
        if self.height:
            payload["height"] = self.height
        if self.callback:
            payload["callback"] = "HIGHCHARTS FOR PYTHON: REPLACE WITH CALLBACK"
        if self.global_options:
            payload["globalOptions"] = "HIGHCHARTS FOR PYTHON: REPLACE WITH GLOBAL"
        if self.custom_code:
            payload["customCode"] = "HIGHCHARTS FOR PYTHON: REPLACE WITH CUSTOM"
        if self.resources:
            payload["resources"] = self.resources
        as_json = json.dumps(payload)
        if not self.is_export_supported(self.options):
            raise errors.HighchartsUnsupportedExportError(
                "The Highcharts Export Server currently only supports "
                "exports from Highcharts (Javascript) v.10. You are "
                "using a series type introduced in v.11. Sorry, but "
                "that functionality is still forthcoming."
            )
        options_as_json = self.options.to_json(for_export=True)
        if isinstance(options_as_json, bytes):
            options_as_str = str(options_as_json, encoding="utf-8")
        else:
            options_as_str = options_as_json
        as_json = as_json.replace(
            '"HIGHCHARTS FOR PYTHON: REPLACE WITH OPTIONS"', options_as_str
        )
        if self.callback:
            callback_as_json = self.callback.to_json(for_export=True)
            if isinstance(callback_as_json, bytes):
                callback_as_str = str(callback_as_json, encoding="utf-8")
            else:
                callback_as_str = callback_as_json
            as_json = as_json.replace(
                '"HIGHCHARTS FOR PYTHON: REPLACE WITH CALLBACK"', callback_as_str
            )
        if self.global_options:
            global_as_json = self.global_options.to_json(for_export=True)
            if isinstance(global_as_json, bytes):
                global_as_str = str(global_as_json, encoding="utf-8")
            else:
                global_as_str = global_as_json
            as_json = as_json.replace(
                '"HIGHCHARTS FOR PYTHON: REPLACE WITH GLOBAL"', global_as_str
            )
        if self.data_options:
            data_as_json = self.data_options.to_json(for_export=True)
            if isinstance(data_as_json, bytes):
                data_as_str = str(data_as_json, encoding="utf-8")
            else:
                data_as_str = data_as_json
            as_json = as_json.replace(
                '"HIGHCHARTS FOR PYTHON: REPLACE WITH DATA"', data_as_str
            )
        if self.custom_code:
            code_as_json = self.custom_code.to_json(for_export=True)
            if isinstance(code_as_json, bytes):
                code_as_str = str(code_as_json, encoding="utf-8")
            else:
                code_as_str = code_as_json
            as_json = as_json.replace(
                '"HIGHCHARTS FOR PYTHON: REPLACE WITH CUSTOM"', code_as_str
            )
        headers = {
            "Content-Type": "application/json",
            "Origin": self.referer,
            "Referer": self.referer,
            "User-Agent": self.user_agent,
        }
        result = requests.post(
            self.url,
            data=as_json.encode("utf-8"),
            headers=headers,
            auth=basic_auth,
            timeout=timeout,
        )
        result.raise_for_status()
        if filename and self.format_ != "svg":
            with open(filename, "wb") as file_:
                file_.write(result.content)
        elif filename and self.format_ == "svg":
            content = str(result.content, encoding="utf-8").replace("\u200b", " ")
            with open(filename, "wt") as file_:
                file_.write(content)
        return result.content 
[docs]    @classmethod
    def get_chart(
        cls, filename=None, auth_user=None, auth_password=None, timeout=3, **kwargs
    ):
        """Produce an exported chart image.
        :param filename: The name of the file where the exported chart should (optionally)
          be persisted. Defaults to :obj:`None <python:None>`.
        :type filename: Path-like or :obj:`None <python:None>`
        :param auth_user: The username to use to authenticate against the
          Export Server, using :term:`basic authentication`. Defaults to
          :obj:`None <python:None>`.
        :type auth_user: :class:`str <python:str>` or :obj:`None <python:None>`
        :param auth_password: The password to use to authenticate against the Export
          Server (using :term:`basic authentication`). Defaults to
          :obj:`None <python:None>`.
        :type auth_password: :class:`str <python:str>` or :obj:`None <python:None>`
        :param timeout: The number of seconds to wait before issuing a timeout error.
          The timeout check is passed if bytes have been received on the socket in less
          than the ``timeout`` value. Defaults to ``3``.
        :type timeout: numeric or :obj:`None <python:None>`
        .. note::
          All other keyword arguments are as per the :class:`ExportServer` constructor
          :meth:`ExportServer.__init__() <highcharts_core.headless_export.ExportServer.__init__>`
        :returns: The exported chart image, either as a :class:`bytes <python:bytes>`
          binary object or as a base-64 encoded string (depending on the ``use_base64``
          keyword argument).
        :rtype: :class:`bytes <python:bytes>` or :class:`str <python:str>`
        """
        instance = cls(**kwargs)
        exported_chart = instance.request_chart(
            filename=filename,
            auth_user=auth_user,
            auth_password=auth_password,
            timeout=timeout,
        )
        return exported_chart