femtocrux 2.4.1__py3-none-any.whl → 2.5.0__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.
- femtocrux/VERSION +1 -1
- femtocrux/client/client.py +150 -45
- femtocrux/util/utils.py +25 -0
- {femtocrux-2.4.1.dist-info → femtocrux-2.5.0.dist-info}/METADATA +1 -1
- {femtocrux-2.4.1.dist-info → femtocrux-2.5.0.dist-info}/RECORD +8 -8
- {femtocrux-2.4.1.dist-info → femtocrux-2.5.0.dist-info}/WHEEL +0 -0
- {femtocrux-2.4.1.dist-info → femtocrux-2.5.0.dist-info}/licenses/LICENSE +0 -0
- {femtocrux-2.4.1.dist-info → femtocrux-2.5.0.dist-info}/top_level.txt +0 -0
femtocrux/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.5.0
|
femtocrux/client/client.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from collections.abc import Iterable
|
|
2
2
|
from dataclasses import dataclass
|
|
3
3
|
import docker
|
|
4
|
-
from getpass import getpass
|
|
5
4
|
import google.protobuf
|
|
6
5
|
import grpc
|
|
7
6
|
import logging
|
|
@@ -11,12 +10,14 @@ import pickle
|
|
|
11
10
|
import queue
|
|
12
11
|
import sys
|
|
13
12
|
import time
|
|
14
|
-
from typing import Any, List, Tuple
|
|
13
|
+
from typing import Any, List, Tuple, Iterator
|
|
15
14
|
from contextlib import contextmanager
|
|
15
|
+
import subprocess
|
|
16
16
|
|
|
17
17
|
from fmot.fqir import GraphProto
|
|
18
18
|
|
|
19
19
|
from femtocrux.util.utils import (
|
|
20
|
+
read_secret_raw,
|
|
20
21
|
get_channel_options,
|
|
21
22
|
serialize_sim_inputs_message,
|
|
22
23
|
deserialize_simulation_output,
|
|
@@ -27,31 +28,38 @@ import femtocrux.grpc.compiler_service_pb2 as cs_pb2
|
|
|
27
28
|
import femtocrux.grpc.compiler_service_pb2_grpc as cs_pb2_grpc
|
|
28
29
|
|
|
29
30
|
# Docker info
|
|
30
|
-
|
|
31
|
+
__ghcr_docker_registry__ = "ghcr.io"
|
|
32
|
+
__ecr_docker_registry__ = "201615177705.dkr.ecr.us-west-2.amazonaws.com"
|
|
31
33
|
|
|
34
|
+
# Docker image names for GHCR and ECR
|
|
35
|
+
__ghcr_image_name__ = None
|
|
36
|
+
__ecr_image_name__ = None
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
|
|
39
|
+
def _set_docker_image_names():
|
|
40
|
+
"""Sets the docker image names based on the current version."""
|
|
41
|
+
from femtocrux.version import __version__
|
|
42
|
+
|
|
43
|
+
global __ghcr_image_name__, __ecr_image_name__
|
|
44
|
+
IMAGE = "femtocrux"
|
|
45
|
+
ORG = "femtosense"
|
|
46
|
+
__ghcr_image_name__ = f"{__ghcr_docker_registry__}/{ORG}/{IMAGE}:{__version__}"
|
|
47
|
+
__ecr_image_name__ = f"{__ecr_docker_registry__}/{IMAGE}:{__version__}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_set_docker_image_names()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_docker_image_names() -> List[str]:
|
|
54
|
+
"""Returns a list of possible docker image names for this client version."""
|
|
38
55
|
try:
|
|
39
|
-
return os.environ["FEMTOCRUX_SERVER_IMAGE_NAME"]
|
|
56
|
+
return [os.environ["FEMTOCRUX_SERVER_IMAGE_NAME"]]
|
|
40
57
|
except KeyError:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
ORG = "femtosense"
|
|
44
|
-
IMAGE = "femtocrux"
|
|
45
|
-
remote_image_name = "%s/%s/%s:%s" % (
|
|
46
|
-
__docker_registry__,
|
|
47
|
-
ORG,
|
|
48
|
-
IMAGE,
|
|
49
|
-
__version__,
|
|
50
|
-
)
|
|
51
|
-
return remote_image_name
|
|
58
|
+
return [__ecr_image_name__, __ghcr_image_name__]
|
|
52
59
|
|
|
53
60
|
|
|
54
|
-
|
|
61
|
+
__docker_image_names__ = _get_docker_image_names()
|
|
62
|
+
__docker_image_name__ = None # Will be set when image is found
|
|
55
63
|
|
|
56
64
|
|
|
57
65
|
# Set up logging
|
|
@@ -63,6 +71,7 @@ def _init_logger():
|
|
|
63
71
|
handler = logging.StreamHandler()
|
|
64
72
|
handler.setFormatter(formatter)
|
|
65
73
|
logger.addHandler(handler)
|
|
74
|
+
logger.propagate = False
|
|
66
75
|
return logger
|
|
67
76
|
|
|
68
77
|
|
|
@@ -499,15 +508,18 @@ class CompilerClient(CompilerClientImpl):
|
|
|
499
508
|
|
|
500
509
|
def _pull_docker_image(self):
|
|
501
510
|
"""Pull the Docker image from remote."""
|
|
511
|
+
global __docker_image_name__
|
|
502
512
|
|
|
503
513
|
logger.info(
|
|
504
514
|
"""
|
|
505
515
|
Attempting to pull docker image from remote.
|
|
506
516
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
517
|
+
If you were provided a femtoAI key beginning with "ghp" within the
|
|
518
|
+
past year, you can use it below.
|
|
519
|
+
|
|
520
|
+
Alternatively, you can generate a new key on the Developer Portal at:
|
|
521
|
+
https://developer.femto.ai/software-tools/sdk
|
|
522
|
+
"""
|
|
511
523
|
)
|
|
512
524
|
|
|
513
525
|
# Log in to Github
|
|
@@ -520,24 +532,62 @@ class CompilerClient(CompilerClientImpl):
|
|
|
520
532
|
manual_pass = False
|
|
521
533
|
else:
|
|
522
534
|
# Prompt the user for password entry
|
|
523
|
-
password =
|
|
535
|
+
password = read_secret_raw("Please enter your femtoAI-provided key:")
|
|
524
536
|
|
|
525
537
|
# Log in to the client
|
|
526
538
|
try:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
539
|
+
resp_status = ""
|
|
540
|
+
if password.startswith("ghp"):
|
|
541
|
+
logger.info("Logging in to GitHub Container Registry...")
|
|
542
|
+
resp = client.login(
|
|
543
|
+
"femtodaemon",
|
|
544
|
+
password,
|
|
545
|
+
registry="https://" + __ghcr_docker_registry__,
|
|
546
|
+
)
|
|
547
|
+
__docker_image_name__ = __ghcr_image_name__
|
|
548
|
+
resp_status = resp.get("Status", "")
|
|
549
|
+
else:
|
|
550
|
+
logger.info("Logging in to AWS ECR...")
|
|
551
|
+
proc = subprocess.run(
|
|
552
|
+
[
|
|
553
|
+
"docker",
|
|
554
|
+
"login",
|
|
555
|
+
"--username",
|
|
556
|
+
"AWS",
|
|
557
|
+
"--password-stdin",
|
|
558
|
+
__ecr_docker_registry__,
|
|
559
|
+
],
|
|
560
|
+
input=password,
|
|
561
|
+
check=True,
|
|
562
|
+
capture_output=True,
|
|
563
|
+
text=True,
|
|
564
|
+
)
|
|
565
|
+
__docker_image_name__ = __ecr_image_name__
|
|
566
|
+
resp_status = (
|
|
567
|
+
proc.stdout or proc.stderr or "Login Succeeded"
|
|
568
|
+
).strip()
|
|
530
569
|
except docker.errors.APIError as exc:
|
|
531
570
|
if "denied" in exc.explanation:
|
|
532
571
|
logger.error("Docker authetication failed.")
|
|
533
572
|
# Retry password entry
|
|
534
573
|
if manual_pass:
|
|
535
574
|
continue
|
|
536
|
-
|
|
575
|
+
raise RuntimeError("Docker authentication failed") from exc
|
|
576
|
+
except subprocess.CalledProcessError as exc:
|
|
577
|
+
output = (exc.stderr or exc.stdout or "").lower()
|
|
578
|
+
if (
|
|
579
|
+
"denied" in output
|
|
580
|
+
or "unauthorized" in output
|
|
581
|
+
or "bad request" in output
|
|
582
|
+
):
|
|
583
|
+
logger.error("Docker authentication failed.")
|
|
584
|
+
# Retry password entry
|
|
585
|
+
if manual_pass:
|
|
586
|
+
continue
|
|
537
587
|
raise RuntimeError("Docker authentication failed") from exc
|
|
538
588
|
|
|
539
589
|
# Login successful
|
|
540
|
-
logger.info(
|
|
590
|
+
logger.info(resp_status)
|
|
541
591
|
break
|
|
542
592
|
|
|
543
593
|
def image_not_found_error() -> RuntimeError:
|
|
@@ -545,24 +595,71 @@ class CompilerClient(CompilerClientImpl):
|
|
|
545
595
|
return RuntimeError(
|
|
546
596
|
"""Docker image not found:
|
|
547
597
|
%s
|
|
548
|
-
Please notify your
|
|
598
|
+
Please notify your femtoAI representative."""
|
|
549
599
|
% (__docker_image_name__)
|
|
550
600
|
)
|
|
551
601
|
|
|
552
602
|
# Download the image
|
|
553
603
|
logger.info("Downloading image. This could take a few minutes...")
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
if exc.explanation == "manifest unknown":
|
|
604
|
+
if __docker_image_name__ == __ghcr_image_name__:
|
|
605
|
+
# For GHCR, use Docker SDK pull (we already authenticated via SDK)
|
|
606
|
+
try:
|
|
607
|
+
client.images.pull(__docker_image_name__)
|
|
608
|
+
except docker.errors.ImageNotFound as exc:
|
|
560
609
|
logger.error(
|
|
561
|
-
"Docker image %s not found on
|
|
562
|
-
|
|
563
|
-
|
|
610
|
+
"Docker image %s not found on remote.", __docker_image_name__
|
|
611
|
+
)
|
|
612
|
+
raise image_not_found_error() from exc
|
|
613
|
+
except docker.errors.APIError as exc:
|
|
614
|
+
exp = (getattr(exc, "explanation", "") or str(exc)).lower()
|
|
615
|
+
if "manifest unknown" in exp or "not found" in exp:
|
|
616
|
+
logger.error(
|
|
617
|
+
"Docker image %s not found on remote.",
|
|
618
|
+
__docker_image_name__,
|
|
619
|
+
)
|
|
620
|
+
raise image_not_found_error() from exc
|
|
621
|
+
if (
|
|
622
|
+
"denied" in exp
|
|
623
|
+
or "unauthorized" in exp
|
|
624
|
+
or "no basic auth credentials" in exp
|
|
625
|
+
or "forbidden" in exp
|
|
626
|
+
):
|
|
627
|
+
logger.error(
|
|
628
|
+
"Pull access denied for %s. Details: %s",
|
|
629
|
+
__docker_image_name__,
|
|
630
|
+
exp,
|
|
631
|
+
)
|
|
632
|
+
raise RuntimeError("Docker authentication failed") from exc
|
|
633
|
+
raise RuntimeError(f"Docker pull failed: {exc}") from exc
|
|
634
|
+
else:
|
|
635
|
+
# For ECR, use Docker CLI to reuse CLI auth and avoid SDK auth issues
|
|
636
|
+
try:
|
|
637
|
+
subprocess.run(
|
|
638
|
+
["docker", "pull", __docker_image_name__],
|
|
639
|
+
check=True,
|
|
564
640
|
)
|
|
565
|
-
|
|
641
|
+
except subprocess.CalledProcessError as exc:
|
|
642
|
+
output_text = (exc.stderr or exc.stdout or "").strip()
|
|
643
|
+
out_lower = output_text.lower()
|
|
644
|
+
if "manifest unknown" in out_lower or "not found" in out_lower:
|
|
645
|
+
logger.error(
|
|
646
|
+
"Docker image %s not found on remote.",
|
|
647
|
+
__docker_image_name__,
|
|
648
|
+
)
|
|
649
|
+
raise image_not_found_error() from exc
|
|
650
|
+
if (
|
|
651
|
+
"denied" in out_lower
|
|
652
|
+
or "unauthorized" in out_lower
|
|
653
|
+
or "no basic auth credentials" in out_lower
|
|
654
|
+
or "forbidden" in out_lower
|
|
655
|
+
):
|
|
656
|
+
logger.error(
|
|
657
|
+
"Pull access denied for %s. Details: %s",
|
|
658
|
+
__docker_image_name__,
|
|
659
|
+
output_text,
|
|
660
|
+
)
|
|
661
|
+
raise RuntimeError("Docker authentication failed") from exc
|
|
662
|
+
raise RuntimeError(f"Docker pull failed: {output_text}") from exc
|
|
566
663
|
|
|
567
664
|
logger.info("Download completed.")
|
|
568
665
|
|
|
@@ -572,6 +669,7 @@ class CompilerClient(CompilerClientImpl):
|
|
|
572
669
|
"""
|
|
573
670
|
Starts the server in a new Docker container.
|
|
574
671
|
"""
|
|
672
|
+
global __docker_image_name__
|
|
575
673
|
if docker_kwargs is None:
|
|
576
674
|
docker_kwargs = {}
|
|
577
675
|
|
|
@@ -582,11 +680,16 @@ class CompilerClient(CompilerClientImpl):
|
|
|
582
680
|
existing_image_names = [
|
|
583
681
|
tag for image in client.images.list() for tag in image.tags
|
|
584
682
|
]
|
|
585
|
-
|
|
683
|
+
for image_name in __docker_image_names__:
|
|
684
|
+
if image_name in existing_image_names:
|
|
685
|
+
__docker_image_name__ = image_name
|
|
686
|
+
break
|
|
687
|
+
if __docker_image_name__ is None:
|
|
586
688
|
# Check if we are allowed to pull the image.
|
|
587
689
|
# This is disabled for CI builds.
|
|
588
690
|
image_not_found_msg = (
|
|
589
|
-
"Failed to find the docker
|
|
691
|
+
"Failed to find the docker images %s locally."
|
|
692
|
+
% " or ".join(__docker_image_names__)
|
|
590
693
|
)
|
|
591
694
|
if not _env_var_to_bool("FS_ALLOW_DOCKER_PULL", default=True):
|
|
592
695
|
raise RuntimeError(
|
|
@@ -676,7 +779,9 @@ class CompilerClient(CompilerClientImpl):
|
|
|
676
779
|
|
|
677
780
|
|
|
678
781
|
@contextmanager
|
|
679
|
-
def ManagedCompilerClient(
|
|
782
|
+
def ManagedCompilerClient(
|
|
783
|
+
docker_kwargs: dict[str, Any] = None
|
|
784
|
+
) -> Iterator[CompilerClient]:
|
|
680
785
|
client = CompilerClient(docker_kwargs=docker_kwargs)
|
|
681
786
|
try:
|
|
682
787
|
yield client
|
femtocrux/util/utils.py
CHANGED
|
@@ -3,6 +3,8 @@ import json
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import torch
|
|
5
5
|
from typing import Any
|
|
6
|
+
import sys
|
|
7
|
+
import termios
|
|
6
8
|
|
|
7
9
|
import femtocrux.grpc.compiler_service_pb2 as cs_pb2
|
|
8
10
|
|
|
@@ -89,3 +91,26 @@ def deserialize_simulation_data(proto: cs_pb2.simulation_data) -> dict:
|
|
|
89
91
|
def deserialize_simulation_output(proto: cs_pb2.simulation_output) -> dict:
|
|
90
92
|
"""Deserializes a LargerMessage back into a dictionary of NumPy arrays."""
|
|
91
93
|
return {key: deserialize_numpy_array(value) for key, value in proto.items()}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def read_secret_raw(prompt="Secret: "):
|
|
97
|
+
"""Read a secret from stdin without echoing or overflowing buffer"""
|
|
98
|
+
fd = sys.stdin.fileno()
|
|
99
|
+
sys.stdout.write(prompt)
|
|
100
|
+
sys.stdout.flush()
|
|
101
|
+
old = termios.tcgetattr(fd)
|
|
102
|
+
try:
|
|
103
|
+
new = termios.tcgetattr(fd)
|
|
104
|
+
new[3] &= ~(termios.ECHO | termios.ICANON) # no echo, raw-ish
|
|
105
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, new)
|
|
106
|
+
chunks = []
|
|
107
|
+
while True:
|
|
108
|
+
ch = sys.stdin.buffer.read(1)
|
|
109
|
+
if ch in (b"\n", b"\r"):
|
|
110
|
+
break
|
|
111
|
+
chunks.append(ch)
|
|
112
|
+
sys.stdout.write("\n")
|
|
113
|
+
sys.stdout.flush()
|
|
114
|
+
return b"".join(chunks).decode("utf-8", "replace")
|
|
115
|
+
finally:
|
|
116
|
+
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
femtocrux/ENV_REQUIREMENTS.sh,sha256=t_O1B4hJAMgxvH9gwp1qls6eVFmhSYBJe64KmuK_H-4,1389
|
|
2
2
|
femtocrux/PY_REQUIREMENTS,sha256=UwXV0o3gieruZUYdN9r2bqQ0Wcf_vosjeP6LJVx6oF0,275
|
|
3
|
-
femtocrux/VERSION,sha256=
|
|
3
|
+
femtocrux/VERSION,sha256=cy9k2HT5B4jAGZsMGb_Zs7ZDbhQBFOU-RigyUy10xhw,6
|
|
4
4
|
femtocrux/__init__.py,sha256=yIWd9I2PEXCn_PKIILAN3mkWeTf0tgtVualeTIHNxfQ,342
|
|
5
5
|
femtocrux/version.py,sha256=uNg2kHxQo6oUN1ah7s9_85rCZVRoTHGPD1GAQPZW4lw,164
|
|
6
6
|
femtocrux/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
-
femtocrux/client/client.py,sha256=
|
|
7
|
+
femtocrux/client/client.py,sha256=op9BX2PKajbqLOKF-uGwqevvn3eWyP4SaPAXVouQUtA,26410
|
|
8
8
|
femtocrux/grpc/__init__.py,sha256=uiMHQt5I2eAKJqI3Zh0h1Gm7cmPR4PbaGS71nCJQCGw,169
|
|
9
9
|
femtocrux/grpc/compiler_service_pb2.py,sha256=grQNjlnoUg8KKcykK7JcT7Pmv_7R7sjLpsoG_EHGyoY,5494
|
|
10
10
|
femtocrux/grpc/compiler_service_pb2_grpc.py,sha256=Hl1bGJEYA7CvdIj38FUvlEolPGCoWyK8WazqOhx4v8s,8500
|
|
@@ -14,9 +14,9 @@ femtocrux/server/exceptions.py,sha256=lI6n471n5QKf5G3aL_1kuBVEItD-jBgithVVpPDwNY
|
|
|
14
14
|
femtocrux/server/healthcheck.py,sha256=ehqAwnv0D0zpy-AUZAPwv8rp874DZCwUmP8nzdXzZvI,1565
|
|
15
15
|
femtocrux/server/server.py,sha256=sumnTj63uOGkzwj4-xwTuN4wVB7CDopy8e7PLOpW64Q,7405
|
|
16
16
|
femtocrux/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
femtocrux/util/utils.py,sha256=
|
|
18
|
-
femtocrux-2.
|
|
19
|
-
femtocrux-2.
|
|
20
|
-
femtocrux-2.
|
|
21
|
-
femtocrux-2.
|
|
22
|
-
femtocrux-2.
|
|
17
|
+
femtocrux/util/utils.py,sha256=v8v09aDaOtfejpSIB-LgaA--JJj9o2_BWvNwjd8wfww,4134
|
|
18
|
+
femtocrux-2.5.0.dist-info/licenses/LICENSE,sha256=eN9ZI1xHjUmFvN3TEeop5kBGXRUBfbsl55KBNBYYFqI,36
|
|
19
|
+
femtocrux-2.5.0.dist-info/METADATA,sha256=kg2G-ADUnaqAcUOqT7K6rAt67Qv03pBwIuda-yqSbkU,2763
|
|
20
|
+
femtocrux-2.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
femtocrux-2.5.0.dist-info/top_level.txt,sha256=BkTttlioC3je__8577wxRieZqY3Abu7FOOdMnmYbcNI,10
|
|
22
|
+
femtocrux-2.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|