Metadata-Version: 2.2
Name: ConfigByLmdb
Version: 0.0.11
Summary: lmdb 自定义封装
Author-email: CC <3204604858@qq.com>
License: The OpenLDAP Public License
          Version 2.8, 17 August 2003
        
        Redistribution and use of this software and associated documentation
        ("Software"), with or without modification, are permitted provided
        that the following conditions are met:
        
        1. Redistributions in source form must retain copyright statements
           and notices,
        
        2. Redistributions in binary form must reproduce applicable copyright
           statements and notices, this list of conditions, and the following
           disclaimer in the documentation and/or other materials provided
           with the distribution,
        
        3. Redistributions must contain a verbatim copy of this document.
        
        The OpenLDAP Foundation may revise this license from time to time.
        Each revision is distinguished by a version number.  You may use
        this Software under terms of this license revision or under the
        terms of any subsequent revision of the license.
        
        THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
        CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
        INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
        AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
        SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
        OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
        INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
        BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
        LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
        LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
        ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
        POSSIBILITY OF SUCH DAMAGE.
        
        The names of the authors and copyright holders must not be used in
        advertising or otherwise to promote the sale, use or other dealing
        in this Software without specific, written prior permission.  Title
        to copyright in this Software shall at all times remain with copyright
        holders.
        
        OpenLDAP is a registered trademark of the OpenLDAP Foundation.
        
        Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
        California, USA.  All Rights Reserved.  Permission to copy and
        distribute verbatim copies of this document is granted.
Project-URL: Homepage, https://github.com/jnwatson/py-lmdb/
Keywords: ConfigByLmdb,ConfigDB,lmdb
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Software Development :: User Interfaces
Requires-Python: >=3.10
Description-Content-Type: text/x-rst
License-File: LICENSE
Requires-Dist: lmdb
Requires-Dist: fastapi
Requires-Dist: uvicorn
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: httpx>=0.22; extra == "test"

=============
ConfigByLmdb
=============

ConfigByLmdb 是一个基于 lmdb 1.5.1 版本 自定义封装的库，主要用于快速动态读写大量配置信息的数据库工具。

概述
----

lmdb是一个轻量级、本地部署的高性能数据库，ConfigByLmdb 仅对其进行了某些场景简单的接口封装。


安装
----

使用 pip 安装 ConfigByLmdb ：

.. code-block:: bash

    pip install ConfigByLmdb

也可以使用 uv 安装或加入项目依赖：

.. code-block:: bash

    uv pip install ConfigByLmdb       # 安装到当前环境
    uv add ConfigByLmdb               # 加入 pyproject.toml 依赖

本项目遵循 PEP 517 / PEP 621，wheel 为 ``py3-none-any`` ，并已包含完整的 web 界面静态资源，
因此从 wheel 或 sdist 安装（含 uv 的构建隔离）都能直接使用 web 功能。

请注意，由于本项目是 lmdb 的自定义封装版本，可能需要从源代码安装或使用特定的安装步骤。

使用示例
--------

以下是一个简单的使用示例，展示如何使用：

