File: application.py

package info (click to toggle)
python-pyface 8.0.0-5
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 13,944 kB
  • sloc: python: 54,107; makefile: 82
file content (326 lines) | stat: -rw-r--r-- 10,717 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# (C) Copyright 2005-2023 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
"""
This module defines the :py:class:`Application` class for Pyface, Tasks
and similar applications.  Although the primary use cases are for GUI
applications, the :py:class:`Application` class does not have any explicit
dependency on GUI code, and can be used for CLI or server applications.

Usual usage is to subclass :py:class:`Application`, overriding at least the
:py:meth:`Application._run` method, but usually the
:py:meth:`Application.start` and :py:meth:`Application.stop`
methods as well.

However the class can be used as-is by listening to the
:py:attr:`Application.application_initialized` event and performing
appropriate work there::

    def do_work(event):
        print("Hello world")

    app = Application()
    app.observe(do_work, 'application_initialized')

"""


import logging
import os

from traits.api import (
    Directory,
    Event,
    HasStrictTraits,
    Instance,
    ReadOnly,
    Str,
    Vetoable,
    VetoableEvent,
)

logger = logging.getLogger(__name__)


class ApplicationException(Exception):
    """ Exception subclass for Application-centric exceptions """

    pass


class ApplicationExit(ApplicationException):
    """ Exception which indicates application should try to exit.

    If no arguments, then assumed to be a normal exit, otherwise the arguments
    give information about the problem.
    """

    pass


class ApplicationEvent(HasStrictTraits):
    """ An event associated with an application """

    #: The application that the event happened to.
    application = ReadOnly

    #: The type of application event.
    event_type = ReadOnly


