# 介绍
- 快速多正则多模匹配,10万个正则表达式编译2分钟,匹配1千次耗时最低0.003s以内,支持多正则之间的布尔运算匹配,基于 hyperscan / pyeda
- 提供封装的 HTTP 服务,编译和匹配过程使用不同进程,支持热更新正则库
## 使用
```shell
pip install fast-multi-regex
fast_multi_regex_server --help
fast_multi_regex_server
```
构建正则库,即增删改 matchers_config_folder 中的 json 文件(允许子文件夹嵌套,不能以 . 开头),也支持其他格式的配置文件,包括 .jsonc .toml .yaml,格式和 json 一样,可以使用注释。json 文件例子参数解释:
```json
{ // 一个正则库, 超过 10 万不重复的 regex 建议拆分成多个库
"cache_size": 128, // match 接口查询的缓存大小
"literal": false, // 是否使用字面量匹配(用于正则当作普通字符更快匹配,但是大部分flag失效)
"targets": [ // 如果要删除一个线上库不能直接将 targets 置空,而是要删除配置文件,空 targets 的配置不会被解析
{
"mark": "example", // 正则组名称,不能重复。不提供或为空则默认为 auto_i,i 为 targets 中所在的索引,从1开始
"regexs": [
{
"expression": "例子", // 正则表达式, 或编号的布尔组合(搭配HS_FLAG_COMBINATION标志,支持 & | ~ () 运算符,编号为正则在 regexs 中的索引号,或者 mark.索引号。例如 "(0|1)&2" 或 "test.0|test.1&test.2")
"flag": 40, // hyperscan flag,例如 40 代表一个正则只匹配一次,并且以字符而不是字节为单位匹配
"flag_ext": { // 扩展标志,null 代表不使用
"min_offset": null, // 最小偏移量, 匹配的结束位置大于等于这个
"max_offset": null, // 最大偏移量, 匹配的结束位置小于等于这个
"min_length": null, // 最小长度,匹配到的长度要大于等于这个
"edit_distance": null, // 在给定的编辑距离(用于计算从一个字符串转换到另一个字符串所需要的最少单字符编辑操作数)内匹配此表达式
"hamming_distance": null // 在给定的汉明距离(计算在相同位置上字符不同的数量,只适用于长度相同的字符串)内匹配此表达式
},
"min_match_count": 1, // 最少匹配次数, 必须大于等于1,大于1不能含HS_FLAG_SINGLEMATCH标志,适用于 match_strict
"max_match_count": 0 // 最多匹配次数,0 代表不限制,适用于 match_strict
}
],
"min_regex_count": 1, // regexs 最少需要满足的正则数量,必须大于等于0,0 代表全部要满足,适用于 match_strict
"max_regex_count": 0, // regexs 最多允许满足的正则数量, 必须大于等于0。0 代表不限制,适用于 match_strict
"bool_expr": "", // 逻辑表达式,为空则不使用,使用则 regex_count 限制失效,适用于 match_strict。支持 => <=> :? ^ & | ~ () 运算符, 变量名为字母 r 加上正则在 regexs 中的索引号,例如 r0, r1, r2,所有正则索引都要覆盖到
"priority": 1, // 优先级, 越小越优先返回,需要 is_sort=true
"meta": {} // 元数据,可以不提供 或 null 或 object,用于匹配返回时携带
}
]
}
```
访问 `http://127.0.0.1:8000/docs` 查看接口文档,接口访问例子:
```shell
curl -X 'POST' \
'http://127.0.0.1:8000/match' \
-H 'accept: application/json' \
-H 'Authorization: Bearer test' \
-H 'Content-Type: application/json' \
-d '{
"qs": [
{
"query": "这是一个例子",
"db": "default",
"method": "strict",
"is_sort": true,
"detailed_level": 2,
"match_top_n": 0
}
]
}'
```
直接调包使用:
```python
from fast_multi_regex import MultiRegexMatcher
mr = MultiRegexMatcher()
targets = [
{
"mark": "test",
"regexs": [
{
"expression": "test",
}
],
}
]
mr.compile(targets)
print(mr.match_first("test"))
```
可以配置 prometheus.yml 查看统计指标:
```yaml
scrape_configs:
- job_name: fast_multi_regex_server
bearer_token: test
static_configs:
- targets: ['127.0.0.1:8000']
```
## 不同配置文件的读取速度测试
仅供参考,速度 JSON > JSONC > TOML > YAML,不需要注释最好用 JSON,target 数量不太多的情况下 YAML 也可以接受。
```
# 1000 对象
读取 JSON 文件的平均时间: 0.001775 秒
读取 YAML 文件的平均时间: 0.373537 秒
读取 TOML 文件的平均时间: 0.080640 秒
读取 JSONC 文件的平均时间: 0.010024 秒
读取 YAML 文件相对于 JSON 慢了 210.49 倍
读取 TOML 文件相对于 JSON 慢了 45.44 倍
读取 JSONC 文件相对于 JSON 慢了 5.65 倍
# 100 对象
读取 JSON 文件的平均时间: 0.000653 秒
读取 YAML 文件的平均时间: 0.037624 秒
读取 TOML 文件的平均时间: 0.008857 秒
读取 JSONC 文件的平均时间: 0.001412 秒
读取 YAML 文件相对于 JSON 慢了 57.58 倍
读取 TOML 文件相对于 JSON 慢了 13.56 倍
读取 JSONC 文件相对于 JSON 慢了 2.16 倍
```
Raw data
{
"_id": null,
"home_page": "https://github.com/aitsc/fast_multi_regex",
"name": "fast-multi-regex",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.9",
"maintainer_email": null,
"keywords": "tools",
"author": "tanshicheng",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/f4/1c/61801142c57d84fbb0e2dee69e06696b58ecfb050e6b5f68aa4a5c406b89/fast_multi_regex-0.25.tar.gz",
"platform": null,
"description": "# \u4ecb\u7ecd\n\n- \u5feb\u901f\u591a\u6b63\u5219\u591a\u6a21\u5339\u914d\uff0c10\u4e07\u4e2a\u6b63\u5219\u8868\u8fbe\u5f0f\u7f16\u8bd12\u5206\u949f\uff0c\u5339\u914d1\u5343\u6b21\u8017\u65f6\u6700\u4f4e0.003s\u4ee5\u5185\uff0c\u652f\u6301\u591a\u6b63\u5219\u4e4b\u95f4\u7684\u5e03\u5c14\u8fd0\u7b97\u5339\u914d\uff0c\u57fa\u4e8e hyperscan / pyeda\n- \u63d0\u4f9b\u5c01\u88c5\u7684 HTTP \u670d\u52a1\uff0c\u7f16\u8bd1\u548c\u5339\u914d\u8fc7\u7a0b\u4f7f\u7528\u4e0d\u540c\u8fdb\u7a0b\uff0c\u652f\u6301\u70ed\u66f4\u65b0\u6b63\u5219\u5e93\n\n## \u4f7f\u7528\n```shell\npip install fast-multi-regex\nfast_multi_regex_server --help\nfast_multi_regex_server\n```\n\n\u6784\u5efa\u6b63\u5219\u5e93\uff0c\u5373\u589e\u5220\u6539 matchers_config_folder \u4e2d\u7684 json \u6587\u4ef6\uff08\u5141\u8bb8\u5b50\u6587\u4ef6\u5939\u5d4c\u5957\uff0c\u4e0d\u80fd\u4ee5 . \u5f00\u5934\uff09\uff0c\u4e5f\u652f\u6301\u5176\u4ed6\u683c\u5f0f\u7684\u914d\u7f6e\u6587\u4ef6\uff0c\u5305\u62ec .jsonc .toml .yaml\uff0c\u683c\u5f0f\u548c json \u4e00\u6837\uff0c\u53ef\u4ee5\u4f7f\u7528\u6ce8\u91ca\u3002json \u6587\u4ef6\u4f8b\u5b50\u53c2\u6570\u89e3\u91ca\uff1a\n```json\n{ // \u4e00\u4e2a\u6b63\u5219\u5e93, \u8d85\u8fc7 10 \u4e07\u4e0d\u91cd\u590d\u7684 regex \u5efa\u8bae\u62c6\u5206\u6210\u591a\u4e2a\u5e93\n \"cache_size\": 128, // match \u63a5\u53e3\u67e5\u8be2\u7684\u7f13\u5b58\u5927\u5c0f\n \"literal\": false, // \u662f\u5426\u4f7f\u7528\u5b57\u9762\u91cf\u5339\u914d\uff08\u7528\u4e8e\u6b63\u5219\u5f53\u4f5c\u666e\u901a\u5b57\u7b26\u66f4\u5feb\u5339\u914d\uff0c\u4f46\u662f\u5927\u90e8\u5206flag\u5931\u6548\uff09\n \"targets\": [ // \u5982\u679c\u8981\u5220\u9664\u4e00\u4e2a\u7ebf\u4e0a\u5e93\u4e0d\u80fd\u76f4\u63a5\u5c06 targets \u7f6e\u7a7a\uff0c\u800c\u662f\u8981\u5220\u9664\u914d\u7f6e\u6587\u4ef6\uff0c\u7a7a targets \u7684\u914d\u7f6e\u4e0d\u4f1a\u88ab\u89e3\u6790\n {\n \"mark\": \"example\", // \u6b63\u5219\u7ec4\u540d\u79f0\uff0c\u4e0d\u80fd\u91cd\u590d\u3002\u4e0d\u63d0\u4f9b\u6216\u4e3a\u7a7a\u5219\u9ed8\u8ba4\u4e3a auto_i\uff0ci \u4e3a targets \u4e2d\u6240\u5728\u7684\u7d22\u5f15\uff0c\u4ece1\u5f00\u59cb\n \"regexs\": [\n {\n \"expression\": \"\u4f8b\u5b50\", // \u6b63\u5219\u8868\u8fbe\u5f0f, \u6216\u7f16\u53f7\u7684\u5e03\u5c14\u7ec4\u5408\uff08\u642d\u914dHS_FLAG_COMBINATION\u6807\u5fd7\uff0c\u652f\u6301 & | ~ () \u8fd0\u7b97\u7b26\uff0c\u7f16\u53f7\u4e3a\u6b63\u5219\u5728 regexs \u4e2d\u7684\u7d22\u5f15\u53f7\uff0c\u6216\u8005 mark.\u7d22\u5f15\u53f7\u3002\u4f8b\u5982 \"(0|1)&2\" \u6216 \"test.0|test.1&test.2\"\uff09\n \"flag\": 40, // hyperscan flag\uff0c\u4f8b\u5982 40 \u4ee3\u8868\u4e00\u4e2a\u6b63\u5219\u53ea\u5339\u914d\u4e00\u6b21\uff0c\u5e76\u4e14\u4ee5\u5b57\u7b26\u800c\u4e0d\u662f\u5b57\u8282\u4e3a\u5355\u4f4d\u5339\u914d\n \"flag_ext\": { // \u6269\u5c55\u6807\u5fd7\uff0cnull \u4ee3\u8868\u4e0d\u4f7f\u7528\n \"min_offset\": null, // \u6700\u5c0f\u504f\u79fb\u91cf, \u5339\u914d\u7684\u7ed3\u675f\u4f4d\u7f6e\u5927\u4e8e\u7b49\u4e8e\u8fd9\u4e2a\n \"max_offset\": null, // \u6700\u5927\u504f\u79fb\u91cf, \u5339\u914d\u7684\u7ed3\u675f\u4f4d\u7f6e\u5c0f\u4e8e\u7b49\u4e8e\u8fd9\u4e2a\n \"min_length\": null, // \u6700\u5c0f\u957f\u5ea6\uff0c\u5339\u914d\u5230\u7684\u957f\u5ea6\u8981\u5927\u4e8e\u7b49\u4e8e\u8fd9\u4e2a\n \"edit_distance\": null, // \u5728\u7ed9\u5b9a\u7684\u7f16\u8f91\u8ddd\u79bb(\u7528\u4e8e\u8ba1\u7b97\u4ece\u4e00\u4e2a\u5b57\u7b26\u4e32\u8f6c\u6362\u5230\u53e6\u4e00\u4e2a\u5b57\u7b26\u4e32\u6240\u9700\u8981\u7684\u6700\u5c11\u5355\u5b57\u7b26\u7f16\u8f91\u64cd\u4f5c\u6570)\u5185\u5339\u914d\u6b64\u8868\u8fbe\u5f0f\n \"hamming_distance\": null // \u5728\u7ed9\u5b9a\u7684\u6c49\u660e\u8ddd\u79bb(\u8ba1\u7b97\u5728\u76f8\u540c\u4f4d\u7f6e\u4e0a\u5b57\u7b26\u4e0d\u540c\u7684\u6570\u91cf,\u53ea\u9002\u7528\u4e8e\u957f\u5ea6\u76f8\u540c\u7684\u5b57\u7b26\u4e32)\u5185\u5339\u914d\u6b64\u8868\u8fbe\u5f0f\n },\n \"min_match_count\": 1, // \u6700\u5c11\u5339\u914d\u6b21\u6570, \u5fc5\u987b\u5927\u4e8e\u7b49\u4e8e1\uff0c\u5927\u4e8e1\u4e0d\u80fd\u542bHS_FLAG_SINGLEMATCH\u6807\u5fd7\uff0c\u9002\u7528\u4e8e match_strict\n \"max_match_count\": 0 // \u6700\u591a\u5339\u914d\u6b21\u6570\uff0c0 \u4ee3\u8868\u4e0d\u9650\u5236\uff0c\u9002\u7528\u4e8e match_strict\n }\n ],\n \"min_regex_count\": 1, // regexs \u6700\u5c11\u9700\u8981\u6ee1\u8db3\u7684\u6b63\u5219\u6570\u91cf\uff0c\u5fc5\u987b\u5927\u4e8e\u7b49\u4e8e0\uff0c0 \u4ee3\u8868\u5168\u90e8\u8981\u6ee1\u8db3\uff0c\u9002\u7528\u4e8e match_strict\n \"max_regex_count\": 0, // regexs \u6700\u591a\u5141\u8bb8\u6ee1\u8db3\u7684\u6b63\u5219\u6570\u91cf, \u5fc5\u987b\u5927\u4e8e\u7b49\u4e8e0\u30020 \u4ee3\u8868\u4e0d\u9650\u5236\uff0c\u9002\u7528\u4e8e match_strict\n \"bool_expr\": \"\", // \u903b\u8f91\u8868\u8fbe\u5f0f\uff0c\u4e3a\u7a7a\u5219\u4e0d\u4f7f\u7528\uff0c\u4f7f\u7528\u5219 regex_count \u9650\u5236\u5931\u6548\uff0c\u9002\u7528\u4e8e match_strict\u3002\u652f\u6301 => <=> :? ^ & | ~ () \u8fd0\u7b97\u7b26, \u53d8\u91cf\u540d\u4e3a\u5b57\u6bcd r \u52a0\u4e0a\u6b63\u5219\u5728 regexs \u4e2d\u7684\u7d22\u5f15\u53f7\uff0c\u4f8b\u5982 r0, r1, r2\uff0c\u6240\u6709\u6b63\u5219\u7d22\u5f15\u90fd\u8981\u8986\u76d6\u5230\n \"priority\": 1, // \u4f18\u5148\u7ea7, \u8d8a\u5c0f\u8d8a\u4f18\u5148\u8fd4\u56de\uff0c\u9700\u8981 is_sort=true\n \"meta\": {} // \u5143\u6570\u636e\uff0c\u53ef\u4ee5\u4e0d\u63d0\u4f9b \u6216 null \u6216 object\uff0c\u7528\u4e8e\u5339\u914d\u8fd4\u56de\u65f6\u643a\u5e26\n }\n ]\n}\n```\n\n\u8bbf\u95ee `http://127.0.0.1:8000/docs` \u67e5\u770b\u63a5\u53e3\u6587\u6863\uff0c\u63a5\u53e3\u8bbf\u95ee\u4f8b\u5b50\uff1a\n```shell\ncurl -X 'POST' \\\n 'http://127.0.0.1:8000/match' \\\n -H 'accept: application/json' \\\n -H 'Authorization: Bearer test' \\\n -H 'Content-Type: application/json' \\\n -d '{\n \"qs\": [\n {\n \"query\": \"\u8fd9\u662f\u4e00\u4e2a\u4f8b\u5b50\",\n \"db\": \"default\",\n \"method\": \"strict\",\n \"is_sort\": true,\n \"detailed_level\": 2,\n \"match_top_n\": 0\n }\n ]\n}'\n```\n\n\u76f4\u63a5\u8c03\u5305\u4f7f\u7528\uff1a\n```python\nfrom fast_multi_regex import MultiRegexMatcher\nmr = MultiRegexMatcher()\ntargets = [\n {\n \"mark\": \"test\",\n \"regexs\": [\n {\n \"expression\": \"test\",\n }\n ],\n }\n]\nmr.compile(targets)\nprint(mr.match_first(\"test\"))\n```\n\n\u53ef\u4ee5\u914d\u7f6e prometheus.yml \u67e5\u770b\u7edf\u8ba1\u6307\u6807\uff1a\n```yaml\nscrape_configs:\n - job_name: fast_multi_regex_server\n bearer_token: test\n static_configs:\n - targets: ['127.0.0.1:8000']\n```\n\n## \u4e0d\u540c\u914d\u7f6e\u6587\u4ef6\u7684\u8bfb\u53d6\u901f\u5ea6\u6d4b\u8bd5\n\u4ec5\u4f9b\u53c2\u8003\uff0c\u901f\u5ea6 JSON > JSONC > TOML > YAML\uff0c\u4e0d\u9700\u8981\u6ce8\u91ca\u6700\u597d\u7528 JSON\uff0ctarget \u6570\u91cf\u4e0d\u592a\u591a\u7684\u60c5\u51b5\u4e0b YAML \u4e5f\u53ef\u4ee5\u63a5\u53d7\u3002\n```\n# 1000 \u5bf9\u8c61\n\u8bfb\u53d6 JSON \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.001775 \u79d2\n\u8bfb\u53d6 YAML \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.373537 \u79d2\n\u8bfb\u53d6 TOML \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.080640 \u79d2\n\u8bfb\u53d6 JSONC \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.010024 \u79d2\n\u8bfb\u53d6 YAML \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 210.49 \u500d\n\u8bfb\u53d6 TOML \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 45.44 \u500d\n\u8bfb\u53d6 JSONC \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 5.65 \u500d\n\n# 100 \u5bf9\u8c61\n\u8bfb\u53d6 JSON \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.000653 \u79d2\n\u8bfb\u53d6 YAML \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.037624 \u79d2\n\u8bfb\u53d6 TOML \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.008857 \u79d2\n\u8bfb\u53d6 JSONC \u6587\u4ef6\u7684\u5e73\u5747\u65f6\u95f4: 0.001412 \u79d2\n\u8bfb\u53d6 YAML \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 57.58 \u500d\n\u8bfb\u53d6 TOML \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 13.56 \u500d\n\u8bfb\u53d6 JSONC \u6587\u4ef6\u76f8\u5bf9\u4e8e JSON \u6162\u4e86 2.16 \u500d\n```\n",
"bugtrack_url": null,
"license": "GPLv3",
"summary": "Fast multi-regex, multi-pattern, boolean expression matching",
"version": "0.25",
"project_urls": {
"Homepage": "https://github.com/aitsc/fast_multi_regex"
},
"split_keywords": [
"tools"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "f41c61801142c57d84fbb0e2dee69e06696b58ecfb050e6b5f68aa4a5c406b89",
"md5": "c488309a4966611eac429899f4aa46c3",
"sha256": "c6300374eb4dbe5f251354d4f173c9dadc7edb532dfbe84aff00d67788e9cc3b"
},
"downloads": -1,
"filename": "fast_multi_regex-0.25.tar.gz",
"has_sig": false,
"md5_digest": "c488309a4966611eac429899f4aa46c3",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.9",
"size": 41434,
"upload_time": "2024-09-13T13:26:33",
"upload_time_iso_8601": "2024-09-13T13:26:33.818050Z",
"url": "https://files.pythonhosted.org/packages/f4/1c/61801142c57d84fbb0e2dee69e06696b58ecfb050e6b5f68aa4a5c406b89/fast_multi_regex-0.25.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-09-13 13:26:33",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "aitsc",
"github_project": "fast_multi_regex",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "aiohttp",
"specs": [
[
"==",
"3.9.5"
]
]
},
{
"name": "aiosignal",
"specs": [
[
"==",
"1.3.1"
]
]
},
{
"name": "annotated-types",
"specs": [
[
"==",
"0.6.0"
]
]
},
{
"name": "anyio",
"specs": [
[
"==",
"4.3.0"
]
]
},
{
"name": "asyncio",
"specs": [
[
"==",
"3.4.3"
]
]
},
{
"name": "attrs",
"specs": [
[
"==",
"23.2.0"
]
]
},
{
"name": "build",
"specs": [
[
"==",
"1.2.1"
]
]
},
{
"name": "certifi",
"specs": [
[
"==",
"2024.2.2"
]
]
},
{
"name": "charset-normalizer",
"specs": [
[
"==",
"3.3.2"
]
]
},
{
"name": "click",
"specs": [
[
"==",
"8.1.7"
]
]
},
{
"name": "dnspython",
"specs": [
[
"==",
"2.6.1"
]
]
},
{
"name": "email-validator",
"specs": [
[
"==",
"2.1.1"
]
]
},
{
"name": "fastapi",
"specs": [
[
"==",
"0.111.0"
]
]
},
{
"name": "fastapi-cli",
"specs": [
[
"==",
"0.0.3"
]
]
},
{
"name": "frozenlist",
"specs": [
[
"==",
"1.4.1"
]
]
},
{
"name": "h11",
"specs": [
[
"==",
"0.14.0"
]
]
},
{
"name": "httpcore",
"specs": [
[
"==",
"1.0.5"
]
]
},
{
"name": "httptools",
"specs": [
[
"==",
"0.6.1"
]
]
},
{
"name": "httpx",
"specs": [
[
"==",
"0.27.0"
]
]
},
{
"name": "hyperscan",
"specs": [
[
"==",
"0.7.7"
]
]
},
{
"name": "idna",
"specs": [
[
"==",
"3.7"
]
]
},
{
"name": "jinja2",
"specs": [
[
"==",
"3.1.4"
]
]
},
{
"name": "json-spec",
"specs": [
[
"==",
"0.12.0"
]
]
},
{
"name": "jsoncomment",
"specs": [
[
"==",
"0.4.2"
]
]
},
{
"name": "markdown-it-py",
"specs": [
[
"==",
"3.0.0"
]
]
},
{
"name": "markupsafe",
"specs": [
[
"==",
"2.1.5"
]
]
},
{
"name": "mdurl",
"specs": [
[
"==",
"0.1.2"
]
]
},
{
"name": "multidict",
"specs": [
[
"==",
"6.0.5"
]
]
},
{
"name": "orjson",
"specs": [
[
"==",
"3.10.3"
]
]
},
{
"name": "packaging",
"specs": [
[
"==",
"24.0"
]
]
},
{
"name": "pip-tools",
"specs": [
[
"==",
"7.4.1"
]
]
},
{
"name": "prometheus-client",
"specs": [
[
"==",
"0.20.0"
]
]
},
{
"name": "psutil",
"specs": [
[
"==",
"6.0.0"
]
]
},
{
"name": "pydantic",
"specs": [
[
"==",
"2.7.1"
]
]
},
{
"name": "pydantic-core",
"specs": [
[
"==",
"2.18.2"
]
]
},
{
"name": "pyeda",
"specs": [
[
"==",
"0.29.0"
]
]
},
{
"name": "pygments",
"specs": [
[
"==",
"2.18.0"
]
]
},
{
"name": "pyproject-hooks",
"specs": [
[
"==",
"1.1.0"
]
]
},
{
"name": "python-dotenv",
"specs": [
[
"==",
"1.0.1"
]
]
},
{
"name": "python-multipart",
"specs": [
[
"==",
"0.0.9"
]
]
},
{
"name": "pyyaml",
"specs": [
[
"==",
"6.0.1"
]
]
},
{
"name": "requests",
"specs": [
[
"==",
"2.31.0"
]
]
},
{
"name": "rich",
"specs": [
[
"==",
"13.7.1"
]
]
},
{
"name": "shellingham",
"specs": [
[
"==",
"1.5.4"
]
]
},
{
"name": "sniffio",
"specs": [
[
"==",
"1.3.1"
]
]
},
{
"name": "starlette",
"specs": [
[
"==",
"0.37.2"
]
]
},
{
"name": "toml",
"specs": [
[
"==",
"0.10.2"
]
]
},
{
"name": "tsc-base",
"specs": [
[
"==",
"0.43"
]
]
},
{
"name": "typer",
"specs": [
[
"==",
"0.12.3"
]
]
},
{
"name": "typing-extensions",
"specs": [
[
"==",
"4.11.0"
]
]
},
{
"name": "ujson",
"specs": [
[
"==",
"5.10.0"
]
]
},
{
"name": "urllib3",
"specs": [
[
"==",
"2.2.1"
]
]
},
{
"name": "uvicorn",
"specs": [
[
"==",
"0.29.0"
]
]
},
{
"name": "uvloop",
"specs": [
[
"==",
"0.19.0"
]
]
},
{
"name": "watchdog",
"specs": [
[
"==",
"4.0.0"
]
]
},
{
"name": "watchfiles",
"specs": [
[
"==",
"0.21.0"
]
]
},
{
"name": "websockets",
"specs": [
[
"==",
"12.0"
]
]
},
{
"name": "wheel",
"specs": [
[
"==",
"0.43.0"
]
]
},
{
"name": "yarl",
"specs": [
[
"==",
"1.9.4"
]
]
}
],
"lcname": "fast-multi-regex"
}