hyperpocket 0.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. hyperpocket/__init__.py +7 -0
  2. hyperpocket/auth/README.KR.md +309 -0
  3. hyperpocket/auth/README.md +323 -0
  4. hyperpocket/auth/__init__.py +24 -0
  5. hyperpocket/auth/calendly/__init__.py +0 -0
  6. hyperpocket/auth/calendly/context.py +13 -0
  7. hyperpocket/auth/calendly/oauth2_context.py +25 -0
  8. hyperpocket/auth/calendly/oauth2_handler.py +146 -0
  9. hyperpocket/auth/calendly/oauth2_schema.py +16 -0
  10. hyperpocket/auth/context.py +38 -0
  11. hyperpocket/auth/github/__init__.py +0 -0
  12. hyperpocket/auth/github/context.py +13 -0
  13. hyperpocket/auth/github/oauth2_context.py +25 -0
  14. hyperpocket/auth/github/oauth2_handler.py +143 -0
  15. hyperpocket/auth/github/oauth2_schema.py +16 -0
  16. hyperpocket/auth/github/token_context.py +12 -0
  17. hyperpocket/auth/github/token_handler.py +79 -0
  18. hyperpocket/auth/github/token_schema.py +9 -0
  19. hyperpocket/auth/google/__init__.py +0 -0
  20. hyperpocket/auth/google/context.py +15 -0
  21. hyperpocket/auth/google/oauth2_context.py +31 -0
  22. hyperpocket/auth/google/oauth2_handler.py +137 -0
  23. hyperpocket/auth/google/oauth2_schema.py +18 -0
  24. hyperpocket/auth/handler.py +171 -0
  25. hyperpocket/auth/linear/__init__.py +0 -0
  26. hyperpocket/auth/linear/context.py +15 -0
  27. hyperpocket/auth/linear/token_context.py +15 -0
  28. hyperpocket/auth/linear/token_handler.py +68 -0
  29. hyperpocket/auth/linear/token_schema.py +9 -0
  30. hyperpocket/auth/provider.py +16 -0
  31. hyperpocket/auth/schema.py +19 -0
  32. hyperpocket/auth/slack/__init__.py +0 -0
  33. hyperpocket/auth/slack/context.py +15 -0
  34. hyperpocket/auth/slack/oauth2_context.py +40 -0
  35. hyperpocket/auth/slack/oauth2_handler.py +151 -0
  36. hyperpocket/auth/slack/oauth2_schema.py +40 -0
  37. hyperpocket/auth/slack/tests/__init__.py +0 -0
  38. hyperpocket/auth/slack/tests/test_oauth2_handler.py +32 -0
  39. hyperpocket/auth/slack/tests/test_token_handler.py +23 -0
  40. hyperpocket/auth/slack/token_context.py +14 -0
  41. hyperpocket/auth/slack/token_handler.py +64 -0
  42. hyperpocket/auth/slack/token_schema.py +9 -0
  43. hyperpocket/auth/tests/__init__.py +0 -0
  44. hyperpocket/auth/tests/test_google_oauth2_handler.py +147 -0
  45. hyperpocket/auth/tests/test_slack_oauth2_handler.py +147 -0
  46. hyperpocket/auth/tests/test_slack_token_handler.py +66 -0
  47. hyperpocket/cli/__init__.py +0 -0
  48. hyperpocket/cli/__main__.py +12 -0
  49. hyperpocket/cli/pull.py +18 -0
  50. hyperpocket/cli/sync.py +17 -0
  51. hyperpocket/config/__init__.py +9 -0
  52. hyperpocket/config/auth.py +36 -0
  53. hyperpocket/config/git.py +17 -0
  54. hyperpocket/config/logger.py +81 -0
  55. hyperpocket/config/session.py +35 -0
  56. hyperpocket/config/settings.py +62 -0
  57. hyperpocket/constants.py +0 -0
  58. hyperpocket/curated_tools.py +10 -0
  59. hyperpocket/external/__init__.py +7 -0
  60. hyperpocket/external/github_client.py +19 -0
  61. hyperpocket/futures/__init__.py +7 -0
  62. hyperpocket/futures/futurestore.py +48 -0
  63. hyperpocket/pocket_auth.py +344 -0
  64. hyperpocket/pocket_main.py +351 -0
  65. hyperpocket/prompts.py +15 -0
  66. hyperpocket/repository/__init__.py +5 -0
  67. hyperpocket/repository/lock.py +156 -0
  68. hyperpocket/repository/lockfile.py +56 -0
  69. hyperpocket/repository/repository.py +18 -0
  70. hyperpocket/server/__init__.py +3 -0
  71. hyperpocket/server/auth/__init__.py +15 -0
  72. hyperpocket/server/auth/calendly.py +16 -0
  73. hyperpocket/server/auth/github.py +25 -0
  74. hyperpocket/server/auth/google.py +16 -0
  75. hyperpocket/server/auth/linear.py +18 -0
  76. hyperpocket/server/auth/slack.py +28 -0
  77. hyperpocket/server/auth/token.py +51 -0
  78. hyperpocket/server/proxy.py +63 -0
  79. hyperpocket/server/server.py +178 -0
  80. hyperpocket/server/tool/__init__.py +10 -0
  81. hyperpocket/server/tool/dto/__init__.py +0 -0
  82. hyperpocket/server/tool/dto/script.py +15 -0
  83. hyperpocket/server/tool/wasm.py +31 -0
  84. hyperpocket/session/README.KR.md +62 -0
  85. hyperpocket/session/README.md +61 -0
  86. hyperpocket/session/__init__.py +4 -0
  87. hyperpocket/session/in_memory.py +76 -0
  88. hyperpocket/session/interface.py +118 -0
  89. hyperpocket/session/redis.py +126 -0
  90. hyperpocket/session/tests/__init__.py +0 -0
  91. hyperpocket/session/tests/test_in_memory.py +145 -0
  92. hyperpocket/session/tests/test_redis.py +151 -0
  93. hyperpocket/tests/__init__.py +0 -0
  94. hyperpocket/tests/test_pocket.py +118 -0
  95. hyperpocket/tests/test_pocket_auth.py +982 -0
  96. hyperpocket/tool/README.KR.md +68 -0
  97. hyperpocket/tool/README.md +75 -0
  98. hyperpocket/tool/__init__.py +13 -0
  99. hyperpocket/tool/builtins/__init__.py +0 -0
  100. hyperpocket/tool/builtins/example/__init__.py +0 -0
  101. hyperpocket/tool/builtins/example/add_tool.py +18 -0
  102. hyperpocket/tool/function/README.KR.md +159 -0
  103. hyperpocket/tool/function/README.md +169 -0
  104. hyperpocket/tool/function/__init__.py +9 -0
  105. hyperpocket/tool/function/annotation.py +30 -0
  106. hyperpocket/tool/function/tool.py +87 -0
  107. hyperpocket/tool/tests/__init__.py +0 -0
  108. hyperpocket/tool/tests/test_function_tool.py +266 -0
  109. hyperpocket/tool/tool.py +106 -0
  110. hyperpocket/tool/wasm/README.KR.md +144 -0
  111. hyperpocket/tool/wasm/README.md +144 -0
  112. hyperpocket/tool/wasm/__init__.py +3 -0
  113. hyperpocket/tool/wasm/browser.py +63 -0
  114. hyperpocket/tool/wasm/invoker.py +41 -0
  115. hyperpocket/tool/wasm/script.py +82 -0
  116. hyperpocket/tool/wasm/templates/__init__.py +28 -0
  117. hyperpocket/tool/wasm/templates/node.py +87 -0
  118. hyperpocket/tool/wasm/templates/python.py +75 -0
  119. hyperpocket/tool/wasm/tool.py +147 -0
  120. hyperpocket/util/__init__.py +1 -0
  121. hyperpocket/util/extract_func_param_desc_from_docstring.py +97 -0
  122. hyperpocket/util/find_all_leaf_class_in_package.py +17 -0
  123. hyperpocket/util/find_all_subclass_in_package.py +29 -0
  124. hyperpocket/util/flatten_json_schema.py +45 -0
  125. hyperpocket/util/function_to_model.py +46 -0
  126. hyperpocket/util/get_objects_from_subpackage.py +28 -0
  127. hyperpocket/util/json_schema_to_model.py +69 -0
  128. hyperpocket-0.0.1.dist-info/METADATA +304 -0
  129. hyperpocket-0.0.1.dist-info/RECORD +131 -0
  130. hyperpocket-0.0.1.dist-info/WHEEL +4 -0
  131. hyperpocket-0.0.1.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,66 @@
