PK!Y9isim/__init__.py"""Wrapper around `xcrun simctl`.""" import os from typing import List, Optional, Union import shlex import subprocess from isim.device import Device, DeviceNotFoundError from isim.device_pair import DevicePair from isim.device_type import DeviceType, DeviceTypeNotFoundError from isim.runtime import Runtime, RuntimeNotFoundError # Advanced: # pbsync Sync the pasteboard content from one pasteboard to another. # pbcopy Copy standard input onto the device pasteboard. # pbpaste Print the contents of the device's pasteboard to standard output. # io Set up a device IO operation. # spawn Spawn a process on a device. # launch Launch an application by identifier on a device. # DONE: # addmedia Add photos, live photos, or videos to the photo library of a device # boot Boot a device. # clone Clone an existing device. # create Create a new device. # delete Delete a device or all unavailable devices. # erase Erase a device's contents and settings. # get_app_container Print the path of the installed app's container # getenv Print an environment variable from a running device. # icloud_sync Trigger iCloud sync on a device. # install Install an app on a device. # list List available devices, device types, runtimes, or device pairs. # logverbose enable or disable verbose logging for a device # openurl Open a URL in a device. # pair Create a new watch and phone pair. # pair_activate Set a given pair as active. # rename Rename a device. # shutdown Shutdown a device. # terminate Terminate an application by identifier on a device. # uninstall Uninstall an app from a device. # unpair Unpair a watch and phone pair. # upgrade Upgrade a device to a newer runtime. # Won't Do: # help Prints the usage for a given subcommand. def diagnose( *, output_path: str, all_logs: bool = False, include_data_directory: bool = False, archive: bool = True, timeout: int = 300, udids: Optional[Union[List[str], str]] = None ) -> str: """Run the xcrun simctl diagnose command. By default, this will run only for booted devices. Set all_logs to True to gather data for non-booted devices. To include the data directory, set include_data_directory to True. If udid is set, it will only connect diagnostics from that device. However, if all_logs is set, that will override this setting. Returns the location of the archive. """ output_archive = f'{output_path}.tar.gz' if os.path.exists(output_path): raise FileExistsError('The output directory already exists') if os.path.exists(output_archive): raise FileExistsError(f'The output archive file already exists: "{output_archive}"') # I'm not entirely sure what the '-l' flag does. It's not documented, but if # I don't set it, the command just waits forever without doing anything. # LinkedIn use this flag for their Bluepill tool. full_command = ['xcrun', 'simctl', 'diagnose', '-l', '-b', f'--timeout={timeout}', f'--output={shlex.quote(output_path)}'] if not archive: full_command.append('--no-archive') if include_data_directory: full_command.append('--data-container') if all_logs: full_command.append('--all-logs') if udids is not None: if isinstance(udids, str): full_command.append(f'--udid={udids}') else: full_command += [f'--udid={udid}' for udid in udids] command_string = " ".join(full_command) # Let the exception bubble up _ = subprocess.run( command_string, universal_newlines=True, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True ) return output_archive PK!isim/base_types.py"""Base types for `xcrun simctl`.""" import enum import json from typing import Any, Dict import subprocess class ErrorCodes(enum.Enum): """Simple lookup for all known error codes.""" # Tried to access a file or directory (such as by searching for an app # container) that doesn't exist no_such_file_or_directory = 2 # Trying to perform an action on a device type, but supplied an invalid # device type invalid_device_type = 161 # Tried to perform an action on the device, but there was an # incompatibility, such as when trying to create a new Apple TV device with # a watchOS runtime. incompatible_device = 162 # Tried to perform an action on the device, but there was an # incompatibility, such as when trying to create a new Apple TV device with # a watchOS runtime. # This seems to be the same as above incompatible_device2 = 163 # The device was in a state where it can't be shutdown. e.g. already # shutdown unable_to_shutdown_device_in_current_state = 164 class SimulatorControlType(enum.Enum): """Which type of simulator control type is it.""" device_pair = "pair" runtime = "runtime" device_type = "device_type" device = "device" def list_key(self): """Define the key passed into the list function for the type.""" # Disable this false positive #pylint: disable=comparison-with-callable if self.name == "device_type": return "devicetypes" #pylint: enable=comparison-with-callable return self.value + "s" class SimulatorControlBase: """Types defined by simctl should inherit from this.""" raw_info: Dict[str, Any] simctl_type: SimulatorControlType def __init__(self, raw_info: Dict[str, Any], simctl_type: SimulatorControlType) -> None: self.raw_info = raw_info self.simctl_type = simctl_type #pylint: disable=no-self-use def _run_command(self, command: str) -> str: """Convenience method for running an xcrun simctl command.""" return SimulatorControlBase.run_command(command) #pylint: enable=no-self-use def __eq__(self, other: object) -> bool: """Override the default Equals behavior""" if not isinstance(other, self.__class__): return False if not self.simctl_type == other.simctl_type: return False return self.raw_info == other.raw_info def __ne__(self, other: object) -> bool: """Define a non-equality test""" return not self.__eq__(other) @staticmethod def run_command(command: str) -> str: """Run an xcrun simctl command.""" full_command = "xcrun simctl %s" % (command,) # Deliberately don't catch the exception - we want it to bubble up return subprocess.run(full_command, universal_newlines=True, shell=True, check=True, stdout=subprocess.PIPE).stdout @staticmethod def list_type(item: SimulatorControlType) -> Any: """Run an `xcrun simctl` command with JSON output.""" full_command = "xcrun simctl list %s --json" % (item.list_key(),) # Deliberately don't catch the exception - we want it to bubble up output = subprocess.run(full_command, universal_newlines=True, shell=True, check=True, stdout=subprocess.PIPE).stdout json_output = json.loads(output) if not isinstance(json_output, dict): raise Exception("Unexpected list type: " + str(type(json_output))) if not json_output.get(item.list_key()): raise Exception("Unexpected format for " + item.list_key() + " list type: " + str(json_output)) return json_output[item.list_key()] PK!ޝ~4~4isim/device.py"""Represents a device for simctl.""" #pylint: disable=too-many-public-methods import os import re import shlex from typing import Any, Dict, List, Optional, Union from isim.runtime import Runtime from isim.device_type import DeviceType from isim.base_types import SimulatorControlBase, SimulatorControlType class MultipleMatchesException(Exception): """Raised when we have multiple matches, but only expect a single one.""" class DeviceNotFoundError(Exception): """Raised when a requested device is not found.""" class InvalidDeviceError(Exception): """Raised when a device is not of the correct type.""" #pylint: disable=too-many-instance-attributes class Device(SimulatorControlBase): """Represents a device for the iOS simulator.""" raw_info: Dict[str, Any] availability: Optional[str] is_available: str name: str runtime_id: str state: str udid: str _runtime: Optional[Runtime] def __init__(self, device_info: Dict[str, Any], runtime_id: str) -> None: """Construct a Device object from simctl output and a runtime key. device_info: The dictionary representing the simctl output for a device. runtime_id: The ID of the runtime that the device uses. """ super().__init__(device_info, SimulatorControlType.device) self._runtime = None self.raw_info = device_info self.availability = device_info.get("availability") self.is_available = device_info["isAvailable"] self.name = device_info["name"] self.runtime_id = runtime_id self.state = device_info["state"] self.udid = device_info["udid"] def refresh_state(self) -> None: """Refreshes the state by consulting simctl.""" device = Device.from_identifier(self.udid) self.raw_info = device.raw_info self.availability = device.availability self.is_available = device.is_available self.name = device.name self.state = device.state self.udid = device.udid def runtime(self) -> Runtime: """Return the runtime of the device.""" if self._runtime is None: self._runtime = Runtime.from_id(self.runtime_id) return self._runtime def get_app_container(self, app_identifier: str, container: Optional[str] = None) -> str: """Get the path of the installed app's container.""" command = 'get_app_container "%s" "%s"' % (self.udid, app_identifier) if container is not None: command += ' "' + container + '"' path = self._run_command(command) # The path has an extra new line at the end, so remove it when returning #pylint: disable=unsubscriptable-object return path[:-1] #pylint: enable=unsubscriptable-object def get_data_directory(self, app_identifier: str) -> Optional[str]: """Get the path of the data directory for the app. (The location where the app can store data, files, etc.) There's no real way of doing this. This method works by scanning the installation logs for the simulator to try and find out where the app actually lives. """ app_container = self.get_app_container(app_identifier) # Drop the *.app app_container = os.path.dirname(app_container) data_folder = os.path.join(app_container, "..", "..", "..", "..") mobile_installation_folder = os.path.join(data_folder, "Library", "Logs", "MobileInstallation") mobile_installation_folder = os.path.abspath(mobile_installation_folder) log_file_names = os.listdir(mobile_installation_folder) # We sort these since we want the latest file (.0) first log_file_names = sorted(log_file_names) container_pattern = re.compile(f'.*Data container for {app_identifier} is now at (.*)') # We are looking for the last match in the file for log_file in log_file_names: log_path = os.path.join(mobile_installation_folder, log_file) with open(log_path, 'r') as log_file_handle: log_lines = log_file_handle.readlines() # We want the last mention in the file (i.e. the latest) log_lines.reverse() for line in log_lines: matches = container_pattern.findall(line.strip()) if not matches: continue # We found a match, so return it return matches[0] return None def openurl(self, url: str) -> None: """Open the url on the device.""" command = 'openurl "%s" "%s"' % (self.udid, url) self._run_command(command) def logverbose(self, enable: bool) -> None: """Enable or disable verbose logging.""" command = 'logverbose "%s" "%s"' % (self.udid, "enable" if enable else "disable") self._run_command(command) def icloud_sync(self) -> None: """Trigger iCloud sync.""" command = 'icloud_sync "%s"' % (self.udid,) self._run_command(command) def getenv(self, variable_name: str) -> str: """Return the specified environment variable.""" command = 'getenv "%s" "%s"' % (self.udid, variable_name) variable = self._run_command(command) # The variable has an extra new line at the end, so remove it when returning #pylint: disable=unsubscriptable-object return variable[:-1] #pylint: enable=unsubscriptable-object def addmedia(self, paths: Union[str, List[str]]) -> None: """Add photos, live photos, or videos to the photo library.""" if isinstance(paths, str): paths = [paths] if not paths: return command = 'addmedia "%s" ' % (self.udid) # Now we need to add the paths quoted_paths = ['"' + path + '"' for path in paths] paths_arg = " ".join(quoted_paths) command += paths_arg self._run_command(command) def terminate(self, app_identifier: str) -> None: """Terminate an application by identifier.""" command = 'terminate "%s" "%s"' % (self.udid, app_identifier) self._run_command(command) def install(self, path: str) -> None: """Install an application from path.""" command = 'install "%s" "%s"' % (self.udid, path) self._run_command(command) def uninstall(self, app_identifier: str) -> None: """Uninstall an application by identifier.""" command = 'uninstall "%s" "%s"' % (self.udid, app_identifier) self._run_command(command) def delete(self) -> None: """Delete the device.""" command = 'delete "%s"' % (self.udid) self._run_command(command) def rename(self, name: str) -> None: """Rename the device.""" command = 'rename "%s" "%s"' % (self.udid, name) self._run_command(command) def boot(self) -> None: """Boot the device.""" command = 'boot "%s"' % (self.udid,) self._run_command(command) def shutdown(self) -> None: """Shutdown the device.""" command = 'shutdown "%s"' % (self.udid,) self._run_command(command) def erase(self) -> None: """Erases the device's contents and settings.""" command = 'erase "%s"' % (self.udid,) self._run_command(command) def upgrade(self, runtime: Runtime) -> None: """Upgrade the device to a newer runtime.""" command = 'upgrade "%s" "%s"' % (self.udid, runtime.identifier) self._run_command(command) self._runtime = None self.runtime_id = runtime.identifier def clone(self, new_name: str) -> str: """Clone the device.""" command = 'clone "%s" "%s"' % (self.udid, new_name) device_id = self._run_command(command) # The device ID has a new line at the end. Strip it when returning. #pylint: disable=unsubscriptable-object return device_id[:-1] #pylint: enable=unsubscriptable-object def pair(self, other_device: 'Device') -> str: """Create a new watch and phone pair.""" watch = None phone = None if "com.apple.CoreSimulator.SimRuntime.iOS" in self.runtime_id: phone = self if "com.apple.CoreSimulator.SimRuntime.iOS" in other_device.runtime_id: phone = other_device if "com.apple.CoreSimulator.SimRuntime.watchOS" in self.runtime_id: watch = self if "com.apple.CoreSimulator.SimRuntime.watchOS" in other_device.runtime_id: watch = other_device if watch is None or phone is None: raise InvalidDeviceError("One device should be a watch and the other a phone") command = 'pair "%s" "%s"' % (watch.udid, phone.udid) pair_id = self._run_command(command) # The pair ID has a new line at the end. Strip it when returning. #pylint: disable=unsubscriptable-object return pair_id[:-1] #pylint: enable=unsubscriptable-object def screenshot(self, output_path: str) -> None: """Take a screenshot of the device and save to `output_path`.""" if os.path.exists(output_path): raise FileExistsError("Output file path already exists") self._run_command(f'io {self.udid} screenshot {shlex.quote(output_path)}') def __str__(self): """Return the string representation of the object.""" return self.name + ": " + self.udid def __repr__(self) -> str: """Return the string programmatic representation of the object.""" return str({"runtime_id": self.runtime_id, "raw_info": self.raw_info}) @staticmethod def from_simctl_info(info: Dict[str, List[Dict[str, Any]]]) -> Dict[str, List['Device']]: """Create a new device from the simctl info.""" all_devices: Dict[str, List[Device]] = {} for runtime_id in info.keys(): runtime_devices_info = info[runtime_id] devices: List['Device'] = [] for device_info in runtime_devices_info: devices.append(Device(device_info, runtime_id)) all_devices[runtime_id] = devices return all_devices @staticmethod def from_identifier(identifier: str) -> 'Device': """Create a new device from the simctl info.""" for _, devices in Device.list_all().items(): for device in devices: if device.udid == identifier: return device raise DeviceNotFoundError("No device with ID: " + identifier) @staticmethod def from_name( name: str, runtime: Optional[Runtime] = None ) -> Optional['Device']: """Get a device from the existing devices using the name. If the name matches multiple devices, the runtime is used as a secondary filter (if supplied). If there are still multiple matching devices, an exception is raised. """ # Only get the ones matching the name (keep track of the runtime_id in case there are multiple) matching_name_devices = [] for runtime_id, runtime_devices in Device.list_all().items(): for device in runtime_devices: if device.name == name: matching_name_devices.append((device, runtime_id)) # If there were none, then we have none to return if not matching_name_devices: return None # If there was 1, then we return it if len(matching_name_devices) == 1: return matching_name_devices[0][0] # If we have more than one, we need a run time in order to differentate between them if runtime is None: raise MultipleMatchesException("Multiple device matches, but no runtime supplied") # Get devices where the runtime name matches matching_devices = [device for device in matching_name_devices if device[1] == runtime.identifier] if not matching_devices: return None # We should only have one if len(matching_devices) > 1: raise MultipleMatchesException("Multiple device matches even with runtime supplied") return matching_devices[0][0] @staticmethod def create( name: str, device_type: DeviceType, runtime: Runtime ) -> 'Device': """Create a new device.""" command = 'create "%s" "%s" "%s"' % (name, device_type.identifier, runtime.identifier) device_id = SimulatorControlBase.run_command(command) # The device ID has a new line at the end, so strip it. #pylint: disable=unsubscriptable-object device_id = device_id[:-1] #pylint: enable=unsubscriptable-object return Device.from_identifier(device_id) @staticmethod def delete_unavailable() -> None: """Delete all unavailable devices.""" SimulatorControlBase.run_command("delete unavailable") @staticmethod def list_all() -> Dict[str, List['Device']]: """Return all available devices.""" raw_info = Device.list_all_raw() return Device.from_simctl_info(raw_info) @staticmethod def list_all_raw() -> Dict[str, List[Dict[str, Any]]]: """Return all device info.""" return SimulatorControlBase.list_type(SimulatorControlType.device) PK! տ isim/device_pair.py"""Handles simulator watch device pairs.""" from typing import Any, Dict, List from isim.base_types import SimulatorControlBase, SimulatorControlType class DevicePair(SimulatorControlBase): """Represents a device pair for the iOS simulator.""" raw_info: Dict[str, Any] identifier: str watch_udid: str phone_udid: str def __init__(self, device_pair_identifier: str, device_pair_info: Dict[str, Any]) -> None: """Construct a DevicePair object from simctl output. device_pair_identifier: The unique identifier for this device pair. device_pair_info: The dictionary representing the simctl output for a device pair. """ super().__init__(device_pair_info, SimulatorControlType.device_pair) self.raw_info = device_pair_info self.identifier = device_pair_identifier self.watch_udid = device_pair_info["watch"]["udid"] self.phone_udid = device_pair_info["phone"]["udid"] def watch(self) -> None: """Return the device representing the watch in the pair.""" raise NotImplementedError() def phone(self) -> None: """Return the device representing the phone in the pair.""" raise NotImplementedError() def unpair(self) -> None: """Unpair a watch and phone pair.""" command = 'unpair "%s"' % (self.identifier,) self._run_command(command) def activate(self) -> None: """Activate a pair.""" command = 'pair_activate "%s"' % (self.identifier,) self._run_command(command) def __str__(self) -> str: """Return the string representation of the object.""" return self.identifier def __repr__(self) -> str: """Return the string programmatic representation of the object.""" return str({"identifier": self.identifier, "raw_info": self.raw_info}) @staticmethod def from_simctl_info(info: Dict[str, Any]) -> List['DevicePair']: """Create a new device pair using the info from simctl.""" device_pairs = [] for device_pair_identifier, device_pair_info in info.items(): device_pairs.append(DevicePair(device_pair_identifier, device_pair_info)) return device_pairs @staticmethod def list_all() -> List['DevicePair']: """Return all available device pairs.""" device_pair_info = SimulatorControlBase.list_type(SimulatorControlType.device_pair) return DevicePair.from_simctl_info(device_pair_info) PK!#Fb!, , isim/device_type.py"""Handles simulator device types.""" from typing import Dict, List from isim.base_types import SimulatorControlBase, SimulatorControlType class DeviceTypeNotFoundError(Exception): """Raised when a requested device type is not found.""" class DeviceType(SimulatorControlBase): """Represents a device type for the iOS simulator.""" raw_info: Dict[str, str] bundle_path: str identifier: str name: str def __init__(self, device_type_info: Dict[str, str]): """Construct a DeviceType object from simctl output. device_type_info: The dictionary representing the simctl output for a device type. """ super().__init__(device_type_info, SimulatorControlType.device_type) self.raw_info = device_type_info self.bundle_path = device_type_info["bundlePath"].replace("\\/", "/") self.identifier = device_type_info["identifier"] self.name = device_type_info["name"] def __str__(self) -> str: """Return a user readable string representing the device type.""" return self.name + ": " + self.identifier def __repr__(self) -> str: """Return the string programmatic representation of the object.""" return str(self.raw_info) @staticmethod def from_simctl_info(info: List[Dict[str, str]]) -> List['DeviceType']: """Create a new device type from the simctl info.""" device_types = [] for device_type_info in info: device_types.append(DeviceType(device_type_info)) return device_types @staticmethod def from_id(identifier: str) -> 'DeviceType': """Get a device type from its identifier.""" for device_type in DeviceType.list_all(): if device_type.identifier == identifier: return device_type raise DeviceTypeNotFoundError("No device type matching identifier: " + identifier) @staticmethod def from_name(name: str) -> 'DeviceType': """Create a device type by looking up the existing ones matching the supplied name.""" # Get all device types device_types = DeviceType.list_all() for device_type in device_types: if device_type.name == name: return device_type raise DeviceTypeNotFoundError("No device type matching name: " + name) @staticmethod def list_all() -> List['DeviceType']: """Return all available device types.""" device_type_info = SimulatorControlBase.list_type(SimulatorControlType.device_type) return DeviceType.from_simctl_info(device_type_info) PK!EI isim/runtime.py"""Handles the runtimes for simctl.""" from typing import Any, Dict, List, Optional from isim.base_types import SimulatorControlBase, SimulatorControlType class RuntimeNotFoundError(Exception): """Raised when a requested runtime is not found.""" #pylint: disable=too-many-instance-attributes class Runtime(SimulatorControlBase): """Represents a runtime for the iOS simulator.""" raw_info: Dict[str, Any] availability: Optional[str] build_version: str bundle_path: str identifier: str is_available: bool name: str version: str def __init__(self, runtime_info: Dict[str, Any]) -> None: """Construct a Runtime object from simctl output. runtime_info: The dictionary representing the simctl output for a runtime. """ super().__init__(runtime_info, SimulatorControlType.runtime) self.raw_info = runtime_info self.availability = runtime_info.get("availability") self.build_version = runtime_info["buildversion"] self.bundle_path = runtime_info["bundlePath"].replace("\\/", "/") self.identifier = runtime_info["identifier"] self.is_available = runtime_info["isAvailable"] self.name = runtime_info["name"] self.version = runtime_info["version"] def __str__(self) -> str: """Return a string representation of the runtime.""" return "%s: %s" % (self.name, self.identifier) def __repr__(self) -> str: """Return the string programmatic representation of the object.""" return str(self.raw_info) @staticmethod def from_simctl_info(info: List[Dict[str, Any]]) -> List['Runtime']: """Create a runtime from the simctl info.""" runtimes = [] for runtime_info in info: runtimes.append(Runtime(runtime_info)) return runtimes @staticmethod def from_id(identifier: str) -> 'Runtime': """Create a runtime by looking up the existing ones matching the supplied identifier.""" # Get all runtimes for runtime in Runtime.list_all(): if runtime.identifier == identifier: return runtime raise RuntimeNotFoundError() @staticmethod def from_name(name: str) -> 'Runtime': """Create a runtime by looking up the existing ones matching the supplied name.""" for runtime in Runtime.list_all(): if runtime.name == name: return runtime raise RuntimeNotFoundError() @staticmethod def list_all() -> List['Runtime']: """Return all available runtimes.""" runtime_info = SimulatorControlBase.list_type(SimulatorControlType.runtime) return Runtime.from_simctl_info(runtime_info) PK!+?**!isim-11.0.0.dist-info/LICENSE.txtMIT License Copyright (c) 2017 Dale Myers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.PK!HnHTUisim-11.0.0.dist-info/WHEEL A н#Z;/"d&F[xzw@Zpy3Fv]\fi4WZ^EgM_-]#0(q7PK!Hg*V isim-11.0.0.dist-info/METADATAVo6|XHr ҤÂ%M]biHFRq)qk>y?ݽw^du^ȂSM9?8ka.{2V$tE~% nVO~7ZDĕ+&nTM$v:9Sٯ\AdL?U׮4=ttQL6Bq_C\J)+]k)훗a^t%[8ӧ'ҕV"Ȏ gӾC2k$;Q0ɳ/C';fQ2w+%7iV. nGϾ|f=>RNaƆټdOJ9!B\g^q%<3 bJ:ZK [QjѯNXeYLgwI (n[f7jӔOgc`VK xCV7ᨬܱJ+ZԛK(4VZ#Ԝ@^4Nʑj<&UV=7ݐ2E%F#$ɟ)y[߂,iz1ek$7$. 5V1B{_Obnr67H5a&[P̭|D'?9=yxAy'_C ?UI%-p<󽇦d!{јJI třKÜyZFf%fF6,k`]f:xڔy*$EH>;6HמZn G#EypJboa@!Q{ @fX@x[,qfh$ҥEXLrDž$6|DȢYМҴh@~j"~D45OJowh]wwh#-%v`sVN;N/ckL7_Y%O x_q9 ݴrΡ% 4ƨCvNqN?_ TPCJ&w2#KېּEq%+9~egwN4edѬ\pYVF m_0Kxkvw9dbQ|#+iOp_ - dS5t1r7bsR])7\I\鉆<J`=fC+_,$oU7NPK!Y9isim/__init__.pyPK!isim/base_types.pyPK!ޝ~4~4isim/device.pyPK! տ {Sisim/device_pair.pyPK!#Fb!, , k]isim/device_type.pyPK!EI gisim/runtime.pyPK!+?**!risim-11.0.0.dist-info/LICENSE.txtPK!HnHTUwisim-11.0.0.dist-info/WHEELPK!Hg*V wisim-11.0.0.dist-info/METADATAPK!H$|isim-11.0.0.dist-info/RECORDPK