.. code-block:: python

    from ConfigByLmdb import DB,Structure

    db = DB()
    
    db_name = 'db'
    name_db = 'test'

    # 创建或初始化数据库
    print(db.init_db(db_name))

    # 创建命名数据库
    # data = Structure("主键名",[('a',str),('b',str),('c',dict),('d',int),("e",float)]) # 设置数据库结构
    # print(db.create_name_db(db_name,name_db,"这是一个测试命名数据库",data))

    # 添加
    # print(db.write(db_name,name_db,'t1',{'a':"你好",'b':"123",'c':{"f":1,"g":'abc'},"d":189,"e":0.123}))
    # print(db.write(db_name,name_db,'t2',{'a':"hello!",'b':"234",'c':{"f":3,"g":'ret'},"d":49,"e":15.48}))
    # print(db.write(db_name,name_db,'t3',{'a':"A和B",'b':"858",'c':{"f":3,"g":'ret'},"d":49,"e":15.48}))
    # print(db.write(db_name,name_db,json.dumps({'a':"A和B",'b':858}),{'a':"A和B",'b':858}))
    # 错误示范(数据格式不对应)
    # l = []
    # for i in range(10):
    #     l.append({'body'+str(i):{'a':"你好",'b':"318",'c':None,"d":4945,"e":185.48}})
    # print(db.batch_write(db_name,name_db,l))
    # 正确示范
    # l = []
    # import random
    # for i in range(1000):
    #     l.append({'body'+str(i):"a"+str(i * random.randint(1, 100))})
    # print(db.batch_write(db_name,name_db,l))
    # print(db.get_sum(db_name,name_db))

    # 查询
    # print(db.read(db_name,name_db,'body2'))
    # print(db.get(db_name,name_db,['body4','c','f']))
    # print(db.get_limit(db_name,name_db,0,100))
    # 匹配查询
    # print(db.matching(db_name,name_db,"body1",0,10),len(db.matching(db_name,name_db,"body1",0,10)[0]))
    # print(db.matching(db_name,name_db,"修改后的值",0,10),len(db.matching(db_name,name_db,"修改后的值",0,10)[0]))
    # print(db.matching(db_name,name_db,45,matching_level=-2))
    # print(db.precise_matching(db_name,name_db,str({'a':"A和B",'b':858}),matching_level=-2))
    # print(db.precise_matching(db_name,name_db,json.dumps({'a':"A和B",'b':858}),matching_level=0))

    # 删除
    # print(db.read(db_name,name_db,'body5'))
    # print(db.remove(db_name,name_db,['body5','c','f']))
    # print(db.read(db_name,name_db,'body5'))
    # print(db.delete(db_name,name_db,json.dumps({'a':"A和B",'b':858})))
    # print(db.read(db_name,name_db,json.dumps({'a':"A和B",'b':858})))

    # 修改
    # print(db.read(db_name,name_db,'body3'))
    # print(db.updata(db_name,name_db,"body3",{'a':"修改后的值",'b':"315",'c':{"t":1},"d":911,"e":1.48}))
    # print(db.read(db_name,name_db,'body3'))
    # print(db.read(db_name,name_db,'body6'))
    # print(db.set(db_name,name_db,['body6','a'],"修改后的值"))
    # print(db.read(db_name,name_db,'body6'))

    # 数据库信息
    # print(db.get_sum(db_name,name_db))
    # print(db.get_name_database_list(db_name)) # 指定数据库下命名数据库列表
    # print(db.get_db_name_list()) # 所有数据库列表
    # print(db.get_db_info(db_name)) # 数据库配置信息

    # 数据库操作
    # print(db.drop_name_db(db_name,name_db)) # 删除指定命名数据库
    # print(db.get_name_database_list(db_name))
    # print(db.env_close(db_name)) # 关闭指定数据库
    # print(db.cleanup(db_name)) # 删除指定数据库
    # print(db.get_db_name_list())

    ########################################################
    # web访问
    from ConfigByLmdb.web import run
    run()
    # 打开浏览器访问
    # http://127.0.0.1:8080/

Web 认证（可选）
-------------------

web 服务默认**不需要登录**；只有配置了凭据才会启用认证，未配置时行为与旧版本完全一致。
支持两种形式，可单独使用也可同时启用：

1. **authenticator 动态口令**（TOTP，RFC 6238）——默认 ``period=180`` ，即验证码 **3 分钟变化一次**；
2. **账户 + 密钥**——静态密钥，支持明文或 ``pbkdf2_sha256`` 哈希存储。

认证凭据的解析顺序为：``run(auth=...)`` 参数 > 环境变量 > ``config/web_auth.json`` 配置文件。

方式一：动态口令（authenticator）
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

先生成密钥并换取 otpauth 链接（用认证器 App 扫码或手动录入）：

.. code-block:: python

    from ConfigByLmdb.web_auth import generate_secret, provisioning_uri

    secret = generate_secret()                      # 随机 Base32 密钥
    print(secret)
    print(provisioning_uri(secret, account="admin", period=180))

然后启动服务：

.. code-block:: python

    from ConfigByLmdb.web import run

    run(auth={
        "totp": {"secret": secret, "period": 180, "window": 1},
        "account": "admin",
    })
    # 打开 http://127.0.0.1:8080/ ，输入 App 上显示的动态口令

.. warning::

   认证器 App 对 ``period`` 的支持并不一致：

   * 支持自定义周期（Aegis、2FAS、Bitwarden 等）可以直接使用 ``period=180`` ；
   * **Google Authenticator 硬编码 30 秒**，会忽略 otpauth 中的 ``period`` ，
     因此 ``period=180`` 时它显示的验证码永远无法通过校验。

   需要在 Google Authenticator 上使用时，请改用「标准 30 秒周期 + 放宽容错窗口」的等价配置：

   .. code-block:: python

       run(auth={"totp": {"secret": secret, "period": 30, "window": 5}})
       # 验证码 30 秒一变，但 3 分钟内提交都有效

方式二：账户 + 密钥
~~~~~~~~~~~~~~~~~~~

.. code-block:: python

    from ConfigByLmdb.web import run
    from ConfigByLmdb.web_auth import hash_key

    run(auth={
        # 建议存哈希而不是明文
        "keys": {"admin": hash_key("your-static-key")},
        "session": {"secret": "please-change-me", "seconds": 3600},
    })

