1"""SimpleDBBackend class with methods for supported APIs.""" 2import re 3from boto3 import Session 4from collections import defaultdict 5from moto.core import BaseBackend, BaseModel 6from threading import Lock 7 8from .exceptions import InvalidDomainName, UnknownDomainName 9 10 11class FakeItem(BaseModel): 12 def __init__(self): 13 self.attributes = [] 14 self.lock = Lock() 15 16 def get_attributes(self, names): 17 if not names: 18 return self.attributes 19 return [attr for attr in self.attributes if attr["name"] in names] 20 21 def put_attributes(self, attributes): 22 # Replacing attributes involves quite a few loops 23 # Lock this, so we know noone else touches this list while we're operating on it 24 with self.lock: 25 for attr in attributes: 26 if attr.get("replace", "false").lower() == "true": 27 self._remove_attributes(attr["name"]) 28 self.attributes.append(attr) 29 30 def _remove_attributes(self, name): 31 self.attributes = [attr for attr in self.attributes if attr["name"] != name] 32 33 34class FakeDomain(BaseModel): 35 def __init__(self, name): 36 self.name = name 37 self.items = defaultdict(FakeItem) 38 39 def get(self, item_name, attribute_names): 40 item = self.items[item_name] 41 return item.get_attributes(attribute_names) 42 43 def put(self, item_name, attributes): 44 item = self.items[item_name] 45 item.put_attributes(attributes) 46 47 48class SimpleDBBackend(BaseBackend): 49 def __init__(self, region_name=None): 50 self.region_name = region_name 51 self.domains = dict() 52 53 def reset(self): 54 region_name = self.region_name 55 self.__dict__ = {} 56 self.__init__(region_name) 57 58 def create_domain(self, domain_name): 59 self._validate_domain_name(domain_name) 60 self.domains[domain_name] = FakeDomain(name=domain_name) 61 62 def list_domains(self, max_number_of_domains, next_token): 63 """ 64 The `max_number_of_domains` and `next_token` parameter have not been implemented yet - we simply return all domains. 65 """ 66 return self.domains.keys(), None 67 68 def delete_domain(self, domain_name): 69 self._validate_domain_name(domain_name) 70 # Ignore unknown domains - AWS does the same 71 self.domains.pop(domain_name, None) 72 73 def _validate_domain_name(self, domain_name): 74 # Domain Name needs to have at least 3 chars 75 # Can only contain characters: a-z, A-Z, 0-9, '_', '-', and '.' 76 if not re.match("^[a-zA-Z0-9-_.]{3,}$", domain_name): 77 raise InvalidDomainName(domain_name) 78 79 def _get_domain(self, domain_name): 80 if domain_name not in self.domains: 81 raise UnknownDomainName() 82 return self.domains[domain_name] 83 84 def get_attributes(self, domain_name, item_name, attribute_names, consistent_read): 85 """ 86 Behaviour for the consistent_read-attribute is not yet implemented 87 """ 88 self._validate_domain_name(domain_name) 89 domain = self._get_domain(domain_name) 90 return domain.get(item_name, attribute_names) 91 92 def put_attributes(self, domain_name, item_name, attributes, expected): 93 """ 94 Behaviour for the expected-attribute is not yet implemented. 95 """ 96 self._validate_domain_name(domain_name) 97 domain = self._get_domain(domain_name) 98 domain.put(item_name, attributes) 99 100 101sdb_backends = {} 102for available_region in Session().get_available_regions("sdb"): 103 sdb_backends[available_region] = SimpleDBBackend(available_region) 104for available_region in Session().get_available_regions( 105 "sdb", partition_name="aws-us-gov" 106): 107 sdb_backends[available_region] = SimpleDBBackend(available_region) 108for available_region in Session().get_available_regions("sdb", partition_name="aws-cn"): 109 sdb_backends[available_region] = SimpleDBBackend(available_region) 110