pax_global_header00006660000000000000000000000064147451215300014514gustar00rootroot0000000000000052 comment=484aa4b9dcbc15836796a724403d92f142a714de pyliblo3-0.16.3/000077500000000000000000000000001474512153000133405ustar00rootroot00000000000000pyliblo3-0.16.3/.github/000077500000000000000000000000001474512153000147005ustar00rootroot00000000000000pyliblo3-0.16.3/.github/workflows/000077500000000000000000000000001474512153000167355ustar00rootroot00000000000000pyliblo3-0.16.3/.github/workflows/test.yml000066400000000000000000000015461474512153000204450ustar00rootroot00000000000000name: Test on: [push] jobs: alltest: runs-on: ${{ matrix.os }} strategy: matrix: os: [ "windows-latest", "ubuntu-latest", "macos-latest" ] python-version: [ "3.10", "3.12" ] # Test extremes only install-method: [ # "git", "pip" ] fail-fast: false steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: check macos arch if: ${{ runner.os }} == macos run: | uname -m - name: install from pip if: ${{ matrix.install-method == 'pip' }} run: | pip install --verbose "pyliblo3>=0.16" - name: run unittests run: | cd test python unit.py pyliblo3-0.16.3/.github/workflows/wheels.yml000066400000000000000000000065741474512153000207630ustar00rootroot00000000000000name: Build on: [push, pull_request] # on: create jobs: build_wheels: name: Build python wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ macos-latest, windows-latest, ubuntu-latest ] steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v3 - name: Install cibuildwheel run: python -m pip install cibuildwheel - name: Windows - Enable Developer Command Prompt uses: ilammy/msvc-dev-cmd@v1.7.0 - name: macos dependencies if: runner.os == 'macos' run: | python -m pip install delocate # brew install liblo git clone https://github.com/radarsat1/liblo cd liblo mkdir macosbuild cd macosbuild cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=11 -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" ../cmake cmake --build . --config Release sudo cmake --install . file /usr/local/lib/liblo.dylib - name: Install dependencies windows if: runner.os == 'windows' run: | git clone https://github.com/radarsat1/liblo cd liblo New-Item -ItemType Directory -Force -Path "windowsbuild" cd windowsbuild cmake -A x64 -DCMAKE_GENERATOR_PLATFORM=x64 -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=OFF -DWITH_CPP_TESTS=OFF -DWITH_EXAMPLES=OFF -DWITH_TOOLS=OFF ../cmake cmake --build . --config Release Get-ChildItem -Recurse cmake --install . - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse # to supply options, put them in 'env', like: env: MACOSX_DEPLOYMENT_TARGET: 11 CIBW_BUILD_VERBOSITY: 1 CIBW_BUILD: 'cp310-* cp311-* cp312-* cp313-*' CIBW_ARCHS_MACOS: 'arm64' CIBW_ARCHS_WINDOWS: AMD64 CIBW_SKIP: 'pp* *686* *-musllinux_*' # CIBW_BEFORE_ALL_LINUX: yum search liblo; yum install -y liblo-devel CIBW_BEFORE_ALL_LINUX: git clone https://github.com/radarsat1/liblo; cd liblo; mkdir linuxbuild; cd linuxbuild; cmake ../cmake; cmake --build .; cmake --install . # CIBW_BEFORE_ALL_MACOS: brew install liblo CIBW_REPAIR_WHEEL_COMMAND_MACOS: delocate-wheel --require-archs {delocate_archs} -w {dest_dir} -v {wheel} --require-target-macos-version 11 # Use delvewheel on windows CIBW_BEFORE_BUILD_WINDOWS: "pip install delvewheel" CIBW_REPAIR_WHEEL_COMMAND_WINDOWS: 'delvewheel repair --add-path "C:/Program Files/liblo/lib;C:/Program Files/liblo/bin" -w {dest_dir} {wheel}' - name: Check wheels if: runner.os == 'macos' run: | delocate-listdeps --all wheelhouse/*.whl # CIBW_SOME_OPTION: value - uses: actions/upload-artifact@v4 with: name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} path: ./wheelhouse/*.whl - name: Upload wheels env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.TWINETOKEN }} run: | echo $TWINE_USERNAME echo $TWINE_PASSWORD python -m pip install -U twine virtualenv twine upload --skip-existing wheelhouse/*.whl continue-on-error: true pyliblo3-0.16.3/.gitignore000077500000000000000000000001731474512153000153340ustar00rootroot00000000000000site/ *.egg* build/ dist/ *.o *.lo *.la *.cache *.pyc *.bak *.so *.whl .deps .idea .mypy* pyliblo3/*.c wheelhouse/ *.kate* pyliblo3-0.16.3/COPYING000077500000000000000000000635041474512153000144060ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! pyliblo3-0.16.3/MANIFEST.in000066400000000000000000000001161474512153000150740ustar00rootroot00000000000000recursive-include pyliblo3 *.py *.pyx *.pxd *.pyi recursive-include scripts * pyliblo3-0.16.3/NEWS000077500000000000000000000073401474512153000140460ustar00rootroot000000000000002024: pyliblo3 can build binary wheels for all platforms * Including native wheels for macos arm64 * All dependencies (the liblo library itself) are included within the wheels 2020: pyliblo3 can build manylinux wheels 2019: pyliblo3 is forked from pylibo * Add the possibility to delete previous registered /methods * Modernize the setup.py to make it pip installable 2011-01-29: pyliblo 0.9.1 * Changed send functions to raise an IOError if sending failed (probaby most useful with TCP connections). * Lots of code cleanup and deobfuscation. * Included unit tests in release tarball. 2010-10-22: pyliblo 0.9.0 * Support Python 3.x. As a result, pyliblo can no longer be built with Pyrex, and requires Cython >= 0.12 instead. * Added free() method to Server and ServerThread classes. * Added fileno() method to Server and ServerThread classes (thanks to Edward George). * Prefer read-only properties over getter methods (which still exist, but are now deprecated). * Added proper docstrings (copied from the HTML docs). * The minimum version of liblo required by pyliblo is now 0.26. 2009-11-30: pyliblo 0.8.1 * Release the Python GIL inside Server.recv(). * Fixed a possible segfault when the error handler was called from the * liblo server thread. 2009-09-13: pyliblo 0.8.0 * Changed license from GPL 2 to LGPL 2.1 (as did liblo in version 0.26). * Added protocol parameter to the Server class. Among other things, this allows TCP connections. * The minumum version of liblo required by pyliblo is now 0.24. * pyliblo can now be built with either Pyrex or Cython. 2009-01-19: pyliblo 0.7.2 * Fixed all compiler warnings properly in Pyrex, without patching the generated C code. * Return values of callback functions are no longer ignored, but handled as in liblo. * The send_osc script can now be run with an explicit type string, instead of trying to determine the argument types automatically. 2008-08-03: pyliblo 0.7.1 * Added manpages for send_osc and dump_osc. 2008-03-03: pyliblo 0.7.0 * Fixed memory leaks, caused by failure to free() the result of lo_server_get_url() and lo_address_get_url(). * Added parameter to Server.register_methods() to allow registering functions of an object other than the server itself. * Allow callback functions to have a variable number of arguments (*args). 2007-12-14: pyliblo 0.6.4 * Avoid creating circular references when using methods as callback functions, which in some cases prevented the server object from being deleted properly. 2007-08-10: pyliblo 0.6.3 * Patched the Pyrex-generated code to make it compile without warnings. * Always build from the existing C source by default. 2007-07-29: pyliblo 0.6.2 * Minor code cleanup, hopefully not breaking anything. * Somewhat faster conversion of blob data from and to Python lists. 2007-07-07: pyliblo 0.6.1 * Fixed a bug that caused the floats 0.0 and 1.0 to be sent as boolean. Thanks to Jesse Chappell for the patch. 2007-05-20: pyliblo 0.6 * Added support for sending bundles, optionally with timestamps. * Added previously unsupported OSC data types (timetag, midi, symbol, true/false/nil/infinitum). * New @make_method decorator. * Various bugfixes. 2007-04-28: pyliblo 0.5.1 * Fixed a stupid typo in Server.send(). 2007-04-26: pyliblo 0.5 * Simplified the way arguments are passed to callback functions. For the server side, this release is therefore incompatible with previous versions! * Some more cleanup. 2007-04-02: pyliblo 0.3 * Added class ServerThread for asynchronous dispatching of incoming messages. 2007-04-01: pyliblo 0.2 * Minor improvements. 2007-02-20: pyliblo 0.1 * Initial release. pyliblo3-0.16.3/PKG-INFO000077500000000000000000000005061474512153000144410ustar00rootroot00000000000000Metadata-Version: 1.0 Name: pyliblo3 Version: 0.16.1 Summary: fork of pyliblo (http://das.nasophon.de/pyliblo/) Home-page: https://github.com/gesellkammer/pyliblo3 Author: Dominic Sacre Author-email: dominic.sacre@gmx.de Maintainer: Eduardo Moguillansky Maintainer-email: License: LGPL Description: UNKNOWN Platform: UNKNOWN pyliblo3-0.16.3/README.md000077500000000000000000000046431474512153000146310ustar00rootroot00000000000000# pyliblo3 This is a fork of the original bindings for liblo, making it pip installable. The provided wheels include the ``liblo`` library and don't have any further dependencies, making it completely pip installable ## Example ### Simple blocking server ```python import pyliblo3 as liblo server = liblo.Server(8080) def test_handler(path, args, types, src): print(args) server.add_method("/test", None, test_handler) while True: server.recv(100) ``` ### Threaded server ```python from pyliblo3 import * import time class MyServer(ServerThread): def __init__(self, port=1234): ServerThread.__init__(self, port) @make_method('/foo', 'ifs') def foo_callback(self, path, args): i, f, s = args print(f"Received message '{path}' with arguments: {i=}, {f=}, {s=}") @make_method(None, None) def fallback(self, path, args): print(f"received unknown message '{path}' with {args=}") server = MyServer() server.start() print(f"Server started in its own thread, send messages to {server.port}. Use CTRL-C to stop") while True: send(("127.0.0.0", server.port), "/foo", 10, 1.5, "bar") send(("127.0.0.0", server.port), "/unknown", (3, 4)) time.sleep(1) ``` ---------------------- ## Documentation https://pyliblo3.readthedocs.io ----------------------- ## Installation ```bash pip install pyliblo3 ``` ## Installation from source When installing from source, ``liblo`` needs to be installed. #### Linux ```bash sudo apt install liblo-dev git clone https://github.com/gesellkammer/pyliblo3 cd pyliblo3 pip install . ``` #### MacOS First install liblo ```bash brew install liblo ``` Or, without using brew: ```bash git clone https://github.com/radarsat1/liblo cd liblo mkdir macosbuild && cd macosbuild cmake -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" ../cmake cmake --build . --config Release sudo cmake --install . ``` Then install pyliblo3 ```bash git clone https://github.com/gesellkammer/pyliblo3 cd pyliblo3 pip install . ``` #### Windows ```bash git clone https://github.com/radarsat1/liblo cd liblo New-Item -ItemType Directory -Force -Path "windowsbuild" cd windowsbuild cmake -A x64 -DCMAKE_GENERATOR_PLATFORM=x64 -DWITH_TESTS=OFF -DWITH_CPP_TESTS=OFF -DWITH_EXAMPLES=OFF -DWITH_TOOLS=OFF ../cmake cmake --build . --config Release cmake --install . ``` ```bash git clone https://github.com/gesellkammer/pyliblo3 cd pyliblo3 pip install . ``` pyliblo3-0.16.3/doc/000077500000000000000000000000001474512153000141055ustar00rootroot00000000000000pyliblo3-0.16.3/doc/Makefile000066400000000000000000000003621474512153000155460ustar00rootroot00000000000000SPHINXOPTS = SPHINXBUILD = sphinx-build BUILDDIR = build ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) . .PHONY: clean html html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html clean: -rm -rf $(BUILDDIR)/* pyliblo3-0.16.3/doc/conf.py000066400000000000000000000044671474512153000154170ustar00rootroot00000000000000# -*- coding: utf-8 -*- # import sys, os sys.path.insert(0, os.path.abspath('..')) #extensions = ['sphinx.ext.autodoc', 'sphinxcontrib.fulltoc'] extensions = ['sphinx.ext.autodoc'] templates_path = ['templates'] html_theme_path = ['theme'] exclude_patterns = ['build'] source_suffix = '.rst' master_doc = 'index' project = u'pyliblo' copyright = u'2007-2014, Dominic Sacré' version = '0.10.0' release = '' html_theme = 'nasophon' html_copy_source = False pygments_style = 'sphinx' add_module_names = False autodoc_member_order = 'bysource' autodoc_default_flags = ['members', 'undoc-members'] from sphinx.ext.autodoc import py_ext_sig_re from sphinx.util.docstrings import prepare_docstring from sphinx.domains.python import PyClassmember, PyObject, py_sig_re def process_docstring(app, what, name, obj, options, lines): """ Remove leading function signatures from docstring. """ while len(lines) and py_ext_sig_re.match(lines[0]) is not None: del lines[0] def process_signature(app, what, name, obj, options, signature, return_annotation): """ Replace function signature with those specified in the docstring. """ if hasattr(obj, '__doc__') and obj.__doc__ is not None: lines = prepare_docstring(obj.__doc__) siglines = [] for line in lines: if py_ext_sig_re.match(line) is not None: siglines.append(line) else: break if len(siglines): siglines[0] = siglines[0][siglines[0].index('('):] return ('\n'.join(siglines), None) return (signature, return_annotation) # monkey-patch PyClassmember.handle_signature() to replace __init__ # with the class name. handle_signature_orig = PyClassmember.handle_signature def handle_signature(self, sig, signode): if '__init__' in sig: m = py_sig_re.match(sig) name_prefix, name, arglist, retann = m.groups() sig = sig.replace('__init__', name_prefix[:-1]) return handle_signature_orig(self, sig, signode) PyClassmember.handle_signature = handle_signature # prevent exception fields from collapsing PyObject.doc_field_types[2].can_collapse = False def setup(app): app.connect('autodoc-process-docstring', process_docstring) app.connect('autodoc-process-signature', process_signature) pyliblo3-0.16.3/doc/index.rst000066400000000000000000000057361474512153000157610ustar00rootroot00000000000000.. module:: liblo ############################## pyliblo 0.10 API Documentation ############################## Homepage: http://das.nasophon.de/pyliblo/ The latest version of this manual can be found at http://dsacre.github.io/pyliblo/doc/. For the most part, pyliblo is just a thin wrapper around `liblo `_, which does all the real work. For questions not answered here, also see the `liblo documentation `_ and the `OSC spec `_. Module-level Functions ====================== .. autofunction:: send .. autofunction:: time OSC Server Classes ================== .. autoclass:: Server :no-members: .. automethod:: __init__ .. automethod:: recv .. automethod:: send .. automethod:: add_method .. automethod:: del_method .. automethod:: register_methods .. automethod:: add_bundle_handlers .. autoattribute:: url .. autoattribute:: port .. autoattribute:: protocol .. automethod:: fileno .. automethod:: free ------- .. autoclass:: ServerThread :no-members: .. automethod:: __init__ .. automethod:: start .. automethod:: stop .. autoclass:: make_method .. automethod:: __init__ Utility Classes =============== .. autoclass:: Address :no-members: .. automethod:: __init__ .. autoattribute:: url .. autoattribute:: hostname .. autoattribute:: port .. autoattribute:: protocol ------- .. autoclass:: Message .. automethod:: __init__ .. autoclass:: Bundle .. automethod:: __init__ ------- .. autoexception:: ServerError .. autoexception:: AddressError Mapping between OSC and Python data types ========================================= When constructing a message, pyliblo automatically converts arguments to an appropriate OSC data type. To explicitly specify the OSC data type to be transmitted, pass a ``(typetag, data)`` tuple instead. Some types can't be unambiguously recognized, so they can only be sent that way. The mapping between OSC and Python data types is shown in the following table: ========= =============== ==================================================== typetag OSC data type Python data type ========= =============== ==================================================== ``'i'`` int32 :class:`int` ``'h'`` int64 :class:`long` (Python 2.x), :class:`int` (Python 3.x) ``'f'`` float :class:`float` ``'d'`` double :class:`float` ``'c'`` char :class:`str` (single character) ``'s'`` string :class:`str` ``'S'`` symbol :class:`str` ``'m'`` midi :class:`tuple` of four :class:`int`\ s ``'t'`` timetag :class:`float` ``'T'`` true ``'F'`` false ``'N'`` nil ``'I'`` infinitum ``'b'`` blob :class:`list` of :class:`int`\ s (Python 2.x), :class:`bytes` (Python 3.x) ========= =============== ==================================================== pyliblo3-0.16.3/doc/theme/000077500000000000000000000000001474512153000152075ustar00rootroot00000000000000pyliblo3-0.16.3/doc/theme/nasophon/000077500000000000000000000000001474512153000170345ustar00rootroot00000000000000pyliblo3-0.16.3/doc/theme/nasophon/static/000077500000000000000000000000001474512153000203235ustar00rootroot00000000000000pyliblo3-0.16.3/doc/theme/nasophon/static/nasophon.css000066400000000000000000000074421474512153000226710ustar00rootroot00000000000000@import url("default.css"); body { background-color: white; margin-left: 1em; margin-right: 1em; width: 68em; margin: 0 auto; font-size: 95%; } div.related { margin-bottom: 1.2em; padding: 0.5em 0; border-top: 1px solid #ccc; margin-top: 0.5em; } div.related:first-child { border-top: 0; border-bottom: 1px solid #ccc; } div.sphinxsidebar { background-color: #f4f4f4; border-radius: 5px; line-height: 110%; } div.sphinxsidebar ul, div.sphinxsidebar ul ul { list-style: none; } div.sphinxsidebar li { margin-bottom: 0.5em; } div.sphinxsidebar li > ul { margin-top: 0.5em; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin-top: 1.5em; } div.sphinxsidebarwrapper > h3:first-child { margin-top: 0.2em; } div.sphinxsidebarwrapper { padding: 10px 10px 0px 10px; } div.sphinxsidebarwrapper > ul { margin-left: 0px; margin-right: 0px; } div.sphinxsidebar li.current { background: #fff; width: auto; border-radius: 3px; padding-bottom: 1px; } div.sphinxsidebar a.current { font-weight: bold; color: #fff; background: #52576a; border: 3px solid #52576a; border-radius: 3px; width: auto; display: block; } div.sphinxsidebar input { font-family: 'Lucida Grande',Arial,sans-serif; border: 1px solid #999999; font-size: smaller; border-radius: 3px; } div.sphinxsidebar input[type=text] { max-width: 150px; } div.body { padding: 0 0.6em 0 2.2em; } div.body p { line-height: 140%; } div.body h1 { margin: 0 -0.4em 0 -0.6em; border: 0; color: #ffffff; background: #52576a; border-radius: 5px; font-size: 150%; } div.body h1 a, div.body h1 a:visited { color: #ffffff; } div.body h2 { margin: 1.5em -0.45em 0 -0.65em; border: 0; color: #111111; background: #eeeeee; border-radius: 5px; font-size: 135%; } div.body h3 { font-weight: bold; color: #444; border: 0; font-size: 105%; padding-left: 1.25em; margin-right: 0em; padding-bottom: 0em; margin-bottom: 0em; } div.body hr { border: 0; background-color: #ccc; height: 1px; } div.body pre { border-radius: 3px; border: 1px solid #ac9; } div.body div.admonition, div.body div.impl-detail { border-radius: 3px; } div.body div.impl-detail > p { margin: 0; } div.body div.seealso { border: 1px solid #dddd66; } div.body div.note { background-color: #ffc; border: 1px solid #dd6; } div.body div.note tt { background: transparent; } div.body a { color: #127; } div.body a:visited { color: #127; } tt, pre { font-family: monospace, sans-serif; font-size: 93%; } div.body tt { border-radius: 3px; } div.body tt.descname { font-size: 100%; } div.body tt.xref, div.body a tt { } .deprecated { border-radius: 3px; } table.docutils { border: 1px solid #ddd; min-width: 20%; border-radius: 3px; margin-top: 10px; margin-bottom: 10px; } table.docutils td, table.docutils th { border: 1px solid #ddd !important; border-radius: 3px; } table p, table li { text-align: left !important; } table.docutils th { background-color: #eee; padding: 0.3em 0.5em; } table.docutils td { background-color: white; padding: 0.3em 0.5em; } table.footnote, table.footnote td { border: 0 !important; } div.footer { line-height: 150%; margin-top: -2em; text-align: right; width: auto; margin-right: 10px; } /* fix space below last line in multi-line table cells */ td .line-block { margin-bottom: 0; } /* No line break before first line of parameter description */ td.field-body strong+p { display: inline; } /* No blank lines within parameter lists */ dd, dl { margin-bottom: 0px; } a.headerlink { float: right; } pyliblo3-0.16.3/doc/theme/nasophon/theme.conf000066400000000000000000000007631474512153000210130ustar00rootroot00000000000000[theme] inherit = default stylesheet = nasophon.css pygments_style = sphinx [options] bodyfont = 'Lucida Grande', Arial, sans-serif headfont = 'Lucida Grande', Arial, sans-serif footerbgcolor = white footertextcolor = #555 relbarbgcolor = #fff relbartextcolor = #666 relbarlinkcolor = #444 sidebarbgcolor = white sidebartextcolor = #111 sidebarlinkcolor = #111 bgcolor = white textcolor = #222 linkcolor = #127 visitedlinkcolor = #0127 headtextcolor = #111 headbgcolor = white headlinkcolor = #aaa pyliblo3-0.16.3/doc/theme/pydoctheme/000077500000000000000000000000001474512153000173505ustar00rootroot00000000000000pyliblo3-0.16.3/doc/theme/pydoctheme/static/000077500000000000000000000000001474512153000206375ustar00rootroot00000000000000pyliblo3-0.16.3/doc/theme/pydoctheme/static/pydoctheme.css000066400000000000000000000051451474512153000235170ustar00rootroot00000000000000@import url("default.css"); body { background-color: white; margin-left: 1em; margin-right: 1em; } div.related { margin-bottom: 1.2em; padding: 0.5em 0; border-top: 1px solid #ccc; margin-top: 0.5em; } div.related a:hover { color: #0095C4; } div.related:first-child { border-top: 0; border-bottom: 1px solid #ccc; } div.sphinxsidebar { background-color: #eeeeee; border-radius: 5px; line-height: 130%; font-size: smaller; } div.sphinxsidebar h3, div.sphinxsidebar h4 { margin-top: 1.5em; } div.sphinxsidebarwrapper > h3:first-child { margin-top: 0.2em; } div.sphinxsidebarwrapper > ul > li > ul > li { margin-bottom: 0.4em; } div.sphinxsidebar a:hover { color: #0095C4; } div.sphinxsidebar input { font-family: 'Lucida Grande',Arial,sans-serif; border: 1px solid #999999; font-size: smaller; border-radius: 3px; } div.sphinxsidebar input[type=text] { max-width: 150px; } div.body { padding: 0 0 0 1.2em; } div.body p { line-height: 140%; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { margin: 0; border: 0; padding: 0.3em 0; } div.body hr { border: 0; background-color: #ccc; height: 1px; } div.body pre { border-radius: 3px; border: 1px solid #ac9; } div.body div.admonition, div.body div.impl-detail { border-radius: 3px; } div.body div.impl-detail > p { margin: 0; } div.body div.seealso { border: 1px solid #dddd66; } div.body a { color: #00608f; } div.body a:visited { color: #30306f; } div.body a:hover { color: #00B0E4; } tt, pre { font-family: monospace, sans-serif; font-size: 96.5%; } div.body tt { border-radius: 3px; } div.body tt.descname { font-size: 120%; } div.body tt.xref, div.body a tt { font-weight: normal; } .deprecated { border-radius: 3px; } table.docutils { border: 1px solid #ddd; min-width: 20%; border-radius: 3px; margin-top: 10px; margin-bottom: 10px; } table.docutils td, table.docutils th { border: 1px solid #ddd !important; border-radius: 3px; } table p, table li { text-align: left !important; } table.docutils th { background-color: #eee; padding: 0.3em 0.5em; } table.docutils td { background-color: white; padding: 0.3em 0.5em; } table.footnote, table.footnote td { border: 0 !important; } div.footer { line-height: 150%; margin-top: -2em; text-align: right; width: auto; margin-right: 10px; } div.footer a:hover { color: #0095C4; } .refcount { color: #060; } .stableabi { color: #229; } pyliblo3-0.16.3/doc/theme/pydoctheme/theme.conf000066400000000000000000000010231474512153000213150ustar00rootroot00000000000000[theme] inherit = default stylesheet = pydoctheme.css pygments_style = sphinx [options] bodyfont = 'Lucida Grande', Arial, sans-serif headfont = 'Lucida Grande', Arial, sans-serif footerbgcolor = white footertextcolor = #555555 relbarbgcolor = white relbartextcolor = #666666 relbarlinkcolor = #444444 sidebarbgcolor = white sidebartextcolor = #444444 sidebarlinkcolor = #444444 bgcolor = white textcolor = #222222 linkcolor = #0090c0 visitedlinkcolor = #00608f headtextcolor = #1a1a1a headbgcolor = white headlinkcolor = #aaaaaa pyliblo3-0.16.3/docs/000077500000000000000000000000001474512153000142705ustar00rootroot00000000000000pyliblo3-0.16.3/docs/Reference.md000066400000000000000000000404731474512153000165200ustar00rootroot00000000000000# Reference --------- | Class | Description | | :---- | :----------- | | [Address](#address) | Address(addr, addr2=None, proto=LO_UDP) | | [AddressError](#addresserror) | Raised when trying to create an invalid `Address` object. | | [Bundle](#bundle) | Bundle(*messages) | | [Callback](#callback) | Callback(func, user_data) | | [Message](#message) | Message(path, *args) | | [Server](#server) | Server(port=None, proto=LO_DEFAULT, reg_methods=True) | | [ServerError](#servererror) | Raised when creating a liblo OSC server fails. | | [ServerThread](#serverthread) | ServerThread(port=None, proto=LO_DEFAULT, reg_methods=True) | | [make_method](#make_method) | A decorator that serves as a more convenient alternative to [Server.add_method](#add_method). | | [struct](#struct) | | | Function | Description | | :------- | :----------- | | `send` | Send a message without requiring a server | | `time` | Return the current time as a floating point number (seconds since January 1, 1900). | --------- ## Address ### ```python Address(addr, addr2=None, proto=LO_UDP) ``` An Address represents a destination for a message Possible forms: * `Address(hostname: str, port: int, proto: [int | str] = LO_UDP`) * `Address(port: int)` # Assumes localhost * `Address(url: str)` # A URL of the form 'osc.udp://hostname:1234/' Create a new `Address` object from the given hostname/port or URL. Raises: AddressError: if the given parameters do not represent a valid address. **Args** * **hostname**: the target's hostname - the name or an IP of the form '127.0.0.0'. * **port**: the port number of the target * **proto**: one of the constants `LO_UDP`, `LO_TCP`, `LO_UNIX` or a string like 'UDP', 'TCP' or 'UNIX' * **url**: a URL in liblo notation, e.g. `'osc.udp://hostname:1234/'`. --------- **Summary** | Property | Description | | :-------- | :----------- | | hostname | The address's hostname. | | port | The address's port number. | | protocol | The address's protocol (one of the constants :const:`UDP`, :const:`TCP`, or :const:`UNIX`). | | url | The address's URL. | | Method | Description | | :------ | :----------- | | [get_hostname](#get_hostname) | The hostname of this Address | | [get_port](#get_port) | The port number of this Address | | [get_protocol](#get_protocol) | The protocol used as an int | | [get_url](#get_url) | This Address as a liblo URL | --------- **Attributes** * **hostname**: The address's hostname. * **port**: The address's port number. * **protocol**: The address's protocol (one of the constants :const:`UDP`, :const:`TCP`, or :const:`UNIX`). * **url**: The address's URL. --------- **Methods** ### get\_hostname ```python Address.get_hostname(self) ``` The hostname of this Address ---------- ### get\_port ```python Address.get_port(self) ``` The port number of this Address ---------- ### get\_protocol ```python Address.get_protocol(self) ``` The protocol used as an int #### Example ```python from pyliblo3 import * address = Address('127.0.0.0', 9876) assert address.get_protocol() == LO_UDP ``` ---------- ### get\_url ```python Address.get_url(self) ``` This Address as a liblo URL --------- ## Bundle ### ```python Bundle(*messages) ``` A bundle of one or more messages to be sent and dispatched together. Possible forms: * `Bundle(*messages)` * `Bundle(timetag: float, *messages)` Create a new `Bundle` object. You can optionally specify a time at which the messages should be dispatched (as an OSC timetag float), and any number of messages to be included in the bundle. **Args** * **timetag** (`float`): optional, speficies the time at which the message should be dispatched * **messages**: any number of `Message`s to include in this `Bundle` --------- **Summary** | Method | Description | | :------ | :----------- | | [add](#add) | Add one or more messages to this bundle | --------- --------- **Methods** ### add ```python Bundle.add(self, *args) ``` Add one or more messages to this bundle Possible forms: * `add(*messages: Message)` * `add(path: str, *args)`, where path is the osc path (for example, '/path1' or '/root/subpath') and `args` are passed directly to `Message` to create a Message to be added to this Bundle Add one or more messages to the bundle. **Args** * **args**: --------- ## Callback ### ```python Callback(func, user_data) ``` Used internally to wrap a python function as a callback **Args** * **func**: the function to call * **user_data**: any python object, will be passed to the callback as the last argument --------- ## Message ### ```python Message(path, *args) ``` An OSC message, consisting of a path and arbitrary arguments. **Args** * **path** (`str`): the path of the message * **args**: any arguments passed will be added to this messag --------- **Summary** | Method | Description | | :------ | :----------- | | [add](#add) | Append the given arguments to this message | --------- --------- **Methods** ### add ```python Message.add(self, *args) ``` Append the given arguments to this message Arguments can be single values or `(typetag, data)` tuples to specify the actual type. This might be needed for numbers, to specify if a float needs to be encoded as a 32-bit (typetag = 'f') or 64-bit float (typetag = 'd'). By default, float numbers are interpreted as 32-bit floats. **Args** * **args**: each argument can be a single value or a tuple `(typetag: str, data: Any)` --------- ## make\_method ### ```python def (path: str | None, types: str, user_data) -> None ``` A decorator that serves as a more convenient alternative to [Server.add_method](#add_method). **Args** * **path** (`str | None`): the message path to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. * **types** (`str`): the argument types to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. * **user_data**: An arbitrary object that will be passed on to the decorated method every time a matching message is received. --------- **Summary** | Method | Description | | :------ | :----------- | | [__init__](#__init__) | make_method.__init__(self, path, types, user_data=None) | --------- --------- **Methods** ### \_\_init\_\_ ```python def __init__(self, path, types, user_data=None) -> None ``` make_method.__init__(self, path, types, user_data=None) **Args** * **path**: * **types**: * **user_data**: (*default*: `None`) --------- ## struct --------- **Summary** | Method | Description | | :------ | :----------- | | [__init__](#__init__) | struct.__init__(self, **kwargs) | --------- --------- **Methods** ### \_\_init\_\_ ```python def __init__(self, kwargs) -> None ``` struct.__init__(self, **kwargs) **Args** * **kwargs**: --------- ## \_ServerBase ### ```python _ServerBase(reg_methods=True) ``` --------- **Summary** | Property | Description | | :-------- | :----------- | | port | The server's port number (int) | | protocol | The server's protocol (one of the constants `LO_UDP`, `LO_TCP` or `LO_UNIX`). | | url | The server's URL. | | Method | Description | | :------ | :----------- | | [add_bundle_handlers](#add_bundle_handlers) | Add bundle notification handlers. | | [add_method](#add_method) | Register a callback for OSC messages with matching path and argument types. | | [del_method](#del_method) | Delete a callback function | | [fileno](#fileno) | Return the file descriptor of the server socket | | [get_port](#get_port) | Returns the port number of this server | | [get_protocol](#get_protocol) | Returns the protocol of this server, as an int | | [get_url](#get_url) | Returns the url of the server | | [register_methods](#register_methods) | Called internally during init if reg_methods is True | | [send](#send) | Send a message or bundle from this server to the the given target. | --------- **Attributes** * **port**: The server's port number (int) * **protocol**: The server's protocol (one of the constants `LO_UDP`, `LO_TCP` or `LO_UNIX`). * **url**: The server's URL. --------- **Methods** ### add\_bundle\_handlers ```python _ServerBase.add_bundle_handlers(self, start_handler, end_handler, user_data=None) ``` Add bundle notification handlers. **Args** * **start_handler**: a callback which fires when at the start of a bundle. This is called with the bundle's timestamp and user_data. * **end_handler**: a callback which fires when at the end of a bundle. This is called with user_data. * **user_data**: data to pass to the handlers. (*default*: `None`) ---------- ### add\_method ```python _ServerBase.add_method(self, str path, str typespec, func, user_data=None) ``` Register a callback for OSC messages with matching path and argument types. **Args** * **path** (`str`): the message path to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. * **typespec** (`str`): the argument types to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. * **func**: the callback function. This may be a global function, a class method, or any other callable object, pyliblo will know what to do either way. * **user_data**: An arbitrary object that will be passed on to *func* every time a matching message is received. (*default*: `None`) ---------- ### del\_method ```python _ServerBase.del_method(self, path, typespec=None) ``` Delete a callback function Delete a callback function. For both *path* and *typespec*, `None` may be used as a wildcard. **Args** * **path** (`str | None`): the method to delete * **typespec** (`str | None`): the typespec to match, or None to delete any method matching the given path (*default*: `None`) ---------- ### fileno ```python _ServerBase.fileno(self) ``` Return the file descriptor of the server socket **Returns**     (`int`) the file descriptor, or -1 if not supported by the underlying server protocol ---------- ### get\_port ```python _ServerBase.get_port(self) ``` Returns the port number of this server **Returns**     (`int`) port number ---------- ### get\_protocol ```python _ServerBase.get_protocol(self) ``` Returns the protocol of this server, as an int This will be one of `LO_TCP`, `LO_TCP` or `LO_UNIX` **Returns**     (`int`) the protocol as an int ---------- ### get\_url ```python _ServerBase.get_url(self) ``` Returns the url of the server **Returns**     (`str`) url of the server ---------- ### register\_methods ```python _ServerBase.register_methods(self, obj=None) ``` Called internally during init if reg_methods is True This function is usually called automatically by the server's constructor, unless its *reg_methods* parameter was set to `False`. **Args** * **obj**: The object that implements the OSC callbacks to be registered. By default this is the server object itself. (*default*: `None`) ---------- ### send ```python _ServerBase.send(self, target, *args) ``` Send a message or bundle from this server to the the given target. * `send(target, *messages)` * `send(target, path, *args)` Send a message or bundle from this server to the the given target. Arguments may be one or more `Message` or `Bundle` objects, or a single message given by its path and optional arguments. Raises: AddressError: if the given target is invalid. IOError: if the message couldn't be sent. **Args** * **target** (`Address | tuple[str, int] | str`): the address to send the message to; an `Address` object, a port number, a `(hostname, port)` tuple, or a URL. * **args**: --------- ## Server - Base Class: [_ServerBase](#_serverbase) ### ```python Server(port=None, proto=LO_DEFAULT, reg_methods=True) ``` A server that can receive OSC messages, blocking Use [ServerThread](#ServerThread) for an OSC server that runs in its own thread and never blocks. Raises: ServerError: if an error occurs created the underlying liblo server **Args** * **port** (`int | None`): a decimal port number or a UNIX socket path. If omitted, an arbitrary free UDP port will be used. * **proto** (`int | str`): one of LO_UDP, LO_TCP, LO_UNIX or LO_DEFAULT, or one of the strings 'UDP', 'TCP', 'UNIX' * **reg_methods** (`bool`): if True, register any methods decorated with the [make_method](#make_method) decorator --------- **Summary** | Method | Description | | :------ | :----------- | | [free](#free) | Free the underlying server object and close its port. | | [recv](#recv) | Receive and dispatch one OSC message. | --------- --------- **Methods** ### free ```python Server.free(self) ``` Free the underlying server object and close its port. Note that this will also happen automatically when the server is deallocated. ---------- ### recv ```python Server.recv(self, timeout=None) ``` Receive and dispatch one OSC message. Blocking by default, unless *timeout* is specified. **Args** * **timeout** (`int, float`): Time in milliseconds after which the function returns if no messages have been received. May be 0, in which case the function always returns immediately, whether messages have been received or not. (*default*: `None`) **Returns**     `True` if a message was received, otherwise `False`. --------- ## ServerThread - Base Class: [_ServerBase](#_serverbase) ### ```python ServerThread(port=None, proto=LO_DEFAULT, reg_methods=True) ``` Server running in a thread Unlike `Server`, `ServerThread` uses its own thread which runs in the background to dispatch messages. `ServerThread` has the same methods as `Server`, with the exception of `.recv`. Instead, it defines two additional methods `.start` and `.stop`. Raises: ServerError: if creating the server fails, e.g. because the given port could not be opened. !!! note Because liblo creates its own thread to receive and dispatch messages, callback functions will not be run in the main Python thread! **Args** * **port** (`int | str`): a decimal port number or a UNIX socket path. If omitted, an arbitrary free UDP port will be used. * **proto** (`int | str`): one of the constants `LO_UDP`, `LO_TCP` or `LO_UNIX` or a corresponding string 'UDP', 'TCP', 'UNIX' * **reg_methods**: if True, register any method decorated with the [make_method](#make_method) decorator --------- **Summary** | Method | Description | | :------ | :----------- | | [free](#free) | Free the underlying server object and close its port. | | [start](#start) | Start the server thread. | | [stop](#stop) | Stop the server thread. | --------- --------- **Methods** ### free ```python ServerThread.free(self) ``` Free the underlying server object and close its port. !!! note This method is called automatically when the server is deallocated. ---------- ### start ```python ServerThread.start(self) ``` Start the server thread. liblo will now start to dispatch any messages it receives. ---------- ### stop ```python ServerThread.stop(self) ``` Stop the server thread. --------- ## send ```python send(target, *args) ``` Send a message without requiring a server The function has two forms: * `send(target, *messages)` * `send(target, path, *args)` Send messages to the the given target, without requiring a server. Arguments may be one or more `Message` or `Bundle` objects, or a single message given by its path and optional arguments. Raises: AddressError: if the given target is invalid IOError: if the message couldn't be sent. **Args** * **target** (`Address | tuple[str, int] | int | str`): the address to send the message to; an `Address` object, a port number, a `(hostname, port)` tuple, or a URL. * **args** (`Any`): the information to send. These are used to construct a message --------- ## time ```python time() ``` Return the current time as a floating point number (seconds since January 1, 1900). **Returns**     (`float`) The liblo timetag as a float, representing seconds since 1900 pyliblo3-0.16.3/docs/generatedocs.py000066400000000000000000000026411474512153000173100ustar00rootroot00000000000000#!/usr/bin/env python3 import os import argparse import sys from pathlib import Path import logging import shutil try: import pyliblo3 as liblo from emlib import doctools except ImportError: import textwrap print("\n**WARNING**: Failed to update documentation. The python present in the current environment" " does not have the needed packages (pyliblo3, emlib)\n") sys.exit(1) def findRoot(): p = Path(__file__).parent if (p.parent/"setup.py").exists(): return p.parent if (p/"index.md").exists(): return p.parent if (p/"setup.py").exists(): return p raise RuntimeError(f"Could not locate the root folder, search started at {p}") def main(dest: Path): config = doctools.RenderConfig(splitName=True, includeInheritedMethods=False) markdown = doctools.generateDocsForModule(liblo._liblo, renderConfig=config, title='Reference', includeCustomExceptions=False) open(dest/"Reference.md", "w").write(markdown) if __name__ == "__main__": logging.basicConfig(level="DEBUG") doctools.logger.setLevel("DEBUG") root = findRoot() docsfolder = root / "docs" print("Documentation folder", docsfolder) assert docsfolder.exists() main(docsfolder) if not shutil.which('mkdocs'): print("\n**WARNING**: mkdocs not found, cannot generate HTML docs") else: os.chdir(root) os.system("mkdocs build") pyliblo3-0.16.3/docs/index.md000066400000000000000000000017261474512153000157270ustar00rootroot00000000000000# pyliblo3 Welcome to the **pyliblo3** documentation! **pyliblo3** is a cython wrapper for the C library [liblo](). ## Quick Introduction ```python from pyliblo3 import * import time class MyServer(ServerThread): def __init__(self, port=1234): ServerThread.__init__(self, port) @make_method('/foo', 'ifs') def foo_callback(self, path, args): i, f, s = args print(f"Received message '{path}' with arguments: {i=}, {f=}, {s=}") @make_method(None, None) def fallback(self, path, args): print(f"received unknown message '{path}' with {args=}") server = MyServer() server.start() print(f"Server started in its own thread, send messages to {server.port}. Use CTRL-C to stop") while True: send(("127.0.0.0", server.port), "/foo", 10, 1.5, "bar") send(("127.0.0.0", server.port), "/unknown", (3, 4)) time.sleep(1) ``` ---- ## Installation ```bash pip install pyliblo3 ``` pyliblo3-0.16.3/docs/requirements.txt000066400000000000000000000000361474512153000175530ustar00rootroot00000000000000emlib>=0.16.0 mkdocs-material pyliblo3-0.16.3/examples/000077500000000000000000000000001474512153000151565ustar00rootroot00000000000000pyliblo3-0.16.3/examples/example_client.py000077500000000000000000000016061474512153000205270ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import liblo, sys # send all messages to port 1234 on the local machine try: target = liblo.Address(1234) except liblo.AddressError as err: print(err) sys.exit() # send message "/foo/message1" with int, float and string arguments liblo.send(target, "/foo/bar", 123, 456.789) # send double, int64 and char liblo.send(target, "/foo/message2", ('d', 3.1415), ('h', 2**42), ('c', 'x')) # we can also build a message object first... msg = liblo.Message("/foo/blah") # ... append arguments later... msg.add(123, "foo") # ... and then send it liblo.send(target, msg) # send a list of bytes as a blob blob = [4, 8, 15, 16, 23, 42] liblo.send(target, "/foo/blob", blob) # wrap a message in a bundle, to be dispatched after 2 seconds bundle = liblo.Bundle(liblo.time() + 2.0, liblo.Message("/blubb", 123)) liblo.send(target, bundle) pyliblo3-0.16.3/examples/example_server.py000077500000000000000000000021511474512153000205530ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import liblo, sys # create server, listening on port 1234 try: server = liblo.Server(1234) except liblo.ServerError as err: print(err) sys.exit() def foo_bar_callback(path, args): i, f = args print("/foo/bar: received message '%s' with arguments '%d' and '%f'" % (path, i, f)) def foo_baz_callback(path, args, types, src, data): print("/foo/baz: received message '%s'" % path) print("blob contains %d bytes, user data was '%s'" % (len(args[0]), data)) def fallback(path, args, types, src): print("got unknown message '%s' from '%s'" % (path, src.url)) for a, t in zip(args, types): print("argument of type '%s': %s" % (t, a)) # register method taking an int and a float server.add_method("/foo/bar", 'if', foo_bar_callback) # register method taking a blob, and passing user data to the callback server.add_method("/foo/baz", 'b', foo_baz_callback, "blah") # register a fallback for unhandled messages server.add_method(None, None, fallback) # loop and dispatch messages every 100ms while True: server.recv(100) pyliblo3-0.16.3/examples/example_server_deco.py000077500000000000000000000013661474512153000215540ustar00rootroot00000000000000#!/usr/bin/env python from pyliblo3 import * import time class MyServer(ServerThread): def __init__(self, port=1234): ServerThread.__init__(self, port) @make_method('/foo', 'ifs') def foo_callback(self, path, args): i, f, s = args print(f"Received message '{path}' with arguments: {i=}, {f=}, {s=}") @make_method(None, None) def fallback(self, path, args): print(f"received unknown message '{path}' with {args=}") server = MyServer() server.start() print(f"Server started in its own thread, send messages to {server.port}. Use CTRL-C to stop") while True: send(("127.0.0.0", server.port), "/foo", 10, 1.5, "bar") send(("127.0.0.0", server.port), "/unknown", (3, 4)) time.sleep(1) pyliblo3-0.16.3/examples/test_server_thread.py000077500000000000000000000021441474512153000214300ustar00rootroot00000000000000#!/usr/bin/env python from __future__ import print_function import liblo import time st = liblo.ServerThread() print("Created Server Thread on Port", st.port) def foo_cb(path, args, types): print("foo_cb():") for a, t in zip(args, types): print("received argument %s of type %s" % (a, t)) def bar_cb(path, args, types, src): print("bar_cb():") print("message from", src.url) print("typespec:", types) for a, t in zip(args, types): print("received argument %s of type %s" % (a, t)) class Blah: def __init__(self, x): self.x = x def baz_cb(self, path, args, types, src, user_data): print("baz_cb():") print(args[0]) print("self.x is", self.x, ", user data was", user_data) st.add_method('/foo', 'ifs', foo_cb) st.add_method('/bar', 'hdc', bar_cb) b = Blah(123) st.add_method('/baz', 'b', b.baz_cb, 456) st.start() liblo.send(st.port, "/foo", 123, 456.789, "buh!") l = 1234567890123456 liblo.send(st.port, "/bar", l, 666, ('c', "x")) time.sleep(1) st.stop() st.start() liblo.send(st.port, "/baz", [1,2,3,4,5,6,7,8,9,10]) time.sleep(1) pyliblo3-0.16.3/mkdocs.yml000066400000000000000000000006301474512153000153420ustar00rootroot00000000000000site_name: pyliblo3 theme: name: material features: - navigation.tabs - navigation.sections extra_css: - stylesheets/extra.css markdown_extensions: - toc: toc_depth: 3 - pymdownx.highlight - pymdownx.superfences - admonition use_directory_urls: false repo_url: https://github.com/gesellkammer/pyliblo3 nav: - index.md - Reference.md pyliblo3-0.16.3/pyliblo3/000077500000000000000000000000001474512153000150755ustar00rootroot00000000000000pyliblo3-0.16.3/pyliblo3/__init__.py000066400000000000000000000011631474512153000172070ustar00rootroot00000000000000from __future__ import absolute_import import platform import os if platform.system() == "Windows": libspath = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir, 'pyliblo3.libs')) if os.path.exists(libspath) and os.path.isdir(libspath): os.add_dll_directory(libspath) else: possible_paths = ['C:/Program Files/liblo/lib', 'C:/Program Files/liblo/bin'] for path in ['C:/Program Files/liblo/lib', 'C:/Program Files/liblo/bin']: if os.path.exists(path) and os.path.isdir(path): os.add_dll_directory(path) del platform del os from ._liblo import * pyliblo3-0.16.3/pyliblo3/_liblo.pxd000066400000000000000000000104241474512153000170530ustar00rootroot00000000000000# # pyliblo - Python bindings for the liblo OSC library # # Copyright (C) 2007-2011 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # from libc.stdint cimport int32_t, uint32_t, int64_t, uint8_t from libc.stdio cimport const_char cdef extern from 'lo/lo.h': # type definitions ctypedef void *lo_server ctypedef void *lo_server_thread ctypedef void *lo_method ctypedef void *lo_address ctypedef void *lo_message ctypedef void *lo_blob ctypedef void *lo_bundle ctypedef struct lo_timetag: uint32_t sec uint32_t frac ctypedef union lo_arg: int32_t i int64_t h float f double d unsigned char c char s uint8_t m[4] lo_timetag t cdef enum: LO_DEFAULT LO_UDP LO_UNIX LO_TCP ctypedef void(*lo_err_handler)(int num, const_char *msg, const_char *where) ctypedef int(*lo_method_handler)(const_char *path, const_char *types, lo_arg **argv, int argc, lo_message msg, void *user_data) ctypedef int(*lo_bundle_start_handler)(lo_timetag time, void *user_data) ctypedef int(*lo_bundle_end_handler)(void *user_data) # send int lo_send_message_from(lo_address targ, lo_server serv, char *path, lo_message msg) int lo_send_bundle_from(lo_address targ, lo_server serv, lo_bundle b) # server lo_server lo_server_new_with_proto(char *port, int proto, lo_err_handler err_h) void lo_server_free(lo_server s) char *lo_server_get_url(lo_server s) int lo_server_get_port(lo_server s) int lo_server_get_protocol(lo_server s) lo_method lo_server_add_method(lo_server s, char *path, char *typespec, lo_method_handler h, void *user_data) void lo_server_del_method(lo_server s, char *path, char *typespec) int lo_server_add_bundle_handlers(lo_server s, lo_bundle_start_handler sh, lo_bundle_end_handler eh, void *user_data) int lo_server_recv(lo_server s) nogil int lo_server_recv_noblock(lo_server s, int timeout) nogil int lo_server_get_socket_fd(lo_server s) # server thread lo_server_thread lo_server_thread_new_with_proto(char *port, int proto, lo_err_handler err_h) void lo_server_thread_free(lo_server_thread st) lo_server lo_server_thread_get_server(lo_server_thread st) void lo_server_thread_start(lo_server_thread st) void lo_server_thread_stop(lo_server_thread st) # address lo_address lo_address_new(char *host, char *port) lo_address lo_address_new_with_proto(int proto, char *host, char *port) lo_address lo_address_new_from_url(char *url) void lo_address_free(lo_address) char *lo_address_get_url(lo_address a) char *lo_address_get_hostname(lo_address a) char *lo_address_get_port(lo_address a) int lo_address_get_protocol(lo_address a) const_char* lo_address_errstr(lo_address a) # message lo_message lo_message_new() void lo_message_free(lo_message) void lo_message_add_int32(lo_message m, int32_t a) void lo_message_add_int64(lo_message m, int64_t a) void lo_message_add_float(lo_message m, float a) void lo_message_add_double(lo_message m, double a) void lo_message_add_char(lo_message m, char a) void lo_message_add_string(lo_message m, char *a) void lo_message_add_symbol(lo_message m, char *a) void lo_message_add_true(lo_message m) void lo_message_add_false(lo_message m) void lo_message_add_nil(lo_message m) void lo_message_add_infinitum(lo_message m) void lo_message_add_midi(lo_message m, uint8_t a[4]) void lo_message_add_timetag(lo_message m, lo_timetag a) void lo_message_add_blob(lo_message m, lo_blob a) lo_address lo_message_get_source(lo_message m) # blob lo_blob lo_blob_new(int32_t size, void *data) void lo_blob_free(lo_blob b) void *lo_blob_dataptr(lo_blob b) uint32_t lo_blob_datasize(lo_blob b) # bundle lo_bundle lo_bundle_new(lo_timetag tt) void lo_bundle_free(lo_bundle b) void lo_bundle_add_message(lo_bundle b, char *path, lo_message m) # timetag void lo_timetag_now(lo_timetag *t) pyliblo3-0.16.3/pyliblo3/_liblo.pyi000066400000000000000000000066301474512153000170650ustar00rootroot00000000000000 UDP = 1 UNIX = 2 TCP = 4 class Callback: def __init__(self, func: Callable, user_data: Any): ... def time() -> float: ... def send(target: Address | tuple[str, str] | int, str, *args) -> None: ... class ServerError(Exception): def __init__(self, num: int, msg: str, where: str): ... # decorator to register callbacks class make_method: def __init__(self, path: str, types: str, user_data: Any = None): ... def __call__(self, f: Callable) -> Callable: ... # common base class for both Server and ServerThread class _ServerBase: def __init__(self, reg_methods: bool = True): ... def register_methods(self, obj: _ServerBase = None) -> None: ... def get_url(self) -> str: ... def get_port(self) -> int: ... def get_protocol(self) -> int: ... def fileno(self) -> int: ... def add_method(self, path: str, typespec: str, func: Callable, user_data: Any = None) -> None: ... def del_method(self, path: str, typespec: str | None = None) -> None: ... def add_bundle_handlers(self, start_handler: Callable, end_handler: Callable, user_data=None) -> None: ... def send(self, target: Address | tuple[str, int] | str, *args) -> None: ... @property def url(self) -> str: ... @property def port(self) -> int: ... @property def protocol(self) -> int: ... class Server(_ServerBase): def __init__(self, port: int = None, proto: int | str = LO_DEFAULT, reg_methods: bool = True): ... def __dealloc__(self): ... def free(self) -> None: ... def recv(self, timeout: int | float | None = None) -> bool: ... class ServerThread(_ServerBase): def __init__(self, port: int = None, proto: int | str = LO_DEFAULT, reg_methods: bool = True): ... def __dealloc__(self): ... def free(self) -> None: ... def start(self) -> None: ... def stop(self) -> None: ... class AddressError(Exception): def __init__(self, msg: str): ... class Address: """ An Address represents a destination for a message Possible forms: * `Address(hostname: str, port: int, proto: [int | str] = LO_UDP`) * `Address(port: int)` # Assumes localhost * `Address(url: str)` # A URL of the form 'osc.udp://hostname:1234/' Create a new `Address` object from the given hostname/port or URL. Args: hostname: the target's hostname - the name or an IP of the form '127.0.0.0'. port: the port number of the target proto: one of the constants `LO_UDP`, `LO_TCP`, `LO_UNIX` or a string like 'UDP', 'TCP' or 'UNIX' url: a URL in liblo notation, e.g. `'osc.udp://hostname:1234/'`. Raises: AddressError: if the given parameters do not represent a valid address. """ cdef lo_address _address def __init__(self, addr: str | int, addr2: int = None, proto: int | str = None): ... def __dealloc__(self) -> None: ... def get_url(self) -> str: ... def get_hostname(self) -> str: ... def get_port(self) -> int | str: ... def get_protocol(self) -> int: ... @property def url(self) -> str: ... @property def hostname(self) -> str: ... @property def port(self) -> int: ... @property def protocol(self) -> int: ... class Message: def __init__(self, path: str, *args): ... def add(self, *args: tuple[str, Any] | int | float | str | bool) -> None: ... class Bundle: def __init__(self, *messages): ... def add(self, *args) -> None: ... pyliblo3-0.16.3/pyliblo3/_liblo.pyx000066400000000000000000001036411474512153000171040ustar00rootroot00000000000000# cython: embedsignature=True # # pyliblo3 - Python bindings for the lublo OSC library, based on pyliblo # # Original copyright: # # pyliblo - Python bindings for the liblo OSC library # # Copyright (C) 2007-2015 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as # published by the Free Software Foundation; either version 2.1 of the # License, or (at your option) any later version. # __version__ = '0.16.1' from cpython cimport PY_VERSION_HEX cdef extern from 'Python.h': void PyEval_InitThreads() from libc.stdlib cimport malloc, free from libc.math cimport modf from libc.stdint cimport int32_t, int64_t from _liblo cimport * import inspect as _inspect import weakref as _weakref def _protostr_to_int(str proto): if proto == 'UDP': return LO_UDP elif proto == 'TCP': return LO_TCP elif proto == 'UNIX': return LO_UNIX else: raise ValueError(f"Proto {proto} not understood, expected one of 'UDP', 'TCP' or 'UNIX'") class _weakref_method: """ Weak reference to a function, including support for bound methods. """ __slots__ = ('_func', 'obj') def __init__(self, f): if _inspect.ismethod(f): self._func = f.__func__ self.obj = _weakref.ref(f.__self__) else: self._func = f self.obj = None @property def func(self): if self.obj: return self._func.__get__(self.obj(), self.obj().__class__) else: return self._func def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) class struct: def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k, v) cdef class Callback: """ Used internally to wrap a python function as a callback Args: func: the function to call user_data: any python object, will be passed to the callback as the last argument """ cdef object func cdef object user_data cdef int numargs cdef int has_varargs def __init__(self, func, user_data): self.func = _weakref_method(func) self.user_data = user_data, spec = _inspect.getfullargspec(func) numargs = len(spec.args) if _inspect.ismethod(func): numargs -= 1 self.numargs = numargs self.has_varargs = 1 if spec.varargs is not None else 0 cdef inline str _decode(s): # convert to standard string type, depending on python version if PY_VERSION_HEX >= 0x03000000 and isinstance(s, bytes): return s.decode() else: return s cdef bytes _encode(s): # convert unicode to bytestring if isinstance(s, unicode): return s.encode() else: return s # forward declarations cdef class _ServerBase cdef class Address cdef class Message cdef class Bundle # liblo protocol constants UDP = LO_UDP TCP = LO_TCP UNIX = LO_UNIX ################################################################################ # timetag ################################################################################ cdef lo_timetag _double_to_timetag(double f): cdef lo_timetag tt cdef double intr, frac frac = modf(f, &intr) tt.sec = intr tt.frac = (frac * 4294967296.0) return tt cdef double _timetag_to_double(lo_timetag tt): return tt.sec + ((tt.frac) / 4294967296.0) def time(): """ Return the current time as a floating point number (seconds since January 1, 1900). Returns: (float) The liblo timetag as a float, representing seconds since 1900 """ cdef lo_timetag tt lo_timetag_now(&tt) return _timetag_to_double(tt) ################################################################################ # send ################################################################################ cdef _send(target, _ServerBase src, args): cdef lo_server from_server cdef Address target_address cdef int r # convert target to Address object, if necessary if isinstance(target, Address): target_address = target elif isinstance(target, tuple): # unpack tuple target_address = Address(*target) else: target_address = Address(target) # 'from' parameter is NULL if no server was specified from_server = src._server if src else NULL if isinstance(args[0], (Message, Bundle)): # args is already a list of Messages/Bundles packets = args else: # make a single Message from all arguments packets = [Message(*args)] # send all packets for p in packets: if isinstance(p, Message): message = p r = lo_send_message_from(target_address._address, from_server, message._path, message._message) else: bundle = p r = lo_send_bundle_from(target_address._address, from_server, bundle._bundle) if r == -1: raise IOError("sending failed: %s" % lo_address_errstr(target_address._address)) def send(target, *args): """ Send a message without requiring a server The function has two forms: * `send(target, *messages)` * `send(target, path, *args)` Send messages to the the given target, without requiring a server. Arguments may be one or more `Message` or `Bundle` objects, or a single message given by its path and optional arguments. Args: target (Address | tuple[str, int] | int | str): the address to send the message to; an `Address` object, a port number, a `(hostname, port)` tuple, or a URL. path (str): the path of the message to be sent args (Any): the information to send. These are used to construct a message messages (Message | Bundle): one or more objects of type `Message` or `Bundle`. Raises: AddressError: if the given target is invalid IOError: if the message couldn't be sent. """ _send(target, None, args) ################################################################################ # Server ################################################################################ class ServerError(Exception): """ Raised when creating a liblo OSC server fails. """ def __init__(self, num, msg, where): self.num = num self.msg = msg self.where = where def __str__(self): s = "server error %d" % self.num if self.where: s += " in %s" % self.where s += ": %s" % self.msg return s cdef int _msg_callback(const_char *path, const_char *types, lo_arg **argv, int argc, lo_message msg, void *cb_data) noexcept with gil: cdef int i cdef char t cdef unsigned char *ptr cdef uint32_t size, j args = [] for i from 0 <= i < argc: t = types[i] if t == 'i': v = argv[i].i elif t == 'h': v = argv[i].h elif t == 'f': v = argv[i].f elif t == 'd': v = argv[i].d elif t == 'c': v = chr(argv[i].c) elif t == 's': v = _decode(&argv[i].s) elif t == 'S': v = _decode(&argv[i].s) elif t == 'T': v = True elif t == 'F': v = False elif t == 'N': v = None elif t == 'I': v = float('inf') elif t == 'm': v = (argv[i].m[0], argv[i].m[1], argv[i].m[2], argv[i].m[3]) elif t == 't': v = _timetag_to_double(argv[i].t) elif t == 'b': v = bytes(lo_blob_dataptr(argv[i])) else: v = None # unhandled data type args.append(v) cdef char *url = lo_address_get_url(lo_message_get_source(msg)) src = Address(url) free(url) cb = cb_data func_args = (_decode(path), args, _decode(types), src, cb.user_data) # call function # spec = _inspect.getfullargspec(func) if cb.has_varargs == 1: r = cb.func(*func_args) else: r = cb.func(*func_args[0:cb.numargs]) return r if r is not None else 0 cdef int _bundle_start_callback(lo_timetag t, void *cb_data) noexcept with gil: cb = cb_data r = cb.start_func(_timetag_to_double(t), cb.user_data) return r if r is not None else 0 cdef int _bundle_end_callback(void *cb_data) noexcept with gil: cb = cb_data r = cb.end_func(cb.user_data) return r if r is not None else 0 cdef void _err_handler(int num, const_char *msg, const_char *where) noexcept with gil: # can't raise exception in cdef callback function, so use a global variable # instead global __exception __exception = ServerError(num, msg, None) if where: __exception.where = where # decorator to register callbacks class make_method: """ A decorator that serves as a more convenient alternative to [Server.add_method](#add_method). Args: path (str | None): the message path to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. types (str): the argument types to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. user_data: An arbitrary object that will be passed on to the decorated method every time a matching message is received. """ # counter to keep track of the order in which the callback functions where # defined _counter = 0 def __init__(self, path, types, user_data=None): self.spec = struct(counter=make_method._counter, path=path, types=types, user_data=user_data) make_method._counter += 1 def __call__(self, f): # we can't access the Server object here, because at the time the # decorator is run it doesn't even exist yet, so we store the # path/typespec in the function object instead... if not hasattr(f, '_method_spec'): f._method_spec = [] f._method_spec.append(self.spec) return f # common base class for both Server and ServerThread cdef class _ServerBase: cdef lo_server _server cdef list _keep_refs def __init__(self, reg_methods=True): self._keep_refs = [] if reg_methods: self.register_methods() cdef _check(self): if self._server == NULL: raise RuntimeError("Server method called after free()") def register_methods(self, obj=None): """ Called internally during init if reg_methods is True Args: obj: The object that implements the OSC callbacks to be registered. By default this is the server object itself. This function is usually called automatically by the server's constructor, unless its *reg_methods* parameter was set to `False`. """ if obj == None: obj = self # find and register methods that were defined using decorators methods = [] for m in _inspect.getmembers(obj): if hasattr(m[1], '_method_spec'): for spec in m[1]._method_spec: methods.append(struct(spec=spec, name=m[1])) # sort by counter methods.sort(key=lambda x: x.spec.counter) for e in methods: self.add_method(e.spec.path, e.spec.types, e.name, e.spec.user_data) def get_url(self): """ Returns the url of the server Returns: (str) url of the server """ self._check() cdef char *tmp = lo_server_get_url(self._server) cdef object r = tmp free(tmp) return _decode(r) def get_port(self): """ Returns the port number of this server Returns: (int) port number """ self._check() return lo_server_get_port(self._server) def get_protocol(self): """ Returns the protocol of this server, as an int This will be one of `LO_TCP`, `LO_TCP` or `LO_UNIX` Returns: (int) the protocol as an int """ self._check() return lo_server_get_protocol(self._server) def fileno(self): """ Return the file descriptor of the server socket Returns: (int) the file descriptor, or -1 if not supported by the underlying server protocol """ self._check() return lo_server_get_socket_fd(self._server) def add_method(self, str path, str typespec, func, user_data=None): """ Register a callback for OSC messages with matching path and argument types. Args: path (str): the message path to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. typespec (str): the argument types to be handled by the registered method. `None` may be used as a wildcard to match any OSC message. func: the callback function. This may be a global function, a class method, or any other callable object, pyliblo will know what to do either way. user_data: An arbitrary object that will be passed on to *func* every time a matching message is received. """ cdef char *p cdef char *t if isinstance(path, (bytes, unicode)): s = _encode(path) p = s elif path is None: p = NULL else: raise TypeError("path must be a string or None") if isinstance(typespec, (bytes, unicode)): s2 = _encode(typespec) t = s2 elif typespec is None: t = NULL else: raise TypeError("typespec must be a string or None") self._check() # use a weak reference if func is a method, to avoid circular # references in cases where func is a method of an object that also # has a reference to the server (e.g. when deriving from the Server # class) # cb = struct(func=_weakref_method(func), user_data=user_data) cb = Callback(func, user_data) # keep a reference to the callback data around self._keep_refs.append(cb) lo_server_add_method(self._server, p, t, _msg_callback, cb) def del_method(self, path, typespec=None): """ Delete a callback function Delete a callback function. For both *path* and *typespec*, `None` may be used as a wildcard. Args: path (str | None): the method to delete typespec (str | None): the typespec to match, or None to delete any method matching the given path """ cdef char *p cdef char *t if isinstance(path, (bytes, unicode)): s = _encode(path) p = s elif path == None: p = NULL else: raise TypeError("path must be a string or None") if isinstance(typespec, (bytes, unicode)): s2 = _encode(typespec) t = s2 elif typespec == None: t = NULL else: raise TypeError("typespec must be a string or None") self._check() lo_server_del_method(self._server, p, t) def add_bundle_handlers(self, start_handler, end_handler, user_data=None): """ Add bundle notification handlers. Args: start_handler: a callback which fires when at the start of a bundle. This is called with the bundle's timestamp and user_data. end_handler: a callback which fires when at the end of a bundle. This is called with user_data. user_data: data to pass to the handlers. """ cb_data = struct(start_func=_weakref_method(start_handler), end_func=_weakref_method(end_handler), user_data=user_data) self._keep_refs.append(cb_data) lo_server_add_bundle_handlers(self._server, _bundle_start_callback, _bundle_end_callback, cb_data) def send(self, target, *args): """ Send a message or bundle from this server to the the given target. * `send(target, *messages)` * `send(target, path, *args)` Send a message or bundle from this server to the the given target. Arguments may be one or more `Message` or `Bundle` objects, or a single message given by its path and optional arguments. Args: target (Address | tuple[str, int] | str): the address to send the message to; an `Address` object, a port number, a `(hostname, port)` tuple, or a URL. messages (Message | Bundle): one or more objects of type `Message` or `Bundle`. path (str): the path of the message to be sent. Raises: AddressError: if the given target is invalid. IOError: if the message couldn't be sent. """ self._check() _send(target, self, args) property url: """ The server's URL. """ def __get__(self): return self.get_url() property port: """ The server's port number (int) """ def __get__(self): return self.get_port() property protocol: """ The server's protocol (one of the constants `LO_UDP`, `LO_TCP` or `LO_UNIX`). """ def __get__(self): return self.get_protocol() cdef class Server(_ServerBase): """ A server that can receive OSC messages, blocking Use [ServerThread](#ServerThread) for an OSC server that runs in its own thread and never blocks. Args: port (int | None): a decimal port number or a UNIX socket path. If omitted, an arbitrary free UDP port will be used. proto (int | str): one of LO_UDP, LO_TCP, LO_UNIX or LO_DEFAULT, or one of the strings 'UDP', 'TCP', 'UNIX' reg_methods (bool): if True, register any methods decorated with the [make_method](#make_method) decorator Raises: ServerError: if an error occurs created the underlying liblo server """ def __init__(self, port=None, proto=LO_DEFAULT, reg_methods=True): cdef char *cs if port != None: p = _encode(str(port)); cs = p else: cs = NULL if isinstance(proto, str): proto = _protostr_to_int(proto) global __exception __exception = None self._server = lo_server_new_with_proto(cs, proto, _err_handler) if __exception: raise __exception _ServerBase.__init__(self, reg_methods=reg_methods) def __dealloc__(self): self.free() def free(self): """ Free the underlying server object and close its port. Note that this will also happen automatically when the server is deallocated. """ if self._server: lo_server_free(self._server) self._server = NULL def recv(self, timeout=None): """ Receive and dispatch one OSC message. Blocking by default, unless *timeout* is specified. Args: timeout (int, float): Time in milliseconds after which the function returns if no messages have been received. May be 0, in which case the function always returns immediately, whether messages have been received or not. Returns: (bool) `True` if a message was received, otherwise `False`. """ cdef int t, r self._check() if timeout != None: t = timeout with nogil: r = lo_server_recv_noblock(self._server, t) return r and True or False else: with nogil: lo_server_recv(self._server) return True cdef class ServerThread(_ServerBase): """ Server running in a thread Unlike `Server`, `ServerThread` uses its own thread which runs in the background to dispatch messages. `ServerThread` has the same methods as `Server`, with the exception of `.recv`. Instead, it defines two additional methods `.start` and `.stop`. Args: port (int | str): a decimal port number or a UNIX socket path. If omitted, an arbitrary free UDP port will be used. proto (int | str): one of the constants `LO_UDP`, `LO_TCP` or `LO_UNIX` or a corresponding string 'UDP', 'TCP', 'UNIX' reg_methods: if True, register any method decorated with the [make_method](#make_method) decorator Raises: ServerError: if creating the server fails, e.g. because the given port could not be opened. !!! note Because liblo creates its own thread to receive and dispatch messages, callback functions will not be run in the main Python thread! """ cdef lo_server_thread _server_thread def __init__(self, port=None, proto=LO_DEFAULT, reg_methods=True): cdef char *cs if port != None: p = _encode(str(port)); cs = p else: cs = NULL # make sure python can handle threading PyEval_InitThreads() global __exception __exception = None self._server_thread = lo_server_thread_new_with_proto(cs, proto, _err_handler) if __exception: raise __exception self._server = lo_server_thread_get_server(self._server_thread) _ServerBase.__init__(self, reg_methods=reg_methods) def __dealloc__(self): self.free() def free(self): """ Free the underlying server object and close its port. !!! note This method is called automatically when the server is deallocated. """ if self._server_thread: lo_server_thread_free(self._server_thread) self._server_thread = NULL self._server = NULL def start(self): """ Start the server thread. liblo will now start to dispatch any messages it receives. """ self._check() lo_server_thread_start(self._server_thread) def stop(self): """ Stop the server thread. """ self._check() lo_server_thread_stop(self._server_thread) ################################################################################ # Address ################################################################################ class AddressError(Exception): """ Raised when trying to create an invalid `Address` object. """ def __init__(self, msg): self.msg = msg def __str__(self): return "address error: %s" % self.msg cdef class Address: """ An Address represents a destination for a message Possible forms: * `Address(hostname: str, port: int, proto: [int | str] = LO_UDP`) * `Address(port: int)` # Assumes localhost * `Address(url: str)` # A URL of the form 'osc.udp://hostname:1234/' Create a new `Address` object from the given hostname/port or URL. Args: hostname: the target's hostname - the name or an IP of the form '127.0.0.0'. port: the port number of the target proto: one of the constants `LO_UDP`, `LO_TCP`, `LO_UNIX` or a string like 'UDP', 'TCP' or 'UNIX' url: a URL in liblo notation, e.g. `'osc.udp://hostname:1234/'`. Raises: AddressError: if the given parameters do not represent a valid address. """ cdef lo_address _address def __init__(self, addr, addr2=None, proto=LO_UDP): if isinstance(proto, str): proto = _protostr_to_int(proto) if addr2: # Address(host, port[, proto]) s = _encode(addr) s2 = _encode(str(addr2)) self._address = lo_address_new_with_proto(proto, s, s2) if not self._address: raise AddressError("invalid protocol") elif isinstance(addr, int) or (isinstance(addr, str) and addr.isdigit()): # Address(port) s = str(addr).encode() self._address = lo_address_new(NULL, s) else: # Address(url) s = _encode(addr) self._address = lo_address_new_from_url(s) # lo_address_errno() is of no use if self._addr == NULL if not self._address: raise AddressError("invalid URL '%s'" % str(addr)) def __dealloc__(self): lo_address_free(self._address) def get_url(self): """This Address as a liblo URL""" cdef char *tmp = lo_address_get_url(self._address) cdef object r = tmp free(tmp) return _decode(r) def get_hostname(self): """The hostname of this Address""" return _decode(lo_address_get_hostname(self._address)) def get_port(self): """The port number of this Address""" cdef bytes s = lo_address_get_port(self._address) if s.isdigit(): return int(s) else: return _decode(s) def get_protocol(self): """ The protocol used as an int Example ------- ```python from pyliblo3 import * address = Address('127.0.0.0', 9876) assert address.get_protocol() == LO_UDP ``` """ return lo_address_get_protocol(self._address) property url: """ The address's URL. """ def __get__(self): return self.get_url() property hostname: """ The address's hostname. """ def __get__(self): return self.get_hostname() property port: """ The address's port number. """ def __get__(self): return self.get_port() property protocol: """ The address's protocol (one of the constants :const:`UDP`, :const:`TCP`, or :const:`UNIX`). """ def __get__(self): return self.get_protocol() ################################################################################ # Message ################################################################################ cdef class _Blob: cdef lo_blob _blob def __init__(self, arr): # arr can by any sequence type cdef unsigned char *p cdef uint32_t size, i size = len(arr) if size < 1: raise ValueError("blob is empty") # copy each element of arr to a C array p = malloc(size) try: if isinstance(arr[0], (str, unicode)): # use ord() if arr is a string (but not bytes) for i from 0 <= i < size: p[i] = ord(arr[i]) else: for i from 0 <= i < size: p[i] = arr[i] # build blob self._blob = lo_blob_new(size, p) finally: free(p) def __dealloc__(self): lo_blob_free(self._blob) cdef class Message: """ An OSC message, consisting of a path and arbitrary arguments. Args: path (str): the path of the message args: any arguments passed will be added to this messag """ cdef bytes _path cdef lo_message _message cdef list _keep_refs def __init__(self, path, *args): self._keep_refs = [] # encode path to bytestring if necessary self._path = _encode(path) self._message = lo_message_new() self.add(*args) def __dealloc__(self): lo_message_free(self._message) def add(self, *args): """ Append the given arguments to this message Arguments can be single values or `(typetag, data)` tuples to specify the actual type. This might be needed for numbers, to specify if a float needs to be encoded as a 32-bit (typetag = 'f') or 64-bit float (typetag = 'd'). By default, float numbers are interpreted as 32-bit floats. Args: args: each argument can be a single value or a tuple `(typetag: str, data: Any)` """ for arg in args: if (isinstance(arg, tuple) and len(arg) <= 2 and isinstance(arg[0], (bytes, unicode)) and len(arg[0]) == 1): # type explicitly specified if len(arg) == 2: self._add(arg[0], arg[1]) else: self._add(arg[0], None) else: # detect type automatically self._add_auto(arg) cdef _add(self, type, value): cdef uint8_t midi[4] # accept both bytes and unicode as type specifier cdef char t = ord(_decode(type)[0]) if t == 'i': lo_message_add_int32(self._message, int(value)) elif t == 'h': lo_message_add_int64(self._message, long(value)) elif t == 'f': lo_message_add_float(self._message, float(value)) elif t == 'd': lo_message_add_double(self._message, float(value)) elif t == 'c': lo_message_add_char(self._message, ord(value)) elif t == 's': s = _encode(value) lo_message_add_string(self._message, s) elif t == 'S': s = _encode(value) lo_message_add_symbol(self._message, s) elif t == 'T': lo_message_add_true(self._message) elif t == 'F': lo_message_add_false(self._message) elif t == 'N': lo_message_add_nil(self._message) elif t == 'I': lo_message_add_infinitum(self._message) elif t == 'm': for n from 0 <= n < 4: midi[n] = value[n] lo_message_add_midi(self._message, midi) elif t == 't': lo_message_add_timetag(self._message, _double_to_timetag(value)) elif t == 'b': b = _Blob(value) # make sure the blob is not deleted as long as this message exists self._keep_refs.append(b) lo_message_add_blob(self._message, (<_Blob>b)._blob) else: raise TypeError("unknown OSC data type '%c'" % t) cdef _add_auto(self, value): # bool is a subclass of int, so check those first if value is True: lo_message_add_true(self._message) elif value is False: lo_message_add_false(self._message) elif isinstance(value, (int, long)): try: lo_message_add_int32(self._message, value) except OverflowError: lo_message_add_int64(self._message, value) elif isinstance(value, float): lo_message_add_float(self._message, float(value)) elif isinstance(value, (bytes, unicode)): s = _encode(value) lo_message_add_string(self._message, s) elif value == None: lo_message_add_nil(self._message) elif value == float('inf'): lo_message_add_infinitum(self._message) else: # last chance: could be a blob try: iter(value) except TypeError: raise TypeError("unsupported message argument type") self._add('b', value) ################################################################################ # Bundle ################################################################################ cdef class Bundle: """ A bundle of one or more messages to be sent and dispatched together. Possible forms: * `Bundle(*messages)` * `Bundle(timetag: float, *messages)` Create a new `Bundle` object. You can optionally specify a time at which the messages should be dispatched (as an OSC timetag float), and any number of messages to be included in the bundle. Args: timetag (float): optional, speficies the time at which the message should be dispatched messages: any number of `Message`s to include in this `Bundle` """ cdef lo_bundle _bundle cdef list _keep_refs def __init__(self, *messages): cdef lo_timetag tt tt.sec, tt.frac = 0, 0 self._keep_refs = [] if len(messages) and not isinstance(messages[0], Message): t = messages[0] if isinstance(t, (float, int, long)): tt = _double_to_timetag(t) elif isinstance(t, tuple) and len(t) == 2: tt.sec, tt.frac = t else: raise TypeError("invalid timetag") # first argument was timetag, so continue with second messages = messages[1:] self._bundle = lo_bundle_new(tt) if len(messages): self.add(*messages) def __dealloc__(self): lo_bundle_free(self._bundle) def add(self, *args): """ Add one or more messages to this bundle Possible forms: * `add(*messages: Message)` * `add(path: str, *args)`, where path is the osc path (for example, '/path1' or '/root/subpath') and `args` are passed directly to `Message` to create a Message to be added to this Bundle Add one or more messages to the bundle. Args: args: either a Message or a set or arguments passed directly to `Message` to create a `Message` which is added to this `Bundle` """ if isinstance(args[0], Message): # args is already a list of Messages messages = args else: # make a single Message from all arguments messages = [Message(*args)] # add all messages for m in messages: self._keep_refs.append(m) message = m lo_bundle_add_message(self._bundle, message._path, message._message) pyliblo3-0.16.3/pyproject.toml000066400000000000000000000001761474512153000162600ustar00rootroot00000000000000[build-system] requires = [ "setuptools>=42", "wheel", "cython>=3.0" ] build-backend = "setuptools.build_meta" pyliblo3-0.16.3/readthedocs.yml000066400000000000000000000006421474512153000163520ustar00rootroot00000000000000# .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Optionally build your docs in additional formats such as PDF # formats: # - pdf build: os: ubuntu-22.04 tools: python: "3.11" python: install: - requirements: docs/requirements.txt # setup_py_install: true mkdocs: configuration: mkdocs.yml pyliblo3-0.16.3/scripts/000077500000000000000000000000001474512153000150275ustar00rootroot00000000000000pyliblo3-0.16.3/scripts/dump_osc.1000066400000000000000000000005011474512153000167160ustar00rootroot00000000000000.TH dump_osc 1 .SH NAME dump_osc \- Prints incoming OSC messages .SH SYNOPSIS .B dump_osc \fIport\fP .SH DESCRIPTION .B dump_osc prints all OSC messages received on \fIport\fP (UDP port number, or any other address string supported by liblo). .SH AUTHOR Dominic Sacre .SH SEE ALSO send_osc(1) pyliblo3-0.16.3/scripts/dump_osc.py000077500000000000000000000041351474512153000172200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # pyliblo - Python bindings for the liblo OSC library # # Copyright (C) 2007-2011 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import sys import pyliblo3 as liblo class DumpOSC: def blob_to_hex(self, b): return " ".join([ (hex(v/16).upper()[-1] + hex(v%16).upper()[-1]) for v in b ]) def callback(self, path, args, types, src): write = sys.stdout.write ## print source #write("from " + src.get_url() + ": ") # print path write(path + " ,") # print typespec write(types) # loop through arguments and print them for a, t in zip(args, types): write(" ") if t == None: #unknown type write("[unknown type]") elif t == 'b': # it's a blob write("[" + self.blob_to_hex(a) + "]") else: # anything else write(str(a)) write('\n') def __init__(self, port = None): # create server object try: self.server = liblo.Server(port) except liblo.ServerError as err: sys.exit(str(err)) print("listening on URL: " + self.server.get_url()) # register callback function for all messages self.server.add_method(None, None, self.callback) def run(self): # just loop and dispatch messages every 10ms while True: self.server.recv(10) if __name__ == '__main__': # display help if len(sys.argv) == 1 or sys.argv[1] in ("-h", "--help"): sys.exit("Usage: " + sys.argv[0] + " port") # require one argument (port number) if len(sys.argv) < 2: sys.exit("please specify a port or URL") app = DumpOSC(sys.argv[1]) try: app.run() except KeyboardInterrupt: del app pyliblo3-0.16.3/scripts/send_osc.1000066400000000000000000000013331474512153000167060ustar00rootroot00000000000000.TH send_osc 1 .SH NAME send_osc \- Sends a single OSC message .SH SYNOPSIS .B send_osc \fIport\fP \fIpath\fP [,\fItypes\fP] [\fIargs\fP...] .SH DESCRIPTION .B send_osc sends an OSC message to the specified \fIport\fP (UDP port number, or any other address string supported by liblo). The message is delivered to \fIpath\fP. If the first argument following the path starts with a comma, it's interpreted as a type string, specifying the OSC data types to be used for sending the message arguments. Otherwise, send_osc automatically tries to use appropriate data types. Valid integer and float values are sent as such, anything else is sent as a string. .SH AUTHOR Dominic Sacre .SH SEE ALSO dump_osc(1) pyliblo3-0.16.3/scripts/send_osc.py000077500000000000000000000033611474512153000172040ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # pyliblo - Python bindings for the liblo OSC library # # Copyright (C) 2007-2011 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import sys import pyliblo3 as liblo def make_message_auto(path, *args): msg = liblo.Message(path) for a in args: try: v = int(a) except ValueError: try: v = float(a) except ValueError: v = a msg.add(v) return msg def make_message_manual(path, types, *args): if len(types) != len(args): sys.exit("length of type string doesn't match number of arguments") msg = liblo.Message(path) try: for a, t in zip(args, types): msg.add((t, a)) except Exception as e: sys.exit(str(e)) return msg if __name__ == '__main__': # display help if len(sys.argv) == 1 or sys.argv[1] in ("-h", "--help"): sys.exit("Usage: " + sys.argv[0] + " port path [,types] [args...]") # require at least two arguments (target port/url and message path) if len(sys.argv) < 2: sys.exit("please specify a port or URL") if len(sys.argv) < 3: sys.exit("please specify a message path") if len(sys.argv) > 3 and sys.argv[3].startswith(','): msg = make_message_manual(sys.argv[2], sys.argv[3][1:], *sys.argv[4:]) else: msg = make_message_auto(*sys.argv[2:]) try: liblo.send(sys.argv[1], msg) except IOError as e: sys.exit(str(e)) else: sys.exit(0) pyliblo3-0.16.3/setup.py000077500000000000000000000060621474512153000150610ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- from setuptools import setup, Extension import platform import glob import os import shutil import subprocess VERSION = '0.16.3' platformname = platform.system().lower() include_dirs = ["pyliblo3"] library_dirs = [] libraries = [] compile_args = [] def append_if_exists(lst: list[str], path: str) -> None: if os.path.exists(path): if path not in lst: lst.append(path) print(f"~~~~~ Added path: {path}") else: print(f"***** Path does not exists, skipping: '{path}'") if platformname == 'darwin': libraries.append('lo') brewpath = shutil.which("brew") if brewpath: brewprefix = subprocess.getoutput("brew --prefix") append_if_exists(include_dirs, brewprefix + "/include") append_if_exists(library_dirs, brewprefix + "/lib") include_dirs.append("/usr/local/include/") append_if_exists(include_dirs, "/opt/local/include/") library_dirs.append("/usr/local/lib") append_if_exists(library_dirs, "/opt/local/lib") compile_args += [ '-fno-strict-aliasing', '-Werror-implicit-function-declaration', '-Wfatal-errors' ] elif platformname == 'linux': libraries.append('lo') include_dirs.extend(['/usr/include', '/usr/local/include']) library_dirs.append("/usr/local/lib") compile_args += [ '-fno-strict-aliasing', '-Werror-implicit-function-declaration', '-Wfatal-errors' ] elif platformname == "windows": libraries.append('liblo') # Default install directory for liblo built from source # See also the wheel build script where we add the .../lib and .../bin # directories so that the wheel repair script can find the liblo dll # to add it to the wheel. When building from source, the user needs # to install liblo and add its lib and bin directories to the path append_if_exists(include_dirs, "C:/Program Files/liblo/include") append_if_exists(library_dirs, "C:/Program Files/liblo/lib") else: pass # read the contents of your README file thisdir = os.path.abspath(os.path.dirname(__file__)) with open(os.path.join(thisdir, 'README.md')) as f: long_description = f.read() setup( name='pyliblo3', python_requires='>=3.9', version=VERSION, scripts=glob.glob("scripts/*.py"), ext_modules=[ Extension( 'pyliblo3._liblo', #sources = ['src/liblo.pyx', 'src/liblo.pxd'], sources = ['pyliblo3/_liblo.pyx'], extra_compile_args=compile_args, libraries=libraries, library_dirs=library_dirs, include_dirs=include_dirs) ], packages=['pyliblo3'], author='Dominic Sacre', author_email='dominic.sacre@gmx.de', maintainer='Eduardo Moguillansky', maintainer_email='eduardo.moguillansky@gmail.com', url='https://github.com/gesellkammer/pyliblo3', description='Python bindings for the liblo OSC library', long_description=long_description, long_description_content_type='text/markdown', license='LGPL', ) pyliblo3-0.16.3/test/000077500000000000000000000000001474512153000143175ustar00rootroot00000000000000pyliblo3-0.16.3/test/unit.py000077500000000000000000000206331474512153000156570ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # pyliblo - Python bindings for the liblo OSC library # # Copyright (C) 2007-2011 Dominic Sacré # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # import unittest import re import time import sys import pyliblo3 as liblo import platform portnum = 9876 def approx(a, b, e = 0.0002): return abs(a - b) < e def matchHost(host, regex): r = re.compile(regex) return r.match(host) != None class Arguments: def __init__(self, path, args, types, src, data): self.path = path self.args = args self.types = types self.src = src self.data = data class ServerTestCaseBase(unittest.TestCase): def setUp(self): self.cb = None def callback(self, path, args, types, src, data): self.cb = Arguments(path, args, types, src, data) def callback_dict(self, path, args, types, src, data): if self.cb == None: self.cb = { } self.cb[path] = Arguments(path, args, types, src, data) class ServerTestCase(ServerTestCaseBase): def setUp(self): ServerTestCaseBase.setUp(self) self.server = liblo.Server(str(portnum)) def tearDown(self): del self.server def testPort(self): assert self.server.get_port() == portnum def testURL(self): assert matchHost(self.server.get_url(), fr'osc\.udp://.*:{portnum}/') def testSendInt(self): self.server.add_method('/foo', 'i', self.callback, "data") self.server.send(str(portnum), '/foo', 123) assert self.server.recv() == True assert self.cb is not None and isinstance(self.cb, Arguments), f".cb is not set, {self.cb=}" assert self.cb.path == '/foo' assert self.cb.args[0] == 123 assert self.cb.types == 'i' assert self.cb.data == ("data",) assert matchHost(self.cb.src.get_url(), fr'osc\.udp://.*:{portnum}/') def testSendBlob(self): self.server.add_method('/blob', 'b', self.callback) self.server.send(str(portnum), '/blob', [4, 8, 15, 16, 23, 42]) assert self.server.recv() == True if sys.hexversion < 0x03000000: assert list(self.cb.args[0]) == [4, 8, 15, 16, 23, 42] else: assert self.cb.args[0] == b'\x04\x08\x0f\x10\x17\x2a' def testSendVarious(self): self.server.add_method('/blah', 'ihfdscb', self.callback) if sys.hexversion < 0x03000000: self.server.send(portnum, '/blah', 123, 2**42, 123.456, 666.666, "hello", ('c', 'x'), (12, 34, 56)) else: self.server.send(portnum, '/blah', 123, ('h', 2**42), 123.456, 666.666, "hello", ('c', 'x'), (12, 34, 56)) assert self.server.recv() == True assert self.cb.types == 'ihfdscb' assert len(self.cb.args) == len(self.cb.types) assert self.cb.args[0] == 123 assert self.cb.args[1] == 2**42 assert approx(self.cb.args[2], 123.456) assert approx(self.cb.args[3], 666.666) assert self.cb.args[4] == "hello" assert self.cb.args[5] == 'x' if sys.hexversion < 0x03000000: assert list(self.cb.args[6]) == [12, 34, 56] else: assert self.cb.args[6] == b'\x0c\x22\x38' def testSendOthers(self): self.server.add_method('/blubb', 'tmSTFNI', self.callback) self.server.send(portnum, '/blubb', ('t', 666666.666), ('m', (1, 2, 3, 4)), ('S', 'foo'), True, ('F',), None, ('I',)) assert self.server.recv() == True assert self.cb.types == 'tmSTFNI' assert approx(self.cb.args[0], 666666.666) assert self.cb.args[1] == (1, 2, 3, 4) assert self.cb.args[2] == 'foo' assert self.cb.args[3] == True assert self.cb.args[4] == False assert self.cb.args[5] == None assert self.cb.args[6] == float('inf') def testSendMessage(self): self.server.add_method('/blah', 'is', self.callback) m = liblo.Message('/blah', 42, 'foo') self.server.send(portnum, m) assert self.server.recv() == True assert self.cb.types == 'is' assert self.cb.args[0] == 42 assert self.cb.args[1] == 'foo' def testSendBundle(self): self.server.add_method('/foo', 'i', self.callback_dict) self.server.add_method('/bar', 's', self.callback_dict) self.server.send(portnum, liblo.Bundle( liblo.Message('/foo', 123), liblo.Message('/bar', "blubb") )) assert self.server.recv(100) == True assert self.cb['/foo'].args[0] == 123 assert self.cb['/bar'].args[0] == "blubb" def testSendTimestamped(self): self.server.add_method('/blubb', 'i', self.callback) d = 1.23 t1 = time.time() b = liblo.Bundle(liblo.time() + d) b.add('/blubb', 42) self.server.send(portnum, b) while not self.cb: self.server.recv(1) t2 = time.time() assert approx(t2 - t1, d, 0.01) def testSendInvalid(self): try: self.server.send(portnum, '/blubb', ('x', 'y')) except TypeError as e: pass else: assert False def testRecvTimeout(self): t1 = time.time() assert self.server.recv(500) == False t2 = time.time() assert t2 - t1 < 0.666 def testRecvImmediate(self): t1 = time.time() assert self.server.recv(0) == False t2 = time.time() assert t2 - t1 < 0.01 class ServerCreationTestCase(unittest.TestCase): def testNoPermission(self): try: s = liblo.Server('22') except liblo.ServerError as e: pass except OSError as e: # On macos this should raise OSError # And it should in fact fail if platform.system().lower() == 'darwin': pass else: assert False else: print(f"Should have failed with exception OSError") def testRandomPort(self): s = liblo.Server() assert 1024 <= s.get_port() <= 65535 def testPort(self): s = liblo.Server(1234) t = liblo.Server('5678') assert s.port == 1234 assert t.port == 5678 assert matchHost(s.url, r'osc\.udp://.*:1234/') def testPortProto(self): s = liblo.Server(1234, liblo.TCP) assert matchHost(s.url, r'osc\.tcp://.*:1234/') class ServerThreadTestCase(ServerTestCaseBase): def setUp(self): ServerTestCaseBase.setUp(self) self.server = liblo.ServerThread('1234') def tearDown(self): del self.server def testSendAndReceive(self): self.server.add_method('/foo', 'i', self.callback) self.server.send('1234', '/foo', 42) self.server.start() time.sleep(0.2) self.server.stop() assert self.cb.args[0] == 42 class DecoratorTestCase(unittest.TestCase): class TestServer(liblo.Server): def __init__(self): liblo.Server.__init__(self, portnum) @liblo.make_method('/foo', 'ibm') def foo_cb(self, path, args, types, src, data): self.cb = Arguments(path, args, types, src, data) def setUp(self): self.server = self.TestServer() def tearDown(self): del self.server def testSendReceive(self): liblo.send(portnum, '/foo', 42, ('b', [4, 8, 15, 16, 23, 42]), ('m', (6, 6, 6, 0))) assert self.server.recv() == True assert self.server.cb.path == '/foo' assert len(self.server.cb.args) == 3 class AddressTestCase(unittest.TestCase): def testPort(self): a = liblo.Address(1234) b = liblo.Address('5678') assert a.port == 1234 assert b.port == 5678 assert a.url == 'osc.udp://localhost:1234/' def testUrl(self): a = liblo.Address('osc.udp://foo:1234/') assert a.url == 'osc.udp://foo:1234/' assert a.hostname == 'foo' assert a.port == 1234 assert a.protocol == liblo.UDP def testHostPort(self): a = liblo.Address('foo', 1234) assert a.url == 'osc.udp://foo:1234/' def testHostPortProto(self): a = liblo.Address('foo', 1234, liblo.TCP) assert a.url == 'osc.tcp://foo:1234/' if __name__ == "__main__": unittest.main() pyliblo3-0.16.3/test/unit.py.patch000077500000000000000000000004101474512153000167440ustar00rootroot0000000000000079c79 < assert list(self.cb.args[0]) == [4, 8, 15, 16, 23, 42] --- > assert self.cb.args[0] == [4, 8, 15, 16, 23, 42] 99c99 < assert list(self.cb.args[6]) == [12, 34, 56] --- > assert self.cb.args[6] == [12, 34, 56]