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.
- salespyforce/__init__.py +53 -0
- salespyforce/api.py +129 -0
- salespyforce/chatter.py +167 -0
- salespyforce/core.py +872 -0
- salespyforce/errors/__init__.py +12 -0
- salespyforce/errors/exceptions.py +389 -0
- salespyforce/errors/handlers.py +15 -0
- salespyforce/knowledge.py +531 -0
- salespyforce/utils/__init__.py +10 -0
- salespyforce/utils/core_utils.py +152 -0
- salespyforce/utils/helper.py +140 -0
- salespyforce/utils/log_utils.py +264 -0
- salespyforce/utils/tests/__init__.py +8 -0
- salespyforce/utils/tests/resources.py +157 -0
- salespyforce/utils/tests/test_instantiate_object.py +49 -0
- salespyforce/utils/tests/test_sobjects.py +58 -0
- salespyforce/utils/tests/test_soql.py +23 -0
- salespyforce/utils/tests/test_sosl.py +29 -0
- salespyforce/utils/version.py +52 -0
- salespyforce-1.4.0.dev0.dist-info/LICENSE +21 -0
- salespyforce-1.4.0.dev0.dist-info/METADATA +253 -0
- salespyforce-1.4.0.dev0.dist-info/RECORD +23 -0
- salespyforce-1.4.0.dev0.dist-info/WHEEL +4 -0
|
@@ -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
|