salespyforce 1.4.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,152 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ :Module: salespyforce.utils.core_utils
4
+ :Synopsis: Collection of supporting utilities and functions to complement the primary modules
5
+ :Usage: ``from salespyforce.utils import core_utils``
6
+ :Example: ``encoded_string = core_utils.encode_url(decoded_string)``
7
+ :Created By: Jeff Shurtliff
8
+ :Last Modified: Jeff Shurtliff
9
+ :Modified Date: 29 May 2023
10
+ """
11
+
12
+ import random
13
+ import string
14
+ import os.path
15
+ import warnings
16
+ import urllib.parse
17
+
18
+ import requests
19
+
20
+ from . import log_utils
21
+ from .. import errors
22
+
23
+ # Initialize the logger for this module
24
+ logger = log_utils.initialize_logging(__name__)
25
+
26
+
27
+ def url_encode(raw_string):
28
+ """This function encodes a string for use in URLs.
29
+
30
+ :param raw_string: The raw string to be encoded
31
+ :type raw_string: str
32
+ :returns: The encoded string
33
+ """
34
+ return urllib.parse.quote_plus(raw_string)
35
+
36
+
37
+ def url_decode(encoded_string):
38
+ """This function decodes a url-encoded string.
39
+
40
+ :param encoded_string: The url-encoded string
41
+ :type encoded_string: str
42
+ :returns: The unencoded string
43
+ """
44
+ return urllib.parse.unquote_plus(encoded_string)
45
+
46
+
47
+ def display_warning(warn_msg):
48
+ """This function displays a :py:exc:`UserWarning` message via the :py:mod:`warnings` module.
49
+
50
+ :param warn_msg: The message to be displayed
51
+ :type warn_msg: str
52
+ :returns: None
53
+ """
54
+ warnings.warn(warn_msg, UserWarning)
55
+
56
+
57
+ def get_file_type(file_path):
58
+ """This function attempts to identify if a given file path is for a YAML or JSON file.
59
+
60
+ :param file_path: The full path to the file
61
+ :type file_path: str
62
+ :returns: The file type in string format (e.g. ``yaml`` or ``json``)
63
+ :raises: :py:exc:`FileNotFoundError`, :py:exc:`khoros.errors.exceptions.UnknownFileTypeError`
64
+ """
65
+ file_type = 'unknown'
66
+ if os.path.isfile(file_path):
67
+ if file_path.endswith('.json'):
68
+ file_type = 'json'
69
+ elif file_path.endswith('.yml') or file_path.endswith('.yaml'):
70
+ file_type = 'yaml'
71
+ else:
72
+ display_warning(f"Unable to recognize the file type of '{file_path}' by its extension.")
73
+ with open(file_path) as cfg_file:
74
+ for line in cfg_file:
75
+ if line.startswith('#'):
76
+ continue
77
+ else:
78
+ if '{' in line:
79
+ file_type = 'json'
80
+ break
81
+ if file_type == 'unknown':
82
+ raise errors.exceptions.UnknownFileTypeError(file=file_path)
83
+ else:
84
+ raise FileNotFoundError(f"Unable to locate the following file: {file_path}")
85
+ return file_type
86
+
87
+
88
+ def get_random_string(length=32, prefix_string=""):
89
+ """This function returns a random alphanumeric string.
90
+
91
+ :param length: The length of the string (``32`` by default)
92
+ :type length: int
93
+ :param prefix_string: A string to which the random string should be appended (optional)
94
+ :type prefix_string: str
95
+ :returns: The alphanumeric string
96
+ """
97
+ return f"{prefix_string}{''.join([random.choice(string.ascii_letters + string.digits) for _ in range(length)])}"
98
+
99
+
100
+ def get_image_ref_id(image_url):
101
+ """This function parses an image URL to identify the reference ID (refid) value.
102
+ (`Reference 1 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/resources_sobject_rich_text_image_retrieve.htm>`_,
103
+ `Reference 2 <https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_rich_text_image_retrieve.htm>`_)
104
+
105
+ :param image_url: The URL of an image from within Salesforce
106
+ :type image_url: str
107
+ :returns: The reference ID (``refid``) value
108
+ """
109
+ query_params = urllib.parse.parse_qs(urllib.parse.urlparse(image_url).query)
110
+ # noinspection PyTypeChecker
111
+ ref_id = query_params.get('refid')
112
+ ref_id = ref_id[0] if not isinstance(ref_id, str) else ref_id
113
+ return ref_id
114
+
115
+
116
+ def download_image(image_url=None, file_name=None, file_path=None, response=None, extension='jpeg'):
117
+ """This function downloads an image and saves it to a specified directory.
118
+
119
+ :param image_url: The absolute URL to the image
120
+ :type image_url: str
121
+ :param file_name: The file name including extension as which to save the file
122
+ :type file_name: str
123
+ :param file_path: File path where the image file should be saved (default: ``var/images/``)
124
+ :type file_path: str, None
125
+ :param response: The response of the previously performed API call
126
+ :param extension: The file extension to use if a file name with extension is not provided
127
+ :type extension: str
128
+ :returns: The full path to the downloaded image
129
+ :raises: :py:exc:`RuntimeError`
130
+ """
131
+ if not image_url and not response:
132
+ raise RuntimeError('An image URL or an API response must be provided to download an image.')
133
+
134
+ # Define an appropriate file path
135
+ file_path = './' if not file_path else file_path
136
+ file_path = f'{file_path}/' if not any((file_path.endswith('/'), file_path.endswith('\\'))) else file_path
137
+
138
+ # Define a file name if not provided
139
+ if not file_name:
140
+ file_name = get_random_string(10, 'image_')
141
+ file_name += extension
142
+
143
+ # Perform the API call if not supplied
144
+ if not response:
145
+ response = requests.get(image_url)
146
+ if response.status_code != 200:
147
+ raise RuntimeError(f'The image failed to download with a {response.status_code} status code.')
148
+
149
+ # Export the response data as an image file
150
+ with open(f'{file_path}{file_name}', 'wb') as file:
151
+ file.write(response.content)
152
+ return f'{file_path}{file_name}'
@@ -0,0 +1,140 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ :Module: salespyforce.utils.helper
4
+ :Synopsis: Module that allows the salespyforce library to leverage a helper configuration file
5
+ :Usage: ``from salespyforce.utils import helper``
6
+ :Example: ``helper_settings = helper.get_settings('/tmp/helper.yml', 'yaml')``
7
+ :Created By: Jeff Shurtliff
8
+ :Last Modified: Jeff Shurtliff
9
+ :Modified Date: 13 Mar 2023
10
+ """
11
+
12
+ import json
13
+
14
+ import yaml
15
+
16
+ from .. import errors
17
+ from . import log_utils
18
+ from .core_utils import get_file_type
19
+
20
+ # Initialize logging within the module
21
+ logger = log_utils.initialize_logging(__name__)
22
+
23
+
24
+ def import_helper_file(file_path, file_type):
25
+ """This function imports a YAML (.yml) or JSON (.json) helper config file.
26
+
27
+ :param file_path: The file path to the YAML file
28
+ :type file_path: str
29
+ :param file_type: Defines the file type as either ``yaml`` or ``json``
30
+ :type file_type: str
31
+ :returns: The parsed configuration data
32
+ :raises: :py:exc:`FileNotFoundError`, :py:exc:`salespyforce.errors.exceptions.InvalidHelperFileTypeError`
33
+ """
34
+ with open(file_path, 'r') as cfg_file:
35
+ if file_type == 'yaml':
36
+ helper_cfg = yaml.safe_load(cfg_file)
37
+ elif file_type == 'json':
38
+ helper_cfg = json.load(cfg_file)
39
+ else:
40
+ raise errors.exceptions.InvalidHelperFileTypeError()
41
+ logger.info(f'The helper file {file_path} was imported successfully.')
42
+ return helper_cfg
43
+
44
+
45
+ def _convert_yaml_to_bool(_yaml_bool_value):
46
+ """This function converts the 'yes' and 'no' YAML values to traditional Boolean values."""
47
+ _true_values = ['yes', 'true']
48
+ if _yaml_bool_value.lower() in _true_values:
49
+ _bool_value = True
50
+ else:
51
+ _bool_value = False
52
+ return _bool_value
53
+
54
+
55
+ def _get_connection_info(_helper_cfg):
56
+ """This function parses any connection information found in the helper file."""
57
+ _connection_info = {}
58
+ _connection_keys = ['username', 'password', 'base_url', 'endpoint_url',
59
+ 'client_key', 'client_secret', 'org_id', 'security_token']
60
+ for _key in _connection_keys:
61
+ if _key in _helper_cfg['connection']:
62
+ _connection_info[_key] = _helper_cfg['connection'][_key]
63
+ return _connection_info
64
+
65
+
66
+ def _collect_values(_top_level_keys, _helper_cfg, _helper_dict=None, _ignore_missing=False):
67
+ """This function loops through a list of top-level keys to collect their corresponding values.
68
+
69
+ :param _top_level_keys: One or more top-level keys that might be found in the helper config file
70
+ :type _top_level_keys: list, tuple, set, str
71
+ :param _helper_cfg: The configuration parsed from the helper configuration file
72
+ :type _helper_cfg: dict
73
+ :param _helper_dict: A predefined dictionary to which the key value pairs should be added
74
+ :type _helper_dict: dict, None
75
+ :param _ignore_missing: Indicates whether fields with null values should be ignored (``False`` by default)
76
+ :type _ignore_missing: bool
77
+ :returns: A dictionary with the identified key value pairs
78
+ """
79
+ _helper_dict = {} if not _helper_dict else _helper_dict
80
+ _top_level_keys = (_top_level_keys, ) if isinstance(_top_level_keys, str) else _top_level_keys
81
+ for _key in _top_level_keys:
82
+ if _key in _helper_cfg:
83
+ _key_val = _helper_cfg[_key]
84
+ if _key_val in HelperParsing.yaml_boolean_values:
85
+ _key_val = HelperParsing.yaml_boolean_values.get(_key_val)
86
+ _helper_dict[_key] = _key_val
87
+ elif _key == "ssl_verify":
88
+ # Verify SSL certificates by default unless explicitly set to false
89
+ _helper_dict[_key] = True
90
+ else:
91
+ if not _ignore_missing:
92
+ _helper_dict[_key] = None
93
+ return _helper_dict
94
+
95
+
96
+ def get_helper_settings(file_path, file_type='yaml', defined_settings=None):
97
+ """This function returns a dictionary of the defined helper settings.
98
+
99
+ :param file_path: The file path to the helper configuration file
100
+ :type file_path: str
101
+ :param file_type: Defines the helper configuration file as a ``yaml`` file (default) or a ``json`` file
102
+ :type file_type: str
103
+ :param defined_settings: Core object settings (if any) defined via the ``defined_settings`` parameter
104
+ :type defined_settings: dict, None
105
+ :returns: Dictionary of helper variables
106
+ :raises: :py:exc:`salespyforce.errors.exceptions.InvalidHelperFileTypeError`
107
+ """
108
+ # Initialize the helper_settings dictionary
109
+ helper_settings = {}
110
+
111
+ # Convert the defined_settings parameter to an empty dictionary if null
112
+ defined_settings = {} if not defined_settings else defined_settings
113
+
114
+ if file_type != 'yaml' and file_type != 'json':
115
+ file_type = get_file_type(file_path)
116
+
117
+ # Import the helper configuration file
118
+ helper_cfg = import_helper_file(file_path, file_type)
119
+
120
+ # Populate the connection information in the helper dictionary
121
+ if 'connection' in helper_cfg and 'connection' not in defined_settings:
122
+ helper_settings['connection'] = _get_connection_info(helper_cfg)
123
+
124
+ # Populate the SSL certificate verification setting in the helper dictionary
125
+ if 'ssl_verify' not in defined_settings:
126
+ helper_settings.update(_collect_values('ssl_verify', helper_cfg))
127
+
128
+ # Return the helper_settings dictionary
129
+ return helper_settings
130
+
131
+
132
+ class HelperParsing:
133
+ """This class is used to help parse values imported from a YAML configuration file."""
134
+ # Define dictionary to map YAML Boolean to Python Boolean
135
+ yaml_boolean_values = {
136
+ True: True,
137
+ False: False,
138
+ 'yes': True,
139
+ 'no': False
140
+ }
@@ -0,0 +1,264 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ :Module: salespyforce.utils.log_utils
4
+ :Synopsis: Collection of logging utilities and functions
5
+ :Usage: ``from salespyforce.utils import log_utils``
6
+ :Example: ``logger = log_utils.initialize_logging(__name__)``
7
+ :Created By: Jeff Shurtliff
8
+ :Last Modified: Jeff Shurtliff
9
+ :Modified Date: 22 Jan 2023
10
+ """
11
+
12
+ import os
13
+ import sys
14
+ import logging
15
+ import logging.handlers
16
+ from pathlib import Path
17
+
18
+ LOGGING_DEFAULTS = {
19
+ 'logger_name': __name__,
20
+ 'log_level': 'info',
21
+ 'formatter': logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s'),
22
+ 'date_format': '%Y-%m-%d %I:%M:%S',
23
+ }
24
+ HANDLER_DEFAULTS = {
25
+ 'file_log_level': 'info',
26
+ 'console_log_level': 'warning',
27
+ 'syslog_log_level': 'info',
28
+ 'syslog_address': 'localhost',
29
+ 'syslog_port': 514,
30
+ }
31
+
32
+
33
+ def initialize_logging(logger_name=None, log_level=None, formatter=None, debug=None, no_output=None, file_output=None,
34
+ file_log_level=None, log_file=None, overwrite_log_files=None, console_output=None,
35
+ console_log_level=None, syslog_output=None, syslog_log_level=None, syslog_address=None,
36
+ syslog_port=None):
37
+ """This function initializes logging for the salespyforce library."""
38
+ # TODO: Complete the docstring above with parameters
39
+ logger_name, log_levels, formatter = _apply_defaults(logger_name, formatter, debug, log_level, file_log_level,
40
+ console_log_level, syslog_log_level)
41
+ log_level, file_log_level, console_log_level, syslog_log_level = _get_log_levels_from_dict(log_levels)
42
+ logger = logging.getLogger(logger_name)
43
+ logger = _set_logging_level(logger, log_level)
44
+ logger = _add_handlers(logger, formatter, no_output, file_output, file_log_level, log_file, overwrite_log_files,
45
+ console_output, console_log_level, syslog_output, syslog_log_level, syslog_address,
46
+ syslog_port)
47
+ return logger
48
+
49
+
50
+ class LessThanFilter(logging.Filter):
51
+ """This class allows filters to be set to limit log levels to only less than a specified level.
52
+
53
+ .. seealso:: `Zoey Greer <https://stackoverflow.com/users/5124424/zoey-greer>`_ is the original author of
54
+ this class which was provided on `Stack Overflow <https://stackoverflow.com/a/31459386>`_.
55
+ """
56
+ def __init__(self, exclusive_maximum, name=""):
57
+ """This method instantiates the :py:class:`salespyforce.utils.log_utils.LessThanFilter` class object."""
58
+ super(LessThanFilter, self).__init__(name)
59
+ self.max_level = exclusive_maximum
60
+
61
+ def filter(self, record):
62
+ """This method returns a Boolean integer value indicating whether or not a message should be logged.
63
+
64
+ .. note:: A non-zero return indicates that the message will be logged.
65
+ """
66
+ return 1 if record.levelno < self.max_level else 0
67
+
68
+
69
+ def _apply_defaults(_logger_name, _formatter, _debug, _log_level, _file_level, _console_level, _syslog_level):
70
+ """This function applies default values to the configuration settings if not explicitly defined.
71
+
72
+ :param _logger_name: The name of the logger instance
73
+ :type _logger_name: str, None
74
+ :param _formatter: The log format to utilize for the logger instance
75
+ :type _formatter: str, None
76
+ :param _debug: Defines if debug mode is enabled
77
+ :type _debug: bool, None
78
+ :param _log_level: The general logging level for the logger instance
79
+ :type _log_level: str, None
80
+ :returns: The values that will be used for the configuration settings
81
+ """
82
+ _log_levels = {
83
+ 'general': _log_level,
84
+ 'file': _file_level,
85
+ 'console': _console_level,
86
+ 'syslog': _syslog_level,
87
+ }
88
+ _logger_name = LOGGING_DEFAULTS.get('logger_name') if not _logger_name else _logger_name
89
+ if _debug:
90
+ for _log_type in _log_levels:
91
+ _log_levels[_log_type] = 'debug'
92
+ else:
93
+ if _log_level:
94
+ for _lvl_type, _lvl_value in _log_levels.items():
95
+ if _lvl_type != 'general' and _lvl_value is None:
96
+ _log_levels[_lvl_type] = _log_level
97
+ else:
98
+ _log_level = LOGGING_DEFAULTS.get('log_level')
99
+ if _formatter and isinstance(_formatter, str):
100
+ _formatter = logging.Formatter(_formatter)
101
+ _formatter = LOGGING_DEFAULTS.get('formatter') if not _formatter else _formatter
102
+ return _logger_name, _log_levels, _formatter
103
+
104
+
105
+ def _get_log_levels_from_dict(_log_levels):
106
+ """This function returns the individual log level values from a dictionary.
107
+
108
+ :param _log_levels: Dictionary containing log levels for different handlers
109
+ :type _log_levels: dict
110
+ :returns: Individual string values for each handler
111
+ """
112
+ _general = _log_levels.get('general')
113
+ _file = _log_levels.get('file')
114
+ _console = _log_levels.get('console')
115
+ _syslog = _log_levels.get('syslog')
116
+ return _general, _file, _console, _syslog
117
+
118
+
119
+ def _set_logging_level(_logger, _log_level):
120
+ """This function sets the logging level for a :py:class:`logging.Logger` instance.
121
+
122
+ :param _logger: The :py:class:`logging.Logger` instance
123
+ :type _logger: Logger
124
+ :param _log_level: The log level as a string (``debug``, ``info``, ``warning``, ``error`` or ``critical``)
125
+ :type _log_level: str
126
+ :returns: The :py:class:`logging.Logger` instance with a logging level set where applicable
127
+ """
128
+ if _log_level == 'debug':
129
+ _logger.setLevel(logging.DEBUG)
130
+ elif _log_level == 'info':
131
+ _logger.setLevel(logging.INFO)
132
+ elif _log_level == 'warning':
133
+ _logger.setLevel(logging.WARNING)
134
+ elif _log_level == 'error':
135
+ _logger.setLevel(logging.ERROR)
136
+ elif _log_level == 'critical':
137
+ _logger.setLevel(logging.CRITICAL)
138
+ return _logger
139
+
140
+
141
+ def _add_handlers(_logger, _formatter, _no_output, _file_output, _file_log_level, _log_file, _overwrite_log_files,
142
+ _console_output, _console_log_level, _syslog_output, _syslog_log_level, _syslog_address,
143
+ _syslog_port):
144
+ # TODO: Add docstring
145
+ if _no_output or not any((_file_output, _console_output, _syslog_output)):
146
+ _logger.addHandler(logging.NullHandler())
147
+ else:
148
+ if _file_output:
149
+ # Add the FileHandler to the Logger object
150
+ _logger = _add_file_handler(_logger, _file_log_level, _log_file, _overwrite_log_files, _formatter)
151
+ if _console_output:
152
+ # Add the StreamHandler to the Logger object
153
+ _logger = _add_stream_handler(_logger, _console_log_level, _formatter)
154
+ if _syslog_output:
155
+ # Add the SyslogHandler to the Logger object
156
+ _logger = _add_syslog_handler(_logger, _syslog_log_level, _formatter, _syslog_address, _syslog_port)
157
+ return _logger
158
+
159
+
160
+ def _add_file_handler(_logger, _log_level, _log_file, _overwrite, _formatter):
161
+ """This function adds a :py:class:`logging.FileHandler` to the :py:class:`logging.Logger` instance.
162
+
163
+ :param _logger: The :py:class:`logging.Logger` instance
164
+ :type _logger: Logger
165
+ :param _log_level: The log level to set for the handler
166
+ :type _log_level: str
167
+ :param _log_file: The log file (as a file name or a file path) to which messages should be written
168
+
169
+ .. note:: If a file path isn't provided then the default directory is the home directory of the user instantiating
170
+ the :py:class:`logging.Logger` object. If a file name is also no provided then it will default to
171
+ using ``salespyforce.log`` as the file name.
172
+
173
+ :param _overwrite: Determines if messages should be appended to the file (default) or overwrite it
174
+ :type _overwrite: bool
175
+ :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
176
+ :type _formatter: Formatter
177
+ :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.FileHandler`
178
+ """
179
+ # Define the log file to use
180
+ _home_dir = str(Path.home())
181
+ if _log_file:
182
+ if not any((('/' in _log_file), ('\\' in _log_file))):
183
+ _log_file = os.path.join(_home_dir, _log_file)
184
+ else:
185
+ _log_file = os.path.join(_home_dir, 'salespyforce.log')
186
+
187
+ # Identify if log file should be overwritten
188
+ _write_mode = 'w' if _overwrite else 'a'
189
+
190
+ # Instantiate the handler
191
+ _handler = logging.FileHandler(_log_file, _write_mode)
192
+ _log_level = HANDLER_DEFAULTS.get('file_log_level') if not _log_level else _log_level
193
+ _handler = _set_logging_level(_handler, _log_level)
194
+ _handler.setFormatter(_formatter)
195
+
196
+ # Add the handler to the logger
197
+ _logger.addHandler(_handler)
198
+ return _logger
199
+
200
+
201
+ def _add_stream_handler(_logger, _log_level, _formatter):
202
+ """This function adds a :py:class:`logging.StreamHandler` to the :py:class:`logging.Logger` instance.
203
+
204
+ :param _logger: The :py:class:`logging.Logger` instance
205
+ :type _logger: Logger
206
+ :param _log_level: The log level to set for the handler
207
+ :type _log_level: str
208
+ :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handler
209
+ :type _formatter: Formatter
210
+ :returns: The :py:class:`logging.Logger` instance with the added :py:class:`logging.StreamHandler`
211
+ """
212
+ _log_level = HANDLER_DEFAULTS.get('console_log_level') if not _log_level else _log_level
213
+ _stdout_levels = ['DEBUG', 'INFO']
214
+ if _log_level.upper() in _stdout_levels:
215
+ _logger = _add_split_stream_handlers(_logger, _log_level, _formatter)
216
+ else:
217
+ _handler = logging.StreamHandler()
218
+ _handler = _set_logging_level(_handler, _log_level)
219
+ _handler.setFormatter(_formatter)
220
+ _logger.addHandler(_handler)
221
+ return _logger
222
+
223
+
224
+ def _add_split_stream_handlers(_logger, _log_level, _formatter):
225
+ """This function splits messages into q ``stdout`` or ``stderr`` handler depending on the log level.
226
+
227
+ .. seealso:: Refer to the documentation for the :py:class:`salespyforce.utils.log_utils.LessThanFilter` for
228
+ more information on how this filtering is implemented and for credit to the original author.
229
+
230
+ :param _logger: The :py:class:`logging.Logger` instance
231
+ :type _logger: Logger
232
+ :param _log_level: The log level provided for the stream handler (i.e. console output)
233
+ :type _log_level: str
234
+ :param _formatter: The :py:class:`logging.Formatter` to apply to messages passed through the handlers
235
+ :type _formatter: Formatter
236
+ :returns: The logger instance with the two handlers added
237
+ """
238
+ # Configure and add the STDOUT handler
239
+ _stdout_handler = logging.StreamHandler(sys.stdout)
240
+ _stdout_handler = _set_logging_level(_stdout_handler, _log_level)
241
+ _stdout_handler.addFilter(LessThanFilter(logging.WARNING))
242
+ _stdout_handler.setFormatter(_formatter)
243
+ _logger.addHandler(_stdout_handler)
244
+
245
+ # Configure and add the STDERR handler
246
+ _stderr_handler = logging.StreamHandler(sys.stderr)
247
+ _stderr_handler.setLevel(logging.WARNING)
248
+ _stderr_handler.setFormatter(_formatter)
249
+ _logger.addHandler(_stderr_handler)
250
+
251
+ # Return the logger with the added handlers
252
+ return _logger
253
+
254
+
255
+ def _add_syslog_handler(_logger, _log_level, _formatter, _address, _port):
256
+ # TODO: Add docstring
257
+ _log_level = HANDLER_DEFAULTS.get('syslog_log_level') if not _log_level else _log_level
258
+ _address = HANDLER_DEFAULTS.get('syslog_address') if not _address else _address
259
+ _port = HANDLER_DEFAULTS.get('syslog_port') if not _port else _port
260
+ _handler = logging.handlers.SysLogHandler(address=(_address, _port))
261
+ _handler = _set_logging_level(_handler, _log_level)
262
+ _handler.setFormatter(_formatter)
263
+ _logger.addHandler(_handler)
264
+ return _logger
@@ -0,0 +1,8 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ :Module: salespyforce.utils.tests
4
+ :Synopsis: This package includes tests for the salespyforce library.
5
+ :Created By: Jeff Shurtliff
6
+ :Last Modified: Jeff Shurtliff
7
+ :Modified Date: 27 May 2023
8
+ """