class XBM(Atom): """ A simple class representing an XMB image. """ #: The width of the xbm image. width = Int() #: The height of the xbm image. height = Int() #: The bytestring of image data. data = Bytes() def __init__(self, width, height, data): """ Initialize an XBM image. Parameters ---------- width : int The width of the bitmap. height : int The height of the bitmap. data : list A list of 1s and 0s which represent the bitmap data. The length must be equal to width * height. """ assert len(data) == (width * height) bytes_list = [] for row in range(height): val = 0 offset = row * width for col in range(width): d = col % 8 if col > 0 and d == 0: bytes_list.append(val) val = 0 v = data[offset + col] val |= v << (7 - d) bytes_list.append(val) self.width = width self.height = height self.data = bytes(bytes_list) def toBitmap(self): size = QSize(self.width, self.height) return QBitmap.fromData(size, self.data, QImage.Format_Mono)
class Connection(Atom, LineReceiver): #: Name name = Str() #: Connection state connected = Int() #: Log output output = ContainerList() #: Split on newlines delimiter = Bytes(b'\n') #: Connection errors errors = Str() #: The factory that created this connection factory = Instance(ConnectionFactory) def connect(self): return self.factory.connect(self) def connectionMade(self): self.connected = 1 def connectionLost(self, reason): self.connected = 0 self.errors = str(reason) def lineReceived(self, line): self.output.append(line) def write(self, message): if self.transport is None: return IOError("Not connected") if not isinstance(message, bytes): message = message.encode() return self.transport.write(message) def disconnect(self): """ """ if self.transport: self.transport.loseConnection()
class Client(Model): """ Handles logging into protonmail """ #: API access api = ForwardInstance(lambda: API) #: Use blocking API blocking = Bool() #: Debug api calls debug = Bool(True) #: Is logged in is_logged_in = Bool() def _default_api(self): return API(client=self) # ========================================================================= # Parameters for api/auth/info # ========================================================================= AppVersion = Str('Web_3.14.21') ApiVersion = Str('3') #: Username Username = Str() #: Web ClientID = Str("Web") #: Client secret from WebClient/env/configDefault.js ClientSecret = Str("4957cc9a2e0a2a49d02475c9d013478d") #: Auth info from login AuthInfo = Instance(responses.AuthInfoResponse) # ========================================================================= # Parameters for api/auth/ # ========================================================================= #: Computed client ephemeral, set in _default_ClientProof ClientEphemeral = Bytes() #: Default key size KeySize = Int(2048) #: Get the hashed password HashedPassword = Bytes() def _observe_HashedPassword(self, change): # Recalculate the proof when the HashedPassword changes self.ClientProof = self._default_ClientProof() def _default_ClientProof(self): """ Computes the ClientProof from the AuthInfo """ info = self.AuthInfo proofs = auth.generate_proofs(self.KeySize, b64d(auth.read_armored(info.Modulus)), self.HashedPassword, b64d(info.ServerEphemeral)) self.ClientEphemeral = proofs['client_ephemeral'] self.ExpectedServerProof = proofs['server_proof'] return proofs['client_proof'] #: Client proof ClientProof = Bytes() #: Expected server proof ExpectedServerProof = Bytes() #: Auth response from login Auth = Instance(responses.AuthResponse) #: Code for api/auth TwoFactorCode = Instance(TwoFactorCode) EventID = Str() def _default_EventID(self): return self.Auth and self.Auth.EventID # ========================================================================= # Parameters for api/auth/cookies/ # ========================================================================= #: Used for api/auth/cookies/ ResponseType = Str("token") GrantType = Str("refresh_token") RedirectURI = Str("https://protonmail.com") def _default_State(self): return auth.generate_random_string(24) #: Random string State = Str() #: Result from the cookies request AuthCookies = Instance(responses.AuthCookiesResponse) #: Cookies set Cookies = Instance((CookieJar, SimpleCookie)) #: TODO: How to make this secure? SessionStorage = Dict() #: The hashed mailbox password MailboxPassword = Str() # ========================================================================= # Results from api/users/ # ========================================================================= #: User info User = Instance(User) # ========================================================================= # Results from api/addresses/ # ========================================================================= Addresses = List(Address) # ========================================================================= # Results for api/settings/ # ========================================================================= #: Settings Settings = Instance(UserSettings) # ========================================================================= # Results for api/settings/ # ========================================================================= #: Settings mail = Instance(UserSettings) #: Remote public keys PublicKeys = Dict() #: Encrypted private key PrivateKey = Instance(PGPKey) def _default_PrivateKey(self): if not self.Auth: return key, _ = PGPKey.from_blob(self.Auth.EncPrivateKey) return key def _observe_Auth(self, change): if self.Auth: self.PrivateKey = self._default_PrivateKey() self.is_logged_in = True else: del self.PrivateKey self.is_logged_in = False def get_public_key(self, email, timeout=None): """ Get the public keys for the given email Parameters ---------- emails: String Email to retrieve timeout: Int or Float Time to wait when blocking Returns -------- result: Dict """ if not self.blocking: return self._get_public_key(email) return run_sync(self._get_public_key, email, timeout=timeout) @coroutine def _get_public_key(self, email): email = utils.str(email) r = yield self.api.keys('?Email={}'.format(email), blocking=False, response=responses.KeysResponse) self.PublicKeys[email] = [ PGPKey.from_blob(k.PublicKey)[0] for k in r.Keys ] return_value(r) def get_addresses(self, timeout=None): """ Get addresses this User has Parameters ---------- timeout: Int or Float Time to wait when blocking Returns -------- result: User """ if not self.blocking: return self._get_addresses() return run_sync(self._get_addresses, timeout=timeout) @coroutine def _get_addresses(self): r = yield self.api.addresses(blocking=False, response=responses.AddressesResponse) if r.Code == 1000: self.Addresses = r.Addresses return_value(r) def get_user_info(self, timeout=None): """ Get the info aboute this User Parameters ---------- timeout: Int or Float Time to wait when blocking Returns -------- result: User """ if not self.blocking: return self._get_user_info() return run_sync(self._get_user_info, timeout=timeout) @coroutine def _get_user_info(self): r = yield self.api.users(blocking=False, response=responses.UsersResponse) if r.Code == 1000: self.User = r.User return_value(r) def read_message(self, message, timeout=None): """ Read and decrypt a Message if necessary Parameters ---------- message: protonmail.message.Message or Dict Returns ------- result: String Decrypted message """ if not self.blocking: return self._read_message(message) return run_sync(self._read_message, message, timeout=timeout) @coroutine def _read_message(self, message): if not isinstance(message, Message): raise TypeError("expected a protonmail.models.Message instance") # If the message hasn't been read yet, do that now if not message.Body: resp = yield self.api.messages(message.ID, blocking=False, response=responses.MessageResponse) if resp.Code != 1000: raise ValueError("Unexpected response: {}".format( resp.to_json())) message = resp.Message # Read and decrypt if needed msg = PGPMessage.from_blob(message.Body) if msg.is_signed: email = message.SenderAddress if email not in self.PublicKeys: yield self._get_public_key(email) pk = self.PublicKeys.get(email) if not pk: raise SecurityError("Failed to verify signed message!") pk[0].verify(msg) # TODO: Support mutiple keys # Decrypt with self.PrivateKey.unlock(self.MailboxPassword) as key: message.decrypt(key) return_value(message) def create_draft(self, address=None, message=None, timeout=None): """ Create a message as a draft. This will populate an ID for the message. Parameters ---------- address: protonmail.models.Address or None Address to send with, if None, the default will be used message: protonmail.models.Message or None Message to create a draft for, if None, one will be created timeout: Int or Float Timeout to wait when blocking Returns ------- result: protonmail.responses.MessageResponse """ if not self.blocking: return self._create_draft(address, message) return run_sync(self._create_draft, address, message, timeout=timeout) @coroutine def _create_draft(self, address=None, message=None): if message is None: # Create a new message user = self.User if not user: r = yield self._get_user_info() user = self.User if not address: addresses = self.Addresses if not addresses: r = yield self._get_addresses() addresses = self.Addresses if not addresses: raise ValueError("No email addresses available") address = addresses[0] name = (address.DisplayName or address.Name or user.DisplayName or user.Name) message = Message(AddressID=address.ID, IsRead=1, MIMEType='text/html', Sender=EmailAddress(Name=name, Address=address.Email)) # Make sure it's encrypted message.encrypt(self.PrivateKey.pubkey) r = yield self.api.messages(method='POST', blocking=False, response=responses.MessageResponse, json={ 'AttachmentKeyPackets': [], 'id': None, 'Message': message.to_json( 'AddressID', 'Sender', 'IsRead', 'CCList', 'BCCList', 'MIMEType', 'Subject', 'Body', 'ToList', ) }) if r.Message: r.Message.Client = self return_value(r) def save_draft(self, message, timeout=None): """ Encrypt (if necessary) and save the message as a draft. Parameters ---------- message: protonmail.models.Message Returns ------- result: protonmail.responses.MessageResponse """ if not self.blocking: return self._save_draft(message) return run_sync(self._save_draft, message, timeout=timeout) @coroutine def _save_draft(self, message): if not isinstance(message, Message): raise TypeError("expected a protonmail.models.Message instance") if not message.ID: raise ValueError("Cannot save a draft without an ID. " "Use create_draft first.") # Encrypt for this client only message.encrypt(self.PrivateKey.pubkey) # Should never happen if not message.is_encrypted(): raise SecurityError("Failed to encrypted draft") r = yield self.api.messages(message.ID, method='PUT', blocking=False, response=responses.MessageResponse, json={ 'AttachmentKeyPackets': {}, 'id': message.ID, 'Message': message.to_json( 'AddressID', 'Sender', 'IsRead', 'CCList', 'BCCList', 'MIMEType', 'Subject', 'Body', 'ToList', ) }) if r.Message: r.Message.Client = self return_value(r) def send_message(self, message, timeout=None): """ Encrypt and send the message. Parameters ---------- message: protonmail.models.Message Returns ------- result: protonmail.responses.MessageResponse """ if not self.blocking: return self._send_message(message) return run_sync(self._send_message, message, timeout=timeout) @coroutine def _send_message(self, message): if not isinstance(message, Message): raise TypeError("expected a protonmail.models.Message instance") if not message.ToList: raise ValueError("message missing email to addresses") # Read draft from server if needed if message.ID and not message.Body: r = yield self.api.messages(message.ID, blocking=False, response=responses.MessageResponse) message = r.Message # Decrypt if message.Body and not message.DecryptedBody: yield self._read_message(message) # Get any missing keys keys = self.PublicKeys emails = list( set([ to.Address for to in (message.ToList + message.CCList + message.BCCList) ])) for e in emails: if e not in keys: yield self._get_public_key(e) keys = self.PublicKeys # Extract the session key #cipher = SymmetricKeyAlgorithm.AES256 #session_key = auth.generate_session_key(cipher) with self.PrivateKey.unlock(self.MailboxPassword) as uk: cipher, session_key = auth.decrypt_session_key(message.Body, key=uk) pkg = { 'Addresses': {}, 'Body': "", 'MIMEType': message.MIMEType or "text/html", 'Type': 0, } # If we need to send the key in clear cleartext = False for to in message.ToList: pk = keys.get(to.Address) if pk is None: raise SecurityError("Failed to get public key for: " "{}".format(to.Address)) if pk: # Inside user # I guess the server does this? Encrypt body for email's pubkey #pkg['Body'] = pk.encrypt(pkg['Body'], cipher=cipher, # sessionkey=session_key) # Encrypt the session key for this user # TODO: Support multiple keys sk = auth.encrypt_session_key(session_key, key=pk[0], cipher=cipher) pkg['Addresses'][to.Address] = { 'AttachmentKeyPackets': {}, 'BodyKeyPacket': utils.str(b64e(sk)), 'Signature': 0, 'Type': Message.SEND_PM } pkg['Type'] |= Message.SEND_PM elif False and message.IsEncrypted: # Disabled for now # Enc outside user token = message.generate_reply_token(cipher) enc_token = PGPMessage.new(b64d(token)).encrypt( message.Password).message.__bytes__() pkg['Addresses'][to.Address] = { 'Auth': 0, 'PasswordHint': message.PasswordHint, 'Type': Message.SEND_EO, 'Token': token, 'EncToken': utils.str(b64e(enc_token)), 'AttachmentKeyPackets': {}, 'BodyKeyPacket': utils.str(b64e(session_key)), 'Signature': int(pkg['Body'].is_signed), } else: cleartext = True # Outside user pkg['Addresses'][to.Address] = { 'Signature': 0, 'Type': Message.SEND_CLEAR } pkg['Type'] |= Message.SEND_CLEAR if cleartext and message.ExpirationTime and not message.Password: raise SecurityError("Expiring emails to non-ProtonMail recipients" \ "require a message password to be set") # Sending to a non PM user screws all security if cleartext: pkg['BodyKey'] = { 'Algorithm': cipher.name.lower(), 'Key': utils.str(b64e(session_key)) } pkg['AttachmentKeys'] = {} # TODO # Get the message msg = PGPMessage.new(message.DecryptedBody) # Sign it with self.PrivateKey.unlock(self.MailboxPassword) as uk: msg |= uk.sign(msg) # Encrypt it using the session key and encode it msg = self.PrivateKey.pubkey.encrypt(msg, cipher=cipher, sessionkey=session_key) # Now encode it pkg['Body'] = utils.str(b64e(msg.message.__bytes__())) r = yield self.api.messages(message.ID, method='POST', blocking=False, response=responses.MessageSendResponse, json={ 'ExpirationTime': 0, 'id': message.ID, 'Packages': [pkg] }) return_value(r) def check_events(self, timeout=None): """ Check for updates""" if not self.blocking: return self._check_events() return run_sync(self._check_events, timeout=timeout) @coroutine def _check_events(self): eid = id or self.EventID data = yield self.api.events(eid, blocking=False) self.EventID = data['EventID'] return_value(data) def send_simple(self, **kwargs): """ Simple API for sending email """ if not self.blocking: return self._send_simple(**kwargs) return run_sync(self._send_simple, **kwargs) @coroutine def _send_simple(self, to, subject="", body="", cc=None, bcc=None): if not to: raise ValueError("Please enter one or more recipient email " "addresses") r = yield self._create_draft() if r.Code != 1000: raise ValueError("Failed to create draft: {}".format(r.to_json())) m = r.Message m.Subject = subject m.DecryptedBody = body if not isinstance(to, (tuple, list)): to = [to] m.ToList = [EmailAddress(Address=addr) for addr in to] if cc is not None: m.CCList = [EmailAddress(Address=addr) for addr in cc] if bcc is not None: m.BCCList = [EmailAddress(Address=addr) for addr in bcc] r = yield self._save_draft(m) if r.Code != 1000: raise ValueError("Failed to save draft: {}".format(r.to_json())) r = yield self._send_message(m) if r.Code != 1000: raise ValueError("Failed to send message: {}".format(r.to_json())) return_value(r)
], 3 * [1], ['a'] + [] if sys.version_info >= (3, ) else [1.0e35]), (Long(strict=True), [long(1)], [long(1)], [1.0, 1] if sys.version_info < (3, ) else [0.1]), (Long(strict=False), [1, 1.0, int(1)], 3 * [1], ['a']), (Range(0, 2), [0, 2], [0, 2], [-1, 3, '']), (Range(2, 0), [0, 2], [0, 2], [-1, 3]), (Range(0), [0, 3], [0, 3], [-1]), (Range(high=2), [-1, 2], [-1, 2], [3]), (Float(), [1, int(1), 1.1], [1.0, 1.0, 1.1], ['']), (Float(strict=True), [1.1], [1.1], [1]), (FloatRange(0.0, 0.5), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.5, 0.0), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.0), [0.0, 0.6], [0.0, 0.6], [-0.1, '']), (FloatRange(high=0.5), [-0.3, 0.5], [-0.3, 0.5], [0.6]), (Bytes(), [b'a', u'a'], [b'a'] * 2, [1]), (Bytes(strict=True), [b'a'], [b'a'], [u'a']), (Str(), [b'a', u'a'], ['a'] * 2, [1]), (Str(strict=True), [b'a'] if sys.version_info < (3, ) else [u'a'], ['a'], [u'a'] if sys.version_info < (3, ) else [b'a']), (Unicode(), [b'a', u'a'], [u'a'] * 2, [1]), (Unicode(strict=True), [u'a'], [u'a'], [b'a']), (Enum(1, 2, 'a'), [1, 2, 'a'], [1, 2, 'a'], [3]), (Callable(), [int, None], [int, None], [1]), (Coerced(set), [{1}, [1], (1, )], [{1}] * 3, [1]), (Coerced(int, coercer=lambda x: int(str(x), 2)), ['101'], [5], []), (Coerced( (int, float), coercer=lambda x: int(str(x), 2)), ['101'], [5], []), (Coerced(int, coercer=lambda x: []), [], [], ['']), (Tuple(), [(1, )], [(1, )], [[1]]), (Tuple(Int()), [(1, )], [(1, )], [(1.0, )]),
class Device(Model, asyncio.Protocol): #: Name name = Str().tag(config=True) #: UUID uuid = Str().tag(config=True) #: Default default = Bool().tag(config=True) #: Device state connected = Bool() busy = Bool() last_read = Bytes() last_write = Bytes() errors = Str() #: Config config = Instance(DeviceConfig, ()).tag(config=True) #: The connection connection = Instance(Connection).tag(config=True) def _default_uuid(self): return str(uuid.uuid4().hex) def __hash__(self): return int(self.uuid, 16) def __eq__(self, other): if not isinstance(other, Device): return False return self.uuid == other.uuid # ------------------------------------------------------------------------- # Protocol API # ------------------------------------------------------------------------- def connection_made(self, transport): self.connected = True self.connection.transport = transport def connection_lost(self, exc): self.connected = False self.errors = f'{exc}' def data_received(self, data): self.last_read = data def pause_writing(self): #print(self.connection.transport.get_write_buffer_size()) pass def resume_writing(self): #print(self.connection.transport.get_write_buffer_size()) pass async def wait_until(self, fn, timeout=30, message="Timeout hit", rate=0.1): """ Wait for the fn to return true or until the timeout hits Parameters ---------- fn: Callable A function that returns a boolean when ready timeout: Float or None Time in seconds to wait before giving an error or None to block forever message: Str Message to set on the error if a timeout occurs rate: Float Time in seconds to wait before checking the fn again """ start = time.time() while not fn(): await asyncio.sleep(rate) if timeout is not None and (time.time() - start) > timeout: raise TimeoutError(message) # ------------------------------------------------------------------------- # Device API # ------------------------------------------------------------------------- async def connect(self): """ Make the connection and wait until connection_made is called. """ if self.connected: return await self.connection.connect(self) # Wait for it to connect await self.wait_until(lambda: self.connected, 30, message="Connection timeout") async def write(self, data): """ Write the data and wait until the write buffer is empty. Parameters ---------- data: Bytes or Str Data to write to the device """ if not self.connected: return IOError("Not connected") if not isinstance(data, bytes): data = data.encode() self.last_write = data # Write just puts it into the write buffer # So wait until the buffer is empty (all written) # or the connection drops self.connection.transport.write(data) get_buffer_size = self.connection.transport.get_write_buffer_size await self.wait_until( lambda: not self.connected or get_buffer_size() == 0, timeout=None) if not self.connected: raise IOError("Connection dropped") async def disconnect(self): """ Drop the connection. This will call connection_lost when it is actually closed. """ if not self.connected: return await self.connection.disconnect() def convert(self, point): """ Convert a point based on this device's configuration Parameters ---------- point: declaracad.occ.shape.Point The point to convert Returns ------- converted_point: Tuple Tuple of converted values """ config = self.config precision = config.PRECISIONS.get(config.precision) o = config.origin x = o.x - point.x if config.mirror_x else point.x - o.x y = o.y - point.y if config.mirror_y else point.y - o.y z = o.z - point.z if config.mirror_z else point.z - o.z x = gcode.convert(x, config.scale_x, precision) y = gcode.convert(y, config.scale_y, precision) z = gcode.convert(z, config.scale_z, precision) if config.swap_xy: x, y = y, x return (x, y, z) async def rapid_move_to(self, point): """ Send a G0 to the point """ x, y, z = self.convert(point) await self.write(f'G0 X{x} Y{y} Z{z}\n')
class ProcessLineReceiver(Atom, asyncio.SubprocessProtocol): """ A process protocol that pushes output into a list of each line. Observe the `output` member in a view to have it update with live output. """ #: Process transport process_transport = Value() transport = Value() #: Status code exit_code = Int() #: Holds process output output = ContainerList() #: Redirect error to output err_to_out = Bool(True) #: Split on each line delimiter = Bytes(b'\n') def connection_made(self, transport): """ Save a reference to the transports Parameters ---------- transport: asyncio.SubprocessTransport The transport for stdin, stdout, and stderr pipes """ self.process_transport = transport self.transport = transport.get_pipe_transport(0) def pipe_data_received(self, fd, data): """ Forward calls to data_received or err_received based one the fd Parameters ---------- fd: Int The fd of the pipe data: Bytes The data received """ if fd == 1: self.data_received(data) elif fd == 2: if self.err_to_out: self.data_received(data) else: self.err_received(data) def data_received(self, data): """ Called for stdout data and stderr data if err_to_out is True Parameters ---------- data: Bytes The data received """ self.output.append(data) def err_received(self, data): """ Called for stderr data if err_to_out is set to False Parameters ---------- data: Bytes The data received """ self.output.append(data) def terminate(self): if self.process_transport: try: self.process_transport.terminate() except ProcessLookupError as e: pass
class File(JSONModel): id = Instance(uuid.UUID, factory=uuid.uuid4) name = Str() data = Bytes()
class Model(with_metaclass(ModelMeta, Atom)): """ An atom model that can be serialized and deserialized to and from a database. """ __slots__ = '__weakref__' #: ID of this object in the database. Subclasses can redefine this as needed _id = Bytes() #: A unique ID used to handle cyclical serialization and deserialization #: Do NOT use python's id() as these are reused and can cause conflicts __ref__ = Bytes(factory=lambda: os.urandom(16)) # ========================================================================== # Serialization API # ========================================================================== #: Handles encoding and decoding. Subclasses should redefine this to a #: subclass of ModelSerializer serializer = None def __getstate__(self, scope=None): state = super(Model, self).__getstate__() flatten = self.serializer.flatten scope = scope or {} ref = self.__ref__ scope[ref] = self state = {f: flatten(state[f], scope) for f in self.__fields__} state['__model__'] = self.__model__ state['__ref__'] = ref # ID for circular references if self._id is not None: state['_id'] = self._id return state async def __setstate__(self, state, scope=None): """ Restore an object from the a state from the database. This is async as it will lookup any referenced objects from the DB. """ unflatten = self.serializer.unflatten name = state.get('__model__') if name is None: raise ValueError("State must contain the __model__ key") if name != self.__model__: raise ValueError(f"Trying to use {name} state for " f"{self.__model__} object") scope = scope or {} ref = state.pop('__ref__', None) if ref is not None: scope[ref] = self members = self.members() for k, v in state.items(): # Don't use getattr because it triggers a default value lookup if members.get(k): try: obj = await unflatten(v, scope) setattr(self, k, obj) except Exception as e: exc = traceback.format_exc() WebApplication.instance().logger.error( f"Error setting state:" f"{self.__model__}.{k} = {pformat(obj)}:" f"\nSelf: {ref}: {scope.get(ref)}" f"\nValue: {pformat(v)}" f"\nScope: {pformat(scope)}" f"\nState: {pformat(state)}" f"\n{exc}" ) # ========================================================================== # Database API # ========================================================================== #: Handles database access. Subclasses should redefine this. objects = None @classmethod async def restore(cls, state): """ Restore an object from the database """ obj = cls.__new__(cls) await obj.__setstate__(state) return obj async def save(self): """ Alias to delete this object to the database """ raise NotImplementedError async def delete(self): """ Alias to delete this object in the database """ raise NotImplementedError
class AbstractUser(SQLModel): email = Str().tag(length=64) hashed_password = Bytes() class Meta: abstract = True
class Attachment(SQLModel): id = Int().tag(primary_key=True) email = Instance(Email).tag(nullable=False) name = Str().tag(length=100) size = Int() data = Bytes()
(Range(high=2), [-1, 2], [-1, 2], [3]), ( Range(sys.maxsize, sys.maxsize + 2), [sys.maxsize, sys.maxsize + 2], [sys.maxsize, sys.maxsize + 2], [sys.maxsize - 1, sys.maxsize + 3], ), (Float(), [1, int(1), 1.1], [1.0, 1.0, 1.1], [""]), (Float(strict=True), [1.1], [1.1], [1]), (FloatRange(0.0, 0.5), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.5, 0.0), [0.0, 0.5], [0.0, 0.5], [-0.1, 0.6]), (FloatRange(0.0), [0.0, 0.6], [0.0, 0.6], [-0.1, ""]), (FloatRange(high=0.5), [-0.3, 0.5], [-0.3, 0.5], [0.6]), (FloatRange(1.0, 10.0, strict=True), [1.0, 3.7], [1.0, 3.7 ], [2, 4, 0, -11]), (Bytes(strict=False), [b"a", "a"], [b"a"] * 2, [1]), (Bytes(), [b"a"], [b"a"], ["a"]), (Str(strict=False), [b"a", "a"], ["a"] * 2, [1]), (Str(), ["a"], ["a"], [b"a"]), (Enum(1, 2, "a"), [1, 2, "a"], [1, 2, "a"], [3]), (Callable(), [int, None], [int, None], [1]), # 3.9 subs and 3.10 union tests in test_typing_utils are sufficient (Coerced(set), [{1}, [1], (1, )], [{1}] * 3, [1]), (Coerced(int, coercer=c), ["101"], [5], []), (Coerced((int, float), coercer=c), ["101"], [5], []), (Coerced(int, coercer=lambda x: []), [], [], [""]), # type: ignore (Coerced(TSet[int]), [{1}, [1], (1, )], [{1}] * 3, [1]), (Tuple(), [(1, )], [(1, )], [[1]]), (Tuple(Int()), [(1, )], [(1, )], [(1.0, )]), (Tuple(int), [(1, )], [(1, )], [(1.0, ), (None, )]), (Tuple(TSet[int]), [({1}, )], [({1}, )], [(1.0, ), (None, )]),