finrl_env-v2-1-0 / src /core /generic_client.py
burtenshaw's picture
burtenshaw HF Staff
Upload folder using huggingface_hub
b03d60d verified
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""
Generic environment client that works with raw dictionaries.
This module provides a GenericEnvClient that doesn't require installing
environment-specific packages. It's useful for connecting to remote servers
without running any untrusted code locally.
"""
from typing import Any, Dict
from .client_types import StepResult
from .env_client import EnvClient
class GenericEnvClient(EnvClient[Dict[str, Any], Dict[str, Any], Dict[str, Any]]):
"""
Environment client that works with raw dictionaries instead of typed classes.
This client doesn't require installing environment-specific packages, making it
ideal for:
- Connecting to remote servers without installing their packages
- Quick prototyping and testing
- Environments where type safety isn't needed
- Security-conscious scenarios where you don't want to run remote code
The trade-off is that you lose type safety and IDE autocomplete for actions
and observations. Instead of typed objects, you work with plain dictionaries.
Example:
>>> # Direct connection to a running server (no installation needed)
>>> with GenericEnvClient(base_url="http://localhost:8000") as env:
... result = env.reset()
... result = env.step({"code": "print('hello')"})
... print(result.observation) # Dict[str, Any]
... print(result.observation.get("output"))
>>> # From local Docker image
>>> env = GenericEnvClient.from_docker_image("coding-env:latest")
>>> result = env.reset()
>>> result = env.step({"code": "x = 1 + 2"})
>>> env.close()
>>> # From HuggingFace Hub (pulls Docker image, no pip install)
>>> env = GenericEnvClient.from_env("user/my-env", use_docker=True)
>>> result = env.reset()
>>> env.close()
Note:
GenericEnvClient inherits `from_docker_image()` and `from_env()` from
EnvClient, so you can use it with Docker containers and HuggingFace
Spaces without any package installation.
"""
def _step_payload(self, action: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert action to payload for the server.
For GenericEnvClient, this handles both raw dictionaries and
typed Action objects (Pydantic models). If a Pydantic model is
passed, it will be converted to a dictionary using model_dump().
Args:
action: Action as a dictionary or Pydantic BaseModel
Returns:
The action as a dictionary for the server
"""
# If it's already a dict, return as-is
if isinstance(action, dict):
return action
# If it's a Pydantic model (Action subclass), convert to dict
if hasattr(action, "model_dump"):
return action.model_dump()
# Fallback for other objects with __dict__
if hasattr(action, "__dict__"):
return vars(action)
# Last resort: try to convert to dict
return dict(action)
def _parse_result(self, payload: Dict[str, Any]) -> StepResult[Dict[str, Any]]:
"""
Parse server response into a StepResult.
Extracts the observation, reward, and done fields from the
server response.
Args:
payload: Response payload from the server
Returns:
StepResult with observation as a dictionary
"""
return StepResult(
observation=payload.get("observation", {}),
reward=payload.get("reward"),
done=payload.get("done", False),
)
def _parse_state(self, payload: Dict[str, Any]) -> Dict[str, Any]:
"""
Parse state response from the server.
For GenericEnvClient, this returns the payload as-is since
we're working with dictionaries.
Args:
payload: State payload from the server
Returns:
The state as a dictionary
"""
return payload
class GenericAction(Dict[str, Any]):
"""
A dictionary subclass for creating actions when using GenericEnvClient.
This provides a semantic wrapper around dictionaries to make code more
readable when working with GenericEnvClient. It behaves exactly like a
dict but signals intent that this is an action for an environment.
Example:
>>> # Without GenericAction (works fine)
>>> env.step({"code": "print('hello')"})
>>> # With GenericAction (more explicit)
>>> action = GenericAction(code="print('hello')")
>>> env.step(action)
>>> # With multiple fields
>>> action = GenericAction(code="x = 1", timeout=30, metadata={"tag": "test"})
>>> env.step(action)
Note:
GenericAction is just a dict with a constructor that accepts keyword
arguments. It's provided for symmetry with typed Action classes and
to make code more readable.
"""
def __init__(self, **kwargs: Any) -> None:
"""
Create a GenericAction from keyword arguments.
Args:
**kwargs: Action fields as keyword arguments
Example:
>>> action = GenericAction(code="print(1)", timeout=30)
>>> action["code"]
'print(1)'
"""
super().__init__(kwargs)
def __repr__(self) -> str:
"""Return a readable representation."""
items = ", ".join(f"{k}={v!r}" for k, v in self.items())
return f"GenericAction({items})"