用不到 200 行的 Python 代码构建一个最小的 NoSQL 数据库(2)

本篇文章是《【转】用不到 200 行的 Python 代码构建一个最小的 NoSQL 数据库》该篇文章的扩展,同时本篇文章是由 Ricky 写的,不是转载。

首先先来回顾一下上篇文章《【转】用不到 200 行的 Python 代码构建一个最小的 NoSQL 数据库》。上篇文章先介绍了 SQL 和 NoSQL 的一些简单概念和区别,紧接着通过使用 Python 自带的数据结构 —— dict(字典)来简单实现了一个玩具级的 NoSQL 数据库。

这篇文章主要是在上篇文章的基础上,增强了这个玩具级 NoSQL 数据库在 Telnet 时的交互能力。接下来大致说说一些改动的部分,具体细节请下载新版(2.0)旧版(1.0)的代码来比较。

def parse_message(data):

以下是 def parse_message(data) 函数的旧版(1.0)代码:

def parse_message(data):
    """Return a tuple containing the command, the key, and (optionally) the
    value cast to the appropriate type."""
    command, key, value, value_type = data.strip().split(';')
    if value_type:
        if value_type == 'LIST':
            value = value.split(',')
        elif value_type == 'INT':
            value = int(value)
        else:
            value = str(value)
    else:
        value = None
    return command, key, value

以下是 def parse_message(data) 函数的新版(2.0)代码:

def parse_message(data):
    """Return a tuple containing the command, the key, and (optionally) the
    value cast to the appropriate type."""
    try:
        message_list = map(str.strip, data.split(';'))
    except:
        return 'Invalid input! Expected: COMMAND; [KEY]; [VALUE]; [VALUE TYPE].'
    command = key = value = None
    message_len = len(message_list)
    if message_len == 1:
        command = message_list[0]
    elif message_len == 2:
        command, key = message_list
    elif message_len == 3:
        command, key, value = message_list
        value = format_value_type('STR', value)
    elif message_len == 4:
        command, key, value, value_type = message_list
        value = format_value_type(value_type, value)
    return command.upper(), key, value


def format_value_type(value_type, value):
    value_type = value_type.upper()
    if value_type == 'LIST':
        return value.split(',')
    elif value_type == 'INT':
        return int(value)
    else:
        return str(value)

现在从上至下简要地来说说改动的部分。

command, key, value, value_type = data.strip().split(';')

上例是旧版(1.0)的代码,代码只是去除 data(即输入的命令)首尾多余的空格,再根据 ” ; “ 切割字符串(data)。

message_list = map(str.strip, data.split(';'))

上例是新版(2.0)的代码,代码先根据 ” ; “ 切割字符串(data),再对切割好的若干个子字符串分别执行去除首尾多余空格的操作。所以在输入命令的时候,旧版(1.0)代码是这么输入的:

  • PUT;foo;1;INT
  • GET;foo;;
  • PUTLIST;bar;a,b,c;LIST
  • APPEND;bar;d;STRING
  • GETLIST;bar;;
  • STATS;;;
  • INCREMENT;foo;;
  • DELETE;foo;;

而新版(2.0)的代码你还可以这么输入,这使得代码的容错性大大提高:

  • PUT ; foo ; 1 ; INT
  • GET ; foo ; ;
  • PUTLIST ; bar ; a,b,c ; LIST
  • APPEND ; bar ; d;STRING
  • GETLIST ; bar ; ;
  • STATS ; ; ;
  • INCREMENT ; foo ; ;
  • DELETE ; foo ; ;
message_len = len(message_list)
if ...
    ...
elif ...
    ...

上例是新版(2.0)的代码,使用 message_len 的方式来格式化各个字段(command、key 和 value),这么做最大的好处是在输入命令时不需要输入多余的 ” ; “ 分隔符咯。所以新版(2.0)的代码还可以这么输入命令:

  • PUT ; foo ; 1 ; INT
  • GET ; foo
  • PUTLIST ; bar ; a,b,c ; LIST
  • APPEND ; bar ; d;STRING
  • GETLIST ; bar
  • STATS
  • INCREMENT ; foo
  • DELETE ; foo
command.upper()
value_type = value_type.upper()

上例是新版(2.0)的代码,代码还对 command 和 value_type 两个字符串执行了一个强制转换为大写的操作,即 command 和 value_type 两个字符串在用户输入时是不区分大小写的。所以新版(2.0)的代码还可以这么输入命令:

  • put ; foo ; 1 ; int
  • Get ; foo
  • PutlisT ; bar ; a,b,c ; lisT
  • apPENd ; bar ; d;String
  • getLIST ; bar
  • StatS
  • IncreMENt ; foo
  • delete ; foo

def main():

以下是 def main() 函数的旧版(1.0)代码:

