Browse Source

Merge pull request #768 from octodns/py3-f-strings

Python 3: use f-strings
pull/781/head
Ross McFarland 4 years ago
committed by GitHub
parent
commit
00d283b217
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 892 additions and 1068 deletions
  1. +1
    -1
      octodns/cmds/args.py
  2. +3
    -2
      octodns/cmds/report.py
  3. +55
    -61
      octodns/manager.py
  4. +2
    -3
      octodns/processor/ownership.py
  5. +37
    -38
      octodns/provider/azuredns.py
  6. +7
    -10
      octodns/provider/base.py
  7. +59
    -48
      octodns/provider/cloudflare.py
  8. +15
    -22
      octodns/provider/constellix.py
  9. +14
    -18
      octodns/provider/digitalocean.py
  10. +20
    -24
      octodns/provider/dnsimple.py
  11. +13
    -14
      octodns/provider/dnsmadeeasy.py
  12. +25
    -23
      octodns/provider/dyn.py
  13. +24
    -29
      octodns/provider/easydns.py
  14. +17
    -24
      octodns/provider/edgedns.py
  15. +10
    -11
      octodns/provider/etc_hosts.py
  16. +19
    -21
      octodns/provider/gandi.py
  17. +13
    -21
      octodns/provider/gcore.py
  18. +17
    -23
      octodns/provider/googlecloud.py
  19. +11
    -11
      octodns/provider/hetzner.py
  20. +30
    -36
      octodns/provider/mythicbeasts.py
  21. +24
    -29
      octodns/provider/ns1.py
  22. +20
    -30
      octodns/provider/ovh.py
  23. +22
    -19
      octodns/provider/plan.py
  24. +18
    -25
      octodns/provider/powerdns.py
  25. +10
    -14
      octodns/provider/rackspace.py
  26. +65
    -80
      octodns/provider/route53.py
  27. +16
    -17
      octodns/provider/selectel.py
  28. +14
    -22
      octodns/provider/ultra.py
  29. +8
    -8
      octodns/provider/yaml.py
  30. +116
    -159
      octodns/record/__init__.py
  31. +6
    -7
      octodns/record/geo.py
  32. +4
    -4
      octodns/source/axfr.py
  33. +3
    -3
      octodns/source/envvar.py
  34. +10
    -11
      octodns/source/tinydns.py
  35. +2
    -2
      octodns/yaml.py
  36. +13
    -16
      octodns/zone.py
  37. +1
    -1
      tests/test_octodns_processor_ownership.py
  38. +35
    -37
      tests/test_octodns_provider_azuredns.py
  39. +1
    -1
      tests/test_octodns_provider_base.py
  40. +9
    -14
      tests/test_octodns_provider_cloudflare.py
  41. +4
    -8
      tests/test_octodns_provider_constellix.py
  42. +2
    -2
      tests/test_octodns_provider_digitalocean.py
  43. +2
    -2
      tests/test_octodns_provider_dnsimple.py
  44. +2
    -3
      tests/test_octodns_provider_dnsmadeeasy.py
  45. +9
    -13
      tests/test_octodns_provider_easydns.py
  46. +1
    -1
      tests/test_octodns_provider_gcore.py
  47. +2
    -3
      tests/test_octodns_provider_googlecloud.py
  48. +2
    -2
      tests/test_octodns_provider_hetzner.py
  49. +4
    -6
      tests/test_octodns_provider_ns1.py
  50. +3
    -4
      tests/test_octodns_provider_route53.py
  51. +40
    -48
      tests/test_octodns_provider_selectel.py
  52. +25
    -29
      tests/test_octodns_provider_ultra.py
  53. +4
    -5
      tests/test_octodns_provider_yaml.py
  54. +1
    -1
      tests/test_octodns_record.py
  55. +2
    -2
      tests/test_octodns_source_envvar.py

+ 1
- 1
octodns/cmds/args.py View File

