Skip to content

Commit f043d6c

Browse files
Merge pull request 'impl/client-server/simple-remote-state' (#11) from impl/client-server/simple-remote-state into main
Reviewed-on: https://git.myzel.io/PyMC/PyMC-Server/pulls/11
2 parents 8a27ce1 + 91e2cdd commit f043d6c

16 files changed

+1364
-317
lines changed

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
## what does it do
44

55
The Tool will be used to deploy on skypilot. It aims to simplify the deployment process.
6-
In the end, the application needs to merge different .yaml files (pyMc-/user Files) together
6+
In the end, the application needs to merge different .yaml files (pyMc-/user Files) together.
7+
8+
The library can be used and configured in 3 forms
9+
10+
- via a Python API and by passing configuration as parameters
11+
- via the CLI and YAML files for configuration
12+
- via the CLI and command line parameters for configuration.
13+
714
# Quickstart
815

916
## Installation
@@ -25,7 +32,7 @@ cd PyMC-Server
2532
pip3 install . # run `pip3 install -e .` if you want to be able to edit the code
2633
```
2734

28-
### Setting up a cloud: Google Cloud
35+
## Setting up a cloud: Google Cloud
2936

3037
1. Run `pymcs check` to see if you already have credentials setup. If you see a green checkmark for GCP (Google Cloud Platform), skip to the next section
3138
2. Install the google cloud SDK and authenticate.
@@ -53,7 +60,7 @@ pip3 install . # run `pip3 install -e .` if you want to be able to edit the code
5360

5461
Next, create a new role and name it e.g. `pymcs-server-role`.
5562

56-
>Note: we will be granting overly permissive rules for convenience of your setup not taking too long.
63+
>Note: we will be granting overly permissive rules for convenience of your setup.
5764
>For production usage it is strictly neccessary that you review the permissions given. Please refer to [GCP: Minimal Permissions](https://skypilot.readthedocs.io/en/latest/cloud-setup/cloud-permissions/gcp.html#minimal-permissions) for more information on how to setup secure and fine grained permissions.
5865
5966
Add the following permissions to your newly created role. At the time of writing this document Google offers a search bar where each permission needs to be entered. The table below the search bar will display the roles under this name, and you can select them all together with the first checkbox in the table (header).
@@ -71,7 +78,12 @@ roles/iam.securityAdmin
7178

7279
>Note: If you use PyMC-Server as part of an organization, your GCP admin might have setup a role already (e.g. `pymc-server-role `) and you can easily attach your user to it.
7380
74-
### Status of your deployments
81+
# Yaml configuration
82+
# Command Line Usage
83+
## Start a new single or multi-node cluster (with YAML configuration)
84+
Setup a configuration file (see #Yaml-configuration)
85+
## Start a new single or multi-node cluster (with CLI parameter configuration)
86+
## Status of your deployments
7587

7688
To check the status of all your deployments run `pymcs status`. A table is printed to your terminal.
7789

examples/cluster/dev.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ resources:
88
ports : 80
99

1010
workdir: ./src/pymc_server/workDir_test
11-
pymc_module: pymc-marketing
11+
module_name: pymc-marketing
1212
run: |
1313
echo "Hello, SkyPilot!"
1414
uname -a

examples/stateful_create_actor.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
# create_actor.py
3+
import ray
4+
5+
@ray.remote
6+
class StatefulActor:
7+
def __init__(self):
8+
self.state = {}
9+
self.invocation_count = 0
10+
11+
def set_value(self, key, value):
12+
self.state[key] = value
13+
14+
def get_value(self, key):
15+
return self.state.get(key, None)
16+
17+
def foobar(self):
18+
self.invocation_count +=1
19+
print("_______FOOOBAR________")
20+
print(self.invocation_count)
21+
self.set_value('invocation_count', self.invocation_count + 1)
22+
return "foobar"
23+
24+
# Use __setattr__ to set values dynamically
25+
def __setattr__(self, key, value):
26+
if key == 'state': # Ensure the actual state is set normally
27+
super().__setattr__(key, value)
28+
else:
29+
self.state[key] = value # Set the key-value pair in the state
30+
31+
# Use __getattr__ to get values dynamically
32+
def __getattr__(self, key):
33+
return self.get_value(key)
34+
35+
36+
# Initialize Ray and specify a namespace for easier reconnection
37+
ray.init()
38+
39+
# Create a detached actor under the namespace "my_namespace"
40+
actor = StatefulActor.options(
41+
name="stateful_actor",
42+
lifetime="detached",
43+
namespace="pymc-stateful-actor-dev"
44+
).remote()
45+
46+
# Set some initial state
47+
actor.set_value.remote("key1", "initial_value")
48+
49+
# Print the value to confirm it works
50+
print(ray.get(actor.get_value.remote("key1"))) # Outputs: initial_value
51+
52+
# You can now safely exit this program

examples/stateful_create_actorv2.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
4+
import ray
5+
6+
"""
7+
@ray.remote
8+
class StatefulActor:
9+
def __init__(self):
10+
super().__setattr__("state", {}) # Initialize state
11+
12+
def __setattr__(self, key, value):
13+
print('key: '+ key + ', value:', value)
14+
if key.startswith('_') or key == 'state':
15+
super().__setattr__(key, value)
16+
else:
17+
# Set the value using remote call
18+
ray.get(self.set_value.remote(key, value))
19+
20+
def __getattr__(self, key):
21+
print(key)
22+
# Dynamically fetch the value from the actor's state
23+
if key.startswith('_') or key == 'state':
24+
raise AttributeError(f"Attribute '{key}' not found") # Avoid infinite loop
25+
26+
return ray.get(self.get_value.remote(key))
27+
28+
def foobar(self):
29+
self.invocation_count +=1
30+
print("_______FOOOBAR________")
31+
print(self.invocation_count)
32+
#self.set_value('invocation_count', self.invocation_count + 1)
33+
return "foobar"
34+
35+
def set_value(self, key, value):
36+
self.state[key] = value
37+
38+
def get_value(self, key):
39+
return self.state.get(key, None)
40+
41+
"""
42+
import ray
43+
44+
@ray.remote
45+
class StatefulActor:
46+
def __init__(self):
47+
# Initialize internal state attributes
48+
self._state = {} # Use a private variable for dynamic attributes
49+
self.invocation_count = 0 # Internal state attribute
50+
51+
"""
52+
def __setattr__(self, key, value):
53+
print('set', key, value)
54+
# Allow normal assignment for internal attributes
55+
if '_' in key:
56+
# Directly set internal state attributes
57+
super().__setattr__(key, value)
58+
else:
59+
# Store dynamic attributes in the _state dictionary
60+
self._state[key] = value
61+
62+
def __getattr__(self, key):
63+
print('get', key)
64+
# Allow normal retrieval for internal attributes
65+
if '_' in key:
66+
return super().__getattribute__(key)
67+
# Return from dynamic state
68+
return self._state.get(key, None)
69+
"""
70+
71+
def foobar(self):
72+
# Increment invocation_count normally
73+
print('will halt')
74+
breakpoint()
75+
self.invocation_count += 1
76+
return self.invocation_count
77+
78+
def get_invocation_count(self):
79+
return self.invocation_count
80+
81+
def get_dynamic_value(self, key):
82+
return self._state.get(key, None)
83+
84+
85+
# Initialize Ray
86+
ray.init(
87+
runtime_env={
88+
"env_vars": {"RAY_DEBUG": "1"},
89+
}
90+
)
91+
92+
# Create a detached actor under the namespace "my_namespace"
93+
actor = StatefulActor.options(
94+
name="stateful_actor",
95+
lifetime="detached",
96+
namespace="my_namespace"
97+
).remote()
98+
99+
# breakpoint()
100+
"""
101+
# Use the abstracted way to set and get values dynamically
102+
actor.key1 = 'value1' # Abstracted assignment
103+
print(ray.get(actor.key1)) # Outputs: value1
104+
105+
actor.key2 = 'value2'
106+
print(ray.get(actor.key2)) # Outputs: value2
107+
108+
"""
109+
ray.shutdown()
110+

examples/stateful_kill_actor.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ray
2+
# Reconnect to Ray with the same namespace
3+
ray.init()
4+
# Get the actor from the namespace "my_namespace"
5+
try:
6+
actor = ray.get_actor("stateful_actor", namespace="pymc-stateful-actor-dev")
7+
ray.kill(actor)
8+
except:
9+
pass
10+
try:
11+
actor = ray.get_actor("stateful_actor", namespace="my_namespace")
12+
ray.kill(actor)
13+
except:
14+
pass
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env python
2+
import ray
3+
# -*- coding: utf-8 -*-
4+
5+
6+
ray.init(namespace="my_namespace")
7+
# Get the actor from the namespace "my_namespace"
8+
actor = ray.get_actor("stateful_actor")
9+
remote_val = actor.foobar.remote()
10+
print(ray.get(remote_val))
11+
12+
#assert remote_val == 'bar'
13+
14+
"""
15+
# Use the abstracted way to set and get values dynamically
16+
actor.key1 = 'value1' # Abstracted assignment
17+
print(ray.get(actor.key1)) # Outputs: value1
18+
19+
actor.key2 = 'value2'
20+
print(ray.get(actor.key2)) # Outputs: value2
21+
"""
22+
23+
ray.shutdown()
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# reconnect_actor.py
2+
import ray
3+
4+
# Reconnect to Ray with the same namespace
5+
ray.init(namespace="pymc-stateful-actor-dev")
6+
7+
# Get the actor from the namespace "my_namespace"
8+
actor = ray.get_actor("stateful_actor")
9+
# Get the previous state
10+
print(ray.get(actor.get_value.remote("key1"))) # Outputs: initial_value
11+
print(ray.get(actor.get_value.remote("key2"))) # Outputs: initial_value
12+
13+
print("-----")
14+
# Set new state
15+
actor.set_value.remote("key2", "new_value")
16+
17+
# Get the new state
18+
print(ray.get(actor.get_value.remote("key2"))) # Outputs: new_value
19+
print("-----")
20+
21+
22+
### foobar
23+
r = actor.foobar.remote()
24+
print(ray.get(r))
25+
26+
r = actor.foobar.remote()
27+
print(ray.get(r))
28+
29+
30+
invocation_count = ray.get(actor.get_value.remote('invocation_count'))
31+
print(invocation_count) # Outputs: new_value
32+

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
pymc-marketing
21
nutpie
32
lifetimes
43
numba

src/pymc_server/cli.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,20 @@
22
import sky
33
from typing import Any, Dict, List, Optional, Tuple, Union
44
from pymc_server.utils.yaml import merge_yaml
5-
from pymc_server.launch_cli import launch as cli_launch
5+
#from pymc_server.commands.launch_cli import launch as cli_launch,cli_launch_
6+
from pymc_server.commands.launch_cli import launch_2
7+
from pymc_server.commands.down_cli import down as down_cli
68
from pymc_server.cli_factory import setup_launch_factory, setup_status_factory
79
from sky.usage import usage_lib
10+
from sky.cli import _get_shell_complete_args, _get_click_major_version, _complete_cluster_name, _NaturalOrderGroup, _DocumentedCodeCommand
11+
812

913

1014
from sky.cli import (
1115
status as sky_status,
1216
launch as sky_launch,
1317
check as sky_check
1418
)
15-
1619

1720
# TODO: remove, check pyproject.py for a reference to this function
1821
@click.group()
@@ -24,7 +27,12 @@ def cli():
2427
def status(*args, **kwargs):
2528
""" calls the sky status command by passing the click context"""
2629
ctx = click.get_current_context()
27-
ctx.invoke(sky_status, *args, **kwargs)
30+
#ctx.invoke(_status_test, *args, **kwargs)
31+
print("*args",*args)
32+
data = ctx.invoke(sky_status, *args, **kwargs)
33+
#print("DATA",data)
34+
35+
#ctx.invoke(sky_status, *args, **kwargs)
2836

2937

3038
@setup_launch_factory
@@ -40,7 +48,8 @@ def launch(*args, **kwargs):
4048
"""
4149
# cli_launch(*args, **kwargs)
4250
ctx = click.get_current_context()
43-
ctx.invoke(cli_launch, *args, **kwargs)
51+
ctx.invoke(launch_2, *args, **kwargs)
52+
#ctx.invoke(sky_launch, *args, **kwargs)
4453

4554
@setup_status_factory
4655
@usage_lib.entrypoint
@@ -51,6 +60,43 @@ def check(*args, **kwargs):
5160
ctx.invoke(sky_check, *args, **kwargs)
5261

5362

63+
@cli.command(cls=_DocumentedCodeCommand)
64+
@click.argument('clusters',
65+
nargs=-1,
66+
required=False,
67+
**_get_shell_complete_args(_complete_cluster_name))
68+
@click.option('--all',
69+
'-a',
70+
default=None,
71+
is_flag=True,
72+
help='Tear down all existing clusters.')
73+
@click.option('--yes',
74+
'-y',
75+
is_flag=True,
76+
default=False,
77+
required=False,
78+
help='Skip confirmation prompt.')
79+
@click.option(
80+
'--purge',
81+
'-p',
82+
is_flag=True,
83+
default=False,
84+
required=False,
85+
help=('(Advanced) Forcefully remove the cluster(s) from '
86+
'SkyPilot\'s cluster table, even if the actual cluster termination '
87+
'failed on the cloud. WARNING: This flag should only be set sparingly'
88+
' in certain manual troubleshooting scenarios; with it set, it is the'
89+
' user\'s responsibility to ensure there are no leaked instances and '
90+
'related resources.'))
91+
@usage_lib.entrypoint
92+
def down(*args, **kwargs):
93+
ctx = click.get_current_context()
94+
#sky_check(*args, **kwargs)
95+
ctx.invoke(down_cli, *args, **kwargs)
96+
"""Deletes a local cluster."""
97+
98+
99+
54100
cli.add_command(status)
55101
cli.add_command(launch)
56102
cli.add_command(check)

0 commit comments

Comments
 (0)