def main():
    """Main entry point for script."""
    SOCKET.bind((HOST, PORT))
    SOCKET.listen(1)
    while 1:
        connection, address = SOCKET.accept()
        print 'New connection from [{}]'.format(address)
        data = connection.recv(4096).decode()
        command, key, value = parse_message(data)
        if command == 'STATS':
            response = handle_stats()
        elif command in (
            'GET',
            'GETLIST',
            'INCREMENT',
            'DELETE'
                ):
            response = COMMAND_HANDLERS[command](key)
        elif command in (
            'PUT',
            'PUTLIST',
            'APPEND',
                ):
            response = COMMAND_HANDLERS[command](key, value)
        else:
            response = (False, 'Unknown command type [{}]'.format(command))
        update_stats(command, response[0])
        connection.sendall('{};{}'.format(response[0], response[1]))
        connection.close()

以下是 def main() 函数的新版(2.0)代码:

def main():
    """Main entry point for script."""
    SOCKET.bind((HOST, PORT))
    SOCKET.listen(1)
    print('Listening on {}'.format((HOST, PORT)))
    connection, address = SOCKET.accept()
    print('{} New connection from {}'.format(time.strftime(("%Y/%m/%d %H:%M:%S INFO"), time.localtime()), address))
    while 1:
        connection.sendall(bytearray('REQ >> ', 'utf-8'))
        data = str(connection.recv(4096).decode().strip().replace("\n", "").replace("\r\n", ""))
        #print data
        command, key, value = parse_message(data)
        if command == 'STATS':
            response = handle_stats()
        elif command in ('QUIT', 'EXIT'):
            connection.sendall(bytearray('BYE >> SEE YOU NEXT TIME ...', 'utf-8'))
            connection.close()
            break
        elif command == '':  # 匹配回车换行
            continue
        elif command in ('GET', 'GETLIST', 'INCREMENT', 'DELETE'):
            response = COMMAND_HANDLERS[command](key)
        elif command in ('PUT', 'PUTLIST', 'APPEND'):
            response = COMMAND_HANDLERS[command](key, value)
        else:
            response = (False, 'Unknown command type [{}].'.format(command))
        update_stats(command, response[0])
        data = 'RSP >> {};{}\r\n\r\n'.format(response[0], response[1])
        connection.sendall(bytearray(data, 'utf-8'))

现在从上至下简要地来说说改动的部分。

print('{} New connection from {}'.format(time.strftime(("%Y/%m/%d %H:%M:%S INFO"), time.localtime()), address))

上例是新版(2.0)的代码,首先增加了在新连接接入时的时间提示。

REQ >>
RSP >>
BYE >>

然后增加了三个提示符:“ REQ ” 顾名思义就是请求(request),“ RSP ” 就是响应(response),“ BYE ” 就是再见(good bye)。具体效果如 example 1 所示。

elif command in ('QUIT', 'EXIT'):
    connection.sendall(bytearray('BYE >> SEE YOU NEXT TIME ...', 'utf-8'))
    connection.close()
    break
elif command == '':  # 匹配回车换行
    continue

同时还加入了 ” QUIT ” 和 ” EXIT ” 两条命令,以及支持用户在不输入任何字符时直接换行的操作。具体效果如 example 1 所示。

除此之外还修改了:在使用 Telnet 交互时采用 utf-8 的字符格式;while 循环也不再是摆设(你运行一下旧版的代码就知道咯 …);等等。

example 1

使用 SecureCRT 软件连接玩具级 NoSQL 数据库
使用 SecureCRT 软件连接玩具级 NoSQL 数据库

先使用 SecureCRT 等软件通过 Telnet 的方式连接该玩具级 NoSQL 数据库。

操作玩具级 NoSQL 数据库
操作玩具级 NoSQL 数据库

以上是操作该玩具级 NoSQL 数据库一个非常简单的例子,我们存入了一个键名为 foo 值为 1 的键值对(put;foo;1;int),数据库提示保存成功(True;key [foo] set to [1].),然后退出数据库(quit),数据库提示退出成功(SEE YOU NEXT TIME …)。

example 2

在本篇文章前半部分,有提及过下面这个例子:

  • put ; foo ; 1 ; int
  • Get ; foo
  • PutlisT ; bar ; a,b,c ; lisT
  • apPENd ; bar ; d;String
  • getLIST ; bar
  • StatS
  • IncreMENt ; foo
  • delete ; foo

现在我们来演示一下,如下图所示:

操作玩具级 NoSQL 数据库(2)
操作玩具级 NoSQL 数据库(2)

到这里本篇文章就结束了,enjoy your time ~

源代码下载

请参见文章下方的 Article Attachments 部分,或者点击右侧链接:NoSQL-2.0.py(Python 2.7.14 ,亲测可执行)

打赏作者
这里是 “ CCIE 工程师社区 ” 官方的捐款通道,您是否可以考虑请我们喝杯咖啡呢?

您的支持将鼓励我们继续创作!

[微信] 扫描二维码打赏

[支付宝] 扫描二维码打赏

Article Attachments

Was this article helpful?

Related Articles

Leave A Comment?

This site uses Akismet to reduce spam. Learn how your comment data is processed.