@ -25,7 +25,7 @@ class ArgumentParser(_Base):
super(ArgumentParser, self).__init__(*args, **kwargs)
def parse_args(self, default_log_level=INFO):
version = 'octoDNS {}'.format(__VERSION__)
version = f'octoDNS {__VERSION__}'
self.add_argument('--version', action='version', version=version,
help='Print octoDNS version and exit')
self.add_argument('--log-stream-stdout', action='store_true',


+ 3
- 2
octodns/cmds/report.py View File

@ -53,13 +53,14 @@ def main():
try:
sources = [manager.providers[source] for source in args.source]
except KeyError as e:
raise Exception('Unknown source: {}'.format(e.args[0]))
raise Exception(f'Unknown source: {e.args[0]}')
zone = manager.get_zone(args.zone)
for source in sources:
source.populate(zone)
print('name,type,ttl,{},consistent'.format(','.join(args.server)))
servers = ','.join(args.server)
print(f'name,type,ttl,{servers},consistent')
resolvers = []
ip_addr_re = re.compile(r'^[\d\.]+$')
for server in args.server:


+ 55
- 61
octodns/manager.py View File

@ -111,16 +111,16 @@ class Manager(object):
_class = provider_config.pop('class')
except KeyError:
self.log.exception('Invalid provider class')
raise ManagerException('Provider {} is missing class'
.format(provider_name))
raise ManagerException(f'Provider {provider_name} is missing '
'class')
_class = self._get_named_class('provider', _class)
kwargs = self._build_kwargs(provider_config)
try:
self.providers[provider_name] = _class(provider_name, **kwargs)
except TypeError:
self.log.exception('Invalid provider config')
raise ManagerException('Incorrect provider config for {}'
.format(provider_name))
raise ManagerException('Incorrect provider config for ' +
provider_name)
self.processors = {}
for processor_name, processor_config in \
@ -129,8 +129,8 @@ class Manager(object):
_class = processor_config.pop('class')
except KeyError:
self.log.exception('Invalid processor class')
raise ManagerException('Processor {} is missing class'
.format(processor_name))
raise ManagerException(f'Processor {processor_name} is '
'missing class')
_class = self._get_named_class('processor', _class)
kwargs = self._build_kwargs(processor_config)
try:
@ -138,8 +138,8 @@ class Manager(object):
**kwargs)
except TypeError:
self.log.exception('Invalid processor config')
raise ManagerException('Incorrect processor config for {}'
.format(processor_name))
raise ManagerException('Incorrect processor config for ' +
processor_name)
zone_tree = {}
# sort by reversed strings so that parent zones always come first
@ -173,8 +173,8 @@ class Manager(object):
_class = plan_output_config.pop('class')
except KeyError:
self.log.exception('Invalid plan_output class')
raise ManagerException('plan_output {} is missing class'
.format(plan_output_name))
raise ManagerException(f'plan_output {plan_output_name} is '
'missing class')
_class = self._get_named_class('plan_output', _class)
kwargs = self._build_kwargs(plan_output_config)
try:
@ -182,8 +182,8 @@ class Manager(object):
_class(plan_output_name, **kwargs)
except TypeError:
self.log.exception('Invalid plan_output config')
raise ManagerException('Incorrect plan_output config for {}'
.format(plan_output_name))
raise ManagerException('Incorrect plan_output config for ' +
plan_output_name)
def _get_named_class(self, _type, _class):
try:
@ -192,15 +192,13 @@ class Manager(object):
except (ImportError, ValueError):
self.log.exception('_get_{}_class: Unable to import '
'module %s', _class)
raise ManagerException('Unknown {} class: {}'
.format(_type, _class))
raise ManagerException(f'Unknown {_type} class: {_class}')
try:
return getattr(module, class_name)
except AttributeError:
self.log.exception('_get_{}_class: Unable to get class %s '
'from module %s', class_name, module)
raise ManagerException('Unknown {} class: {}'
.format(_type, _class))
raise ManagerException(f'Unknown {_type} class: {_class}')
def _build_kwargs(self, source):
# Build up the arguments we need to pass to the provider
@ -214,8 +212,7 @@ class Manager(object):
except KeyError:
self.log.exception('Invalid provider config')
raise ManagerException('Incorrect provider config, '
'missing env var {}'
.format(env_var))
'missing env var ' + env_var)
except AttributeError:
pass
kwargs[k] = v
@ -279,7 +276,7 @@ class Manager(object):
meta = Record.new(zone, 'octodns-meta', {
'type': 'TXT',
'ttl': 60,
'value': 'provider={}'.format(target.id)
'value': f'provider={target.id}',
})
zone.add_record(meta, replace=True)
try:
@ -322,19 +319,19 @@ class Manager(object):
# Check that the source zone is defined.
if source_zone not in self.config['zones']:
self.log.error('Invalid alias zone {}, target {} does '
'not exist'.format(zone_name, source_zone))
raise ManagerException('Invalid alias zone {}: '
'source zone {} does not exist'
.format(zone_name, source_zone))
self.log.error(f'Invalid alias zone {zone_name}, '
f'target {source_zone} does not exist')
raise ManagerException(f'Invalid alias zone {zone_name}: '
f'source zone {source_zone} does '
'not exist')
# Check that the source zone is not an alias zone itself.
if 'alias' in self.config['zones'][source_zone]:
self.log.error('Invalid alias zone {}, target {} is an '
'alias zone'.format(zone_name, source_zone))
raise ManagerException('Invalid alias zone {}: source '
'zone {} is an alias zone'
.format(zone_name, source_zone))
self.log.error(f'Invalid alias zone {zone_name}, '
f'target {source_zone} is an alias zone')
raise ManagerException(f'Invalid alias zone {zone_name}: '
f'source zone {source_zone} is an '
'alias zone')
aliased_zones[zone_name] = source_zone
continue
@ -343,14 +340,12 @@ class Manager(object):
try:
sources = config['sources']
except KeyError:
raise ManagerException('Zone {} is missing sources'
.format(zone_name))
raise ManagerException(f'Zone {zone_name} is missing sources')
try:
targets = config['targets']
except KeyError:
raise ManagerException('Zone {} is missing targets'
.format(zone_name))
raise ManagerException(f'Zone {zone_name} is missing targets')
processors = config.get('processors', [])
@ -377,8 +372,8 @@ class Manager(object):
collected.append(self.processors[processor])
processors = collected
except KeyError:
raise ManagerException('Zone {}, unknown processor: {}'
.format(zone_name, processor))
raise ManagerException(f'Zone {zone_name}, unknown '
f'processor: {processor}')
try:
# rather than using a list comprehension, we break this loop
@ -389,21 +384,21 @@ class Manager(object):
collected.append(self.providers[source])
sources = collected
except KeyError:
raise ManagerException('Zone {}, unknown source: {}'
.format(zone_name, source))
raise ManagerException(f'Zone {zone_name}, unknown '
f'source: {source}')
try:
trgs = []
for target in targets:
trg = self.providers[target]
if not isinstance(trg, BaseProvider):
raise ManagerException('{} - "{}" does not support '
'targeting'.format(trg, target))
raise ManagerException(f'{trg} - "{target}" does not '
'support targeting')
trgs.append(trg)
targets = trgs
except KeyError:
raise ManagerException('Zone {}, unknown target: {}'
.format(zone_name, target))
raise ManagerException(f'Zone {zone_name}, unknown '
f'target: {target}')
futures.append(self._executor.submit(self._populate_and_plan,
zone_name, processors,
@ -427,9 +422,9 @@ class Manager(object):
try:
desired_config = desired[zone_source]
except KeyError:
raise ManagerException('Zone {} cannot be sync without zone '
'{} sinced it is aliased'
.format(zone_name, zone_source))
raise ManagerException(f'Zone {zone_name} cannot be sync '
f'without zone {zone_source} sinced '
'it is aliased')
futures.append(self._executor.submit(
self._populate_and_plan,
zone_name,
@ -488,7 +483,7 @@ class Manager(object):
a = [self.providers[source] for source in a]
b = [self.providers[source] for source in b]
except KeyError as e:
raise ManagerException('Unknown source: {}'.format(e.args[0]))
raise ManagerException(f'Unknown source: {e.args[0]}')
za = self.get_zone(zone)
for source in a:
@ -513,7 +508,7 @@ class Manager(object):
try:
sources = [self.providers[s] for s in sources]
except KeyError as e:
raise ManagerException('Unknown source: {}'.format(e.args[0]))
raise ManagerException(f'Unknown source: {e.args[0]}')
clz = YamlProvider
if split:
@ -537,15 +532,15 @@ class Manager(object):
if source_zone:
if source_zone not in self.config['zones']:
self.log.exception('Invalid alias zone')
raise ManagerException('Invalid alias zone {}: '
'source zone {} does not exist'
.format(zone_name, source_zone))
raise ManagerException(f'Invalid alias zone {zone_name}: '
f'source zone {source_zone} does '
'not exist')
if 'alias' in self.config['zones'][source_zone]:
self.log.exception('Invalid alias zone')
raise ManagerException('Invalid alias zone {}: '
'source zone {} is an alias zone'
.format(zone_name, source_zone))
raise ManagerException(f'Invalid alias zone {zone_name}: '
'source zone {source_zone} is an '
'alias zone')
# this is just here to satisfy coverage, see
# https://github.com/nedbat/coveragepy/issues/198
@ -556,8 +551,7 @@ class Manager(object):
try:
sources = config['sources']
except KeyError:
raise ManagerException('Zone {} is missing sources'
.format(zone_name))
raise ManagerException(f'Zone {zone_name} is missing sources')
try:
# rather than using a list comprehension, we break this
@ -568,8 +562,8 @@ class Manager(object):
collected.append(self.providers[source])
sources = collected
except KeyError:
raise ManagerException('Zone {}, unknown source: {}'
.format(zone_name, source))
raise ManagerException(f'Zone {zone_name}, unknown source: ' +
source)
for source in sources:
if isinstance(source, YamlProvider):
@ -582,16 +576,16 @@ class Manager(object):
for processor in processors:
collected.append(self.processors[processor])
except KeyError:
raise ManagerException('Zone {}, unknown processor: {}'
.format(zone_name, processor))
raise ManagerException(f'Zone {zone_name}, unknown '
f'processor: {processor}')
def get_zone(self, zone_name):
if not zone_name[-1] == '.':
raise ManagerException('Invalid zone name {}, missing ending dot'
.format(zone_name))
raise ManagerException(f'Invalid zone name {zone_name}, missing '
'ending dot')
for name, config in self.config['zones'].items():
if name == zone_name:
return Zone(name, self.configured_sub_zones(name))
raise ManagerException('Unknown zone name {}'.format(zone_name))
raise ManagerException(f'Unknown zone name {zone_name}')

+ 2
- 3
octodns/processor/ownership.py View File

@ -29,10 +29,9 @@ class OwnershipProcessor(BaseProcessor):
# Then create and add an ownership TXT for each of them
record_name = record.name.replace('*', '_wildcard')
if record.name:
name = '{}.{}.{}'.format(self.txt_name, record._type,
record_name)
name = f'{self.txt_name}.{record._type}.{record_name}'
else:
name = '{}.{}'.format(self.txt_name, record._type)
name = f'{self.txt_name}.{record._type}'
txt = Record.new(desired, name, {
'type': 'TXT',
'ttl': 60,


+ 37
- 38
octodns/provider/azuredns.py View File

@ -116,12 +116,12 @@ class _AzureRecord(object):
return
# Refer to function docstring for key_name and class_name.
key_name = '{}_records'.format(self.record_type).lower()
key_name = f'{self.record_type}_records'.lower()
if record._type == 'CNAME':
key_name = key_name[:-1]
azure_class = self.TYPE_MAP[self.record_type]
params_for = getattr(self, '_params_for_{}'.format(record._type))
params_for = getattr(self, f'_params_for_{record._type}')
self.params = params_for(record.data, key_name, azure_class)
self.params['ttl'] = record.ttl
@ -228,7 +228,7 @@ class _AzureRecord(object):
'''
def key_dict(d):
return sum([hash('{}:{}'.format(k, v)) for k, v in d.items()])
return sum([hash(f'{k}:{v}') for k, v in d.items()])
def parse_dict(params):
vals = []
@ -272,18 +272,18 @@ def _root_traffic_manager_name(record):
# hoping that real life FQDNs won't have double hyphens
name = record.fqdn[:-1].replace('.', '--')
if record._type != 'CNAME':
name += '-{}'.format(record._type)
name += f'-{record._type}'
return name
def _rule_traffic_manager_name(pool, record):
prefix = _root_traffic_manager_name(record)
return '{}-rule-{}'.format(prefix, pool)
return f'{prefix}-rule-{pool}'
def _pool_traffic_manager_name(pool, record):
prefix = _root_traffic_manager_name(record)
return '{}-pool-{}'.format(prefix, pool)
return f'{prefix}-pool-{pool}'
def _get_monitor(record):
@ -314,14 +314,14 @@ def _check_valid_dynamic(record):
if defaults != vals:
# we don't yet support multi-value defaults, specifying all
# pool values allows for Traffic Manager profile optimization
msg = ('{} {}: Values of A/AAAA dynamic records must either '
'have a single value or contain all values from all '
'pools')
raise AzureException(msg.format(record.fqdn, record._type))
raise AzureException(f'{record.fqdn} {record._type}: Values '
'of A/AAAA dynamic records must either '
'have a single value or contain all '
'values from all pools')
elif typ != 'CNAME':
# dynamic records of unsupported type
msg = '{}: Dynamic records in Azure must be of type A/AAAA/CNAME'
raise AzureException(msg.format(record.fqdn))
raise AzureException(f'{record.fqdn}: Dynamic records in Azure must '
'be of type A/AAAA/CNAME')
def _profile_is_match(have, desired):
@ -331,7 +331,7 @@ def _profile_is_match(have, desired):
log = logging.getLogger('azuredns._profile_is_match').debug
def false(have, desired, name=None):
prefix = 'profile={}'.format(name) if name else ''
prefix = f'profile={name}' if name else ''
attr = have.__class__.__name__
log('%s have.%s = %s', prefix, attr, have)
log('%s desired.%s = %s', prefix, attr, desired)
@ -463,7 +463,7 @@ class AzureProvider(BaseProvider):
def __init__(self, id, client_id, key, directory_id, sub_id,
resource_group, *args, **kwargs):
self.log = logging.getLogger('AzureProvider[{}]'.format(id))
self.log = logging.getLogger(f'AzureProvider[{id}]')
self.log.debug('__init__: id=%s, client_id=%s, '
'key=***, directory_id:%s', id, client_id, directory_id)
super(AzureProvider, self).__init__(id, *args, **kwargs)
@ -635,7 +635,7 @@ class AzureProvider(BaseProvider):
record_name = azrecord.name if azrecord.name != '@' else ''
typ = _parse_azure_type(azrecord.type)
data_for = getattr(self, '_data_for_{}'.format(typ))
data_for = getattr(self, f'_data_for_{typ}')
data = data_for(azrecord)
data['type'] = typ
data['ttl'] = azrecord.ttl
@ -764,10 +764,10 @@ class AzureProvider(BaseProvider):
# in the list. Throw exception otherwise, which should not happen
# if the profile was generated by octoDNS.
if 'GEO-AS' not in geo_map:
msg = 'Profile={} for record {}: Middle East (GEO-ME) is ' \
'not supported by octoDNS. It needs to be either ' \
'paired with Asia (GEO-AS) or expanded into individual' \
'list of countries.'.format(name, fqdn)
msg = f'Profile={name} for record {fqdn}: Middle East ' \
'(GEO-ME) is not supported by octoDNS. It needs to be ' \
'either paired with Asia (GEO-AS) or expanded into ' \
'individual list of countries.'
raise AzureException(msg)
geo_map.remove('GEO-ME')
@ -786,7 +786,7 @@ class AzureProvider(BaseProvider):
# state
country, province = code.split('-', 1)
country = GeoCodes.country_to_code(country)
geos.append('{}-{}'.format(country, province))
geos.append(f'{country}-{province}')
elif code == 'WORLD':
geos.append(code)
else:
@ -927,17 +927,17 @@ class AzureProvider(BaseProvider):
if not ep.target:
continue
if ep.target in endpoints:
msg = '{} contains duplicate endpoint {}'
raise AzureException(msg.format(name, ep.target))
raise AzureException(f'{name} contains duplicate '
f'endpoint {ep.target}')
endpoints.add(ep.target)
if name in seen_profiles:
# exit if a possible collision is detected, even though
# we've tried to ensure unique mapping
msg = 'Collision in Traffic Manager names detected'
msg = '{}: {} and {} both want to use {}'.format(
msg, seen_profiles[name], record.fqdn, name)
raise AzureException(msg)
raise AzureException('Collision in Traffic Manager names '
f'detected: {seen_profiles[name]} '
f'and {record.fqdn} both want to '
f'use {name}')
else:
seen_profiles[name] = record.fqdn
@ -975,10 +975,9 @@ class AzureProvider(BaseProvider):
elif ep.target:
ep.type = endpoint_type_prefix + 'externalEndpoints'
else:
msg = ('Invalid endpoint {} in profile {}, needs to have ' +
'either target or target_resource_id').format(
ep.name, name)
raise AzureException(msg)
raise AzureException(f'Invalid endpoint {ep.name} in profile '
f'{name}, needs to have either target '
'or target_resource_id')
# build and return
return Profile(
@ -1019,7 +1018,7 @@ class AzureProvider(BaseProvider):
# Azure uses Australia/Pacific (AP) instead of Oceania
geo = 'AP'
geos.append('GEO-{}'.format(geo))
geos.append(f'GEO-{geo}')
return geos
@ -1033,7 +1032,7 @@ class AzureProvider(BaseProvider):
# strip trailing dot from CNAME value
if record._type == 'CNAME':
target = target[:-1]
ep_name = '{}--{}'.format(pool_name, target)
ep_name = f'{pool_name}--{target}'
# Endpoint names cannot have colons, drop them from IPv6 addresses
ep_name = ep_name.replace(':', '-')
if target in defaults:
@ -1250,8 +1249,8 @@ class AzureProvider(BaseProvider):
# match existing profiles with record's prefix
name = profile_id.split('/')[-1]
if name == tm_prefix or \
name.startswith('{}-pool-'.format(tm_prefix)) or \
name.startswith('{}-rule-'.format(tm_prefix)):
name.startswith(f'{tm_prefix}-pool-') or \
name.startswith(f'{tm_prefix}-rule-'):
profiles.add(name)
return profiles
@ -1306,7 +1305,7 @@ class AzureProvider(BaseProvider):
root_profile.endpoints = endpoints
self._sync_traffic_managers([root_profile])
self.log.debug('* Success Create: {}'.format(record))
self.log.debug('* Success Create: %s', record)
def _apply_Update(self, change):
'''A record from change must be created.
@ -1370,7 +1369,7 @@ class AzureProvider(BaseProvider):
# changed to a simple record
self._traffic_managers_gc(existing, set())
self.log.debug('* Success Update: {}'.format(new))
self.log.debug('* Success Update: %s', new)
def _apply_Delete(self, change):
'''A record from change must be deleted.
@ -1390,7 +1389,7 @@ class AzureProvider(BaseProvider):
if getattr(record, 'dynamic', False):
self._traffic_managers_gc(record, set())
self.log.debug('* Success Delete: {}'.format(record))
self.log.debug('* Success Delete: %s', record)
def _apply(self, plan):
'''Required function of manager.py to actually apply a record change.
@ -1423,4 +1422,4 @@ class AzureProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
if class_name != 'Delete':
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)

+ 7
- 10
octodns/provider/base.py View File

@ -53,15 +53,13 @@ class BaseProvider(BaseSource):
for record in desired.records:
if record._type not in self.SUPPORTS:
msg = '{} records not supported for {}'.format(record._type,
record.fqdn)
msg = f'{record._type} records not supported for {record.fqdn}'
fallback = 'omitting record'
self.supports_warn_or_except(msg, fallback)
desired.remove_record(record)
elif getattr(record, 'dynamic', False) and \
not self.SUPPORTS_DYNAMIC:
msg = 'dynamic records not supported for {}'\
.format(record.fqdn)
msg = f'dynamic records not supported for {record.fqdn}'
fallback = 'falling back to simple record'
self.supports_warn_or_except(msg, fallback)
record = record.copy()
@ -70,10 +68,9 @@ class BaseProvider(BaseSource):
elif record._type == 'PTR' and len(record.values) > 1 and \
not self.SUPPORTS_MUTLIVALUE_PTR:
# replace with a single-value copy
msg = 'multi-value PTR records not supported for {}' \
.format(record.fqdn)
fallback = 'falling back to single value, {}' \
.format(record.value)
msg = \
f'multi-value PTR records not supported for {record.fqdn}'
fallback = f'falling back to single value, {record.value}'
self.supports_warn_or_except(msg, fallback)
record = record.copy()
record.values = [record.value]
@ -98,8 +95,8 @@ class BaseProvider(BaseSource):
def supports_warn_or_except(self, msg, fallback):
if self.strict_supports:
raise SupportsException('{}: {}'.format(self.id, msg))
self.log.warning('{}; {}'.format(msg, fallback))
raise SupportsException(f'{self.id}: {msg}')
self.log.warning('%s; %s', msg, fallback)
def plan(self, desired, processors=[]):
self.log.info('plan: desired=%s', desired.name)


+ 59
- 48
octodns/provider/cloudflare.py View File

@ -86,7 +86,7 @@ class CloudflareProvider(BaseProvider):
def __init__(self, id, email=None, token=None, cdn=False, retry_count=4,
retry_period=300, zones_per_page=50, records_per_page=100,
*args, **kwargs):
self.log = getLogger('CloudflareProvider[{}]'.format(id))
self.log = getLogger(f'CloudflareProvider[{id}]')
self.log.debug('__init__: id=%s, email=%s, token=***, cdn=%s', id,
email, cdn)
super(CloudflareProvider, self).__init__(id, *args, **kwargs)
@ -101,7 +101,7 @@ class CloudflareProvider(BaseProvider):
# https://api.cloudflare.com/#getting-started-requests
# https://tools.ietf.org/html/rfc6750#section-2.1
sess.headers.update({
'Authorization': 'Bearer {}'.format(token),
'Authorization': f'Bearer {token}',
})
self.cdn = cdn
self.retry_count = retry_count
@ -130,7 +130,7 @@ class CloudflareProvider(BaseProvider):
def _request(self, method, path, params=None, data=None):
self.log.debug('_request: method=%s, path=%s', method, path)
url = 'https://api.cloudflare.com/client/v4{}'.format(path)
url = f'https://api.cloudflare.com/client/v4{path}'
resp = self._sess.request(method, url, params=params, json=data,
timeout=self.TIMEOUT)
self.log.debug('_request: status=%d', resp.status_code)
@ -168,7 +168,7 @@ class CloudflareProvider(BaseProvider):
else:
page = None
self._zones = {'{}.'.format(z['name']): z['id'] for z in zones}
self._zones = {f'{z["name"]}.': z['id'] for z in zones}
return self._zones
@ -184,7 +184,7 @@ class CloudflareProvider(BaseProvider):
return {
'ttl': self._ttl_data(records[0]['ttl']),
'type': _type,
'value': '{}.cdn.cloudflare.net.'.format(records[0]['name']),
'value': f'{records[0]["name"]}.cdn.cloudflare.net.',
}
def _data_for_multiple(self, _type, records):
@ -221,7 +221,7 @@ class CloudflareProvider(BaseProvider):
return {
'ttl': self._ttl_data(only['ttl']),
'type': _type,
'value': '{}.'.format(only['content'])
'value': f'{only["content"]}.'
}
_data_for_ALIAS = _data_for_CNAME
@ -256,7 +256,7 @@ class CloudflareProvider(BaseProvider):
for r in records:
values.append({
'preference': r['priority'],
'exchange': '{}.'.format(r['content']),
'exchange': f'{r["content"]}.',
})
return {
'ttl': self._ttl_data(records[0]['ttl']),
@ -268,13 +268,13 @@ class CloudflareProvider(BaseProvider):
return {
'ttl': self._ttl_data(records[0]['ttl']),
'type': _type,
'values': ['{}.'.format(r['content']) for r in records],
'values': [f'{r["content"]}.' for r in records],
}
def _data_for_SRV(self, _type, records):
values = []
for r in records:
target = ('{}.'.format(r['data']['target'])
target = (f'{r["data"]["target"]}.'
if r['data']['target'] != "." else ".")
values.append({
'priority': r['data']['priority'],
@ -311,7 +311,7 @@ class CloudflareProvider(BaseProvider):
return []
records = []
path = '/zones/{}/dns_records'.format(zone_id)
path = f'/zones/{zone_id}/dns_records'
page = 1
while page:
resp = self._try_request('GET', path, params={'page': page,
@ -323,7 +323,7 @@ class CloudflareProvider(BaseProvider):
else:
page = None
path = '/zones/{}/pagerules'.format(zone_id)
path = f'/zones/{zone_id}/pagerules'
resp = self._try_request('GET', path, params={'status': 'active'})
for r in resp['result']:
# assumption, base on API guide, will only contain 1 action
@ -343,7 +343,7 @@ class CloudflareProvider(BaseProvider):
if _type == 'CNAME' and name == '':
_type = 'ALIAS'
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data = data_for(_type, records)
record = Record.new(zone, name, data, source=self, lenient=lenient)
@ -563,11 +563,11 @@ class CloudflareProvider(BaseProvider):
_type = 'CNAME'
if _type == 'URLFWD':
contents_for = getattr(self, '_contents_for_{}'.format(_type))
contents_for = getattr(self, f'_contents_for_{_type}')
for content in contents_for(record):
yield content
else:
contents_for = getattr(self, '_contents_for_{}'.format(_type))
contents_for = getattr(self, f'_contents_for_{_type}')
for content in contents_for(record):
content.update({
'name': name,
@ -597,46 +597,58 @@ class CloudflareProvider(BaseProvider):
# AND... for URLFWD/Redirects additional adventures are created.
_type = data.get('type', 'URLFWD')
if _type == 'MX':
return '{priority} {content}'.format(**data)
priority = data['priority']
content = data['content']
return f'{priority} {content}'
elif _type == 'CAA':
data = data['data']
return '{flags} {tag} {value}'.format(**data)
flags = data['flags']
tag = data['tag']
value = data['value']
return f'{flags} {tag} {value}'
elif _type == 'SRV':
data = data['data']
return '{port} {priority} {target} {weight}'.format(**data)
port = data['port']
priority = data['priority']
target = data['target']
weight = data['weight']
return f'{port} {priority} {target} {weight}'
elif _type == 'LOC':
data = data['data']
loc = (
'{lat_degrees}',
'{lat_minutes}',
'{lat_seconds}',
'{lat_direction}',
'{long_degrees}',
'{long_minutes}',
'{long_seconds}',
'{long_direction}',
'{altitude}',
'{size}',
'{precision_horz}',
'{precision_vert}')
return ' '.join(loc).format(**data)
lat_degrees = data['lat_degrees']
lat_minutes = data['lat_minutes']
lat_seconds = data['lat_seconds']
lat_direction = data['lat_direction']
long_degrees = data['long_degrees']
long_minutes = data['long_minutes']
long_seconds = data['long_seconds']
long_direction = data['long_direction']
altitude = data['altitude']
size = data['size']
precision_horz = data['precision_horz']
precision_vert = data['precision_vert']
return f'{lat_degrees} {lat_minutes} {lat_seconds} ' \
f'{lat_direction} {long_degrees} {long_minutes} ' \
f'{long_seconds} {long_direction} {altitude} {size} ' \
f'{precision_horz} {precision_vert}'
elif _type == 'URLFWD':
uri = data['targets'][0]['constraint']['value']
uri = '//' + uri if not uri.startswith('http') else uri
parsed_uri = urlsplit(uri)
return '{name} {path} {url} {status_code}' \
.format(name=parsed_uri.netloc,
path=parsed_uri.path,
**data['actions'][0]['value'])
url = data['actions'][0]['value']['url']
status_code = data['actions'][0]['value']['status_code']
return f'{parsed_uri.netloc} {parsed_uri.path} {url} ' + \
f'{status_code}'
return data['content']
def _apply_Create(self, change):
new = change.new
zone_id = self.zones[new.zone.name]
if new._type == 'URLFWD':
path = '/zones/{}/pagerules'.format(zone_id)
path = f'/zones/{zone_id}/pagerules'
else:
path = '/zones/{}/dns_records'.format(zone_id)
path = f'/zones/{zone_id}/dns_records'
for content in self._gen_data(new):
self._try_request('POST', path, data=content)
@ -738,9 +750,9 @@ class CloudflareProvider(BaseProvider):
# Creates
if _type == 'URLFWD':
path = '/zones/{}/pagerules'.format(zone_id)
path = f'/zones/{zone_id}/pagerules'
else:
path = '/zones/{}/dns_records'.format(zone_id)
path = f'/zones/{zone_id}/dns_records'
for _, data in sorted(creates.items()):
self.log.debug('_apply_Update: creating %s', data)
self._try_request('POST', path, data=data)
@ -751,9 +763,9 @@ class CloudflareProvider(BaseProvider):
data = info['data']
old_data = info['old_data']
if _type == 'URLFWD':
path = '/zones/{}/pagerules/{}'.format(zone_id, record_id)
path = f'/zones/{zone_id}/pagerules/{record_id}'
else:
path = '/zones/{}/dns_records/{}'.format(zone_id, record_id)
path = f'/zones/{zone_id}/dns_records/{record_id}'
self.log.debug('_apply_Update: updating %s, %s -> %s',
record_id, data, old_data)
self._try_request('PUT', path, data=data)
@ -763,9 +775,9 @@ class CloudflareProvider(BaseProvider):
record_id = info['record_id']
old_data = info['data']
if _type == 'URLFWD':
path = '/zones/{}/pagerules/{}'.format(zone_id, record_id)
path = f'/zones/{zone_id}/pagerules/{record_id}'
else:
path = '/zones/{}/dns_records/{}'.format(zone_id, record_id)
path = f'/zones/{zone_id}/dns_records/{record_id}'
self.log.debug('_apply_Update: removing %s, %s', record_id,
old_data)
self._try_request('DELETE', path)
@ -786,14 +798,13 @@ class CloudflareProvider(BaseProvider):
zone_id = self.zones.get(existing.zone.name, False)
if existing_name == record_name and \
existing_type == record_type:
path = '/zones/{}/pagerules/{}' \
.format(zone_id, record['id'])
path = f'/zones/{zone_id}/pagerules/{record["id"]}'
self._try_request('DELETE', path)
else:
if existing_name == record['name'] and \
existing_type == record['type']:
path = '/zones/{}/dns_records/{}' \
.format(record['zone_id'], record['id'])
path = f'/zones/{record["zone_id"]}/dns_records/' \
f'{record["id"]}'
self._try_request('DELETE', path)
def _apply(self, plan):
@ -821,7 +832,7 @@ class CloudflareProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# clear the cache
self._zone_records.pop(name, None)


+ 15
- 22
octodns/provider/constellix.py View File

@ -27,9 +27,8 @@ class ConstellixClientException(ProviderException):
class ConstellixClientBadRequest(ConstellixClientException):
def __init__(self, resp):
errors = resp.json()['errors']
super(ConstellixClientBadRequest, self).__init__(
'\n - {}'.format('\n - '.join(errors)))
errors = '\n - '.join(resp.json()['errors'])
super(ConstellixClientBadRequest, self).__init__(f'\n - {errors}')
class ConstellixClientUnauthorized(ConstellixClientException):
@ -73,7 +72,7 @@ class ConstellixClient(object):
'x-cnsdns-requestDate': now
}
url = '{}{}'.format(self.BASE, path)
url = f'{self.BASE}{path}'
resp = self._sess.request(method, url, headers=headers,
params=params, json=data)
if resp.status_code == 400:
@ -94,7 +93,7 @@ class ConstellixClient(object):
resp = self._request('GET', '/domains').json()
zones += resp
self._domains = {'{}.'.format(z['name']): z['id'] for z in zones}
self._domains = {f'{z["name"]}.': z['id'] for z in zones}
return self._domains
@ -195,7 +194,7 @@ class ConstellixClient(object):
return pool
def pool_create(self, data):
path = '/pools/{}'.format(data.get('type'))
path = f'/pools/{data.get("type")}'
# This returns a list of items, we want the first one
response = self._request('POST', path, data=data).json()
@ -204,7 +203,7 @@ class ConstellixClient(object):
return response[0]
def pool_update(self, pool_id, data):
path = '/pools/{}/{}'.format(data.get('type'), pool_id)
path = f'/pools/{data.get("type")}/{pool_id}'
try:
self._request('PUT', path, data=data).json()
@ -358,17 +357,15 @@ class ConstellixProvider(BaseProvider):
if 'geoipCountries' in geofilter.keys():
for country_code in geofilter['geoipCountries']:
geos.append('{}-{}'.format(
country_alpha2_to_continent_code(country_code),
country_code
))
continent_code = \
country_alpha2_to_continent_code(country_code)
geos.append(f'{continent_code}-{country_code}')
if 'regions' in geofilter.keys():
for region in geofilter['regions']:
geos.append('{}-{}-{}'.format(
region['continentCode'],
region['countryCode'],
region['regionCode']))
geos.append(f'{region["continentCode"]}-'
f'{region["countryCode"]}-'
f'{region["regionCode"]}')
rules.append({
'pool': pool_name,
@ -625,12 +622,8 @@ class ConstellixProvider(BaseProvider):
values = pool.data.get('values')
# Make a pool name based on zone, record, type and name
generated_pool_name = '{}:{}:{}:{}'.format(
record.zone.name,
record.name,
record._type,
pool_name
)
generated_pool_name = \
f'{record.zone.name}:{record.name}:{record._type}:{pool_name}'
# OK, pool is valid, let's create it or update it
self.log.debug("Creating pool %s", generated_pool_name)
@ -726,7 +719,7 @@ class ConstellixProvider(BaseProvider):
def _apply_Create(self, change, domain_name):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
pools = self._handle_pools(new)
for params in params_for(new):


+ 14
- 18
octodns/provider/digitalocean.py View File

@ -35,11 +35,11 @@ class DigitalOceanClient(object):
def __init__(self, token):
sess = Session()
sess.headers.update({'Authorization': 'Bearer {}'.format(token)})
sess.headers.update({'Authorization': f'Bearer {token}'})
self._sess = sess
def _request(self, method, path, params=None, data=None):
url = '{}{}'.format(self.BASE, path)
url = f'{self.BASE}{path}'
resp = self._sess.request(method, url, params=params, json=data)
if resp.status_code == 401:
raise DigitalOceanClientUnauthorized()
@ -49,7 +49,7 @@ class DigitalOceanClient(object):
return resp
def domain(self, name):
path = '/domains/{}'.format(name)
path = f'/domains/{name}'
return self._request('GET', path).json()
def domain_create(self, name):
@ -64,7 +64,7 @@ class DigitalOceanClient(object):
self.record_delete(name, record['id'])
def records(self, zone_name):
path = '/domains/{}/records'.format(zone_name)
path = f'/domains/{zone_name}/records'
ret = []
page = 1
@ -95,7 +95,7 @@ class DigitalOceanClient(object):
return ret
def record_create(self, zone_name, params):
path = '/domains/{}/records'.format(zone_name)
path = f'/domains/{zone_name}/records'
# change empty name string to @, DO uses @ for apex record names
if params['name'] == '':
params['name'] = '@'
@ -103,7 +103,7 @@ class DigitalOceanClient(object):
self._request('POST', path, data=params)
def record_delete(self, zone_name, record_id):
path = '/domains/{}/records/{}'.format(zone_name, record_id)
path = f'/domains/{zone_name}/records/{record_id}'
self._request('DELETE', path)
@ -121,7 +121,7 @@ class DigitalOceanProvider(BaseProvider):
SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'TXT', 'SRV'))
def __init__(self, id, token, *args, **kwargs):
self.log = logging.getLogger('DigitalOceanProvider[{}]'.format(id))
self.log = logging.getLogger(f'DigitalOceanProvider[{id}]')
self.log.debug('__init__: id=%s, token=***', id)
super(DigitalOceanProvider, self).__init__(id, *args, **kwargs)
self._client = DigitalOceanClient(token)
@ -157,7 +157,7 @@ class DigitalOceanProvider(BaseProvider):
return {
'ttl': record['ttl'],
'type': _type,
'value': '{}.'.format(record['data'])
'value': f'{record["data"]}.'
}
def _data_for_MX(self, _type, records):
@ -165,7 +165,7 @@ class DigitalOceanProvider(BaseProvider):
for record in records:
values.append({
'preference': record['priority'],
'exchange': '{}.'.format(record['data'])
'exchange': f'{record["data"]}.'
})
return {
'ttl': records[0]['ttl'],
@ -176,8 +176,7 @@ class DigitalOceanProvider(BaseProvider):
def _data_for_NS(self, _type, records):
values = []
for record in records:
data = '{}.'.format(record['data'])
values.append(data)
values.append(f'{record["data"]}.')
return {
'ttl': records[0]['ttl'],
'type': _type,
@ -187,10 +186,7 @@ class DigitalOceanProvider(BaseProvider):
def _data_for_SRV(self, _type, records):
values = []
for record in records:
target = (
'{}.'.format(record['data'])
if record['data'] != "." else "."
)
target = f'{record["data"]}.' if record['data'] != "." else "."
values.append({
'port': record['port'],
'priority': record['priority'],
@ -237,7 +233,7 @@ class DigitalOceanProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -316,7 +312,7 @@ class DigitalOceanProvider(BaseProvider):
def _apply_Create(self, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self._client.record_create(new.zone.name[:-1], params)
@ -347,7 +343,7 @@ class DigitalOceanProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 20
- 24
octodns/provider/dnsimple.py View File

@ -35,7 +35,7 @@ class DnsimpleClient(object):
def __init__(self, token, account, sandbox):
self.account = account
sess = Session()
sess.headers.update({'Authorization': 'Bearer {}'.format(token)})
sess.headers.update({'Authorization': f'Bearer {token}'})
self._sess = sess
if sandbox:
self.base = 'https://api.sandbox.dnsimple.com/v2/'
@ -43,7 +43,7 @@ class DnsimpleClient(object):
self.base = 'https://api.dnsimple.com/v2/'
def _request(self, method, path, params=None, data=None):
url = '{}{}{}'.format(self.base, self.account, path)
url = f'{self.base}{self.account}{path}'
resp = self._sess.request(method, url, params=params, json=data)
if resp.status_code == 401:
raise DnsimpleClientUnauthorized()
@ -53,7 +53,7 @@ class DnsimpleClient(object):
return resp
def zone(self, name):
path = '/zones/{}'.format(name)
path = f'/zones/{name}'
return self._request('GET', path).json()
def domain_create(self, name):
@ -64,7 +64,7 @@ class DnsimpleClient(object):
page = 1
while True:
data = self._request('GET', '/zones/{}/records'.format(zone_name),
data = self._request('GET', f'/zones/{zone_name}/records',
{'page': page}).json()
ret += data['data']
pagination = data['pagination']
@ -75,11 +75,11 @@ class DnsimpleClient(object):
return ret
def record_create(self, zone_name, params):
path = '/zones/{}/records'.format(zone_name)
path = f'/zones/{zone_name}/records'
self._request('POST', path, data=params)
def record_delete(self, zone_name, record_id):
path = '/zones/{}/records/{}'.format(zone_name, record_id)
path = f'/zones/{zone_name}/records/{record_id}'
self._request('DELETE', path)
@ -102,7 +102,7 @@ class DnsimpleProvider(BaseProvider):
'PTR', 'SPF', 'SRV', 'SSHFP', 'TXT'))
def __init__(self, id, token, account, sandbox=False, *args, **kwargs):
self.log = logging.getLogger('DnsimpleProvider[{}]'.format(id))
self.log = logging.getLogger(f'DnsimpleProvider[{id}]')
self.log.debug('__init__: id=%s, token=***, account=%s', id, account)
super(DnsimpleProvider, self).__init__(id, *args, **kwargs)
self._client = DnsimpleClient(token, account, sandbox)
@ -148,7 +148,7 @@ class DnsimpleProvider(BaseProvider):
return {
'ttl': record['ttl'],
'type': _type,
'value': '{}.'.format(record['content'])
'value': f'{record["content"]}.'
}
_data_for_ALIAS = _data_for_CNAME
@ -158,7 +158,7 @@ class DnsimpleProvider(BaseProvider):
for record in records:
values.append({
'preference': record['priority'],
'exchange': '{}.'.format(record['content'])
'exchange': f'{record["content"]}.'
})
return {
'ttl': records[0]['ttl'],
@ -197,7 +197,7 @@ class DnsimpleProvider(BaseProvider):
for record in records:
content = record['content']
if content[-1] != '.':
content = '{}.'.format(content)
content = f'{content}.'
values.append(content)
return {
'ttl': records[0]['ttl'],
@ -230,7 +230,7 @@ class DnsimpleProvider(BaseProvider):
)
continue
target = '{}.'.format(target) if target != "." else "."
target = f'{target}.' if target != "." else "."
values.append({
'port': port,
@ -296,7 +296,7 @@ class DnsimpleProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -354,8 +354,7 @@ class DnsimpleProvider(BaseProvider):
def _params_for_CAA(self, record):
for value in record.values:
yield {
'content': '{} {} "{}"'.format(value.flags, value.tag,
value.value),
'content': f'{value.flags} {value.tag} "{value.value}"',
'name': record.name,
'ttl': record.ttl,
'type': record._type
@ -385,9 +384,8 @@ class DnsimpleProvider(BaseProvider):
def _params_for_NAPTR(self, record):
for value in record.values:
content = '{} {} "{}" "{}" "{}" {}' \
.format(value.order, value.preference, value.flags,
value.service, value.regexp, value.replacement)
content = f'{value.order} {value.preference} "{value.flags}" ' \
f'"{value.service}" "{value.preference}" {value.flags}'
yield {
'content': content,
'name': record.name,
@ -398,8 +396,7 @@ class DnsimpleProvider(BaseProvider):
def _params_for_SRV(self, record):
for value in record.values:
yield {
'content': '{} {} {}'.format(value.weight, value.port,
value.target),
'content': f'{value.weight} {value.port} {value.target}',
'name': record.name,
'priority': value.priority,
'ttl': record.ttl,
@ -409,9 +406,8 @@ class DnsimpleProvider(BaseProvider):
def _params_for_SSHFP(self, record):
for value in record.values:
yield {
'content': '{} {} {}'.format(value.algorithm,
value.fingerprint_type,
value.fingerprint),
'content': f'{value.algorithm} {value.fingerprint_type} '
f'{value.fingerprint}',
'name': record.name,
'ttl': record.ttl,
'type': record._type
@ -419,7 +415,7 @@ class DnsimpleProvider(BaseProvider):
def _apply_Create(self, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self._client.record_create(new.zone.name[:-1], params)
@ -450,7 +446,7 @@ class DnsimpleProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 13
- 14
octodns/provider/dnsmadeeasy.py View File

@ -24,9 +24,8 @@ class DnsMadeEasyClientException(ProviderException):
class DnsMadeEasyClientBadRequest(DnsMadeEasyClientException):
def __init__(self, resp):
errors = resp.json()['error']
super(DnsMadeEasyClientBadRequest, self).__init__(
'\n - {}'.format('\n - '.join(errors)))
errors = '\n - '.join(resp.json()['error'])
super(DnsMadeEasyClientBadRequest, self).__init__(f'\n - {errors}')
class DnsMadeEasyClientUnauthorized(DnsMadeEasyClientException):
@ -71,7 +70,7 @@ class DnsMadeEasyClient(object):
'x-dnsme-requestDate': now
}
url = '{}{}'.format(self._base, path)
url = f'{self._base}{path}'
resp = self._sess.request(method, url, headers=headers,
params=params, json=data)
if resp.status_code == 400:
@ -93,12 +92,12 @@ class DnsMadeEasyClient(object):
resp = self._request('GET', '/').json()
zones += resp['data']
self._domains = {'{}.'.format(z['name']): z['id'] for z in zones}
self._domains = {f'{z["name"]}.': z['id'] for z in zones}
return self._domains
def domain(self, name):
path = '/id/{}'.format(name)
path = f'/id/{name}'
return self._request('GET', path).json()
def domain_create(self, name):
@ -106,7 +105,7 @@ class DnsMadeEasyClient(object):
def records(self, zone_name):
zone_id = self.domains.get(zone_name, False)
path = '/{}/records'.format(zone_id)
path = f'/{zone_id}/records'
ret = []
# has pages in resp, do we need paging?
@ -124,13 +123,13 @@ class DnsMadeEasyClient(object):
if value == '':
record['value'] = zone_name
elif not value.endswith('.'):
record['value'] = '{}.{}'.format(value, zone_name)
record['value'] = f'{value}.{zone_name}'
return ret
def record_create(self, zone_name, params):
zone_id = self.domains.get(zone_name, False)
path = '/{}/records'.format(zone_id)
path = f'/{zone_id}/records'
# change ALIAS records to ANAME
if params['type'] == 'ALIAS':
@ -140,7 +139,7 @@ class DnsMadeEasyClient(object):
def record_delete(self, zone_name, record_id):
zone_id = self.domains.get(zone_name, False)
path = '/{}/records/{}'.format(zone_id, record_id)
path = f'/{zone_id}/records/{record_id}'
self._request('DELETE', path)
@ -165,7 +164,7 @@ class DnsMadeEasyProvider(BaseProvider):
def __init__(self, id, api_key, secret_key, sandbox=False,
ratelimit_delay=0.0, *args, **kwargs):
self.log = logging.getLogger('DnsMadeEasyProvider[{}]'.format(id))
self.log = logging.getLogger(f'DnsMadeEasyProvider[{id}]')
self.log.debug('__init__: id=%s, api_key=***, secret_key=***, '
'sandbox=%s', id, sandbox)
super(DnsMadeEasyProvider, self).__init__(id, *args, **kwargs)
@ -275,7 +274,7 @@ class DnsMadeEasyProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -385,7 +384,7 @@ class DnsMadeEasyProvider(BaseProvider):
def _apply_Create(self, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self._client.record_create(new.zone.name, params)
@ -416,7 +415,7 @@ class DnsMadeEasyProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 25
- 23
octodns/provider/dyn.py View File

@ -142,7 +142,7 @@ class _CachingDynZone(DynZone):
return None
# this value shouldn't really matter, it's not tied to
# whois or anything
hostname = 'hostmaster@{}'.format(zone_name[:-1])
hostname = f'hostmaster@{zone_name[:-1]}'
# Try again with the params necessary to create
dyn_zone = _CachingDynZone(zone_name, ttl=3600,
contact=hostname,
@ -247,7 +247,7 @@ class DynProvider(BaseProvider):
def __init__(self, id, customer, username, password,
traffic_directors_enabled=False, *args, **kwargs):
self.log = getLogger('DynProvider[{}]'.format(id))
self.log = getLogger(f'DynProvider[{id}]')
self.log.debug('__init__: id=%s, customer=%s, username=%s, '
'password=***, traffic_directors_enabled=%s', id,
customer, username, traffic_directors_enabled)
@ -429,7 +429,7 @@ class DynProvider(BaseProvider):
# problems indicate a malformed ruleset, ignore it
continue
if ruleset.label.startswith('default:'):
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data.update(data_for(_type, record_set.records))
else:
# We've stored the geo in label
@ -465,8 +465,8 @@ class DynProvider(BaseProvider):
default = {}
pools = {}
data_for = getattr(self, '_data_for_{}'.format(_type))
value_for = getattr(self, '_value_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
value_for = getattr(self, f'_value_for_{_type}')
# Build the list of pools, we can't just read them off of rules b/c we
# won't see unused pools there. If/when we dis-allow unused pools we
@ -626,7 +626,7 @@ class DynProvider(BaseProvider):
for fqdn, types in self.traffic_directors.items():
for _type, td in types.items():
# Does this TD belong to the current zone
td_zone = '{}.'.format(td.nodes[0]['zone'])
td_zone = f'{td.nodes[0]["zone"]}.'
if td_zone != zone.name:
# Doesn't belong to the current zone, skip it
continue
@ -676,7 +676,7 @@ class DynProvider(BaseProvider):
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data = data_for(_type, records)
record = Record.new(zone, name, data, source=self,
lenient=lenient)
@ -697,7 +697,7 @@ class DynProvider(BaseProvider):
if record in changed or not getattr(record, 'geo', False):
# Already changed, or no geo, no need to check it
continue
label = '{}:{}'.format(record.fqdn, record._type)
label = f'{record.fqdn}:{record._type}'
try:
monitor = self.traffic_director_monitors[label]
except KeyError:
@ -812,7 +812,7 @@ class DynProvider(BaseProvider):
def _traffic_director_monitor(self, record):
fqdn = record.fqdn
label = '{}:{}'.format(fqdn, record._type)
label = f'{fqdn}:{record._type}'
try:
try:
monitor = self.traffic_director_monitors[label]
@ -920,14 +920,14 @@ class DynProvider(BaseProvider):
continue
# And the (sorted) values must match once converted for comparison
# purposes
value_for = getattr(self, '_value_for_{}'.format(_type))
value_for = getattr(self, f'_value_for_{_type}')
record_values = [value_for(_type, r) for r in records]
if record_values == values:
# it's a match
return pool
# We don't have this pool and thus need to create it
records_for = getattr(self, '_dynamic_records_for_{}'.format(_type))
records_for = getattr(self, f'_dynamic_records_for_{_type}')
records = records_for(values, record_extras)
record_set = DSFRecordSet(_type, label, serve_count=1, records=records,
dsf_monitor_id=monitor_id)
@ -996,7 +996,7 @@ class DynProvider(BaseProvider):
self.log.debug('_mod_geo_rulesets: insert_at=%d', insert_at)
# add the default
label = 'default:{}'.format(uuid4().hex)
label = f'default:{uuid4().hex}'
ruleset = DSFRuleset(label, 'always', [])
ruleset.create(td, index=insert_at)
pool = self._find_or_create_geo_pool(td, pools, 'default', new._type,
@ -1026,7 +1026,7 @@ class DynProvider(BaseProvider):
'region': self.REGION_CODES[geo.continent_code]
}
label = '{}:{}'.format(geo.code, uuid4().hex)
label = f'{geo.code}:{uuid4().hex}'
ruleset = DSFRuleset(label, 'geoip', [], {
'geoip': criteria
})
@ -1068,7 +1068,7 @@ class DynProvider(BaseProvider):
new = change.new
fqdn = new.fqdn
_type = new._type
label = '{}:{}'.format(fqdn, _type)
label = f'{fqdn}:{_type}'
node = DSFNode(new.zone.name, fqdn)
td = TrafficDirector(label, ttl=new.ttl, nodes=[node], publish='Y')
self.log.debug('_mod_geo_Create: td=%s', td.service_id)
@ -1158,7 +1158,7 @@ class DynProvider(BaseProvider):
self.log.debug('_mod_dynamic_rulesets: insert_at=%d', insert_at)
# Add the base record values as the ultimate/unhealthchecked default
label = 'default:{}'.format(uuid4().hex)
label = f'default:{uuid4().hex}'
ruleset = DSFRuleset(label, 'always', [])
ruleset.create(td, index=insert_at)
# If/when we go beyond A, AAAA, and CNAME this will have to get
@ -1224,7 +1224,7 @@ class DynProvider(BaseProvider):
criteria['geoip']['region'] \
.append(self.REGION_CODES[geo['continent_code']])
label = '{}:{}'.format(rule_num, uuid4().hex)
label = f'{rule_num}:{uuid4().hex}'
ruleset = DSFRuleset(label, criteria_type, [], criteria)
# Something you have to call create others the constructor does it
ruleset.create(td, index=insert_at)
@ -1277,7 +1277,7 @@ class DynProvider(BaseProvider):
fqdn = new.fqdn
_type = new._type
# Create a new traffic director
label = '{}:{}'.format(fqdn, _type)
label = f'{fqdn}:{_type}'
node = DSFNode(new.zone.name, fqdn)
td = TrafficDirector(label, ttl=new.ttl, nodes=[node], publish='Y')
self.log.debug('_mod_dynamic_Create: td=%s', td.service_id)
@ -1335,14 +1335,14 @@ class DynProvider(BaseProvider):
def _mod_Create(self, dyn_zone, change):
new = change.new
kwargs_for = getattr(self, '_kwargs_for_{}'.format(new._type))
kwargs_for = getattr(self, f'_kwargs_for_{new._type}')
for kwargs in kwargs_for(new):
dyn_zone.add_record(new.name, new._type, **kwargs)
def _mod_Delete(self, dyn_zone, change):
existing = change.existing
if existing.name:
target = '{}.{}'.format(existing.name, existing.zone.name[:-1])
target = f'{existing.name}.{existing.zone.name[:-1]}'
else:
target = existing.zone.name[:-1]
_type = self.TYPE_TO_RECORDS[existing._type]
@ -1361,12 +1361,13 @@ class DynProvider(BaseProvider):
# we only mess with changes that have geo info somewhere
if getattr(c.new, 'dynamic', False) or getattr(c.existing,
'dynamic', False):
mod = getattr(self, '_mod_dynamic_{}'
.format(c.__class__.__name__))
klass = c.__class__.__name__
mod = getattr(self, f'_mod_dynamic_{klass}')
mod(dyn_zone, c)
elif getattr(c.new, 'geo', False) or getattr(c.existing, 'geo',
False):
mod = getattr(self, '_mod_geo_{}'.format(c.__class__.__name__))
klass = c.__class__.__name__
mod = getattr(self, f'_mod_geo_{klass}')
mod(dyn_zone, c)
else:
unhandled_changes.append(c)
@ -1376,7 +1377,8 @@ class DynProvider(BaseProvider):
def _apply_regular(self, desired, changes, dyn_zone):
self.log.debug('_apply_regular: zone=%s', desired.name)
for c in changes:
mod = getattr(self, '_mod_{}'.format(c.__class__.__name__))
klass = c.__class__.__name__
mod = getattr(self, f'_mod_{klass}')
mod(dyn_zone, c)
# TODO: detect "extra" changes when monitors are out of date or failover


+ 24
- 29
octodns/provider/easydns.py View File

@ -50,22 +50,22 @@ class EasyDNSClient(object):
def __init__(self, token, api_key, currency, portfolio, sandbox,
domain_create_sleep):
self.log = logging.getLogger('EasyDNSProvider[{}]'.format(id))
self.log = logging.getLogger(f'EasyDNSProvider[{id}]')
self.default_currency = currency
self.domain_portfolio = portfolio
self.domain_create_sleep = domain_create_sleep
auth_key = '{}:{}'.format(token, api_key)
auth_key = f'{token}:{api_key}'
auth_key = base64.b64encode(auth_key.encode("utf-8"))
auth_key = auth_key.decode('utf-8')
self.base_path = self.SANDBOX if sandbox else self.LIVE
sess = Session()
sess.headers.update({'Authorization': 'Basic {}'
.format(auth_key.decode('utf-8'))})
sess.headers.update({'Authorization': f'Basic {auth_key}'})
sess.headers.update({'accept': 'application/json'})
self._sess = sess
def _request(self, method, path, params=None, data=None):
url = '{}{}'.format(self.base_path, path)
url = f'{self.base_path}{path}'
resp = self._sess.request(method, url, params=params, json=data)
if resp.status_code == 400:
self.log.debug('Response code 400, path=%s', path)
@ -80,14 +80,14 @@ class EasyDNSClient(object):
return resp
def domain(self, name):
path = '/domain/{}'.format(name)
path = f'/domain/{name}'
return self._request('GET', path).json()
def domain_create(self, name):
# EasyDNS allows for new domains to be created for the purpose of DNS
# only, or with domain registration. This function creates a DNS only
# record expectig the domain to be registered already
path = '/domains/add/{}'.format(name)
path = f'/domains/add/{name}'
domain_data = {'service': 'dns',
'term': 1,
'dns_only': 1,
@ -108,9 +108,9 @@ class EasyDNSClient(object):
def records(self, zone_name, raw=False):
if raw:
path = '/zones/records/all/{}'.format(zone_name)
path = f'/zones/records/all/{zone_name}'
else:
path = '/zones/records/parsed/{}'.format(zone_name)
path = f'/zones/records/parsed/{zone_name}'
ret = []
resp = self._request('GET', path).json()
@ -123,12 +123,12 @@ class EasyDNSClient(object):
# change any apex value to zone name
if record['rdata'] == '@':
record['rdata'] = '{}.'.format(zone_name)
record['rdata'] = f'{zone_name}.'
return ret
def record_create(self, zone_name, params):
path = '/zones/records/add/{}/{}'.format(zone_name, params['type'])
path = f'/zones/records/add/{zone_name}/{params["type"]}'
# change empty name string to @, EasyDNS uses @ for apex record names
params['host'] = params['name']
if params['host'] == '':
@ -136,7 +136,7 @@ class EasyDNSClient(object):
self._request('PUT', path, data=params)
def record_delete(self, zone_name, record_id):
path = '/zones/records/{}/{}'.format(zone_name, record_id)
path = f'/zones/records/{zone_name}/{record_id}'
self._request('DELETE', path)
@ -164,7 +164,7 @@ class EasyDNSProvider(BaseProvider):
def __init__(self, id, token, api_key, currency='CAD', portfolio='myport',
sandbox=False, domain_create_sleep=1, *args, **kwargs):
self.log = logging.getLogger('EasyDNSProvider[{}]'.format(id))
self.log = logging.getLogger(f'EasyDNSProvider[{id}]')
self.log.debug('__init__: id=%s, token=***', id)
super(EasyDNSProvider, self).__init__(id, *args, **kwargs)
self._client = EasyDNSClient(token, api_key, currency, portfolio,
@ -226,7 +226,7 @@ class EasyDNSProvider(BaseProvider):
return {
'ttl': record['ttl'],
'type': _type,
'value': '{}'.format(record['rdata'])
'value': str(record['rdata'])
}
def _data_for_MX(self, _type, records):
@ -234,7 +234,7 @@ class EasyDNSProvider(BaseProvider):
for record in records:
values.append({
'preference': record['prio'],
'exchange': '{}'.format(record['rdata'])
'exchange': str(record['rdata'])
})
return {
'ttl': records[0]['ttl'],
@ -245,7 +245,7 @@ class EasyDNSProvider(BaseProvider):
def _data_for_NS(self, _type, records):
values = []
for record in records:
data = '{}'.format(record['rdata'])
data = str(record['rdata'])
values.append(data)
return {
'ttl': records[0]['ttl'],
@ -318,7 +318,7 @@ class EasyDNSProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -344,8 +344,7 @@ class EasyDNSProvider(BaseProvider):
def _params_for_CAA(self, record):
for value in record.values:
yield {
'rdata': "{} {} {}".format(value.flags, value.tag,
value.value),
'rdata': f"{value.flags} {value.tag} {value.value}",
'name': record.name,
'ttl': record.ttl,
'type': record._type
@ -353,12 +352,8 @@ class EasyDNSProvider(BaseProvider):
def _params_for_NAPTR(self, record):
for value in record.values:
content = '{} {} "{}" "{}" "{}" {}'.format(value.order,
value.preference,
value.flags,
value.service,
value.regexp,
value.replacement)
content = f'{value.order} {value.preference} "{value.flags}" ' \
f'"{value.service}" "{value.regexp}" {value.replacement}'
yield {
'rdata': content,
'name': record.name,
@ -389,8 +384,8 @@ class EasyDNSProvider(BaseProvider):
def _params_for_SRV(self, record):
for value in record.values:
yield {
'rdata': "{} {} {} {}".format(value.priority, value.port,
value.weight, value.target),
'rdata': f"{value.priority} {value.port} {value.weight} "
f"{value.target}",
'name': record.name,
'ttl': record.ttl,
'type': record._type,
@ -407,7 +402,7 @@ class EasyDNSProvider(BaseProvider):
def _apply_Create(self, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self._client.record_create(new.zone.name[:-1], params)
@ -440,7 +435,7 @@ class EasyDNSProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 17
- 24
octodns/provider/edgedns.py View File

@ -59,34 +59,34 @@ class AkamaiClient(object):
return resp
def record_create(self, zone, name, record_type, content):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
path = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('POST', path, data=content)
return result
def record_delete(self, zone, name, record_type):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
path = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('DELETE', path)
return result
def record_replace(self, zone, name, record_type, content):
path = 'zones/{}/names/{}/types/{}'.format(zone, name, record_type)
path = f'zones/{zone}/names/{name}/types/{record_type}'
result = self._request('PUT', path, data=content)
return result
def zone_get(self, zone):
path = 'zones/{}'.format(zone)
path = f'zones/{zone}'
result = self._request('GET', path)
return result
def zone_create(self, contractId, params, gid=None):
path = 'zones?contractId={}'.format(contractId)
path = f'zones?contractId={contractId}'
if gid is not None:
path += '&gid={}'.format(gid)
path += f'&gid={gid}'
result = self._request('POST', path, data=params)
@ -104,7 +104,7 @@ class AkamaiClient(object):
'types': types
}
path = 'zones/{}/recordsets'.format(zone)
path = f'zones/{zone}/recordsets'
result = self._request('GET', path, params=params)
return result
@ -167,7 +167,7 @@ class AkamaiProvider(BaseProvider):
def __init__(self, id, client_secret, host, access_token, client_token,
contract_id=None, gid=None, *args, **kwargs):
self.log = getLogger('AkamaiProvider[{}]'.format(id))
self.log = getLogger(f'AkamaiProvider[{id}]')
self.log.debug('__init__: id=%s, ')
super(AkamaiProvider, self).__init__(id, *args, **kwargs)
@ -212,7 +212,7 @@ class AkamaiProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records[0]),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -239,7 +239,7 @@ class AkamaiProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)
@ -249,7 +249,7 @@ class AkamaiProvider(BaseProvider):
new = change.new
record_type = new._type
params_for = getattr(self, '_params_for_{}'.format(record_type))
params_for = getattr(self, f'_params_for_{record_type}')
values = self._get_values(new.data)
rdata = params_for(values)
@ -282,7 +282,7 @@ class AkamaiProvider(BaseProvider):
new = change.new
record_type = new._type
params_for = getattr(self, '_params_for_{}'.format(record_type))
params_for = getattr(self, f'_params_for_{record_type}')
values = self._get_values(new.data)
rdata = params_for(values)
@ -316,7 +316,7 @@ class AkamaiProvider(BaseProvider):
def _data_for_CNAME(self, _type, records):
value = records['rdata'][0]
if (value[-1] != '.'):
value = '{}.'.format(value)
value = f'{value}.'
return {
'ttl': records['ttl'],
@ -429,9 +429,7 @@ class AkamaiProvider(BaseProvider):
for r in values:
preference = r['preference']
exchange = r['exchange']
record = '{} {}'.format(preference, exchange)
rdata.append(record)
rdata.append(f'{preference} {exchange}')
return rdata
@ -445,9 +443,7 @@ class AkamaiProvider(BaseProvider):
srvc = "\"" + r['service'] + "\""
rgx = "\"" + r['regexp'] + "\""
rpl = r['replacement']
record = '{} {} {} {} {} {}'.format(ordr, prf, flg, srvc, rgx, rpl)
rdata.append(record)
rdata.append(f'{ordr} {prf} {flg} {srvc} {rgx} {rpl}')
return rdata
@ -467,9 +463,7 @@ class AkamaiProvider(BaseProvider):
weight = r['weight']
port = r['port']
target = r['target']
record = '{} {} {} {}'.format(priority, weight, port, target)
rdata.append(record)
rdata.append(f'{priority} {weight} {port} {target}')
return rdata
@ -480,8 +474,7 @@ class AkamaiProvider(BaseProvider):
fp_type = r['fingerprint_type']
fp = r['fingerprint']
record = '{} {} {}'.format(algorithm, fp_type, fp)
rdata.append(record)
rdata.append(f'{algorithm} {fp_type} {fp}')
return rdata


+ 10
- 11
octodns/provider/etc_hosts.py View File

@ -29,7 +29,7 @@ class EtcHostsProvider(BaseProvider):
SUPPORTS = set(('A', 'AAAA', 'ALIAS', 'CNAME'))
def __init__(self, id, directory, *args, **kwargs):
self.log = logging.getLogger('EtcHostsProvider[{}]'.format(id))
self.log = logging.getLogger(f'EtcHostsProvider[{id}]')
self.log.debug('__init__: id=%s, directory=%s', id, directory)
super(EtcHostsProvider, self).__init__(id, *args, **kwargs)
self.directory = directory
@ -67,24 +67,25 @@ class EtcHostsProvider(BaseProvider):
if not isdir(self.directory):
makedirs(self.directory)
filename = '{}hosts'.format(path.join(self.directory, desired.name))
filepath = path.join(self.directory, desired.name)
filename = f'{filepath}hosts'
self.log.info('_apply: filename=%s', filename)
with open(filename, 'w') as fh:
fh.write('##################################################\n')
fh.write('# octoDNS {} {}\n'.format(self.id, desired.name))
fh.write(f'# octoDNS {self.id} {desired.name}\n')
fh.write('##################################################\n\n')
if values:
fh.write('## A & AAAA\n\n')
for fqdn, value in sorted(values.items()):
if fqdn[0] == '*':
fh.write('# ')
fh.write('{}\t{}\n\n'.format(value, fqdn))
fh.write(f'{value}\t{fqdn}\n\n')
if cnames:
fh.write('\n## CNAME (mapped)\n\n')
for fqdn, value in sorted(cnames.items()):
# Print out a comment of the first level
fh.write('# {} -> {}\n'.format(fqdn, value))
fh.write(f'# {fqdn} -> {value}\n')
seen = set()
while True:
seen.add(value)
@ -92,7 +93,7 @@ class EtcHostsProvider(BaseProvider):
value = values[value]
# If we're here we've found the target, print it
# and break the loop
fh.write('{}\t{}\n'.format(value, fqdn))
fh.write(f'{value}\t{fqdn}\n')
break
except KeyError:
# Try and step down one level
@ -102,15 +103,13 @@ class EtcHostsProvider(BaseProvider):
if value:
if value in seen:
# We'd loop here, break it
fh.write('# {} -> {} **loop**\n'
.format(orig, value))
fh.write(f'# {orig} -> {value} **loop**\n')
break
else:
fh.write('# {} -> {}\n'
.format(orig, value))
fh.write(f'# {orig} -> {value}\n')
else:
# Don't have anywhere else to go
fh.write('# {} -> **unknown**\n'.format(orig))
fh.write(f'# {orig} -> **unknown**\n')
break
fh.write('\n')

+ 19
- 21
octodns/provider/gandi.py View File

@ -52,12 +52,12 @@ class GandiClient(object):
def __init__(self, token):
session = Session()
session.headers.update({'Authorization': 'Apikey {}'.format(token)})
session.headers.update({'Authorization': f'Apikey {token}'})
self._session = session
self.endpoint = 'https://api.gandi.net/v5'
def _request(self, method, path, params={}, data=None):
url = '{}{}'.format(self.endpoint, path)
url = f'{self.endpoint}{path}'
r = self._session.request(method, url, params=params, json=data)
if r.status_code == 400:
raise GandiClientBadRequest(r)
@ -71,8 +71,7 @@ class GandiClient(object):
return r
def zone(self, zone_name):
return self._request('GET', '/livedns/domains/{}'
.format(zone_name)).json()
return self._request('GET', f'/livedns/domains/{zone_name}').json()
def zone_create(self, zone_name):
return self._request('POST', '/livedns/domains', data={
@ -81,8 +80,8 @@ class GandiClient(object):
}).json()
def zone_records(self, zone_name):
records = self._request('GET', '/livedns/domains/{}/records'
.format(zone_name)).json()
records = self._request('GET',
f'/livedns/domains/{zone_name}/records').json()
for record in records:
if record['rrset_name'] == '@':
@ -93,18 +92,17 @@ class GandiClient(object):
'NS', 'SRV']:
for i, value in enumerate(record['rrset_values']):
if not value.endswith('.'):
record['rrset_values'][i] = '{}.{}.'.format(
value, zone_name)
record['rrset_values'][i] = f'{value}.{zone_name}.'
return records
def record_create(self, zone_name, data):
self._request('POST', '/livedns/domains/{}/records'.format(zone_name),
self._request('POST', f'/livedns/domains/{zone_name}/records',
data=data)
def record_delete(self, zone_name, record_name, record_type):
self._request('DELETE', '/livedns/domains/{}/records/{}/{}'
.format(zone_name, record_name, record_type))
self._request('DELETE', f'/livedns/domains/{zone_name}/records/'
f'{record_name}/{record_type}')
class GandiProvider(BaseProvider):
@ -123,7 +121,7 @@ class GandiProvider(BaseProvider):
'MX', 'NS', 'PTR', 'SPF', 'SRV', 'SSHFP', 'TXT']))
def __init__(self, id, token, *args, **kwargs):
self.log = logging.getLogger('GandiProvider[{}]'.format(id))
self.log = logging.getLogger(f'GandiProvider[{id}]')
self.log.debug('__init__: id=%s, token=***', id)
super(GandiProvider, self).__init__(id, *args, **kwargs)
self._client = GandiClient(token)
@ -246,7 +244,7 @@ class GandiProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -280,7 +278,7 @@ class GandiProvider(BaseProvider):
'rrset_name': self._record_name(record.name),
'rrset_ttl': record.ttl,
'rrset_type': record._type,
'rrset_values': ['{} {} "{}"'.format(v.flags, v.tag, v.value)
'rrset_values': [f'{v.flags} {v.tag} "{v.value}"'
for v in record.values]
}
@ -302,7 +300,7 @@ class GandiProvider(BaseProvider):
'rrset_name': self._record_name(record.name),
'rrset_ttl': record.ttl,
'rrset_type': record._type,
'rrset_values': ['{} {}'.format(v.preference, v.exchange)
'rrset_values': [f'{v.preference} {v.exchange}'
for v in record.values]
}
@ -311,8 +309,8 @@ class GandiProvider(BaseProvider):
'rrset_name': self._record_name(record.name),
'rrset_ttl': record.ttl,
'rrset_type': record._type,
'rrset_values': ['{} {} {} {}'.format(v.priority, v.weight, v.port,
v.target) for v in record.values]
'rrset_values': [f'{v.priority} {v.weight} {v.port} {v.target}'
for v in record.values]
}
def _params_for_SSHFP(self, record):
@ -320,13 +318,13 @@ class GandiProvider(BaseProvider):
'rrset_name': self._record_name(record.name),
'rrset_ttl': record.ttl,
'rrset_type': record._type,
'rrset_values': ['{} {} {}'.format(v.algorithm, v.fingerprint_type,
v.fingerprint) for v in record.values]
'rrset_values': [f'{v.algorithm} {v.fingerprint_type} '
f'{v.fingerprint}' for v in record.values]
}
def _apply_create(self, change):
new = change.new
data = getattr(self, '_params_for_{}'.format(new._type))(new)
data = getattr(self, f'_params_for_{new._type}')(new)
self._client.record_create(new.zone.name[:-1], data)
def _apply_update(self, change):
@ -373,7 +371,7 @@ class GandiProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name.lower()))(change)
getattr(self, f'_apply_{class_name.lower()}')(change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 13
- 21
octodns/provider/gcore.py View File

@ -55,12 +55,12 @@ class GCoreClient(object):
self._api_url = api_url
if token is not None and token_type is not None:
self._session.headers.update(
{"Authorization": "{} {}".format(token_type, token)}
{"Authorization": f"{token_type} {token}"}
)
elif login is not None and password is not None:
token = self._auth(auth_url, login, password)
self._session.headers.update(
{"Authorization": "Bearer {}".format(token)}
{"Authorization": f"Bearer {token}"}
)
else:
raise ValueError("either token or login & password must be set")
@ -107,15 +107,9 @@ class GCoreClient(object):
).json()
def zone_records(self, zone_name):
rrsets = self._request(
"GET",
"{}".format(
self._build_url(
self._api_url, self.ROOT_ZONES, zone_name, "rrsets"
)
),
params={"all": "true"},
).json()
url = self._build_url(self._api_url, self.ROOT_ZONES, zone_name,
"rrsets")
rrsets = self._request("GET", url, params={"all": "true"}).json()
records = rrsets["rrsets"]
return records
@ -174,7 +168,7 @@ class GCoreProvider(BaseProvider):
api_url = kwargs.pop("url", "https://dnsapi.gcorelabs.com/v2")
auth_url = kwargs.pop("auth_url", "https://api.gcdn.co")
self.records_per_response = kwargs.pop("records_per_response", 1)
self.log = logging.getLogger("GCoreProvider[{}]".format(id))
self.log = logging.getLogger(f"GCoreProvider[{id}]")
self.log.debug("__init__: id=%s", id)
super(GCoreProvider, self).__init__(id, *args, **kwargs)
self._client = GCoreClient(
@ -188,7 +182,7 @@ class GCoreProvider(BaseProvider):
)
def _add_dot_if_need(self, value):
return "{}.".format(value) if not value.endswith(".") else value
return f"{value}." if not value.endswith(".") else value
def _build_pools(self, record, default_pool_name, value_transform_fn):
defaults = []
@ -215,7 +209,7 @@ class GCoreProvider(BaseProvider):
[GeoCodes.country_to_code(cc.upper()) for cc in countries]
) | frozenset(cc.upper() for cc in continents)
if geo_set not in geo_sets:
geo_sets[geo_set] = "pool-{}".format(pool_idx)
geo_sets[geo_set] = f"pool-{pool_idx}"
pool_idx += 1
pools[geo_sets[geo_set]]["values"].append(value)
@ -247,9 +241,7 @@ class GCoreProvider(BaseProvider):
)
if len(pools) == 0:
raise RuntimeError(
"filter is enabled, but no pools where built for {}".format(
record
)
f"filter is enabled, but no pools where built for {record}"
)
# defaults can't be empty, so use first pool values
@ -402,7 +394,7 @@ class GCoreProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, record in types.items():
data_for = getattr(self, "_data_for_{}".format(_type))
data_for = getattr(self, f"_data_for_{_type}")
record = Record.new(
zone,
name,
@ -582,7 +574,7 @@ class GCoreProvider(BaseProvider):
def _apply_create(self, change):
self.log.info("creating: %s", change)
new = change.new
data = getattr(self, "_params_for_{}".format(new._type))(new)
data = getattr(self, f"_params_for_{new._type}")(new)
self._client.record_create(
new.zone.name[:-1], new.fqdn, new._type, data
)
@ -590,7 +582,7 @@ class GCoreProvider(BaseProvider):
def _apply_update(self, change):
self.log.info("updating: %s", change)
new = change.new
data = getattr(self, "_params_for_{}".format(new._type))(new)
data = getattr(self, f"_params_for_{new._type}")(new)
self._client.record_update(
new.zone.name[:-1], new.fqdn, new._type, data
)
@ -621,4 +613,4 @@ class GCoreProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, "_apply_{}".format(class_name.lower()))(change)
getattr(self, f"_apply_{class_name.lower()}")(change)

+ 17
- 23
octodns/provider/googlecloud.py View File

@ -54,7 +54,7 @@ class GoogleCloudProvider(BaseProvider):
self.gcloud_client = dns.Client(project=project)
# Logger
self.log = getLogger('GoogleCloudProvider[{}]'.format(id))
self.log = getLogger(f'GoogleCloudProvider[{id}]')
self.id = id
self._gcloud_zones = {}
@ -85,8 +85,7 @@ class GoogleCloudProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
_rrset_func = getattr(
self, '_rrset_for_{}'.format(change.record._type))
_rrset_func = getattr(self, f'_rrset_for_{change.record._type}')
if class_name == 'Create':
gcloud_changes.add_record_set(
@ -100,9 +99,9 @@ class GoogleCloudProvider(BaseProvider):
gcloud_changes.add_record_set(
_rrset_func(gcloud_zone, change.new))
else:
raise RuntimeError('Change type "{}" for change "{!s}" '
'is none of "Create", "Delete" or "Update'
.format(class_name, change))
msg = f'Change type "{class_name}" for change ' \
f'"{str(change)}" is none of "Create", "Delete" or "Update'
raise RuntimeError(msg)
gcloud_changes.create()
@ -116,8 +115,8 @@ class GoogleCloudProvider(BaseProvider):
time.sleep(self.CHANGE_LOOP_WAIT)
if gcloud_changes.status != 'done':
raise RuntimeError("Timeout reached after {} seconds".format(
i * self.CHANGE_LOOP_WAIT))
timeout = i * self.CHANGE_LOOP_WAIT
raise RuntimeError(f"Timeout reached after {timeout} seconds")
def _create_gcloud_zone(self, dns_name):
"""Creates a google cloud ManagedZone with dns_name, and zone named
@ -131,8 +130,7 @@ class GoogleCloudProvider(BaseProvider):
# Zone name must begin with a letter, end with a letter or digit,
# and only contain lowercase letters, digits or dashes,
# and be 63 characters or less
zone_name = 'zone-{}-{}'.format(
dns_name.replace('.', '-'), uuid4().hex)[:63]
zone_name = f'zone-{dns_name.replace(".", "-")}-{uuid4().hex}'[:63]
gcloud_zone = self.gcloud_client.zone(
name=zone_name,
@ -143,7 +141,7 @@ class GoogleCloudProvider(BaseProvider):
# add this new zone to the list of zones.
self._gcloud_zones[gcloud_zone.dns_name] = gcloud_zone
self.log.info("Created zone {}. Fqdn {}.".format(zone_name, dns_name))
self.log.info(f"Created zone {zone_name}. Fqdn {dns_name}.")
return gcloud_zone
@ -224,12 +222,12 @@ class GoogleCloudProvider(BaseProvider):
# which is also the way octodns likes it.
record_name = record_name[:-(len(zone.name) + 1)]
typ = gcloud_record.record_type.upper()
data = getattr(self, '_data_for_{}'.format(typ))
data = getattr(self, f'_data_for_{typ}')
data = data(gcloud_record)
data['type'] = typ
data['ttl'] = gcloud_record.ttl
self.log.debug('populate: adding record {} records: {!s}'
.format(record_name, data))
self.log.debug('populate: adding record %s records: %s',
record_name, data)
record = Record.new(zone, record_name, data, source=self)
zone.add_record(record, lenient=lenient)
@ -306,8 +304,7 @@ class GoogleCloudProvider(BaseProvider):
def _rrset_for_CAA(self, gcloud_zone, record):
return gcloud_zone.resource_record_set(
record.fqdn, record._type, record.ttl, [
'{} {} {}'.format(v.flags, v.tag, v.value)
for v in record.values])
f'{v.flags} {v.tag} {v.value}' for v in record.values])
def _rrset_for_CNAME(self, gcloud_zone, record):
return gcloud_zone.resource_record_set(
@ -316,15 +313,13 @@ class GoogleCloudProvider(BaseProvider):
def _rrset_for_MX(self, gcloud_zone, record):
return gcloud_zone.resource_record_set(
record.fqdn, record._type, record.ttl, [
'{} {}'.format(v.preference, v.exchange)
for v in record.values])
f'{v.preference} {v.exchange}' for v in record.values])
def _rrset_for_NAPTR(self, gcloud_zone, record):
return gcloud_zone.resource_record_set(
record.fqdn, record._type, record.ttl, [
'{} {} "{}" "{}" "{}" {}'.format(
v.order, v.preference, v.flags, v.service,
v.regexp, v.replacement) for v in record.values])
f'{v.order} {v.preference} "{v.flags}" "{v.service}" '
f'"{v.regexp}" {v.replacement}' for v in record.values])
_rrset_for_NS = _rrset_for_A
@ -337,8 +332,7 @@ class GoogleCloudProvider(BaseProvider):
def _rrset_for_SRV(self, gcloud_zone, record):
return gcloud_zone.resource_record_set(
record.fqdn, record._type, record.ttl, [
'{} {} {} {}'
.format(v.priority, v.weight, v.port, v.target)
f'{v.priority} {v.weight} {v.port} {v.target}'
for v in record.values])
_rrset_for_TXT = _rrset_for_SPF

+ 11
- 11
octodns/provider/hetzner.py View File

@ -39,7 +39,7 @@ class HetznerClient(object):
self._session = session
def _do(self, method, path, params=None, data=None):
url = '{}{}'.format(self.BASE_URL, path)
url = f'{self.BASE_URL}{path}'
response = self._session.request(method, url, params=params, json=data)
if response.status_code == 401:
raise HetznerClientUnauthorized()
@ -73,7 +73,7 @@ class HetznerClient(object):
self._do('POST', '/records', data=data)
def zone_record_delete(self, zone_id, record_id):
self._do('DELETE', '/records/{}'.format(record_id))
self._do('DELETE', f'/records/{record_id}')
class HetznerProvider(BaseProvider):
@ -90,7 +90,7 @@ class HetznerProvider(BaseProvider):
SUPPORTS = set(('A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'SRV', 'TXT'))
def __init__(self, id, token, *args, **kwargs):
self.log = logging.getLogger('HetznerProvider[{}]'.format(id))
self.log = logging.getLogger(f'HetznerProvider[{id}]')
self.log.debug('__init__: id=%s, token=***', id)
super(HetznerProvider, self).__init__(id, *args, **kwargs)
self._client = HetznerClient(token)
@ -102,7 +102,7 @@ class HetznerProvider(BaseProvider):
def _append_dot(self, value):
if value == '@' or value[-1] == '.':
return value
return '{}.'.format(value)
return f'{value}.'
def zone_metadata(self, zone_id=None, zone_name=None):
if zone_name is not None:
@ -232,7 +232,7 @@ class HetznerProvider(BaseProvider):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -256,7 +256,7 @@ class HetznerProvider(BaseProvider):
def _params_for_CAA(self, record):
for value in record.values:
data = '{} {} "{}"'.format(value.flags, value.tag, value.value)
data = f'{value.flags} {value.tag} "{value.value}"'
yield {
'value': data,
'name': record.name,
@ -276,7 +276,7 @@ class HetznerProvider(BaseProvider):
def _params_for_MX(self, record):
for value in record.values:
data = '{} {}'.format(value.preference, value.exchange)
data = f'{value.preference} {value.exchange}'
yield {
'value': data,
'name': record.name,
@ -288,8 +288,8 @@ class HetznerProvider(BaseProvider):
def _params_for_SRV(self, record):
for value in record.values:
data = '{} {} {} {}'.format(value.priority, value.weight,
value.port, value.target)
data = f'{value.priority} {value.weight} {value.port} ' \
f'{value.target}'
yield {
'value': data,
'name': record.name,
@ -301,7 +301,7 @@ class HetznerProvider(BaseProvider):
def _apply_Create(self, zone_id, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self._client.zone_record_create(zone_id, params['name'],
params['type'], params['value'],
@ -334,7 +334,7 @@ class HetznerProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(zone_id, change)
getattr(self, f'_apply_{class_name}')(zone_id, change)
# Clear out the cache if any
self._zone_records.pop(desired.name, None)

+ 30
- 36
octodns/provider/mythicbeasts.py View File

@ -38,9 +38,7 @@ def remove_trailing_dot(value):
class MythicBeastsUnauthorizedException(ProviderException):
def __init__(self, zone, *args):
self.zone = zone
self.message = 'Mythic Beasts unauthorized for zone: {}'.format(
self.zone
)
self.message = f'Mythic Beasts unauthorized for zone: {self.zone}'
super(MythicBeastsUnauthorizedException, self).__init__(
self.message, self.zone, *args)
@ -50,10 +48,8 @@ class MythicBeastsRecordException(ProviderException):
def __init__(self, zone, command, *args):
self.zone = zone
self.command = command
self.message = 'Mythic Beasts could not action command: {} {}'.format(
self.zone,
self.command,
)
self.message = 'Mythic Beasts could not action command: ' \
f'{self.zone} {self.command}'
super(MythicBeastsRecordException, self).__init__(
self.message, self.zone, self.command, *args)
@ -108,7 +104,7 @@ class MythicBeastsProvider(BaseProvider):
BASE = 'https://dnsapi.mythic-beasts.com/'
def __init__(self, identifier, passwords, *args, **kwargs):
self.log = getLogger('MythicBeastsProvider[{}]'.format(identifier))
self.log = getLogger(f'MythicBeastsProvider[{identifier}]')
assert isinstance(passwords, dict), 'Passwords must be a dictionary'
@ -147,11 +143,12 @@ class MythicBeastsProvider(BaseProvider):
return self._request('POST', self.BASE, data=data)
def records(self, zone):
assert zone in self._passwords, 'Missing password for domain: {}' \
.format(remove_trailing_dot(zone))
domain = remove_trailing_dot(zone)
assert zone in self._passwords, \
f'Missing password for domain: {domain}'
return self._post({
'domain': remove_trailing_dot(zone),
'domain': domain,
'password': self._passwords[zone],
'showall': 0,
'command': 'LIST',
@ -202,7 +199,7 @@ class MythicBeastsProvider(BaseProvider):
exchange = match.group('exchange')
if not exchange.endswith('.'):
exchange = '{}.{}'.format(exchange, data['zone'])
exchange = f'{exchange}.{data["zone"]}'
values.append({
'preference': match.group('preference'),
@ -220,7 +217,7 @@ class MythicBeastsProvider(BaseProvider):
ttl = data['raw_values'][0]['ttl']
value = data['raw_values'][0]['value']
if not value.endswith('.'):
value = '{}.{}'.format(value, data['zone'])
value = f'{value}.{data["zone"]}'
return MythicBeastsProvider._data_for_single(
_type,
@ -252,7 +249,7 @@ class MythicBeastsProvider(BaseProvider):
target = match.group('target')
if not target.endswith('.'):
target = '{}.{}'.format(target, data['zone'])
target = f'{target}.{data["zone"]}'
values.append({
'priority': match.group('priority'),
@ -344,7 +341,7 @@ class MythicBeastsProvider(BaseProvider):
_ttl = int(match.group('ttl'))
_value = match.group('value').strip()
if hasattr(self, '_data_for_{}'.format(_type)):
if hasattr(self, f'_data_for_{_type}'):
if _name not in data[_type]:
data[_type][_name] = {
'raw_values': [{'value': _value, 'ttl': _ttl}],
@ -361,7 +358,7 @@ class MythicBeastsProvider(BaseProvider):
for _type in data:
for _name in data[_type]:
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(
zone,
@ -391,7 +388,7 @@ class MythicBeastsProvider(BaseProvider):
else:
values = [record.value]
base = '{} {} {} {}'.format(action, hostname, ttl, _type)
base = f'{action} {hostname} {ttl} {_type}'
# Unescape TXT records
if _type == 'TXT':
@ -400,35 +397,32 @@ class MythicBeastsProvider(BaseProvider):
# Handle specific types or default
if _type == 'SSHFP':
data = values[0].data
commands.append('{} {} {} {}'.format(
base,
data['algorithm'],
data['fingerprint_type'],
data['fingerprint']
))
algorithm = data['algorithm']
fingerprint_type = data['fingerprint_type']
fingerprint = data['fingerprint']
commands.append(f'{base} {algorithm} {fingerprint_type} '
f'{fingerprint}')
elif _type == 'SRV':
for value in values:
data = value.data
commands.append('{} {} {} {} {}'.format(
base,
data['priority'],
data['weight'],
data['port'],
data['target']))
priority = data['priority']
weight = data['weight']
port = data['port']
target = data['target']
commands.append(f'{base} {priority} {weight} {port} {target}')
elif _type == 'MX':
for value in values:
data = value.data
commands.append('{} {} {}'.format(
base,
data['preference'],
data['exchange']))
preference = data['preference']
exchange = data['exchange']
commands.append(f'{base} {preference} {exchange}')
else:
if hasattr(self, '_data_for_{}'.format(_type)):
if hasattr(self, f'_data_for_{_type}'):
for value in values:
commands.append('{} {}'.format(base, value))
commands.append(f'{base} {value}')
else:
self.log.debug('skipping %s as not supported', _type)
@ -472,4 +466,4 @@ class MythicBeastsProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)

+ 24
- 29
octodns/provider/ns1.py View File

@ -22,7 +22,7 @@ from .base import BaseProvider
def _ensure_endswith_dot(string):
return string if string.endswith('.') else '{}.'.format(string)
return string if string.endswith('.') else f'{string}.'
class Ns1Exception(ProviderException):
@ -445,7 +445,7 @@ class Ns1Provider(BaseProvider):
def __init__(self, id, api_key, retry_count=4, monitor_regions=None,
parallelism=None, client_config=None, shared_notifylist=False,
*args, **kwargs):
self.log = getLogger('Ns1Provider[{}]'.format(id))
self.log = getLogger(f'Ns1Provider[{id}]')
self.log.debug('__init__: id=%s, api_key=***, retry_count=%d, '
'monitor_regions=%s, parallelism=%s, client_config=%s',
id, retry_count, monitor_regions, parallelism,
@ -480,8 +480,7 @@ class Ns1Provider(BaseProvider):
return filter_chain
def _encode_notes(self, data):
return ' '.join(['{}:{}'.format(k, v)
for k, v in sorted(data.items())])
return ' '.join([f'{k}:{v}' for k, v in sorted(data.items())])
def _parse_notes(self, note):
data = {}
@ -515,13 +514,13 @@ class Ns1Provider(BaseProvider):
ca_province = meta.get('ca_province', [])
for cntry in country:
con = country_alpha2_to_continent_code(cntry)
key = '{}-{}'.format(con, cntry)
key = f'{con}-{cntry}'
geo[key].extend(answer['answer'])
for state in us_state:
key = 'NA-US-{}'.format(state)
key = f'NA-US-{state}'
geo[key].extend(answer['answer'])
for province in ca_province:
key = 'NA-CA-{}'.format(province)
key = f'NA-CA-{province}'
geo[key].extend(answer['answer'])
for code in meta.get('iso_region_code', []):
key = code
@ -660,7 +659,7 @@ class Ns1Provider(BaseProvider):
if con in self._CONTINENT_TO_LIST_OF_COUNTRIES:
special_continents.setdefault(con, set()).add(country)
else:
geos.add('{}-{}'.format(con, country))
geos.add(f'{con}-{country}')
for continent, countries in special_continents.items():
if countries == self._CONTINENT_TO_LIST_OF_COUNTRIES[
@ -670,15 +669,15 @@ class Ns1Provider(BaseProvider):
else:
# Partial countries found, so just add them as-is to geos
for c in countries:
geos.add('{}-{}'.format(continent, c))
geos.add(f'{continent}-{c}')
# States and provinces are easy too,
# just assume NA-US or NA-CA
for state in meta.get('us_state', []):
geos.add('NA-US-{}'.format(state))
geos.add(f'NA-US-{state}')
for province in meta.get('ca_province', []):
geos.add('NA-CA-{}'.format(province))
geos.add(f'NA-CA-{province}')
if geos:
# There are geos, combine them with any existing geos for this
@ -892,7 +891,7 @@ class Ns1Provider(BaseProvider):
_type = record['type']
if _type not in self.SUPPORTS:
continue
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
name = zone.hostname_from_fqdn(record['domain'])
data = data_for(_type, record)
record = Record.new(zone, name, data, source=self, lenient=lenient)
@ -969,7 +968,7 @@ class Ns1Provider(BaseProvider):
def _feed_create(self, monitor):
monitor_id = monitor['id']
self.log.debug('_feed_create: monitor=%s', monitor_id)
name = '{} - {}'.format(monitor['name'], self._uuid()[:6])
name = f'{monitor["name"]} - {self._uuid()[:6]}'
# Create the data feed
config = {
@ -1037,7 +1036,7 @@ class Ns1Provider(BaseProvider):
},
'frequency': 60,
'job_type': 'tcp',
'name': '{} - {} - {}'.format(host, _type, value),
'name': f'{host} - {_type} - {value}',
'notes': self._encode_notes({
'host': host,
'type': _type,
@ -1055,8 +1054,8 @@ class Ns1Provider(BaseProvider):
# IF it's HTTP we need to send the request string
path = record.healthcheck_path
host = record.healthcheck_host(value=value)
request = r'GET {path} HTTP/1.0\r\nHost: {host}\r\n' \
r'User-agent: NS1\r\n\r\n'.format(path=path, host=host)
request = fr'GET {path} HTTP/1.0\r\nHost: {host}\r\n' \
r'User-agent: NS1\r\n\r\n'
ret['config']['send'] = request
# We'll also expect a HTTP response
ret['rules'] = [{
@ -1238,7 +1237,7 @@ class Ns1Provider(BaseProvider):
if georegion:
georegion_meta = dict(meta)
georegion_meta['georegion'] = sorted(georegion)
regions['{}__georegion'.format(pool_name)] = {
regions[f'{pool_name}__georegion'] = {
'meta': georegion_meta,
}
@ -1255,12 +1254,12 @@ class Ns1Provider(BaseProvider):
country_state_meta['us_state'] = sorted(us_state)
if ca_province:
country_state_meta['ca_province'] = sorted(ca_province)
regions['{}__country'.format(pool_name)] = {
regions[f'{pool_name}__country'] = {
'meta': country_state_meta,
}
elif not georegion:
# If there's no targeting it's a catchall
regions['{}__catchall'.format(pool_name)] = {
regions[f'{pool_name}__catchall'] = {
'meta': meta,
}
@ -1403,7 +1402,7 @@ class Ns1Provider(BaseProvider):
ns1_record['domain'],
ns1_record['type'])
if 'filters' in full_rec:
filter_key = '{}.'.format(ns1_record['domain'])
filter_key = f'{ns1_record["domain"]}.'
ns1_filters[filter_key] = full_rec['filters']
return ns1_filters
@ -1412,8 +1411,7 @@ class Ns1Provider(BaseProvider):
disabled_count = ['disabled' in f for f in filters].count(True)
if disabled_count and disabled_count != len(filters):
# Some filters have the disabled flag, and some don't. Disallow
exception_msg = 'Mixed disabled flag in filters for {}'.format(
domain)
exception_msg = f'Mixed disabled flag in filters for {domain}'
raise Ns1Exception(exception_msg)
return disabled_count == len(filters)
@ -1431,7 +1429,7 @@ class Ns1Provider(BaseProvider):
# Check if filters for existing domains need an update
# Needs an explicit check since there might be no change in the
# config at all. Filters however might still need an update
domain = '{}.{}'.format(record.name, record.zone.name)
domain = f'{record.name}.{record.zone.name}'
if domain in ns1_filters:
domain_filters = ns1_filters[domain]
if not self._disabled_flag_in_filters(domain_filters, domain):
@ -1463,8 +1461,7 @@ class Ns1Provider(BaseProvider):
zone = new.zone.name[:-1]
domain = new.fqdn[:-1]
_type = new._type
params, active_monitor_ids = \
getattr(self, '_params_for_{}'.format(_type))(new)
params, active_monitor_ids = getattr(self, f'_params_for_{_type}')(new)
self._client.records_create(zone, domain, _type, **params)
self._monitors_gc(new, active_monitor_ids)
@ -1473,8 +1470,7 @@ class Ns1Provider(BaseProvider):
zone = new.zone.name[:-1]
domain = new.fqdn[:-1]
_type = new._type
params, active_monitor_ids = \
getattr(self, '_params_for_{}'.format(_type))(new)
params, active_monitor_ids = getattr(self, f'_params_for_{_type}')(new)
self._client.records_update(zone, domain, _type, **params)
# If we're cleaning up we need to send in the old record since it'd
# have anything that needs cleaning up
@ -1518,5 +1514,4 @@ class Ns1Provider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(ns1_zone,
change)
getattr(self, f'_apply_{class_name}')(ns1_zone, change)

+ 20
- 30
octodns/provider/ovh.py View File

@ -45,7 +45,7 @@ class OvhProvider(BaseProvider):
def __init__(self, id, endpoint, application_key, application_secret,
consumer_key, *args, **kwargs):
self.log = logging.getLogger('OvhProvider[{}]'.format(id))
self.log = logging.getLogger(f'OvhProvider[{id}]')
self.log.debug('__init__: id=%s, endpoint=%s, application_key=%s, '
'application_secret=***, consumer_key=%s', id, endpoint,
application_key, consumer_key)
@ -81,7 +81,7 @@ class OvhProvider(BaseProvider):
self.log.warning('Not managed record of type %s, skip',
_type)
continue
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -98,15 +98,14 @@ class OvhProvider(BaseProvider):
len(changes))
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name).lower())(zone_name,
change)
getattr(self, f'_apply_{class_name}'.lower())(zone_name, change)
# We need to refresh the zone to really apply the changes
self._client.post('/domain/zone/{}/refresh'.format(zone_name))
self._client.post(f'/domain/zone/{zone_name}/refresh')
def _apply_create(self, zone_name, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self.create_record(zone_name, params)
@ -198,7 +197,7 @@ class OvhProvider(BaseProvider):
values.append({
'port': port,
'priority': priority,
'target': '{}.'.format(target),
'target': f'{target}.',
'weight': weight
})
return {
@ -264,8 +263,7 @@ class OvhProvider(BaseProvider):
def _params_for_CAA(record):
for value in record.values:
yield {
'target': '{} {} "{}"'.format(value.flags, value.tag,
value.value),
'target': f'{value.flags} {value.tag} "{value.value}"',
'subDomain': record.name,
'ttl': record.ttl,
'fieldType': record._type
@ -275,7 +273,7 @@ class OvhProvider(BaseProvider):
def _params_for_MX(record):
for value in record.values:
yield {
'target': '%d %s' % (value.preference, value.exchange),
'target': f'{value.preference:d} {value.exchange}',
'subDomain': record.name,
'ttl': record.ttl,
'fieldType': record._type
@ -284,9 +282,8 @@ class OvhProvider(BaseProvider):
@staticmethod
def _params_for_NAPTR(record):
for value in record.values:
content = '{} {} "{}" "{}" "{}" {}' \
.format(value.order, value.preference, value.flags,
value.service, value.regexp, value.replacement)
content = f'{value.order} {value.preference} "{value.flags}" ' \
f'"{value.service}" "{value.regexp}" {value.replacement}'
yield {
'target': content,
'subDomain': record.name,
@ -298,10 +295,8 @@ class OvhProvider(BaseProvider):
def _params_for_SRV(record):
for value in record.values:
yield {
'target': '{} {} {} {}'.format(value.priority,
value.weight,
value.port,
value.target),
'target': f'{value.priority} {value.weight} {value.port} '
f'{value.target}',
'subDomain': record.name,
'ttl': record.ttl,
'fieldType': record._type
@ -311,9 +306,8 @@ class OvhProvider(BaseProvider):
def _params_for_SSHFP(record):
for value in record.values:
yield {
'target': '{} {} {}'.format(value.algorithm,
value.fingerprint_type,
value.fingerprint),
'target': f'{value.algorithm} {value.fingerprint_type} '
f'{value.fingerprint}',
'subDomain': record.name,
'ttl': record.ttl,
'fieldType': record._type
@ -386,7 +380,7 @@ class OvhProvider(BaseProvider):
:param zone_name: Name of zone
:return: list of id's records
"""
records = self._client.get('/domain/zone/{}/record'.format(zone_name))
records = self._client.get(f'/domain/zone/{zone_name}/record')
return [self.get_record(zone_name, record_id) for record_id in records]
def get_record(self, zone_name, record_id):
@ -396,8 +390,7 @@ class OvhProvider(BaseProvider):
:param record_id: Id of the record
:return: Value of the record
"""
return self._client.get(
'/domain/zone/{}/record/{}'.format(zone_name, record_id))
return self._client.get(f'/domain/zone/{zone_name}/record/{record_id}')
def delete_records(self, zone_name, record_type, subdomain):
"""
@ -406,7 +399,7 @@ class OvhProvider(BaseProvider):
:param record_type: fieldType
:param subdomain: subDomain
"""
records = self._client.get('/domain/zone/{}/record'.format(zone_name),
records = self._client.get(f'/domain/zone/{zone_name}/record',
fieldType=record_type, subDomain=subdomain)
for record in records:
self.delete_record(zone_name, record)
@ -417,10 +410,8 @@ class OvhProvider(BaseProvider):
:param zone_name: Name of the zone
:param record_id: Id of the record
"""
self.log.debug('Delete record: zone: %s, id %s', zone_name,
record_id)
self._client.delete(
'/domain/zone/{}/record/{}'.format(zone_name, record_id))
self.log.debug('Delete record: zone: %s, id %s', zone_name, record_id)
self._client.delete(f'/domain/zone/{zone_name}/record/{record_id}')
def create_record(self, zone_name, params):
"""
@ -431,5 +422,4 @@ class OvhProvider(BaseProvider):
"""
self.log.debug('Create record: zone: %s, id %s', zone_name,
params)
return self._client.post('/domain/zone/{}/record'.format(zone_name),
**params)
return self._client.post(f'/domain/zone/{zone_name}/record', **params)

+ 22
- 19
octodns/provider/plan.py View File

@ -12,7 +12,12 @@ from six import StringIO, text_type
class UnsafePlan(Exception):
pass
def __init__(self, why, update_pcent, update_threshold, change_count,
existing_count):
msg = f'{why}, {update_pcent:.2f} is over {update_threshold:.2f} %' \
f'({change_count}/{existing_count})'
super(UnsafePlan, self).__init__(msg)
class Plan(object):
@ -66,25 +71,23 @@ class Plan(object):
delete_pcent = self.change_counts['Delete'] / existing_record_count
if update_pcent > self.update_pcent_threshold:
raise UnsafePlan('Too many updates, {:.2f} is over {:.2f} %'
'({}/{})'.format(
update_pcent * 100,
self.update_pcent_threshold * 100,
self.change_counts['Update'],
existing_record_count))
raise UnsafePlan('Too many updates', update_pcent * 100,
self.update_pcent_threshold * 100,
self.change_counts['Update'],
existing_record_count)
if delete_pcent > self.delete_pcent_threshold:
raise UnsafePlan('Too many deletes, {:.2f} is over {:.2f} %'
'({}/{})'.format(
delete_pcent * 100,
self.delete_pcent_threshold * 100,
self.change_counts['Delete'],
existing_record_count))
raise UnsafePlan('Too many deletes', delete_pcent * 100,
self.delete_pcent_threshold * 100,
self.change_counts['Delete'],
existing_record_count)
def __repr__(self):
return 'Creates={}, Updates={}, Deletes={}, Existing Records={}' \
.format(self.change_counts['Create'], self.change_counts['Update'],
self.change_counts['Delete'],
len(self.existing.records))
creates = self.change_counts['Create']
updates = self.change_counts['Update']
deletes = self.change_counts['Delete']
existing = len(self.existing.records)
return f'Creates={creates}, Updates={updates}, Deletes={deletes}, ' \
f'Existing Records={existing}'
class _PlanOutput(object):
@ -106,7 +109,7 @@ class PlanLogger(_PlanOutput):
'error': ERROR
}[level.lower()]
except (AttributeError, KeyError):
raise Exception('Unsupported level: {}'.format(level))
raise Exception(f'Unsupported level: {level}')
def run(self, log, plans, *args, **kwargs):
hr = '*************************************************************' \
@ -157,7 +160,7 @@ def _value_stringifier(record, sep):
values = [record.value]
for code, gv in sorted(getattr(record, 'geo', {}).items()):
vs = ', '.join([text_type(v) for v in gv.values])
values.append('{}: {}'.format(code, vs))
values.append(f'{code}: {vs}')
return sep.join(values)


+ 18
- 25
octodns/provider/powerdns.py View File

@ -38,8 +38,8 @@ class PowerDnsBaseProvider(BaseProvider):
def _request(self, method, path, data=None):
self.log.debug('_request: method=%s, path=%s', method, path)
url = '{}://{}:{}/api/v1/servers/localhost/{}' \
.format(self.scheme, self.host, self.port, path).rstrip("/")
url = f'{self.scheme}://{self.host}:{self.port}/api/v1/servers/' \
f'localhost/{path}'.rstrip('/')
# Strip trailing / from url.
resp = self._sess.request(method, url, json=data, timeout=self.timeout)
self.log.debug('_request: status=%d', resp.status_code)
@ -204,8 +204,7 @@ class PowerDnsBaseProvider(BaseProvider):
except HTTPError as e:
if e.response.status_code == 401:
# Nicer error message for auth problems
raise Exception('PowerDNS unauthorized host={}'
.format(self.host))
raise Exception(f'PowerDNS unauthorized host={self.host}')
raise
version = resp.json()['version']
@ -241,14 +240,13 @@ class PowerDnsBaseProvider(BaseProvider):
resp = None
try:
resp = self._get('zones/{}'.format(zone.name))
resp = self._get(f'zones/{zone.name}')
self.log.debug('populate: loaded')
except HTTPError as e:
error = self._get_error(e)
if e.response.status_code == 401:
# Nicer error message for auth problems
raise Exception('PowerDNS unauthorized host={}'
.format(self.host))
raise Exception(f'PowerDNS unauthorized host={self.host}')
elif e.response.status_code == 404 \
and self.check_status_not_found:
# 404 means powerdns doesn't know anything about the requested
@ -275,7 +273,7 @@ class PowerDnsBaseProvider(BaseProvider):
_type = rrset['type']
if _type == 'SOA':
continue
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record_name = zone.hostname_from_fqdn(rrset['name'])
record = Record.new(zone, record_name, data_for(rrset),
source=self, lenient=lenient)
@ -295,7 +293,7 @@ class PowerDnsBaseProvider(BaseProvider):
def _records_for_CAA(self, record):
return [{
'content': '{} {} "{}"'.format(v.flags, v.tag, v.value),
'content': f'{v.flags} {v.tag} "{v.value}"',
'disabled': False
} for v in record.values]
@ -307,7 +305,7 @@ class PowerDnsBaseProvider(BaseProvider):
_records_for_PTR = _records_for_single
def _records_for_quoted(self, record):
return [{'content': '"{}"'.format(v), 'disabled': False}
return [{'content': f'"{v}"', 'disabled': False}
for v in record.values]
_records_for_SPF = _records_for_quoted
@ -336,36 +334,32 @@ class PowerDnsBaseProvider(BaseProvider):
def _records_for_MX(self, record):
return [{
'content': '{} {}'.format(v.preference, v.exchange),
'content': f'{v.preference} {v.exchange}',
'disabled': False
} for v in record.values]
def _records_for_NAPTR(self, record):
return [{
'content': '{} {} "{}" "{}" "{}" {}'.format(v.order, v.preference,
v.flags, v.service,
v.regexp,
v.replacement),
'content': f'{v.order} {v.preference} "{v.flags}" "{v.service}" '
f'"{v.regexp}" {v.replacement}',
'disabled': False
} for v in record.values]
def _records_for_SSHFP(self, record):
return [{
'content': '{} {} {}'.format(v.algorithm, v.fingerprint_type,
v.fingerprint),
'content': f'{v.algorithm} {v.fingerprint_type} {v.fingerprint}',
'disabled': False
} for v in record.values]
def _records_for_SRV(self, record):
return [{
'content': '{} {} {} {}'.format(v.priority, v.weight, v.port,
v.target),
'content': f'{v.priority} {v.weight} {v.port} {v.target}',
'disabled': False
} for v in record.values]
def _mod_Create(self, change):
new = change.new
records_for = getattr(self, '_records_for_{}'.format(new._type))
records_for = getattr(self, f'_records_for_{new._type}')
return {
'name': new.fqdn,
'type': new._type,
@ -378,7 +372,7 @@ class PowerDnsBaseProvider(BaseProvider):
def _mod_Delete(self, change):
existing = change.existing
records_for = getattr(self, '_records_for_{}'.format(existing._type))
records_for = getattr(self, f'_records_for_{existing._type}')
return {
'name': existing.fqdn,
'type': existing._type,
@ -429,7 +423,7 @@ class PowerDnsBaseProvider(BaseProvider):
mods = []
for change in changes:
class_name = change.__class__.__name__
mods.append(getattr(self, '_mod_{}'.format(class_name))(change))
mods.append(getattr(self, f'_mod_{class_name}')(change))
# Ensure that any DELETE modifications always occur before any REPLACE
# modifications. This ensures that an A record can be replaced by a
@ -439,8 +433,7 @@ class PowerDnsBaseProvider(BaseProvider):
self.log.debug('_apply: sending change request')
try:
self._patch('zones/{}'.format(desired.name),
data={'rrsets': mods})
self._patch(f'zones/{desired.name}', data={'rrsets': mods})
self.log.debug('_apply: patched')
except HTTPError as e:
error = self._get_error(e)
@ -510,7 +503,7 @@ class PowerDnsProvider(PowerDnsBaseProvider):
def __init__(self, id, host, api_key, port=8081, nameserver_values=None,
nameserver_ttl=600,
*args, **kwargs):
self.log = logging.getLogger('PowerDnsProvider[{}]'.format(id))
self.log = logging.getLogger(f'PowerDnsProvider[{id}]')
self.log.debug('__init__: id=%s, host=%s, port=%d, '
'nameserver_values=%s, nameserver_ttl=%d',
id, host, port, nameserver_values, nameserver_ttl)


+ 10
- 14
octodns/provider/rackspace.py View File

@ -58,7 +58,7 @@ class RackspaceProvider(BaseProvider):
# The api key that grants access for that user (required)
api_key: api-key
'''
self.log = logging.getLogger('RackspaceProvider[{}]'.format(id))
self.log = logging.getLogger(f'RackspaceProvider[{id}]')
super(RackspaceProvider, self).__init__(id, *args, **kwargs)
auth_token, dns_endpoint = self._get_auth_token(username, api_key)
@ -91,7 +91,7 @@ class RackspaceProvider(BaseProvider):
def _request(self, method, path, data=None, pagination_key=None):
self.log.debug('_request: method=%s, path=%s', method, path)
url = '{}/{}'.format(self.dns_endpoint, path)
url = f'{self.dns_endpoint}/{path}'
if pagination_key:
resp = self._paginated_request_for_url(method, url, data,
@ -194,8 +194,7 @@ class RackspaceProvider(BaseProvider):
resp_data = None
try:
domain_id = self._get_zone_id_for(zone)
resp_data = self._request('GET',
'domains/{}/records'.format(domain_id),
resp_data = self._request('GET', f'domains/{domain_id}/records',
pagination_key='records')
self.log.debug('populate: loaded')
except HTTPError as e:
@ -213,8 +212,7 @@ class RackspaceProvider(BaseProvider):
records = self._group_records(resp_data)
for record_type, records_of_type in records.items():
for raw_record_name, record_set in records_of_type.items():
data_for = getattr(self,
'_data_for_{}'.format(record_type))
data_for = getattr(self, f'_data_for_{record_type}')
record_name = zone.hostname_from_fqdn(raw_record_name)
record = Record.new(zone, record_name,
data_for(record_set),
@ -291,7 +289,7 @@ class RackspaceProvider(BaseProvider):
self._get_values(change.new))
def _create_given_change_values(self, change, values):
transformer = getattr(self, "_record_for_{}".format(change.new._type))
transformer = getattr(self, f"_record_for_{change.new._type}")
return [transformer(change.new, v) for v in values]
def _mod_Update(self, change):
@ -311,8 +309,7 @@ class RackspaceProvider(BaseProvider):
update_out = []
update_values = set(new_values).intersection(set(existing_values))
for value in update_values:
transformer = getattr(self,
"_record_for_{}".format(change.new._type))
transformer = getattr(self, f"_record_for_{change.new._type}")
prior_rs_record = transformer(change.existing, value)
prior_key = self._key_for_record(prior_rs_record)
next_rs_record = transformer(change.new, value)
@ -329,8 +326,7 @@ class RackspaceProvider(BaseProvider):
change.existing))
def _delete_given_change_values(self, change, values):
transformer = getattr(self, "_record_for_{}".format(
change.existing._type))
transformer = getattr(self, f"_record_for_{change.existing._type}")
out = []
for value in values:
rs_record = transformer(change.existing, value)
@ -367,12 +363,12 @@ class RackspaceProvider(BaseProvider):
if deletes:
params = "&".join(sorted(deletes))
self._delete('domains/{}/records?{}'.format(domain_id, params))
self._delete(f'domains/{domain_id}/records?{params}')
if updates:
data = {"records": sorted(updates, key=_value_keyer)}
self._put('domains/{}/records'.format(domain_id), data=data)
self._put(f'domains/{domain_id}/records', data=data)
if creates:
data = {"records": sorted(creates, key=_value_keyer)}
self._post('domains/{}/records'.format(domain_id), data=data)
self._post(f'domains/{domain_id}/records', data=data)

+ 65
- 80
octodns/provider/route53.py View File

@ -47,7 +47,7 @@ class _Route53Record(EqualityTupleMixin):
# healthchecks, which hopefully never happens.
fqdn = record.fqdn
ret.add(_Route53Record(provider, record, creating,
'_octodns-default-pool.{}'.format(fqdn)))
f'_octodns-default-pool.{fqdn}'))
# Pools
for pool_name, pool in record.dynamic.pools.items():
@ -136,7 +136,7 @@ class _Route53Record(EqualityTupleMixin):
self._type = record._type
self.ttl = record.ttl
values_for = getattr(self, '_values_for_{}'.format(self._type))
values_for = getattr(self, f'_values_for_{self._type}')
self.values = values_for(record)
def mod(self, action, existing_rrsets):
@ -156,7 +156,7 @@ class _Route53Record(EqualityTupleMixin):
def __hash__(self):
'sub-classes should never use this method'
return '{}:{}'.format(self.fqdn, self._type).__hash__()
return f'{self.fqdn}:{self._type}'.__hash__()
def _equality_tuple(self):
'''Sub-classes should call up to this and return its value and add
@ -164,8 +164,8 @@ class _Route53Record(EqualityTupleMixin):
return (self.__class__.__name__, self.fqdn, self._type)
def __repr__(self):
return '_Route53Record<{} {} {} {}>'.format(self.fqdn, self._type,
self.ttl, self.values)
return '_Route53Record<{self.fqdn} {self._type} {self.ttl} ' \
f'{self.values}>'
def _value_convert_value(self, value, record):
return value
@ -184,7 +184,7 @@ class _Route53Record(EqualityTupleMixin):
_values_for_NS = _values_for_values
def _value_convert_CAA(self, value, record):
return '{} {} "{}"'.format(value.flags, value.tag, value.value)
return f'{value.flags} {value.tag} "{value.value}"'
def _values_for_CAA(self, record):
return [self._value_convert_CAA(v, record) for v in record.values]
@ -196,18 +196,17 @@ class _Route53Record(EqualityTupleMixin):
_values_for_PTR = _values_for_value
def _value_convert_MX(self, value, record):
return '{} {}'.format(value.preference, value.exchange)
return f'{value.preference} {value.exchange}'
def _values_for_MX(self, record):
return [self._value_convert_MX(v, record) for v in record.values]
def _value_convert_NAPTR(self, value, record):
return '{} {} "{}" "{}" "{}" {}' \
.format(value.order, value.preference,
value.flags if value.flags else '',
value.service if value.service else '',
value.regexp if value.regexp else '',
value.replacement)
flags = value.flags if value.flags else ''
service = value.service if value.service else ''
regexp = value.regexp if value.regexp else ''
return f'{value.order} {value.preference} "{flags}" "{service}" ' \
f'"{regexp}" {value.replacement}'
def _values_for_NAPTR(self, record):
return [self._value_convert_NAPTR(v, record) for v in record.values]
@ -225,8 +224,7 @@ class _Route53Record(EqualityTupleMixin):
_values_for_TXT = _values_for_quoted
def _value_for_SRV(self, value, record):
return '{} {} {} {}'.format(value.priority, value.weight,
value.port, value.target)
return f'{value.priority} {value.weight} {value.port} {value.target}'
def _values_for_SRV(self, record):
return [self._value_for_SRV(v, record) for v in record.values]
@ -236,7 +234,7 @@ class _Route53DynamicPool(_Route53Record):
def __init__(self, provider, hosted_zone_id, record, pool_name, creating,
target_name=None):
fqdn_override = '_octodns-{}-pool.{}'.format(pool_name, record.fqdn)
fqdn_override = f'_octodns-{pool_name}-pool.{record.fqdn}'
super(_Route53DynamicPool, self) \
.__init__(provider, record, creating, fqdn_override=fqdn_override)
@ -246,12 +244,10 @@ class _Route53DynamicPool(_Route53Record):
self.target_name = target_name
if target_name:
# We're pointing down the chain
self.target_dns_name = '_octodns-{}-pool.{}'.format(target_name,
record.fqdn)
self.target_dns_name = f'_octodns-{target_name}-pool.{record.fqdn}'
else:
# We're a paimary, point at our values
self.target_dns_name = '_octodns-{}-value.{}'.format(pool_name,
record.fqdn)
self.target_dns_name = f'_octodns-{pool_name}-value.{record.fqdn}'
@property
def mode(self):
@ -260,9 +256,8 @@ class _Route53DynamicPool(_Route53Record):
@property
def identifer(self):
if self.target_name:
return '{}-{}-{}'.format(self.pool_name, self.mode,
self.target_name)
return '{}-{}'.format(self.pool_name, self.mode)
return f'{self.pool_name}-{self.mode}-{self.target_name}'
return f'{self.pool_name}-{self.mode}'
def mod(self, action, existing_rrsets):
return {
@ -281,12 +276,11 @@ class _Route53DynamicPool(_Route53Record):
}
def __hash__(self):
return '{}:{}:{}'.format(self.fqdn, self._type,
self.identifer).__hash__()
return f'{self.fqdn}:{self._type}:{self.identifer}'.__hash__()
def __repr__(self):
return '_Route53DynamicPool<{} {} {} {}>' \
.format(self.fqdn, self._type, self.mode, self.target_dns_name)
return f'_Route53DynamicPool<{self.fqdn} {self._type} {self.mode} ' \
f'{self.target_dns_name}>'
class _Route53DynamicRule(_Route53Record):
@ -300,12 +294,11 @@ class _Route53DynamicRule(_Route53Record):
self.pool_name = pool_name
self.index = index
self.target_dns_name = '_octodns-{}-pool.{}'.format(pool_name,
record.fqdn)
self.target_dns_name = f'_octodns-{pool_name}-pool.{record.fqdn}'
@property
def identifer(self):
return '{}-{}-{}'.format(self.index, self.pool_name, self.geo)
return f'{self.index}-{self.pool_name}-{self.geo}'
def mod(self, action, existing_rrsets):
rrset = {
@ -345,26 +338,24 @@ class _Route53DynamicRule(_Route53Record):
}
def __hash__(self):
return '{}:{}:{}'.format(self.fqdn, self._type,
self.identifer).__hash__()
return f'{self.fqdn}:{self._type}:{self.identifer}'.__hash__()
def __repr__(self):
return '_Route53DynamicRule<{} {} {} {} {}>' \
.format(self.fqdn, self._type, self.index, self.geo,
self.target_dns_name)
return f'_Route53DynamicRule<{self.fqdn} {self._type} {self.index} ' \
f'{self.geo} {self.target_dns_name}>'
class _Route53DynamicValue(_Route53Record):
def __init__(self, provider, record, pool_name, value, weight, index,
creating):
fqdn_override = '_octodns-{}-value.{}'.format(pool_name, record.fqdn)
fqdn_override = f'_octodns-{pool_name}-value.{record.fqdn}'
super(_Route53DynamicValue, self).__init__(provider, record, creating,
fqdn_override=fqdn_override)
self.pool_name = pool_name
self.index = index
value_convert = getattr(self, '_value_convert_{}'.format(record._type))
value_convert = getattr(self, f'_value_convert_{record._type}')
self.value = value_convert(value, record)
self.weight = weight
@ -373,7 +364,7 @@ class _Route53DynamicValue(_Route53Record):
@property
def identifer(self):
return '{}-{:03d}'.format(self.pool_name, self.index)
return f'{self.pool_name}-{self.index:03d}'
def mod(self, action, existing_rrsets):
@ -404,12 +395,11 @@ class _Route53DynamicValue(_Route53Record):
}
def __hash__(self):
return '{}:{}:{}'.format(self.fqdn, self._type,
self.identifer).__hash__()
return f'{self.fqdn}:{self._type}:{self.identifer}'.__hash__()
def __repr__(self):
return '_Route53DynamicValue<{} {} {} {}>' \
.format(self.fqdn, self._type, self.identifer, self.value)
return f'_Route53DynamicValue<{self.fqdn} {self._type} ' \
f'{self.identifer} {self.value}>'
class _Route53GeoDefault(_Route53Record):
@ -430,11 +420,11 @@ class _Route53GeoDefault(_Route53Record):
}
def __hash__(self):
return '{}:{}:default'.format(self.fqdn, self._type).__hash__()
return f'{self.fqdn}:{self._type}:default'.__hash__()
def __repr__(self):
return '_Route53GeoDefault<{} {} {} {}>'.format(self.fqdn, self._type,
self.ttl, self.values)
return f'_Route53GeoDefault<{self.fqdn} {self._type} {self.ttl} ' \
f'{self.values}>'
class _Route53GeoRecord(_Route53Record):
@ -499,18 +489,15 @@ class _Route53GeoRecord(_Route53Record):
}
def __hash__(self):
return '{}:{}:{}'.format(self.fqdn, self._type,
self.geo.code).__hash__()
return f'{self.fqdn}:{self._type}:{self.geo.code}'.__hash__()
def _equality_tuple(self):
return super(_Route53GeoRecord, self)._equality_tuple() + \
(self.geo.code,)
def __repr__(self):
return '_Route53GeoRecord<{} {} {} {} {}>'.format(self.fqdn,
self._type, self.ttl,
self.geo.code,
self.values)
return f'_Route53GeoRecord<{self.fqdn} {self._type} {self.ttl} ' \
f'{self.geo.code} {self.values}>'
class Route53ProviderException(ProviderException):
@ -552,7 +539,7 @@ def _mod_keyer(mod):
unique_id = rrset['SetIdentifier']
else:
if 'SetIdentifier' in rrset:
unique_id = '{}-{}'.format(rrset['Name'], rrset['SetIdentifier'])
unique_id = f'{rrset["Name"]}-{rrset["SetIdentifier"]}'
else:
unique_id = rrset['Name']
@ -626,13 +613,13 @@ class Route53Provider(BaseProvider):
session_token=None, delegation_set_id=None, *args, **kwargs):
self.max_changes = max_changes
self.delegation_set_id = delegation_set_id
_msg = 'access_key_id={}, secret_access_key=***, ' \
'session_token=***'.format(access_key_id)
_msg = f'access_key_id={access_key_id}, secret_access_key=***, ' \
'session_token=***'
use_fallback_auth = access_key_id is None and \
secret_access_key is None and session_token is None
if use_fallback_auth:
_msg = 'auth=fallback'
self.log = logging.getLogger('Route53Provider[{}]'.format(id))
self.log = logging.getLogger(f'Route53Provider[{id}]')
self.log.debug('__init__: id=%s, %s', id, _msg)
super(Route53Provider, self).__init__(id, *args, **kwargs)
@ -710,9 +697,9 @@ class Route53Provider(BaseProvider):
return
cn = country_alpha2_to_continent_code(cc)
try:
return '{}-{}-{}'.format(cn, cc, loc['SubdivisionCode'])
return f'{cn}-{cc}-{loc["SubdivisionCode"]}'
except KeyError:
return '{}-{}'.format(cn, cc)
return f'{cn}-{cc}'
def _data_for_geo(self, rrset):
ret = {
@ -876,7 +863,7 @@ class Route53Provider(BaseProvider):
if pool_name == 'default':
# default becomes the base for the record and its
# value(s) will fill the non-dynamic values
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data.update(data_for(rrset))
elif rrset['Failover'] == 'SECONDARY':
# This is a failover record, we'll ignore PRIMARY, but
@ -940,17 +927,16 @@ class Route53Provider(BaseProvider):
if not g.startswith('NA-CA-')]
if not filtered_geos:
# We've removed all geos, we'll have to skip this rule
msg = 'NA-CA-* not supported for {}' \
.format(record.fqdn)
fallback = 'skipping rule {}'.format(i)
msg = f'NA-CA-* not supported for {record.fqdn}'
fallback = f'skipping rule {i}'
self.supports_warn_or_except(msg, fallback)
continue
elif geos != filtered_geos:
msg = 'NA-CA-* not supported for {}' \
.format(record.fqdn)
fallback = 'filtering rule {} from ({}) to ({})' \
.format(i, ', '.join(geos),
', '.join(filtered_geos))
msg = f'NA-CA-* not supported for {record.fqdn}'
before = ', '.join(geos)
after = ', '.join(filtered_geos)
fallback = f'filtering rule {i} from ({before}) to ' \
f'({after})'
self.supports_warn_or_except(msg, fallback)
rule.data['geos'] = filtered_geos
rules.append(rule)
@ -1001,7 +987,7 @@ class Route53Provider(BaseProvider):
% rrset['Name'])
continue
# A basic record (potentially including geo)
data = getattr(self, '_data_for_{}'.format(record_type))(rrset)
data = getattr(self, f'_data_for_{record_type}')(rrset)
records[record_name][record_type].append(data)
# Convert the dynamic rrsets to Records
@ -1137,8 +1123,8 @@ class Route53Provider(BaseProvider):
# we're looking for a healthcheck with the current version & our record
# type, we'll ignore anything else
expected_ref = '{}:{}:{}:'.format(self.HEALTH_CHECK_VERSION,
record._type, record.fqdn)
expected_ref = \
f'{self.HEALTH_CHECK_VERSION}:{record._type}:{record.fqdn}:'
for id, health_check in self.health_checks.items():
if not health_check['CallerReference'].startswith(expected_ref):
# not match, ignore
@ -1175,16 +1161,16 @@ class Route53Provider(BaseProvider):
if value:
config['IPAddress'] = value
ref = '{}:{}:{}:{}'.format(self.HEALTH_CHECK_VERSION, record._type,
record.fqdn, uuid4().hex[:12])
ref = f'{self.HEALTH_CHECK_VERSION}:{record._type}:{record.fqdn}:' + \
uuid4().hex[:12]
resp = self._conn.create_health_check(CallerReference=ref,
HealthCheckConfig=config)
health_check = resp['HealthCheck']
id = health_check['Id']
# Set a Name for the benefit of the UI
name = '{}:{} - {}'.format(record.fqdn, record._type,
value or healthcheck_host)
value_or_host = value or healthcheck_host
name = f'{record.fqdn}:{record._type} - {value_or_host}'
self._conn.change_tags_for_resource(ResourceType='healthcheck',
ResourceId=id,
AddTags=[{
@ -1221,12 +1207,11 @@ class Route53Provider(BaseProvider):
# Now we need to run through ALL the health checks looking for those
# that apply to this record, deleting any that do and are no longer in
# use
expected_re = re.compile(r'^\d\d\d\d:{}:{}:'
.format(record._type, record.fqdn))
expected_re = re.compile(fr'^\d\d\d\d:{record._type}:{record.fqdn}:')
# UNITL 1.0: we'll clean out the previous version of Route53 health
# checks as best as we can.
expected_legacy_host = record.fqdn[:-1]
expected_legacy = '0000:{}:'.format(record._type)
expected_legacy = f'0000:{record._type}:'
for id, health_check in self.health_checks.items():
ref = health_check['CallerReference']
if expected_re.match(ref) and id not in in_use:
@ -1423,7 +1408,8 @@ class Route53Provider(BaseProvider):
existing_rrsets = self._load_records(zone_id)
for c in changes:
# Generate the mods for this change
mod_type = getattr(self, '_mod_{}'.format(c.__class__.__name__))
klass = c.__class__.__name__
mod_type = getattr(self, f'_mod_{klass}')
mods = mod_type(c, zone_id, existing_rrsets)
# Order our mods to make sure targets exist before alises point to
@ -1437,8 +1423,7 @@ class Route53Provider(BaseProvider):
if mods_rs_count > self.max_changes:
# a single mod resulted in too many ResourceRecords changes
raise Exception('Too many modifications: {}'
.format(mods_rs_count))
raise Exception(f'Too many modifications: {mods_rs_count}')
# r53 limits changesets to 1000 entries
if (batch_rs_count + mods_rs_count) < self.max_changes:
@ -1469,7 +1454,7 @@ class Route53Provider(BaseProvider):
batch.sort(key=_mod_keyer)
uuid = uuid4().hex
batch = {
'Comment': 'Change: {}'.format(uuid),
'Comment': f'Change: {uuid}',
'Changes': batch,
}
self.log.debug('_really_apply: sending change request, comment=%s',


+ 16
- 17
octodns/provider/selectel.py View File

@ -39,7 +39,7 @@ class SelectelProvider(BaseProvider):
API_URL = 'https://api.selectel.ru/domains/v1'
def __init__(self, id, token, *args, **kwargs):
self.log = getLogger('SelectelProvider[{}]'.format(id))
self.log = getLogger(f'SelectelProvider[{id}]')
self.log.debug('__init__: id=%s', id)
super(SelectelProvider, self).__init__(id, *args, **kwargs)
@ -55,7 +55,7 @@ class SelectelProvider(BaseProvider):
def _request(self, method, path, params=None, data=None):
self.log.debug('_request: method=%s, path=%s', method, path)
url = '{}{}'.format(self.API_URL, path)
url = f'{self.API_URL}{path}'
resp = self._sess.request(method, url, params=params, json=data)
self.log.debug('_request: status=%s', resp.status_code)
@ -69,7 +69,7 @@ class SelectelProvider(BaseProvider):
return resp.json()
def _get_total_count(self, path):
url = '{}{}'.format(self.API_URL, path)
url = f'{self.API_URL}{path}'
resp = self._sess.request('HEAD', url)
return int(resp.headers['X-Total-Count'])
@ -101,12 +101,11 @@ class SelectelProvider(BaseProvider):
zone_name = desired.name[:-1]
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name).lower())(zone_name,
change)
getattr(self, f'_apply_{class_name}'.lower())(zone_name, change)
def _apply_create(self, zone_name, change):
new = change.new
params_for = getattr(self, '_params_for_{}'.format(new._type))
params_for = getattr(self, f'_params_for_{new._type}')
for params in params_for(new):
self.create_record(zone_name, params)
@ -178,7 +177,7 @@ class SelectelProvider(BaseProvider):
return {
'ttl': records[0]['ttl'],
'type': _type,
'values': ['{}.'.format(r['content']) for r in records],
'values': [f'{r["content"]}.' for r in records],
}
def _data_for_MX(self, _type, records):
@ -186,7 +185,7 @@ class SelectelProvider(BaseProvider):
for record in records:
values.append({
'preference': record['priority'],
'exchange': '{}.'.format(record['content']),
'exchange': f'{record["content"]}.',
})
return {
'ttl': records[0]['ttl'],
@ -199,7 +198,7 @@ class SelectelProvider(BaseProvider):
return {
'ttl': only['ttl'],
'type': _type,
'value': '{}.'.format(only['content'])
'value': f'{only["content"]}.',
}
def _data_for_TXT(self, _type, records):
@ -216,7 +215,7 @@ class SelectelProvider(BaseProvider):
'priority': record['priority'],
'weight': record['weight'],
'port': record['port'],
'target': '{}.'.format(record['target']),
'target': f'{record["target"]}.',
})
return {
@ -239,7 +238,7 @@ class SelectelProvider(BaseProvider):
values[name][record['type']].append(record)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data = data_for(_type, records)
record = Record.new(zone, name, data, source=self,
lenient=lenient)
@ -260,7 +259,7 @@ class SelectelProvider(BaseProvider):
return domains
def zone_records(self, zone):
path = '/{}/records/'.format(zone.name[:-1])
path = f'/{zone.name[:-1]}/records/'
zone_records = []
total_count = self._get_total_count(path)
@ -288,24 +287,24 @@ class SelectelProvider(BaseProvider):
else:
domain_id = self.create_domain(zone_name)['id']
path = '/{}/records/'.format(domain_id)
path = f'/{domain_id}/records/'
return self._request('POST', path, data=data)
def delete_record(self, domain, _type, zone):
self.log.debug('Delete record. Domain: %s, Type: %s', domain, _type)
domain_id = self._domain_list[domain]['id']
records = self._zone_records.get('{}.'.format(domain), False)
records = self._zone_records.get(f'{domain}.', False)
if not records:
path = '/{}/records/'.format(domain_id)
path = f'/{domain_id}/records/'
records = self._request('GET', path)
for record in records:
full_domain = domain
if zone:
full_domain = '{}{}'.format(zone, domain)
full_domain = f'{zone}{domain}'
if record['type'] == _type and record['name'] == full_domain:
path = '/{}/records/{}'.format(domain_id, record['id'])
path = f'/{domain_id}/records/{record["id"]}'
return self._request('DELETE', path)
self.log.debug('Delete record failed (Record not found)')

+ 14
- 22
octodns/provider/ultra.py View File

@ -79,7 +79,7 @@ class UltraProvider(BaseProvider):
data=None, json=None, json_response=True):
self.log.debug('_request: method=%s, path=%s', method, path)
url = '{}{}'.format(self._base_uri, path)
url = f'{self._base_uri}{path}'
resp = self._sess.request(method,
url,
params=params,
@ -128,12 +128,12 @@ class UltraProvider(BaseProvider):
resp = self._post(path, data=data)
self._sess.headers.update({
'Authorization': 'Bearer {}'.format(resp['access_token']),
'Authorization': f'Bearer {resp["access_token"]}',
})
def __init__(self, id, account, username, password, timeout=TIMEOUT,
*args, **kwargs):
self.log = getLogger('UltraProvider[{}]'.format(id))
self.log = getLogger(f'UltraProvider[{id}]')
self.log.debug('__init__: id=%s, account=%s, username=%s, '
'password=***', id, account, username)
@ -250,7 +250,7 @@ class UltraProvider(BaseProvider):
return []
records = []
path = '/v2/zones/{}/rrsets'.format(zone.name)
path = f'/v2/zones/{zone.name}/rrsets'
offset = 0
limit = 100
paging = True
@ -269,7 +269,7 @@ class UltraProvider(BaseProvider):
return self._zone_records[zone.name]
def _record_for(self, zone, name, _type, records, lenient):
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data = data_for(_type, records)
record = Record.new(zone, name, data, source=self, lenient=lenient)
return record
@ -327,7 +327,7 @@ class UltraProvider(BaseProvider):
for change in changes:
class_name = change.__class__.__name__
getattr(self, '_apply_{}'.format(class_name))(change)
getattr(self, f'_apply_{class_name}')(change)
# Clear the cache
self._zone_records.pop(name, None)
@ -380,25 +380,20 @@ class UltraProvider(BaseProvider):
def _contents_for_SRV(self, record):
return {
'ttl': record.ttl,
'rdata': ['{} {} {} {}'.format(x.priority,
x.weight,
x.port,
x.target) for x in record.values]
'rdata': [f'{x.priority} {x.weight} {x.port} {x.target}'
for x in record.values]
}
def _contents_for_CAA(self, record):
return {
'ttl': record.ttl,
'rdata': ['{} {} {}'.format(x.flags,
x.tag,
x.value) for x in record.values]
'rdata': [f'{x.flags} {x.tag} {x.value}' for x in record.values]
}
def _contents_for_MX(self, record):
return {
'ttl': record.ttl,
'rdata': ['{} {}'.format(x.preference,
x.exchange) for x in record.values]
'rdata': [f'{x.preference} {x.exchange}' for x in record.values]
}
def _gen_data(self, record):
@ -410,10 +405,8 @@ class UltraProvider(BaseProvider):
else:
record_type = record._type
path = '/v2/zones/{}/rrsets/{}/{}'.format(zone_name,
record_type,
record.fqdn)
contents_for = getattr(self, '_contents_for_{}'.format(record._type))
path = f'/v2/zones/{zone_name}/rrsets/{record_type}/{record.fqdn}'
contents_for = getattr(self, f'_contents_for_{record._type}')
return path, contents_for(record)
def _apply_Create(self, change):
@ -459,7 +452,6 @@ class UltraProvider(BaseProvider):
if existing_type == "ALIAS":
existing_type = "APEXALIAS"
path = '/v2/zones/{}/rrsets/{}/{}'.format(zone_name,
existing_type,
existing.fqdn)
path = f'/v2/zones/{zone_name}/rrsets/{existing_type}/' + \
existing.fqdn
self._delete(path, json_response=False)

+ 8
- 8
octodns/provider/yaml.py View File

@ -111,8 +111,8 @@ class YamlProvider(BaseProvider):
def __init__(self, id, directory, default_ttl=3600, enforce_order=True,
populate_should_replace=False, *args, **kwargs):
self.log = logging.getLogger('{}[{}]'.format(
self.__class__.__name__, id))
klass = self.__class__.__name__
self.log = logging.getLogger(f'{klass}[{id}]')
self.log.debug('__init__: id=%s, directory=%s, default_ttl=%d, '
'enforce_order=%d, populate_should_replace=%d',
id, directory, default_ttl, enforce_order,
@ -150,7 +150,7 @@ class YamlProvider(BaseProvider):
return False
before = len(zone.records)
filename = join(self.directory, '{}yaml'.format(zone.name))
filename = join(self.directory, f'{zone.name}yaml')
self._populate_from_file(filename, zone, lenient)
self.log.info('populate: found %s records, exists=False',
@ -188,7 +188,7 @@ class YamlProvider(BaseProvider):
self._do_apply(desired, data)
def _do_apply(self, desired, data):
filename = join(self.directory, '{}yaml'.format(desired.name))
filename = join(self.directory, f'{desired.name}yaml')
self.log.debug('_apply: writing filename=%s', filename)
with open(filename, 'w') as fh:
safe_dump(dict(data), fh)
@ -197,7 +197,7 @@ class YamlProvider(BaseProvider):
def _list_all_yaml_files(directory):
yaml_files = set()
for f in listdir(directory):
filename = join(directory, '{}'.format(f))
filename = join(directory, f)
if f.endswith('.yaml') and isfile(filename):
yaml_files.add(filename)
return list(yaml_files)
@ -246,7 +246,7 @@ class SplitYamlProvider(YamlProvider):
self.extension = extension
def _zone_directory(self, zone):
filename = '{}{}'.format(zone.name[:-1], self.extension)
filename = f'{zone.name[:-1]}{self.extension}'
return join(self.directory, filename)
def populate(self, zone, target=False, lenient=False):
@ -278,7 +278,7 @@ class SplitYamlProvider(YamlProvider):
if record in self.CATCHALL_RECORD_NAMES:
catchall[record] = config
continue
filename = join(zone_dir, '{}.yaml'.format(record))
filename = join(zone_dir, f'{record}.yaml')
self.log.debug('_apply: writing filename=%s', filename)
with open(filename, 'w') as fh:
record_data = {record: config}
@ -286,7 +286,7 @@ class SplitYamlProvider(YamlProvider):
if catchall:
# Scrub the trailing . to make filenames more sane.
dname = desired.name[:-1]
filename = join(zone_dir, '${}.yaml'.format(dname))
filename = join(zone_dir, f'${dname}.yaml')
self.log.debug('_apply: writing catchall filename=%s', filename)
with open(filename, 'w') as fh:
safe_dump(catchall, fh)

+ 116
- 159
octodns/record/__init__.py View File

@ -41,7 +41,7 @@ class Create(Change):
def __repr__(self, leader=''):
source = self.new.source.id if self.new.source else ''
return 'Create {} ({})'.format(self.new, source)
return f'Create {self.new} ({source})'
class Update(Change):
@ -52,9 +52,8 @@ class Update(Change):
# do nothing
def __repr__(self, leader=''):
source = self.new.source.id if self.new.source else ''
return 'Update\n{leader} {existing} ->\n{leader} {new} ({src})' \
.format(existing=self.existing, new=self.new, leader=leader,
src=source)
return f'Update\n{leader} {self.existing} ->\n' \
f'{leader} {self.new} ({source})'
class Delete(Change):
@ -63,14 +62,15 @@ class Delete(Change):
super(Delete, self).__init__(existing, None)
def __repr__(self, leader=''):
return 'Delete {}'.format(self.existing)
return f'Delete {self.existing}'
class ValidationError(Exception):
@classmethod
def build_message(cls, fqdn, reasons):
return 'Invalid record {}\n - {}'.format(fqdn, '\n - '.join(reasons))
reasons = '\n - '.join(reasons)
return f'Invalid record {fqdn}\n - {reasons}'
def __init__(self, fqdn, reasons):
super(Exception, self).__init__(self.build_message(fqdn, reasons))
@ -84,11 +84,11 @@ class Record(EqualityTupleMixin):
@classmethod
def new(cls, zone, name, data, source=None, lenient=False):
name = text_type(name)
fqdn = '{}.{}'.format(name, zone.name) if name else zone.name
fqdn = f'{name}.{zone.name}' if name else zone.name
try:
_type = data['type']
except KeyError:
raise Exception('Invalid record {}, missing type'.format(fqdn))
raise Exception(f'Invalid record {fqdn}, missing type')
try:
_class = {
'A': ARecord,
@ -109,7 +109,7 @@ class Record(EqualityTupleMixin):
'URLFWD': UrlfwdRecord,
}[_type]
except KeyError:
raise Exception('Unknown record type: "{}"'.format(_type))
raise Exception(f'Unknown record type: "{_type}"')
reasons = _class.validate(name, fqdn, data)
try:
lenient |= data['octodns']['lenient']
@ -127,13 +127,13 @@ class Record(EqualityTupleMixin):
reasons = []
n = len(fqdn)
if n > 253:
reasons.append('invalid fqdn, "{}" is too long at {} chars, max '
'is 253'.format(fqdn, n))
reasons.append(f'invalid fqdn, "{fqdn}" is too long at {n} '
'chars, max is 253')
for label in name.split('.'):
n = len(label)
if n > 63:
reasons.append('invalid label, "{}" is too long at {} chars, '
'max is 63'.format(label, n))
reasons.append(f'invalid label, "{label}" is too long at {n}'
' chars, max is 63')
try:
ttl = int(data['ttl'])
if ttl < 0:
@ -169,7 +169,7 @@ class Record(EqualityTupleMixin):
@property
def fqdn(self):
if self.name:
return '{}.{}'.format(self.name, self.zone.name)
return f'{self.name}.{self.zone.name}'
return self.zone.name
@property
@ -236,7 +236,7 @@ class Record(EqualityTupleMixin):
# is useful when computing diffs/changes.
def __hash__(self):
return '{}:{}'.format(self.name, self._type).__hash__()
return f'{self.name}:{self._type}'.__hash__()
def _equality_tuple(self):
return (self.name, self._type)
@ -255,7 +255,7 @@ class GeoValue(EqualityTupleMixin):
reasons = []
match = cls.geo_re.match(code)
if not match:
reasons.append('invalid geo "{}"'.format(code))
reasons.append(f'invalid geo "{code}"')
return reasons
def __init__(self, geo, values):
@ -278,9 +278,8 @@ class GeoValue(EqualityTupleMixin):
self.values)
def __repr__(self):
return "'Geo {} {} {} {}'".format(self.continent_code,
self.country_code,
self.subdivision_code, self.values)
return f"'Geo {self.continent_code} {self.country_code} " \
"{self.subdivision_code} {self.values}'"
class _ValuesMixin(object):
@ -324,11 +323,9 @@ class _ValuesMixin(object):
return ret
def __repr__(self):
values = "['{}']".format("', '".join([text_type(v)
for v in self.values]))
return '<{} {} {}, {}, {}>'.format(self.__class__.__name__,
self._type, self.ttl,
self.fqdn, values)
values = "', '".join([text_type(v) for v in self.values])
klass = self.__class__.__name__
return f"<{klass} {self._type} {self.ttl}, {self.fqdn}, ['{values}']>"
class _GeoMixin(_ValuesMixin):
@ -376,10 +373,9 @@ class _GeoMixin(_ValuesMixin):
def __repr__(self):
if self.geo:
return '<{} {} {}, {}, {}, {}>'.format(self.__class__.__name__,
self._type, self.ttl,
self.fqdn, self.values,
self.geo)
klass = self.__class__.__name__
return f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' \
f'{self.values}, {self.geo}>'
return super(_GeoMixin, self).__repr__()
@ -408,9 +404,8 @@ class _ValueMixin(object):
return ret
def __repr__(self):
return '<{} {} {}, {}, {}>'.format(self.__class__.__name__,
self._type, self.ttl,
self.fqdn, self.value)
klass = self.__class__.__name__
return f'<{klass} {self._type} {self.ttl}, {self.fqdn}, {self.value}>'
class _DynamicPool(object):
@ -454,7 +449,7 @@ class _DynamicPool(object):
return not self.__eq__(other)
def __repr__(self):
return '{}'.format(self.data)
return f'{self.data}'
class _DynamicRule(object):
@ -484,7 +479,7 @@ class _DynamicRule(object):
return not self.__eq__(other)
def __repr__(self):
return '{}'.format(self.data)
return f'{self.data}'
class _Dynamic(object):
@ -515,7 +510,7 @@ class _Dynamic(object):
return not self.__eq__(other)
def __repr__(self):
return '{}, {}'.format(self.pools, self.rules)
return f'{self.pools}, {self.rules}'
class _DynamicMixin(object):
@ -546,12 +541,12 @@ class _DynamicMixin(object):
else:
for _id, pool in sorted(pools.items()):
if not isinstance(pool, dict):
reasons.append('pool "{}" must be a dict'.format(_id))
reasons.append(f'pool "{_id}" must be a dict')
continue
try:
values = pool['values']
except KeyError:
reasons.append('pool "{}" is missing values'.format(_id))
reasons.append(f'pool "{_id}" is missing values')
continue
pools_exist.add(_id)
@ -562,35 +557,33 @@ class _DynamicMixin(object):
weight = value['weight']
weight = int(weight)
if weight < 1 or weight > 15:
reasons.append('invalid weight "{}" in pool "{}" '
'value {}'.format(weight, _id,
value_num))
reasons.append(f'invalid weight "{weight}" in '
f'pool "{_id}" value {value_num}')
except KeyError:
pass
except ValueError:
reasons.append('invalid weight "{}" in pool "{}" '
'value {}'.format(weight, _id,
value_num))
reasons.append(f'invalid weight "{weight}" in '
f'pool "{_id}" value {value_num}')
try:
value = value['value']
reasons.extend(cls._value_type.validate(value,
cls._type))
except KeyError:
reasons.append('missing value in pool "{}" '
'value {}'.format(_id, value_num))
reasons.append(f'missing value in pool "{_id}" '
f'value {value_num}')
if len(values) == 1 and values[0].get('weight', 1) != 1:
reasons.append('pool "{}" has single value with '
'weight!=1'.format(_id))
reasons.append(f'pool "{_id}" has single value with '
'weight!=1')
fallback = pool.get('fallback', None)
if fallback is not None:
if fallback in pools:
pools_seen_as_fallback.add(fallback)
else:
reasons.append('undefined fallback "{}" for pool "{}"'
.format(fallback, _id))
reasons.append(f'undefined fallback "{fallback}" '
f'for pool "{_id}"')
# Check for loops
fallback = pools[_id].get('fallback', None)
@ -600,8 +593,7 @@ class _DynamicMixin(object):
fallback = pools.get(fallback, {}).get('fallback', None)
if fallback in seen:
loop = ' -> '.join(seen)
reasons.append('loop in pool fallbacks: {}'
.format(loop))
reasons.append(f'loop in pool fallbacks: {loop}')
# exit the loop
break
seen.append(fallback)
@ -623,7 +615,7 @@ class _DynamicMixin(object):
try:
pool = rule['pool']
except KeyError:
reasons.append('rule {} missing pool'.format(rule_num))
reasons.append(f'rule {rule_num} missing pool')
continue
try:
@ -632,35 +624,32 @@ class _DynamicMixin(object):
geos = []
if not isinstance(pool, string_types):
reasons.append('rule {} invalid pool "{}"'
.format(rule_num, pool))
reasons.append(f'rule {rule_num} invalid pool "{pool}"')
else:
if pool not in pools:
reasons.append('rule {} undefined pool "{}"'
.format(rule_num, pool))
reasons.append(f'rule {rule_num} undefined pool '
f'"{pool}"')
elif pool in pools_seen and geos:
reasons.append('rule {} invalid, target pool "{}" '
'reused'.format(rule_num, pool))
reasons.append(f'rule {rule_num} invalid, target '
f'pool "{pool}" reused')
pools_seen.add(pool)
if not geos:
if seen_default:
reasons.append('rule {} duplicate default'
.format(rule_num))
reasons.append(f'rule {rule_num} duplicate default')
seen_default = True
if not isinstance(geos, (list, tuple)):
reasons.append('rule {} geos must be a list'
.format(rule_num))
reasons.append(f'rule {rule_num} geos must be a list')
else:
for geo in geos:
reasons.extend(GeoCodes.validate(geo, 'rule {} '
.format(rule_num)))
reasons.extend(GeoCodes.validate(geo,
f'rule {rule_num} '))
unused = pools_exist - pools_seen - pools_seen_as_fallback
if unused:
unused = '", "'.join(sorted(unused))
reasons.append('unused pools: "{}"'.format(unused))
reasons.append(f'unused pools: "{unused}"')
return reasons
@ -718,10 +707,9 @@ class _DynamicMixin(object):
except AttributeError:
values = self.value
return '<{} {} {}, {}, {}, {}>'.format(self.__class__.__name__,
self._type, self.ttl,
self.fqdn, values,
self.dynamic)
klass = self.__class__.__name__
return f'<{klass} {self._type} {self.ttl}, {self.fqdn}, ' \
f'{values}, {self.dynamic}>'
return super(_DynamicMixin, self).__repr__()
@ -743,8 +731,8 @@ class _IpList(object):
try:
cls._address_type(text_type(value))
except Exception:
reasons.append('invalid {} address "{}"'
.format(cls._address_name, value))
addr_name = cls._address_name
reasons.append(f'invalid {addr_name} address "{value}"')
return reasons
@classmethod
@ -780,11 +768,9 @@ class _TargetValue(object):
# NOTE: FQDN complains if the data it receives isn't a str, it doesn't
# allow unicode... This is likely specific to 2.7
elif not FQDN(str(data), allow_underscores=True).is_valid:
reasons.append('{} value "{}" is not a valid FQDN'
.format(_type, data))
reasons.append(f'{_type} value "{data}" is not a valid FQDN')
elif not data.endswith('.'):
reasons.append('{} value "{}" missing trailing .'
.format(_type, data))
reasons.append(f'{_type} value "{data}" missing trailing .')
return reasons
@classmethod
@ -841,9 +827,9 @@ class CaaValue(EqualityTupleMixin):
try:
flags = int(value.get('flags', 0))
if flags < 0 or flags > 255:
reasons.append('invalid flags "{}"'.format(flags))
reasons.append(f'invalid flags "{flags}"')
except ValueError:
reasons.append('invalid flags "{}"'.format(value['flags']))
reasons.append(f'invalid flags "{value["flags"]}"')
if 'tag' not in value:
reasons.append('missing tag')
@ -872,7 +858,7 @@ class CaaValue(EqualityTupleMixin):
return (self.flags, self.tag, self.value)
def __repr__(self):
return '{} {} "{}"'.format(self.flags, self.tag, self.value)
return f'{self.flags} {self.tag} "{self.value}"'
class CaaRecord(_ValuesMixin, Record):
@ -943,13 +929,12 @@ class LocValue(EqualityTupleMixin):
not 0 <= int(value[key]) <= 59
)
):
reasons.append('invalid value for {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid value for {key} '
f'"{value[key]}"')
except KeyError:
reasons.append('missing {}'.format(key))
reasons.append(f'missing {key}')
except ValueError:
reasons.append('invalid {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid {key} "{value[key]}"')
for key in float_keys:
try:
@ -968,13 +953,12 @@ class LocValue(EqualityTupleMixin):
not 0 <= float(value[key]) <= 90000000.00
)
):
reasons.append('invalid value for {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid value for {key} '
f'"{value[key]}"')
except KeyError:
reasons.append('missing {}'.format(key))
reasons.append(f'missing {key}')
except ValueError:
reasons.append('invalid {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid {key} "{value[key]}"')
for key in direction_keys:
try:
@ -983,16 +967,16 @@ class LocValue(EqualityTupleMixin):
key == 'lat_direction' and
value[key] not in ['N', 'S']
):
reasons.append('invalid direction for {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid direction for {key} '
f'"{value[key]}"')
if (
key == 'long_direction' and
value[key] not in ['E', 'W']
):
reasons.append('invalid direction for {} "{}"'
.format(key, value[key]))
reasons.append(f'invalid direction for {key} '
f'"{value[key]}"')
except KeyError:
reasons.append('missing {}'.format(key))
reasons.append(f'missing {key}')
return reasons
@classmethod
@ -1063,23 +1047,12 @@ class LocValue(EqualityTupleMixin):
)
def __repr__(self):
loc_format = "'{0} {1} {2:.3f} {3} " + \
"{4} {5} {6:.3f} {7} " + \
"{8:.2f}m {9:.2f}m {10:.2f}m {11:.2f}m'"
return loc_format.format(
self.lat_degrees,
self.lat_minutes,
self.lat_seconds,
self.lat_direction,
self.long_degrees,
self.long_minutes,
self.long_seconds,
self.long_direction,
self.altitude,
self.size,
self.precision_horz,
self.precision_vert,
)
return f"'{self.lat_degrees} {self.lat_minutes} " \
f"{self.lat_seconds:.3f} {self.lat_direction} " \
f"{self.long_degrees} {self.long_minutes} " \
f"{self.long_seconds:.3f} {self.long_direction} " \
f"{self.altitude:.2f}m {self.size:.2f}m " \
f"{self.precision_horz:.2f}m {self.precision_vert:.2f}m'"
class LocRecord(_ValuesMixin, Record):
@ -1103,14 +1076,12 @@ class MxValue(EqualityTupleMixin):
except KeyError:
reasons.append('missing preference')
except ValueError:
reasons.append('invalid preference "{}"'
.format(value['preference']))
reasons.append(f'invalid preference "{value["preference"]}"')
exchange = None
try:
exchange = value.get('exchange', None) or value['value']
if not exchange.endswith('.'):
reasons.append('MX value "{}" missing trailing .'
.format(exchange))
reasons.append(f'MX value "{exchange}" missing trailing .')
except KeyError:
reasons.append('missing exchange')
return reasons
@ -1147,7 +1118,7 @@ class MxValue(EqualityTupleMixin):
return (self.preference, self.exchange)
def __repr__(self):
return "'{} {}'".format(self.preference, self.exchange)
return f"'{self.preference} {self.exchange}'"
class MxRecord(_ValuesMixin, Record):
@ -1169,25 +1140,24 @@ class NaptrValue(EqualityTupleMixin):
except KeyError:
reasons.append('missing order')
except ValueError:
reasons.append('invalid order "{}"'.format(value['order']))
reasons.append(f'invalid order "{value["order"]}"')
try:
int(value['preference'])
except KeyError:
reasons.append('missing preference')
except ValueError:
reasons.append('invalid preference "{}"'
.format(value['preference']))
reasons.append(f'invalid preference "{value["preference"]}"')
try:
flags = value['flags']
if flags not in cls.VALID_FLAGS:
reasons.append('unrecognized flags "{}"'.format(flags))
reasons.append(f'unrecognized flags "{flags}"')
except KeyError:
reasons.append('missing flags')
# TODO: validate these... they're non-trivial
for k in ('service', 'regexp', 'replacement'):
if k not in value:
reasons.append('missing {}'.format(k))
reasons.append(f'missing {k}')
return reasons
@ -1225,9 +1195,8 @@ class NaptrValue(EqualityTupleMixin):
flags = self.flags if self.flags is not None else ''
service = self.service if self.service is not None else ''
regexp = self.regexp if self.regexp is not None else ''
return "'{} {} \"{}\" \"{}\" \"{}\" {}'" \
.format(self.order, self.preference, flags, service, regexp,
self.replacement)
return f"'{self.order} {self.preference} \"{flags}\" \"{service}\" " \
f"\"{regexp}\" {self.replacement}'"
class NaptrRecord(_ValuesMixin, Record):
@ -1246,8 +1215,7 @@ class _NsValue(object):
reasons = []
for value in data:
if not value.endswith('.'):
reasons.append('NS value "{}" missing trailing .'
.format(value))
reasons.append(f'NS value "{value}" missing trailing .')
return reasons
@classmethod
@ -1306,23 +1274,21 @@ class SshfpValue(EqualityTupleMixin):
try:
algorithm = int(value['algorithm'])
if algorithm not in cls.VALID_ALGORITHMS:
reasons.append('unrecognized algorithm "{}"'
.format(algorithm))
reasons.append(f'unrecognized algorithm "{algorithm}"')
except KeyError:
reasons.append('missing algorithm')
except ValueError:
reasons.append('invalid algorithm "{}"'
.format(value['algorithm']))
reasons.append(f'invalid algorithm "{value["algorithm"]}"')
try:
fingerprint_type = int(value['fingerprint_type'])
if fingerprint_type not in cls.VALID_FINGERPRINT_TYPES:
reasons.append('unrecognized fingerprint_type "{}"'
.format(fingerprint_type))
reasons.append('unrecognized fingerprint_type '
f'"{fingerprint_type}"')
except KeyError:
reasons.append('missing fingerprint_type')
except ValueError:
reasons.append('invalid fingerprint_type "{}"'
.format(value['fingerprint_type']))
reasons.append('invalid fingerprint_type '
f'"{value["fingerprint_type"]}"')
if 'fingerprint' not in value:
reasons.append('missing fingerprint')
return reasons
@ -1351,8 +1317,7 @@ class SshfpValue(EqualityTupleMixin):
return (self.algorithm, self.fingerprint_type, self.fingerprint)
def __repr__(self):
return "'{} {} {}'".format(self.algorithm, self.fingerprint_type,
self.fingerprint)
return f"'{self.algorithm} {self.fingerprint_type} {self.fingerprint}'"
class SshfpRecord(_ValuesMixin, Record):
@ -1369,7 +1334,7 @@ class _ChunkedValuesMixin(_ValuesMixin):
vs = [value[i:i + self.CHUNK_SIZE]
for i in range(0, len(value), self.CHUNK_SIZE)]
vs = '" "'.join(vs)
return '"{}"'.format(vs)
return f'"{vs}"'
@property
def chunked_values(self):
@ -1391,7 +1356,7 @@ class _ChunkedValue(object):
reasons = []
for value in data:
if cls._unescaped_semicolon_re.search(value):
reasons.append('unescaped ; in "{}"'.format(value))
reasons.append(f'unescaped ; in "{value}"')
return reasons
@classmethod
@ -1423,24 +1388,23 @@ class SrvValue(EqualityTupleMixin):
except KeyError:
reasons.append('missing priority')
except ValueError:
reasons.append('invalid priority "{}"'
.format(value['priority']))
reasons.append(f'invalid priority "{value["priority"]}"')
try:
int(value['weight'])
except KeyError:
reasons.append('missing weight')
except ValueError:
reasons.append('invalid weight "{}"'.format(value['weight']))
reasons.append(f'invalid weight "{value["weight"]}"')
try:
int(value['port'])
except KeyError:
reasons.append('missing port')
except ValueError:
reasons.append('invalid port "{}"'.format(value['port']))
reasons.append(f'invalid port "{value["port"]}"')
try:
if not value['target'].endswith('.'):
reasons.append('SRV value "{}" missing trailing .'
.format(value['target']))
reasons.append(f'SRV value "{value["target"]}" missing '
'trailing .')
except KeyError:
reasons.append('missing target')
return reasons
@ -1471,8 +1435,7 @@ class SrvValue(EqualityTupleMixin):
return (self.priority, self.weight, self.port, self.target)
def __repr__(self):
return "'{} {} {} {}'".format(self.priority, self.weight, self.port,
self.target)
return f"'{self.priority} {self.weight} {self.port} {self.target}'"
class SrvRecord(_ValuesMixin, Record):
@ -1512,36 +1475,30 @@ class UrlfwdValue(EqualityTupleMixin):
try:
code = int(value['code'])
if code not in cls.VALID_CODES:
reasons.append('unrecognized return code "{}"'
.format(code))
reasons.append(f'unrecognized return code "{code}"')
except KeyError:
reasons.append('missing code')
except ValueError:
reasons.append('invalid return code "{}"'
.format(value['code']))
reasons.append(f'invalid return code "{value["code"]}"')
try:
masking = int(value['masking'])
if masking not in cls.VALID_MASKS:
reasons.append('unrecognized masking setting "{}"'
.format(masking))
reasons.append(f'unrecognized masking setting "{masking}"')
except KeyError:
reasons.append('missing masking')
except ValueError:
reasons.append('invalid masking setting "{}"'
.format(value['masking']))
reasons.append(f'invalid masking setting "{value["masking"]}"')
try:
query = int(value['query'])
if query not in cls.VALID_QUERY:
reasons.append('unrecognized query setting "{}"'
.format(query))
reasons.append(f'unrecognized query setting "{query}"')
except KeyError:
reasons.append('missing query')
except ValueError:
reasons.append('invalid query setting "{}"'
.format(value['query']))
reasons.append(f'invalid query setting "{value["query"]}"')
for k in ('path', 'target'):
if k not in value:
reasons.append('missing {}'.format(k))
reasons.append(f'missing {k}')
return reasons
@classmethod
@ -1572,8 +1529,8 @@ class UrlfwdValue(EqualityTupleMixin):
return (self.path, self.target, self.code, self.masking, self.query)
def __repr__(self):
return '"{}" "{}" {} {} {}'.format(self.path, self.target, self.code,
self.masking, self.query)
return f'"{self.path}" "{self.target}" {self.code} ' \
f'{self.masking} {self.query}'
class UrlfwdRecord(_ValuesMixin, Record):


+ 6
- 7
octodns/record/geo.py View File

@ -24,15 +24,14 @@ class GeoCodes(object):
pieces = code.split('-')
n = len(pieces)
if n > 3:
reasons.append('{}invalid geo code "{}"'.format(prefix, code))
reasons.append(f'{prefix}invalid geo code "{code}"')
elif n > 0 and pieces[0] not in geo_data:
reasons.append('{}unknown continent code "{}"'
.format(prefix, code))
reasons.append(f'{prefix}unknown continent code "{code}"')
elif n > 1 and pieces[1] not in geo_data[pieces[0]]:
reasons.append('{}unknown country code "{}"'.format(prefix, code))
reasons.append(f'{prefix}unknown country code "{code}"')
elif n > 2 and \
pieces[2] not in geo_data[pieces[0]][pieces[1]]['provinces']:
reasons.append('{}unknown province code "{}"'.format(prefix, code))
reasons.append(f'{prefix}unknown province code "{code}"')
return reasons
@ -57,7 +56,7 @@ class GeoCodes(object):
def country_to_code(cls, country):
for continent, countries in geo_data.items():
if country in countries:
return '{}-{}'.format(continent, country)
return f'{continent}-{country}'
cls.log.warn('country_to_code: unrecognized country "%s"', country)
return
@ -74,4 +73,4 @@ class GeoCodes(object):
country = 'US'
if province in geo_data['NA']['CA']['provinces']:
country = 'CA'
return 'NA-{}-{}'.format(country, province)
return f'NA-{country}-{province}'

+ 4
- 4
octodns/source/axfr.py View File

@ -151,7 +151,7 @@ class AxfrBaseSource(BaseSource):
before = len(zone.records)
for name, types in values.items():
for _type, records in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
record = Record.new(zone, name, data_for(_type, records),
source=self, lenient=lenient)
zone.add_record(record, lenient=lenient)
@ -181,7 +181,7 @@ class AxfrSource(AxfrBaseSource):
master: ns1.example.com
'''
def __init__(self, id, master):
self.log = logging.getLogger('AxfrSource[{}]'.format(id))
self.log = logging.getLogger(f'AxfrSource[{id}]')
self.log.debug('__init__: id=%s, master=%s', id, master)
super(AxfrSource, self).__init__(id)
self.master = master
@ -244,7 +244,7 @@ class ZoneFileSource(AxfrBaseSource):
check_origin: false
'''
def __init__(self, id, directory, file_extension='.', check_origin=True):
self.log = logging.getLogger('ZoneFileSource[{}]'.format(id))
self.log = logging.getLogger(f'ZoneFileSource[{id}]')
self.log.debug('__init__: id=%s, directory=%s, file_extension=%s, '
'check_origin=%s', id,
directory, file_extension, check_origin)
@ -256,7 +256,7 @@ class ZoneFileSource(AxfrBaseSource):
self._zone_records = {}
def _load_zone_file(self, zone_name):
zone_filename = '{}{}'.format(zone_name[:-1], self.file_extension)
zone_filename = f'{zone_name[:-1]}{self.file_extension}'
zonefiles = listdir(self.directory)
if zone_filename in zonefiles:
try:


+ 3
- 3
octodns/source/envvar.py View File

@ -13,7 +13,7 @@ class EnvVarSourceException(Exception):
class EnvironmentVariableNotFoundException(EnvVarSourceException):
def __init__(self, data):
super(EnvironmentVariableNotFoundException, self).__init__(
'Unknown environment variable {}'.format(data))
f'Unknown environment variable {data}')
class EnvVarSource(BaseSource):
@ -63,8 +63,8 @@ class EnvVarSource(BaseSource):
DEFAULT_TTL = 60
def __init__(self, id, variable, name, ttl=DEFAULT_TTL):
self.log = logging.getLogger('{}[{}]'.format(
self.__class__.__name__, id))
klass = self.__class__.__name__
self.log = logging.getLogger(f'{klass}[{id}]')
self.log.debug('__init__: id=%s, variable=%s, name=%s, '
'ttl=%d', id, variable, name, ttl)
super(EnvVarSource, self).__init__(id)


+ 10
- 11
octodns/source/tinydns.py View File

@ -90,7 +90,7 @@ class TinyDnsBaseSource(BaseSource):
return {
'ttl': ttl,
'type': _type,
'value': '{}.'.format(first[0])
'value': f'{first[0]}.'
}
def _data_for_MX(self, _type, records):
@ -103,7 +103,7 @@ class TinyDnsBaseSource(BaseSource):
'type': _type,
'values': [{
'preference': r[1],
'exchange': '{}.'.format(r[0])
'exchange': f'{r[0]}.'
} for r in records]
}
@ -115,7 +115,7 @@ class TinyDnsBaseSource(BaseSource):
return {
'ttl': ttl,
'type': _type,
'values': ['{}.'.format(r[0]) for r in records]
'values': [f'{r[0]}.' for r in records]
}
def populate(self, zone, target=False, lenient=False):
@ -144,7 +144,7 @@ class TinyDnsBaseSource(BaseSource):
'3': 'AAAA',
'6': 'AAAA',
}
name_re = re.compile(r'((?P<name>.+)\.)?{}$'.format(zone.name[:-1]))
name_re = re.compile(fr'((?P<name>.+)\.)?{zone.name[:-1]}$')
data = defaultdict(lambda: defaultdict(list))
for line in self._lines():
@ -168,7 +168,7 @@ class TinyDnsBaseSource(BaseSource):
for name, types in data.items():
for _type, d in types.items():
data_for = getattr(self, '_data_for_{}'.format(_type))
data_for = getattr(self, f'_data_for_{_type}')
data = data_for(_type, d)
if data:
record = Record.new(zone, name, data, source=self,
@ -180,7 +180,7 @@ class TinyDnsBaseSource(BaseSource):
'record=%s', record)
def _populate_in_addr_arpa(self, zone, lenient):
name_re = re.compile(r'(?P<name>.+)\.{}$'.format(zone.name[:-1]))
name_re = re.compile(fr'(?P<name>.+)\.{zone.name[:-1]}$')
for line in self._lines():
_type = line[0]
@ -196,11 +196,11 @@ class TinyDnsBaseSource(BaseSource):
if line[0].endswith('in-addr.arpa'):
# since it's already in in-addr.arpa format
match = name_re.match(line[0])
value = '{}.'.format(line[1])
value = f'{line[1]}.'
else:
addr = ip_address(line[1])
match = name_re.match(addr.reverse_pointer)
value = '{}.'.format(line[0])
value = f'{line[0]}.'
if match:
try:
@ -217,8 +217,7 @@ class TinyDnsBaseSource(BaseSource):
try:
zone.add_record(record, lenient=lenient)
except DuplicateRecordException:
self.log.warn('Duplicate PTR record for {}, '
'skipping'.format(addr))
self.log.warn(f'Duplicate PTR record for {addr}, skipping')
class TinyDnsFileSource(TinyDnsBaseSource):
@ -236,7 +235,7 @@ class TinyDnsFileSource(TinyDnsBaseSource):
NOTE: timestamps & lo fields are ignored if present.
'''
def __init__(self, id, directory, default_ttl=3600):
self.log = logging.getLogger('TinyDnsFileSource[{}]'.format(id))
self.log = logging.getLogger(f'TinyDnsFileSource[{id}]')
self.log.debug('__init__: id=%s, directory=%s, default_ttl=%d', id,
directory, default_ttl)
super(TinyDnsFileSource, self).__init__(id, default_ttl)


+ 2
- 2
octodns/yaml.py View File

@ -26,8 +26,8 @@ class SortEnforcingLoader(SafeLoader):
expected = keys_sorted.pop(0)
if key != expected:
raise ConstructorError(None, None, 'keys out of order: '
'expected {} got {} at {}'
.format(expected, key, node.start_mark))
f'expected {expected} got {key} at ' +
str(node.start_mark))
return dict(ret)


+ 13
- 16
octodns/zone.py View File

@ -37,8 +37,7 @@ class Zone(object):
def __init__(self, name, sub_zones):
if not name[-1] == '.':
raise Exception('Invalid zone name {}, missing ending dot'
.format(name))
raise Exception(f'Invalid zone name {name}, missing ending dot')
# Force everything to lowercase just to be safe
self.name = text_type(name).lower() if name else name
self.sub_zones = sub_zones
@ -47,7 +46,7 @@ class Zone(object):
self._records = defaultdict(set)
# optional leading . to match empty hostname
# optional trailing . b/c some sources don't have it on their fqdn
self._name_re = re.compile(r'\.?{}?$'.format(name))
self._name_re = re.compile(fr'\.?{name}?$')
# Copy-on-write semantics support, when `not None` this property will
# point to a location with records for this `Zone`. Once `hydrated`
@ -75,14 +74,13 @@ class Zone(object):
if not lenient and last in self.sub_zones:
if name != last:
# it's a record for something under a sub-zone
raise SubzoneRecordException('Record {} is under a '
'managed subzone'
.format(record.fqdn))
raise SubzoneRecordException(f'Record {record.fqdn} is under '
'a managed subzone')
elif record._type != 'NS':
# It's a non NS record for exactly a sub-zone
raise SubzoneRecordException('Record {} a managed sub-zone '
'and not of type NS'
.format(record.fqdn))
raise SubzoneRecordException(f'Record {record.fqdn} a '
'managed sub-zone and not of '
'type NS')
if replace:
# will remove it if it exists
@ -91,16 +89,15 @@ class Zone(object):
node = self._records[name]
if record in node:
# We already have a record at this node of this type
raise DuplicateRecordException('Duplicate record {}, type {}'
.format(record.fqdn,
record._type))
raise DuplicateRecordException(f'Duplicate record {record.fqdn}, '
f'type {record._type}')
elif not lenient and ((record._type == 'CNAME' and len(node) > 0) or
('CNAME' in [r._type for r in node])):
# We're adding a CNAME to existing records or adding to an existing
# CNAME
raise InvalidNodeException('Invalid state, CNAME at {} cannot '
'coexist with other records'
.format(record.fqdn))
raise InvalidNodeException('Invalid state, CNAME at '
f'{record.fqdn} cannot coexist with '
'other records')
node.add(record)
@ -236,4 +233,4 @@ class Zone(object):
return copy
def __repr__(self):
return 'Zone<{}>'.format(self.name)
return f'Zone<{self.name}>'

+ 1
- 1
tests/test_octodns_processor_ownership.py View File

@ -129,7 +129,7 @@ class TestOwnershipProcessor(TestCase):
# two delete changes.
the_a = records['the-a']
plan.existing.add_record(the_a)
name = '{}.a.the-a'.format(ownership.txt_name)
name = f'{ownership.txt_name}.a.the-a'
the_a_ownership = Record.new(zone, name, {
'ttl': 30,
'type': 'TXT',


+ 35
- 37
tests/test_octodns_provider_azuredns.py View File

@ -616,7 +616,7 @@ class TestAzureDnsProvider(TestCase):
'/resourceGroups/' + rg + \
'/providers/Microsoft.Network/trafficManagerProfiles/'
prefix = 'foo--unit--tests'
name_format = prefix + '-{}'
name_format = prefix + '-'
id_format = base_id + name_format
header = MonitorConfigCustomHeadersItem(name='Host',
@ -628,8 +628,8 @@ class TestAzureDnsProvider(TestCase):
profiles = [
Profile(
id=id_format.format('pool-two'),
name=name_format.format('pool-two'),
id=f'{id_format}pool-two',
name=f'{name_format}pool-two',
traffic_routing_method='Weighted',
dns_config=DnsConfig(ttl=60),
monitor_config=monitor,
@ -649,8 +649,8 @@ class TestAzureDnsProvider(TestCase):
],
),
Profile(
id=id_format.format('rule-one'),
name=name_format.format('rule-one'),
id=f'{id_format}rule-one',
name=f'{name_format}rule-one',
traffic_routing_method='Priority',
dns_config=DnsConfig(ttl=60),
monitor_config=monitor,
@ -664,7 +664,7 @@ class TestAzureDnsProvider(TestCase):
Endpoint(
name='two',
type=nested,
target_resource_id=id_format.format('pool-two'),
target_resource_id=f'{id_format}pool-two',
priority=2,
),
Endpoint(
@ -682,8 +682,8 @@ class TestAzureDnsProvider(TestCase):
],
),
Profile(
id=id_format.format('rule-two'),
name=name_format.format('rule-two'),
id=f'{id_format}rule-two',
name=f'{name_format}rule-two',
traffic_routing_method='Priority',
dns_config=DnsConfig(ttl=60),
monitor_config=monitor,
@ -691,7 +691,7 @@ class TestAzureDnsProvider(TestCase):
Endpoint(
name='two',
type=nested,
target_resource_id=id_format.format('pool-two'),
target_resource_id=f'{id_format}pool-two',
priority=1,
),
Endpoint(
@ -719,13 +719,13 @@ class TestAzureDnsProvider(TestCase):
geo_mapping=['GEO-AF', 'DE', 'US-CA', 'GEO-AP'],
name='one',
type=nested,
target_resource_id=id_format.format('rule-one'),
target_resource_id=f'{id_format}rule-one',
),
Endpoint(
geo_mapping=['WORLD'],
name='two',
type=nested,
target_resource_id=id_format.format('rule-two'),
target_resource_id=f'{id_format}rule-two',
),
],
),
@ -923,8 +923,8 @@ class TestAzureDnsProvider(TestCase):
tm_id = provider._profile_name_to_id
root_profile_name = _root_traffic_manager_name(record)
extra_profile = Profile(
id=tm_id('{}-pool-random'.format(root_profile_name)),
name='{}-pool-random'.format(root_profile_name),
id=tm_id(f'{root_profile_name}-pool-random'),
name=f'{root_profile_name}-pool-random',
traffic_routing_method='Weighted',
dns_config=sample_profile.dns_config,
monitor_config=sample_profile.monitor_config,
@ -1150,7 +1150,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1191,7 +1191,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=tm_id(tm_suffix)),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
with self.assertRaises(AzureException) as ctx:
provider._populate_record(zone, azrecord)
self.assertTrue(text_type(ctx).startswith(
@ -1261,7 +1261,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1320,7 +1320,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1416,7 +1416,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1496,7 +1496,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1604,7 +1604,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1711,7 +1711,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1857,7 +1857,7 @@ class TestAzureDnsProvider(TestCase):
azrecord = RecordSet(
ttl=60, target_resource=SubResource(id=None))
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.values, ['255.255.255.255'])
@ -1870,7 +1870,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1939,7 +1939,7 @@ class TestAzureDnsProvider(TestCase):
azrecord = RecordSet(
ttl=60, target_resource=SubResource(id=None))
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.values, ['::1'])
@ -1951,7 +1951,7 @@ class TestAzureDnsProvider(TestCase):
target_resource=SubResource(id=profiles[-1].id),
)
azrecord.name = record.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record._type)
azrecord.type = f'Microsoft.Network/dnszones/{record._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.dynamic._data(), record.dynamic._data())
@ -1969,8 +1969,8 @@ class TestAzureDnsProvider(TestCase):
prefix = 'foo--unit--tests'
expected_seen = {
prefix, '{}-pool-two'.format(prefix),
'{}-rule-one'.format(prefix), '{}-rule-two'.format(prefix),
prefix, f'{prefix}-pool-two', f'{prefix}-rule-one',
f'{prefix}-rule-two',
}
# test no change
@ -1997,9 +1997,7 @@ class TestAzureDnsProvider(TestCase):
tm_sync.assert_called_once()
# test that new profile was successfully inserted in cache
new_profile = provider._get_tm_profile_by_name(
'{}-pool-two'.format(prefix)
)
new_profile = provider._get_tm_profile_by_name(f'{prefix}-pool-two')
self.assertEqual(new_profile.endpoints[0].weight, 14)
def test_sync_traffic_managers_duplicate(self):
@ -2028,8 +2026,8 @@ class TestAzureDnsProvider(TestCase):
prefix2 = _root_traffic_manager_name(record2)
tm_id = provider._profile_name_to_id
extra_profile = Profile(
id=tm_id('{}-pool-random'.format(prefix2)),
name='{}-pool-random'.format(prefix2),
id=tm_id(f'{prefix2}-pool-random'),
name=f'{prefix2}-pool-random',
traffic_routing_method='Weighted',
dns_config=sample_profile.dns_config,
monitor_config=sample_profile.monitor_config,
@ -2042,8 +2040,8 @@ class TestAzureDnsProvider(TestCase):
# implicitly asserts that non-matching profile is not included
prefix = _root_traffic_manager_name(record)
self.assertEqual(provider._find_traffic_managers(record), {
prefix, '{}-pool-two'.format(prefix),
'{}-rule-one'.format(prefix), '{}-rule-two'.format(prefix),
prefix, f'{prefix}-pool-two', f'{prefix}-rule-one',
f'{prefix}-rule-two',
})
def test_traffic_manager_gc(self):
@ -2158,8 +2156,8 @@ class TestAzureDnsProvider(TestCase):
tm_id = provider._profile_name_to_id
root_profile_name = _root_traffic_manager_name(dynamic_record)
extra_profile = Profile(
id=tm_id('{}-pool-random'.format(root_profile_name)),
name='{}-pool-random'.format(root_profile_name),
id=tm_id(f'{root_profile_name}-pool-random'),
name=f'{root_profile_name}-pool-random',
traffic_routing_method='Weighted',
dns_config=sample_profile.dns_config,
monitor_config=sample_profile.monitor_config,
@ -2183,7 +2181,7 @@ class TestAzureDnsProvider(TestCase):
azrecord = RecordSet(
ttl=record1.ttl, target_resource=SubResource(id=None))
azrecord.name = record1.name or '@'
azrecord.type = 'Microsoft.Network/dnszones/{}'.format(record1._type)
azrecord.type = f'Microsoft.Network/dnszones/{record1._type}'
record2 = provider._populate_record(zone, azrecord)
self.assertEqual(record2.value, 'iam.invalid.')


+ 1
- 1
tests/test_octodns_provider_base.py View File

@ -508,7 +508,7 @@ class TestBaseProvider(TestCase):
normal.supports_warn_or_except('Hello World!', 'Goodbye')
normal.log.warning.assert_called_once()
normal.log.warning.assert_has_calls([
call('Hello World!; Goodbye')
call('%s; %s', 'Hello World!', 'Goodbye')
])
strict = MinimalProvider(strict_supports=True)


+ 9
- 14
tests/test_octodns_provider_cloudflare.py View File

@ -157,36 +157,31 @@ class TestCloudflareProvider(TestCase):
# zones
with open('tests/fixtures/cloudflare-zones-page-1.json') as fh:
mock.get('{}?page=1'.format(base), status_code=200,
text=fh.read())
mock.get(f'{base}?page=1', status_code=200, text=fh.read())
with open('tests/fixtures/cloudflare-zones-page-2.json') as fh:
mock.get('{}?page=2'.format(base), status_code=200,
text=fh.read())
mock.get('{}?page=3'.format(base), status_code=200,
mock.get(f'{base}?page=2', status_code=200, text=fh.read())
mock.get(f'{base}?page=3', status_code=200,
json={'result': [], 'result_info': {'count': 0,
'per_page': 0}})
base = '{}/234234243423aaabb334342aaa343435'.format(base)
base = f'{base}/234234243423aaabb334342aaa343435'
# pagerules/URLFWD
with open('tests/fixtures/cloudflare-pagerules.json') as fh:
mock.get('{}/pagerules?status=active'.format(base),
mock.get(f'{base}/pagerules?status=active',
status_code=200, text=fh.read())
# records
base = '{}/dns_records'.format(base)
base = f'{base}/dns_records'
with open('tests/fixtures/cloudflare-dns_records-'
'page-1.json') as fh:
mock.get('{}?page=1'.format(base), status_code=200,
text=fh.read())
mock.get(f'{base}?page=1', status_code=200, text=fh.read())
with open('tests/fixtures/cloudflare-dns_records-'
'page-2.json') as fh:
mock.get('{}?page=2'.format(base), status_code=200,
text=fh.read())
mock.get(f'{base}?page=2', status_code=200, text=fh.read())
with open('tests/fixtures/cloudflare-dns_records-'
'page-3.json') as fh:
mock.get('{}?page=3'.format(base), status_code=200,
text=fh.read())
mock.get(f'{base}?page=3', status_code=200, text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 4
- 8
tests/test_octodns_provider_constellix.py View File

@ -192,17 +192,13 @@ class TestConstellixProvider(TestCase):
with requests_mock() as mock:
base = 'https://api.dns.constellix.com/v1'
with open('tests/fixtures/constellix-domains.json') as fh:
mock.get('{}{}'.format(base, '/domains'),
text=fh.read())
mock.get(f'{base}/domains', text=fh.read())
with open('tests/fixtures/constellix-records.json') as fh:
mock.get('{}{}'.format(base, '/domains/123123/records'),
text=fh.read())
mock.get(f'{base}/domains/123123/records', text=fh.read())
with open('tests/fixtures/constellix-pools.json') as fh:
mock.get('{}{}'.format(base, '/pools/A'),
text=fh.read())
mock.get(f'{base}/pools/A', text=fh.read())
with open('tests/fixtures/constellix-geofilters.json') as fh:
mock.get('{}{}'.format(base, '/geoFilters'),
text=fh.read())
mock.get(f'{base}/geoFilters', text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 2
- 2
tests/test_octodns_provider_digitalocean.py View File

@ -77,9 +77,9 @@ class TestDigitalOceanProvider(TestCase):
base = 'https://api.digitalocean.com/v2/domains/unit.tests/' \
'records?page='
with open('tests/fixtures/digitalocean-page-1.json') as fh:
mock.get('{}{}'.format(base, 1), text=fh.read())
mock.get(f'{base}1', text=fh.read())
with open('tests/fixtures/digitalocean-page-2.json') as fh:
mock.get('{}{}'.format(base, 2), text=fh.read())
mock.get(f'{base}2', text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 2
- 2
tests/test_octodns_provider_dnsimple.py View File

@ -79,9 +79,9 @@ class TestDnsimpleProvider(TestCase):
base = 'https://api.dnsimple.com/v2/42/zones/unit.tests/' \
'records?page='
with open('tests/fixtures/dnsimple-page-1.json') as fh:
mock.get('{}{}'.format(base, 1), text=fh.read())
mock.get(f'{base}1', text=fh.read())
with open('tests/fixtures/dnsimple-page-2.json') as fh:
mock.get('{}{}'.format(base, 2), text=fh.read())
mock.get(f'{base}2', text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 2
- 3
tests/test_octodns_provider_dnsmadeeasy.py View File

@ -95,10 +95,9 @@ class TestDnsMadeEasyProvider(TestCase):
with requests_mock() as mock:
base = 'https://api.dnsmadeeasy.com/V2.0/dns/managed'
with open('tests/fixtures/dnsmadeeasy-domains.json') as fh:
mock.get('{}{}'.format(base, '/'), text=fh.read())
mock.get(f'{base}/', text=fh.read())
with open('tests/fixtures/dnsmadeeasy-records.json') as fh:
mock.get('{}{}'.format(base, '/123123/records'),
text=fh.read())
mock.get(f'{base}/123123/records', text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 9
- 13
tests/test_octodns_provider_easydns.py View File

@ -73,11 +73,9 @@ class TestEasyDNSProvider(TestCase):
with requests_mock() as mock:
base = 'https://rest.easydns.net/zones/records/'
with open('tests/fixtures/easydns-records.json') as fh:
mock.get('{}{}'.format(base, 'parsed/unit.tests'),
text=fh.read())
mock.get(f'{base}parsed/unit.tests', text=fh.read())
with open('tests/fixtures/easydns-records.json') as fh:
mock.get('{}{}'.format(base, 'all/unit.tests'),
text=fh.read())
mock.get(f'{base}all/unit.tests', text=fh.read())
provider.populate(zone)
self.assertEquals(15, len(zone.records))
@ -97,7 +95,7 @@ class TestEasyDNSProvider(TestCase):
with requests_mock() as mock:
base = 'https://rest.easydns.net/'
mock.get('{}{}'.format(base, 'domain/unit.tests'), status_code=400,
mock.get(f'{base}domain/unit.tests', status_code=400,
text='{"id":"not_found","message":"The resource you '
'were accessing could not be found."}')
@ -120,18 +118,16 @@ class TestEasyDNSProvider(TestCase):
with requests_mock() as mock:
base = 'https://rest.easydns.net/'
mock.get('{}{}'.format(base, 'domain/unit.tests'), status_code=404,
mock.get(f'{base}domain/unit.tests', status_code=404,
text='{"id":"not_found","message":"The resource you '
'were accessing could not be found."}')
mock.put('{}{}'.format(base, 'domains/add/unit.tests'),
status_code=200,
mock.put(f'{base}domains/add/unit.tests', status_code=200,
text='{"id":"OK","message":"Zone created."}')
mock.get('{}{}'.format(base, 'zones/records/parsed/unit.tests'),
mock.get(f'{base}zones/records/parsed/unit.tests',
status_code=404,
text='{"id":"not_found","message":"The resource you '
'were accessing could not be found."}')
mock.get('{}{}'.format(base, 'zones/records/all/unit.tests'),
status_code=404,
mock.get(f'{base}zones/records/all/unit.tests', status_code=404,
text='{"id":"not_found","message":"The resource you '
'were accessing could not be found."}')
@ -188,9 +184,9 @@ class TestEasyDNSProvider(TestCase):
}
with requests_mock() as mock:
base = 'https://rest.easydns.net/'
mock.put('{}{}'.format(base, 'domains/add/unit.tests'),
mock.put(f'{base}domains/add/unit.tests',
status_code=201, text='{"id":"OK"}')
mock.get('{}{}'.format(base, 'zones/records/all/unit.tests'),
mock.get(f'{base}zones/records/all/unit.tests',
text=json.dumps(domain_after_creation))
mock.delete(ANY, text='{"id":"OK"}')
provider._client.domain_create('unit.tests')


+ 1
- 1
tests/test_octodns_provider_gcore.py View File

@ -195,7 +195,7 @@ class TestGCoreProvider(TestCase):
str(ctx.exception).startswith(
"filter is enabled, but no pools where built for"
),
"{} - is not start from desired text".format(ctx.exception),
f"{ctx.exception} - is not start from desired text",
)
def test_apply(self):


+ 2
- 3
tests/test_octodns_provider_googlecloud.py View File

@ -156,8 +156,7 @@ class DummyResourceRecordSet:
return False
def __repr__(self):
return "{} {} {} {!s}"\
.format(self.name, self.record_type, self.ttl, self.rrdatas)
return f"{self.name} {self.record_type} {self.ttl} {self.rrdatas}"
def __hash__(self):
return hash(repr(self))
@ -419,7 +418,7 @@ class TestGoogleCloudProvider(TestCase):
dummy_gcloud_zone = DummyGoogleCloudZone("unit.tests")
for octo_record in octo_records:
_rrset_func = getattr(
provider, '_rrset_for_{}'.format(octo_record._type))
provider, f'_rrset_for_{octo_record._type}')
self.assertEqual(
_rrset_func(dummy_gcloud_zone, octo_record).record_type,
octo_record._type


+ 2
- 2
tests/test_octodns_provider_hetzner.py View File

@ -68,9 +68,9 @@ class TestHetznerProvider(TestCase):
with requests_mock() as mock:
base = provider._client.BASE_URL
with open('tests/fixtures/hetzner-zones.json') as fh:
mock.get('{}/zones'.format(base), text=fh.read())
mock.get(f'{base}/zones', text=fh.read())
with open('tests/fixtures/hetzner-records.json') as fh:
mock.get('{}/records'.format(base), text=fh.read())
mock.get(f'{base}/records', text=fh.read())
zone = Zone('unit.tests.', [])
provider.populate(zone)


+ 4
- 6
tests/test_octodns_provider_ns1.py View File

@ -1653,7 +1653,7 @@ class TestNs1ProviderDynamic(TestCase):
'meta': {
'priority': 1,
'weight': 12,
'note': 'from:{}'.format(catchall_pool_name),
'note': f'from:{catchall_pool_name}',
},
'region': catchall_pool_name,
}, {
@ -1774,8 +1774,7 @@ class TestNs1ProviderDynamic(TestCase):
partial_oc_cntry_list
data4 = provider._data_for_A('A', ns1_record)
for c in partial_oc_cntry_list:
self.assertTrue(
'OC-{}'.format(c) in data4['dynamic']['rules'][0]['geos'])
self.assertTrue(f'OC-{c}' in data4['dynamic']['rules'][0]['geos'])
# NA test cases
# 1. Full list of countries should return 'NA' in geos
@ -1792,8 +1791,7 @@ class TestNs1ProviderDynamic(TestCase):
partial_na_cntry_list
data6 = provider._data_for_A('A', ns1_record)
for c in partial_na_cntry_list:
self.assertTrue(
'NA-{}'.format(c) in data6['dynamic']['rules'][0]['geos'])
self.assertTrue(f'NA-{c}' in data6['dynamic']['rules'][0]['geos'])
# Test out fallback only pools and new-style notes
ns1_record = {
@ -1919,7 +1917,7 @@ class TestNs1ProviderDynamic(TestCase):
'meta': {
'priority': 1,
'weight': 12,
'note': 'from:{}'.format(catchall_pool_name),
'note': f'from:{catchall_pool_name}',
},
'region': catchall_pool_name,
}, {


+ 3
- 4
tests/test_octodns_provider_route53.py View File

@ -291,8 +291,8 @@ class TestRoute53Provider(TestCase):
record = Record.new(expected, name, data)
expected.add_record(record)
caller_ref = '{}:A:unit.tests.:1324' \
.format(Route53Provider.HEALTH_CHECK_VERSION)
caller_ref = f'{Route53Provider.HEALTH_CHECK_VERSION}:A:unit.tests.:1324'
health_checks = [{
'Id': '42',
'CallerReference': caller_ref,
@ -1243,8 +1243,7 @@ class TestRoute53Provider(TestCase):
provider, stubber = self._get_stubbed_provider()
# No match based on type
caller_ref = \
'{}:AAAA:foo1234'.format(Route53Provider.HEALTH_CHECK_VERSION)
caller_ref = f'{Route53Provider.HEALTH_CHECK_VERSION}:AAAA:foo1234'
health_checks = [{
'Id': '42',
# No match based on version


+ 40
- 48
tests/test_octodns_provider_selectel.py View File

@ -196,12 +196,12 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_populate(self, fake_http):
zone = Zone('unit.tests.', [])
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/unit.tests/records/',
json=self.api_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.head('{}/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
provider = SelectelProvider(123, 'secret_token')
@ -220,12 +220,12 @@ class TestSelectelProvider(TestCase):
"email": "support@unit.tests"})
zone = Zone('unit.tests.', [])
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/unit.tests/records/',
json=more_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.head('{}/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
zone.add_record(Record.new(self.zone, 'unsup', {
@ -249,14 +249,13 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_apply(self, fake_http):
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=list())
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/unit.tests/records/', json=list())
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': '0'})
fake_http.head('{}/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
fake_http.post('{}/100000/records/'.format(self.API_URL), json=list())
fake_http.post(f'{self.API_URL}/100000/records/', json=list())
provider = SelectelProvider(123, 'test_token')
@ -271,8 +270,8 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_domain_list(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
expected = {'unit.tests': self.domain[0]}
@ -283,8 +282,8 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_authentication_fail(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), status_code=401)
fake_http.head('{}/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', status_code=401)
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
with self.assertRaises(Exception) as ctx:
@ -294,20 +293,17 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_not_exist_domain(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), status_code=404, json='')
fake_http.head('{}/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', status_code=404, json='')
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
fake_http.post('{}/'.format(self.API_URL),
json={"name": "unit.tests",
"create_date": 1507154178,
"id": 100000})
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=list())
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.post(f'{self.API_URL}/', json={"name": "unit.tests",
"create_date": 1507154178,
"id": 100000})
fake_http.get(f'{self.API_URL}/unit.tests/records/', json=list())
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': str(len(self.api_record))})
fake_http.post('{}/100000/records/'.format(self.API_URL),
json=list())
fake_http.post(f'{self.API_URL}/100000/records/', json=list())
provider = SelectelProvider(123, 'test_token')
@ -322,11 +318,11 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_delete_no_exist_record(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.get('{}/100000/records/'.format(self.API_URL), json=list())
fake_http.head('{}/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.get(f'{self.API_URL}/100000/records/', json=list())
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': '0'})
provider = SelectelProvider(123, 'test_token')
@ -348,23 +344,19 @@ class TestSelectelProvider(TestCase):
"type": "A",
"id": 100002,
"name": "unit.tests"}] # exist
fake_http.get('{}/unit.tests/records/'.format(self.API_URL),
json=exist_record)
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.get('{}/100000/records/'.format(self.API_URL),
json=exist_record)
fake_http.head('{}/unit.tests/records/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/unit.tests/records/', json=exist_record)
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.get(f'{self.API_URL}/100000/records/', json=exist_record)
fake_http.head(f'{self.API_URL}/unit.tests/records/',
headers={'X-Total-Count': str(len(exist_record))})
fake_http.head('{}/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
fake_http.head('{}/100000/records/'.format(self.API_URL),
fake_http.head(f'{self.API_URL}/100000/records/',
headers={'X-Total-Count': str(len(exist_record))})
fake_http.post('{}/100000/records/'.format(self.API_URL),
fake_http.post(f'{self.API_URL}/100000/records/',
json=list())
fake_http.delete('{}/100000/records/100001'.format(self.API_URL),
text="")
fake_http.delete('{}/100000/records/100002'.format(self.API_URL),
text="")
fake_http.delete(f'{self.API_URL}/100000/records/100001', text="")
fake_http.delete(f'{self.API_URL}/100000/records/100002', text="")
provider = SelectelProvider(123, 'test_token')
@ -379,8 +371,8 @@ class TestSelectelProvider(TestCase):
@requests_mock.Mocker()
def test_include_change_returns_false(self, fake_http):
fake_http.get('{}/'.format(self.API_URL), json=self.domain)
fake_http.head('{}/'.format(self.API_URL),
fake_http.get(f'{self.API_URL}/', json=self.domain)
fake_http.head(f'{self.API_URL}/',
headers={'X-Total-Count': str(len(self.domain))})
provider = SelectelProvider(123, 'test_token')
zone = Zone('unit.tests.', [])


+ 25
- 29
tests/test_octodns_provider_ultra.py View File

@ -41,7 +41,7 @@ class TestUltraProvider(TestCase):
# Bad Auth
with requests_mock() as mock:
mock.post('{}{}'.format(self.host, path), status_code=401,
mock.post(f'{self.host}{path}', status_code=401,
text='{"errorCode": 60001}')
with self.assertRaises(Exception) as ctx:
UltraProvider('test', 'account', 'user', 'wrongpass')
@ -50,7 +50,7 @@ class TestUltraProvider(TestCase):
# Good Auth
with requests_mock() as mock:
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
mock.post('{}{}'.format(self.host, path), status_code=200,
mock.post(f'{self.host}{path}', status_code=200,
request_headers=headers,
text='{"token type": "Bearer", "refresh_token": "abc", '
'"access_token":"123", "expires_in": "3600"}')
@ -67,7 +67,7 @@ class TestUltraProvider(TestCase):
# Test authorization issue
with requests_mock() as mock:
mock.get('{}{}'.format(self.host, path), status_code=400,
mock.get(f'{self.host}{path}', status_code=400,
json={"errorCode": 60004,
"errorMessage": "Authorization Header required"})
with self.assertRaises(HTTPError) as ctx:
@ -76,7 +76,7 @@ class TestUltraProvider(TestCase):
# Test no zones exist error
with requests_mock() as mock:
mock.get('{}{}'.format(self.host, path), status_code=404,
mock.get(f'{self.host}{path}', status_code=404,
headers={'Authorization': 'Bearer 123'},
json=self.empty_body)
zones = provider.zones
@ -109,7 +109,7 @@ class TestUltraProvider(TestCase):
]
}
mock.get('{}{}'.format(self.host, path), status_code=200,
mock.get(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'},
json=payload)
zones = provider.zones
@ -120,14 +120,14 @@ class TestUltraProvider(TestCase):
# Test different paging behavior
provider._zones = None
with requests_mock() as mock:
mock.get('{}{}?limit=100&q=zone_type%3APRIMARY&offset=0'
.format(self.host, path), status_code=200,
mock.get(f'{self.host}{path}?limit=100&q=zone_type%3APRIMARY&'
'offset=0', status_code=200,
json={"resultInfo": {"totalCount": 15,
"offset": 0,
"returnedCount": 10},
"zones": []})
mock.get('{}{}?limit=100&q=zone_type%3APRIMARY&offset=10'
.format(self.host, path), status_code=200,
mock.get(f'{self.host}{path}?limit=100&q=zone_type%3APRIMARY'
'&offset=10', status_code=200,
json={"resultInfo": {"totalCount": 15,
"offset": 10,
"returnedCount": 5},
@ -141,7 +141,7 @@ class TestUltraProvider(TestCase):
payload = {'a': 1}
with requests_mock() as mock:
mock.get('{}{}'.format(self.host, path), status_code=401,
mock.get(f'{self.host}{path}', status_code=401,
headers={'Authorization': 'Bearer 123'}, json={})
with self.assertRaises(Exception) as ctx:
provider._get(path)
@ -149,37 +149,37 @@ class TestUltraProvider(TestCase):
# Test all GET patterns
with requests_mock() as mock:
mock.get('{}{}'.format(self.host, path), status_code=200,
mock.get(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'},
json=payload)
provider._get(path, json=payload)
mock.get('{}{}?a=1'.format(self.host, path), status_code=200,
mock.get(f'{self.host}{path}?a=1', status_code=200,
headers={'Authorization': 'Bearer 123'})
provider._get(path, params=payload, json_response=False)
# Test all POST patterns
with requests_mock() as mock:
mock.post('{}{}'.format(self.host, path), status_code=200,
mock.post(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'},
json=payload)
provider._post(path, json=payload)
mock.post('{}{}'.format(self.host, path), status_code=200,
mock.post(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'},
text="{'a':1}")
provider._post(path, data=payload, json_response=False)
# Test all PUT patterns
with requests_mock() as mock:
mock.put('{}{}'.format(self.host, path), status_code=200,
mock.put(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'},
json=payload)
provider._put(path, json=payload)
# Test all DELETE patterns
with requests_mock() as mock:
mock.delete('{}{}'.format(self.host, path), status_code=200,
mock.delete(f'{self.host}{path}', status_code=200,
headers={'Authorization': 'Bearer 123'})
provider._delete(path, json_response=False)
@ -221,10 +221,9 @@ class TestUltraProvider(TestCase):
zone_path = '/v2/zones'
rec_path = '/v2/zones/octodns1.test./rrsets'
with requests_mock() as mock:
mock.get('{}{}?limit=100&q=zone_type%3APRIMARY&offset=0'
.format(self.host, zone_path),
status_code=200, json=zone_payload)
mock.get('{}{}?offset=0&limit=100'.format(self.host, rec_path),
mock.get(f'{self.host}{zone_path}?limit=100&q=zone_type%3APRIMARY&'
'offset=0', status_code=200, json=zone_payload)
mock.get(f'{self.host}{rec_path}?offset=0&limit=100',
status_code=200, json=records_payload)
zone = Zone('octodns1.test.', [])
@ -257,21 +256,18 @@ class TestUltraProvider(TestCase):
path = '/v2/zones'
with requests_mock() as mock:
with open('tests/fixtures/ultra-zones-page-1.json') as fh:
mock.get('{}{}?limit=100&q=zone_type%3APRIMARY&offset=0'
.format(self.host, path),
status_code=200, text=fh.read())
mock.get(f'{self.host}{path}?limit=100&q=zone_type%3APRIMARY&'
'offset=0', status_code=200, text=fh.read())
with open('tests/fixtures/ultra-zones-page-2.json') as fh:
mock.get('{}{}?limit=100&q=zone_type%3APRIMARY&offset=10'
.format(self.host, path),
status_code=200, text=fh.read())
mock.get(f'{self.host}{path}?limit=100&q=zone_type%3APRIMARY&'
'offset=10', status_code=200, text=fh.read())
with open('tests/fixtures/ultra-records-page-1.json') as fh:
rec_path = '/v2/zones/octodns1.test./rrsets'
mock.get('{}{}?offset=0&limit=100'.format(self.host, rec_path),
mock.get(f'{self.host}{rec_path}?offset=0&limit=100',
status_code=200, text=fh.read())
with open('tests/fixtures/ultra-records-page-2.json') as fh:
rec_path = '/v2/zones/octodns1.test./rrsets'
mock.get('{}{}?offset=10&limit=100'
.format(self.host, rec_path),
mock.get(f'{self.host}{rec_path}?offset=10&limit=100',
status_code=200, text=fh.read())
zone = Zone('octodns1.test.', [])


+ 4
- 5
tests/test_octodns_provider_yaml.py View File

@ -309,7 +309,7 @@ class TestSplitYamlProvider(TestCase):
# ensure correctness.
for record_name in ('_srv._tcp', 'mx', 'naptr', 'sub', 'txt',
'urlfwd'):
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
yaml_file = join(zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
data = safe_load(fh.read())
@ -318,7 +318,7 @@ class TestSplitYamlProvider(TestCase):
# These are stored as singular "value." Again, check each file.
for record_name in ('aaaa', 'cname', 'dname', 'included', 'ptr',
'spf', 'www.sub', 'www'):
yaml_file = join(zone_dir, '{}.yaml'.format(record_name))
yaml_file = join(zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
data = safe_load(fh.read())
@ -327,7 +327,7 @@ class TestSplitYamlProvider(TestCase):
# Again with the plural, this time checking dynamic.tests.
for record_name in ('a', 'aaaa', 'real-ish-a'):
yaml_file = join(
dynamic_zone_dir, '{}.yaml'.format(record_name))
dynamic_zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
data = safe_load(fh.read())
@ -337,8 +337,7 @@ class TestSplitYamlProvider(TestCase):
# Singular again.
for record_name in ('cname', 'simple-weighted'):
yaml_file = join(
dynamic_zone_dir, '{}.yaml'.format(record_name))
yaml_file = join(dynamic_zone_dir, f'{record_name}.yaml')
self.assertTrue(isfile(yaml_file))
with open(yaml_file) as fh:
data = safe_load(fh.read())


+ 1
- 1
tests/test_octodns_record.py View File

@ -2664,7 +2664,7 @@ class TestRecordValidation(TestCase):
'ttl': 600,
'value': v
})
self.assertEquals(['missing {}'.format(k)], ctx.exception.reasons)
self.assertEquals([f'missing {k}'], ctx.exception.reasons)
# non-int order
v = dict(value)


+ 2
- 2
tests/test_octodns_source_envvar.py View File

@ -14,7 +14,7 @@ class TestEnvVarSource(TestCase):
source = EnvVarSource('testid', envvar, 'recordname', ttl=120)
with self.assertRaises(EnvironmentVariableNotFoundException) as ctx:
source._read_variable()
msg = 'Unknown environment variable {}'.format(envvar)
msg = f'Unknown environment variable {envvar}'
self.assertEquals(msg, text_type(ctx.exception))
with patch.dict('os.environ', {envvar: 'testvalue'}):
@ -35,7 +35,7 @@ class TestEnvVarSource(TestCase):
self.assertEquals(1, len(zone.records))
record = list(zone.records)[0]
self.assertEquals(name, record.name)
self.assertEquals('{}.{}'.format(name, zone_name), record.fqdn)
self.assertEquals(f'{name}.{zone_name}', record.fqdn)
self.assertEquals('TXT', record._type)
self.assertEquals(1, len(record.values))
self.assertEquals(value, record.values[0])

Loading…
Cancel
Save