modAL 1.6.2.dev9__py3-none-any.whl → 1.6.2.dev10__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.
- modal/_workspace.py +14 -11
- modal/cli/workspace.py +365 -1
- modal/client.pyi +2 -2
- modal/functions.pyi +6 -6
- modal/workspace.pyi +6 -18
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/METADATA +1 -1
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/RECORD +12 -12
- modal_version/__init__.py +1 -1
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/WHEEL +0 -0
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/entry_points.txt +0 -0
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/licenses/LICENSE +0 -0
- {modal-1.6.2.dev9.dist-info → modal-1.6.2.dev10.dist-info}/top_level.txt +0 -0
modal/_workspace.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
|
|
9
9
|
from google.protobuf.empty_pb2 import Empty
|
|
10
10
|
|
|
11
|
-
from modal.exception import DeprecationError, InvalidError
|
|
11
|
+
from modal.exception import DeprecationError, InvalidError, NotFoundError
|
|
12
12
|
from modal.types import ServiceUserInfo
|
|
13
13
|
from modal_proto import api_pb2
|
|
14
14
|
|
|
@@ -629,8 +629,7 @@ class _WorkspaceServiceUserManager:
|
|
|
629
629
|
service_user_name: The name of the Service User.
|
|
630
630
|
environment_name: The name of the Environment to which the Service User's new role will
|
|
631
631
|
apply.
|
|
632
|
-
role: The access level to grant this Service User.
|
|
633
|
-
`"viewer"`, or `"contributor"`.
|
|
632
|
+
role: The access level to grant this Service User.
|
|
634
633
|
|
|
635
634
|
Examples:
|
|
636
635
|
|
|
@@ -644,6 +643,9 @@ class _WorkspaceServiceUserManager:
|
|
|
644
643
|
```
|
|
645
644
|
"""
|
|
646
645
|
|
|
646
|
+
if not role:
|
|
647
|
+
raise InvalidError("Invalid argument: `role` must not be empty.")
|
|
648
|
+
|
|
647
649
|
if not self._workspace._is_hydrated:
|
|
648
650
|
await self._workspace.hydrate()
|
|
649
651
|
|
|
@@ -663,8 +665,6 @@ class _WorkspaceServiceUserManager:
|
|
|
663
665
|
async def info(self, name: str) -> ServiceUserInfo:
|
|
664
666
|
"""Get information about a specific Service User.
|
|
665
667
|
|
|
666
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
667
|
-
|
|
668
668
|
Args:
|
|
669
669
|
name: The name of the Service User to introspect.
|
|
670
670
|
|
|
@@ -691,8 +691,6 @@ class _WorkspaceServiceUserManager:
|
|
|
691
691
|
async def list(self) -> list[ServiceUserInfo]:
|
|
692
692
|
"""List all Service Users in the current workspace.
|
|
693
693
|
|
|
694
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
695
|
-
|
|
696
694
|
Returns:
|
|
697
695
|
A list of `ServiceUserInfo` dataclasses.
|
|
698
696
|
|
|
@@ -712,13 +710,12 @@ class _WorkspaceServiceUserManager:
|
|
|
712
710
|
|
|
713
711
|
return [ServiceUserInfo._from_proto(su) for su in resp.service_users]
|
|
714
712
|
|
|
715
|
-
async def delete(self, name: str) -> None:
|
|
713
|
+
async def delete(self, name: str, *, allow_missing: bool = False) -> None:
|
|
716
714
|
"""Permanently delete a Service User.
|
|
717
715
|
|
|
718
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
719
|
-
|
|
720
716
|
Args:
|
|
721
717
|
name: The name of the Service User to delete.
|
|
718
|
+
allow_missing: If `True`, do not throw an exception if the Service User does not exist.
|
|
722
719
|
|
|
723
720
|
Examples:
|
|
724
721
|
|
|
@@ -731,4 +728,10 @@ class _WorkspaceServiceUserManager:
|
|
|
731
728
|
if not self._workspace._is_hydrated:
|
|
732
729
|
await self._workspace.hydrate()
|
|
733
730
|
|
|
734
|
-
|
|
731
|
+
try:
|
|
732
|
+
await self._workspace.client._stub.ServiceUserDelete(
|
|
733
|
+
api_pb2.ServiceUserDeleteRequest(service_user_name=name)
|
|
734
|
+
)
|
|
735
|
+
except NotFoundError:
|
|
736
|
+
if not allow_missing:
|
|
737
|
+
raise
|
modal/cli/workspace.py
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
# Copyright Modal Labs 2025
|
|
2
2
|
import json as json_mod
|
|
3
|
+
from datetime import timezone
|
|
3
4
|
|
|
4
5
|
import click
|
|
5
6
|
import rich
|
|
6
7
|
import rich.box
|
|
7
8
|
import rich.table
|
|
9
|
+
from rich.box import SIMPLE, SIMPLE_HEAD
|
|
10
|
+
from rich.text import Text
|
|
8
11
|
|
|
9
|
-
from modal._utils.
|
|
12
|
+
from modal._utils.async_utils import synchronizer
|
|
13
|
+
from modal._utils.time_utils import parse_duration, timestamp_to_localized_str
|
|
14
|
+
from modal._workspace import _Workspace
|
|
10
15
|
from modal.cli.utils import confirm_or_suggest_yes, display_table, yes_option
|
|
11
16
|
from modal.output import OutputManager
|
|
12
17
|
from modal.workspace import Workspace
|
|
18
|
+
from modal_proto import api_pb2
|
|
13
19
|
|
|
14
20
|
from ._help import ModalGroup
|
|
15
21
|
|
|
@@ -267,3 +273,361 @@ def settings_set(setting: str, value: str):
|
|
|
267
273
|
except Exception as e:
|
|
268
274
|
rich.print(f"[red]✗[/red] {setting}: {e}")
|
|
269
275
|
raise SystemExit(1)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
SERVICE_USERS_HELP_TEXT = """Manage Service Users. Must be workspace manager or owner.
|
|
279
|
+
|
|
280
|
+
Service users are programmatic accounts that allow automated systems to interact with Modal. They're
|
|
281
|
+
ideal for CI/CD pipelines, automated deployments, and other workflows that need to authenticate.
|
|
282
|
+
|
|
283
|
+
Service User credentials are typically made available to a process as environment variables:
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
export MODAL_TOKEN_ID=your-token-id
|
|
287
|
+
export MODAL_TOKEN_SECRET=your-token-secret
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
A newly created Service User has no permissions by default. In order to interact with a specific
|
|
291
|
+
Environment, Service Users must be granted explicit permissions.
|
|
292
|
+
|
|
293
|
+
See https://modal.com/docs/guide/service-users for more information.
|
|
294
|
+
"""
|
|
295
|
+
|
|
296
|
+
service_users_cli = ModalGroup(name="service-users", help=SERVICE_USERS_HELP_TEXT)
|
|
297
|
+
workspace_cli.add_command(service_users_cli)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@service_users_cli.command("create", no_args_is_help=True)
|
|
301
|
+
@click.argument("name")
|
|
302
|
+
@click.option(
|
|
303
|
+
"--expires-in",
|
|
304
|
+
"expires_in_str",
|
|
305
|
+
default=None,
|
|
306
|
+
help="Expiration time. Accepts integers (interpreted as seconds), as well as values suffixed by "
|
|
307
|
+
"`s`, `m`, `h`, or `d`.",
|
|
308
|
+
)
|
|
309
|
+
@click.option("--json", "json", is_flag=True, default=False, help="Output as JSON.")
|
|
310
|
+
@synchronizer.create_blocking
|
|
311
|
+
async def service_users_create(name: str, expires_in_str: str | None, json: bool):
|
|
312
|
+
"""Create a Service User in this workspace.
|
|
313
|
+
|
|
314
|
+
In order to run this command, you must have at least Manager level permissions in the Workspace.
|
|
315
|
+
|
|
316
|
+
By default, Service Users have no permissions. To explicitly grant the Service User permissions
|
|
317
|
+
to interact with a given environment, use [`modal workspace service-users set-role`](https://modal.com/docs/cli/latest/workspace#modal-workspace-service-users-set-role)
|
|
318
|
+
or [`modal environment roles update`](https://modal.com/docs/cli/latest/environment#modal-environment-roles-update).
|
|
319
|
+
|
|
320
|
+
Optionally, you can also specify an expiration window by passing `--expires-in`. After the specified
|
|
321
|
+
amount of time elapses, this Service User will be deleted, and any credentials associated with
|
|
322
|
+
this Service User will stop working. The option expects a positive integer value, optionally
|
|
323
|
+
suffixed by the units `s`, `m`, `h`, or `d`, meaning seconds, minutes, hours, and days respectively.
|
|
324
|
+
If no suffix is provided, the value is interpreted as seconds. If `--expires-in` is not provided,
|
|
325
|
+
the newly created Service User will never expire.
|
|
326
|
+
|
|
327
|
+
Examples:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
# Create a Service User named `test-1`, which never expires:
|
|
331
|
+
modal workspace service-users create test-1
|
|
332
|
+
|
|
333
|
+
# Two ways of creating a Service User which is valid for an hour:
|
|
334
|
+
modal workspace service-users create test-2 --expires-in 3600
|
|
335
|
+
modal workspace service-users create test-3 --expires-in 1h
|
|
336
|
+
|
|
337
|
+
# Create a Service User which is valid for 1 year, outputting JSON:
|
|
338
|
+
modal workspace service-users create test-4 --expires-in 365d --json
|
|
339
|
+
```
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
expires_in: int | None
|
|
343
|
+
if expires_in_str is None:
|
|
344
|
+
expires_in = None
|
|
345
|
+
else:
|
|
346
|
+
try:
|
|
347
|
+
expires_in = int(expires_in_str)
|
|
348
|
+
except ValueError:
|
|
349
|
+
try:
|
|
350
|
+
expires_in = int(parse_duration(expires_in_str).total_seconds())
|
|
351
|
+
except ValueError as e:
|
|
352
|
+
raise click.UsageError(str(e)) from e
|
|
353
|
+
|
|
354
|
+
if expires_in is not None and expires_in <= 0:
|
|
355
|
+
raise click.UsageError("Value passed to `--expires-in` must be a positive integer.")
|
|
356
|
+
|
|
357
|
+
if expires_in is None:
|
|
358
|
+
req = api_pb2.ServiceUserCreateRequest(name=name)
|
|
359
|
+
else:
|
|
360
|
+
req = api_pb2.ServiceUserCreateRequest(name=name, expires_in_seconds=expires_in)
|
|
361
|
+
|
|
362
|
+
ws = _Workspace.from_context()
|
|
363
|
+
await ws.hydrate()
|
|
364
|
+
|
|
365
|
+
resp = await ws.client._stub.ServiceUserCreate(req)
|
|
366
|
+
|
|
367
|
+
output_manager = OutputManager.get()
|
|
368
|
+
|
|
369
|
+
if json:
|
|
370
|
+
output_manager.print_json(
|
|
371
|
+
json_mod.dumps(
|
|
372
|
+
{
|
|
373
|
+
"token_id": resp.token_id,
|
|
374
|
+
"token_secret": resp.token_secret,
|
|
375
|
+
"expires_at": timestamp_to_localized_str(resp.expires_at.ToDatetime(timezone.utc).timestamp())
|
|
376
|
+
if expires_in
|
|
377
|
+
else None,
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
return
|
|
382
|
+
|
|
383
|
+
output_manager.print(f"[green]✓[/green] Successfully created Service User `{name}`, token details below:")
|
|
384
|
+
|
|
385
|
+
table = rich.table.Table("", pad_edge=False, box=None)
|
|
386
|
+
table.add_row("Token ID:", resp.token_id)
|
|
387
|
+
table.add_row("Token Secret:", resp.token_secret)
|
|
388
|
+
|
|
389
|
+
if expires_in:
|
|
390
|
+
table.add_row(
|
|
391
|
+
"Expires At:", timestamp_to_localized_str(resp.expires_at.ToDatetime(timezone.utc).timestamp(), isotz=False)
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
output_manager.print(table)
|
|
395
|
+
output_manager.print("")
|
|
396
|
+
|
|
397
|
+
output_manager.print("[yellow]:warning:[/yellow] This is the only time you will be able to see the Token Secret.")
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@service_users_cli.command("list")
|
|
401
|
+
@click.option("--json", is_flag=True, default=False, help="Output as JSON.")
|
|
402
|
+
def service_users_list(json: bool):
|
|
403
|
+
"""List all Service Users in this workspace.
|
|
404
|
+
|
|
405
|
+
In order to run this command, you must have at least Manager level permissions in the Workspace.
|
|
406
|
+
|
|
407
|
+
Examples:
|
|
408
|
+
|
|
409
|
+
```bash
|
|
410
|
+
# List all Service Users with a human-readable output format
|
|
411
|
+
modal workspace service-users list
|
|
412
|
+
|
|
413
|
+
# List all Service Users and return JSON output
|
|
414
|
+
modal workspace service-users list --json
|
|
415
|
+
```
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
ws = Workspace.from_context()
|
|
419
|
+
service_users = ws.service_users.list()
|
|
420
|
+
|
|
421
|
+
output_manager = OutputManager.get()
|
|
422
|
+
|
|
423
|
+
if json:
|
|
424
|
+
output_manager.print_json(
|
|
425
|
+
json_mod.dumps(
|
|
426
|
+
[
|
|
427
|
+
{
|
|
428
|
+
"name": su.name,
|
|
429
|
+
"service_user_id": su.service_user_id,
|
|
430
|
+
"token_id": su.token_id,
|
|
431
|
+
"created_by": su.created_by,
|
|
432
|
+
"created_at": timestamp_to_localized_str(su.created_at.timestamp()),
|
|
433
|
+
"last_used_at": timestamp_to_localized_str(su.last_used_at.timestamp())
|
|
434
|
+
if su.last_used_at is not None
|
|
435
|
+
else None,
|
|
436
|
+
"expires_at": timestamp_to_localized_str(su.expires_at.timestamp())
|
|
437
|
+
if su.expires_at is not None
|
|
438
|
+
else None,
|
|
439
|
+
"environment_roles": su.environment_roles,
|
|
440
|
+
}
|
|
441
|
+
for su in service_users
|
|
442
|
+
]
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
return
|
|
446
|
+
|
|
447
|
+
table = rich.table.Table(box=SIMPLE_HEAD)
|
|
448
|
+
table.add_column("Name")
|
|
449
|
+
table.add_column("Token ID")
|
|
450
|
+
table.add_column("Created By")
|
|
451
|
+
table.add_column("Expires")
|
|
452
|
+
|
|
453
|
+
for su in service_users:
|
|
454
|
+
table.add_row(
|
|
455
|
+
su.name,
|
|
456
|
+
su.token_id,
|
|
457
|
+
su.created_by,
|
|
458
|
+
timestamp_to_localized_str(su.expires_at.timestamp(), isotz=False) if su.expires_at is not None else "-",
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
output_manager.print(table)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
@service_users_cli.command("delete", no_args_is_help=True)
|
|
465
|
+
@click.argument("name")
|
|
466
|
+
@click.option("--allow-missing", is_flag=True, default=False, help="Don't error if the Volume doesn't exist.")
|
|
467
|
+
@yes_option
|
|
468
|
+
def service_users_delete(name: str, *, allow_missing: bool, yes: bool):
|
|
469
|
+
"""Delete a Service User in this Workspace.
|
|
470
|
+
|
|
471
|
+
Note that this action is irreversible. Any existing processes / scripts that use the credentials
|
|
472
|
+
associated with the target Service User will stop being able to authenticate with Modal.
|
|
473
|
+
|
|
474
|
+
```bash
|
|
475
|
+
# Delete the Service User named `test-service-user`
|
|
476
|
+
modal workspace service-users delete test-service-user
|
|
477
|
+
```
|
|
478
|
+
"""
|
|
479
|
+
if not yes:
|
|
480
|
+
confirm_or_suggest_yes(
|
|
481
|
+
f"Are you sure you want to irrevocably delete the Service User '{name}' and its associated credentials?"
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
ws = Workspace.from_context()
|
|
485
|
+
ws.service_users.delete(name, allow_missing=allow_missing)
|
|
486
|
+
|
|
487
|
+
output_manager = OutputManager.get()
|
|
488
|
+
output_manager.print(f"[green]✓[/green] Deleted Service User `{name}`.")
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
@service_users_cli.command("info", no_args_is_help=True)
|
|
492
|
+
@click.argument("name")
|
|
493
|
+
@click.option("--json", is_flag=True, default=False, help="Output as JSON.")
|
|
494
|
+
def service_users_info(name: str, json: bool):
|
|
495
|
+
"""Inspect a Service User in this workspace.
|
|
496
|
+
|
|
497
|
+
Examples:
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
# Inspect the Service User named `test-service-user`
|
|
501
|
+
modal workspace service-users info test-service-user
|
|
502
|
+
|
|
503
|
+
# Inspect the Service User named `test-service-user-2`, outputting JSON
|
|
504
|
+
modal workspace service-users info test-service-user-2 --json
|
|
505
|
+
```
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
ws = Workspace.from_context()
|
|
509
|
+
su = ws.service_users.info(name)
|
|
510
|
+
|
|
511
|
+
output_manager = OutputManager.get()
|
|
512
|
+
if json:
|
|
513
|
+
output_manager.print_json(
|
|
514
|
+
json_mod.dumps(
|
|
515
|
+
{
|
|
516
|
+
"name": su.name,
|
|
517
|
+
"service_user_id": su.service_user_id,
|
|
518
|
+
"token_id": su.token_id,
|
|
519
|
+
"created_by": su.created_by,
|
|
520
|
+
"created_at": timestamp_to_localized_str(su.created_at.timestamp()),
|
|
521
|
+
"last_used_at": timestamp_to_localized_str(su.last_used_at.timestamp())
|
|
522
|
+
if su.last_used_at is not None
|
|
523
|
+
else None,
|
|
524
|
+
"expires_at": timestamp_to_localized_str(su.expires_at.timestamp())
|
|
525
|
+
if su.expires_at is not None
|
|
526
|
+
else None,
|
|
527
|
+
"environment_roles": su.environment_roles,
|
|
528
|
+
}
|
|
529
|
+
)
|
|
530
|
+
)
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
rows: list[tuple[str, str]] = []
|
|
534
|
+
|
|
535
|
+
rows.append(("Name:", su.name))
|
|
536
|
+
rows.append(("Service User ID:", su.service_user_id))
|
|
537
|
+
rows.append(("Token ID:", su.token_id))
|
|
538
|
+
|
|
539
|
+
environment_roles: list[tuple[str, str]] = []
|
|
540
|
+
for env_name, role in su.environment_roles.items():
|
|
541
|
+
if role == "no-access":
|
|
542
|
+
continue
|
|
543
|
+
|
|
544
|
+
environment_roles.append((env_name, role))
|
|
545
|
+
|
|
546
|
+
rows.append(("Created By:", su.created_by))
|
|
547
|
+
rows.append(("Created At:", str(timestamp_to_localized_str(su.created_at.timestamp(), isotz=False))))
|
|
548
|
+
rows.append(
|
|
549
|
+
(
|
|
550
|
+
"Last Used:",
|
|
551
|
+
str(timestamp_to_localized_str(su.last_used_at.timestamp(), isotz=False))
|
|
552
|
+
if su.last_used_at is not None
|
|
553
|
+
else "-",
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
rows.append(
|
|
557
|
+
(
|
|
558
|
+
"Expires:",
|
|
559
|
+
str(timestamp_to_localized_str(su.expires_at.timestamp(), isotz=False))
|
|
560
|
+
if su.expires_at is not None
|
|
561
|
+
else "-",
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
if len(environment_roles) > 0:
|
|
566
|
+
rows.append(("", ""))
|
|
567
|
+
rows.append(("Environment Access:", ""))
|
|
568
|
+
for env_name, r in environment_roles:
|
|
569
|
+
rows.append((f" {env_name}", r.capitalize()))
|
|
570
|
+
|
|
571
|
+
if len(environment_roles) == 0:
|
|
572
|
+
output_manager.print("")
|
|
573
|
+
output_manager.print(" [yellow]:warning:[/yellow] This Service User does not have access to any Environments.")
|
|
574
|
+
|
|
575
|
+
table = rich.table.Table(box=SIMPLE, show_header=False)
|
|
576
|
+
for row in rows:
|
|
577
|
+
left, right = row
|
|
578
|
+
table.add_row(left, Text(right, justify="right"))
|
|
579
|
+
|
|
580
|
+
output_manager.print(table)
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
@service_users_cli.command("set-role", no_args_is_help=True)
|
|
584
|
+
@click.argument("service_user_name")
|
|
585
|
+
@click.argument("environment_name")
|
|
586
|
+
@click.argument("role")
|
|
587
|
+
def service_users_set_role(
|
|
588
|
+
service_user_name: str,
|
|
589
|
+
environment_name: str,
|
|
590
|
+
role: str,
|
|
591
|
+
):
|
|
592
|
+
"""Update a Service User's role in a specific Environment.
|
|
593
|
+
|
|
594
|
+
Examples:
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
# Give Contributor access in Environment `main` to Service User `test-service-user`
|
|
598
|
+
modal workspace service-users set-role test-service-user main contributor
|
|
599
|
+
|
|
600
|
+
# Revoke access to Environment `prod` from Service User `test-service-user`
|
|
601
|
+
modal workspace service-users set-role test-service-user prod no-access
|
|
602
|
+
```
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
ws = Workspace.from_context()
|
|
606
|
+
info = ws.service_users.info(service_user_name)
|
|
607
|
+
|
|
608
|
+
existing_role = info.environment_roles.get(environment_name) or "no-access"
|
|
609
|
+
|
|
610
|
+
ws.service_users.set_role(service_user_name, environment_name, role=role)
|
|
611
|
+
|
|
612
|
+
output_manager = OutputManager.get()
|
|
613
|
+
if role == existing_role:
|
|
614
|
+
if role == "no-access":
|
|
615
|
+
output_manager.print(
|
|
616
|
+
f"Service User [bold]{service_user_name}[/bold] does not have access to "
|
|
617
|
+
f"Environment [bold]{environment_name}[/bold] already. Role was not updated."
|
|
618
|
+
)
|
|
619
|
+
else:
|
|
620
|
+
output_manager.print(
|
|
621
|
+
f"Service User [bold]{service_user_name}[/bold] is already a {role.capitalize()} in "
|
|
622
|
+
f"Environment [bold]{environment_name}[/bold]. Role was not updated."
|
|
623
|
+
)
|
|
624
|
+
elif role == "no-access":
|
|
625
|
+
output_manager.print(
|
|
626
|
+
f"[green]✓[/green] Revoked Service User [bold]{service_user_name}[/bold]'s access to "
|
|
627
|
+
f"Environment [bold]{environment_name}[/bold]."
|
|
628
|
+
)
|
|
629
|
+
else:
|
|
630
|
+
output_manager.print(
|
|
631
|
+
f"[green]✓[/green] Updated Service User [bold]{service_user_name}[/bold]'s role in "
|
|
632
|
+
f"Environment [bold]{environment_name}[/bold] to {role.capitalize()}."
|
|
633
|
+
)
|
modal/client.pyi
CHANGED
|
@@ -80,7 +80,7 @@ class _Client:
|
|
|
80
80
|
server_url: str,
|
|
81
81
|
client_type: int,
|
|
82
82
|
credentials: typing.Optional[tuple[str, str]],
|
|
83
|
-
version: str = "1.6.2.
|
|
83
|
+
version: str = "1.6.2.dev10",
|
|
84
84
|
*,
|
|
85
85
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
86
86
|
):
|
|
@@ -290,7 +290,7 @@ class Client:
|
|
|
290
290
|
server_url: str,
|
|
291
291
|
client_type: int,
|
|
292
292
|
credentials: typing.Optional[tuple[str, str]],
|
|
293
|
-
version: str = "1.6.2.
|
|
293
|
+
version: str = "1.6.2.dev10",
|
|
294
294
|
*,
|
|
295
295
|
oauth_credentials: typing.Optional[_OAuthCredentials] = None,
|
|
296
296
|
):
|
modal/functions.pyi
CHANGED
|
@@ -619,7 +619,7 @@ class Function(
|
|
|
619
619
|
|
|
620
620
|
_call_generator: ___call_generator_spec
|
|
621
621
|
|
|
622
|
-
class __remote_spec(typing_extensions.Protocol[
|
|
622
|
+
class __remote_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
623
623
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> ReturnType_INNER:
|
|
624
624
|
"""Calls the function remotely, executing it with the given arguments and returning the execution's result.
|
|
625
625
|
|
|
@@ -644,7 +644,7 @@ class Function(
|
|
|
644
644
|
"""
|
|
645
645
|
...
|
|
646
646
|
|
|
647
|
-
remote: __remote_spec[modal._functions.
|
|
647
|
+
remote: __remote_spec[modal._functions.P, modal._functions.ReturnType]
|
|
648
648
|
|
|
649
649
|
class __remote_gen_spec(typing_extensions.Protocol):
|
|
650
650
|
def __call__(self, /, *args, **kwargs) -> typing.Generator[typing.Any, None, None]:
|
|
@@ -694,7 +694,7 @@ class Function(
|
|
|
694
694
|
"""
|
|
695
695
|
...
|
|
696
696
|
|
|
697
|
-
class ___experimental_spawn_spec(typing_extensions.Protocol[
|
|
697
|
+
class ___experimental_spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
698
698
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
699
699
|
"""[Experimental] Calls the function with the given arguments, without waiting for the results.
|
|
700
700
|
|
|
@@ -727,7 +727,7 @@ class Function(
|
|
|
727
727
|
"""
|
|
728
728
|
...
|
|
729
729
|
|
|
730
|
-
_experimental_spawn: ___experimental_spawn_spec[modal._functions.
|
|
730
|
+
_experimental_spawn: ___experimental_spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
731
731
|
|
|
732
732
|
class ___spawn_map_inner_spec(typing_extensions.Protocol[P_INNER]):
|
|
733
733
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> None: ...
|
|
@@ -735,7 +735,7 @@ class Function(
|
|
|
735
735
|
|
|
736
736
|
_spawn_map_inner: ___spawn_map_inner_spec[modal._functions.P]
|
|
737
737
|
|
|
738
|
-
class __spawn_spec(typing_extensions.Protocol[
|
|
738
|
+
class __spawn_spec(typing_extensions.Protocol[P_INNER, ReturnType_INNER]):
|
|
739
739
|
def __call__(self, /, *args: P_INNER.args, **kwargs: P_INNER.kwargs) -> FunctionCall[ReturnType_INNER]:
|
|
740
740
|
"""Calls the function with the given arguments, without waiting for the results.
|
|
741
741
|
|
|
@@ -768,7 +768,7 @@ class Function(
|
|
|
768
768
|
"""
|
|
769
769
|
...
|
|
770
770
|
|
|
771
|
-
spawn: __spawn_spec[modal._functions.
|
|
771
|
+
spawn: __spawn_spec[modal._functions.P, modal._functions.ReturnType]
|
|
772
772
|
|
|
773
773
|
@property
|
|
774
774
|
def _raw_f_(self) -> collections.abc.Callable[..., typing.Any]: ...
|
modal/workspace.pyi
CHANGED
|
@@ -807,8 +807,7 @@ class WorkspaceServiceUserManager:
|
|
|
807
807
|
service_user_name: The name of the Service User.
|
|
808
808
|
environment_name: The name of the Environment to which the Service User's new role will
|
|
809
809
|
apply.
|
|
810
|
-
role: The access level to grant this Service User.
|
|
811
|
-
`"viewer"`, or `"contributor"`.
|
|
810
|
+
role: The access level to grant this Service User.
|
|
812
811
|
|
|
813
812
|
Examples:
|
|
814
813
|
|
|
@@ -832,8 +831,7 @@ class WorkspaceServiceUserManager:
|
|
|
832
831
|
service_user_name: The name of the Service User.
|
|
833
832
|
environment_name: The name of the Environment to which the Service User's new role will
|
|
834
833
|
apply.
|
|
835
|
-
role: The access level to grant this Service User.
|
|
836
|
-
`"viewer"`, or `"contributor"`.
|
|
834
|
+
role: The access level to grant this Service User.
|
|
837
835
|
|
|
838
836
|
Examples:
|
|
839
837
|
|
|
@@ -854,8 +852,6 @@ class WorkspaceServiceUserManager:
|
|
|
854
852
|
def __call__(self, /, name: str) -> modal.types.ServiceUserInfo:
|
|
855
853
|
"""Get information about a specific Service User.
|
|
856
854
|
|
|
857
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
858
|
-
|
|
859
855
|
Args:
|
|
860
856
|
name: The name of the Service User to introspect.
|
|
861
857
|
|
|
@@ -874,8 +870,6 @@ class WorkspaceServiceUserManager:
|
|
|
874
870
|
async def aio(self, /, name: str) -> modal.types.ServiceUserInfo:
|
|
875
871
|
"""Get information about a specific Service User.
|
|
876
872
|
|
|
877
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
878
|
-
|
|
879
873
|
Args:
|
|
880
874
|
name: The name of the Service User to introspect.
|
|
881
875
|
|
|
@@ -897,8 +891,6 @@ class WorkspaceServiceUserManager:
|
|
|
897
891
|
def __call__(self, /) -> list[modal.types.ServiceUserInfo]:
|
|
898
892
|
"""List all Service Users in the current workspace.
|
|
899
893
|
|
|
900
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
901
|
-
|
|
902
894
|
Returns:
|
|
903
895
|
A list of `ServiceUserInfo` dataclasses.
|
|
904
896
|
|
|
@@ -915,8 +907,6 @@ class WorkspaceServiceUserManager:
|
|
|
915
907
|
async def aio(self, /) -> list[modal.types.ServiceUserInfo]:
|
|
916
908
|
"""List all Service Users in the current workspace.
|
|
917
909
|
|
|
918
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
919
|
-
|
|
920
910
|
Returns:
|
|
921
911
|
A list of `ServiceUserInfo` dataclasses.
|
|
922
912
|
|
|
@@ -933,13 +923,12 @@ class WorkspaceServiceUserManager:
|
|
|
933
923
|
list: __list_spec
|
|
934
924
|
|
|
935
925
|
class __delete_spec(typing_extensions.Protocol):
|
|
936
|
-
def __call__(self, /, name: str) -> None:
|
|
926
|
+
def __call__(self, /, name: str, *, allow_missing: bool = False) -> None:
|
|
937
927
|
"""Permanently delete a Service User.
|
|
938
928
|
|
|
939
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
940
|
-
|
|
941
929
|
Args:
|
|
942
930
|
name: The name of the Service User to delete.
|
|
931
|
+
allow_missing: If `True`, do not throw an exception if the Service User does not exist.
|
|
943
932
|
|
|
944
933
|
Examples:
|
|
945
934
|
|
|
@@ -950,13 +939,12 @@ class WorkspaceServiceUserManager:
|
|
|
950
939
|
"""
|
|
951
940
|
...
|
|
952
941
|
|
|
953
|
-
async def aio(self, /, name: str) -> None:
|
|
942
|
+
async def aio(self, /, name: str, *, allow_missing: bool = False) -> None:
|
|
954
943
|
"""Permanently delete a Service User.
|
|
955
944
|
|
|
956
|
-
To call this function, you are required to have Manager permissions in the Workspace.
|
|
957
|
-
|
|
958
945
|
Args:
|
|
959
946
|
name: The name of the Service User to delete.
|
|
947
|
+
allow_missing: If `True`, do not throw an exception if the Service User does not exist.
|
|
960
948
|
|
|
961
949
|
Examples:
|
|
962
950
|
|
|
@@ -32,14 +32,14 @@ modal/_tunnel.py,sha256=4M1qLFXvJLV2s_vr5vHXDtGdZr0OUNmXNj1C3lyF4qE,7360
|
|
|
32
32
|
modal/_tunnel.pyi,sha256=WVP0dkoN__mM73ZQ-r3nHKCXvYlRMmEWW8iw2xrSY6M,14698
|
|
33
33
|
modal/_type_manager.py,sha256=DWjgmjYJuOagw2erin506UUbG2H5UzZCFEekS-7hmfA,9087
|
|
34
34
|
modal/_watcher.py,sha256=Ot1My6fhTp5v0NXR7-ESV8kNQqvpgozrIcvLBtf5r-U,3564
|
|
35
|
-
modal/_workspace.py,sha256=
|
|
35
|
+
modal/_workspace.py,sha256=HgV--qq7SDc4v5fcogoUdypPTA9MXh2w998J5iQ1Zao,27830
|
|
36
36
|
modal/app.py,sha256=tAILXi3rk-d2KgdLKGMsB7awShiPBcPfEfiPLn1fGQY,74703
|
|
37
37
|
modal/app.pyi,sha256=TjcJhoWwZTK2eQ_HCIChU62xSdLi6SvHVQYksy-eZ7w,88565
|
|
38
38
|
modal/billing.py,sha256=E1-P_P8bJFLp-bTaggYd4BaaViImPB5UdtXiY8VeAZQ,390
|
|
39
39
|
modal/billing.pyi,sha256=4m7VLYNS8IiJ2I3CJx0awvrOM6BILWRVLfdeqtAYr8M,7189
|
|
40
40
|
modal/call_graph.py,sha256=XGALIDn1bLchWo_goyX23bcPjzCZcfqKUo8INIfDapo,1775
|
|
41
41
|
modal/client.py,sha256=cstrL0wpXCZmcvRs7Sy3QRw9Vz8zkZy8NNk0tPe38sk,22852
|
|
42
|
-
modal/client.pyi,sha256=
|
|
42
|
+
modal/client.pyi,sha256=FIs-LOX3P2SlgWbBi0iRAAeqRM-7pRxdSXrdFK2jai0,23490
|
|
43
43
|
modal/cloud_bucket_mount.py,sha256=ZOg69dhIFcJcwim4JP-6s1tqwOYAfgS9806jRpspifw,7511
|
|
44
44
|
modal/cloud_bucket_mount.pyi,sha256=HoPe_NG5jYdVfmTNY-ZJ3-C8Eo_FzJovl9orJRjkGbE,10252
|
|
45
45
|
modal/cls.py,sha256=qoGJD590GnHHLvVhkn-BMl4ATMrw2Fg_V4evy5ZQbJQ,41784
|
|
@@ -56,7 +56,7 @@ modal/environments.pyi,sha256=drmg6yVLjpPhGJ4byOrdBEp2KRLburPrKT-FYtgNPoM,23628
|
|
|
56
56
|
modal/exception.py,sha256=L-dUKust6feZzn5qhTVusSAr0IBa4Wa3qIA225IT_lY,12334
|
|
57
57
|
modal/file_pattern_matcher.py,sha256=CpaNNj7oBlOSewX-AAUZCF8KDxH0_DmgwzfpVGbPYJ4,8138
|
|
58
58
|
modal/functions.py,sha256=EZRcHFfnoYbvawUJnAHF5nIvoo9GX2Djy7mUmOCXmXc,258
|
|
59
|
-
modal/functions.pyi,sha256=
|
|
59
|
+
modal/functions.pyi,sha256=SuolW78OayX4pRq8rsIZr7SXUH6Qsf_sPagsB-YCAqE,56516
|
|
60
60
|
modal/image.py,sha256=c0lUSAoU-Ip3g5DGZ0uhdCzoWSQWh1g0i-7Rxo-Sz-I,160
|
|
61
61
|
modal/image.pyi,sha256=R5rdY4P30IEm-F7A2S9CdXa6QYNk-pQwsEun9BKTyLw,61094
|
|
62
62
|
modal/io_streams.py,sha256=iWwhGCcTwhNtIo2t2f-qVV3FBEK1DIi40Tm_Ai01eYA,32992
|
|
@@ -96,7 +96,7 @@ modal/types.py,sha256=LuOQoaDt8T-1i2XOfXzy3Ym1yxHEZ08HTszhGddvCvI,37474
|
|
|
96
96
|
modal/volume.py,sha256=wbTVEriIv3djjeUAuUWkLABgG4ppbNMmXLJeV0bev3Q,71117
|
|
97
97
|
modal/volume.pyi,sha256=Rp2gw_zh8HAAHRbdDkF782tttQfyv3uzSriO-zpMXDU,67350
|
|
98
98
|
modal/workspace.py,sha256=83AGrCXEN1-QhC6XfQusgWO50GOIJX_c1QKsF52wjdA,1157
|
|
99
|
-
modal/workspace.pyi,sha256=
|
|
99
|
+
modal/workspace.pyi,sha256=IfyKAkNmnFi-lMVBDuykPu2UTi7kg7495kmZ4w7yiYY,35009
|
|
100
100
|
modal/_output/__init__.py,sha256=xtGN6SPv2zvByrJ32JPao0yJKpxyCIZ-2JMTScrisuQ,28
|
|
101
101
|
modal/_output/manager.py,sha256=DqEguniI8uWMFUZFX4U4gsCFpio1zQeWjJhJC_GZhi4,18286
|
|
102
102
|
modal/_output/pty.py,sha256=ngkKVpU8zDOxg94LiWYmdwXavd305HQDXzjWGrHQBIM,10408
|
|
@@ -189,7 +189,7 @@ modal/cli/skills.py,sha256=oniDafrmu_ARYTlButnlXbKE9XlEU1-EFz-eO4tdnFY,11954
|
|
|
189
189
|
modal/cli/token.py,sha256=TOuauMzSe7PV1bMnmGQO7oJaAsYY03C1GY6Nww8ac8Y,4905
|
|
190
190
|
modal/cli/utils.py,sha256=1tkDLqtVqYvj4xLoRaOuO_cl8WJxPpHQnIdTg5S2tjA,11670
|
|
191
191
|
modal/cli/volume.py,sha256=nM5QKRjX-d6lQO4FPNDFbIZhuWfZ2O_RW8gtWWwgs4Q,11281
|
|
192
|
-
modal/cli/workspace.py,sha256=
|
|
192
|
+
modal/cli/workspace.py,sha256=JHIX9XyYYyrgq8GgjexSGmqwO6dSyPL5irYhecYRVZI,22323
|
|
193
193
|
modal/experimental/__init__.py,sha256=iHj9XLSZ_J0ciUalr96hJ57GbOsi2v87u5QZ6mPiHtg,13205
|
|
194
194
|
modal/experimental/_flash_container_entry.py,sha256=u0ciXgUain0tosulHFVh_Dp7vi51uHWWiThdQbj0whs,2318
|
|
195
195
|
modal/experimental/flash.py,sha256=ZZJIlJ9lLv03CQr6muRUXQB-5tZII_zX7qiLqRuJWGM,13120
|
|
@@ -207,7 +207,7 @@ modal/sandbox/_task_command_router_client.py,sha256=qhFXy8eyGIxwxRFZkTsxJVxsAc8c
|
|
|
207
207
|
modal/sandbox/sandbox.py,sha256=4ekghND9qUGf5wtIQH4mVa6PHGjHgY0UN5RKryOM05k,683
|
|
208
208
|
modal/sandbox/sandbox.pyi,sha256=HYxopAUYMfMEVMWgzeb4qPATPFzLkTCnqpSl9lI-3ac,150321
|
|
209
209
|
modal/skills/modal/SKILL.md,sha256=Bm-0BPTYppJoeQzA-N-Re8L6-nKnV6gspJXMl4cD0P0,5490
|
|
210
|
-
modal-1.6.2.
|
|
210
|
+
modal-1.6.2.dev10.dist-info/licenses/LICENSE,sha256=psuoW8kuDP96RQsdhzwOqi6fyWv0ct8CR6Jr7He_P_k,10173
|
|
211
211
|
modal_docs/__init__.py,sha256=svYKtV8HDwDCN86zbdWqyq5T8sMdGDj0PVlzc2tIxDM,28
|
|
212
212
|
modal_docs/gen_cli_docs.py,sha256=9-b8vl2EsLWD9zJqpCK8IUi5h44Bjn3i3rSdedZV4bA,6415
|
|
213
213
|
modal_docs/gen_cli_docs_main.py,sha256=UPGr7O1DYTIRLwfINe2rbdh89J3cPVTU1OWL-SjxnOQ,198
|
|
@@ -231,10 +231,10 @@ modal_proto/task_command_router_pb2.py,sha256=ysC0d8p3YxwyOpP3B-qDlvE81Yo4ntQiwi
|
|
|
231
231
|
modal_proto/task_command_router_pb2.pyi,sha256=FDcd5t3ZCmQHUjefOzg07iglrgKYcBrzFvdKIWsEMHI,50628
|
|
232
232
|
modal_proto/task_command_router_pb2_grpc.py,sha256=8iGUGwEVbqOsZWPJ7OsQ9OKUGaSCf9Dr2tvb04Ww6d4,44620
|
|
233
233
|
modal_proto/task_command_router_pb2_grpc.pyi,sha256=26Fq9RQo09B9KLHsgdpRuw-I6Z4-2jrXLQkuDzB-moI,15097
|
|
234
|
-
modal_version/__init__.py,sha256=
|
|
234
|
+
modal_version/__init__.py,sha256=2lqb4ysefgTqscFMp9LWCNfAU0rKY3oVgW2wnWTy-pw,121
|
|
235
235
|
modal_version/__main__.py,sha256=nLUbubxKx7GXNzJIkTB7M7_u8080LyLM0IS0rzFkC_c,119
|
|
236
|
-
modal-1.6.2.
|
|
237
|
-
modal-1.6.2.
|
|
238
|
-
modal-1.6.2.
|
|
239
|
-
modal-1.6.2.
|
|
240
|
-
modal-1.6.2.
|
|
236
|
+
modal-1.6.2.dev10.dist-info/METADATA,sha256=9gYqJTJseHyFzcr4xOZ3H3E-6BriZbJuQKCaBfxANZ8,2513
|
|
237
|
+
modal-1.6.2.dev10.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
238
|
+
modal-1.6.2.dev10.dist-info/entry_points.txt,sha256=An-wYgeEUnm6xzrAP9_NTSTSciYvvEWsMZILtYrvpAI,46
|
|
239
|
+
modal-1.6.2.dev10.dist-info/top_level.txt,sha256=4BWzoKYREKUZ5iyPzZpjqx4G8uB5TWxXPDwibLcVa7k,43
|
|
240
|
+
modal-1.6.2.dev10.dist-info/RECORD,,
|
modal_version/__init__.py
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|