class Dummy(Model): _private = Int() computed = Int().tag(store=False) id = Int() enabled = Bool() string = Bool() list_of_int = List(int) list_of_str = List(str) list_of_any = List() list_of_tuple = List(Tuple()) list_of_tuple_of_float = List(Tuple(float)) tuple_of_any = Tuple() tuple_of_number = Tuple((float, int)) tuple_of_int_or_model = Tuple((int, Model)) tuple_of_forwarded = Tuple(ForwardTyped(lambda: NotYetDefined)) set_of_any = Tuple() set_of_number = Set(float) set_of_model = Set(AbstractModel) dict_of_any = Dict() dict_of_str_any = Dict(str) dict_of_str_int = Dict(str, int) typed_int = Typed(int) typed_dict = Typed(dict) instance_of_model = Instance(AbstractModel) forwarded_instance = ForwardInstance(lambda: NotYetDefined) coerced_int = Coerced(int) prop = Property(lambda self: True) tagged_prop = Property(lambda self: 0).tag(store=True)
class Comment(SQLModel): page = Instance(Page) author = Instance(User) status = Enum("pending", "approved") body = Str().tag(type=sa.UnicodeText()) reply_to = ForwardInstance(lambda: Comment).tag(nullable=True) when = Instance(time)
class ProxyAbstractWidgetItem(ProxyControl): #: Reference to the declaration declaration = ForwardInstance(lambda: AbstractWidgetItem) def set_row(self, row): pass def set_column(self, column): pass def set_text(self, text): pass def set_text_alignment(self, text_alignment): pass def set_icon(self, icon): pass def set_icon_size(self, size): pass def set_editable(self, editable): pass def set_checkable(self, checkable): pass
class Page(NoSQLModel): title = Str() status = Enum("preview", "live") body = Str() author = Instance(User) images = List(Image) related = List(ForwardInstance(lambda: Page))
class AbstractQtWidgetItem(AbstractQtWidgetItemGroup, ProxyAbstractWidgetItem): #: is_destroyed = Bool() #: Index within the view index = Instance(QModelIndex) #: Delegate widget to display when editing the cell #: if the widget is editable delegate = Instance(QtWidget) #: Reference to view view = ForwardInstance(_abstract_item_view) def create_widget(self): # View items have no widget! for child in self.children(): if isinstance(child, (Pattern, QtWidget)): self.delegate = child def init_widget(self): pass def init_layout(self): super(AbstractQtWidgetItem, self).init_layout() self._update_index() def _update_index(self): """ Update where this item is within the model""" raise NotImplementedError def destroy(self): """ Set the flag so we know when this item is destroyed """ self.is_destroyed = True super(AbstractQtWidgetItem, self).destroy()
class ExecutableGraph(model.Graph): controller = ForwardInstance(_import_graph_calculator_controller) nxgraph = Property(lambda self: self._get_nxgraph(), cached=True) topologyChanged = Event() valuesChanged = Event() attributesChanged = Event() def _get_nxgraph(self): g = nx.MultiDiGraph() for node in self.nodes: g.add_node(node.id, id=node.id, name=node.name) for edge in self.edges: g.add_edge(edge.start_socket.node.id, edge.end_socket.node.id, id=edge.id, source_socket=edge.start_socket.name, target_socket=edge.end_socket.name) return g def _observe_topologyChanged(self, change): self.get_member('nxgraph').reset(self) self.execute_graph() def _observe_valuesChanged(self, change): self.execute_graph() def _observe_attributesChanged(self, change): self.execute_graph() def execute_graph(self): for node_id in nx.topological_sort(self.nxgraph): self.node_dict[node_id].update()
class Bookmark(Atom): #: Name of it name = Unicode() #: Bible bible = ForwardInstance(lambda: Bible) #: Book book = Instance(Book) #: Chapter chapter = Instance(Chapter) #: Verse #verse = Instance(Verse) #: Save / load state state = Dict() def __init__(self, *args, **kwargs): super(Bookmark, self).__init__(*args, **kwargs) if kwargs.get('state') is None: #: If we're not loading from state self.state = { 'bible': self.bible.version.key, 'book': self.book.name, 'chapter': self.chapter.number, #'verse': self.verse.number } def _default_name(self): return u"{} {}".format(self.book.name, self.chapter.number) def _default_bible(self): try: #: Prevent loading two bibles if it was bookmarked in a different bible bible = AppState.instance().bible if bible is not None: return bible return AppState.instance().get_bible(self.state['bible']) except KeyError: return None def _default_book(self): if not self.bible: return None try: return self.bible.get_book(self.state['book']) except KeyError: return None def _default_chapter(self): if not self.book: return None try: #: They're supposed to be in order return self.book.chapters[self.state['chapter'] - 1] except KeyError: return None
class DeviceTransport(Model): #: The declaration that defined this transport declaration = Typed(extensions.DeviceTransport).tag(config=True) #: The transport specific config config = Instance(Model, ()).tag(config=True) #: The active protocol protocol = ForwardInstance(lambda: DeviceProtocol).tag(config=True) #: Connection state. Subclasses must implement and properly update this connected = Bool() #: Distinguish between transports that always spool (e.g. Printer, File I/O) #: or are dependent on the 'spooling' configuration option (e.g. Serial) always_spools = Bool() #: Most recent input/output. These can be observed to update the UI last_read = Bytes() last_write = Bytes() def __init__(self, *args, **kwargs): super(DeviceTransport, self).__init__(*args, **kwargs) if self.protocol: self.protocol.transport = self def _observe_protocol(self, change): """ Whenever the protocol changes update the transport reference """ if (change['type'] == 'update' or change['type'] == 'create') and change['value']: self.protocol.transport = self def connect(self): """ Connect using whatever implementation necessary """ raise NotImplementedError def write(self, data): """ Write using whatever implementation necessary """ raise NotImplementedError def read(self, size=None): """ Read using whatever implementation necessary and invoke `protocol.data_received` with the output. """ raise NotImplementedError def disconnect(self): """ Disconnect using whatever implementation necessary """ raise NotImplementedError
class Edge(GraphItem): id = Str() graph = ForwardInstance(import_graph_type) start_socket = ForwardInstance(import_socket_type) end_socket = ForwardInstance(import_socket_type) edge_type = Typed(EdgeType) def _default_edge_type(self): return EdgeType.EDGE_TYPE_BEZIER def _observe_start_socket(self, change): if change.get('oldvalue', None) is not None: s = change['oldvalue'] if self in s.edges: s.edges.remove(self) if change['value'] is not None: change['value'].edges.append(self) if self.end_socket is not None and change[ 'value'].data_type != self.end_socket.data_type: raise TypeError( "Incompatible type for connection - %s->%s" % (change['value'].data_type, self.end_socket.data_type)) def _observe_end_socket(self, change): if change.get('oldvalue', None) is not None: s = change['oldvalue'] if self in s.edges: s.edges.remove(self) if change['value'] is not None: change['value'].edges.append(self) if self.start_socket is not None and change[ 'value'].data_type != self.start_socket.data_type: raise TypeError( "Incompatible type for connection - %s->%s" % (self.start_socket.data_type, change['value'].data_type)) @property def data_type(self): return getattr(self.start_socket, "data_type", getattr(self.end_socket, "data_type", "")) @property def is_open(self): return self.end_socket is None or self.start_socket is None
class QtPlotItem3D(QtPlotItem2D): """Use forward instance to not cause import issues if not installed.""" widget = ForwardInstance(gl_view_widget) def create_widget(self): from pyqtgraph.opengl import GLViewWidget if isinstance(self.parent(), AbstractQtPlotItem): self.widget = self.parent_widget() else: self.widget = GLViewWidget(parent=self.parent_widget()) self.widget.opts["distance"] = 40 self.widget.raise_() def init_signals(self): pass def _create_grid(self): from pyqtgraph.opengl import GLGridItem gx = GLGridItem() gx.rotate(90, 0, 1, 0) gx.translate(-10, 0, 0) self.widget.addItem(gx) gy = GLGridItem() gy.rotate(90, 1, 0, 0) gy.translate(0, -10, 0) self.widget.addItem(gy) gz = GLGridItem() gz.translate(0, 0, -10) self.widget.addItem(gz) def set_z(self, z): self._refresh_plot() def _refresh_plot(self): import numpy as np # import pyqtgraph as pg from pyqtgraph import opengl as gl self._create_grid() pts = np.vstack( [self.declaration.x, self.declaration.y, self.declaration.z]).transpose() plt = gl.GLLinePlotItem( pos=pts ) # , color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True) self.widget.addItem(plt) def set_grid(self, grid): pass
class HttpRequest(Atom): """ The request object created for fetch calls. It's based on the design of Tornado's HttpRequest. """ #: Request url url = Unicode() #: Request method method = Unicode('get') #: Request headers headers = Dict() #: Retry count retries = Int() #: Request parameter data data = Dict() #: Content type content_type = Unicode("application/x-www-urlencoded") #: Raw request body body = Unicode() #: Response created response = ForwardInstance(lambda: HttpResponse) #: Called when complete callback = Callable() #: Streaming callback streaming_callback = Callable() #: Start time start_time = Float() def __init__(self, *args, **kwargs): """ Build the request as configured. """ super(HttpRequest, self).__init__(*args, **kwargs) self.start_time = time.time() self.response = HttpResponse(request=self) self.init_request() def init_request(self): """ Initialize the request using whatever native means necessary """ raise NotImplementedError
class Fit1D(FitObject): 'class for fitting 1d datasets' parent = ForwardInstance(lambda: ap.data.XYDataObject) plot = ForwardInstance(lambda: ap.plot.Plot1D) fitted = Bool( default=False ) # boolean which indicates if current model and data are fitted _model = Value() result = Value() _fit = Typed(Fit) def __init__(self, parent, *args, **kwargs): self.parent = parent super(Fit1D, self).__init__(*args, **kwargs) self.result = None def add_model(self, model): if self._model: del self._model if isinstance(model, str): self._model = get_model(model, self.parent.x, self.parent.y) else: self._model = model self.fitted = False @observe('parent.x', 'parent.x_updated', 'parent.y', 'parent.y_updated') def _data_updated(self, change): self.fitted = False def execute(self, *options, **kwoptions): self._fit = Fit(self._model, self.parent.x, self.parent.y) self.result = self._fit.execute(*options, **kwoptions)
class VTKCanvas(Control): """ A control which can be used to embded vtk renderers. """ #: The vtk renderer to display in the window. This should be used #: if only a single renderer is required for the scene. renderer = d_(ForwardInstance(vtkRenderer)) #: The list of vtk renderers to display in the window. This should #: be used if multiple renderers are required for the scene. renderers = d_(List(ForwardInstance(vtkRenderer))) #: A VTKCanvas expands freely in height and width by default. hug_width = set_default('ignore') hug_height = set_default('ignore') #: A reference to the ProxyVTKCanvas object. proxy = Typed(ProxyVTKCanvas) def render(self): """ Request a render of the underlying scene. """ if self.proxy_is_active: self.proxy.render() #-------------------------------------------------------------------------- # Observers #-------------------------------------------------------------------------- @observe('renderer', 'renderers') def _update_proxy(self, change): """ An observer which sends state change to the proxy. """ # The superclass handler implementation is sufficient. super(VTKCanvas, self)._update_proxy(change)
class Page(SQLModel): title = Str().tag(length=60) status = Enum("preview", "live") body = Str().tag(type=sa.UnicodeText()) author = Instance(User) if DATABASE_URL.startswith("postgres"): images = List(Instance(Image)) related = List(ForwardInstance(lambda: Page)).tag(nullable=True) tags = List(str) visits = BigInt() date = Instance(date) last_updated = Instance(datetime) rating = Instance(Decimal) ranking = Float().tag(name="order") # A bit verbose but provides a custom column specification data = Instance(object).tag(column=sa.Column("data", sa.LargeBinary()))
class UIView(UIResponder): """ From: https://developer.apple.com/documentation/uikit/uiview?language=objc """ #__signature__ = set_default((dict(initWithFrame='CGRect'),)) yoga = ForwardInstance(yoga_class) def _default_yoga(self): return yoga_class()(self, 'yoga') #: Properties backgroundColor = ObjcProperty('UIColor') hidden = ObjcProperty('bool') alpha = ObjcProperty('float') opaque = ObjcProperty('bool') tintColor = ObjcProperty('UIColor') tintAdjustmentMode = ObjcProperty('UIViewTintAdjustmentMode') clipsToBounds = ObjcProperty('bool') clearsContextBeforeDrawing = ObjcProperty('bool') maskView = ObjcProperty('UIView') userInteractionEnabled = ObjcProperty('bool') multipleTouchEnabled = ObjcProperty('bool') exclusiveTouch = ObjcProperty('bool') frame = ObjcProperty('CGRect') bounds = ObjcProperty('CGRect') center = ObjcProperty('CGPoint') transform = ObjcProperty('CGAffineTransform') layoutMargins = ObjcProperty('UIEdgeInserts') preservesSuperviewLayoutMargins = ObjcProperty('bool') #: Methods addSubview = ObjcMethod('UIView') bringSubviewToFront = ObjcMethod('UIView') sendSubviewToBack = ObjcMethod('UIView') removeFromSuperview = ObjcMethod() insertSubview = ObjcMethod( 'UIView', dict(atIndex='NSInteger', aboveSubview='UIView', belowSubview='UIView')) exchangeSubviewAtIndex = ObjcMethod('NSInteger', dict(withSubviewAtIndex='NSInteger'))
class AtomAxes(Atom): """axis for matplot, has reference to which plots, takes care of updateing etc""" mp_parent = ForwardInstance( lambda: MatPlot) #todo figure out if its needed axes = Instance(Axes) data_objects = Dict(default={}) def __init__(self, mp_parent, axes): self.mp_parent = mp_parent self.axes = axes super(AtomAxes, self).__init__() def __add__(self, other): #add a dataobject to these axes self.add_dataobject(other) def __sub__(self, other): # remove a dataobject from these axes self.remove_dataobject(other) def add_dataobject(self, data_obj): uid = id(data_obj) if uid in self.data_objects: raise KeyError( 'Data object already added') #todo print the label and s***e self.data_objects[uid] = data_obj data_obj.plot.line = self.axes.plot(data_obj.plot.x, data_obj.plot.y)[0] data_obj.plot.observe('updated', self.redraw_plot) def remove_dataobject(self, uid): assert type(uid) == int data_obj = self.data_objects.pop(uid) data_obj.plot.unobserve('updated') def redraw_plot(self, change): print(change) print('redrawing') self.axes.relim() self.axes.autoscale_view()
class Pipe(Operation): #: Reference to the implementation control proxy = Typed(ProxyPipe) #: Spline to make the pipe along spline = d_(Instance(Shape)) #: Profile to make the pipe from profile = d_(ForwardInstance(WireFactory)) #: Fill mode fill_mode = d_( Enum(None, 'corrected_frenet', 'fixed', 'frenet', 'constant_normal', 'darboux', 'guide_ac', 'guide_plan', 'guide_ac_contact', 'guide_plan_contact', 'discrete_trihedron')).tag(view=True, group='Pipe') @observe('spline', 'profile', 'fill_mode') def _update_proxy(self, change): super(Pipe, self)._update_proxy(change)
class DeviceTransport(Model): #: The declaration that defined this transport declaration = Typed(extensions.DeviceTransport).tag(config=True) #: The transport specific config config = Instance(Model, ()).tag(config=True) #: The active protocol protocol = ForwardInstance(lambda: DeviceProtocol).tag(config=True) #: Connection state. Subclasses must implement and properly update this connected = Bool() #: Distinguish between transports that always spool (e.g. Printer, File I/O) #: or are dependent on the 'spooling' configuration option (e.g. Serial) always_spools = Bool() def connect(self): """ Connect using whatever implementation necessary """ raise NotImplementedError def write(self, data): """ Write using whatever implementation necessary """ raise NotImplementedError def read(self, size=None): """ Read using whatever implementation necessary. """ raise NotImplementedError def disconnect(self): """ Disconnect using whatever implementation necessary """ raise NotImplementedError
class Block(Declarative): """ An object which dynamically insert's its children into another block's parent object. The 'Block' object is used to cleanly and easily insert it's children into the children of another object. The 'Object' instance assigned to the 'object' property of the 'Block' will be parented with the parent of the 'Include'. Creating an 'Include' with no parent is a programming error. """ #: The Block to which this blocks children should be inserted into block = d_(ForwardInstance(lambda: Block)) def initialize(self): """ A reimplemented initializer. This method will add the include objects to the parent of the include and ensure that they are initialized. """ super(Block, self).initialize() if self.block: self.block.parent.insert_children(self.block, self.children) def _observe_block(self, change): """ A change handler for the 'objects' list of the Include. If the object is initialized objects which are removed will be unparented and objects which are added will be reparented. Old objects will be destroyed if the 'destroy_old' flag is True. """ if self.is_initialized: if change['type'] == 'update': old_block = change['oldvalue'] old_block.parent.remove_children(old_block, self.children) new_block = change['value'] new_block.parent.insert_children(new_block, self.children)
class Pipe(Operation): """ An operation that extrudes a profile along a spline, wire, or path. Attributes ---------- spline: Edge or Wire The spline to extrude along. profile: Wire The profile to extrude. fill_mode: String, optional The fill mode to use. Examples -------- See examples/pipes.enaml """ #: Reference to the implementation control proxy = Typed(ProxyPipe) #: Spline to make the pipe along spline = d_(ForwardInstance(WireFactory)) #: Profile to make the pipe from profile = d_(Instance(Shape)) #: Fill mode fill_mode = d_( Enum(None, 'corrected_frenet', 'fixed', 'frenet', 'constant_normal', 'darboux', 'guide_ac', 'guide_plan', 'guide_ac_contact', 'guide_plan_contact', 'discrete_trihedron')).tag(view=True, group='Pipe') @observe('spline', 'profile', 'fill_mode') def _update_proxy(self, change): super(Pipe, self)._update_proxy(change)
class Dataset(Atom): """Represent a node in the data store.""" #: Custom metadata attached to the node. metadata = Dict(str) def __getitem__(self, key: str) -> Union[DataArray, "Dataset"]: return self._data[key] def __contains__(self, key: str) -> bool: return key in self._data def __iter__(self) -> Iterator[str]: return iter(self._data) def values(self) -> Iterator[Union[DataArray, "Dataset"]]: """Iterable on the values stored.""" return iter(self._data.values()) def items(self) -> Iterator[Tuple[str, Union[DataArray, "Dataset"]]]: """Iterable on the keys and values stored.""" return iter(self._data.items()) _data = Dict(str, ForwardInstance(lambda: (DataArray, Dataset)))
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)
class DevServerSession(Atom): """ Connect to a dev server running on the LAN or if host is 0.0.0.0 server a page to let code be pasted in. Note this should NEVER be used in a released app! """ #: Singleton Instance of this class _instance = None #: Reference to the current Application app = ForwardInstance(get_app) #: Host to connect to (in client mode) or #: if set to "server" it will enable "server" mode host = Unicode() #: Port to serve on (in server mode) or port to connect to (in client mode) port = Int(8888) #: URL to connect to (in client mode) url = Unicode('ws://192.168.21.119:8888/dev') #: Websocket connection state connected = Bool() #: Message buffer buf = Unicode() #: Dev session mode mode = Enum('client', 'server', 'remote') #: Hotswap support class hotswap = Instance(Hotswapper) #: Delegate dev server servers = List(Subclass(DevServer), default=[ TornadoDevServer, TwistedDevServer, ]) server = Instance(DevServer) #: Delegate dev client clients = List(Subclass(DevClient), default=[ TornadoDevClient, TwistedDevClient, ]) client = Instance(DevClient) # ------------------------------------------------------------------------- # Initialization # ------------------------------------------------------------------------- @classmethod def initialize(cls, *args, **kwargs): """ Create an instance of this class. """ try: return DevServerSession(*args, **kwargs) except ImportError: pass @classmethod def instance(cls): """ Get the singleton instance """ return cls._instance def __init__(self, *args, **kwargs): """ Overridden constructor that forces only one instance to ever exist. """ if self.instance() is not None: raise RuntimeError("A DevServerClient instance already exists!") super(DevServerSession, self).__init__(*args, **kwargs) DevServerSession._instance = self def start(self): """ Start the dev session. Attempt to use tornado first, then try twisted """ print("Starting debug client cwd: {}".format(os.getcwd())) print("Sys path: {}".format(sys.path)) #: Initialize the hotswapper self.hotswap = Hotswapper(debug=False) if self.mode == 'server': self.server.start(self) else: self.client.start(self) # ------------------------------------------------------------------------- # Defaults # ------------------------------------------------------------------------- def _default_mode(self): """ If host is set to server then serve it from the app! """ host = self.host if host == 'server': return 'server' elif host == 'remote': return 'remote' return 'client' def _default_url(self): """ Websocket URL to connect to and listen for reload requests """ host = 'localhost' if self.mode == 'remote' else self.host return 'ws://{}:{}/dev'.format(host, self.port) def _default_app(self): """ Application instance """ return get_app().instance() def _default_server(self): for Server in self.servers: if Server.available(): return Server() raise NotImplementedError( "No dev servers are available! " "Include tornado or twisted in your requirements!") def _default_client(self): for Client in self.clients: if Client.available(): return Client() raise NotImplementedError( "No dev clients are available! " "Include tornado or twisted in your requirements!") def _observe_connected(self, change): """ Log connection state changes """ print("Dev session {}".format( "connected" if self.connected else "disconnected")) # ------------------------------------------------------------------------- # Dev Session API # ------------------------------------------------------------------------- def write_message(self, data, binary=False): """ Write a message to the active client """ self.client.write_message(data, binary=binary) def handle_message(self, data): """ When we get a message """ msg = json.loads(data) print("Dev server message: {}".format(msg)) handler_name = 'do_{}'.format(msg['type']) if hasattr(self, handler_name): handler = getattr(self, handler_name) result = handler(msg) return {'ok': True, 'result': result} else: err = "Warning: Unhandled message: {}".format(msg) print(err) return {'ok': False, 'message': err} # ------------------------------------------------------------------------- # Message handling API # ------------------------------------------------------------------------- def do_reload(self, msg): """ Called when the dev server wants to reload the view. """ #: TODO: This should use the autorelaoder app = self.app #: Show loading screen try: self.app.widget.showLoading("Reloading... Please wait.", now=True) #self.app.widget.restartPython(now=True) #sys.exit(0) except: #: TODO: Implement for iOS... pass self.save_changed_files(msg) if app.load_view is None: print("Warning: Reloading the view is not implemented. " "Please set `app.load_view` to support this.") return if app.view is not None: try: app.view.destroy() except: pass def wrapped(f): def safe_reload(*args, **kwargs): try: return f(*args, **kwargs) except: #: Display the error app.send_event(Command.ERROR, traceback.format_exc()) return safe_reload app.deferred_call(wrapped(app.load_view), app) def do_hotswap(self, msg): """ Attempt to hotswap the code """ #: Show hotswap tooltip try: self.app.widget.showTooltip("Hot swapping...", now=True) except: pass self.save_changed_files(msg) hotswap = self.hotswap app = self.app try: print("Attempting hotswap....") with hotswap.active(): hotswap.update(app.view) except: #: Display the error app.send_event(Command.ERROR, traceback.format_exc()) # ------------------------------------------------------------------------- # Utility methods # ------------------------------------------------------------------------- def save_changed_files(self, msg): #: On iOS we can't write in the app bundle if os.environ.get('TMP'): tmp_dir = os.environ['TMP'] if not os.path.exists(tmp_dir): os.makedirs(tmp_dir) if tmp_dir not in sys.path: sys.path.insert(0, tmp_dir) import site reload(site) with cd(sys.path[0]): #: Clear cache if os.path.exists('__enamlcache__'): shutil.rmtree('__enamlcache__') for fn in msg['files']: print("Updating {}".format(fn)) folder = os.path.dirname(fn) if folder and not os.path.exists(folder): os.makedirs(folder) with open(fn, 'wb') as f: f.write(msg['files'][fn].encode('utf-8'))
}], [{ 1: 2 }], [{ 2: '' }]), (Dict(int, int), [{ 1: 2 }], [{ 1: 2 }], [{ '': 2 }, { 2: '' }]), (Instance((int, float)), [1, 2.0, None], [1, 2.0, None], ['']), (ForwardInstance(lambda: (int, float)), [1, 2.0], [1, 2.0], ['']), (Typed(float), [1.0, None], [1.0, None], [1]), (ForwardTyped(lambda: float), [1.0], [1.0], [1]), (Subclass(CAtom), [Atom], [Atom], [int, 1]), (Subclass((CAtom, float)), [Atom], [Atom], [int, 1]), (ForwardSubclass(lambda: CAtom), [Atom], [Atom], [int]), ] + ([(Range(sys.maxsize, sys.maxsize + 2), [sys.maxsize, sys.maxsize + 2], [sys.maxsize, sys.maxsize + 2], [sys.maxsize - 1, sys.maxsize + 3])] if sys.version_info > (3, ) else [])) def test_validation_modes(member, set_values, values, raising_values): """Test the validation modes. """ class MemberTest(Atom):
class Sensor(JavaBridgeObject): """ A wrapper for an Android sensor. This should be retrieved using the `Sensor.get(sensor_type)` method. Examples -------- # In a function with an @inlineCallbacks or @coroutine decorator def on_data(data): # Handle data here... # data is a dict with keys # {'acc': <accuracy>, 'data': [v1, v2, ...], 'sensor': id, 'time': t} def on_ready(sensor): if sensor is not None: sensor.start(callback=on_data) # ... sensor.stop() """ __nativeclass__ = set_default('android.hardware.Sensor') #: Reference to the sensor manager manager = ForwardInstance(lambda: SensorManager) #: Sensor type type = Int() TYPE_ACCELEROMETER = 1 TYPE_MAGNETIC_FIELD = 2 TYPE_ORIENTATION = 3 # Depreciated TYPE_GYROSCOPE = 4 TYPE_LIGHT = 5 TYPE_PRESSURE = 6 TYPE_TEMPERATURE = 7 # Depreciated Use ambient temp instead TYPE_PROXIMITY = 8 TYPE_GRAVITY = 9 TYPE_LINEAR_ACCELERATION = 10 TYPE_ROTATION_VECTOR = 11 TYPE_RELATIVE_HUMIDITY = 12 TYPE_AMBIENT_TEMPERATURE = 13 TYPE_MAGNETIC_FIELD_UNCALIBRATED = 14 TYPE_GAME_ROTATION_VECTOR = 15 TYPE_GYROSCOPE_UNCALIBRATED = 16 TYPE_SIGNIFICANT_MOTION = 17 TYPE_STEP_DETECTOR = 18 TYPE_STEP_COUNTER = 19 TYPE_GEOMAGNETIC_ROTATION_VECTOR = 20 TYPE_HEART_RATE = 21 TYPE_POSE_6DOF = 28 # Requires API 24+ TYPE_STATIONARY_DETECT = 29 # Requires API 24+ TYPE_MOTION_DETECT = 30 # Requires API 24+ TYPE_HEART_BEAT = 31 # Requires API 24+ TYPE_LOW_LATENCY_OFFBODY_DETECT = 34 # Requires API 26+ SENSOR_DELAY_NORMAL = 3 SENSOR_DELAY_UI = 2 SENSOR_DELAY_GAME = 1 SENSOR_DELAY_FASTEST = 0 getFifoMaxEventCount = JavaMethod(returns='int') getFifoReservedEventCount = JavaMethod(returns='int') getMaximumRange = JavaMethod(returns='float') getMaxDelay = JavaMethod(returns='int') getMinDelay = JavaMethod(returns='int') getName = JavaMethod(returns='java.lang.String') getPower = JavaMethod(returns='float') getReportingMode = JavaMethod(returns='int') getResolution = JavaMethod(returns='float') getVendor = JavaMethod(returns='java.lang.String') getVersion = JavaMethod(returns='int') getType = JavaMethod(returns='int') getStringType = JavaMethod(returns='java.lang.String') isWakeUpSensor = JavaMethod(returns='boolean') # ------------------------------------------------------------------------- # SensorEventListener API # ------------------------------------------------------------------------- onSensorChanged = JavaCallback('android.hardware.SensorEvent') onAccuracyChanged = JavaCallback('android.hardware.Sensor', 'int') @classmethod def get(cls, sensor_type): """ Shortcut that acquires the default Sensor of a given type. Parameters ---------- sensor_type: int Type of sensor to get. Returns ------- result: Future A future that resolves to an instance of the Sensor or None if the sensor is not present or access is not allowed. """ app = AndroidApplication.instance() f = app.create_future() def on_sensor(sid, mgr): if sid is None: f.set_result(None) else: f.set_result(Sensor(__id__=sid, manager=mgr, type=sensor_type)) SensorManager.get().then(lambda sm: sm.getDefaultSensor(sensor_type). then(lambda sid, sm=sm: on_sensor(sid, sm))) return f def start(self, callback, rate=SENSOR_DELAY_NORMAL): """ Start listening to sensor events. Sensor event data depends on the type of sensor that was given to Parameters ---------- callback: Callable A callback that takes one argument that will be passed the sensor data. Sensor data is a dict with data based on the type of sensor. rate: Integer How fast to update. One of the Sensor.SENSOR_DELAY values Returns ------- result: Future A future that resolves to whether the register call completed. """ if not self.manager: raise RuntimeError( "Cannot start a sensor without a SensorManager!") self.onSensorChanged.connect(callback) return self.manager.registerListener(self.getId(), self, rate) def stop(self): """ Stop listening to sensor events. This should be done in on resume. """ self.manager.unregisterListener(self.getId(), self)
class BridgeObject(Atom): """ A proxy to a class in java. This sends the commands over the bridge for execution. The object is stored in a map with the given id and is valid until this object is deleted. Parameters ---------- __id__: Int If an __id__ keyward argument is passed during creation, this will assume the object was already created and only a reference to the object with the given id is needed. """ __slots__ = ('__weakref__', ) #: Native Class name __nativeclass__ = Unicode() #: Constructor signature __signature__ = Tuple() #: Suppressed methods / fields __suppressed__ = Dict() #: Callbacks __callbacks__ = Dict() #: Bridge object ID __id__ = Int(0, factory=generate_id) #: ID of this class __bridge_id__ = Int() #: Prefix to add to all names used during method and property calls #: used for nested objects __prefix__ = Unicode() #: Bridge __app__ = ForwardInstance(get_app_class) def _default___app__(self): return get_app_class().instance() def _default___bridge_id__(self): cls = self.__class__ if cls not in CLASS_CACHE: CLASS_CACHE[cls] = generate_property_id() return CLASS_CACHE[cls] def getId(self): return self.__id__ def __init__(self, *args, **kwargs): """ Sends the event to create this View in Java """ super(BridgeObject, self).__init__(**kwargs) #: Send the event over the bridge to construct the view __id__ = kwargs.get('__id__', None) CACHE[self.__id__] = self if __id__ is None: self.__app__.send_event( Command.CREATE, #: method self.__id__, #: id to assign in bridge cache self.__bridge_id__, self.__nativeclass__, [ msgpack_encoder(sig, arg) for sig, arg in zip(self.__signature__, args) ], ) def __del__(self): """ Destroy this object and send a command to destroy the actual object reference the bridge implementation holds (allowing it to be released). """ self.__app__.send_event( Command.DELETE, #: method self.__id__, #: id to assign in java ) _cleanup_id(self)
class Block(Declarative): """ An object which dynamically insert's its children into another block's parent object. The 'Block' object is used to cleanly and easily insert it's children into the children of another object. The 'Object' instance assigned to the 'block' property of the 'Block' will be parented with the parent of the 'Block'. Creating a 'Block' with no parent is a programming error. """ #: The Block to which this blocks children should be inserted into block = d_(ForwardInstance(lambda: Block)) #: If replace, replace all parent's children (except the block of course) mode = d_(Enum('replace', 'append')) def initialize(self): """ A reimplemented initializer. This method will add the include objects to the parent of the include and ensure that they are initialized. """ super(Block, self).initialize() if self.block: #: This block is setting the content of another block #: Remove the existing blocks children if self.mode == 'replace': #: Clear the blocks children for c in self.block.children: c.destroy() #: Add this blocks children to the other block self.block.insert_children(None, self.children) else: #: This block is inserting it's children into it's parent self.parent.insert_children(self, self.children) def _observe_block(self, change): """ A change handler for the 'objects' list of the Include. If the object is initialized objects which are removed will be unparented and objects which are added will be reparented. Old objects will be destroyed if the 'destroy_old' flag is True. """ if self.is_initialized: if change['type'] == 'update': raise NotImplementedError old_block = change['oldvalue'] old_block.parent.remove_children(old_block, self.children) new_block = change['value'] new_block.parent.insert_children(new_block, self.children) def _observe__children(self, change): if not self.is_initialized: return if change['type'] == 'update': if self.block: if self.mode == 'replace': self.block.children = change['value'] else: for c in change['oldvalue']: self.block.children.remove(c) c.destroy() before = self.block.children[ -1] if self.block.children else None self.block.insert_children(before, change['value']) else: for c in change['oldvalue']: if c not in change['value']: c.destroy() self.parent.insert_children(self, change['value'])
class BridgeObject(Atom): """A proxy to a class in java. This sends the commands over the bridge for execution. The object is stored in a map with the given id and is valid until this object is deleted. Parameters ---------- __id__: Int, Future, or None If an __id__ keyword argument is passed during creation, then If the __id__ is an int, this will assume the object was already created and only a reference to the object with the given id is needed. If the __id__ is a Future (as specified by the app event loop), then the __id__ of he future will be used. When the future completes this object will then be put into the cache. This allows passing results directly instead of using the `.then()` method. """ __slots__ = ("__weakref__", "__constructor_ids__") #: Native Class name __nativeclass__: ClassVar[str] = "" #: Constructor signature __signature__: ClassVar[list[Union[dict, str, "BridgeObject", type]]] = [] #: Constructor cache id __constructor_ids__: ClassVar[list[int]] #: Suppressed methods / fields __suppressed__ = Dict() #: Callbacks __callbacks__ = Dict() #: Bridge object ID __id__ = Int(0, factory=generate_id) #: Prefix to add to all names used during method and property calls #: used for nested objects __prefix__ = Str() #: Bridge __app__ = ForwardInstance(get_app_class) @classmethod def __init_subclass__(cls, *args, **kwargs): #: Convert signature to string cls.__signature__ = [convert_arg(arg) for arg in cls.__signature__] # Create a method id for each signature length as some may be # optional. TODO: Rethink this... n = max(1, len(cls.__signature__)) cls.__constructor_ids__ = [method_id() for i in range(n)] REGISTRY[cls.__nativeclass__] = cls def _default___app__(self): return get_app_class().instance() def getId(self): return self.__id__ def __init__(self, *args, **kwargs): """Sends the event to create this object in Java.""" #: Send the event over the bridge to construct the view __id__ = kwargs.pop("__id__", None) cache = True if __id__ is not None: if isinstance(__id__, int): kwargs["__id__"] = __id__ elif isinstance(__id__, Future): #: If a future is given don't store this object in the cache #: until after the future completes f = __id__ def set_result(f): CACHE[f.__id__] = self f.add_done_callback(set_result) #: The future is used to return the result kwargs["__id__"] = f.__id__ #: Save it into the cache when the result from the future #: arrives over the bridge. cache = False else: raise TypeError("Invalid __id__ reference, expected an int or" f"a future/deferred object, got: {__id__}") #: Construct the object super().__init__(**kwargs) if cache: CACHE[self.__id__] = self if __id__ is None: if args: constructor_id = self.__constructor_ids__[len(args) - 1] else: constructor_id = self.__constructor_ids__[0] self.__app__.send_event( Command.CREATE, #: method self.__id__, #: id to assign in bridge cache constructor_id, self.__nativeclass__, [ msgpack_encoder(sig, arg) for sig, arg in zip(self.__signature__, args) ], ) def __del__(self): """Destroy this object and send a command to destroy the actual object reference the bridge implementation holds (allowing it to be released). """ ref = self.__id__ self.__app__.send_event(Command.DELETE, ref) try: del CACHE[ref] except KeyError: pass
typed.set_index(5) cl2 = typed.clone() assert cl2.index == typed.index validators = ([typed.item] if hasattr(typed, 'item') else typed.validate_mode[1]) c_validators = ([cl2.item] if hasattr(typed, 'item') else cl2.validate_mode[1]) for v, cv in zip(validators, c_validators): assert cv is not v assert isinstance(cv, type(v)) # XXX should the kwargs be copied rather than simply re-assigned @pytest.mark.parametrize( "member, cloned_attributes", [(ForwardSubclass(lambda: object), ['resolve']), (ForwardTyped(lambda: object, (1, ), {'a': 1}), ['resolve', 'args', 'kwargs']), (ForwardInstance(lambda: object, (1, ), {'a': 1}), ['resolve', 'args', 'kwargs'])]) def test_cloning_forward(member, cloned_attributes): """Test that subclasses of Member are properly cloned. """ member.set_index(5) clone = member.clone() assert clone.index == member.index for attr in cloned_attributes: assert getattr(clone, attr) is getattr(member, attr)
class ViewerProcess(ProcessLineReceiver): #: Window id obtained after starting the process window_id = Int() #: Process handle process = Instance(object) #: Reference to the plugin plugin = ForwardInstance(lambda: ViewerPlugin) #: Document document = ForwardInstance(document_type) #: Rendering error errors = Str() #: Process terminated intentionally terminated = Bool(False) #: Count restarts so we can detect issues with startup s restarts = Int() #: Max number it will attempt to restart max_retries = Int(20) #: ID count _id = Int() #: Holds responses temporarily _responses = Dict() #: Seconds to ping _ping_rate = Int(40) #: Capture stderr separately err_to_out = set_default(False) def redraw(self): if self.document: # Trigger a reload self.document.version += 1 else: self.set_version(self._id) @observe('document', 'document.version') def _update_document(self, change): doc = self.document if doc is None: self.set_filename('-') else: self.set_filename(doc.name) self.set_version(doc.version) def send_message(self, method, *args, **kwargs): # Defer until it's ready if not self.transport or not self.window_id: #log.debug('renderer | message not ready deferring') timed_call(1000, self.send_message, method, *args, **kwargs) return _id = kwargs.pop('_id') _silent = kwargs.pop('_silent', False) request = { 'jsonrpc': '2.0', 'method': method, 'params': args or kwargs } if _id is not None: request['id'] = _id if not _silent: log.debug(f'renderer | sent | {request}') encoded_msg = jsonpickle.dumps(request).encode() + b'\r\n' deferred_call(self.transport.write, encoded_msg) async def start(self): atexit.register(self.terminate) cmd = [sys.executable] if not sys.executable.endswith('declaracad'): cmd.extend(['-m', 'declaracad']) cmd.extend(['view', '-', '-f']) loop = asyncio.get_event_loop() self.process = await loop.subprocess_exec(lambda: self, *cmd) return self.process def restart(self): self.window_id = 0 self.restarts += 1 # TODO: 100 is probably excessive if self.restarts > self.max_retries: plugin = self.plugin plugin.workbench.message_critical( "Viewer failed to start", "Could not get the viewer to start after several attempts.") raise RuntimeError( "renderer | Failed to successfully start renderer aborting!") log.debug(f"Attempting to restart viewer {self.process}") deferred_call(self.start) def connection_made(self, transport): super().connection_made(transport) self.schedule_ping() self.terminated = False def data_received(self, data): line = data.decode() try: response = jsonpickle.loads(line) # log.debug(f"viewer | resp | {response}") except Exception as e: log.debug(f"viewer | out | {line.rstrip()}") response = {} doc = self.document if not isinstance(response, dict): log.debug(f"viewer | out | {response.rstrip()}") return #: Special case for startup response_id = response.get('id') if response_id == 'window_id': self.window_id = response['result'] self.restarts = 0 # Clear the restart count return elif response_id == 'keep_alive': return elif response_id == 'invoke_command': command_id = response.get('command_id') parameters = response.get('parameters', {}) log.debug(f"viewer | out | {command_id}({parameters})") self.plugin.workbench.invoke_command(command_id, parameters) elif response_id == 'render_error': if doc: doc.errors.extend(response['error']['message'].split("\n")) return elif response_id == 'render_success': if doc: doc.errors = [] return elif response_id == 'capture_output': # Script output capture it if doc: doc.output = response['result'].split("\n") return elif response_id == 'shape_selection': #: TODO: Do something with this? if doc: doc.output.append(str(response['result'])) return elif response_id is not None: # Lookup the deferred object that should be stored for this id # when it is called and invoke the callback or errback based on the # result d = self._responses.get(response_id) if d is not None: del self._responses[response_id] try: error = response.get('error') if error is not None: if doc: doc.errors.extend( error.get('message', '').split("\n")) d.add_done_callback(error) else: d.add_done_callback(response.get('result')) return except Exception as e: log.warning("RPC response not properly handled!") log.exception(e) else: log.warning("Got unexpected reply") # else we got a response from something else, possibly an error? if 'error' in response and doc: doc.errors.extend(response['error'].get('message', '').split("\n")) doc.output.append(line) elif 'message' in response and doc: doc.output.extend(response['message'].split("\n")) elif doc: # Append to output doc.output.extend(line.split("\n")) def err_received(self, data): """ Catch and log error output attempting to decode it """ for line in data.split(b"\n"): if not line: continue if line.startswith(b"QWidget::") or line.startswith(b"QPainter::"): continue try: line = line.decode() log.debug(f"render | err | {line}") if self.document: self.document.errors.append(line) except Exception as e: log.debug(f"render | err | {line}") def process_ended(self, reason): log.warning(f"renderer | process ended: {reason}") if not self.terminated: # Clear the filename on crash so it works when reset #self.document = None self.restart() log.warning("renderer | stdout closed") def terminate(self): super(ViewerProcess, self).terminate() terminated = True def schedule_ping(self): """ Ping perioidcally so the process stays awake """ if self.terminated: return # Ping the viewer to tell it to keep alive self.send_message("ping", _id="keep_alive", _silent=True) timed_call(self._ping_rate * 1000, self.schedule_ping) def __getattr__(self, name): """ Proxy all calls not defined here to the remote viewer. This makes doing `setattr(renderer, attr, value)` get passed to the remote viewer. """ if name.startswith('set_'): def remote_viewer_call(*args, **kwargs): d = asyncio.Future() self._id += 1 kwargs['_id'] = self._id self._responses[self._id] = d self.send_message(name, *args, **kwargs) return d return remote_viewer_call raise AttributeError("No attribute %s" % name)