11from __future__ import annotations
22
33import contextlib
4+ import copy
45import datetime
56import io
67import json
@@ -547,12 +548,14 @@ def _canonicalize_from_key(self, key: str | Task) -> Task:
547548 return self ._canonicalize (t )
548549
549550 def get_default (self , tag : str = "pkg" ) -> tuple [Task , DockerContext ]:
550- if tag not in self .VALID_TAGS :
551- raise ValueError (f"Unknown tag '{ tag } '. Valid tags: { sorted (self .VALID_TAGS )} " )
552- # lookup under canonical default; return Task with requested tag
553- user_task = Task (owner = "default" , repo = "default" , sha = None , tag = tag )
554- canonical = self ._canonicalize (user_task )
555- return user_task , self .registry [canonical ]
551+ with self ._lock :
552+ if tag not in self .VALID_TAGS :
553+ raise ValueError (f"Unknown tag '{ tag } '. Valid tags: { sorted (self .VALID_TAGS )} " )
554+ # lookup under canonical default; return Task with requested tag
555+ user_task = Task (owner = "default" , repo = "default" , sha = None , tag = tag )
556+ canonical = self ._canonicalize (user_task )
557+ ctx = copy .deepcopy (self .registry [canonical ])
558+ return user_task , ctx
556559
557560 def get_lock (self ) -> threading .Lock :
558561 return self ._lock
@@ -622,23 +625,24 @@ def get(self, key: str | Task) -> DockerContext:
622625 2) owner/repo (canonical key, tag='pkg')
623626 3) default (canonical key, tag='pkg')
624627 """
625- # Keep the user's tag but look up under canonical keys
626- user_task = self .parse_key (key ) if isinstance (key , str ) else key
627- canonical = self ._canonicalize (user_task )
628+ with self ._lock :
629+ # Keep the user's tag but look up under canonical keys
630+ user_task = self .parse_key (key ) if isinstance (key , str ) else key
631+ canonical = self ._canonicalize (user_task )
628632
629- # exact match first (canonical)
630- if canonical .sha is not None and canonical in self .registry :
631- logger .debug (f"Found exact context for key '{ user_task } ' via '{ canonical } '." )
632- return self .registry [canonical ]
633+ # exact match first (canonical)
634+ if canonical .sha is not None and canonical in self .registry :
635+ logger .debug (f"Found exact context for key '{ user_task } ' via '{ canonical } '." )
636+ return self .registry [canonical ]
633637
634- # owner/repo base (canonical)
635- base = Task (owner = canonical .owner , repo = canonical .repo , sha = None , tag = "pkg" )
636- if base in self .registry :
637- logger .debug (f"Found fallback context '{ base } ' for key '{ user_task } '." )
638- return self .registry [base ]
638+ # owner/repo base (canonical)
639+ base = Task (owner = canonical .owner , repo = canonical .repo , sha = None , tag = "pkg" )
640+ if base in self .registry :
641+ logger .debug (f"Found fallback context '{ base } ' for key '{ user_task } '." )
642+ return self .registry [base ]
639643
640- logger .info (f"No context found for key '{ user_task } '. Using default context." )
641- return self .registry [Task (owner = "default" , repo = "default" , sha = None , tag = "pkg" )]
644+ logger .info (f"No context found for key '{ user_task } '. Using default context." )
645+ return self .registry [Task (owner = "default" , repo = "default" , sha = None , tag = "pkg" )]
642646
643647 def get_similar (self , key : str | Task ) -> list [tuple [Task , DockerContext ]]: # noqa: C901
644648 """
@@ -649,56 +653,57 @@ def get_similar(self, key: str | Task) -> list[tuple[Task, DockerContext]]: # n
649653 sorted by |commit_date diff| if available, else by SHA
650654 3) base owner/repo — returned Tasks use the caller's tag
651655 """
652- user_task = self .parse_key (key ) if isinstance (key , str ) else key
653- canonical = self ._canonicalize (user_task )
656+ with self ._lock :
657+ user_task = self .parse_key (key ) if isinstance (key , str ) else key
658+ canonical = self ._canonicalize (user_task )
659+
660+ results : list [tuple [Task , DockerContext ]] = []
661+ seen_canonical : set [Task ] = set ()
662+
663+ # 1) Exact match (if present)
664+ if canonical in self .registry :
665+ results .append ((canonical .with_tag (user_task .tag ), self .registry [canonical ]))
666+ seen_canonical .add (canonical )
667+
668+ # 2) Other SHAs for same owner/repo (canonical keys in registry)
669+ candidates : list [tuple [Task , DockerContext ]] = []
670+ for t , ctx in self .registry .items ():
671+ if t in seen_canonical :
672+ continue
673+ if t .owner == canonical .owner and t .repo == canonical .repo and t .sha is not None :
674+ candidates .append ((t , ctx ))
675+
676+ has_valid_commit_date = (
677+ getattr (canonical , "sha" , None ) is not None and getattr (canonical , "commit_date" , None ) is not None
678+ )
679+ if has_valid_commit_date :
680+
681+ def _sort (item : tuple [Task , DockerContext ]) -> tuple [float , str ]:
682+ t , _ = item
683+ cand_cd = getattr (t , "commit_date" , None )
684+ if cand_cd is None :
685+ return (float ("inf" ), str (t .sha ))
686+ try :
687+ return (abs (canonical .commit_date - cand_cd ), str (t .sha ))
688+ except Exception :
689+ return (float ("inf" ), str (t .sha ))
690+
691+ candidates .sort (key = _sort )
692+ else :
693+ candidates .sort (key = lambda item : str (item [0 ].sha ))
654694
655- results : list [tuple [Task , DockerContext ]] = []
656- seen_canonical : set [Task ] = set ()
695+ for t , ctx in candidates :
696+ if t not in seen_canonical :
697+ # Present with the user's tag for downstream execution behavior
698+ results .append ((t .with_tag (user_task .tag ), ctx ))
699+ seen_canonical .add (t )
657700
658- # 1) Exact match (if present)
659- if canonical in self .registry :
660- results .append ((canonical .with_tag (user_task .tag ), self .registry [canonical ]))
661- seen_canonical .add (canonical )
662-
663- # 2) Other SHAs for same owner/repo (canonical keys in registry)
664- candidates : list [tuple [Task , DockerContext ]] = []
665- for t , ctx in self .registry .items ():
666- if t in seen_canonical :
667- continue
668- if t .owner == canonical .owner and t .repo == canonical .repo and t .sha is not None :
669- candidates .append ((t , ctx ))
670-
671- has_valid_commit_date = (
672- getattr (canonical , "sha" , None ) is not None and getattr (canonical , "commit_date" , None ) is not None
673- )
674- if has_valid_commit_date :
701+ # 3) Base owner/repo
702+ base = Task (owner = canonical .owner , repo = canonical .repo , sha = None , tag = "pkg" )
703+ if base in self .registry and base not in seen_canonical :
704+ results .append ((base .with_tag (user_task .tag ), self .registry [base ]))
675705
676- def _sort (item : tuple [Task , DockerContext ]) -> tuple [float , str ]:
677- t , _ = item
678- cand_cd = getattr (t , "commit_date" , None )
679- if cand_cd is None :
680- return (float ("inf" ), str (t .sha ))
681- try :
682- return (abs (canonical .commit_date - cand_cd ), str (t .sha ))
683- except Exception :
684- return (float ("inf" ), str (t .sha ))
685-
686- candidates .sort (key = _sort )
687- else :
688- candidates .sort (key = lambda item : str (item [0 ].sha ))
689-
690- for t , ctx in candidates :
691- if t not in seen_canonical :
692- # Present with the user's tag for downstream execution behavior
693- results .append ((t .with_tag (user_task .tag ), ctx ))
694- seen_canonical .add (t )
695-
696- # 3) Base owner/repo
697- base = Task (owner = canonical .owner , repo = canonical .repo , sha = None , tag = "pkg" )
698- if base in self .registry and base not in seen_canonical :
699- results .append ((base .with_tag (user_task .tag ), self .registry [base ]))
700-
701- return results
706+ return results
702707
703708 def __getitem__ (self , key : str ) -> DockerContext :
704709 return self .get (key )
0 commit comments