Skip to content

Commit 0456701

Browse files
committed
add more projects management support to python/conat api
1 parent 6f78f48 commit 0456701

File tree

4 files changed

+147
-49
lines changed

4 files changed

+147
-49
lines changed

src/packages/conat/hub/api/projects.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ export const projects = {
1010
inviteCollaborator: authFirstRequireAccount,
1111
inviteCollaboratorWithoutAccount: authFirstRequireAccount,
1212
setQuotas: authFirstRequireAccount,
13+
start: authFirstRequireAccount,
14+
stop: authFirstRequireAccount,
1315
};
1416

1517
export type AddCollaborator =
@@ -98,4 +100,7 @@ export interface Projects {
98100
member_host?: number;
99101
always_running?: number;
100102
}) => Promise<void>;
103+
104+
start: (opts: { account_id: string; project_id: string }) => Promise<void>;
105+
stop: (opts: { account_id: string; project_id: string }) => Promise<void>;
101106
}

src/packages/server/conat/api/projects.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,31 @@ export async function setQuotas(opts: {
7575
// @ts-ignore
7676
await project?.setAllQuotas();
7777
}
78+
79+
export async function start({
80+
account_id,
81+
project_id,
82+
}: {
83+
account_id: string;
84+
project_id: string;
85+
}): Promise<void> {
86+
if (!(await isCollaborator({ account_id, project_id }))) {
87+
throw Error("must be collaborator on project to start it");
88+
}
89+
const project = await getProject(project_id);
90+
await project.start();
91+
}
92+
93+
export async function stop({
94+
account_id,
95+
project_id,
96+
}: {
97+
account_id: string;
98+
project_id: string;
99+
}): Promise<void> {
100+
if (!(await isCollaborator({ account_id, project_id }))) {
101+
throw Error("must be collaborator on project to stop it");
102+
}
103+
const project = await getProject(project_id);
104+
await project.stop();
105+
}

src/python/cocalc-api/src/cocalc_api/hub.py

Lines changed: 102 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -101,19 +101,19 @@ def get_names(self, account_ids: list[str]) -> list[str]:
101101
def user_search(self, query: str) -> UserSearchResult:
102102
"""
103103
Search for existing users by name or email address.
104-
104+
105105
Args:
106106
query (str): A query, e.g., partial name, email address, etc.
107-
107+
108108
Returns:
109109
list[UserSearchResult]: array of dicts with account_id, name,
110110
first_name, last_name, last_active (in ms since epoch),
111111
created (in ms since epoch) and email_address_verified.
112-
112+
113113
Examples:
114-
114+
115115
Search for myself:
116-
116+
117117
>>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk...")
118118
>>> hub.system.user_search('w')
119119
[{'account_id': 'd0bdabfd-850e-4c8d-8510-f6f1ecb9a5eb',
@@ -123,10 +123,10 @@ def user_search(self, query: str) -> UserSearchResult:
123123
'last_active': 1756503700052,
124124
'created': 1756056224470,
125125
'email_address_verified': None}]
126-
126+
127127
You can search by email address to ONLY get the user
128128
that has that email address:
129-
129+
130130
>>> hub.system.user_search('[email protected]')
131131
[{'account_id': 'd0bdabfd-850e-4c8d-8510-f6f1ecb9a5eb',
132132
'first_name': 'W',
@@ -145,6 +145,39 @@ class Projects:
145145
def __init__(self, parent: "Hub"):
146146
self._parent = parent
147147

148+
def get(self,
149+
fields: Optional[list[str]] = None,
150+
all: Optional[bool] = False,
151+
project_id: Optional[str] = None):
152+
"""
153+
Get data about projects that you are a collaborator on. Only gets
154+
recent projects by default; set all=True to get all projects.
155+
156+
Args:
157+
fields (Optional[list[str]]): the fields about the project to get.
158+
default: ['project_id', 'title', 'last_edited', 'state'], but see
159+
https://github.com/sagemathinc/cocalc/blob/master/src/packages/util/db-schema/projects.ts
160+
all (Optional[bool]): if True, return ALL your projects,
161+
not just the recent ones. False by default.
162+
project_id (Optional[string]): if given as a project_id, gets just the
163+
one project (as a length of length 1).
164+
165+
Returns:
166+
list[dict[str,Any]]: list of projects
167+
"""
168+
if fields is None:
169+
fields = ['project_id', 'title', 'last_edited', 'state']
170+
v: list[dict[str, Any]] = [{}]
171+
for field in fields:
172+
v[0][field] = None
173+
if project_id:
174+
v[0]['project_id'] = project_id
175+
query: dict[str, list[dict[str, None]]] = {}
176+
table = 'projects_all' if all else 'projects'
177+
query[table] = v
178+
result = self._parent.db.query(query)
179+
return result[table]
180+
148181
@api_method("projects.copyPathBetweenProjects")
149182
def copy_path_between_projects(
150183
self,
@@ -224,6 +257,26 @@ def remove_collaborator(self, project_id: str, account_id: str):
224257
"""
225258
...
226259

260+
@api_method("projects.start")
261+
def start(self, project_id: str):
262+
"""
263+
Start a project.
264+
265+
Args:
266+
project_id (str): project_id of the project to start
267+
"""
268+
...
269+
270+
@api_method("projects.stop")
271+
def stop(self, project_id: str):
272+
"""
273+
Stop a project.
274+
275+
Args:
276+
project_id (str): project_id of the project to stop
277+
"""
278+
...
279+
227280

228281
class Jupyter:
229282

@@ -265,19 +318,19 @@ def execute(
265318
266319
Returns:
267320
Any: JSON response containing execution results.
268-
321+
269322
Examples:
270323
Execute a simple sum using a Jupyter kernel:
271-
324+
272325
>>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk-...")
273-
>>> hub.jupyter.execute(history=['a=100;print(a)'],
326+
>>> hub.jupyter.execute(history=['a=100;print(a)'],
274327
input='sum(range(a+1))',
275328
kernel='python3')
276329
{'output': [{'data': {'text/plain': '5050'}}], ...}
277-
330+
278331
Factor a number using the sagemath kernel in a specific project:
279-
280-
>>> hub.jupyter.execute(history=['a=2025'], input='factor(a)', kernel='sagemath',
332+
333+
>>> hub.jupyter.execute(history=['a=2025'], input='factor(a)', kernel='sagemath',
281334
... project_id='6e75dbf1-0342-4249-9dce-6b21648656e9')
282335
{'output': [{'data': {'text/plain': '3^4 * 5^2'}}], ...}
283336
"""
@@ -314,21 +367,21 @@ def query(self, query: dict[str, Any]) -> dict[str, Any]:
314367
"""
315368
Do a user query. The input is of one of the following forms, where the tables are defined at
316369
https://github.com/sagemathinc/cocalc/tree/master/src/packages/util/db-schema
317-
370+
318371
- `{"table-name":{"key":"value", ...}}` with no None values sets one record in the database
319-
- `{"table-name":[{"key":"value", "key2":None...}]}` gets an array of all matching records
372+
- `{"table-name":[{"key":"value", "key2":None...}]}` gets an array of all matching records
320373
in the database, filling in None's with the actual values.
321374
- `{"table-name:{"key":"value", "key2":None}}` gets one record, filling in None's with actual values.
322-
375+
323376
This is used for most configuration, e.g., user names, project descriptions, etc.
324377
325378
Args:
326379
query (dict[str, Any]): Object that defines the query, as explained above.
327-
380+
328381
Examples:
329-
382+
330383
Get and also change your first name:
331-
384+
332385
>>> import cocalc_api; hub = cocalc_api.Hub(api_key="sk...")
333386
>>> hub.db.query({"accounts":{"first_name":None}})
334387
{'accounts': {'first_name': 'William'}}
@@ -346,8 +399,11 @@ def __init__(self, parent: "Hub"):
346399
self._parent = parent
347400

348401
@api_method("messages.send")
349-
def send(self, subject: str, body: str, to_ids: list[str],
350-
reply_id: Optional[int]) -> int:
402+
def send(self,
403+
subject: str,
404+
body: str,
405+
to_ids: list[str],
406+
reply_id: Optional[int] = None) -> int:
351407
"""
352408
Send a message to one or more users.
353409
@@ -365,9 +421,10 @@ def send(self, subject: str, body: str, to_ids: list[str],
365421
@api_method("messages.get")
366422
def get(
367423
self,
368-
limit: Optional[int],
369-
offset: Optional[int],
370-
type: Optional[Literal["received", "sent", "new", "starred", "liked"]],
424+
limit: Optional[int] = None,
425+
offset: Optional[int] = None,
426+
type: Optional[Literal["received", "sent", "new", "starred",
427+
"liked"]] = None,
371428
) -> list[MessageType]:
372429
"""
373430
Get your messages.
@@ -418,23 +475,27 @@ def get(self, name: str):
418475
419476
Args:
420477
name (str) - name of the organization
421-
478+
422479
Returns:
423480
Any: ...
424481
"""
425482
raise NotImplementedError
426483

427484
@api_method("org.set")
428-
def set(self, name: str, title: Optional[str], description: Optional[str],
429-
email_address: Optional[str], link: Optional[str]):
485+
def set(self,
486+
name: str,
487+
title: Optional[str] = None,
488+
description: Optional[str] = None,
489+
email_address: Optional[str] = None,
490+
link: Optional[str] = None):
430491
"""
431492
Set properties of an organization.
432493
433494
Args:
434495
name (str): name of the organization
435496
title (Optional[str]): the title of the organization
436497
description (Optional[str]): description of the organization
437-
email_address (Optional[str]): email address to reach the organization
498+
email_address (Optional[str]): email address to reach the organization
438499
(nothing to do with a cocalc account)
439500
link (Optional[str]): a website of the organization
440501
"""
@@ -467,8 +528,12 @@ def add_user(self, name: str, user: str):
467528
raise NotImplementedError
468529

469530
@api_method("org.createUser")
470-
def create_user(self, name: str, email: str, firstName: Optional[str],
471-
lastName: Optional[str], password: Optional[str]) -> str:
531+
def create_user(self,
532+
name: str,
533+
email: str,
534+
firstName: Optional[str] = None,
535+
lastName: Optional[str] = None,
536+
password: Optional[str] = None) -> str:
472537
"""
473538
Create a new cocalc account that is a member of the
474539
named organization.
@@ -478,10 +543,10 @@ def create_user(self, name: str, email: str, firstName: Optional[str],
478543
email (str): email address
479544
firstName (Optional[str]): optional first name of the user
480545
lastName (Optional[str]): optional last name of the user
481-
password (Optional[str]): optional password (will be randomized if
546+
password (Optional[str]): optional password (will be randomized if
482547
not given; you can instead use create_token to grant temporary
483548
account access).
484-
549+
485550
Returns:
486551
str: account_id of the new user
487552
"""
@@ -496,10 +561,10 @@ def create_token(self, user: str) -> TokenType:
496561
497562
Args:
498563
user (str): email address or account_id
499-
564+
500565
Returns:
501566
TokenType: token that grants temporary access
502-
567+
503568
Notes:
504569
The returned `TokenType` has the following fields:
505570
@@ -528,10 +593,10 @@ def get_users(self, name: str) -> OrganizationUser:
528593
529594
Args:
530595
name (str): name of the organization
531-
596+
532597
Returns:
533598
list[OrganizationUser]
534-
599+
535600
Notes:
536601
The returned `OrganizationUser` has the following fields:
537602
@@ -547,7 +612,7 @@ def message(self, name: str, subject: str, body: str):
547612
"""
548613
Send a message from you to every account that is a member of
549614
the named organization.
550-
615+
551616
Args:
552617
name (str): name of the organization
553618
subject (str): plain text subject of the message

src/python/cocalc-api/src/cocalc_api/project.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,11 @@ def ping(self) -> PingResponse:
6060
6161
Returns:
6262
Any: JSON object containing the current server time.
63-
63+
6464
Examples:
6565
Ping a project. The api_key can be either an account api key or a project
6666
specific api key (in which case the project_id option is optional):
67-
67+
6868
>>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...", project_id='...')
6969
>>> project.ping()
7070
{'now': 1756489740133}
@@ -88,9 +88,9 @@ def exec(
8888
) -> ExecuteCodeOutput:
8989
"""
9090
Execute an arbitrary shell command in the project.
91-
91+
9292
Args:
93-
93+
9494
command (str): command to run; can be a program name (e.g., "ls") or absolute path, or a full bash script
9595
args (Optional[list[str]]): optional arguments to the command
9696
path (Optional[str]): path (relative to HOME directory) where command will be run
@@ -100,7 +100,7 @@ def exec(
100100
bash (Optional[bool]): if True, ignore args and evaluate command as a bash command
101101
env (Optional[dict[str, Any]]): if given, added to exec environment
102102
compute_server_id (Optional[number]): compute server to run code on (instead of home base project)
103-
103+
104104
Returns:
105105
ExecuteCodeOutput: Result of executing the command.
106106
@@ -111,11 +111,11 @@ def exec(
111111
- `stderr` (str): Output written to stderr.
112112
- `exit_code` (int): Exit code of the process.
113113
114-
114+
115115
Examples:
116-
116+
117117
>>> import cocalc_api
118-
>>> project = cocalc_api.Project(api_key="sk-...",
118+
>>> project = cocalc_api.Project(api_key="sk-...",
119119
project_id='6e75dbf1-0342-4249-9dce-6b21648656e9')
120120
>>> project.system.exec(command="echo 'hello from cocalc'")
121121
{'stdout': 'hello from cocalc\\n', 'stderr':'', 'exit_code': 0}
@@ -141,13 +141,13 @@ def jupyter_execute(
141141
142142
Returns:
143143
Any: JSON response containing execution results.
144-
144+
145145
Examples:
146146
Execute a simple sum using a Jupyter kernel:
147-
147+
148148
>>> import cocalc_api; project = cocalc_api.Project(api_key="sk-...")
149-
>>> project.jupyter.execute(history=['a=100;print(a)'],
150-
input='sum(range(a+1))',
149+
>>> project.jupyter.execute(history=['a=100;print(a)'],
150+
input='sum(range(a+1))',
151151
kernel='python3')
152152
{'output': [{'data': {'text/plain': '5050'}}], ...}
153153
"""

0 commit comments

Comments
 (0)