11import collections
22import shutil
33from pathlib import PurePath
4+ from typing import Union
45
56from pyorderly .outpack .config import Location , update_config
67from pyorderly .outpack .location_driver import LocationDriver
78from pyorderly .outpack .location_http import OutpackLocationHTTP
89from pyorderly .outpack .location_packit import outpack_location_packit
910from pyorderly .outpack .location_path import OutpackLocationPath
1011from pyorderly .outpack .location_ssh import OutpackLocationSSH , parse_ssh_url
12+ from pyorderly .outpack .metadata import MetadataCore
1113from pyorderly .outpack .root import OutpackRoot , root_open
1214from pyorderly .outpack .static import (
1315 LOCATION_LOCAL ,
1416 LOCATION_ORPHAN ,
1517 LOCATION_RESERVED_NAME ,
1618)
1719
20+ LocationSelector = Union [None , str , list [str ]]
21+
1822
1923def outpack_location_list (root = None , * , locate = True ):
2024 root = root_open (root , locate = locate )
@@ -94,15 +98,20 @@ def outpack_location_rename(old, new, root=None, *, locate=True):
9498
9599
96100def location_resolve_valid (
97- location , root , * , include_local , include_orphan , allow_no_locations
98- ):
101+ location : LocationSelector ,
102+ root : OutpackRoot ,
103+ * ,
104+ include_local : bool ,
105+ include_orphan : bool ,
106+ allow_no_locations : bool ,
107+ ) -> list [str ]:
99108 if location is None :
100- location = outpack_location_list (root )
109+ result = outpack_location_list (root )
101110 elif isinstance (location , str ):
102111 if location not in outpack_location_list (root ):
103112 msg = f"Unknown location: '{ location } '"
104113 raise Exception (msg )
105- location = [location ]
114+ result = [location ]
106115 elif isinstance (location , collections .abc .Iterable ) and all (
107116 isinstance (item , str ) for item in location
108117 ):
@@ -111,24 +120,24 @@ def location_resolve_valid(
111120 unknown_text = "', '" .join (unknown )
112121 msg = f"Unknown location: '{ unknown_text } '"
113122 raise Exception (msg )
114- location = list (location )
123+ result = list (location )
115124 else :
116125 msg = (
117126 "Invalid input for 'location'; expected None or a list of "
118127 "strings"
119128 )
120129 raise Exception (msg )
121130
122- if not include_local and LOCATION_LOCAL in location :
123- location .remove (LOCATION_LOCAL )
124- if not include_orphan and LOCATION_ORPHAN in location : # pragma: no cover
125- location .remove (LOCATION_ORPHAN )
131+ if not include_local and LOCATION_LOCAL in result :
132+ result .remove (LOCATION_LOCAL )
133+ if not include_orphan and LOCATION_ORPHAN in result : # pragma: no cover
134+ result .remove (LOCATION_ORPHAN )
126135
127- if len (location ) == 0 and not allow_no_locations :
136+ if len (result ) == 0 and not allow_no_locations :
128137 msg = "No suitable location found"
129138 raise Exception (msg )
130139
131- return location
140+ return result
132141
133142
134143def _location_check_new_name (root , name ):
@@ -169,3 +178,39 @@ def _location_driver(location_name, root) -> LocationDriver:
169178
170179 msg = "invalid location type"
171180 raise Exception (msg )
181+
182+
183+ def _find_all_dependencies (
184+ packet_ids : list [str ],
185+ metadata : dict [str , MetadataCore ],
186+ * ,
187+ allow_missing_packets : bool = False ,
188+ ) -> list [str ]:
189+ result = []
190+
191+ # This is a standard depth first search through the packet graph.
192+ seen = set (packet_ids )
193+ todo = list (packet_ids )
194+ while todo :
195+ packet_id = todo .pop ()
196+ result .append (packet_id )
197+
198+ m = metadata .get (packet_id )
199+ if m is not None :
200+ for dep in m .depends :
201+ if dep .packet not in seen :
202+ seen .add (dep .packet )
203+ todo .append (dep .packet )
204+ elif not allow_missing_packets :
205+ msg = f"Unknown packet { packet_id } "
206+ raise Exception (msg )
207+
208+ # We want the result to be reverse-topologically sorted, such that
209+ # dependencies come before their dependents. In principle, we could get
210+ # such an order directly from the graph traversal, but doing this with
211+ # multiple start points is not as easy as it seems.
212+ #
213+ # Using a lexicographic sort on the packet IDs is a reasonable alternative
214+ # because the IDs start with their timestamp, and dependencies have smaller
215+ # timestamps than dependents.
216+ return sorted (result )
0 commit comments