1
+ from unittest.async_case import IsolatedAsyncioTestCase
2
+ from urllib.parse import urlparse, parse_qs
3
+
4
+ from hyperpocket.auth import SlackTokenAuthContext
5
+ from hyperpocket.auth.slack.token_handler import SlackTokenAuthHandler
6
+ from hyperpocket.auth.slack.token_schema import SlackTokenRequest
7
+ from hyperpocket.futures import FutureStore
8
+
9
+
10
+ class TestSlackTokenAuthHandler(IsolatedAsyncioTestCase):
11
+
12
+ async def asyncSetUp(self):
13
+ self.handler: SlackTokenAuthHandler = SlackTokenAuthHandler()
14
+ self.auth_req = SlackTokenRequest()
15
+
16
+ async def test_make_auth_url(self):
17
+ auth_url = self.handler._make_auth_url(
18
+ req=self.auth_req,
19
+ redirect_uri="http://test-redirect-uri.com",
20
+ state="test-future-id"
21
+ )
22
+ parsed = urlparse(auth_url)
23
+ query_params = parse_qs(parsed.query)
24
+ base_url = f"{parsed.scheme}://{parsed.netloc}{parsed.path}"
25
+
26
+ # then
27
+ self.assertEqual(base_url, self.handler._TOKEN_URL)
28
+ self.assertEqual(query_params["state"][0], "test-future-id")
29
+ self.assertEqual(query_params["redirect_uri"][0], "http://test-redirect-uri.com")
30
+
31
+ async def test_prepare(self):
32
+ # when
33
+ prepare: str = self.handler.prepare(
34
+ auth_req=self.auth_req,
35
+ thread_id="test-prepare-thread-id",
36
+ profile="test-prepare-profile",
37
+ future_uid="test-prepare-future-uid",
38
+ )
39
+ auth_url = prepare.removeprefix("User needs to authenticate using the following URL:").strip()
40
+ future_data = FutureStore.get_future( uid="test-prepare-future-uid")
41
+
42
+ # then
43
+ self.assertTrue(auth_url.startswith(self.handler._TOKEN_URL))
44
+ self.assertIsNotNone(future_data)
45
+ self.assertEqual(future_data.data["thread_id"], "test-prepare-thread-id")
46
+ self.assertEqual(future_data.data["profile"], "test-prepare-profile")
47
+ self.assertFalse(future_data.future.done())
48
+
49
+ async def test_authenticate(self):
50
+ self.handler.prepare(
51
+ auth_req=self.auth_req,
52
+ thread_id="test-thread-id",
53
+ profile="test-profile",
54
+ future_uid="test-future-uid"
55
+ )
56
+ future_data = FutureStore.get_future( uid="test-future-uid")
57
+ future_data.future.set_result("test-token")
58
+
59
+ response: SlackTokenAuthContext = await self.handler.authenticate(
60
+ auth_req=self.auth_req,
61
+ future_uid="test-future-uid"
62
+ )
63
+
64
+ self.assertIsInstance(response, SlackTokenAuthContext)
65
+ self.assertEqual(response.access_token, "test-token")
66
+ self.assertIsNone(response.expires_at)
File without changes
@@ -0,0 +1,12 @@
1
+ import click
2
+
3
+ from hyperpocket.cli.pull import pull
4
+
5
+
6
+ @click.group()
7
+ def cli():
8
+ pass
9
+
10
+ cli.add_command(pull)
11
+
12
+ cli()
@@ -0,0 +1,18 @@
1
+ import pathlib
2
+ from typing import Optional
3
+
4
+ import click
5
+
6
+ import hyperpocket.repository as repository
7
+
8
+
9
+ @click.command()
10
+ @click.argument('url', type=str)
11
+ @click.option("--lockfile", envvar='PATHS', type=click.Path(exists=True))
12
+ @click.option("--git-ref", type=str, default='HEAD')
13
+ def pull(url: str, lockfile: Optional[pathlib.Path], git_ref: str):
14
+ if not lockfile:
15
+ lockfile = pathlib.Path.cwd() / 'pocket.lock'
16
+ if not lockfile.exists():
17
+ lockfile.touch()
18
+ repository.pull(repository.Lockfile(path=lockfile), url, git_ref)
@@ -0,0 +1,17 @@
1
+ import pathlib
2
+ from typing import Optional
3
+
4
+ import click
5
+
6
+ import hyperpocket.repository as repository
7
+
8
+
9
+ @click.command()
10
+ @click.option("--lockfile", envvar='PATHS', type=click.Path(exists=True))
11
+ @click.option("--force-update", type=str, default='HEAD')
12
+ def sync(url: str, lockfile: Optional[pathlib.Path], force_update: bool):
13
+ if not lockfile:
14
+ lockfile = pathlib.Path.cwd() / 'pocket.lock'
15
+ if not lockfile.exists():
16
+ lockfile.touch()
17
+ repository.sync(repository.Lockfile(path=lockfile), force_update)
@@ -0,0 +1,9 @@
1
+ from hyperpocket.config.settings import config as _config
2
+ from hyperpocket.config.settings import settings as _settings
3
+ from hyperpocket.config.logger import pocket_logger as _pocket_logger
4
+
5
+ config = _config
6
+ secret = _settings
7
+ pocket_logger = _pocket_logger
8
+
9
+ __all__ = ["config", "secret", "pocket_logger"]
@@ -0,0 +1,36 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class BaseAuthConfig(BaseModel):
7
+ use_recommended_scope: bool = Field(default=True)
8
+
9
+
10
+ class SlackAuthConfig(BaseAuthConfig):
11
+ client_id: str
12
+ client_secret: str
13
+
14
+
15
+ class GoogleAuthConfig(BaseAuthConfig):
16
+ client_id: str
17
+ client_secret: str
18
+
19
+
20
+ class GithubAuthConfig(BaseAuthConfig):
21
+ client_id: str
22
+ client_secret: str
23
+
24
+ class CalendlyAuthConfig(BaseAuthConfig):
25
+ client_id: str
26
+ client_secret: str
27
+
28
+ class AuthConfig(BaseModel):
29
+ slack: Optional[SlackAuthConfig] = None
30
+ google: Optional[GoogleAuthConfig] = None
31
+ github: Optional[GithubAuthConfig] = None
32
+ calendly: Optional[CalendlyAuthConfig] = None
33
+ use_prebuilt_auth: bool = Field(default=True)
34
+
35
+
36
+ DefaultAuthConfig = AuthConfig()
@@ -0,0 +1,17 @@
1
+ from typing import Optional
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class GithubConfig(BaseModel):
7
+ github_token: Optional[str] = None
8
+ app_id: Optional[str] = None
9
+ app_private_key: Optional[str] = None
10
+ app_installation_id: Optional[str] = None
11
+
12
+
13
+ class GitConfig(BaseModel):
14
+ github: GithubConfig
15
+
16
+
17
+ DefaultGitConfig = GitConfig(github=GithubConfig())
@@ -0,0 +1,81 @@
1
+ import logging
2
+ import os
3
+ from logging.handlers import RotatingFileHandler
4
+ from pathlib import Path
5
+
6
+ import hyperpocket
7
+ from hyperpocket.config.settings import config
8
+
9
+ class ColorFormatter(logging.Formatter):
10
+ """Custom formatter to add colors based on log level."""
11
+ # ANSI escape codes for text colors
12
+ LEVEL_COLORS = {
13
+ logging.DEBUG: "\033[36m", # Cyan
14
+ logging.INFO: "\033[32m", # Green
15
+ logging.WARNING: "\033[33m", # Yellow
16
+ logging.ERROR: "\033[31m", # Red
17
+ logging.CRITICAL: "\033[35m", # Magenta
18
+ }
19
+ RESET = "\033[0m"
20
+
21
+ def format(self, record):
22
+ log_color = self.LEVEL_COLORS.get(record.levelno, self.RESET)
23
+ message = super().format(record)
24
+ return f"{log_color}{message}{self.RESET}"
25
+
26
+
27
+
28
+ def get_logger():
29
+ # init log file
30
+ package_path = Path(os.path.dirname(hyperpocket.__file__))
31
+ log_dir = package_path / ".log"
32
+ os.makedirs(log_dir, exist_ok=True)
33
+ log_file = log_dir / "pocket.log"
34
+ if not log_file.exists():
35
+ print(f"created log file in {log_file}")
36
+ with open(log_file, "w"):
37
+ pass
38
+
39
+ # set log level
40
+ log_level = logging.INFO
41
+ logger = logging.getLogger("pocket_logger")
42
+ if config.log_level.lower() == "debug":
43
+ log_level = logging.DEBUG
44
+ elif config.log_level.lower() == "info":
45
+ log_level = logging.INFO
46
+ elif config.log_level.lower() == "warning":
47
+ log_level = logging.WARNING
48
+ elif config.log_level.lower() == "error":
49
+ log_level = logging.ERROR
50
+ elif config.log_level.lower() == "critical":
51
+ log_level = logging.CRITICAL
52
+ elif config.log_level.lower() == "fatal":
53
+ log_level = logging.FATAL
54
+
55
+ # set formatter
56
+ logger.setLevel(log_level)
57
+ color_formatter = ColorFormatter("[%(asctime)s] [%(levelname)s] [%(processName)s(%(process)d):%(threadName)s(%(thread)d)] [%(name)s] %(message)s",
58
+ datefmt="%Y-%m-%d %H:%M:%S")
59
+ formatter = logging.Formatter(
60
+ "[%(asctime)s] [%(levelname)s] [%(processName)s(%(process)d):%(threadName)s(%(thread)d)] [%(name)s] %(message)s",
61
+ datefmt="%Y-%m-%d %H:%M:%S"
62
+ )
63
+
64
+ # add console handler
65
+ console_handler = logging.StreamHandler()
66
+ console_handler.setLevel(log_level) # 콘솔 출력 레벨 설정
67
+ console_handler.setFormatter(color_formatter)
68
+ logger.addHandler(console_handler)
69
+
70
+ # add rotating file handler
71
+ file_handler = RotatingFileHandler(
72
+ log_file, maxBytes=5 * 1024 * 1024, backupCount=100 # 파일 크기 5MB, 백업 파일 3개
73
+ )
74
+ file_handler.setLevel(logging.DEBUG) # 파일 출력 레벨 설정
75
+ file_handler.setFormatter(formatter)
76
+ logger.addHandler(file_handler)
77
+
78
+ return logger
79
+
80
+
81
+ pocket_logger = get_logger()
@@ -0,0 +1,35 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class SessionType(Enum):
8
+ IN_MEMORY = "in_memory"
9
+ REDIS = "redis"
10
+
11
+
12
+ class SessionConfigInMemory(BaseModel):
13
+ pass
14
+
15
+
16
+ class SessionConfigRedis(BaseModel):
17
+ class Config:
18
+ extra = "allow"
19
+
20
+ host: str = Field(default="localhost")
21
+ port: int = Field(default=6379)
22
+ db: int = Field(default=0)
23
+
24
+
25
+ class SessionConfig(BaseModel):
26
+ session_type: SessionType
27
+ in_memory: Optional[SessionConfigInMemory] = Field(default_factory=SessionConfigInMemory)
28
+ redis: Optional[SessionConfigRedis] = Field(default_factory=SessionConfigRedis)
29
+
30
+
31
+ DefaultSessionConfig = SessionConfig(
32
+ session_type=SessionType.IN_MEMORY,
33
+ in_memory=SessionConfigInMemory(),
34
+ redis=SessionConfigRedis()
35
+ )
@@ -0,0 +1,62 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Literal
4
+
5
+ from dynaconf import Dynaconf
6
+ from pydantic import BaseModel
7
+
8
+ from hyperpocket.config.auth import AuthConfig, DefaultAuthConfig
9
+ from hyperpocket.config.git import DefaultGitConfig, GitConfig
10
+ from hyperpocket.config.session import DefaultSessionConfig, SessionConfig
11
+
12
+ pocket_root = Path.home() / ".pocket"
13
+ if not pocket_root.exists():
14
+ os.makedirs(pocket_root)
15
+ settings_path = pocket_root / "settings.toml"
16
+ if not settings_path.exists():
17
+ with open(settings_path, "w"):
18
+ pass
19
+ secret_path = pocket_root / ".secrets.toml"
20
+ if not secret_path.exists():
21
+ with open(secret_path, "w"):
22
+ pass
23
+ toolpkg_path = pocket_root / "toolpkg"
24
+ if not toolpkg_path.exists():
25
+ os.makedirs(toolpkg_path)
26
+
27
+ settings = Dynaconf(
28
+ envvar_prefix="POCKET",
29
+ settings_files=[settings_path, secret_path],
30
+ )
31
+
32
+ for key, value in os.environ.items():
33
+ if settings.get(key) is None:
34
+ settings[key] = value
35
+
36
+
37
+ class Config(BaseModel):
38
+ internal_server_port: int = 8000
39
+ enable_local_callback_proxy: bool = True
40
+ public_hostname: str = "localhost"
41
+ public_server_protocol: str = "https"
42
+ public_server_port: int = 8001
43
+ callback_url_rewrite_prefix: str = "proxy" # should not start with a slash
44
+ log_level: str = "INFO"
45
+ auth: AuthConfig = DefaultAuthConfig
46
+ git: GitConfig = DefaultGitConfig
47
+ session: SessionConfig = DefaultSessionConfig
48
+
49
+ @property
50
+ def internal_base_url(self):
51
+ return f"http://localhost:{self.internal_server_port}"
52
+
53
+ @property
54
+ def public_base_url(self):
55
+ if self.public_server_protocol == 'https' and self.public_server_port == 443:
56
+ return f"{self.public_server_protocol}://{self.public_hostname}"
57
+ elif self.public_server_protocol == 'http' and self.public_server_port == 80:
58
+ return f"{self.public_server_protocol}://{self.public_hostname}"
59
+ return f"{self.public_server_protocol}://{self.public_hostname}:{self.public_server_port}"
60
+
61
+
62
+ config: Config = Config.model_validate({k.lower(): v for k, v in settings.items()})
File without changes
@@ -0,0 +1,10 @@
1
+ from hyperpocket.tool import from_git
2
+
3
+ SLACK = [
4
+ from_git("https://github.com/vessl-ai/tool-calling", "main", "examples/slack-get-message"),
5
+ from_git("https://github.com/vessl-ai/tool-calling", "main", "examples/slack-post-message")
6
+ ]
7
+
8
+ LINEAR = [
9
+ from_git("https://github.com/vessl-ai/tool-calling", "main", "examples/linear-get-issues"),
10
+ ]
@@ -0,0 +1,7 @@
1
+ from hyperpocket.external.github_client import github_instance
2
+
3
+ github = github_instance()
4
+
5
+ __all__ = [
6
+ "github",
7
+ ]
@@ -0,0 +1,19 @@
1
+ from github import Auth, Github, GithubIntegration
2
+
3
+ from hyperpocket.config import config
4
+
5
+ _github = None
6
+
7
+
8
+ def github_instance() -> Github:
9
+ global _github
10
+ if _github is None:
11
+ if config.git.github.github_token:
12
+ _github = Github(auth=Auth.Token(config.git.github.github_token))
13
+ elif config.git.github.app_id:
14
+ auth = Auth.AppAuth(config.git.github.app_id, config.git.github.app_private_key)
15
+ gi = GithubIntegration(auth=auth)
16
+ _github = gi.get_github_for_installation(config.git.github.app_installation_id)
17
+ else:
18
+ _github = Github()
19
+ return _github
@@ -0,0 +1,7 @@
1
+ from hyperpocket.futures.futurestore import FutureStore as _FutureStore
2
+
3
+ FutureStore = _FutureStore()
4
+
5
+ __all__ = [
6
+ 'FutureStore',
7
+ ]
@@ -0,0 +1,48 @@
1
+ import asyncio
2
+ import enum
3
+ from typing import Any
4
+
5
+ from hyperpocket.config import pocket_logger
6
+
7
+
8
+ class FutureData:
9
+ future: asyncio.Future
10
+ data: dict
11
+
12
+ def __init__(self, future: asyncio.Future, data: dict):
13
+ self.future = future
14
+ self.data = data
15
+
16
+
17
+ class FutureStore(object):
18
+ futures: dict[str, FutureData]
19
+
20
+ def __init__(self):
21
+ self.futures = dict()
22
+
23
+ def create_future(self, uid: str, data: dict = None) -> FutureData:
24
+ if future := self.get_future(uid) is not None:
25
+ pocket_logger.info(
26
+ f"the future already exists. the existing future is returned. uid: {uid}")
27
+ return future
28
+
29
+ loop = asyncio.get_running_loop()
30
+ future = loop.create_future()
31
+ future_data = FutureData(future=future, data=data)
32
+
33
+ self.futures[uid] = future_data
34
+
35
+ return future_data
36
+
37
+ def get_future(self, uid: str) -> FutureData:
38
+ return self.futures.get(uid, None)
39
+
40
+ def resolve_future(self, uid: str, value: Any):
41
+ future_data = self.futures.get(uid)
42
+ if not future_data:
43
+ raise ValueError(f'Future not found for uid={uid}')
44
+ if not future_data.future.done():
45
+ future_data.future.set_result(value)
46
+
47
+ def delete_future(self, uid: str):
48
+ self.futures.pop(uid, None)