femtocrux 2.4.1__py3-none-any.whl → 2.5.1__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 CHANGED
@@ -1 +1 @@
1
- 2.4.1
1
+ 2.5.1
@@ -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
- __docker_registry__ = "ghcr.io"
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
- def _get_docker_image_name() -> str:
34
- """
35
- Returns the docker image name. For testing, override with the
36
- FEMTOCRUX_SERVER_IMAGE_NAME environment variable.
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
- from femtocrux.version import __version__
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
- __docker_image_name__ = _get_docker_image_name()
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
- Alternatively, you can pull the image yourself with the command:
508
- docker pull %s
509
- """,
510
- __docker_image_name__,
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#compiler-download
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 = getpass("Please enter your Femtosense-provided key:")
535
+ password = read_secret_raw("Please enter your femtoAI-provided key:")
524
536
 
525
537
  # Log in to the client
526
538
  try:
527
- resp = client.login(
528
- "femtodaemon", password, registry="https://" + __docker_registry__
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(resp.get("Status", ""))
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 Femtosense representative."""
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
- try:
555
- client.images.pull(__docker_image_name__)
556
- except docker.errors.ImageNotFound as exc:
557
- raise image_not_found_error() from exc
558
- except docker.errors.APIError as exc:
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 the remote. Check if it is "
562
- "published.",
563
- __docker_image_name__,
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
- raise image_not_found_error() from exc
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
- if __docker_image_name__ not in existing_image_names:
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 image %s locally." % __docker_image_name__
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(docker_kwargs: dict[str, Any] = None) -> CompilerClient:
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: femtocrux
3
- Version: 2.4.1
3
+ Version: 2.5.1
4
4
  Summary: Femtosense Compiler
5
5
  Home-page: https://github.com/femtosense/femtocrux
6
6
  Author: Femtosense
@@ -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=bYUOOsQtDdBr_czpFRwea0D0z1Jyd49j7s7Oonx48LY,6
3
+ femtocrux/VERSION,sha256=mWAPanv-bDPr0aJRj0SoYaZ6_EDCXaQrxiJZVxZSlYQ,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=jIkfdIwfTxveC4G-WtQVzumK0m_icdznHFE175P48y0,21601
7
+ femtocrux/client/client.py,sha256=eJH-CM_Pob1V-M9EuwhFyyWd1NsdLBIUiF99Psz46-8,26428
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=HlmlqS-R7d7PaYaYi8sY6U0p9NoeHjQ1ObapBMKhCrU,3356
18
- femtocrux-2.4.1.dist-info/licenses/LICENSE,sha256=eN9ZI1xHjUmFvN3TEeop5kBGXRUBfbsl55KBNBYYFqI,36
19
- femtocrux-2.4.1.dist-info/METADATA,sha256=Ai9FkAA9F-z1pH90dxJm_6EaPsmYfiUQIIjBUFejlWw,2763
20
- femtocrux-2.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
- femtocrux-2.4.1.dist-info/top_level.txt,sha256=BkTttlioC3je__8577wxRieZqY3Abu7FOOdMnmYbcNI,10
22
- femtocrux-2.4.1.dist-info/RECORD,,
17
+ femtocrux/util/utils.py,sha256=v8v09aDaOtfejpSIB-LgaA--JJj9o2_BWvNwjd8wfww,4134
18
+ femtocrux-2.5.1.dist-info/licenses/LICENSE,sha256=eN9ZI1xHjUmFvN3TEeop5kBGXRUBfbsl55KBNBYYFqI,36
19
+ femtocrux-2.5.1.dist-info/METADATA,sha256=8hmP0A4KIm8CLNlJA0X4GuW2MjuthJYg2NOpEg2ruZA,2763
20
+ femtocrux-2.5.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
21
+ femtocrux-2.5.1.dist-info/top_level.txt,sha256=BkTttlioC3je__8577wxRieZqY3Abu7FOOdMnmYbcNI,10
22
+ femtocrux-2.5.1.dist-info/RECORD,,