ErisPulse 1.0.0__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ import os
2
+ import json
3
+ import sqlite3
4
+ import importlib.util
5
+ from pathlib import Path
6
+
7
+ class EnvManager:
8
+ _instance = None
9
+ db_path = "./config.db"
10
+
11
+ def __new__(cls, *args, **kwargs):
12
+ if not cls._instance:
13
+ cls._instance = super().__new__(cls)
14
+ return cls._instance
15
+
16
+ def __init__(self):
17
+ if not hasattr(self, "_initialized"):
18
+ self._init_db()
19
+
20
+ def _init_db(self):
21
+ conn = sqlite3.connect(self.db_path)
22
+ cursor = conn.cursor()
23
+ cursor.execute("""
24
+ CREATE TABLE IF NOT EXISTS config (
25
+ key TEXT PRIMARY KEY,
26
+ value TEXT NOT NULL
27
+ )
28
+ """)
29
+ cursor.execute("""
30
+ CREATE TABLE IF NOT EXISTS modules (
31
+ module_name TEXT PRIMARY KEY,
32
+ status INTEGER NOT NULL,
33
+ version TEXT,
34
+ description TEXT,
35
+ author TEXT,
36
+ dependencies TEXT,
37
+ optional_dependencies TEXT
38
+ )
39
+ """)
40
+ conn.commit()
41
+ conn.close()
42
+
43
+ def get(self, key, default=None):
44
+ try:
45
+ with sqlite3.connect(self.db_path) as conn:
46
+ cursor = conn.cursor()
47
+ cursor.execute("SELECT value FROM config WHERE key = ?", (key,))
48
+ result = cursor.fetchone()
49
+ if result:
50
+ try:
51
+ return json.loads(result[0])
52
+ except json.JSONDecodeError:
53
+ return result[0]
54
+ return default
55
+ except sqlite3.OperationalError as e:
56
+ if "no such table" in str(e):
57
+ self._init_db()
58
+ return self.get(key, default)
59
+ else:
60
+ raise
61
+
62
+ def set(self, key, value):
63
+ serialized_value = json.dumps(value) if isinstance(value, (dict, list)) else str(value)
64
+ conn = sqlite3.connect(self.db_path)
65
+ cursor = conn.cursor()
66
+ cursor.execute("INSERT OR REPLACE INTO config (key, value) VALUES (?, ?)", (key, serialized_value))
67
+ conn.commit()
68
+ conn.close()
69
+
70
+ def delete(self, key):
71
+ conn = sqlite3.connect(self.db_path)
72
+ cursor = conn.cursor()
73
+ cursor.execute("DELETE FROM config WHERE key = ?", (key,))
74
+ conn.commit()
75
+ conn.close()
76
+
77
+ def clear(self):
78
+ conn = sqlite3.connect(self.db_path)
79
+ cursor = conn.cursor()
80
+ cursor.execute("DELETE FROM config")
81
+ conn.commit()
82
+ conn.close()
83
+
84
+ def load_env_file(self):
85
+ env_file = Path("env.py")
86
+ if env_file.exists():
87
+ spec = importlib.util.spec_from_file_location("env_module", env_file)
88
+ env_module = importlib.util.module_from_spec(spec)
89
+ spec.loader.exec_module(env_module)
90
+ for key, value in vars(env_module).items():
91
+ if not key.startswith("__") and isinstance(value, (dict, list, str, int, float, bool)):
92
+ self.set(key, value)
93
+ def set_module_status(self, module_name, status):
94
+ with sqlite3.connect(self.db_path) as conn:
95
+ cursor = conn.cursor()
96
+ cursor.execute("""
97
+ UPDATE modules SET status = ? WHERE module_name = ?
98
+ """, (int(status), module_name))
99
+ conn.commit()
100
+
101
+ def get_module_status(self, module_name):
102
+ with sqlite3.connect(self.db_path) as conn:
103
+ cursor = conn.cursor()
104
+ cursor.execute("""
105
+ SELECT status FROM modules WHERE module_name = ?
106
+ """, (module_name,))
107
+ result = cursor.fetchone()
108
+ return bool(result[0]) if result else True
109
+
110
+ def set_all_modules(self, modules_info):
111
+ with sqlite3.connect(self.db_path) as conn:
112
+ cursor = conn.cursor()
113
+ for module_name, module_info in modules_info.items():
114
+ cursor.execute("""
115
+ INSERT OR REPLACE INTO modules (
116
+ module_name, status, version, description, author, dependencies, optional_dependencies
117
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
118
+ """, (
119
+ module_name,
120
+ int(module_info.get('status', True)),
121
+ module_info.get('info', {}).get('version', ''),
122
+ module_info.get('info', {}).get('description', ''),
123
+ module_info.get('info', {}).get('author', ''),
124
+ json.dumps(module_info.get('info', {}).get('dependencies', [])),
125
+ json.dumps(module_info.get('info', {}).get('optional_dependencies', []))
126
+ ))
127
+ conn.commit()
128
+
129
+ def get_all_modules(self):
130
+ with sqlite3.connect(self.db_path) as conn:
131
+ cursor = conn.cursor()
132
+ cursor.execute("SELECT * FROM modules")
133
+ rows = cursor.fetchall()
134
+ modules_info = {}
135
+ for row in rows:
136
+ module_name, status, version, description, author, dependencies, optional_dependencies = row
137
+ modules_info[module_name] = {
138
+ 'status': bool(status),
139
+ 'info': {
140
+ 'version': version,
141
+ 'description': description,
142
+ 'author': author,
143
+ 'dependencies': json.loads(dependencies) if dependencies else [],
144
+ 'optional_dependencies': json.loads(optional_dependencies) if optional_dependencies else []
145
+ }
146
+ }
147
+ return modules_info
148
+
149
+ def get_module(self, module_name):
150
+ with sqlite3.connect(self.db_path) as conn:
151
+ cursor = conn.cursor()
152
+ cursor.execute("SELECT * FROM modules WHERE module_name = ?", (module_name,))
153
+ row = cursor.fetchone()
154
+ if row:
155
+ module_name, status, version, description, author, dependencies, optional_dependencies = row
156
+ return {
157
+ 'status': bool(status),
158
+ 'info': {
159
+ 'version': version,
160
+ 'description': description,
161
+ 'author': author,
162
+ 'dependencies': json.loads(dependencies) if dependencies else [],
163
+ 'optional_dependencies': json.loads(optional_dependencies) if optional_dependencies else []
164
+ }
165
+ }
166
+ return None
167
+
168
+ def set_module(self, module_name, module_info):
169
+ with sqlite3.connect(self.db_path) as conn:
170
+ cursor = conn.cursor()
171
+ cursor.execute("""
172
+ INSERT OR REPLACE INTO modules (
173
+ module_name, status, version, description, author, dependencies, optional_dependencies
174
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
175
+ """, (
176
+ module_name,
177
+ int(module_info.get('status', True)),
178
+ module_info.get('info', {}).get('version', ''),
179
+ module_info.get('info', {}).get('description', ''),
180
+ module_info.get('info', {}).get('author', ''),
181
+ json.dumps(module_info.get('info', {}).get('dependencies', [])),
182
+ json.dumps(module_info.get('info', {}).get('optional_dependencies', []))
183
+ ))
184
+ conn.commit()
185
+
186
+ def update_module(self, module_name, module_info):
187
+ self.set_module(module_name, module_info)
188
+
189
+ def remove_module(self, module_name):
190
+ with sqlite3.connect(self.db_path) as conn:
191
+ cursor = conn.cursor()
192
+ cursor.execute("DELETE FROM modules WHERE module_name = ?", (module_name,))
193
+ conn.commit()
194
+ return cursor.rowcount > 0
195
+
196
+ def __getattr__(self, key):
197
+ try:
198
+ return self.get(key)
199
+ except KeyError:
200
+ raise AttributeError(f"配置项 {key} 不存在")
201
+
202
+ env = EnvManager()
@@ -0,0 +1,16 @@
1
+ class CycleDependencyError(Exception):
2
+ def __init__(self, message):
3
+ self.message = message
4
+ super().__init__(self.message)
5
+
6
+
7
+ class InvalidDependencyError(Exception):
8
+ def __init__(self, message):
9
+ self.message = message
10
+ super().__init__(self.message)
11
+
12
+
13
+ class InvalidModuleError(Exception):
14
+ def __init__(self, message):
15
+ self.message = message
16
+ super().__init__(self.message)
@@ -0,0 +1,71 @@
1
+ import logging
2
+ import inspect
3
+ from . import sdk
4
+ from .envManager import env
5
+
6
+ _logger = logging.getLogger("RyhBot")
7
+ _log_level = env.get("LOG_LEVEL", "DEBUG")
8
+ if _log_level is None:
9
+ _log_level = logging.DEBUG
10
+ _logger.setLevel(_log_level)
11
+
12
+ if not _logger.handlers:
13
+ log_format = (
14
+ f"[%(asctime)s] "
15
+ f"[%(levelname)s] "
16
+ f"%(message)s"
17
+ )
18
+ color_map = {
19
+ "DEBUG": "\033[94m",
20
+ "INFO": "\033[92m",
21
+ "WARNING": "\033[93m",
22
+ "ERROR": "\033[91m",
23
+ "CRITICAL": "\033[95m",
24
+ "RESET": "\033[0m",
25
+ }
26
+
27
+ class ColoredFormatter(logging.Formatter):
28
+ def format(self, record):
29
+ levelname = record.levelname
30
+ if levelname in color_map:
31
+ record.levelname = (
32
+ f"{color_map[levelname]}{levelname}{color_map['RESET']}"
33
+ )
34
+ return super().format(record)
35
+
36
+ console_handler = logging.StreamHandler()
37
+ date_format = "%y/%m/%d-%H:%M:%S"
38
+ formatter = ColoredFormatter(log_format, datefmt=date_format)
39
+ console_handler.setFormatter(formatter)
40
+ _logger.addHandler(console_handler)
41
+
42
+
43
+ def _get_caller():
44
+ frame = inspect.currentframe().f_back.f_back
45
+ module = inspect.getmodule(frame)
46
+ module_name = module.__name__
47
+ if module_name == "__main__":
48
+ module_name = "Main"
49
+ if module_name.endswith(".Core"):
50
+ module_name = module_name[:-5]
51
+ return module_name
52
+
53
+ def debug(msg, *args, **kwargs):
54
+ caller_module = _get_caller()
55
+ _logger.debug(f"[{caller_module}] {msg}", *args, **kwargs)
56
+
57
+ def info(msg, *args, **kwargs):
58
+ caller_module = _get_caller()
59
+ _logger.info(f"[{caller_module}] {msg}", *args, **kwargs)
60
+
61
+ def warning(msg, *args, **kwargs):
62
+ caller_module = _get_caller()
63
+ _logger.warning(f"[{caller_module}] {msg}", *args, **kwargs)
64
+
65
+ def error(msg, *args, **kwargs):
66
+ caller_module = _get_caller()
67
+ _logger.error(f"[{caller_module}] {msg}", *args, **kwargs)
68
+
69
+ def critical(msg, *args, **kwargs):
70
+ caller_module = _get_caller()
71
+ _logger.critical(f"[{caller_module}] {msg}", *args, **kwargs)
@@ -0,0 +1,112 @@
1
+ import asyncio
2
+ import aiohttp
3
+ from .envManager import env
4
+
5
+ class OriginManager:
6
+ def __init__(self):
7
+ self._init_origins()
8
+
9
+ def _init_origins(self):
10
+ if not env.get('origins'):
11
+ env.set('origins', [])
12
+
13
+ async def _validate_url(self, url):
14
+ if not url.startswith(('http://', 'https://')):
15
+ protocol = input("未指定协议,请输入使用的协议 (http 或 https): ").strip().lower()
16
+ if protocol not in ['http', 'https']:
17
+ print("无效的协议类型,必须是 http 或 https。")
18
+ return None
19
+ url = f"{protocol}://{url}"
20
+
21
+ if not url.endswith('.json'):
22
+ url = f"{url}/map.json"
23
+
24
+ try:
25
+ async with aiohttp.ClientSession() as session:
26
+ async with session.get(url) as response:
27
+ response.raise_for_status()
28
+ if response.headers.get('Content-Type', '').startswith('application/json'):
29
+ return url
30
+ else:
31
+ print(f"源 {url} 返回的内容不是有效的 JSON 格式。")
32
+ return None
33
+ except Exception as e:
34
+ print(f"访问源 {url} 失败: {e}")
35
+ return None
36
+
37
+
38
+ def add_origin(self, value):
39
+ validated_url = asyncio.run(self._validate_url(value))
40
+ if not validated_url:
41
+ print("提供的源不是一个有效源,请检查后重试。")
42
+ return
43
+
44
+ origins = env.get('origins')
45
+ if validated_url not in origins:
46
+ origins.append(validated_url)
47
+ env.set('origins', origins)
48
+ print(f"源 {validated_url} 已成功添加。")
49
+ else:
50
+ print(f"源 {validated_url} 已存在,无需重复添加。")
51
+
52
+ def update_origins(self):
53
+ origins = env.get('origins')
54
+ providers = {}
55
+ modules = {}
56
+ module_alias = {}
57
+
58
+ print("\033[1m{:<10} | {:<20} | {:<50}\033[0m".format("源", "模块", "地址"))
59
+ print("-" * 85)
60
+
61
+ async def fetch_origin_data():
62
+ async with aiohttp.ClientSession() as session:
63
+ for origin in origins:
64
+ print(f"\n\033[94m正在获取 {origin}\033[0m")
65
+ try:
66
+ async with session.get(origin) as response:
67
+ response.raise_for_status()
68
+ if response.headers.get('Content-Type', '').startswith('application/json'):
69
+ content = await response.json()
70
+ providers[content["name"]] = content["base"]
71
+
72
+ for module in list(content["modules"].keys()):
73
+ module_content = content["modules"][module]
74
+ modules[f'{module}@{content["name"]}'] = module_content
75
+ module_origin_name = module_content["path"]
76
+ module_alias_name = module
77
+ module_alias[f'{module_origin_name}@{content["name"]}'] = module_alias_name
78
+
79
+ print("{:<10} | {:<20} | {:<50}".format(
80
+ content['name'],
81
+ module,
82
+ f"{providers[content['name']]}{module_origin_name}"
83
+ ))
84
+ else:
85
+ print(f"\033[91m源 {origin} 返回的内容不是有效的 JSON 格式。\033[0m")
86
+ except Exception as e:
87
+ print(f"\033[91m获取 {origin} 时出错: {e}\033[0m")
88
+
89
+ # 使用 asyncio.run 调用异步任务
90
+ asyncio.run(fetch_origin_data())
91
+
92
+ env.set('providers', providers)
93
+ env.set('modules', modules)
94
+ env.set('module_alias', module_alias)
95
+
96
+ print("\n\033[92m{}\033[0m".format("完成".center(85, "-")))
97
+
98
+ def list_origins(self):
99
+ origins = env.get('origins')
100
+ for origin in origins:
101
+ print(origin)
102
+
103
+ def del_origin(self, value):
104
+ origins = env.get('origins')
105
+ if value in origins:
106
+ origins.remove(value)
107
+ env.set('origins', origins)
108
+ print(f"源 {value} 已删除。")
109
+ else:
110
+ print(f"源 {value} 不存在。")
111
+
112
+ origin_manager = OriginManager()
@@ -0,0 +1,3 @@
1
+
2
+ if __name__ == "__main__":
3
+ print("别删我哦 (。•́︿•̀。) ,我超级有用呢!")
@@ -0,0 +1,30 @@
1
+ from collections import defaultdict, deque
2
+ import asyncio
3
+ from concurrent.futures import ThreadPoolExecutor
4
+
5
+ executor = ThreadPoolExecutor()
6
+
7
+
8
+ def topological_sort(elements, dependencies, error):
9
+ graph = defaultdict(list)
10
+ in_degree = {element: 0 for element in elements}
11
+ for element, deps in dependencies.items():
12
+ for dep in deps:
13
+ graph[dep].append(element)
14
+ in_degree[element] += 1
15
+ queue = deque([element for element in elements if in_degree[element] == 0])
16
+ sorted_list = []
17
+ while queue:
18
+ node = queue.popleft()
19
+ sorted_list.append(node)
20
+ for neighbor in graph[node]:
21
+ in_degree[neighbor] -= 1
22
+ if in_degree[neighbor] == 0:
23
+ queue.append(neighbor)
24
+ if len(sorted_list) != len(elements):
25
+ raise error(f"Cycle detected in the dependencies: {elements} -> {dependencies}")
26
+ return sorted_list
27
+
28
+ def ExecAsync(async_func, *args, **kwargs):
29
+ loop = asyncio.get_event_loop()
30
+ return loop.run_in_executor(executor, lambda: asyncio.run(async_func(*args, **kwargs)))
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.2
2
+ Name: ErisPulse
3
+ Version: 1.0.0
4
+ Summary: ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
5
+ Home-page: https://github.com/wsu2059q/ErisPulse
6
+ Author: 艾莉丝·格雷拉特(WSu2059)&r1a
7
+ Author-email: wsu2059@qq.com&dev@oneall.eu.org
8
+ License: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.7
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: aiohttp
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: license
28
+ Dynamic: requires-dist
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # ErisPulse
33
+
34
+ 本项目基于 [RyhBotPythonSDK V2](https://github.com/runoneall/RyhBotPythonSDK2) 构建,并由 [sdkFrame](https://github.com/runoneall/sdkFrame) 提供支持。这是一个异步版本的 SDK,可能在功能和特性上与原库存在一定差异。
35
+
36
+ ErisPulse 是一个模块化、可扩展的异步 Python SDK 框架,主要用于构建高效、可维护的机器人应用程序。
37
+
38
+ ## 开发指南
39
+
40
+ 项目的模块化设计允许开发者通过实现符合规范的模块快速扩展功能。模块的结构和接口规范可以参考 [异步模块开发指南](https://github.com/wsu2059q/AsyncRyhBotPythonSDK2/blob/main/%E5%BC%82%E6%AD%A5%E6%A8%A1%E5%9D%97%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97.md)。
41
+
42
+ ## 项目结构
43
+
44
+ ```
45
+ ErisPulse/
46
+ ├── __init__.py # 项目初始化
47
+ ├── __main__.py # CLI 接口
48
+ ├── envManager.py # 环境配置管理
49
+ ├── errors.py # 自定义异常
50
+ ├── logger.py # 日志记录
51
+ ├── origin.py # 模块源管理
52
+ ├── sdk.py # SDK 核心
53
+ ├── util.py # 工具函数
54
+ └── modules/ # 功能模块目录
55
+ └── ...
56
+ ```
57
+
58
+ ## 主要模块说明
59
+
60
+ - **envManager**: 负责管理环境配置和模块信息,使用 SQLite 数据库存储配置
61
+ - **logger**: 提供日志功能,支持不同日志级别
62
+ - **origin**: 管理模块源,添加、删除、更新模块源等方法在此处
63
+ - **util**: 提供工具函数,拓扑排序、异步执行
64
+ - **modules**: 功能模块目录
65
+
66
+ ## 使用说明
67
+
68
+ 1. 安装依赖:`pip install -r requirements.txt`
69
+ 2. 查看可用CLI命令:`python -m ErisPulse`
70
+
71
+ ### CLI命令介绍
72
+
73
+ `ErisPulse` 提供了丰富的 CLI 命令,用于管理模块、源和环境配置。以下是主要命令:
74
+
75
+ | 命令 | 功能描述 |
76
+ |-------------------------------|------------------------------------|
77
+ | `enable <module_name>` | 启用指定模块 |
78
+ | `disable <module_name>` | 禁用指定模块 |
79
+ | `list [--module <module_name>]` | 列出所有模块或指定模块的详细信息 |
80
+ | `update` | 更新模块列表 |
81
+ | `upgrade [--force]` | 升级所有模块到最新版本 |
82
+ | `uninstall <module_name>` | 删除指定模块 |
83
+ | `install <module_name>` | 安装指定模块,支持多个模块 |
84
+
85
+ #### 模块源管理命令
86
+
87
+ | 命令 | 功能描述 |
88
+ |-------------------------------|------------------------------------|
89
+ | `origin add <url>` | 添加新的模块源 |
90
+ | `origin list` | 列出所有已配置的模块源 |
91
+ | `origin del <url>` | 删除指定的模块源 |
92
+
93
+
94
+ ---
95
+
96
+ ### 模块源
97
+
98
+ 在使用 `ErisPulse` 时,模块源是管理模块的重要组成部分。根据不同的使用场景,模块源分为两种类型:**异步模块源** 和 **同步模块源**。以下是它们的详细说明:
99
+
100
+ #### 异步模块源
101
+ - URL 1: [https://github.com/wsu2059q/AsyncRBPS-Origin/raw/refs/heads/main/map.json](https://github.com/wsu2059q/AsyncRBPS-Origin/raw/refs/heads/main/map.json)
102
+ - URL 2: [https://sdkframe.anran.xyz/map.json](https://sdkframe.anran.xyz/map.json)
103
+ - 特性:
104
+ - 支持异步加载模块。
105
+ - 适用于需要高性能和非阻塞操作的场景。
106
+ - 推荐用于现代异步框架和应用。
107
+
108
+ #### 同步模块源
109
+ - URL: [https://runoneall.serv00.net/ryhsdk2/map.json](https://runoneall.serv00.net/ryhsdk2/map.json)
110
+ - 特性:
111
+ - 传统同步加载模块。
112
+ - 适用于兼容性要求较高的场景。
113
+ - 可能会在某些高并发场景下表现不如异步源。
114
+
115
+ #### 自定义模块源
116
+ 用户可以搭建自己的模块源,以下是一个示例格式:
117
+ ```json
118
+ {
119
+ "name": "Custom-Origin",
120
+ "base": "https://example.com/modules",
121
+ "modules": {
122
+ "CustomModule": {
123
+ "path": "/CustomModule.zip",
124
+ "version": "1.0.0",
125
+ "description": "自定义模块示例",
126
+ "author": "YourName",
127
+ "dependencies": [],
128
+ "optional_dependencies": []
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ #### 提供以下命令方便您快速添加源
135
+ ```bash
136
+ # 添加异步模块源
137
+ python -m ErisPulse origin add https://github.com/wsu2059q/AsyncRBPS-Origin/raw/refs/heads/main/map.json
138
+ # 添加同步模块源
139
+ python -m ErisPulse origin add https://runoneall.serv00.net/ryhsdk2/map.json
140
+
141
+ # 添加自定义模块源
142
+ # python -m ErisPulse origin add https://example.com/modules/map.json
143
+
144
+ # 查看当前配置的模块源
145
+ python -m ErisPulse origin list
146
+
147
+ # 更新模块列表
148
+ python -m ErisPulse update
149
+ ```
@@ -0,0 +1,15 @@
1
+ README.md
2
+ setup.py
3
+ ErisPulse/__init__.py
4
+ ErisPulse/__main__.py
5
+ ErisPulse/envManager.py
6
+ ErisPulse/errors.py
7
+ ErisPulse/logger.py
8
+ ErisPulse/origin.py
9
+ ErisPulse/sdk.py
10
+ ErisPulse/util.py
11
+ ErisPulse.egg-info/PKG-INFO
12
+ ErisPulse.egg-info/SOURCES.txt
13
+ ErisPulse.egg-info/dependency_links.txt
14
+ ErisPulse.egg-info/requires.txt
15
+ ErisPulse.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ aiohttp
@@ -0,0 +1 @@
1
+ ErisPulse