""" Formatting helpers that perform quoting and escaping """ import urllib.parse from datetime import date, datetime, timezone from string import Formatter # https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_quotedstringescapes.htm soql_escapes = str.maketrans({ '\\': '\\\\', '\'': '\\\'', '"': '\\"', '\n': '\\n', '\r': '\\r', '\t': '\\t', '\b': '\\b', '\f': '\\f', }) soql_like_escapes = str.maketrans({ '%': '\\%', '_': '\\_', }) class SoqlFormatter(Formatter): """ Custom formatter to apply quoting or the :literal format spec """ def format_field(self, value, format_spec): if not format_spec: return quote_soql_value(value) if format_spec == 'literal': # literal: allows circumventing everything while still using # the same format string return value if format_spec == 'like': # like: allows escaping substring used in LIKE expression # does not quote return (str(value).translate(soql_escapes) .translate(soql_like_escapes)) return super().format_field(value, format_spec) def format_soql(query, *args, **kwargs): """ Insert values quoted for SOQL into a format string """ return SoqlFormatter().vformat(query, args, kwargs) # pylint: disable=too-many-return-statements def quote_soql_value(value): """ Quote/escape either an individual value or a list of values for a SOQL value expression """ if isinstance(value, str): return "'" + value.translate(soql_escapes) + "'" if value is True: return 'true' if value is False: return 'false' if value is None: return 'null' if isinstance(value, (int, float)): return str(value) if isinstance(value, (list, set, tuple)): quoted_items = [quote_soql_value(member) for member in value] return '(' + ','.join(quoted_items) + ')' if isinstance(value, datetime): # Salesforce spec requires a datetime literal # that is not naive and without MS value = value.replace(microsecond=0) value = value.astimezone(tz=timezone.utc) return value.isoformat() if isinstance(value, date): return value.isoformat() raise ValueError('unquotable value type') def format_external_id(field, value): """ Create an external ID string for use with get() or upsert() """ return field + '/' + urllib.parse.quote(value, safe='')