class Application(HasStrictTraits):
    """ A base class for applications.

    This class handles the basic lifecycle of an application and a few
    fundamental facilities.  It is suitable as a base for any application,
    not just GUI applications.
    """

    # 'Application' traits ----------------------------------------------------

    # Branding ----------------------------------------------------------------

    #: Human-readable application name
    name = Str("Pyface Application")

    #: Human-readable company name
    company = Str()

    #: Human-readable description of the application
    description = Str()

    # Infrastructure ---------------------------------------------------------

    #: The application's globally unique identifier.
    id = Str()

    #: Application home directory (for preferences, logging, etc.)
    home = Directory()

    #: User data directory (for user files, projects, etc)
    user_data = Directory()

    # Application lifecycle --------------------------------------------------

    #: Fired when the application is starting. Called immediately before the
    #: start method is run.
    starting = Event(Instance(ApplicationEvent))

    #: Upon successful completion of the start method.
    started = Event(Instance(ApplicationEvent))

    #: Fired after the GUI event loop has been started during the run method.
    application_initialized = Event(Instance(ApplicationEvent))

    #: Fired when the application is starting. Called immediately before the
    #: stop method is run.
    exiting = VetoableEvent()

    #: Fired when the application is starting. Called immediately before the
    #: stop method is run.
    stopping = Event(Instance(ApplicationEvent))

    #: Upon successful completion of the stop method.
    stopped = Event(Instance(ApplicationEvent))

    # -------------------------------------------------------------------------
    # Application interface
    # -------------------------------------------------------------------------

    # Application lifecycle methods ------------------------------------------

    def start(self):
        """ Start the application, setting up things that are required

        Subclasses should call the superclass start() method before doing any
        work themselves.
        """
        return True

    def stop(self):
        """ Stop the application, cleanly releasing resources if possible.

        Subclasses should call the superclass stop() method after doing any
        work themselves.
        """
        return True

    def run(self):
        """ Run the application.

        Return
        ------
        status : bool
            Whether or not the application ran normally
        """
        run = stopped = False

        # Start up the application.
        logger.info("---- Application starting ----")
        self._fire_application_event("starting")
        started = self.start()
        if started:

            logger.info("---- Application started ----")
            self._fire_application_event("started")

            try:
                run = self._run()
            except ApplicationExit as exc:
                if exc.args == ():
                    logger.info("---- ApplicationExit raised ----")
                else:
                    logger.exception("---- ApplicationExit raised ----")
                run = exc.args == ()
            finally:
                # Try to shut the application down.
                logger.info("---- Application stopping ----")
                self._fire_application_event("stopping")
                stopped = self.stop()
                if stopped:
                    self._fire_application_event("stopped")
                    logger.info("---- Application stopped ----")

        return started and run and stopped

    def exit(self, force=False):
        """ Exits the application.

        This method handles a request to shut down the application by the user,
        eg. from a menu.  If force is False, the application can potentially
        veto the close event, leaving the application in the state that it was
        before the exit method was called.

        Parameters
        ----------
        force : bool, optional (default False)
            If set, windows will receive no closing events and will be
            destroyed unconditionally. This can be useful for reliably tearing
            down regression tests, but should be used with caution.

        Raises
        ------
        ApplicationExit
            Some subclasses may trigger the exit by raising ApplicationExit.
        """
        logger.info("---- Application exit started ----")
        if force or self._can_exit():
            try:
                self._prepare_exit()
            except Exception:
                logger.exception("Error preparing for application exit")
            finally:
                logger.info("---- Application exit ----")
                self._exit()
        else:
            logger.info("---- Application exit vetoed ----")

    # Initialization utilities -----------------------------------------------

    def initialize_application_home(self):
        """ Set up the home directory for the application

        This is where logs, preference files and other config files will be
        stored.
        """
        if not os.path.exists(self.home):
            logger.info("Application home directory does not exist, creating")
            os.makedirs(self.home)

    # -------------------------------------------------------------------------
    # Private interface
    # -------------------------------------------------------------------------

    # Main method -------------------------------------------------------------

    def _run(self):
        """ Actual implementation of running the application

        This should be completely overriden by applications which want to
        actually do something.  Usually this method starts an event loop and
        blocks, but for command-line applications this could be where the
        main application logic is called from.
        """
        # Fire a notification that the app is running.  If the app has an
        # event loop (eg. a GUI, Tornado web app, etc.) then this should be
        # fired _after_ the event loop starts using an appropriate callback
        # (eg. gui.set_trait_later).
        self._fire_application_event("application_initialized")
        return True

    # Utilities ---------------------------------------------------------------

    def _fire_application_event(self, event_type):
        event = ApplicationEvent(application=self, event_type=event_type)
        setattr(self, event_type, event)

    # Destruction methods -----------------------------------------------------

    def _can_exit(self):
        """ Is exit vetoed by anything?

        The default behaviour is to fire the :py:attr:`exiting` event and check
        to see if any listeners veto.  Subclasses may wish to override to
        perform additional checks.

        Returns
        -------
        can_exit : bool
            Return True if exit is OK, False if something vetoes the exit.
        """
        self.exiting = event = Vetoable()
        return not event.veto

    def _prepare_exit(self):
        """ Do any application-level state saving and clean-up

        Subclasses should override this method.
        """
        pass

    def _exit(self):
        """ Shut down the application

        This is where application event loops and similar should be shut down.
        """
        # invoke a normal exit from the application
        raise ApplicationExit()

    # Traits defaults ---------------------------------------------------------

    def _id_default(self):
        """ Use the application's directory as the id """
        from traits.etsconfig.api import ETSConfig

        return ETSConfig._get_application_dirname()

    def _home_default(self):
        """ Default home comes from ETSConfig. """
        from traits.etsconfig.api import ETSConfig

        return os.path.join(ETSConfig.application_data, self.id)

    def _user_data_default(self):
        """ Default user_data comes from ETSConfig. """
        from traits.etsconfig.api import ETSConfig

        return ETSConfig.user_data

    def _company_default(self):
        """ Default company comes from ETSConfig. """
        from traits.etsconfig.api import ETSConfig

        return ETSConfig.company

    def _description_default(self):
        """ Default description is the docstring of the application class. """
        from inspect import getdoc

        text = getdoc(self)
        return text