``keys`` 支持三种写法：``{"admin": "key"}`` 、``[{"account": "admin", "key": "key"}]`` 、
``"admin:key,user2:key2"`` 。

同时启用两种形式
~~~~~~~~~~~~~~~~

.. code-block:: python

    run(auth={
        "methods": ["totp", "key"],
        "totp": {"secret": secret, "period": 180},
        "keys": {"admin": hash_key("your-static-key")},
        "require_both": False,      # True = 必须同时通过两种验证（真正的双因素）
    })

配置文件方式
~~~~~~~~~~~~

在项目目录下创建 ``config/web_auth.json`` （该文件不会被发布到 PyPI）：

.. code-block:: json

    {
      "enabled": true,
      "methods": ["totp", "key"],
      "require_both": false,
      "issuer": "ConfigByLmdb",
      "account": "admin",
      "totp": {"secret": "你的BASE32密钥", "period": 180, "digits": 6, "window": 1},
      "key": {"account": "admin", "key": "你的静态密钥或pbkdf2哈希"},
      "session": {"secret": "会话签名密钥", "seconds": 3600, "secure": false}
    }

环境变量方式
~~~~~~~~~~~~

.. code-block:: bash

    set CONFIGBYLMDB_WEB_TOTP_SECRET=你的BASE32密钥
    set CONFIGBYLMDB_WEB_TOTP_PERIOD=180
    set CONFIGBYLMDB_WEB_ACCOUNT=admin
    set CONFIGBYLMDB_WEB_KEY=你的静态密钥
    set CONFIGBYLMDB_WEB_SESSION_SECRET=会话签名密钥

Linux/macOS 用 ``export`` 代替 ``set`` 。常用变量还有
``CONFIGBYLMDB_WEB_METHODS`` 、``CONFIGBYLMDB_WEB_WINDOW`` 、``CONFIGBYLMDB_WEB_KEYS`` 、
``CONFIGBYLMDB_WEB_SESSION_SECONDS`` 、``CONFIGBYLMDB_WEB_ENABLED`` （设为 ``0`` 可强制关闭）。

自行托管 uvicorn 时
~~~~~~~~~~~~~~~~~~~~

.. code-block:: bash

    uvicorn ConfigByLmdb.web:app --port 8080

此时不会经过 ``run()`` ，但导入时会按「环境变量 > ``config/web_auth.json`` 」自动启用认证；
也可以在代码里先调用 ``ConfigByLmdb.web.configure_auth({...})`` 再启动。

认证相关接口
~~~~~~~~~~~~

============================ ====================================================
接口                          说明
============================ ====================================================
``GET /login``               登录页（后端渲染，包含两种认证形式的表单）
``POST /auth/login``         登录，成功后下发会话 Cookie 并返回 ``token``
``POST /auth/logout``        登出（服务端吊销令牌并清除 Cookie）
``GET|POST /auth/status``    查询认证状态（无需登录，不返回任何密钥）
============================ ====================================================

登录成功后除浏览器 Cookie 外，也可以把返回的 ``token`` 作为接口凭据：

.. code-block:: bash

    curl -X POST http://127.0.0.1:8080/auth/login ^
         -H "Content-Type: application/json" ^
         -d "{\"method\": \"key\", \"account\": \"admin\", \"key\": \"your-static-key\"}"
    # 返回 {"result":true, "token":"...", ...}
    curl -X POST http://127.0.0.1:8080/get_db_name_list ^
         -H "Authorization: Bearer 上一步的token"

安全说明
~~~~~~~~

* 未启用认证时，web 服务对能访问该端口的所有人开放，请勿直接暴露到公网；
* 多进程/多副本部署请显式配置 ``session.secret`` （否则每个进程各自随机生成，
  会话无法互通且重启即失效）；
* 登出采用「令牌吊销表」，只对当前进程生效；需要跨进程即时失效请更换 ``session.secret`` ；
* 连续登录失败达到 ``max_failures`` （默认 10 次 / 300 秒）会临时返回 ``429`` ；
* 动态口令默认开启防重放，同一验证码只能用一次。

贡献
----

我们欢迎任何形式的贡献，包括但不限于：

- 报告问题或错误。
- 提供功能请求或改进建议。

测试
----

测试代码只保留在源码仓库中，**不会随发行包发布**。从源码检出后运行：

.. code-block:: bash

    pip install -e ".[test]"
    python -m pytest ConfigByLmdb/Test/test_web_auth.py -v

许可证
------

本项目采用 OLDAP-2.8 许可证。有关更多信息，请查看 `LICENSE` 文件。
