././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1536692852.0336921 jeepney-0.4.2/.gitignore0000644000000000000000000000006100000000000013270 0ustar0000000000000000__pycache__/ /dist/ /docs/_build/ .pytest_cache/ ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1495972857.3032477 jeepney-0.4.2/.gitlab-ci.yml0000644000000000000000000000017000000000000013735 0ustar0000000000000000image: python:3.5 before_script: - pip install pytest testpath test_job_py35: script: - py.test stage: test ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1488126885.7924078 jeepney-0.4.2/LICENSE0000644000000000000000000000207100000000000012310 0ustar0000000000000000The MIT License (MIT) Copyright (c) 2017 Thomas Kluyver Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496068908.3313727 jeepney-0.4.2/README.rst0000644000000000000000000000073400000000000012776 0ustar0000000000000000This is a low-level, pure Python DBus protocol client. It has an `I/O-free `__ core, and integration modules for different event loops. DBus is an inter-process communication system, mainly used in Linux. `Jeepney docs on Readthedocs `__ This project is experimental, and there are a number of `more mature Python DBus bindings `__. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496064397.9796813 jeepney-0.4.2/docs/Makefile0000644000000000000000000001667200000000000013707 0ustar0000000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " applehelp to make an Apple Help Book" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " epub3 to make an epub3" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" @echo " coverage to run coverage check of the documentation (if enabled)" @echo " dummy to check syntax errors of document sources" .PHONY: clean clean: rm -rf $(BUILDDIR)/* .PHONY: html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." .PHONY: qthelp qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Jeepney.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Jeepney.qhc" .PHONY: applehelp applehelp: $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp @echo @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." @echo "N.B. You won't be able to view it unless you put it in" \ "~/Library/Documentation/Help or install it in your application" \ "bundle." .PHONY: devhelp devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/Jeepney" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Jeepney" @echo "# devhelp" .PHONY: epub epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: epub3 epub3: $(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3 @echo @echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3." .PHONY: latex latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." .PHONY: latexpdf latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: latexpdfja latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." .PHONY: text text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." .PHONY: info info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." .PHONY: gettext gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." .PHONY: doctest doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." .PHONY: coverage coverage: $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage @echo "Testing of coverage in the sources finished, look at the " \ "results in $(BUILDDIR)/coverage/python.txt." .PHONY: xml xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." .PHONY: pseudoxml pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." .PHONY: dummy dummy: $(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy @echo @echo "Build finished. Dummy builder generates no files." ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1496068245.103242 jeepney-0.4.2/docs/bindgen.rst0000644000000000000000000000126300000000000014375 0ustar0000000000000000Generating D-Bus wrappers ========================= D-Bus includes a mechanism to introspect remote objects and discover the methods they define. Jeepney can use this to generate classes defining the messages to send. Use it like this:: python3 -m jeepney.bindgen --name org.freedesktop.Notifications \ --path /org/freedesktop/Notifications This command will produce the code used in the previous page (see :ref:`msggen_proxies`). You specify *name*—which D-Bus service you're talking to—and *path*—an object in that service. Jeepney will generate a wrapper for each interface that object has, except for some standard ones like the introspection interface itself. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1565520637.7967727 jeepney-0.4.2/docs/conf.py0000644000000000000000000002334200000000000013536 0ustar0000000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Jeepney documentation build configuration file, created by # sphinx-quickstart on Mon May 29 14:26:37 2017. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath('..')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The encoding of source files. # # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Jeepney' copyright = '2017, Thomas Kluyver' author = 'Thomas Kluyver' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. import jeepney version = jeepney.__version__ # The full version, including alpha/beta/rc tags. release = version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # # today = '' # # Else, today_fmt is used as the format for a strftime call. # # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The reST default role (used for this markup: `text`) to use for all # documents. # # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. # # show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # import sphinx_rtd_theme html_theme = "sphinx_rtd_theme" html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. # " v documentation" by default. # # html_title = 'Jeepney v0.3' # A shorter title for the navigation bar. Default is the same as html_title. # # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # # html_logo = None # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. # # html_extra_path = [] # If not None, a 'Last updated on:' timestamp is inserted at every page # bottom, using the given strftime format. # The empty string is equivalent to '%b %d, %Y'. # # html_last_updated_fmt = None # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # # html_additional_pages = {} # If false, no module index is generated. # # html_domain_indices = True # If false, no index is generated. # # html_use_index = True # If true, the index is split into individual pages for each letter. # # html_split_index = False # If true, links to the reST sources are added to the pages. # # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh' # # html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # 'ja' uses this config value. # 'zh' user can custom change `jieba` dictionary path. # # html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. # # html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. htmlhelp_basename = 'Jeepneydoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'Jeepney.tex', 'Jeepney Documentation', 'Thomas Kluyver', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # # latex_use_parts = False # If true, show page references after internal links. # # latex_show_pagerefs = False # If true, show URL addresses after external links. # # latex_show_urls = False # Documents to append as an appendix to all manuals. # # latex_appendices = [] # It false, will not define \strong, \code, itleref, \crossref ... but only # \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added # packages. # # latex_keep_old_macro_names = True # If false, no module index is generated. # # latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'jeepney', 'Jeepney Documentation', [author], 1) ] # If true, show URL addresses after external links. # # man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'Jeepney', 'Jeepney Documentation', author, 'Jeepney', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # # texinfo_appendices = [] # If false, no module index is generated. # # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # # texinfo_no_detailmenu = False ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1565520637.7977726 jeepney-0.4.2/docs/index.rst0000644000000000000000000000233600000000000014100 0ustar0000000000000000Jeepney |version| ================= Jeepney is a pure Python interface to D-Bus, a protocol for interprocess communication on desktop Linux (mostly). The core of Jeepney is `I/O free `__, and the ``jeepney.integrate`` package contains bindings for different event loops to handle I/O. Jeepney tries to be *non-magical*, so you may have to write a bit more code than with other interfaces such as `dbus-python `_ or `pydbus `_. Jeepney doesn't rely on libdbus or other compiled libraries, so it's easy to install with Python tools like ``pip``. To use it, the DBus daemon needs to be running on your computer; this is a standard part of most modern Linux desktops. Contents: .. toctree:: :maxdepth: 2 integrate limitations messages bindgen release-notes .. seealso:: `D-Feet `__ App for exploring available D-Bus services on your machine. `D-Bus Specification `__ Technical details about the D-Bus protocol. Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1524992204.5619812 jeepney-0.4.2/docs/integrate.rst0000644000000000000000000000272500000000000014755 0ustar0000000000000000Connecting to DBus and sending messages ======================================= So far, Jeepney can be used with three different I/O systems: - Blocking (synchronous) I/O - `asyncio `_ - `Tornado `_ For each of these, there is a module in ``jeepney.integrate`` which exposes a function called ``connect_and_authenticate``. This establishes a DBus connection and returns an object you can use to send and receive messages. Exactly what it returns may vary, though. Here's an example of sending a desktop notification, using blocking I/O: .. literalinclude:: /../examples/blocking_notify.py And here is the same thing using asyncio: .. literalinclude:: /../examples/aio_notify_noproxy.py .. _msggen_proxies: Message generators and proxies ------------------------------ If you're calling a number of different methods, you can make a *message generator* class containing their definitions. Jeepney includes a tool to generate these classes automatically—see :doc:`bindgen`. Message generators define how to construct messages. *Proxies* are wrappers around message generators which send a message and get the reply back. Let's rewrite the example above to use a message generator and a proxy: .. literalinclude:: /../examples/aio_notify.py This is more code for the simple use case here, but in a larger application collecting the message definitions together like this could make it clearer. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1525174355.934796 jeepney-0.4.2/docs/limitations.rst0000644000000000000000000000130600000000000015321 0ustar0000000000000000Limitations =========== Some lesser-used parts of the D-Bus spec are not implemented: 1. Jeepney only connects to Unix domain sockets. This is how D-Bus is normally exposed, but the specification allows for other transports, such as TCP sockets, which Jeepney does not support. 2. Only the 'external' auth method is used. The specification recommends this mechanism where it's available, and it's the obvious thing to use with Unix domain sockets. 3. Sending and receiving Unix file descriptors is not supported. Any of these limitations may be lifted in the future, if there's a need and we can find a clean way to do so. If you want to remove a limitation, be prepared to get involved. :-) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1525175829.565636 jeepney-0.4.2/docs/messages.rst0000644000000000000000000000270300000000000014576 0ustar0000000000000000Making and parsing messages =========================== The core of Jeepney is code to build, serialise and deserialise DBus messages. .. module:: jeepney .. autoclass:: Message .. automethod:: serialise .. autoclass:: Parser .. automethod:: feed Making messages --------------- .. autoclass:: DBusAddress .. autofunction:: new_method_call .. autofunction:: new_method_return .. autofunction:: new_error .. autofunction:: new_signal .. seealso:: :ref:`msggen_proxies` Signatures ~~~~~~~~~~ DBus is strongly typed, and every message has a *signature* describing the body data. These are strings using characters such as ``i`` for a signed 32-bit integer. See the `DBus specification `_ for the full list. Jeepney does not try to guess or discover the signature when you build a message: your code must explicitly specify a signature for every message. However, Jeepney can help you write this code: see :doc:`bindgen`. In most cases, DBus types have an obvious corresponding type in Python. However, a few types require further explanation: * DBus *ARRAY* are Python lists, except for arrays of *DICT_ENTRY*, which are dicts. * DBus *STRUCT* are Python tuples. * DBus *VARIANT* are 2-tuples ``(signature, data)``. E.g. to put a string into a variant field, you would pass the data ``("s", "my string")``. * Jeepney does not (yet) support sending or receiving file descriptors. ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1578076686.976187 jeepney-0.4.2/docs/release-notes.rst0000644000000000000000000000267200000000000015542 0ustar0000000000000000Release notes ============= 0.4.2 ----- * The blocking ``DBusConnection`` integration class now has a ``.close()`` method, and can be used as a context manager:: from jeepney.integrate.blocking import connect_and_authenticate with connect_and_authenticate() as connection: ... 0.4.1 ----- * Avoid using :class:`asyncio.Future` for the blocking integration. * Set the 'destination' field on method return and error messages to the 'sender' from the parent message. Thanks to Oscar Caballero and Thomas Grainger for contributing to this release. 0.4 --- * Authentication failures now raise a new :exc:`AuthenticationError` subclass of :exc:`ValueError`, so that they can be caught specifically. * Fixed logic error when authentication is rejected. * Use *effective* user ID for authentication instead of *real* user ID. In typical use cases these are the same, but where they differ, effective uid seems to be the relevant one. * The 64 MiB size limit for an array is now checked when serialising it. * New function :func:`jeepney.auth.make_auth_anonymous` to prepare an anonymous authentication message. This is not used by the wrappers in Jeepney at the moment, but may be useful for third party code in some situations. * New examples for subscribing to D-Bus signals, with blocking I/O and with asyncio. * Various improvements to documentation. Thanks to Jane Soko and Gitlab user xiretza for contributing to this release. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1488121892.3649094 jeepney-0.4.2/examples/aio_hello.py0000644000000000000000000000041200000000000015423 0ustar0000000000000000import asyncio from jeepney.integrate.asyncio import connect_and_authenticate async def hello(): (t, p) = await connect_and_authenticate(bus='SESSION') print('My ID is:', p.unique_name) loop = asyncio.get_event_loop() loop.run_until_complete(hello()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1537732210.7234025 jeepney-0.4.2/examples/aio_notify.py0000644000000000000000000000345600000000000015643 0ustar0000000000000000import asyncio from jeepney import MessageGenerator, new_method_call from jeepney.integrate.asyncio import connect_and_authenticate, Proxy # ---- Message generator, created by jeepney.bindgen ---- class Notifications(MessageGenerator): interface = 'org.freedesktop.Notifications' def __init__(self, object_path='/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications'): super().__init__(object_path=object_path, bus_name=bus_name) def Notify(self, arg_0, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6, arg_7): return new_method_call(self, 'Notify', 'susssasa{sv}i', (arg_0, arg_1, arg_2, arg_3, arg_4, arg_5, arg_6, arg_7)) def CloseNotification(self, arg_0): return new_method_call(self, 'CloseNotification', 'u', (arg_0,)) def GetCapabilities(self): return new_method_call(self, 'GetCapabilities') def GetServerInformation(self): return new_method_call(self, 'GetServerInformation') # ---- End auto generated code ---- async def send_notification(): (transport, protocol) = await connect_and_authenticate(bus='SESSION') proxy = Proxy(Notifications(), protocol) resp = await proxy.Notify('jeepney_test', # App name 0, # Not replacing any previous notification '', # Icon 'Hello, world!', # Summary 'This is an example notification from Jeepney', [], {}, # Actions, hints -1, # expire_timeout (-1 = default) ) print('Notification ID:', resp[0]) if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(send_notification()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1578076453.9306874 jeepney-0.4.2/examples/aio_notify_noproxy.py0000644000000000000000000000223500000000000017433 0ustar0000000000000000import asyncio from jeepney import DBusAddress, new_method_call from jeepney.integrate.asyncio import connect_and_authenticate notifications = DBusAddress('/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications', interface='org.freedesktop.Notifications') async def send_notification(): (transport, protocol) = await connect_and_authenticate(bus='SESSION') msg = new_method_call(notifications, 'Notify', 'susssasa{sv}i', ('jeepney_test', # App name 0, # Not replacing any previous notification '', # Icon 'Hello, world!', # Summary 'This is an example notification from Jeepney', [], {}, # Actions, hints -1, # expire_timeout (-1 = default) )) # Send the message and await the reply reply = await protocol.send_message(msg) print('Notification ID:', reply[0]) loop = asyncio.get_event_loop() loop.run_until_complete(send_notification()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496069275.7025495 jeepney-0.4.2/examples/aio_secretstorage.py0000644000000000000000000000230000000000000017170 0ustar0000000000000000"""Example accessing the SecretStorage DBus interface with asyncio APIs https://freedesktop.org/wiki/Specifications/secret-storage-spec/secrets-api-0.1.html#ref-dbus-api """ import asyncio from jeepney import new_method_call, DBusAddress, Properties from jeepney.integrate.asyncio import connect_and_authenticate secrets = DBusAddress('/org/freedesktop/secrets', bus_name= 'org.freedesktop.secrets', interface='org.freedesktop.Secret.Service') login_keyring = DBusAddress('/org/freedesktop/secrets/collection/login', bus_name= 'org.freedesktop.secrets', interface='org.freedesktop.Secret.Collection') msg = new_method_call(login_keyring, 'SearchItems', 'a{ss}', ({ 'user': 'tk2e15', },) ) async def send_notification(): (t, p) = await connect_and_authenticate(bus='SESSION') resp = await p.send_message(Properties(secrets).get('Collections')) print('Collections:', resp[0][1]) resp = await p.send_message(msg) print('Search res:', resp) loop = asyncio.get_event_loop() loop.run_until_complete(send_notification()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1537732592.3020396 jeepney-0.4.2/examples/aio_subscribe.py0000644000000000000000000000467100000000000016314 0ustar0000000000000000""" This example simulates two clients interacting with the message bus, more or less independently. Client 1 Any app on the bus (here, called *service*). It asks the bus for sole custody of its preferred name, a `well-known bus name`_ that it wants others to recognize. Client 2 An interested party (here, called *watcher*). It asks the bus to emit a signal (send it an update) whenever *service*'s well-known bus name changes hands. It uses Jeepney's ``router.subscribe_signal`` to fire a callback every time this occurs. .. _well-known bus name: https://dbus.freedesktop.org/doc /dbus-specification.html#message-protocol-names """ import asyncio from jeepney.integrate.asyncio import connect_and_authenticate, Proxy from jeepney.bus_messages import message_bus, MatchRule well_known_bus_name = "io.readthedocs.jeepney.aio_subscribe_example" def print_match(match_info): """A demo callback triggered on successful matches.""" print("[watcher] match hit:", match_info) async def main(): __, service_proto = await connect_and_authenticate("SESSION") __, watcher_proto = await connect_and_authenticate("SESSION") # Interface objects used by each app to interact with the message bus service = Proxy(message_bus, service_proto) watcher = Proxy(message_bus, watcher_proto) # Create a "signal-selection" match rule match_rule = MatchRule( type="signal", sender=message_bus.bus_name, interface=message_bus.interface, member="NameOwnerChanged", path=message_bus.object_path, ) # Condition: arg number 0 must match the bus name (try changing either) match_rule.add_arg_condition(0, well_known_bus_name) # Register a callback watcher_proto.router.subscribe_signal( callback=print_match, path=message_bus.object_path, interface=message_bus.interface, member="NameOwnerChanged" ) # Tell the session bus to pass us matching signal messages. print("[watcher] adding match rule") await watcher.AddMatch(match_rule) await asyncio.sleep(1) print("[service] calling 'RequestName'") resp = await service.RequestName(well_known_bus_name, 4) print("[service] reply:", (None, "primary owner", "in queue", "exists", "already owned")[resp[0]]) await asyncio.sleep(1) if __name__ == "__main__": loop = asyncio.get_event_loop() loop.run_until_complete(main()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574453557.4868345 jeepney-0.4.2/examples/blocking_notify.py0000644000000000000000000000215100000000000016652 0ustar0000000000000000from jeepney import DBusAddress, new_method_call from jeepney.integrate.blocking import connect_and_authenticate notifications = DBusAddress('/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications', interface='org.freedesktop.Notifications') connection = connect_and_authenticate(bus='SESSION') # Construct a new D-Bus message. new_method_call takes the address, the # method name, the signature string, and a tuple of arguments. msg = new_method_call(notifications, 'Notify', 'susssasa{sv}i', ('jeepney_test', # App name 0, # Not replacing any previous notification '', # Icon 'Hello, world!', # Summary 'This is an example notification from Jeepney', [], {}, # Actions, hints -1, # expire_timeout (-1 = default) )) # Send the message and wait for the reply reply = connection.send_and_get_reply(msg) print('Notification ID:', reply[0]) connection.close() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574453563.8028474 jeepney-0.4.2/examples/blocking_secretstorage.py0000644000000000000000000000257400000000000020225 0ustar0000000000000000"""Example accessing the SecretStorage DBus interface with blocking APIs https://freedesktop.org/wiki/Specifications/secret-storage-spec/secrets-api-0.1.html#ref-dbus-api """ from jeepney import new_method_call, DBusAddress, Properties from jeepney.integrate.blocking import connect_and_authenticate secrets = DBusAddress('/org/freedesktop/secrets', bus_name= 'org.freedesktop.secrets', interface='org.freedesktop.Secret.Service') login_keyring = DBusAddress('/org/freedesktop/secrets/collection/login', bus_name= 'org.freedesktop.secrets', interface='org.freedesktop.Secret.Collection') list_items = new_method_call(login_keyring, 'SearchItems', 'a{ss}', ([],)) conn = connect_and_authenticate(bus='SESSION') resp = conn.send_and_get_reply(Properties(secrets).get('Collections')) print('Collections:', resp[0][1]) print('\nItems in login collection:') all_items = conn.send_and_get_reply(list_items)[0] for obj_path in all_items: item = DBusAddress(obj_path, 'org.freedesktop.secrets', interface='org.freedesktop.Secret.Item') props_resp = conn.send_and_get_reply(Properties(item).get_all()) props = dict(props_resp[0]) state = '(locked)' if props['Locked'][1] else '' print(props['Label'][1], state) conn.close() ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1574453573.062865 jeepney-0.4.2/examples/blocking_subscribe.py0000644000000000000000000000430200000000000017323 0ustar0000000000000000""" Example of subscribing to a D-Bus signal using blocking I/O. This subscribes to the signal for a desktop notification being closed. To try it, start this script, then trigger a desktop notification, and close it somehow to trigger the signal. Use Ctrl-C to stop the script. This example relies on the ``org.freedesktop.Notifications.NotificationClosed`` signal; some desktops may not support it. See the notification spec for more details: https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html Match rules are defined in the D-Bus specification: https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules """ from jeepney.bus_messages import MatchRule, message_bus from jeepney.integrate.blocking import connect_and_authenticate, Proxy from jeepney.wrappers import DBusAddress noti = DBusAddress('/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications', interface='org.freedesktop.Notifications') connection = connect_and_authenticate(bus="SESSION") match_rule = MatchRule( type="signal", sender=noti.bus_name, interface=noti.interface, member="NotificationClosed", path=noti.object_path, ) # This defines messages for talking to the D-Bus bus daemon itself: session_bus = Proxy(message_bus, connection) # Tell the session bus to pass us matching signal messages: print("Match added?", session_bus.AddMatch(match_rule) == ()) reasons = {1: 'expiry', 2: 'dismissal', 3: 'dbus', '4': 'undefined'} def notification_closed(data): """Callback for when we receive a notification closed signal""" nid, reason_no = data reason = reasons.get(reason_no, 'unknown') print('Notification {} closed by: {}'.format(nid, reason)) # Connect the callback to the relevant signal connection.router.subscribe_signal( callback=notification_closed, path=noti.object_path, interface=noti.interface, member="NotificationClosed" ) # Using dbus-send or d-feet or blocking_notify.py, send a notification and # manually close it or call ``.CloseNotification`` after a beat. try: while True: connection.recv_messages() except KeyboardInterrupt: pass connection.close() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1578076753.9283805 jeepney-0.4.2/jeepney/__init__.py0000644000000000000000000000034400000000000015054 0ustar0000000000000000"""Low-level, pure Python DBus protocol wrapper. """ from .auth import AuthenticationError from .low_level import Message, Parser from .bus import find_session_bus, find_system_bus from .wrappers import * __version__ = '0.4.2' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1537731911.7425144 jeepney-0.4.2/jeepney/auth.py0000644000000000000000000000247100000000000014261 0ustar0000000000000000from binascii import hexlify import os def make_auth_external(): hex_uid = hexlify(str(os.geteuid()).encode('ascii')) return b'AUTH EXTERNAL %b\r\n' % hex_uid def make_auth_anonymous(): """Format an AUTH command line for the ANONYMOUS mechanism Jeepney's higher-level wrappers don't currently use this mechanism, but third-party code may choose to. See for details. """ from . import __version__ trace = hexlify(('Jeepney %s' % __version__).encode('ascii')) return b'AUTH ANONYMOUS %s\r\n' % trace BEGIN = b'BEGIN\r\n' class AuthenticationError(ValueError): """Raised when DBus authentication fails""" def __init__(self, data): self.data = data def __str__(self): return "Authentication failed. Bus sent: %r" % self.data class SASLParser: def __init__(self): self.buffer = b'' self.authenticated = False self.error = None def process_line(self, line): if line.startswith(b'OK '): self.authenticated = True else: self.error = line def feed(self, data): self.buffer += data while (b'\r\n' in self.buffer) and not self.authenticated: line, self.buffer = self.buffer.split(b'\r\n', 1) self.process_line(line) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496063726.3009348 jeepney-0.4.2/jeepney/bindgen.py0000644000000000000000000000772600000000000014736 0ustar0000000000000000"""Generate a wrapper class from DBus introspection data""" import argparse from textwrap import indent import xml.etree.ElementTree as ET from jeepney.wrappers import Introspectable from jeepney.integrate.blocking import connect_and_authenticate from jeepney import __version__ class Method: def __init__(self, xml_node): self.name = xml_node.attrib['name'] self.in_args = [] self.signature = [] for arg in xml_node.findall("arg[@direction='in']"): try: name = arg.attrib['name'] except KeyError: name = 'arg{}'.format(len(self.in_args)) self.in_args.append(name) self.signature.append(arg.attrib['type']) def _make_code_noargs(self): return ("def {name}(self):\n" " return new_method_call(self, '{name}')\n").format( name=self.name) def make_code(self): if not self.in_args: return self._make_code_noargs() args = ', '.join(self.in_args) signature = ''.join(self.signature) tuple = ('({},)' if len(self.in_args) == 1 else '({})').format(args) return ("def {name}(self, {args}):\n" " return new_method_call(self, '{name}', '{signature}',\n" " {tuple})\n").format( name=self.name, args=args, signature=signature, tuple=tuple ) INTERFACE_CLASS_TEMPLATE = """ class {cls_name}(MessageGenerator): interface = {interface!r} def __init__(self, object_path={path!r}, bus_name={bus_name!r}): super().__init__(object_path=object_path, bus_name=bus_name) """ class Interface: def __init__(self, xml_node, path, bus_name): self.name = xml_node.attrib['name'] self.path = path self.bus_name = bus_name self.methods = [Method(node) for node in xml_node.findall('method')] def make_code(self): cls_name = self.name.split('.')[-1] chunks = [INTERFACE_CLASS_TEMPLATE.format(cls_name=cls_name, interface=self.name, path=self.path, bus_name=self.bus_name)] for method in self.methods: chunks.append(indent(method.make_code(), ' ' * 4)) return '\n'.join(chunks) MODULE_TEMPLATE = '''\ """Auto-generated DBus bindings Generated by jeepney version {version} Object path: {path} Bus name : {bus_name} """ from jeepney.wrappers import MessageGenerator, new_method_call ''' # Jeepney already includes bindings for these common interfaces IGNORE_INTERFACES = { 'org.freedesktop.DBus.Introspectable', 'org.freedesktop.DBus.Properties', 'org.freedesktop.DBus.Peer', } def code_from_xml(xml, path, bus_name, fh): if isinstance(fh, (bytes, str)): with open(fh, 'w') as f: return code_from_xml(xml, path, bus_name, f) root = ET.fromstring(xml) fh.write(MODULE_TEMPLATE.format(version=__version__, path=path, bus_name=bus_name)) i = 0 for interface_node in root.findall('interface'): if interface_node.attrib['name'] in IGNORE_INTERFACES: continue fh.write(Interface(interface_node, path, bus_name).make_code()) i += 1 return i def generate(path, name, output_file, bus='SESSION'): conn = connect_and_authenticate(bus) msg = Introspectable(path, name).Introspect() xml = conn.send_and_get_reply(msg)[0] # print(xml) n_interfaces = code_from_xml(xml, path, name, output_file) print("Written {} interface wrappers to {}".format(n_interfaces, output_file)) def main(): ap = argparse.ArgumentParser() ap.add_argument('-n', '--name', required=True) ap.add_argument('-p', '--path', required=True) ap.add_argument('--bus', default='SESSION') ap.add_argument('-o', '--output') args = ap.parse_args() output = args.output or (args.path[1:].replace('/', '_') + '.py') generate(args.path, args.name, output, args.bus) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1488127325.1232011 jeepney-0.4.2/jeepney/bus.py0000644000000000000000000000343100000000000014106 0ustar0000000000000000import os import re _escape_pat = re.compile(r'%([0-9A-Fa-f]{2})') def unescape(v): def repl(match): n = int(match.group(1), base=16) return chr(n) return _escape_pat.sub(repl, v) def parse_addresses(s): for addr in s.split(';'): transport, info = addr.split(':', 1) kv = {} for x in info.split(','): k, v = x.split('=', 1) kv[k] = unescape(v) yield (transport, kv) SUPPORTED_TRANSPORTS = ('unix',) def get_connectable_addresses(addr): unsupported_transports = set() found = False for transport, kv in parse_addresses(addr): if transport not in SUPPORTED_TRANSPORTS: unsupported_transports.add(transport) elif transport == 'unix': if 'abstract' in kv: yield '\0' + kv['abstract'] found = True elif 'path' in kv: yield kv['path'] found = True if not found: raise RuntimeError("DBus transports ({}) not supported. Supported: {}" .format(unsupported_transports, SUPPORTED_TRANSPORTS)) def find_session_bus(): addr = os.environ['DBUS_SESSION_BUS_ADDRESS'] return next(get_connectable_addresses(addr)) # TODO: fallbacks to X, filesystem def find_system_bus(): addr = os.environ.get('DBUS_SYSTEM_BUS_ADDRESS', '') \ or 'unix:path=/var/run/dbus/system_bus_socket' return next(get_connectable_addresses(addr)) def get_bus(addr): if addr == 'SESSION': return find_session_bus() elif addr == 'SYSTEM': return find_system_bus() else: return next(get_connectable_addresses(addr)) if __name__ == '__main__': print('System bus at:', find_system_bus()) print('Session bus at:', find_session_bus()) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496049770.1681256 jeepney-0.4.2/jeepney/bus_messages.py0000644000000000000000000001256000000000000016000 0ustar0000000000000000"""Messages for talking to the DBus daemon itself Generated by jeepney.bindgen and modified by hand. """ from .wrappers import MessageGenerator, new_method_call class DBusNameFlags: allow_replacement = 1 replace_existing = 2 do_not_queue = 4 class DBus(MessageGenerator): interface = 'org.freedesktop.DBus' def __init__(self, object_path='/org/freedesktop/DBus', bus_name='org.freedesktop.DBus'): super().__init__(object_path=object_path, bus_name=bus_name) def Hello(self): return new_method_call(self, 'Hello') def RequestName(self, name, flags=0): return new_method_call(self, 'RequestName', 'su', (name, flags)) def ReleaseName(self, name): return new_method_call(self, 'ReleaseName', 's', (name,)) def StartServiceByName(self, name): return new_method_call(self, 'StartServiceByName', 'su', (name, 0)) def UpdateActivationEnvironment(self, env): return new_method_call(self, 'UpdateActivationEnvironment', 'a{ss}', (env,)) def NameHasOwner(self, name): return new_method_call(self, 'NameHasOwner', 's', (name,)) def ListNames(self): return new_method_call(self, 'ListNames') def ListActivatableNames(self): return new_method_call(self, 'ListActivatableNames') def AddMatch(self, rule): if isinstance(rule, MatchRule): rule = rule.serialise() return new_method_call(self, 'AddMatch', 's', (rule,)) def RemoveMatch(self, rule): if isinstance(rule, MatchRule): rule = rule.serialise() return new_method_call(self, 'RemoveMatch', 's', (rule,)) def GetNameOwner(self, name): return new_method_call(self, 'GetNameOwner', 's', (name,)) def ListQueuedOwners(self, name): return new_method_call(self, 'ListQueuedOwners', 's', (name,)) def GetConnectionUnixUser(self, name): return new_method_call(self, 'GetConnectionUnixUser', 's', (name,)) def GetConnectionUnixProcessID(self, name): return new_method_call(self, 'GetConnectionUnixProcessID', 's', (name,)) def GetAdtAuditSessionData(self, name): return new_method_call(self, 'GetAdtAuditSessionData', 's', (name,)) def GetConnectionSELinuxSecurityContext(self, name): return new_method_call(self, 'GetConnectionSELinuxSecurityContext', 's', (name,)) def ReloadConfig(self): return new_method_call(self, 'ReloadConfig') def GetId(self): return new_method_call(self, 'GetId') def GetConnectionCredentials(self, name): return new_method_call(self, 'GetConnectionCredentials', 's', (name,)) message_bus = DBus() class Monitoring(MessageGenerator): interface = 'org.freedesktop.DBus.Monitoring' def __init__(self, object_path='/org/freedesktop/DBus', bus_name='org.freedesktop.DBus'): super().__init__(object_path=object_path, bus_name=bus_name) def BecomeMonitor(self, rules): return new_method_call(self, 'BecomeMonitor', 'asu', (rules, 0)) class Stats(MessageGenerator): interface = 'org.freedesktop.DBus.Debug.Stats' def __init__(self, object_path='/org/freedesktop/DBus', bus_name='org.freedesktop.DBus'): super().__init__(object_path=object_path, bus_name=bus_name) def GetStats(self): return new_method_call(self, 'GetStats') def GetConnectionStats(self, arg0): return new_method_call(self, 'GetConnectionStats', 's', (arg0,)) def GetAllMatchRules(self): return new_method_call(self, 'GetAllMatchRules') class MatchRule: """Construct a match rule to subscribe to DBus messages. e.g.:: mr = MatchRule(interface='org.freedesktop.DBus', member='NameOwnerChanged', type='signal') msg = add_match(mr) # Send this message to subscribe to the signal """ def __init__(self, *, type=None, sender=None, interface=None, member=None, path=None, path_namespace=None, destination=None, eavesdrop=False): self.conditions = c ={} if type: c['type'] = type if sender: c['sender'] = sender if interface: c['interface'] = interface if member: c['member'] = member if path: c['path'] = path if path_namespace: c['path_namespace'] = path_namespace if destination: c['destination'] = destination if eavesdrop: c['eavesdrop'] = 'true' def add_arg_condition(self, argno, value, kind='string'): """Add a condition for a particular argument argno: int, 0-63 kind: 'string', 'path', 'namespace' """ if kind not in {'string', 'path', 'namespace'}: raise ValueError("kind={!r}".format(kind)) if kind == 'namespace' and argno != 0: raise ValueError("argno must be 0 for kind='namespace'") if kind == 'string': kind = '' name = 'arg{}{}'.format(argno, kind) self.conditions[name] = value def serialise(self): parts = [] for k, v in sorted(self.conditions.items()): parts.append('{}={}'.format(k, v.replace("'", r"\'"))) return ','.join(parts) ././@PaxHeader0000000000000000000000000000003200000000000011450 xustar000000000000000026 mtime=1488110945.47843 jeepney-0.4.2/jeepney/integrate/__init__.py0000644000000000000000000000000000000000000017023 0ustar0000000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574461942.1193795 jeepney-0.4.2/jeepney/integrate/asyncio.py0000644000000000000000000000464400000000000016753 0ustar0000000000000000import asyncio from jeepney.auth import SASLParser, make_auth_external, BEGIN, AuthenticationError from jeepney.bus import get_bus from jeepney.low_level import Parser, MessageType from jeepney.wrappers import ProxyBase from jeepney.routing import Router from jeepney.bus_messages import message_bus class DBusProtocol(asyncio.Protocol): def __init__(self): self.auth_parser = SASLParser() self.parser = Parser() self.router = Router(asyncio.Future) self.authentication = asyncio.Future() self.unique_name = None def connection_made(self, transport): self.transport = transport self.transport.write(b'\0' + make_auth_external()) def _authenticated(self): self.transport.write(BEGIN) self.authentication.set_result(True) self.data_received = self.data_received_post_auth self.data_received(self.auth_parser.buffer) def data_received(self, data): self.auth_parser.feed(data) if self.auth_parser.authenticated: self._authenticated() elif self.auth_parser.error: self.authentication.set_exception(AuthenticationError(self.auth_parser.error)) def data_received_post_auth(self, data): for msg in self.parser.feed(data): self.router.incoming(msg) def send_message(self, message): if not self.authentication.done(): raise RuntimeError("Wait for authentication before sending messages") future = self.router.outgoing(message) data = message.serialise() self.transport.write(data) return future class Proxy(ProxyBase): def __init__(self, msggen, protocol): super().__init__(msggen) self._protocol = protocol def __repr__(self): return 'Proxy({}, {})'.format(self._msggen, self._protocol) def _method_call(self, make_msg): def inner(*args, **kwargs): msg = make_msg(*args, **kwargs) assert msg.header.message_type is MessageType.method_call return self._protocol.send_message(msg) return inner async def connect_and_authenticate(bus='SESSION', loop=None): if loop is None: loop = asyncio.get_event_loop() (t, p) = await loop.create_unix_connection(DBusProtocol, path=get_bus(bus)) await p.authentication bus = Proxy(message_bus, p) hello_reply = await bus.Hello() p.unique_name = hello_reply[0] return (t, p) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1574453484.8696887 jeepney-0.4.2/jeepney/integrate/blocking.py0000644000000000000000000000624200000000000017072 0ustar0000000000000000"""Synchronous IO wrappers around jeepney """ import functools import socket from jeepney.auth import SASLParser, make_auth_external, BEGIN, AuthenticationError from jeepney.bus import get_bus from jeepney.low_level import Parser, MessageType from jeepney.wrappers import ProxyBase from jeepney.routing import Router from jeepney.bus_messages import message_bus class _Future: def __init__(self): self._result = None def done(self): return bool(self._result) def set_exception(self, exception): self._result = (False, exception) def set_result(self, result): self._result = (True, result) def result(self): success, value = self._result if success: return value raise value class DBusConnection: def __init__(self, sock): self.sock = sock self.parser = Parser() self.router = Router(_Future) self.bus_proxy = Proxy(message_bus, self) hello_reply = self.bus_proxy.Hello() self.unique_name = hello_reply[0] def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() return False def send_message(self, message): future = self.router.outgoing(message) data = message.serialise() self.sock.sendall(data) return future def recv_messages(self): """Read data from the socket and handle incoming messages. Blocks until at least one message has been read. """ while True: b = self.sock.recv(4096) msgs = self.parser.feed(b) if msgs: for msg in msgs: self.router.incoming(msg) return def send_and_get_reply(self, message): """Send a message, wait for the reply and return it. """ future = self.send_message(message) while not future.done(): self.recv_messages() return future.result() def close(self): self.sock.close() class Proxy(ProxyBase): def __init__(self, msggen, connection): super().__init__(msggen) self._connection = connection def __repr__(self): return "Proxy({}, {})".format(self._msggen, self._connection) def _method_call(self, make_msg): @functools.wraps(make_msg) def inner(*args, **kwargs): msg = make_msg(*args, **kwargs) assert msg.header.message_type is MessageType.method_call return self._connection.send_and_get_reply(msg) return inner def connect_and_authenticate(bus='SESSION'): bus_addr = get_bus(bus) sock = socket.socket(family=socket.AF_UNIX) sock.connect(bus_addr) sock.sendall(b'\0' + make_auth_external()) auth_parser = SASLParser() while not auth_parser.authenticated: auth_parser.feed(sock.recv(1024)) if auth_parser.error: raise AuthenticationError(auth_parser.error) sock.sendall(BEGIN) conn = DBusConnection(sock) conn.parser.buf = auth_parser.buffer return conn if __name__ == '__main__': conn = connect_and_authenticate() print("Unique name:", conn.unique_name) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1536692863.851728 jeepney-0.4.2/jeepney/integrate/tornado.py0000644000000000000000000000545200000000000016752 0ustar0000000000000000import socket from tornado.concurrent import Future from tornado.gen import coroutine from tornado.ioloop import IOLoop from tornado.iostream import IOStream from jeepney.auth import SASLParser, make_auth_external, BEGIN, AuthenticationError from jeepney.bus import get_bus from jeepney.low_level import Parser, MessageType from jeepney.wrappers import ProxyBase from jeepney.routing import Router from jeepney.bus_messages import message_bus class DBusConnection: def __init__(self, bus_addr): self.auth_parser = SASLParser() self.parser = Parser() self.router = Router(Future) self.authentication = Future() self.unique_name = None self._sock = socket.socket(family=socket.AF_UNIX) self.stream = IOStream(self._sock, read_chunk_size=4096) def connected(): self.stream.write(b'\0' + make_auth_external()) self.stream.connect(bus_addr, connected) self.stream.read_until_close(streaming_callback=self.data_received) def _authenticated(self): self.stream.write(BEGIN) self.authentication.set_result(True) self.data_received_post_auth(self.auth_parser.buffer) def data_received(self, data): if self.authentication.done(): return self.data_received_post_auth(data) self.auth_parser.feed(data) if self.auth_parser.authenticated: self._authenticated() elif self.auth_parser.error: self.authentication.set_exception(AuthenticationError(self.auth_parser.error)) def data_received_post_auth(self, data): for msg in self.parser.feed(data): self.router.incoming(msg) def send_message(self, message): if not self.authentication.done(): raise RuntimeError("Wait for authentication before sending messages") future = self.router.outgoing(message) data = message.serialise() self.stream.write(data) return future class Proxy(ProxyBase): def __init__(self, msggen, connection): super().__init__(msggen) self._connection = connection def __repr__(self): return 'Proxy({}, {})'.format(self._msggen, self._connection) def _method_call(self, make_msg): def inner(*args, **kwargs): msg = make_msg(*args, **kwargs) assert msg.header.message_type is MessageType.method_call return self._connection.send_message(msg) return inner @coroutine def connect_and_authenticate(bus='SESSION'): bus_addr = get_bus(bus) conn = DBusConnection(bus_addr) yield conn.authentication conn.unique_name = (yield Proxy(message_bus, conn).Hello())[0] return conn if __name__ == '__main__': conn = IOLoop.current().run_sync(connect_and_authenticate) print("Unique name is:", conn.unique_name) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1578076453.9326873 jeepney-0.4.2/jeepney/low_level.py0000644000000000000000000003212600000000000015310 0ustar0000000000000000from enum import Enum, IntEnum import struct class SizeLimitError(ValueError): pass class Endianness(Enum): little = 1 big = 2 def struct_code(self): return '<' if (self is Endianness.little) else '>' def dbus_code(self): return b'l' if (self is Endianness.little) else b'B' endian_map = {b'l': Endianness.little, b'B': Endianness.big} class MessageType(Enum): method_call = 1 method_return = 2 error = 3 signal = 4 msg_type_map = {t.value: t for t in MessageType} # Flags: NO_REPLY_EXPECTED = 1 NO_AUTO_START = 2 ALLOW_INTERACTIVE_AUTHORIZATION = 4 class HeaderFields(IntEnum): path = 1 interface = 2 member = 3 error_name = 4 reply_serial = 5 destination = 6 sender = 7 signature = 8 unix_fds = 9 header_fields_map = {t.value: t for t in HeaderFields} def padding(pos, step): pad = step - (pos % step) if pad == step: return 0 return pad class FixedType: def __init__(self, size, struct_code): self.size = self.alignment = size self.struct_code = struct_code def parse_data(self, buf, pos, endianness): pos += padding(pos, self.alignment) code = endianness.struct_code() + self.struct_code val = struct.unpack(code, buf[pos:pos + self.size])[0] return val, pos + self.size def serialise(self, data, pos, endianness): pad = b'\0' * padding(pos, self.alignment) code = endianness.struct_code() + self.struct_code return pad + struct.pack(code, data) def __repr__(self): return 'FixedType({!r}, {!r})'.format(self.size, self.struct_code) def __eq__(self, other): return (type(other) is FixedType) and (self.size == other.size) \ and (self.struct_code == other.struct_code) simple_types = { 'y': FixedType(1, 'B'), # unsigned 8 bit 'b': FixedType(4, 'I'), # bool 'n': FixedType(2, 'h'), # signed 16 bit 'q': FixedType(2, 'H'), # unsigned 16 bit 'i': FixedType(4, 'i'), # signed 32-bit 'u': FixedType(4, 'I'), # unsigned 32-bit 'x': FixedType(8, 'q'), # signed 64-bit 't': FixedType(8, 'Q'), # unsigned 64-bit 'd': FixedType(8, 'd'), # double 'h': FixedType(8, 'I'), # file descriptor (32-bit unsigned, index) TODO } class StringType: def __init__(self, length_type): self.length_type = length_type @property def alignment(self): return self.length_type.size def parse_data(self, buf, pos, endianness): length, pos = self.length_type.parse_data(buf, pos, endianness) end = pos + length val = buf[pos:end].decode('utf-8') assert buf[end:end + 1] == b'\0' return val, end + 1 def serialise(self, data, pos, endianness): if not isinstance(data, str): raise TypeError("Expected str, not {!r}".format(data)) encoded = data.encode('utf-8') len_data = self.length_type.serialise(len(encoded), pos, endianness) return len_data + encoded + b'\0' def __repr__(self): return 'StringType({!r})'.format(self.length_type) def __eq__(self, other): return (type(other) is StringType) \ and (self.length_type == other.length_type) simple_types.update({ 's': StringType(simple_types['u']), # String 'o': StringType(simple_types['u']), # Object path 'g': StringType(simple_types['y']), # Signature }) class Struct: alignment = 8 def __init__(self, fields): if any(isinstance(f, DictEntry) for f in fields): raise TypeError("Found dict entry outside array") self.fields = fields def parse_data(self, buf, pos, endianness): pos += padding(pos, 8) res = [] for field in self.fields: v, pos = field.parse_data(buf, pos, endianness) res.append(v) return tuple(res), pos def serialise(self, data, pos, endianness): if not isinstance(data, tuple): raise TypeError("Expected tuple, not {!r}".format(data)) if len(data) != len(self.fields): raise ValueError("{} entries for {} fields".format( len(data), len(self.fields) )) pad = b'\0' * padding(pos, self.alignment) pos += len(pad) res_pieces = [] for item, field in zip(data, self.fields): res_pieces.append(field.serialise(item, pos, endianness)) pos += len(res_pieces[-1]) return pad + b''.join(res_pieces) def __repr__(self): return "{}({!r})".format(type(self).__name__, self.fields) def __eq__(self, other): return (type(other) is type(self)) and (self.fields == other.fields) class DictEntry(Struct): def __init__(self, fields): if len(fields) != 2: raise TypeError( "Dict entry must have 2 fields, not %d" % len(fields)) if not isinstance(fields[0], (FixedType, StringType)): raise TypeError( "First field in dict entry must be simple type, not {}" .format(type(fields[0]))) super().__init__(fields) class Array: alignment = 4 length_type = FixedType(4, 'I') def __init__(self, elt_type): self.elt_type = elt_type def parse_data(self, buf, pos, endianness): # print('Array start', pos) length, pos = self.length_type.parse_data(buf, pos, endianness) pos += padding(pos, self.elt_type.alignment) end = pos + length res = [] while pos < end: # print('Array elem', pos) v, pos = self.elt_type.parse_data(buf, pos, endianness) res.append(v) return res, pos def serialise(self, data, pos, endianness): if isinstance(self.elt_type, DictEntry) and isinstance(data, dict): data = sorted(data.items()) elif (self.elt_type == simple_types['y']) and isinstance(data, bytes): pass elif not isinstance(data, list): raise TypeError("Not suitable for array: {!r}".format(data)) # Fail fast if we know in advance that the data is too big: if isinstance(self.elt_type, FixedType): if (self.elt_type.size * len(data)) > 2**26: raise SizeLimitError("Array size exceeds 64 MiB limit") pad1 = padding(pos, self.alignment) pos_after_length = pos + pad1 + 4 pad2 = padding(pos_after_length, self.elt_type.alignment) data_pos = pos_after_length + pad2 limit_pos = data_pos + 2**26 chunks = [] for item in data: chunks.append(self.elt_type.serialise(item, data_pos, endianness)) data_pos += len(chunks[-1]) if data_pos > limit_pos: raise SizeLimitError("Array size exceeds 64 MiB limit") buf = b''.join(chunks) len_data = self.length_type.serialise(len(buf), pos+pad1, endianness) pos += len(len_data) # print('Array ser: pad1={!r}, len_data={!r}, pad2={!r}, buf={!r}'.format( # pad1, len_data, pad2, buf)) return (b'\0' * pad1) + len_data + (b'\0' * pad2) + buf def __repr__(self): return 'Array({!r})'.format(self.elt_type) def __eq__(self, other): return (type(other) is Array) and (self.elt_type == other.elt_type) class Variant: alignment = 1 def parse_data(self, buf, pos, endianness): # print('variant', pos) sig, pos = simple_types['g'].parse_data(buf, pos, endianness) # print('variant sig:', repr(sig), pos) valtype = parse_signature(list(sig)) val, pos = valtype.parse_data(buf, pos, endianness) # print('variant done', (sig, val), pos) return (sig, val), pos def serialise(self, data, pos, endianness): sig, data = data valtype = parse_signature(list(sig)) sig_buf = simple_types['g'].serialise(sig, pos, endianness) return sig_buf + valtype.serialise(data, pos + len(sig_buf), endianness) def __repr__(self): return 'Variant()' def __eq__(self, other): return type(other) is Variant def parse_signature(sig): """Parse a symbolic signature into objects. """ # Based on http://norvig.com/lispy.html token = sig.pop(0) if token == 'a': return Array(parse_signature(sig)) if token == 'v': return Variant() elif token == '(': fields = [] while sig[0] != ')': fields.append(parse_signature(sig)) sig.pop(0) # ) return Struct(fields) elif token == '{': de = [] while sig[0] != '}': de.append(parse_signature(sig)) sig.pop(0) # } return DictEntry(de) elif token in ')}': raise ValueError('Unexpected end of struct') else: return simple_types[token] def calc_msg_size(buf): endian, = struct.unpack('c', buf[:1]) endian = endian_map[endian] body_length, = struct.unpack(endian.struct_code() + 'I', buf[4:8]) fields_array_len, = struct.unpack(endian.struct_code() + 'I', buf[12:16]) header_len = 16 + fields_array_len return header_len + padding(header_len, 8) + body_length _header_fields_type = Array(Struct([simple_types['y'], Variant()])) def parse_header_fields(buf, endianness): l, pos = _header_fields_type.parse_data(buf, 12, endianness) return {header_fields_map[k]: v[1] for (k, v) in l}, pos header_field_codes = { 1: 'o', 2: 's', 3: 's', 4: 's', 5: 'u', 6: 's', 7: 's', 8: 'g', 9: 'u', } def serialise_header_fields(d, endianness): l = [(i.value, (header_field_codes[i], v)) for (i, v) in sorted(d.items())] return _header_fields_type.serialise(l, 12, endianness) class Header: def __init__(self, endianness, message_type, flags, protocol_version, body_length, serial, fields): self.endianness = endianness self.message_type = message_type self.flags = flags self.protocol_version = protocol_version self.body_length = body_length self.serial = serial self.fields = fields def __repr__(self): return 'Header({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, fields={!r})'.format( self.endianness, self.message_type, self.flags, self.protocol_version, self.body_length, self.serial, self.fields) def serialise(self): s = self.endianness.struct_code() + 'cBBBII' return struct.pack(s, self.endianness.dbus_code(), self.message_type.value, self.flags, self.protocol_version, self.body_length, self.serial) \ + serialise_header_fields(self.fields, self.endianness) @classmethod def from_buffer(cls, buf): endian, msgtype, flags, pv = struct.unpack('cBBB', buf[:4]) endian = endian_map[endian] bodylen, serial = struct.unpack(endian.struct_code() + 'II', buf[4:12]) fields, pos = parse_header_fields(buf, endian) return cls(endian, msg_type_map[msgtype], flags, pv, bodylen, serial, fields), pos class Message: """Object representing a DBus message. It's not normally necessary to construct this directly: use higher level functions and methods instead. """ def __init__(self, header, body): self.header = header self.body = body def __repr__(self): return "{}({!r}, {!r})".format(type(self).__name__, self.header, self.body) @classmethod def from_buffer(cls, buf): header, pos = Header.from_buffer(buf) body = () if HeaderFields.signature in header.fields: sig = header.fields[HeaderFields.signature] body_type = parse_signature(list('(%s)' % sig)) body = body_type.parse_data(buf, pos, header.endianness)[0] return cls(header, body) def serialise(self): """Convert this message to bytes.""" endian = self.header.endianness if HeaderFields.signature in self.header.fields: sig = self.header.fields[HeaderFields.signature] body_type = parse_signature(list('(%s)' % sig)) body_buf = body_type.serialise(self.body, 0, endian) else: body_buf = b'' self.header.body_length = len(body_buf) header_buf = self.header.serialise() pad = b'\0' * padding(len(header_buf), 8) return header_buf + pad + body_buf class Parser: """Parse DBus messages from a stream of incoming data. """ def __init__(self): self.buf = b'' self.next_msg_size = None def feed(self, data): """Feed the parser newly read data. Returns a list of messages completed by the new data. """ self.buf += data return list(iter(self._read1, None)) def _read1(self): if self.next_msg_size is None: if len(self.buf) >= 16: self.next_msg_size = calc_msg_size(self.buf) nms = self.next_msg_size if (nms is not None) and len(self.buf) >= nms: raw_msg, self.buf = self.buf[:nms], self.buf[nms:] msg = Message.from_buffer(raw_msg) self.next_msg_size = None return msg ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496054627.1641717 jeepney-0.4.2/jeepney/routing.py0000644000000000000000000000445600000000000015014 0ustar0000000000000000from .low_level import MessageType, HeaderFields from .wrappers import DBusErrorResponse class Router: """Routing for messages coming back to a client application. :param handle_factory: Constructor for an object like asyncio.Future, with methods *set_result* and *set_exception*. Outgoing method call messages will get a handle associated with them. :param on_unhandled: Callback for messages not otherwise dispatched. """ def __init__(self, handle_factory, on_unhandled=None): self.handle_factory = handle_factory self.on_unhandled = on_unhandled self.outgoing_serial = 0 self.awaiting_reply = {} self.signal_callbacks = {} def outgoing(self, msg): """Set the serial number in the message & make a handle if a method call """ self.outgoing_serial += 1 msg.header.serial = self.outgoing_serial if msg.header.message_type is MessageType.method_call: self.awaiting_reply[msg.header.serial] = handle = self.handle_factory() return handle def subscribe_signal(self, callback, path, interface, member): """Add a callback for a signal. """ self.signal_callbacks[(path, interface, member)] = callback def incoming(self, msg): """Route an incoming message. """ hdr = msg.header # Signals: if hdr.message_type is MessageType.signal: key = (hdr.fields.get(HeaderFields.path, None), hdr.fields.get(HeaderFields.interface, None), hdr.fields.get(HeaderFields.member, None) ) cb = self.signal_callbacks.get(key, None) if cb is not None: cb(msg.body) return # Method returns & errors reply_serial = hdr.fields.get(HeaderFields.reply_serial, -1) reply_handle = self.awaiting_reply.pop(reply_serial, None) if reply_handle is not None: if hdr.message_type is MessageType.method_return: reply_handle.set_result(msg.body) return elif hdr.message_type is MessageType.error: reply_handle.set_exception(DBusErrorResponse(msg)) return if self.on_unhandled: self.on_unhandled(msg) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1488048345.5744677 jeepney-0.4.2/jeepney/tests/__init__.py0000644000000000000000000000000000000000000016203 0ustar0000000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496063709.4918911 jeepney-0.4.2/jeepney/tests/secrets_introspect.xml0000644000000000000000000001073700000000000020560 0ustar0000000000000000 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1537731654.3740282 jeepney-0.4.2/jeepney/tests/test_auth.py0000644000000000000000000000103600000000000016456 0ustar0000000000000000from jeepney import auth def test_make_auth_external(): b = auth.make_auth_external() assert b.startswith(b'AUTH EXTERNAL') def test_make_auth_anonymous(): b = auth.make_auth_anonymous() assert b.startswith(b'AUTH ANONYMOUS') def test_parser(): p = auth.SASLParser() p.feed(b'OK 728d62bc2eb394') assert not p.authenticated p.feed(b'1ebbb0b42958b1e0d6\r\n') assert p.authenticated def test_parser_rejected(): p = auth.SASLParser() p.feed(b'REJECTED EXTERNAL\r\n') assert not p.authenticated ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496064311.9834578 jeepney-0.4.2/jeepney/tests/test_bindgen.py0000644000000000000000000000211200000000000017117 0ustar0000000000000000from io import StringIO import os.path from jeepney.low_level import MessageType, HeaderFields from jeepney.bindgen import code_from_xml sample_file = os.path.join(os.path.dirname(__file__), 'secrets_introspect.xml') def test_bindgen(): with open(sample_file) as f: xml = f.read() sio = StringIO() n_interfaces = code_from_xml(xml, path='/org/freedesktop/secrets', bus_name='org.freedesktop.secrets', fh=sio) # 5 interfaces defined, but we ignore Properties, Introspectable, Peer assert n_interfaces == 2 # Run the generated code, defining the message generator classes. binding_ns = {} exec(sio.getvalue(), binding_ns) Service = binding_ns['Service'] # Check basic functionality of the Service class assert Service.interface == 'org.freedesktop.Secret.Service' msg = Service().SearchItems({"service": "foo", "user": "bar"}) assert msg.header.message_type is MessageType.method_call assert msg.header.fields[HeaderFields.destination] == 'org.freedesktop.secrets' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1488127671.3603182 jeepney-0.4.2/jeepney/tests/test_bus.py0000644000000000000000000000151700000000000016312 0ustar0000000000000000import pytest from testpath import modified_env from jeepney import bus def test_get_connectable_addresses(): a = list(bus.get_connectable_addresses('unix:path=/run/user/1000/bus')) assert a == ['/run/user/1000/bus'] a = list(bus.get_connectable_addresses('unix:abstract=/tmp/foo')) assert a == ['\0/tmp/foo'] with pytest.raises(RuntimeError): list(bus.get_connectable_addresses('unix:tmpdir=/tmp')) def test_get_bus(): with modified_env({ 'DBUS_SESSION_BUS_ADDRESS':'unix:path=/run/user/1000/bus', 'DBUS_SYSTEM_BUS_ADDRESS': 'unix:path=/var/run/dbus/system_bus_socket' }): assert bus.get_bus('SESSION') == '/run/user/1000/bus' assert bus.get_bus('SYSTEM') == '/var/run/dbus/system_bus_socket' assert bus.get_bus('unix:path=/run/user/1002/bus') == '/run/user/1002/bus' ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1536692852.0366921 jeepney-0.4.2/jeepney/tests/test_low_level.py0000644000000000000000000000477200000000000017517 0ustar0000000000000000import pytest from jeepney.low_level import * HELLO_METHOD_CALL = ( b'l\x01\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00m\x00\x00\x00\x01\x01o\x00\x15' b'\x00\x00\x00/org/freedesktop/DBus\x00\x00\x00\x02\x01s\x00\x14\x00\x00\x00' b'org.freedesktop.DBus\x00\x00\x00\x00\x03\x01s\x00\x05\x00\x00\x00Hello\x00' b'\x00\x00\x06\x01s\x00\x14\x00\x00\x00org.freedesktop.DBus\x00\x00\x00\x00') def test_parser_simple(): msg = Parser().feed(HELLO_METHOD_CALL)[0] assert msg.header.fields[HeaderFields.member] == 'Hello' def chunks(src, size): pos = 0 while pos < len(src): end = pos + size yield src[pos:end] pos = end def test_parser_chunks(): p = Parser() chunked = list(chunks(HELLO_METHOD_CALL, 16)) for c in chunked[:-1]: assert p.feed(c) == [] msg = p.feed(chunked[-1])[0] assert msg.header.fields[HeaderFields.member] == 'Hello' def test_multiple(): msgs = Parser().feed(HELLO_METHOD_CALL * 6) assert len(msgs) == 6 for msg in msgs: assert msg.header.fields[HeaderFields.member] == 'Hello' def test_roundtrip(): msg = Parser().feed(HELLO_METHOD_CALL)[0] assert msg.serialise() == HELLO_METHOD_CALL def test_serialise_dict(): data = { 'a': 'b', 'de': 'f', } string_type = simple_types['s'] sig = Array(DictEntry([string_type, string_type])) print(sig.serialise(data, 0, Endianness.little)) assert sig.serialise(data, 0, Endianness.little) == ( b'\x1e\0\0\0' + # Length b'\0\0\0\0' + # Padding b'\x01\0\0\0a\0\0\0' + b'\x01\0\0\0b\0\0\0' + b'\x02\0\0\0de\0\0' + b'\x01\0\0\0f\0' ) def test_parse_signature(): sig = parse_signature(list('(a{sv}(oayays)b)')) print(sig) assert sig == Struct([ Array(DictEntry([simple_types['s'], Variant()])), Struct([ simple_types['o'], Array(simple_types['y']), Array(simple_types['y']), simple_types['s'] ]), simple_types['b'], ]) class fake_list(list): def __init__(self, n): super().__init__() self._n = n def __len__(self): return self._n def __iter__(self): return iter(range(self._n)) def test_array_limit(): # The spec limits arrays to 64 MiB a = Array(FixedType(8, 'Q')) # 'at' - array of uint64 a.serialise(fake_list(100), 0, Endianness.little) with pytest.raises(SizeLimitError): a.serialise(fake_list(2**23 + 1), 0, Endianness.little) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1496058352.3221672 jeepney-0.4.2/jeepney/tests/test_routing.py0000644000000000000000000000167700000000000017217 0ustar0000000000000000from asyncio import Future import pytest from jeepney.routing import Router from jeepney.wrappers import new_method_return, new_error, DBusErrorResponse from jeepney.bus_messages import message_bus def test_message_reply(): router = Router(Future) call = message_bus.Hello() future = router.outgoing(call) router.incoming(new_method_return(call, 's', ('test',))) assert future.result() == ('test',) def test_error(): router = Router(Future) call = message_bus.Hello() future = router.outgoing(call) router.incoming(new_error(call, 'TestError', 'u', (31,))) with pytest.raises(DBusErrorResponse) as e: future.result() assert e.value.name == 'TestError' assert e.value.data == (31,) def test_unhandled(): unhandled = [] router = Router(Future, on_unhandled=unhandled.append) msg = message_bus.Hello() router.incoming(msg) assert len(unhandled) == 1 assert unhandled[0] == msg ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1565520637.7987726 jeepney-0.4.2/jeepney/wrappers.py0000644000000000000000000001551300000000000015164 0ustar0000000000000000from typing import Union from warnings import warn from .low_level import * __all__ = [ 'DBusAddress', 'new_method_call', 'new_method_return', 'new_error', 'new_signal', 'MessageGenerator', 'Properties', 'DBusErrorResponse', ] class DBusAddress: """This identifies the object and interface a message is for. e.g. messages to display desktop notifications would have this address:: DBusAddress('/org/freedesktop/Notifications', bus_name='org.freedesktop.Notifications', interface='org.freedesktop.Notifications') """ def __init__(self, object_path, bus_name=None, interface=None): self.object_path = object_path self.bus_name = bus_name self.interface = interface def __repr__(self): return '{}({!r}, bus_name={!r}, interface={!r})'.format(type(self).__name__, self.object_path, self.bus_name, self.interface) def with_interface(self, interface): return type(self)(self.object_path, self.bus_name, interface) class DBusObject(DBusAddress): def __init__(self, object_path, bus_name=None, interface=None): super().__init__(object_path, bus_name, interface) warn('Deprecated alias, use DBusAddress instead', stacklevel=2) def new_header(msg_type): return Header(Endianness.little, msg_type, flags=0, protocol_version=1, body_length=-1, serial=-1, fields={}) def new_method_call(remote_obj, method, signature=None, body=()): """Construct a new method call message :param DBusAddress remote_obj: The object to call a method on :param str method: The name of the method to call :param str signature: The DBus signature of the body data :param tuple body: Body data (i.e. method parameters) """ header = new_header(MessageType.method_call) header.fields[HeaderFields.path] = remote_obj.object_path if remote_obj.bus_name is None: raise ValueError("remote_obj.bus_name cannot be None for method calls") header.fields[HeaderFields.destination] = remote_obj.bus_name if remote_obj.interface is not None: header.fields[HeaderFields.interface] = remote_obj.interface header.fields[HeaderFields.member] = method if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_method_return(parent_msg, signature=None, body=()): """Construct a new response message :param Message parent_msg: The method call this is a reply to :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.method_return) header.fields[HeaderFields.reply_serial] = parent_msg.header.serial sender = parent_msg.header.fields.get(HeaderFields.sender, None) if sender is not None: header.fields[HeaderFields.destination] = sender if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_error(parent_msg, error_name, signature=None, body=()): """Construct a new error response message :param Message parent_msg: The method call this is a reply to :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.error) header.fields[HeaderFields.reply_serial] = parent_msg.header.serial header.fields[HeaderFields.error_name] = error_name sender = parent_msg.header.fields.get(HeaderFields.sender, None) if sender is not None: header.fields[HeaderFields.destination] = sender if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) def new_signal(emitter, signal, signature=None, body=()): """Construct a new signal message :param DBusAddress emitter: The object sending the signal :param str signal: The name of the signal :param str signature: The DBus signature of the body data :param tuple body: Body data """ header = new_header(MessageType.signal) header.fields[HeaderFields.path] = emitter.object_path if emitter.interface is None: raise ValueError("emitter.interface cannot be None for signals") header.fields[HeaderFields.interface] = emitter.interface header.fields[HeaderFields.member] = signal if signature is not None: header.fields[HeaderFields.signature] = signature return Message(header, body) class MessageGenerator: """Subclass this to define the methods available on a DBus interface. jeepney.bindgen can automatically create subclasses using introspection. """ def __init__(self, object_path, bus_name): self.object_path = object_path self.bus_name = bus_name def __repr__(self): return "{}({!r}, bus_name={!r})".format(type(self).__name__, self.object_path, self.bus_name) class ProxyBase: """A proxy is an IO-aware wrapper around a MessageGenerator Calling methods on a proxy object will send a message and wait for the reply. This is a base class for proxy implementations in jeepney.integrate. """ def __init__(self, msggen): self._msggen = msggen def __getattr__(self, item): if item.startswith('__'): raise AttributeError(item) make_msg = getattr(self._msggen, item, None) if callable(make_msg): return self._method_call(make_msg) raise AttributeError(item) def _method_call(self, make_msg): raise NotImplementedError("Needs to be implemented in subclass") class Properties: """Build messages for accessing object properties This uses the standard DBus interface org.freedesktop.DBus.Properties """ def __init__(self, obj: Union[DBusAddress, MessageGenerator]): self.obj = obj self.props_if = DBusAddress(obj.object_path, bus_name=obj.bus_name, interface='org.freedesktop.DBus.Properties') def get(self, name): return new_method_call(self.props_if, 'Get', 'ss', (self.obj.interface, name)) def get_all(self): return new_method_call(self.props_if, 'GetAll', 's', (self.obj.interface,)) def set(self, name, signature, value): return new_method_call(self.props_if, 'Set', 'ssv', (self.obj.interface, name, (signature, value))) class Introspectable(MessageGenerator): interface = 'org.freedesktop.DBus.Introspectable' def Introspect(self): return new_method_call(self, 'Introspect') class DBusErrorResponse(Exception): def __init__(self, msg): self.name = msg.header.fields.get(HeaderFields.error_name) self.data = msg.body def __str__(self): return '[{}] {}'.format(self.name, self.data) ././@PaxHeader0000000000000000000000000000003300000000000011451 xustar000000000000000027 mtime=1578076735.714305 jeepney-0.4.2/pyproject.toml0000644000000000000000000000111000000000000014210 0ustar0000000000000000[build-system] requires = ["flit_core >=2,<3"] build-backend = "flit_core.buildapi" [tool.flit.metadata] module = "jeepney" author = "Thomas Kluyver" author-email = "thomas@kluyver.me.uk" home-page = "https://gitlab.com/takluyver/jeepney" description-file = "README.rst" requires-python = ">=3.5" classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Topic :: Desktop Environment" ] [tool.flit.metadata.requires-extra] dev = ["testpath"] [tool.flit.metadata.urls] Documentation = "https://jeepney.readthedocs.io/en/latest/" jeepney-0.4.2/setup.py0000644000000000000000000000116400000000000013017 0ustar0000000000000000#!/usr/bin/env python # setup.py generated by flit for tools that don't yet use PEP 517 from distutils.core import setup packages = \ ['jeepney', 'jeepney.integrate', 'jeepney.tests'] package_data = \ {'': ['*']} extras_require = \ {'dev': ['testpath']} setup(name='jeepney', version='0.4.2', description='Low-level, pure Python DBus protocol wrapper.', author='Thomas Kluyver', author_email='thomas@kluyver.me.uk', url='https://gitlab.com/takluyver/jeepney', packages=packages, package_data=package_data, extras_require=extras_require, python_requires='>=3.5', ) jeepney-0.4.2/PKG-INFO0000644000000000000000000000032400000000000012377 0ustar0000000000000000Metadata-Version: 1.1 Name: jeepney Version: 0.4.2 Summary: Low-level, pure Python DBus protocol wrapper. Home-page: https://gitlab.com/takluyver/jeepney Author: Thomas Kluyver Author-email: thomas@kluyver.me.uk