def get(self, *keys, default=None, cast=None): ''' str ..., maybe any, maybe any -> any | DatabaseError retrieve a given key, if the key is not found `default` will be returned instead >>> values = db.get('nonexistant', 'index', default={}) >>> type(values) dict >>> values = db.get('index', 'that', 'exists', cast=set) >>> type(values) set ''' keys = list(keys) if keys else [''] result = self.query(keys, interpret=True) if not result: return default if cast: try: if not isinstance(result, (list, dict,)): result = [result] return cast(result) except ValueError: raise DatabaseError( 'error: unable to case to ' + str(cast)) from None return result
def _query(args, host='localhost', port=9999, interpret=False, close=True, sock=None): ''' list of str, str, int, bool, bool, bool -> any the real query function, all the others are wrappers send a query to an Apocrypha server, either returning a list of strs or the result of json.loads() on the result ''' args = list(args) remote = (host, port) if interpret and args and args[-1] not in {'-e', '--edit'}: args += ['--edit'] message = '\n'.join(args) + '\n' # if they didn't give us a socket create a new one if not sock: sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(remote) # send the message, get the reply using apocrypha.network calls write(sock, message) result, error = read(sock) if error: sock.close() sock = None raise DatabaseError('error: network length') if close: sock.close() result = list(filter(None, result.split('\n'))) if result and result[0].startswith('error: '): raise DatabaseError(result[0]) from None if interpret: result = json.loads(''.join(result)) if result else None return result, sock
def _error(self, message: str) -> None: ''' @message description of the error that occurred #impure self.output Send an error to the user and stop execution. In headless mode, errors are appended to the class.output list ''' message = 'error: ' + message if self.headless: self.output += [message] raise DatabaseError(message + '\n') print(message, file=sys.stderr) sys.exit(1)
def remove(self, *keys, value): ''' str ..., str | list of str -> none | DatabaseError remove an element from a list, if more than one of the element exists in the list, only one is removed >>> db.set('my', 'list', value=['a', 'b', 'c']) >>> db.remove('my', 'list', value='b') ''' keys = list(keys) if keys else [''] if isinstance(value, str): value = [value] if not isinstance(value, (str, list)): raise DatabaseError('error: {v} is not a str or list') from None self.query(keys + ['-'] + value)
def pop(self, *keys, cast=None): ''' str ... -> any | None ''' keys = list(keys) if keys else [''] result = self.query(keys + ['--pop']) result = result[0] if result else None try: if result and cast: result = cast(result) except ValueError: raise DatabaseError( 'error: cast {c} is not applicable to {t}' .format(c=cast.__name__, t=result)) from None return result
def set(self, *keys, value): ''' str ..., str | list | dict | none -> none set a value for a given key, creating if necessary. can be used to delete keys if value={} >>> events = {'key': 'value'} >>> db.set('devbot', 'events', value=events) >>> db.get('devbot', 'events') {'key': 'value'} ''' keys = list(keys) if keys else [''] try: value = json.dumps(value) self.query(keys + ['--set', value]) except (TypeError, ValueError): raise DatabaseError( 'error: value is not JSON serializable') from None
def append(self, *keys, value): ''' str ..., str | list of str -> none | DatabaseError append an element to an apocrypha list. appending to a str creates a list with the original element and the new element >>> db.append('new key', value='hello') >>> type(db.get('new key')) str >>> db.append('new key', value='there') >>> type(db.get('new key')) list ''' keys = list(keys) if keys else [''] if isinstance(value, str): value = [value] try: self.query(keys + ['+'] + value) except (TypeError, ValueError): raise DatabaseError('error: {v} is not a str or list') from None