' in content
70 | else:
71 | global_toc = (
72 | '
'
75 | )
76 | local_toc = (
77 | '
'
79 | )
80 | assert global_toc in content
81 | assert local_toc not in content
82 | else:
83 | global_toc = '
'
88 | )
89 | assert global_toc in content
90 | assert local_toc not in content
91 |
92 |
93 | def test_missing_toctree():
94 | """Local TOC is showing, as toctree was missing"""
95 | for (app, status, warning) in build_all('test-missing-toctree'):
96 | assert app.env.get_doctree('index').traverse(addnodes.toctree) == []
97 | content = open(os.path.join(app.outdir, 'index.html')).read()
98 | assert '
' in content
100 |
--------------------------------------------------------------------------------
/tests/functional/test_unsubscribe_event.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 |
3 | import pytest
4 |
5 | from pubmarine import PubPen
6 |
7 |
8 | @pytest.fixture
9 | def pubpen(request, event_loop):
10 | pubpen = PubPen(event_loop)
11 | return pubpen
12 |
13 |
14 | class Function:
15 | def __init__(self):
16 | self.called = 0
17 |
18 | def __call__(self, *args):
19 | self.called += 1
20 |
21 |
22 | @pytest.fixture
23 | def function1(request):
24 | request.cls.function1 = Function()
25 |
26 |
27 | @pytest.fixture
28 | def function2(request):
29 | request.cls.function2 = Function()
30 |
31 |
32 | @pytest.mark.usefixtures('function1', 'function2')
33 | class TestFunctionalUnsubscribe:
34 |
35 | def test_no_callbacks_made(self, pubpen):
36 | """
37 | Subscribe to an event then unsubscribe.
38 |
39 | No callbacks are called no matter how many times the event is emitted
40 | """
41 | first = pubpen.subscribe('test_event', self.function1)
42 | pubpen.unsubscribe(first)
43 | assert self.function1.called == 0
44 |
45 | for iteration in range(1, 3):
46 | pubpen.emit('test_event')
47 | pending = asyncio.Task.all_tasks(loop=pubpen.loop)
48 | pubpen.loop.run_until_complete(asyncio.gather(*pending, loop=pubpen.loop))
49 | assert self.function1.called == 0
50 |
51 | def test_no_further_callbacks_made(self, pubpen):
52 | """
53 | Subscribe to an event emit event once, then unsubscribe.
54 |
55 | Callback is called for the first event but not for any further events
56 | """
57 | first = pubpen.subscribe('test_event', self.function1)
58 | assert self.function1.called == 0
59 | pubpen.emit('test_event')
60 | pubpen.unsubscribe(first)
61 |
62 | for iteration in range(1, 3):
63 | pubpen.emit('test_event')
64 | pending = asyncio.Task.all_tasks(loop=pubpen.loop)
65 | pubpen.loop.run_until_complete(asyncio.gather(*pending, loop=pubpen.loop))
66 | assert self.function1.called == 1
67 |
68 | def test_events_and_callbacks_isolated(self, pubpen):
69 | """
70 | Subscribe to two events. Unsubscribe from one of them
71 |
72 | callbacks invoked on one event but not the other
73 | """
74 | first = pubpen.subscribe('test_event1', self.function1)
75 | second = pubpen.subscribe('test_event2', self.function2)
76 | pubpen.unsubscribe(first)
77 | assert self.function1.called == 0
78 | assert self.function2.called == 0
79 |
80 | for iteration in range(1, 3):
81 | pubpen.emit('test_event1')
82 | pubpen.emit('test_event2')
83 | pending = asyncio.Task.all_tasks(loop=pubpen.loop)
84 | pubpen.loop.run_until_complete(asyncio.gather(*pending, loop=pubpen.loop))
85 | assert self.function1.called == 0
86 | assert self.function2.called == 1 * iteration
87 |
88 | def test_events_isolated(self, pubpen):
89 | """
90 | Subscribe to two events. Unsubscribe from one of them
91 |
92 | callbacks invoked on one event but not the other
93 | """
94 | first = pubpen.subscribe('test_event1', self.function1)
95 | second = pubpen.subscribe('test_event2', self.function1)
96 | pubpen.unsubscribe(first)
97 | assert self.function1.called == 0
98 |
99 | for iteration in range(1, 3):
100 | pubpen.emit('test_event1')
101 | pubpen.emit('test_event2')
102 | pending = asyncio.Task.all_tasks(loop=pubpen.loop)
103 | pubpen.loop.run_until_complete(asyncio.gather(*pending, loop=pubpen.loop))
104 | assert self.function1.called == 1 * iteration
105 |
106 | def test_callbacks_isolated(self, pubpen):
107 | """
108 | Subscribe to two callbacks to one event. Unsubscribe one callback
109 |
110 | One callback invoked but not the other
111 | """
112 | first = pubpen.subscribe('test_event', self.function1)
113 | second = pubpen.subscribe('test_event', self.function2)
114 | pubpen.unsubscribe(first)
115 | assert self.function1.called == 0
116 | assert self.function2.called == 0
117 |
118 | for iteration in range(1, 3):
119 | pubpen.emit('test_event')
120 | pending = asyncio.Task.all_tasks(loop=pubpen.loop)
121 | pubpen.loop.run_until_complete(asyncio.gather(*pending, loop=pubpen.loop))
122 | assert self.function1.called == 0
123 | assert self.function2.called == 1 * iteration
124 |
--------------------------------------------------------------------------------
/tests/units/test_unsubscribe.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from pubmarine import PubPen
4 |
5 |
6 | @pytest.fixture
7 | def pubpen(event_loop):
8 | pubpen = PubPen(event_loop)
9 | return pubpen
10 |
11 |
12 | @pytest.fixture
13 | def pubpen_multi_event(event_loop):
14 | pubpen = PubPen(event_loop)
15 |
16 | # Fake up the internal data structures that unsubscribe uses
17 | pubpen._subscriptions[0] = 'test_event1'
18 | pubpen._subscriptions[1] = 'test_event2'
19 | pubpen._event_handlers['test_event1'][0] = 'handler1'
20 | pubpen._event_handlers['test_event2'][1] = 'handler2'
21 |
22 | return pubpen
23 |
24 |
25 | @pytest.fixture
26 | def pubpen_multi_callback(event_loop):
27 | pubpen = PubPen(event_loop)
28 |
29 | # Fake up the internal data structures that unsubscribe uses
30 | pubpen._subscriptions[0] = 'test_event1'
31 | pubpen._subscriptions[1] = 'test_event1'
32 | pubpen._subscriptions[2] = 'test_event1'
33 | pubpen._event_handlers['test_event1'][0] = 'handler1'
34 | pubpen._event_handlers['test_event1'][1] = 'handler2'
35 | pubpen._event_handlers['test_event1'][2] = 'handler3'
36 |
37 | return pubpen
38 |
39 |
40 | @pytest.fixture
41 | def pubpen_partially_dealloc(event_loop):
42 | """The event handler has been partially removed already."""
43 | pubpen = PubPen(event_loop)
44 | pubpen._subscriptions[0] = 'test_event1'
45 | return pubpen
46 |
47 | class Foo:
48 | def method(self):
49 | pass
50 |
51 |
52 | def function():
53 | pass
54 |
55 |
56 | class TestPubPenUnsubscribe:
57 |
58 | def test_unsubscribe_nonexisting(self, pubpen):
59 | result = pubpen.unsubscribe(0)
60 | assert result is None
61 |
62 | # Test internal implementation is correct
63 | assert len(pubpen._subscriptions) == 0
64 | assert len(pubpen._event_handlers) == 0
65 |
66 | def test_unsubscribe_multi_event_remove_first(self, pubpen_multi_event):
67 | result = pubpen_multi_event.unsubscribe(0)
68 | assert result is None
69 |
70 | # Test internal implementation is correct
71 | assert len(pubpen_multi_event._subscriptions) == 1
72 | assert pubpen_multi_event._subscriptions[1] == 'test_event2'
73 |
74 | assert len(pubpen_multi_event._event_handlers['test_event1']) == 0
75 | assert len(pubpen_multi_event._event_handlers['test_event2']) == 1
76 | assert (1, 'handler2') in pubpen_multi_event._event_handlers['test_event2'].items()
77 |
78 |
79 | def test_unsubscribe_multi_event_remove_last(self, pubpen_multi_event):
80 | result = pubpen_multi_event.unsubscribe(1)
81 | assert result is None
82 |
83 | # Test internal implementation is correct
84 | assert len(pubpen_multi_event._subscriptions) == 1
85 | assert pubpen_multi_event._subscriptions[0] == 'test_event1'
86 |
87 | assert len(pubpen_multi_event._event_handlers['test_event1']) == 1
88 | assert len(pubpen_multi_event._event_handlers['test_event2']) == 0
89 | assert (0, 'handler1') in pubpen_multi_event._event_handlers['test_event1'].items()
90 |
91 | def test_unsubscribe_multi_callback_remove_first(self, pubpen_multi_callback):
92 | result = pubpen_multi_callback.unsubscribe(0)
93 | assert result is None
94 |
95 | # Test internal implementation is correct
96 | assert len(pubpen_multi_callback._subscriptions) == 2
97 | assert pubpen_multi_callback._subscriptions[1] == 'test_event1'
98 | assert pubpen_multi_callback._subscriptions[2] == 'test_event1'
99 |
100 | assert len(pubpen_multi_callback._event_handlers['test_event1']) == 2
101 | assert (1, 'handler2') in pubpen_multi_callback._event_handlers['test_event1'].items()
102 | assert (2, 'handler3') in pubpen_multi_callback._event_handlers['test_event1'].items()
103 |
104 | def test_unsubscribe_multi_callback_remove_last(self, pubpen_multi_callback):
105 | result = pubpen_multi_callback.unsubscribe(2)
106 | assert result is None
107 |
108 | # Test internal implementation is correct
109 | assert len(pubpen_multi_callback._subscriptions) == 2
110 | assert pubpen_multi_callback._subscriptions[0] == 'test_event1'
111 | assert pubpen_multi_callback._subscriptions[1] == 'test_event1'
112 |
113 | assert len(pubpen_multi_callback._event_handlers['test_event1']) == 2
114 | assert (0, 'handler1') in pubpen_multi_callback._event_handlers['test_event1'].items()
115 | assert (1, 'handler2') in pubpen_multi_callback._event_handlers['test_event1'].items()
116 |
117 | def test_unsubscribe_partially_deallocated_handler(self, pubpen_partially_dealloc):
118 | pubpenpd = pubpen_partially_dealloc
119 | result = pubpenpd.unsubscribe(0)
120 | assert result is None
121 |
122 | # Test internal implementation is correct
123 | assert len(pubpenpd._subscriptions) == 0
124 | assert len(pubpenpd._event_handlers['test_event1']) == 0
125 |
126 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/OFL-License.txt:
--------------------------------------------------------------------------------
1 | sphinx_rtd_theme/static/fonts/Inconsolata-Bold.ttf
2 | sphinx_rtd_theme/static/fonts/Lato-Bold.ttf
3 | sphinx_rtd_theme/static/fonts/Inconsolata.ttf
4 | sphinx_rtd_theme/static/fonts/Lato-Regular.ttf
5 |
6 |
7 |
8 | Copyright (c)
, (),
9 | with Reserved Font Name .
10 | Copyright (c) , (),
11 | with Reserved Font Name .
12 | Copyright (c) , ().
13 |
14 | This Font Software is licensed under the SIL Open Font License, Version 1.1.
15 | This license is copied below, and is also available with a FAQ at:
16 | http://scripts.sil.org/OFL
17 |
18 |
19 | -----------------------------------------------------------
20 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
21 | -----------------------------------------------------------
22 |
23 | PREAMBLE
24 | The goals of the Open Font License (OFL) are to stimulate worldwide
25 | development of collaborative font projects, to support the font creation
26 | efforts of academic and linguistic communities, and to provide a free and
27 | open framework in which fonts may be shared and improved in partnership
28 | with others.
29 |
30 | The OFL allows the licensed fonts to be used, studied, modified and
31 | redistributed freely as long as they are not sold by themselves. The
32 | fonts, including any derivative works, can be bundled, embedded,
33 | redistributed and/or sold with any software provided that any reserved
34 | names are not used by derivative works. The fonts and derivatives,
35 | however, cannot be released under any other type of license. The
36 | requirement for fonts to remain under this license does not apply
37 | to any document created using the fonts or their derivatives.
38 |
39 | DEFINITIONS
40 | "Font Software" refers to the set of files released by the Copyright
41 | Holder(s) under this license and clearly marked as such. This may
42 | include source files, build scripts and documentation.
43 |
44 | "Reserved Font Name" refers to any names specified as such after the
45 | copyright statement(s).
46 |
47 | "Original Version" refers to the collection of Font Software components as
48 | distributed by the Copyright Holder(s).
49 |
50 | "Modified Version" refers to any derivative made by adding to, deleting,
51 | or substituting -- in part or in whole -- any of the components of the
52 | Original Version, by changing formats or by porting the Font Software to a
53 | new environment.
54 |
55 | "Author" refers to any designer, engineer, programmer, technical
56 | writer or other person who contributed to the Font Software.
57 |
58 | PERMISSION & CONDITIONS
59 | Permission is hereby granted, free of charge, to any person obtaining
60 | a copy of the Font Software, to use, study, copy, merge, embed, modify,
61 | redistribute, and sell modified and unmodified copies of the Font
62 | Software, subject to the following conditions:
63 |
64 | 1) Neither the Font Software nor any of its individual components,
65 | in Original or Modified Versions, may be sold by itself.
66 |
67 | 2) Original or Modified Versions of the Font Software may be bundled,
68 | redistributed and/or sold with any software, provided that each copy
69 | contains the above copyright notice and this license. These can be
70 | included either as stand-alone text files, human-readable headers or
71 | in the appropriate machine-readable metadata fields within text or
72 | binary files as long as those fields can be easily viewed by the user.
73 |
74 | 3) No Modified Version of the Font Software may use the Reserved Font
75 | Name(s) unless explicit written permission is granted by the corresponding
76 | Copyright Holder. This restriction only applies to the primary font name as
77 | presented to the users.
78 |
79 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
80 | Software shall not be used to promote, endorse or advertise any
81 | Modified Version, except to acknowledge the contribution(s) of the
82 | Copyright Holder(s) and the Author(s) or with their explicit written
83 | permission.
84 |
85 | 5) The Font Software, modified or unmodified, in part or in whole,
86 | must be distributed entirely under this license, and must not be
87 | distributed under any other license. The requirement for fonts to
88 | remain under this license does not apply to any document created
89 | using the Font Software.
90 |
91 | TERMINATION
92 | This license becomes null and void if any of the above conditions are
93 | not met.
94 |
95 | DISCLAIMER
96 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
97 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
98 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
99 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
100 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
101 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
102 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
103 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
104 | OTHER DEALINGS IN THE FONT SOFTWARE.
105 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/Gruntfile.js:
--------------------------------------------------------------------------------
1 | module.exports = function(grunt) {
2 |
3 | // load all grunt tasks
4 | require('matchdep').filterDev('grunt-*').forEach(grunt.loadNpmTasks);
5 |
6 | grunt.initConfig({
7 | open : {
8 | dev: {
9 | path: 'http://localhost:1919'
10 | }
11 | },
12 |
13 | connect: {
14 | server: {
15 | options: {
16 | port: 1919,
17 | base: 'demo_docs/build',
18 | livereload: true
19 | }
20 | }
21 | },
22 | copy: {
23 | fonts: {
24 | files: [
25 | {
26 | expand: true,
27 | flatten: true,
28 | src: ['bower_components/font-awesome/fonts/*'],
29 | dest: 'sphinx_rtd_theme/static/fonts/',
30 | filter: 'isFile'
31 | },
32 | {
33 | expand: true,
34 | flatten: true,
35 | src: ['bower_components/lato-googlefont/Lato-Regular.ttf',
36 | 'bower_components/lato-googlefont/Lato-Italic.ttf',
37 | 'bower_components/lato-googlefont/Lato-Bold.ttf',
38 | 'bower_components/lato-googlefont/Lato-BoldItalic.ttf'],
39 | dest: 'sphinx_rtd_theme/static/fonts/',
40 | filter: 'isFile'
41 | },
42 | {
43 | expand: true,
44 | flatten: true,
45 | src: ['bower_components/robotoslab-googlefont/RobotoSlab-Bold.ttf',
46 | 'bower_components/robotoslab-googlefont/RobotoSlab-Regular.ttf'],
47 | dest: 'sphinx_rtd_theme/static/fonts/',
48 | filter: 'isFile'
49 | },
50 | {
51 | expand: true,
52 | flatten: true,
53 | src: ['bower_components/inconsolata-googlefont/Inconsolata-Bold.ttf',
54 | 'bower_components/inconsolata-googlefont/Inconsolata-Regular.ttf'],
55 | dest: 'sphinx_rtd_theme/static/fonts/',
56 | filter: 'isFile'
57 | }
58 | ]
59 | }
60 | },
61 |
62 | sass: {
63 | dev: {
64 | options: {
65 | style: 'expanded',
66 | loadPath: ['bower_components/bourbon/dist', 'bower_components/neat/app/assets/stylesheets', 'bower_components/font-awesome/scss', 'bower_components/wyrm/sass']
67 | },
68 | files: [{
69 | expand: true,
70 | cwd: 'sass',
71 | src: ['*.sass'],
72 | dest: 'sphinx_rtd_theme/static/css',
73 | ext: '.css'
74 | }]
75 | },
76 | build: {
77 | options: {
78 | style: 'compressed',
79 | loadPath: ['bower_components/bourbon/dist', 'bower_components/neat/app/assets/stylesheets', 'bower_components/font-awesome/scss', 'bower_components/wyrm/sass']
80 | },
81 | files: [{
82 | expand: true,
83 | cwd: 'sass',
84 | src: ['*.sass'],
85 | dest: 'sphinx_rtd_theme/static/css',
86 | ext: '.css'
87 | }]
88 | }
89 | },
90 |
91 | browserify: {
92 | dev: {
93 | options: {
94 | external: ['jquery'],
95 | alias: {
96 | 'sphinx-rtd-theme': './js/theme.js'
97 | }
98 | },
99 | src: ['js/*.js'],
100 | dest: 'sphinx_rtd_theme/static/js/theme.js'
101 | },
102 | build: {
103 | options: {
104 | external: ['jquery'],
105 | alias: {
106 | 'sphinx-rtd-theme': './js/theme.js'
107 | }
108 | },
109 | src: ['js/*.js'],
110 | dest: 'sphinx_rtd_theme/static/js/theme.js'
111 | }
112 | },
113 |
114 | exec: {
115 | bower_update: {
116 | cmd: 'bower update'
117 | },
118 | build_sphinx: {
119 | cmd: 'sphinx-build demo_docs/source demo_docs/build'
120 | }
121 | },
122 | clean: {
123 | build: ["demo_docs/build"],
124 | fonts: ["sphinx_rtd_theme/static/fonts"]
125 | },
126 |
127 | watch: {
128 | /* Compile sass changes into theme directory */
129 | sass: {
130 | files: ['sass/*.sass', 'bower_components/**/*.sass'],
131 | tasks: ['sass:dev']
132 | },
133 | /* Changes in theme dir rebuild sphinx */
134 | sphinx: {
135 | files: ['sphinx_rtd_theme/**/*', 'demo_docs/**/*.rst', 'demo_docs/**/*.py'],
136 | tasks: ['clean:build','exec:build_sphinx']
137 | },
138 | /* JavaScript */
139 | browserify: {
140 | files: ['js/*.js'],
141 | tasks: ['browserify:dev']
142 | },
143 | /* live-reload the demo_docs if sphinx re-builds */
144 | livereload: {
145 | files: ['demo_docs/build/**/*'],
146 | options: { livereload: true }
147 | }
148 | }
149 |
150 | });
151 |
152 | grunt.loadNpmTasks('grunt-exec');
153 | grunt.loadNpmTasks('grunt-contrib-connect');
154 | grunt.loadNpmTasks('grunt-contrib-watch');
155 | grunt.loadNpmTasks('grunt-contrib-sass');
156 | grunt.loadNpmTasks('grunt-contrib-clean');
157 | grunt.loadNpmTasks('grunt-contrib-copy');
158 | grunt.loadNpmTasks('grunt-open');
159 | grunt.loadNpmTasks('grunt-browserify');
160 |
161 | grunt.registerTask('fonts', ['clean:fonts','copy:fonts']);
162 | grunt.registerTask('default', ['exec:bower_update','clean:build','sass:dev','browserify:dev','exec:build_sphinx','connect','open','watch']);
163 | grunt.registerTask('build', ['exec:bower_update','clean:build','sass:build','browserify:build','exec:build_sphinx']);
164 | }
165 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # -*- coding: utf-8 -*-
3 | #
4 | # Pubmarine documentation build configuration file, created by
5 | # sphinx-quickstart on Tue Sep 12 22:28:56 2017.
6 | #
7 | # This file is execfile()d with the current directory set to its
8 | # containing dir.
9 | #
10 | # Note that not all possible configuration values are present in this
11 | # autogenerated file.
12 | #
13 | # All configuration values have a default; values that are commented out
14 | # serve to show the default.
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #
20 | import os
21 | import sys
22 | sys.path.insert(0, os.path.abspath('../'))
23 |
24 | from pubmarine import __version_info__
25 |
26 | # -- General configuration ------------------------------------------------
27 |
28 | # If your documentation needs a minimal Sphinx version, state it here.
29 | #
30 | # needs_sphinx = '1.0'
31 |
32 | # Add any Sphinx extension module names here, as strings. They can be
33 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
34 | # ones.
35 | extensions = ['sphinx.ext.autodoc',
36 | 'sphinx.ext.doctest',
37 | 'sphinx.ext.intersphinx',
38 | 'sphinx.ext.coverage',
39 | 'sphinx.ext.viewcode',
40 | 'reno.sphinxext',]
41 |
42 | # Add any paths that contain templates here, relative to this directory.
43 | templates_path = ['_templates']
44 |
45 | # The suffix(es) of source filenames.
46 | # You can specify multiple suffix as a list of string:
47 | #
48 | # source_suffix = ['.rst', '.md']
49 | source_suffix = '.rst'
50 |
51 | # The master toctree document.
52 | master_doc = 'index'
53 |
54 | # General information about the project.
55 | project = 'Pubmarine'
56 | copyright = '2017, Toshio Kuratomi'
57 | author = 'Toshio Kuratomi'
58 |
59 | # The version info for the project you're documenting, acts as replacement for
60 | # |version| and |release|, also used in various other places throughout the
61 | # built documents.
62 | #
63 | # The short X.Y version.
64 | version = '.'.join(__version_info__[:1])
65 | # The full version, including alpha/beta/rc tags.
66 | release = '.'.join(__version_info__)
67 |
68 | # The language for content autogenerated by Sphinx. Refer to documentation
69 | # for a list of supported languages.
70 | #
71 | # This is also used if you do content translation via gettext catalogs.
72 | # Usually you set "language" from the command line for these cases.
73 | language = None
74 |
75 | # List of patterns, relative to source directory, that match files and
76 | # directories to ignore when looking for source files.
77 | # This patterns also effect to html_static_path and html_extra_path
78 | exclude_patterns = ['_build', '_themes', 'Thumbs.db', '.DS_Store']
79 |
80 | # The name of the Pygments (syntax highlighting) style to use.
81 | pygments_style = 'sphinx'
82 |
83 | # If true, `todo` and `todoList` produce output, else they produce nothing.
84 | todo_include_todos = False
85 |
86 |
87 | # -- Options for HTML output ----------------------------------------------
88 |
89 | html_theme_path = ['_themes']
90 | # The theme to use for HTML and HTML Help pages. See the documentation for
91 | # a list of builtin themes.
92 | #
93 | html_theme = 'sphinx_rtd_theme'
94 |
95 | # Theme options are theme-specific and customize the look and feel of a theme
96 | # further. For a list of options available for each theme, see the
97 | # documentation.
98 | #
99 | # html_theme_options = {}
100 |
101 | # Add any paths that contain custom static files (such as style sheets) here,
102 | # relative to this directory. They are copied after the builtin static files,
103 | # so a file named "default.css" will overwrite the builtin "default.css".
104 | html_static_path = ['_static']
105 |
106 |
107 | # -- Options for HTMLHelp output ------------------------------------------
108 |
109 | # Output file base name for HTML help builder.
110 | htmlhelp_basename = 'Pubmarinedoc'
111 |
112 |
113 | # -- Options for LaTeX output ---------------------------------------------
114 |
115 | latex_elements = {
116 | # The paper size ('letterpaper' or 'a4paper').
117 | #
118 | # 'papersize': 'letterpaper',
119 |
120 | # The font size ('10pt', '11pt' or '12pt').
121 | #
122 | # 'pointsize': '10pt',
123 |
124 | # Additional stuff for the LaTeX preamble.
125 | #
126 | # 'preamble': '',
127 |
128 | # Latex figure (float) alignment
129 | #
130 | # 'figure_align': 'htbp',
131 | }
132 |
133 | # Grouping the document tree into LaTeX files. List of tuples
134 | # (source start file, target name, title,
135 | # author, documentclass [howto, manual, or own class]).
136 | latex_documents = [
137 | (master_doc, 'Pubmarine.tex', 'Pubmarine Documentation',
138 | 'Toshio Kuratomi', 'manual'),
139 | ]
140 |
141 |
142 | # -- Options for manual page output ---------------------------------------
143 |
144 | # One entry per manual page. List of tuples
145 | # (source start file, name, description, authors, manual section).
146 | man_pages = [
147 | (master_doc, 'pubmarine', 'Pubmarine Documentation',
148 | [author], 1)
149 | ]
150 |
151 |
152 | # -- Options for Texinfo output -------------------------------------------
153 |
154 | # Grouping the document tree into Texinfo files. List of tuples
155 | # (source start file, target name, title, author,
156 | # dir menu entry, description, category)
157 | texinfo_documents = [
158 | (master_doc, 'Pubmarine', 'Pubmarine Documentation',
159 | author, 'Pubmarine', 'One line description of project.',
160 | 'Miscellaneous'),
161 | ]
162 |
163 |
164 |
165 |
166 | # Example configuration for intersphinx: refer to the Python standard library.
167 | intersphinx_mapping = {'https://docs.python.org/3/': None}
168 |
--------------------------------------------------------------------------------
/tests/units/test_subscribe.py:
--------------------------------------------------------------------------------
1 | import weakref
2 |
3 | import pytest
4 |
5 | import pubmarine
6 | from pubmarine import PubPen
7 |
8 |
9 | @pytest.fixture
10 | def pubpen(event_loop):
11 | pubpen = PubPen(event_loop)
12 | return pubpen
13 |
14 |
15 | @pytest.fixture
16 | def pubpen_predefined(event_loop):
17 | pubpen = PubPen(event_loop, event_list=['test_event1', 'test_event2'])
18 | return pubpen
19 |
20 |
21 | class Foo:
22 | def method(self):
23 | pass
24 |
25 |
26 | def function():
27 | pass
28 |
29 |
30 | class TestPubPenSubscribe:
31 |
32 | def test_pubpen_id_integer(self, pubpen):
33 | id_ = pubpen.subscribe('test_event', function)
34 | assert isinstance(id_, int)
35 |
36 | def test_pubpen_id_increments(self, pubpen):
37 | first = pubpen.subscribe('test_event', lambda: None)
38 | second = pubpen.subscribe('test_event', lambda: None)
39 | assert second > first
40 |
41 | def test_subscribe_function(self, pubpen):
42 | """Test that adding function callbacks succeeds"""
43 | first = pubpen.subscribe('test_event', function)
44 |
45 | # Test internals of saving worked
46 | assert pubpen._subscriptions[first] == 'test_event'
47 | assert len(pubpen._event_handlers['test_event']) == 1
48 |
49 | event = pubpen._event_handlers['test_event']
50 | assert list(event.keys()) == [first]
51 | assert list(event.values()) == [weakref.ref(function)]
52 |
53 | def test_subscribe_method(self, pubpen):
54 | """Test that adding method callbacks succeed"""
55 | foo = Foo()
56 | first = pubpen.subscribe('test_event', foo.method)
57 |
58 | # Test internals of saving worked
59 | assert pubpen._subscriptions[first] == 'test_event'
60 | assert len(pubpen._event_handlers['test_event']) == 1
61 |
62 | event = pubpen._event_handlers['test_event']
63 | assert list(event.keys()) == [first]
64 | assert list(event.values()) == [weakref.WeakMethod(foo.method)]
65 |
66 | def test_in_event_list(self, pubpen_predefined):
67 | """Test that adding events that are in the event list succeeds"""
68 | first = pubpen_predefined.subscribe('test_event1', function)
69 |
70 | # Test internals of saving worked
71 | assert pubpen_predefined._subscriptions[first] == 'test_event1'
72 | assert len(pubpen_predefined._event_handlers['test_event1']) == 1
73 |
74 | event = pubpen_predefined._event_handlers['test_event1']
75 | assert list(event.keys()) == [first]
76 | assert list(event.values()) == [weakref.ref(function)]
77 |
78 | def test_not_in_event_list(self, pubpen_predefined):
79 | """Test that we raise an error when adding an event not in the event list"""
80 | with pytest.raises(pubmarine.EventNotFoundError) as e:
81 | first = pubpen_predefined.subscribe('test_event3', function)
82 | assert 'test_event3' in '{}'.format(e)
83 |
84 | def test_subscribe_same_callback_same_event(self, pubpen):
85 | first = pubpen.subscribe('test_event', function)
86 | second = pubpen.subscribe('test_event', function)
87 |
88 | # Test internals of subscribing worked
89 | assert pubpen._subscriptions[first] == 'test_event'
90 | assert pubpen._subscriptions[second] == 'test_event'
91 | assert len(pubpen._event_handlers['test_event']) == 2
92 |
93 | events = pubpen._event_handlers['test_event']
94 | assert events[first] == weakref.ref(function)
95 | assert events[second] == weakref.ref(function)
96 |
97 | def test_subscribe_same_callback_diff_event(self, pubpen):
98 | first = pubpen.subscribe('test_event1', function)
99 | second = pubpen.subscribe('test_event2', function)
100 |
101 | # Test internals of subscribing worked
102 | assert pubpen._subscriptions[first] == 'test_event1'
103 | assert pubpen._subscriptions[second] == 'test_event2'
104 | assert len(pubpen._event_handlers['test_event1']) == 1
105 | assert len(pubpen._event_handlers['test_event2']) == 1
106 |
107 | events = pubpen._event_handlers['test_event1']
108 | assert events[first] == weakref.ref(function)
109 | events = pubpen._event_handlers['test_event2']
110 | assert events[second] == weakref.ref(function)
111 |
112 | def test_subscribe_diff_callback_same_event(self, pubpen):
113 | first = pubpen.subscribe('test_event', function)
114 | foo = Foo()
115 | second = pubpen.subscribe('test_event', foo.method)
116 |
117 | # Test internals of subscribing worked
118 | assert pubpen._subscriptions[first] == 'test_event'
119 | assert pubpen._subscriptions[second] == 'test_event'
120 | assert len(pubpen._event_handlers['test_event']) == 2
121 |
122 | events = pubpen._event_handlers['test_event']
123 | assert events[first] != events[second]
124 | assert events[first] in (weakref.ref(function), weakref.WeakMethod(foo.method))
125 | assert events[second] in (weakref.ref(function), weakref.WeakMethod(foo.method))
126 |
127 | def test_subscribe_diff_callback_diff_event(self, pubpen):
128 | first = pubpen.subscribe('test_event1', function)
129 | foo = Foo()
130 | second = pubpen.subscribe('test_event2', foo.method)
131 |
132 | # Test internals of subscribing worked
133 | assert pubpen._subscriptions[first] == 'test_event1'
134 | assert pubpen._subscriptions[second] == 'test_event2'
135 | assert len(pubpen._event_handlers['test_event1']) == 1
136 | assert len(pubpen._event_handlers['test_event2']) == 1
137 |
138 | events = pubpen._event_handlers['test_event1']
139 | assert events[first] == weakref.ref(function)
140 | events = pubpen._event_handlers['test_event2']
141 | assert events[second] == weakref.WeakMethod(foo.method)
142 |
--------------------------------------------------------------------------------
/examples/talk.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3 -tt
2 | #
3 | # Copyright: 2017, Toshio Kuratomi
4 | # License: MIT
5 | """
6 | An example implementing a very simple chat application.
7 | """
8 | import asyncio
9 | import curses
10 | import os.path
11 | import sys
12 | from functools import partial
13 |
14 | from pubmarine import PubPen
15 |
16 |
17 | # Python-3.7 introduced a new asyncio API. The following code shows both
18 | # pre-3.7 and post-3.7 coding style
19 | PY37 = sys.version_info >= (3, 7)
20 |
21 | PATH = os.path.expanduser('~/talk.sock')
22 |
23 |
24 | class Display:
25 | def __init__(self, pubpen):
26 | self.pubpen = pubpen
27 |
28 | self.pubpen.subscribe('incoming', self.show_message)
29 | self.pubpen.subscribe('typed', self.show_typing)
30 | self.pubpen.subscribe('error', self.show_error)
31 | self.pubpen.subscribe('info', self.show_error)
32 | self.pubpen.subscribe('conn_lost', self.show_error)
33 |
34 | def __enter__(self):
35 | self.stdscr = curses.initscr()
36 |
37 | curses.noecho()
38 | curses.cbreak()
39 | self.stdscr.keypad(1)
40 |
41 | max_y, max_x = self.stdscr.getmaxyx()
42 |
43 | self.error_buffer = self.stdscr.derwin(1, max_x, 0, 0)
44 |
45 | self.separator1 = self.stdscr.derwin(1, max_x, 1, 0)
46 | sep_txt = b'-' * (max_x - 1)
47 | self.separator1.addstr(0, 0, sep_txt)
48 |
49 | self.chat_log = self.stdscr.derwin(max_y - 3, max_x, 2, 0)
50 | self.chat_max_y, self.chat_max_x = self.chat_log.getmaxyx()
51 | self.current_chat_line = 0
52 |
53 | self.separator2 = self.stdscr.derwin(1, max_x, max_y - 2, 0)
54 | sep_txt = b'=' * (max_x - 1)
55 | self.separator2.addstr(0, 0, sep_txt)
56 |
57 | self.input_buffer = self.stdscr.derwin(1, max_x, max_y - 1, 0)
58 | self.input_max_y, self.input_max_x = self.input_buffer.getmaxyx()
59 | self.input_current_x = 0
60 | self.input_contents = ''
61 |
62 | self.stdscr.refresh()
63 | return self
64 |
65 | def __exit__(self, *args):
66 | curses.nocbreak()
67 | self.stdscr.keypad(0)
68 | curses.echo()
69 | curses.endwin()
70 |
71 | return False
72 |
73 | async def get_ch(self):
74 | char = chr(await self.pubpen.loop.run_in_executor(None, self.stdscr.getch))
75 | self.pubpen.publish('typed', char)
76 |
77 | def show_message(self, message, user):
78 | # Instead of scrolling, simply stop the program
79 | if self.current_chat_line >= self.chat_max_y:
80 | self.pubpen.loop.stop()
81 | return
82 |
83 | message = "%s %s" % (user, message)
84 |
85 | # Instead of line breaking, simply truncate the message
86 | if len(message) > self.chat_max_x:
87 | message = message[:self.chat_max_x]
88 |
89 | self.chat_log.addstr(self.current_chat_line, 0, message.encode('utf-8'))
90 | self.current_chat_line += 1
91 | self.chat_log.refresh()
92 |
93 | def show_typing(self, char):
94 | if char == '\n':
95 | if self.input_contents == '.':
96 | self.pubpen.loop.stop()
97 | return
98 | self.pubpen.publish('outgoing', self.input_contents)
99 | self.show_message(self.input_contents, '')
100 | self.clear_typing()
101 | return
102 |
103 | self.input_current_x += 1
104 | self.input_contents += char
105 | self.input_buffer.addstr(0, self.input_current_x - 1, char.encode('utf-8'))
106 | self.input_buffer.refresh()
107 |
108 | def clear_typing(self):
109 | self.input_current_x = 0
110 | self.input_buffer.clear()
111 | self.input_contents = ''
112 | self.input_buffer.refresh()
113 |
114 | def show_error(self, exc):
115 | self.error_buffer.clear()
116 | self.error_buffer.addstr(0, 0, str(exc).encode('utf-8'))
117 | self.error_buffer.refresh()
118 |
119 |
120 | class TalkProtocol(asyncio.Protocol):
121 | def __init__(self, pubpen):
122 | self.pubpen = pubpen
123 |
124 | self.pubpen.subscribe('outgoing', self.send_message)
125 |
126 | def send_message(self, message):
127 | self.transport.write(message.encode('utf-8'))
128 |
129 | def connection_made(self, transport):
130 | self.transport = transport
131 |
132 | def data_received(self, data):
133 | self.pubpen.publish('incoming', data.decode('utf-8', errors='replace'), "")
134 |
135 | def error_received(self, exc):
136 | self.pubpen.publish('error', exc)
137 |
138 | def connection_lost(self, exc):
139 | self.pubpen.publish('conn_lost', exc)
140 | self.pubpen.loop.stop()
141 |
142 |
143 | async def start():
144 | if PY37:
145 | loop = asyncio.get_running_loop()
146 | else:
147 | loop = asyncio.get_event_loop()
148 |
149 | pubpen = PubPen(loop)
150 |
151 | with Display(pubpen) as display:
152 | try:
153 | # try Client first
154 | connection = loop.create_unix_connection(partial(TalkProtocol, pubpen), PATH)
155 | await connection
156 | except (ConnectionRefusedError, FileNotFoundError):
157 | # server
158 | connection = loop.create_unix_server(partial(TalkProtocol, pubpen), PATH)
159 | await connection
160 |
161 | while True:
162 | await display.get_ch()
163 |
164 |
165 | def main():
166 | # Note: there doesn't appear to be a way to get asyncio.run() to cancel all the tasks so there's
167 | # not a nicer python-3.7 version of this startup
168 | loop = asyncio.get_event_loop()
169 | try:
170 | loop.run_until_complete(start())
171 | except RuntimeError:
172 | # asyncio complains that the start() task hasn't had a chance to complete when stop() is
173 | # called. Go ahead and cancel all the tasks.
174 | for task in asyncio.Task.all_tasks():
175 | task.cancel()
176 | finally:
177 | loop.run_until_complete(loop.shutdown_asyncgens())
178 |
179 |
180 | if __name__ == '__main__':
181 | main()
182 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/demo_docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/SphinxRTDthemedemo.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/SphinxRTDthemedemo.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/SphinxRTDthemedemo"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/SphinxRTDthemedemo"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/tests/units/test_publish.py:
--------------------------------------------------------------------------------
1 | from unittest import mock
2 | import pytest
3 |
4 | import pubmarine
5 | from pubmarine import PubPen
6 |
7 | def handler1():
8 | return 'handler1'
9 |
10 |
11 | def handler2():
12 | return 'handler2'
13 |
14 |
15 | def handler3():
16 | return 'handler3'
17 |
18 |
19 | @pytest.fixture
20 | def pubpen(event_loop):
21 | pubpen = PubPen(event_loop)
22 | pubpen.loop = mock.MagicMock()
23 | # In the real code, we store a weakref to the handlers. We use lambda
24 | # here to wrape the actual handler once to emulate the aspects of the
25 | # weakref that are important to this test
26 | pubpen._event_handlers['test_event1'][0] = lambda: handler1
27 | pubpen._event_handlers['test_event2'][1] = lambda: handler2
28 | pubpen._event_handlers['test_event2'][2] = lambda: handler3
29 | return pubpen
30 |
31 |
32 | @pytest.fixture
33 | def pubpen_predefined(event_loop):
34 | pubpen = PubPen(event_loop, event_list=['test_event1', 'test_event2'])
35 | pubpen.loop = mock.MagicMock()
36 | return pubpen
37 |
38 |
39 | @pytest.fixture
40 | def pubpen_emit(event_loop):
41 | pubpen = PubPen(event_loop)
42 | pubpen.publish = mock.MagicMock()
43 | return pubpen
44 |
45 |
46 | @pytest.fixture
47 | def pubpen_handlers_dealloc(event_loop):
48 | pubpen = PubPen(event_loop)
49 | pubpen.loop = mock.MagicMock()
50 | pubpen._subscriptions[0] = 'test_event1'
51 | pubpen._subscriptions[1] = 'test_event1'
52 | # In the real code, we store a weakref to the handlers. We use lambda
53 | # here to wrape the actual handler once to emulate the aspects of the
54 | # weakref that are important to this test
55 | pubpen._event_handlers['test_event1'][0] = lambda: handler1
56 | # But this one represents a weakref where the target has been deallocated.
57 | # In that case, calling the weakref returns None
58 | pubpen._event_handlers['test_event1'][1] = lambda: None
59 | return pubpen
60 |
61 |
62 | @pytest.fixture
63 | def pubpen_handlers_partially_removed(event_loop):
64 | """
65 | The handler has been deallocated (because it's a weakref) but it's only
66 | been removed from the _subscriptions list already.
67 | """
68 | pubpen = PubPen(event_loop)
69 | pubpen.loop = mock.MagicMock()
70 | pubpen._subscriptions[0] = 'test_event1'
71 | # In the real code, we store a weakref to the handlers. We use lambda
72 | # here to wrape the actual handler once to emulate the aspects of the
73 | # weakref that are important to this test
74 | pubpen._event_handlers['test_event1'][0] = lambda: handler1
75 | # But this one represents a weakref where the target has been deallocated.
76 | # In that case, calling the weakref returns None
77 | pubpen._event_handlers['test_event1'][1] = lambda: None
78 | return pubpen
79 |
80 |
81 | class TestPubPenPublish:
82 | def test_publish_event_list_pass(self, pubpen_predefined):
83 | result = pubpen_predefined.publish('test_event1')
84 | assert result is None
85 | assert pubpen_predefined.loop.call_soon.called is False
86 |
87 | result = pubpen_predefined.publish('test_event2')
88 | assert result is None
89 | assert pubpen_predefined.loop.call_soon.called is False
90 |
91 | def test_publish_event_list_fail(self, pubpen_predefined):
92 | with pytest.raises(pubmarine.EventNotFoundError) as e:
93 | result = pubpen_predefined.publish('test_event_bad')
94 | assert 'test_event_bad' in '{}'.format(e)
95 |
96 | def test_no_callbacks(self, pubpen):
97 | result = pubpen.publish('no_event')
98 | assert result is None
99 |
100 | assert pubpen.loop.call_soon.called is False
101 |
102 | def test_one_callback(self, pubpen):
103 | result = pubpen.publish('test_event1')
104 | assert result is None
105 |
106 | assert pubpen.loop.call_soon.called is True
107 | assert pubpen.loop.call_soon.call_count == 1
108 | assert handler1() in pubpen.loop.call_soon.call_args[0][0]()
109 |
110 | def test_multi_callbacks(self, pubpen):
111 | result = pubpen.publish('test_event2')
112 | assert result is None
113 |
114 | assert pubpen.loop.call_soon.called is True
115 | assert pubpen.loop.call_soon.call_count == 2
116 | for call in pubpen.loop.call_soon.call_args_list:
117 | assert handler2() in call[0][0]() or handler3() in call[0][0]()
118 | assert pubpen.loop.call_soon.call_args_list[0][0] != pubpen.loop.call_soon.call_args_list[1][0]
119 |
120 | def test_deallocated_callback(self, pubpen_handlers_dealloc):
121 | pubpenhd = pubpen_handlers_dealloc
122 | result = pubpenhd.publish('test_event1')
123 | assert result is None
124 |
125 | assert pubpenhd.loop.call_soon.called is True
126 | assert pubpenhd.loop.call_soon.call_count == 1
127 | assert handler1() in pubpenhd.loop.call_soon.call_args_list[0][0][0]()
128 |
129 | # Test internals have been updated correctly
130 | assert len(pubpenhd._event_handlers['test_event1']) == 1
131 | assert len(pubpenhd._subscriptions) == 1
132 | assert pubpenhd._subscriptions[0] == 'test_event1'
133 |
134 | def test_partially_removed_callback(self, pubpen_handlers_partially_removed):
135 | pubpenhpr = pubpen_handlers_partially_removed
136 | result = pubpenhpr.publish('test_event1')
137 | assert result is None
138 |
139 | assert pubpenhpr.loop.call_soon.called is True
140 | assert pubpenhpr.loop.call_soon.call_count == 1
141 | assert handler1() in pubpenhpr.loop.call_soon.call_args_list[0][0][0]()
142 |
143 | # Test internals have been updated correctly
144 | assert len(pubpenhpr._event_handlers['test_event1']) == 1
145 | assert len(pubpenhpr._subscriptions) == 1
146 | assert pubpenhpr._subscriptions[0] == 'test_event1'
147 |
148 |
149 | class TestPubPenEmit:
150 | def test_emit_warns(self, pubpen):
151 | with pytest.warns(DeprecationWarning) as e:
152 | pubpen.emit('test_event')
153 |
154 | def test_called_publish(self, pubpen_emit):
155 | pubpen_emit.emit('test_event')
156 | assert pubpen_emit.publish.called
157 | assert pubpen_emit.publish.call_count == 1
158 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/demo_docs/source/structure.rst:
--------------------------------------------------------------------------------
1 |
2 | *******************
3 | Structural Elements
4 | *******************
5 |
6 | .. contents:: Table of Contents
7 |
8 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec lorem neque, interdum in ipsum nec,
9 | finibus dictum velit. Ut eu efficitur arcu, id aliquam erat. In sit amet diam gravida, imperdiet tellus eu,
10 | gravida nisl. Praesent aliquet odio eget libero elementum, quis rhoncus tellus tincidunt.
11 | Suspendisse quis volutpat ipsum. Sed lobortis scelerisque tristique. Aenean condimentum risus tellus,
12 | quis accumsan ipsum laoreet ut. Integer porttitor maximus suscipit. Mauris in posuere sapien.
13 | Aliquam accumsan feugiat ligula, nec fringilla libero commodo sed. Proin et erat pharetra.
14 |
15 | ---------
16 |
17 | Etiam turpis ante, luctus sed velit tristique, finibus volutpat dui. Nam sagittis vel ante nec malesuada.
18 | Praesent dignissim mi nec ornare elementum. Nunc eu augue vel sem dignissim cursus sed et nulla.
19 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
20 | Pellentesque dictum dui sem, non placerat tortor rhoncus in. Sed placerat nulla at rhoncus iaculis.
21 |
22 | Document Section
23 | ================
24 |
25 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed condimentum nulla vel neque venenatis,
26 | nec placerat lorem placerat. Cras purus eros, gravida vitae tincidunt id, vehicula nec nulla.
27 | Fusce aliquet auctor cursus. Phasellus ex neque, vestibulum non est vitae, viverra fringilla tortor.
28 | Donec vestibulum convallis justo, a faucibus lorem vulputate vel. Aliquam cursus odio eu felis sodales aliquet.
29 | Aliquam erat volutpat. Maecenas eget dictum mauris. Suspendisse arcu eros, condimentum eget risus sed,
30 | luctus efficitur arcu. Cras ut dictum mi. Nulla congue interdum lorem, semper semper enim commodo nec.
31 |
32 | Document Subsection
33 | -------------------
34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam efficitur in eros et blandit. Nunc maximus,
36 | nisl at auctor vestibulum, justo ex sollicitudin ligula, id faucibus urna orci tristique nisl.
37 | Duis auctor rutrum orci, in ornare lacus condimentum quis. Quisque arcu velit, facilisis quis interdum ac,
38 | hendrerit auctor mauris. Curabitur urna nibh, porttitor at ante sit amet, vestibulum interdum dolor.
39 | Duis dictum elit orci, tincidunt imperdiet sem pellentesque et. In vehicula pellentesque varius.
40 | Phasellus a turpis sollicitudin, bibendum massa et, imperdiet neque. Integer quis sapien in magna rutrum bibendum.
41 | Integer cursus ex sed magna vehicula finibus. Proin tempus orci quis dolor tempus, nec condimentum odio vestibulum.
42 | Etiam efficitur sollicitudin libero, tincidunt volutpat ligula interdum sed.
43 |
44 | Document Subsubsection
45 | ^^^^^^^^^^^^^^^^^^^^^^
46 |
47 | Donec non rutrum lorem. Aenean sagittis metus at pharetra fringilla. Nunc sapien dolor, cursus sed nisi at,
48 | pretium tristique lectus. Sed pellentesque leo lectus, et convallis ipsum euismod a.
49 | Integer at leo vitae felis pretium aliquam fringilla quis odio. Sed pharetra enim accumsan feugiat pretium.
50 | Maecenas at pharetra tortor. Morbi semper eget mi vel finibus. Cras rutrum nulla eros, id feugiat arcu pellentesque ut.
51 | Sed finibus tortor ac nisi ultrices viverra. Duis feugiat malesuada sapien, at commodo ante porttitor ac.
52 | Curabitur posuere mauris mi, vel ornare orci scelerisque sit amet. Suspendisse nec fringilla dui.
53 |
54 | Document Paragraph
55 | """"""""""""""""""
56 |
57 | Pellentesque nec est in odio ultrices elementum. Vestibulum et hendrerit sapien, quis vulputate turpis.
58 | Suspendisse potenti. Curabitur tristique sit amet lectus non viverra. Phasellus rutrum dapibus turpis sed imperdiet.
59 | Mauris maximus viverra ante. Donec eu egestas mauris. Morbi vulputate tincidunt euismod. Integer vel porttitor neque.
60 | Donec at lacus suscipit, lacinia lectus vel, sagittis lectus.
61 |
62 | *********************
63 | Structural Elements 2
64 | *********************
65 |
66 | Etiam turpis ante, luctus sed velit tristique, finibus volutpat dui. Nam sagittis vel ante nec malesuada.
67 | Praesent dignissim mi nec ornare elementum. Nunc eu augue vel sem dignissim cursus sed et nulla.
68 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
69 | Pellentesque dictum dui sem, non placerat tortor rhoncus in. Sed placerat nulla at rhoncus iaculis.
70 |
71 | Document Section
72 | ================
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed condimentum nulla vel neque venenatis,
75 | nec placerat lorem placerat. Cras purus eros, gravida vitae tincidunt id, vehicula nec nulla.
76 | Fusce aliquet auctor cursus. Phasellus ex neque, vestibulum non est vitae, viverra fringilla tortor.
77 | Donec vestibulum convallis justo, a faucibus lorem vulputate vel. Aliquam cursus odio eu felis sodales aliquet.
78 | Aliquam erat volutpat. Maecenas eget dictum mauris. Suspendisse arcu eros, condimentum eget risus sed,
79 | luctus efficitur arcu. Cras ut dictum mi. Nulla congue interdum lorem, semper semper enim commodo nec.
80 |
81 | Document Subsection
82 | -------------------
83 |
84 | .. figure:: static/yi_jing_01_chien.jpg
85 | :align: right
86 | :figwidth: 200px
87 |
88 | This is a caption for a figure. Text should wrap around the caption.
89 |
90 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam efficitur in eros et blandit. Nunc maximus,
91 | nisl at auctor vestibulum, justo ex sollicitudin ligula, id faucibus urna orci tristique nisl.
92 | Duis auctor rutrum orci, in ornare lacus condimentum quis. Quisque arcu velit, facilisis quis interdum ac,
93 | hendrerit auctor mauris. Curabitur urna nibh, porttitor at ante sit amet, vestibulum interdum dolor.
94 | Duis dictum elit orci, tincidunt imperdiet sem pellentesque et. In vehicula pellentesque varius.
95 | Phasellus a turpis sollicitudin, bibendum massa et, imperdiet neque. Integer quis sapien in magna rutrum bibendum.
96 | Integer cursus ex sed magna vehicula finibus. Proin tempus orci quis dolor tempus, nec condimentum odio vestibulum.
97 | Etiam efficitur sollicitudin libero, tincidunt volutpat ligula interdum sed. Praesent congue sagittis nisl et suscipit.
98 | Vivamus sagittis risus et egestas commodo.Cras venenatis arcu in pharetra interdum.
99 | Donec quis metus porttitor tellus cursus lobortis. Quisque et orci magna. Fusce rhoncus mi mi,
100 | at vehicula massa rhoncus quis. Mauris augue leo, pretium eget molestie vitae, efficitur nec nulla.
101 | In hac habitasse platea dictumst. Sed sit amet imperdiet purus.
102 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/js/theme.js:
--------------------------------------------------------------------------------
1 | var jQuery = (typeof(window) != 'undefined') ? window.jQuery : require('jquery');
2 |
3 | // Sphinx theme nav state
4 | function ThemeNav () {
5 |
6 | var nav = {
7 | navBar: null,
8 | win: null,
9 | winScroll: false,
10 | winResize: false,
11 | linkScroll: false,
12 | winPosition: 0,
13 | winHeight: null,
14 | docHeight: null,
15 | isRunning: false
16 | };
17 |
18 | nav.enable = function () {
19 | var self = this;
20 |
21 | if (!self.isRunning) {
22 | self.isRunning = true;
23 | jQuery(function ($) {
24 | self.init($);
25 |
26 | self.reset();
27 | self.win.on('hashchange', self.reset);
28 |
29 | // Set scroll monitor
30 | self.win.on('scroll', function () {
31 | if (!self.linkScroll) {
32 | self.winScroll = true;
33 | }
34 | });
35 | setInterval(function () { if (self.winScroll) self.onScroll(); }, 25);
36 |
37 | // Set resize monitor
38 | self.win.on('resize', function () {
39 | self.winResize = true;
40 | });
41 | setInterval(function () { if (self.winResize) self.onResize(); }, 25);
42 | self.onResize();
43 | });
44 | };
45 | };
46 |
47 | nav.init = function ($) {
48 | var doc = $(document),
49 | self = this;
50 |
51 | this.navBar = $('div.wy-side-scroll:first');
52 | this.win = $(window);
53 |
54 | // Set up javascript UX bits
55 | $(document)
56 | // Shift nav in mobile when clicking the menu.
57 | .on('click', "[data-toggle='wy-nav-top']", function() {
58 | $("[data-toggle='wy-nav-shift']").toggleClass("shift");
59 | $("[data-toggle='rst-versions']").toggleClass("shift");
60 | })
61 |
62 | // Nav menu link click operations
63 | .on('click', ".wy-menu-vertical .current ul li a", function() {
64 | var target = $(this);
65 | // Close menu when you click a link.
66 | $("[data-toggle='wy-nav-shift']").removeClass("shift");
67 | $("[data-toggle='rst-versions']").toggleClass("shift");
68 | // Handle dynamic display of l3 and l4 nav lists
69 | self.toggleCurrent(target);
70 | self.hashChange();
71 | })
72 | .on('click', "[data-toggle='rst-current-version']", function() {
73 | $("[data-toggle='rst-versions']").toggleClass("shift-up");
74 | })
75 |
76 | // Make tables responsive
77 | $("table.docutils:not(.field-list)")
78 | .wrap("");
79 |
80 | // Add expand links to all parents of nested ul
81 | $('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () {
82 | var link = $(this);
83 | expand = $('');
84 | expand.on('click', function (ev) {
85 | self.toggleCurrent(link);
86 | ev.stopPropagation();
87 | return false;
88 | });
89 | link.prepend(expand);
90 | });
91 | };
92 |
93 | nav.reset = function () {
94 | // Get anchor from URL and open up nested nav
95 | var anchor = encodeURI(window.location.hash);
96 | if (anchor) {
97 | try {
98 | var link = $('.wy-menu-vertical')
99 | .find('[href="' + anchor + '"]');
100 | // If we didn't find a link, it may be because we clicked on
101 | // something that is not in the sidebar (eg: when using
102 | // sphinxcontrib.httpdomain it generates headerlinks but those
103 | // aren't picked up and placed in the toctree). So let's find
104 | // the closest header in the document and try with that one.
105 | if (link.length === 0) {
106 | var doc_link = $('.document a[href="' + anchor + '"]');
107 | var closest_section = doc_link.closest('div.section');
108 | // Try again with the closest section entry.
109 | link = $('.wy-menu-vertical')
110 | .find('[href="#' + closest_section.attr("id") + '"]');
111 |
112 | }
113 | $('.wy-menu-vertical li.toctree-l1 li.current')
114 | .removeClass('current');
115 | link.closest('li.toctree-l2').addClass('current');
116 | link.closest('li.toctree-l3').addClass('current');
117 | link.closest('li.toctree-l4').addClass('current');
118 | }
119 | catch (err) {
120 | console.log("Error expanding nav for anchor", err);
121 | }
122 | }
123 | };
124 |
125 | nav.onScroll = function () {
126 | this.winScroll = false;
127 | var newWinPosition = this.win.scrollTop(),
128 | winBottom = newWinPosition + this.winHeight,
129 | navPosition = this.navBar.scrollTop(),
130 | newNavPosition = navPosition + (newWinPosition - this.winPosition);
131 | if (newWinPosition < 0 || winBottom > this.docHeight) {
132 | return;
133 | }
134 | this.navBar.scrollTop(newNavPosition);
135 | this.winPosition = newWinPosition;
136 | };
137 |
138 | nav.onResize = function () {
139 | this.winResize = false;
140 | this.winHeight = this.win.height();
141 | this.docHeight = $(document).height();
142 | };
143 |
144 | nav.hashChange = function () {
145 | this.linkScroll = true;
146 | this.win.one('hashchange', function () {
147 | this.linkScroll = false;
148 | });
149 | };
150 |
151 | nav.toggleCurrent = function (elem) {
152 | var parent_li = elem.closest('li');
153 | parent_li.siblings('li.current').removeClass('current');
154 | parent_li.siblings().find('li.current').removeClass('current');
155 | parent_li.find('> ul li.current').removeClass('current');
156 | parent_li.toggleClass('current');
157 | }
158 |
159 | return nav;
160 | };
161 |
162 | module.exports.ThemeNav = ThemeNav();
163 |
164 | if (typeof(window) != 'undefined') {
165 | window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
166 | }
167 |
--------------------------------------------------------------------------------
/docs/_themes/sphinx_rtd_theme/sphinx_rtd_theme/static/js/theme.js:
--------------------------------------------------------------------------------
1 | require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o");
80 |
81 | // Add expand links to all parents of nested ul
82 | $('.wy-menu-vertical ul').not('.simple').siblings('a').each(function () {
83 | var link = $(this);
84 | expand = $('
');
85 | expand.on('click', function (ev) {
86 | self.toggleCurrent(link);
87 | ev.stopPropagation();
88 | return false;
89 | });
90 | link.prepend(expand);
91 | });
92 | };
93 |
94 | nav.reset = function () {
95 | // Get anchor from URL and open up nested nav
96 | var anchor = encodeURI(window.location.hash);
97 | if (anchor) {
98 | try {
99 | var link = $('.wy-menu-vertical')
100 | .find('[href="' + anchor + '"]');
101 | // If we didn't find a link, it may be because we clicked on
102 | // something that is not in the sidebar (eg: when using
103 | // sphinxcontrib.httpdomain it generates headerlinks but those
104 | // aren't picked up and placed in the toctree). So let's find
105 | // the closest header in the document and try with that one.
106 | if (link.length === 0) {
107 | var doc_link = $('.document a[href="' + anchor + '"]');
108 | var closest_section = doc_link.closest('div.section');
109 | // Try again with the closest section entry.
110 | link = $('.wy-menu-vertical')
111 | .find('[href="#' + closest_section.attr("id") + '"]');
112 |
113 | }
114 | $('.wy-menu-vertical li.toctree-l1 li.current')
115 | .removeClass('current');
116 | link.closest('li.toctree-l2').addClass('current');
117 | link.closest('li.toctree-l3').addClass('current');
118 | link.closest('li.toctree-l4').addClass('current');
119 | }
120 | catch (err) {
121 | console.log("Error expanding nav for anchor", err);
122 | }
123 | }
124 | };
125 |
126 | nav.onScroll = function () {
127 | this.winScroll = false;
128 | var newWinPosition = this.win.scrollTop(),
129 | winBottom = newWinPosition + this.winHeight,
130 | navPosition = this.navBar.scrollTop(),
131 | newNavPosition = navPosition + (newWinPosition - this.winPosition);
132 | if (newWinPosition < 0 || winBottom > this.docHeight) {
133 | return;
134 | }
135 | this.navBar.scrollTop(newNavPosition);
136 | this.winPosition = newWinPosition;
137 | };
138 |
139 | nav.onResize = function () {
140 | this.winResize = false;
141 | this.winHeight = this.win.height();
142 | this.docHeight = $(document).height();
143 | };
144 |
145 | nav.hashChange = function () {
146 | this.linkScroll = true;
147 | this.win.one('hashchange', function () {
148 | this.linkScroll = false;
149 | });
150 | };
151 |
152 | nav.toggleCurrent = function (elem) {
153 | var parent_li = elem.closest('li');
154 | parent_li.siblings('li.current').removeClass('current');
155 | parent_li.siblings().find('li.current').removeClass('current');
156 | parent_li.find('> ul li.current').removeClass('current');
157 | parent_li.toggleClass('current');
158 | }
159 |
160 | return nav;
161 | };
162 |
163 | module.exports.ThemeNav = ThemeNav();
164 |
165 | if (typeof(window) != 'undefined') {
166 | window.SphinxRtdTheme = { StickyNav: module.exports.ThemeNav };
167 | }
168 |
169 | },{"jquery":"jquery"}]},{},["sphinx-rtd-theme"]);
170 |
--------------------------------------------------------------------------------
/pubmarine/__init__.py:
--------------------------------------------------------------------------------
1 | # This file is part of PubMarine.
2 | #
3 | # PubMarine is free software: you can redistribute it and/or modify
4 | # it under the terms of the GNU Lesser General Public License as published by
5 | # the Free Software Foundation, either version 3 of the License, or
6 | # (at your option) any later version.
7 | #
8 | # Foobar is distributed in the hope that it will be useful,
9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | # GNU Lesser General Public License for more details.
12 | #
13 | # You should have received a copy of the GNU Lesser General Public License
14 | # along with PubMarine. If not, see
.
15 | #
16 | # Copyright: 2017, Toshio Kuratomi
17 | # License: LGPLv3+
18 | """
19 | PubMarine is a simple PubSub framework for Python3's asyncio.
20 |
21 | Authors: Toshio Kuratomi