futurehouse-client 0.0.1__py3-none-any.whl

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,19 @@
1
+ from pydantic import BaseModel, JsonValue
2
+
3
+
4
+ class FinalEnvironmentRequest(BaseModel):
5
+ status: str
6
+
7
+
8
+ class StoreAgentStatePostRequest(BaseModel):
9
+ agent_id: str
10
+ step: str
11
+ state: JsonValue
12
+ trajectory_timestep: int
13
+
14
+
15
+ class StoreEnvironmentFrameRequest(BaseModel):
16
+ agent_state_point_in_time: str
17
+ current_agent_step: str
18
+ state: JsonValue
19
+ trajectory_timestep: int
File without changes
@@ -0,0 +1,149 @@
1
+ import ast
2
+ import importlib.util
3
+ import logging
4
+ import types
5
+ from pathlib import Path
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+
10
+ def extract_docstring_from_file(
11
+ file_path: Path,
12
+ class_name: str,
13
+ function_name: str,
14
+ ) -> str | None:
15
+ """Extract the docstring for a specific function in a class without importing the module.
16
+
17
+ Args:
18
+ file_path (Path): Path to the Python file.
19
+ class_name (str): Name of the class containing the function.
20
+ function_name (str): Name of the function.
21
+
22
+ Returns:
23
+ str: The docstring of the function, or None if not found.
24
+
25
+ """
26
+ with file_path.open(encoding="utf-8") as file:
27
+ source = file.read()
28
+
29
+ tree = ast.parse(source)
30
+
31
+ for node in tree.body:
32
+ if isinstance(node, ast.ClassDef) and node.name == class_name:
33
+ for class_node in node.body:
34
+ if (
35
+ isinstance(class_node, ast.FunctionDef)
36
+ and class_node.name == function_name
37
+ ):
38
+ return ast.get_docstring(class_node)
39
+ return None
40
+
41
+
42
+ def load_module(file_path: Path, package_name: str) -> types.ModuleType | None:
43
+ """Load a Python file as part of a package.
44
+
45
+ Args:
46
+ file_path (Path): Path to the Python file.
47
+ package_name (str): Full package name (e.g., "envs.dummy_env.env").
48
+
49
+ Returns:
50
+ Module: The loaded module.
51
+
52
+ """
53
+ spec = importlib.util.spec_from_file_location(package_name, file_path)
54
+ if not spec:
55
+ return None
56
+ return importlib.util.module_from_spec(spec)
57
+
58
+
59
+ def fetch_environment_function_docstring(
60
+ environment_name: str,
61
+ directory: Path,
62
+ function_name: str,
63
+ ) -> str | None:
64
+ """Retrieve the docstring for a specific function within a class, identified by an environment-style module path.
65
+
66
+ The function attempts the following:
67
+ 1. Parse the file inferred from the environment name (e.g., environment "my_env.module.MyClass"
68
+ attempts to find `directory/my_env/module.py`).
69
+ 2. If not found there, recursively searches all `.py` files under `directory` for a class
70
+ with the given name containing the specified function.
71
+
72
+ Args:
73
+ environment_name (str): The environment name in dot notation
74
+ (e.g., "package.module.ClassName").
75
+ directory (Path): The base directory containing the environment files.
76
+ function_name (str): The name of the function to retrieve the docstring for.
77
+
78
+ Raises:
79
+ ValueError: If multiple classes with the same name are found in different files
80
+ (making the intended class ambiguous), an error is raised requiring
81
+ disambiguation.
82
+
83
+ Returns:
84
+ str | None: The docstring of the specified function, or None if not found.
85
+
86
+ """
87
+ parts = environment_name.split(".")
88
+ class_name = parts[-1]
89
+
90
+ guessed_file_path = directory / ("/".join(parts[1:-1]) + ".py")
91
+ if guessed_file_path.exists():
92
+ doc = extract_docstring_from_file(guessed_file_path, class_name, function_name)
93
+ if doc:
94
+ return doc
95
+
96
+ matches = []
97
+ for py_file in directory.rglob("*.py"):
98
+ doc = extract_docstring_from_file(py_file, class_name, function_name)
99
+ if doc:
100
+ matches.append((py_file, doc))
101
+
102
+ if not matches:
103
+ return None
104
+ if len(matches) == 1:
105
+ return matches[0][1]
106
+ match_paths = [str(m[0]) for m in matches]
107
+ raise ValueError(
108
+ f"Multiple classes named '{class_name}' found in:\n"
109
+ + "\n".join(match_paths)
110
+ + "\nPlease specify a more explicit path or ensure unique class names.",
111
+ )
112
+
113
+
114
+ class OrganizationSelector:
115
+ @staticmethod
116
+ def select_organization(organizations: list[str]) -> str | None:
117
+ """Prompts the user to select an organization from a list.
118
+
119
+ Args:
120
+ organizations: List of organization names/IDs
121
+
122
+ Returns:
123
+ Selected organization name/ID or None if selection was cancelled
124
+
125
+ """
126
+ if not organizations:
127
+ raise ValueError("User does not belong to any organizations")
128
+
129
+ if len(organizations) == 1:
130
+ logger.debug(f"Only one organization available: {organizations[0]}")
131
+ return organizations[0]
132
+
133
+ print("\nAvailable organizations:")
134
+ for idx, org in enumerate(organizations, 1):
135
+ print(f"[{idx}]. {org}")
136
+
137
+ while True:
138
+ try:
139
+ selection = input("\nSelect an organization number (or 'q' to quit): ")
140
+
141
+ if selection.lower().strip() == "q":
142
+ return None
143
+
144
+ idx = int(selection)
145
+ if 1 <= idx <= len(organizations):
146
+ return organizations[idx - 1]
147
+ print(f"Please enter a number between 1 and {len(organizations)}")
148
+ except ValueError:
149
+ print("Please enter a valid number or 'q' to quit")
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: futurehouse-client
3
+ Version: 0.0.1
4
+ Summary: A client for interacting with endpoints of the FutureHouse service.
5
+ Author-email: FutureHouse technical staff <hello@futurehouse.org>
6
+ Classifier: Operating System :: OS Independent
7
+ Classifier: Programming Language :: Python :: 3 :: Only
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python
11
+ Requires-Python: <3.13,>=3.11
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: cloudpickle
14
+ Requires-Dist: dm-tree<0.1.9
15
+ Requires-Dist: fhaviary
16
+ Requires-Dist: httpx
17
+ Requires-Dist: ldp>=0.22.0
18
+ Requires-Dist: pydantic
19
+ Requires-Dist: python-dotenv
20
+ Requires-Dist: tenacity
21
+ Provides-Extra: dev
22
+ Requires-Dist: black; extra == "dev"
23
+ Requires-Dist: jupyter; extra == "dev"
24
+ Requires-Dist: jupyterlab; extra == "dev"
25
+ Requires-Dist: mypy; extra == "dev"
26
+ Requires-Dist: notebook; extra == "dev"
27
+ Requires-Dist: pre-commit; extra == "dev"
28
+ Requires-Dist: pylint; extra == "dev"
29
+ Requires-Dist: pylint-per-file-ignores; extra == "dev"
30
+ Requires-Dist: pylint-pydantic; extra == "dev"
31
+ Requires-Dist: pytest; extra == "dev"
32
+ Requires-Dist: pytest-rerunfailures; extra == "dev"
33
+ Requires-Dist: pytest-subtests; extra == "dev"
34
+ Requires-Dist: pytest-timeout; extra == "dev"
35
+ Requires-Dist: pytest-xdist; extra == "dev"
36
+ Requires-Dist: ruff; extra == "dev"
37
+ Requires-Dist: setuptools_scm; extra == "dev"
38
+
39
+ # crow-client
40
+
41
+ A client for interacting with endpoints of the FutureHouse crow service.
42
+
43
+ ## Installation
44
+
45
+ ```bash
46
+ uv pip install crow-client
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ The CrowClient provides simple functions to deploy and monitor your crow.
52
+
53
+ In the case of environments the deployment looks like this
54
+
55
+ ```python
56
+ from pathlib import Path
57
+ from crow_client import CrowClient
58
+ from crow_client.models import CrowDeploymentConfig
59
+
60
+ client = CrowClient()
61
+
62
+ crow = CrowDeploymentConfig(
63
+ path=Path("../envs/dummy_env"),
64
+ environment="dummy_env.env.DummyEnv",
65
+ requires_aviary_internal=False,
66
+ environment_variables={"SAMPLE_ENV_VAR": "sample_val"},
67
+ agent="ldp.agent.SimpleAgent",
68
+ )
69
+
70
+ client.create_crow(crow)
71
+
72
+ # checks the status
73
+ client.get_build_status()
74
+ ```
75
+
76
+ For functional environments we don't need to pass the file path and can pass the environment builder instead
77
+
78
+ ```python
79
+ from aviary.core import fenv
80
+ import numpy as np
81
+
82
+
83
+ def function_to_use_here(inpste: str):
84
+ a = np.array(np.asmatrix("1 2; 3 4"))
85
+ return inpste
86
+
87
+
88
+ @fenv.start()
89
+ def my_env(topic: str):
90
+ """
91
+ Here is the doc string describing the task.
92
+ """
93
+ a = np.array(np.asmatrix("1 2; 3 4"))
94
+ return f"Write a sad story about {topic}", {"chosen_topic": topic}
95
+
96
+
97
+ @my_env.tool()
98
+ def print_story(story: str, state) -> None:
99
+ """Print the story and complete the task"""
100
+ print(story)
101
+ print(function_to_use_here(story))
102
+ state.reward = 1
103
+ state.done = True
104
+
105
+
106
+ from crow_client import CrowClient
107
+ from crow_client.models import CrowDeploymentConfig, Stage
108
+ from crow_client.clients.rest_client import generate_requirements
109
+
110
+ client = CrowClient(stage=Stage.LOCAL)
111
+
112
+ crow = CrowDeploymentConfig(
113
+ functional_environment=my_env,
114
+ environment="my_env",
115
+ requires_aviary_internal=False,
116
+ environment_variables={"SAMPLE_ENV_VAR": "sample_val"},
117
+ agent="ldp.agent.SimpleAgent",
118
+ requirements=generate_requirements(my_env, globals()),
119
+ )
120
+
121
+ client.create_crow(crow)
122
+ ```
123
+
124
+ This client also provides functions that let you send tasks to an existing crow:
125
+
126
+ ```python
127
+ from crow_client import CrowJob
128
+
129
+ client = CrowClient()
130
+
131
+ job_data = {"name": "your-job-name", "query": "your task"}
132
+ client.create_job(job_data)
133
+
134
+ # checks the status
135
+ client.get_job()
136
+ ```
137
+
138
+ The CrowJobClient provides an interface for managing environment states and agent interactions in the FutureHouse crow service.
139
+
140
+ ```python
141
+ from crow_client import CrowJobClient
142
+ from crow_client.models.app import Stage
143
+
144
+ client = CrowJobClient(
145
+ environment="your_environment_name",
146
+ agent="your_agent_id",
147
+ auth_token="your_auth_token",
148
+ base_uri=Stage.DEV,
149
+ trajectory_id=None,
150
+ )
151
+ ```
@@ -0,0 +1,14 @@
1
+ futurehouse_client/__init__.py,sha256=SaaBeLHh4K81dONiXxowhsgM-Zr12t1g-phDgwi5iWc,316
2
+ futurehouse_client/clients/__init__.py,sha256=OWJtj6WNJ1GvzA5v-Z8yiKlta47k4dprih-9n7mag40,300
3
+ futurehouse_client/clients/job_client.py,sha256=M0siUz-5Ffri4_fhXXeBb54qnELwMa1hYzosS8bx3ek,8833
4
+ futurehouse_client/clients/rest_client.py,sha256=dEes4lP0JOsuRoFgjhU9uXSogBGFOWgSw8ZfopUJ4w8,24734
5
+ futurehouse_client/models/__init__.py,sha256=VBKYb_2kmxtDwp9LvTL7BEFyioekggQsrQg5I_dhZoI,342
6
+ futurehouse_client/models/app.py,sha256=_MO31xvKJaAztWz4KrV13ty4POg-RuWrGgqmvS9w2bc,22298
7
+ futurehouse_client/models/client.py,sha256=n4HD0KStKLm6Ek9nL9ylP-bkK10yzAaD1uIDF83Qp_A,1828
8
+ futurehouse_client/models/rest.py,sha256=W-wNFTN7HALYFFphw-RQYRMm6_TSa1cl4T-mZ1msk90,393
9
+ futurehouse_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ futurehouse_client/utils/module_utils.py,sha256=aFyd-X-pDARXz9GWpn8SSViUVYdSbuy9vSkrzcVIaGI,4955
11
+ futurehouse_client-0.0.1.dist-info/METADATA,sha256=L7c7KYAcl5HgkRBNmjHS0xnWmZb4QHwmAS3-vrLmtsw,4076
12
+ futurehouse_client-0.0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
13
+ futurehouse_client-0.0.1.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
14
+ futurehouse_client-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (78.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ futurehouse_client