From 70624e71ddc5e84a45f16128e6872e00fad1fbb3 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Mon, 23 Mar 2026 17:19:08 +0100 Subject: [PATCH 01/10] Migrate to mkdocs --- CHANGELOG.md | 2 + LICENSE | 2 +- docs/_logo/compas_logo_white_transparent.png | Bin 0 -> 7852 bytes docs/_logo/favicon.ico | Bin 0 -> 4856 bytes docs/api.rst | 14 -- docs/api/compas_eve.codecs.md | 3 + docs/api/compas_eve.codecs.rst | 2 - docs/api/compas_eve.ghpython.md | 3 + docs/api/compas_eve.ghpython.rst | 2 - docs/api/compas_eve.md | 3 + docs/api/compas_eve.memory.md | 3 + docs/api/compas_eve.memory.rst | 2 - docs/api/compas_eve.mqtt.md | 3 + docs/api/compas_eve.mqtt.rst | 2 - docs/api/compas_eve.rst | 1 - docs/api/compas_eve.zenoh.md | 3 + docs/api/compas_eve.zenoh.rst | 2 - docs/assets/stylesheets/custom.css | 5 + docs/conf.py | 166 ------------------- docs/{examples.rst => examples.md} | 97 ++++++----- docs/{grasshopper.rst => grasshopper.md} | 48 +++--- docs/index.md | 15 ++ docs/index.rst | 44 ----- docs/installation.md | 97 +++++++++++ docs/installation.rst | 117 ------------- docs/license.md | 3 + docs/license.rst | 5 - mkdocs.yml | 145 ++++++++++++++++ requirements-dev.txt | 18 +- src/compas_eve/__init__.py | 2 +- tasks.py | 3 +- 31 files changed, 391 insertions(+), 421 deletions(-) create mode 100644 docs/_logo/compas_logo_white_transparent.png create mode 100644 docs/_logo/favicon.ico delete mode 100644 docs/api.rst create mode 100644 docs/api/compas_eve.codecs.md delete mode 100644 docs/api/compas_eve.codecs.rst create mode 100644 docs/api/compas_eve.ghpython.md delete mode 100644 docs/api/compas_eve.ghpython.rst create mode 100644 docs/api/compas_eve.md create mode 100644 docs/api/compas_eve.memory.md delete mode 100644 docs/api/compas_eve.memory.rst create mode 100644 docs/api/compas_eve.mqtt.md delete mode 100644 docs/api/compas_eve.mqtt.rst delete mode 100644 docs/api/compas_eve.rst create mode 100644 docs/api/compas_eve.zenoh.md delete mode 100644 docs/api/compas_eve.zenoh.rst create mode 100644 docs/assets/stylesheets/custom.css delete mode 100644 docs/conf.py rename docs/{examples.rst => examples.md} (58%) rename docs/{grasshopper.rst => grasshopper.md} (63%) create mode 100644 docs/index.md delete mode 100644 docs/index.rst create mode 100644 docs/installation.md delete mode 100644 docs/installation.rst create mode 100644 docs/license.md delete mode 100644 docs/license.rst create mode 100644 mkdocs.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7142634b..8752f49c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +* Migrated documentation to mkdocs. + ### Removed diff --git a/LICENSE b/LICENSE index 2cd68cc6..a3443640 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Gramazio Kohler Research +Copyright (c) 2026 COMPAS Association Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/_logo/compas_logo_white_transparent.png b/docs/_logo/compas_logo_white_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd158c84d0aeaebe246993a3a6090bc9803e299 GIT binary patch literal 7852 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 KMNUMnLSTYD|Am_X literal 0 HcmV?d00001 diff --git a/docs/_logo/favicon.ico b/docs/_logo/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..381bd9414fc39233e98365aa1149bccf33b64f23 GIT binary patch literal 4856 zcmVKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000OhNkl?W98HS&I&dI$ucghVR2^mNtASFN~V1Q**q&P&ERz$5(M4_My zVriEyZR=212bZ|4Ev;6Ry40F13nB>>D#{=V0W5?FBq2hGW+HRQbcZB&IA`}qFg4^R z(Y`;rrw}&dxtM@69GL7Rhfh)%pgEFBq8Ghs&d+H zjVN+iwEaR*BjuRVXrl%eAP^H;y#B`mzQ6+xrzPQ*DKTVEj3UitA>L*}RRmpqAsRjY z87CUuGd{o2bAMgC4^*)u(G~fMrieqvC{9K+pecf`{?IRby83=MCCO=Bxgd>u(&I>v zwT+rldvB1vUv-dQ+I;u^%8Pdsp}v>uvaB@>=?-T@g!=^%s-UK$uRvABJkT z#b#26@jk8TqNS&w<(Ua#&1|n|nGcnG#5J|lp%Ydc`YTJE#(#BhJw%?aQ zR-!Yk_{+yH@aX$zAf$8Kyou~snt?@AhVlHm!F}(Sv+GoIPjaH;8X-CRZ4=7S%oS=c z_U^MsM&uk?ef5aJC9tWu0c;V(rMURGyoE2CJz=?KRbl5(W^v<;_^5_PcZq~aLWD6O z1hlyO3)AE6H<$iwJ}FMisJkL(>Ui)5XlwN{IVFlY364?M#wROhvobr?+T`vpmXf4l z2Ks}>V}X!y_r8_0ndFKbRZL1KI@ivuD`R*dJB6CMZYtV+V{W;3mP}`Mf+MEQ8~AXD z0ZkDuUr^s@2*LW@l^m$<998I>-H1S zjZ9UDr2vWme182mlcFQZOmMLCvubYIT+HM9s^|+D!-PVH>6fcALZAqN*528>?vbn{idN0xk;T)&q_VNF zf?2O0C%>#^h}g#B2Ji;Rzbk$4-I}W=kTgAx)jQ4%;eOHL;m)mPJoL^<+)W-9ElA_Y zPp@XzvRU+nj3i%3&tcpji|(**I4JA7`{Xw`cA-aRzj;FP@V_Jv?T|O;pBfzFvEA~) zonOk!ar8pBWObn|sBD)~O8L^!I?4K7l2rvV_QfLk*2%^p{M+6=EqQkT7Nye{xMN|O ziyfil)PByuv>ABA?DyT|wYShSkUR4H8jTP^e;g}M` z)7h!KQrtl5^9R{l-pb+nZf3sn9~R}EWJauw+#k;*Z$|~?Z9X(bjHsI&Wg)>~vM6YZ zCE7Y_QDozZ2Aa!Td3Qx70DHgcptr@t@@-}0Y$>JcTs!A3_7XHCuiZKoVyvtvJTt1M zJwnA6p&AMTR57wsPeAAKZRKQKo5Gz_VgM+-FN4?C&1am$OrST&%3H6bJa_(J#(!Fx zLD|uIww-Gqqtt_fE_IJTG$L{R-t*Ae$Gi8;9DHZd6mqkYm>gq8@95>X3$Gx_Y37?W z9-o^`LPk8Rx0a5m+aEFr=!T*YGFlrwfv}|0tzPnXo@4pqbgqgU-UlcGFl#ip{lmuc zmS)gf)6K8B0(HJbs>9a0S!GTL;HawRmv5{TDT>)%3}sy6$`Zx`A08s7-4> ztmK`2Rp2m_lH_1_RR=x&K`w75;%z3(5h|@7KYo4au^p)?C_Ts3O4rRN z!EOTJKy?>LS*?w;Xdl$IdN+Sm)xnZ!u}ENd)kTQ2krr)b`^hG@AFktjLja0G`~*9i z%R*y)Hx=z(671vX3mSY>)q&k%-l+*eE1I%*(}~8MCDURNz`CoGc&^|KwVi$B-f|_& zX2yq&k?1gEHK{c8^wS@vaGmey<>8%QLYt4QIf=}@)ZpZmHq+SZVPb+KPm>ZRxFVlA zaJDVyKus4nUJ=b7emIE~r-h(lkUKYd#3Tc{L3dw>c}d|rrv<4lCPzn7QrAUpb}Dw0 ziY_G^4%ZUtF#iScisX$)P=uhVr|*M_QI^Hkk6!zozw)fj#gv_C<+CTQV_{nKcbfC_ zkI(b!!D>A<(P`HW*{=W?Qj+M3Tv5~Jt6#jO^gA0cX#yftMmceZU~@?$uO6u*F4lgB zVaWa=Etxc=cVe7<-mcTl-kUB}w6S1T6^IyE?PK9@$1jk(tsIxryecw6*&{FS3FSbu z*P5K@n0KJO#kYKnv;6yDQ*i^oD5$_S-uh&eMa$C-8NMx-fRLO`PIS)RTGG^U&6_7U z-r)Yf1NZnt$LhL^u6w(Lwf{axNs~Kl zK|z;9nTJ->7H@!;KCdJ5^* { + --md-primary-fg-color: #0092d2; + --md-primary-fg-color--light: #0092d2; + --md-primary-fg-color--dark: #0092d2; +} \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index f07772d8..00000000 --- a/docs/conf.py +++ /dev/null @@ -1,166 +0,0 @@ -# flake8: noqa -# -*- coding: utf-8 -*- - -from sphinx.writers import html, html5 -import sphinx_compas2_theme - -# -- General configuration ------------------------------------------------ - -project = "COMPAS EVE" -copyright = "Gramazio Kohler Research" -author = "Gonzalo Casas" -organization = "compas-dev" -package = "compas_eve" - -master_doc = "index" -source_suffix = {".rst": "restructuredtext", ".md": "markdown"} -templates_path = sphinx_compas2_theme.get_autosummary_templates_path() -exclude_patterns = sphinx_compas2_theme.default_exclude_patterns -add_module_names = True -language = "en" - -latest_version = sphinx_compas2_theme.get_latest_version() - -if latest_version == "Unreleased": - release = "Unreleased" - version = "latest" -else: - release = latest_version - version = ".".join(release.split(".")[0:2]) # type: ignore - -# -- Extension configuration ------------------------------------------------ - -extensions = sphinx_compas2_theme.default_extensions - -# numpydoc options - -numpydoc_show_class_members = False -numpydoc_class_members_toctree = False -numpydoc_attributes_as_param_list = True -numpydoc_show_inherited_class_members = False - -# bibtex options - -# autodoc options - -autodoc_type_aliases = {} -autodoc_typehints_description_target = "documented" -autodoc_mock_imports = sphinx_compas2_theme.default_mock_imports -autodoc_default_options = { - "undoc-members": True, - "show-inheritance": True, -} -autodoc_member_order = "groupwise" -autodoc_typehints = "description" -autodoc_class_signature = "separated" - -autoclass_content = "class" - - -def setup(app): - app.connect("autodoc-skip-member", sphinx_compas2_theme.skip) - - -# autosummary options - -autosummary_generate = True -autosummary_mock_imports = sphinx_compas2_theme.default_mock_imports - -# graph options - -# plot options - -plot_include_source = False -plot_html_show_source_link = False -plot_html_show_formats = False -plot_formats = ["png"] - -# intersphinx options - -intersphinx_mapping = { - "python": ("https://docs.python.org/", None), - "compas": ("https://compas.dev/compas/latest/", None), -} - -# linkcode - -linkcode_resolve = sphinx_compas2_theme.get_linkcode_resolve(organization, package) - -# extlinks - -extlinks = { - "rhino": ("https://developer.rhino3d.com/api/RhinoCommon/html/T_%s.htm", "%s"), - "blender": ("https://docs.blender.org/api/2.93/%s.html", "%s"), -} - -# from pytorch - -sphinx_compas2_theme.replace(html.HTMLTranslator) -sphinx_compas2_theme.replace(html5.HTML5Translator) - -# -- Options for HTML output ---------------------------------------------- - -html_theme = "sidebaronly" -html_title = project -html_sidebars = {"index": []} - -favicons = [ - { - "rel": "icon", - "href": "compas.ico", - } -] - -html_theme_options = { - "external_links": [ - {"name": "COMPAS Framework", "url": "https://compas.dev"}, - ], - "icon_links": [ - { - "name": "GitHub", - "url": f"https://github.com/{organization}/{package}", - "icon": "fa-brands fa-github", - "type": "fontawesome", - }, - { - "name": "Discourse", - "url": "http://forum.compas-framework.org/", - "icon": "fa-brands fa-discourse", - "type": "fontawesome", - }, - { - "name": "PyPI", - "url": f"https://pypi.org/project/{package}/", - "icon": "fa-brands fa-python", - "type": "fontawesome", - }, - ], - "switcher": { - "json_url": f"https://raw.githubusercontent.com/{organization}/{package}/gh-pages/versions.json", - "version_match": version, - }, - "logo": { - "image_light": "_static/compas_icon_white.png", - "image_dark": "_static/compas_icon_white.png", - "text": "COMPAS docs", - }, - "navigation_depth": 2, -} - -html_context = { - "github_url": "https://github.com", - "github_user": organization, - "github_repo": package, - "github_version": "main", - "doc_path": "docs", -} - -html_static_path = sphinx_compas2_theme.get_html_static_path() + ["_static"] -html_css_files = [] -html_extra_path = [] -html_last_updated_fmt = "" -html_copy_source = False -html_show_sourcelink = True -html_permalinks = False -html_permalinks_icon = "" -html_compact_lists = True diff --git a/docs/examples.rst b/docs/examples.md similarity index 58% rename from docs/examples.rst rename to docs/examples.md index 1707fb9a..f53f405a 100644 --- a/docs/examples.rst +++ b/docs/examples.md @@ -1,21 +1,16 @@ -******************************************************************************** -Examples -******************************************************************************** +# Examples -.. currentmodule:: compas_eve +!!! note + This tutorial assumes that you have already installed `compas_eve`. + If you haven't, please follow the instructions in the [installation](installation.md) section. -.. note:: - - This tutorial assumes that you have already installed ``compas_eve``. - If you haven't, please follow the instructions in the :ref:`installation` section. - -The main feature of ``compas_eve`` is to allow communication between different +The main feature of `compas_eve` is to allow communication between different parts of a program using messages. These messages are sent around using a publisher/subscriber model, or pub/sub for short. In pub/sub communication, messages are not sent directly from a sender to a receiver, instead, they are -sent to a :class:`Topic`. A topic is like a mailbox, the :class:`Publisher` +sent to a [Topic][compas_eve.Topic]. A topic is like a mailbox, the [Publisher][compas_eve.Publisher] sends messages to the topic without the need for a subcriber to be -actively listening for messages, and also the :class:`Subscriber` can start +actively listening for messages, and also the [Subscriber][compas_eve.Subscriber] can start listening for messages on a topic without the need for any publisher to be currently sending anything. @@ -26,43 +21,42 @@ An additional benefit of pub/sub is that it is not limited to 1-to-1 communicati on a single topic, there can be multiple publishers and multiple subscribers all communicating at the same time without additional coordination. -Hello World ------------ +## Hello World -Let's see a **Hello World** example of this type of communication using ``compas_eve``. +Let's see a **Hello World** example of this type of communication using `compas_eve`. This example is very contrived because both the publisher and the subscriber are defined in the same script and the same thread. -.. literalinclude :: examples/01_hello_world.py - :language: python +```python +--8<-- "docs/examples/01_hello_world.py" +``` This example is the simplest possible, and it only shows the main concepts needed to communicate. In particular, ``compas_eve`` uses by default an **in-memory transport** for the messages, this means that messages are can only be received within the same program. -Hello Threaded World --------------------- +## Hello Threaded World Let's try to extend this first example and add multiple threads to illustrate multi-threaded communication: -.. literalinclude :: examples/02_hello_threaded_world.py - :language: python +```python +--8<-- "docs/examples/02_hello_threaded_world.py" +``` + +This get more interesting! Now the publisher and subscriber are in separate threads. +However, the in-memory transport is limited to *same-process*. This means that if we launch this script twice, the messages will not jump from one process to the other. -This get more interesting! Now the publisher and subscriber are in separate threads. However, -the in-memory transport is limited to *same-process*. This means that if we launch this -script twice, the messages will not jump from one process to the other. -In other words, if we want to communicate with a subscriber on a different process on the machine, -or even on a completely separate machine, we need to take an extra step. +In other words, if we want to communicate with a subscriber on a different process on +the machine, or even on a completely separate machine, we need to take an extra step. -Hello Distributed World ------------------------ +## Hello Distributed World Fortunately, it is very easy to extend our example and enable communication across processes, machines, networks, continents, and anything that is connected to the Internet! -The only difference is that we are going to configure a different :class:`Transport` implementation for -our messages. In this case, we will use the MQTT transport method. `MQTT `_ +The only difference is that we are going to configure a different [Transport][compas_eve.Transport] implementation for +our messages. In this case, we will use the MQTT transport method. [MQTT](https://en.wikipedia.org/wiki/MQTT) is a network protocol very popular for IoT applications because of its lightweight. We are going to split the code and create one script for sending messages with a publisher and a different @@ -71,13 +65,17 @@ potentially from different machines! First the publisher example: -.. literalinclude :: examples/03_hello_distributed_world_pub.py - :language: python +```python +--8<-- "docs/examples/03_hello_distributed_world_pub.py" +``` + +### Subscriber And now the subscriber example: -.. literalinclude :: examples/03_hello_distributed_world_sub.py - :language: python +```python +--8<-- "docs/examples/03_hello_distributed_world_sub.py" +``` You can start both programs in two completely different terminal windows, or even completely different computers and they will be able to communicate! @@ -86,15 +84,38 @@ And since pub/sub allows any number of publishers and any number of subscriber per topic, you can start the same scripts more than once and the messages will be received and send multiple times! -Add typing information to messages ----------------------------------- +## Add typing information to messages So far, we have defined our messages as simple dictionaries. It is also possible to define a class that messages need to comform to, in order to get typing information on the messages. -.. literalinclude :: examples/04_message_type.py - :language: python +```python +--8<-- "docs/examples/04_message_type.py" +``` This example also shows how to set a default transport so that it does not need to be specified on every publisher and subscriber instance. + +## Distributed communication with Zenoh + +Just like the MQTT example above, we can achieve distributed communication +using [Apache Zenoh](https://zenoh.io/) as transport. Zenoh is an open source +implementation of a very fast and efficient protocol built around the idea of +Zero Overhead Network Protocol. + +The syntax remains almost identical, demonstrating how `compas_eve` abstracts +away the underlying transport layers. You only need to change the transport +initialization. + +First, let's look at the publisher using Zenoh: + +```python +--8<-- "docs/examples/05_zenoh_distributed_world_pub.py" +``` + +Next, we create the matching subscriber: + +```python +--8<-- "docs/examples/05_zenoh_distributed_world_sub.py" +``` diff --git a/docs/grasshopper.rst b/docs/grasshopper.md similarity index 63% rename from docs/grasshopper.rst rename to docs/grasshopper.md index c5231eb9..942b9e0a 100644 --- a/docs/grasshopper.rst +++ b/docs/grasshopper.md @@ -1,38 +1,29 @@ -******************************************************************************** -Examples in Rhino/Grasshopper -******************************************************************************** +# Grasshopper Integration -.. currentmodule:: compas_eve - -.. note:: - - This tutorial assumes that you have already installed ``compas_eve``. - If you haven't, please follow the instructions in the :ref:`installation` section. +!!! note + This tutorial assumes that you have already installed `compas_eve`. + If you haven't, please follow the instructions in the [installation](installation.md) section. **COMPAS EVE** provides tools to work with events inside Rhino/Grasshopper, as well as the ability to run long-running tasks in the background, which would otherwise block the UI. -Long-running tasks ------------------- +## Long-running tasks A long-running task is any snippet of code that takes a long time to execute. Normally, this would freeze the Grasshopper user interface. **COMPAS EVE** provides a mechanism to run such tasks in the background, so that the user can continue working with Grasshopper while the task is running. -In order to use it, add a ``Background task`` component to your Grasshopper definition, and connect +In order to use it, add a `Background task` component to your Grasshopper definition, and connect an input with a python function containing the code that needs to run in the background. The only -requirement is that this function must accept a ``worker`` argument, which is an instance of -:class:`~compas_eve.ghpython.BackgroundWorker`. +requirement is that this function must accept a `worker` argument, which is an instance of +[BackgroundWorker](compas_eve.ghpython.BackgroundWorker). -.. figure:: /_images/background-task.png - :figclass: figure - :class: figure-img img-fluid +![PubSub](_images/background-task.png) The following code exemplifies how to use it to create a simple background task that generates a list of random values. The function adds some delay to simulate a long-running task. -.. code-block:: python - +```python import time import random @@ -47,7 +38,7 @@ a list of random values. The function adds some delay to simulate a long-running worker.display_message("Done!") return result - +``` It is also possible to update the results during the execution of the task. The result can be of any type, in the previous example it was a list of numbers. @@ -56,8 +47,7 @@ In the following example, the code generates a list of randomly placed Rhino poi and continuously updates the results as the list grows. The points will appear in the Rhino Viewport even before the task has completed. -.. code-block:: python - +```python import time import random import Rhino.Geometry as rg @@ -74,3 +64,17 @@ in the Rhino Viewport even before the task has completed. worker.display_message("Done!") return result +``` + +## Components + +The following components are available in Grasshopper: + +| Component | Description | +| --------- | ----------- | +| `MqttConnect` | Connects to an MQTT broker. | +| `ZenohConnect` | Connects to a Zenoh router. | +| `Message` | Creates a new `compas_eve` message. | +| `Publish` | Publishes a message to a specific topic. | +| `Subscribe` | Subscribes to a certain topic. | +| `BackgroundTask` | Runs a function continuously in the background,
useful for running any kind of long running tasks. | diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..009fb604 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,15 @@ +# Event Extensions for COMPAS + +`compas_eve` adds event-based communication infrastructure to the COMPAS framework. +Using events is a way to decouple the components of a system, making it easier to develop, test, and maintain. + +![PubSub](_images/pubsub.png) + +```pycon +>>> import compas_eve as eve +>>> pub = eve.Publisher("/hello_world") +>>> sub = eve.Subscriber("/hello_world", print) +>>> sub.subscribe() +>>> for i in range(10): +... pub.publish(dict(text=f"Hello World {i}")) +``` diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 20950188..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,44 +0,0 @@ -******************************************************************************** -Event Extensions for COMPAS -******************************************************************************** - -.. rst-class:: lead - -``compas_eve`` adds event-based communication infrastructure to the COMPAS framework. - -Using events is a way to decouple the components of a system, making it easier to develop, test, and maintain. - -.. figure:: /_images/pubsub.png - :figclass: figure - :class: figure-img img-fluid - - -.. code-block:: python - - >>> import compas_eve as eve - >>> pub = eve.Publisher("/hello_world") - >>> sub = eve.EchoSubscriber("/hello_world") - >>> sub.subscribe() - >>> for i in range(10): - ... pub.publish(dict(text=f"Hello World {i}")) - -Table of Contents -================= - -.. toctree:: - :maxdepth: 2 - :titlesonly: - - Introduction - installation - examples - grasshopper - api - license - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 00000000..d58ae469 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,97 @@ +# Installation + +This chapter provides a step-by-step guide for installing `compas_eve` on your system. +We highly recommend using [uv](https://docs.astral.sh/uv/) for managing +your Python environment and dependencies, as it is significantly faster and +more reliable. Alternatively, you can simply use standard `pip` or `conda`. + +## Install uv + +If you do not have `uv` installed, follow the instructions on their website or run: + +=== "Mac/Linux" + + ```bash + curl -LsSf https://astral.sh/uv/install.sh | sh + ``` + +=== "Windows" + + ```powershell + powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" + ``` + +## Create a virtual environment + +It is best practice to install `compas_eve` in a virtual environment. +Navigate to your project directory and run: + +```bash +uv venv +``` + +This creates a virtual environment in `.venv`. Activate it with: + +=== "Mac/Linux" + + ```bash + source .venv/bin/activate + ``` + +=== "Windows" + + ```powershell + .venv\Scripts\activate + ``` + +## Install compas_eve + +With your virtual environment activated, install `compas_eve`: + +```bash +uv pip install compas_eve +``` + +## Verify installation + +Verify that `compas_eve` is available: + +```bash +python -m compas_eve +``` + +If the version number is printed, the installation is complete. + +## Install for Rhino + +COMPAS EVE is compatible with Rhino 8 and later versions. + +### Rhino Script Editor + +To use `compas_eve` in a Python script, simply add the requirement header to the top of your script in the Rhino 8 Script Editor: + +```python +# r: compas_eve +``` +### Grasshopper + +To use `compas_eve` Grasshopper components, open the `Package Manager`, search for `compas_eve` and click `Install`. + +## Transports + +Depending on the transport you want to use, you might need to install additional dependencies. + +### MQTT Transport + +The `MQTT` transport is the default option and is installed automatically with `compas_eve`. + +### Zenoh Transport + +The `Zenoh` transport requires the `eclipse-zenoh` package, which is an optional dependency. +To install it, run: + +```bash +uv pip install compas_eve[zenoh] +``` + +For more details about Zenoh, refer to the [Eclipse Zenoh](https://zenoh.io/) website. diff --git a/docs/installation.rst b/docs/installation.rst deleted file mode 100644 index 6784cc5d..00000000 --- a/docs/installation.rst +++ /dev/null @@ -1,117 +0,0 @@ -******************************************************************************** -Installation -******************************************************************************** - -.. highlight:: bash - -**COMPAS EVE** can be easily installed on multiple platforms, -using popular package managers such as conda or pip. - -Install with conda -================== - -The recommended way to install **COMPAS EVE** is with `conda `_. -For example, create an environment named ``project_name`` and install ``compas_eve``. - -:: - - conda create -n project_name -c conda-forge compas_eve - -Afterwards, simply activate the environment and run the following -command to check if the installation process was successful. - -.. code-block:: bash - - conda activate project_name - python -m compas_eve - -.. code-block:: none - - COMPAS EVE v2.1.1 is installed! - -You are ready to use **COMPAS EVE**! - -Installation options --------------------- - -Install COMPAS EVE in an environment with a specific version of Python. - -.. code-block:: bash - - conda create -n project_name python=3.8 compas_eve - -Install COMPAS EVE in an existing environment. - -.. code-block:: bash - - conda install -n project_name compas_eve - -Install with pip -================ - -Install COMPAS EVE using ``pip`` from the Python Package Index. - -.. code-block:: bash - - pip install compas_eve - -Install an editable version from local source. - -.. code-block:: bash - - cd path/to/compas_eve - pip install -e . - -Note that installation with ``pip`` is also possible within a ``conda`` environment. - -.. code-block:: bash - - conda activate project_name - pip install -e . - - -Update with conda -================= - -To update COMPAS EVE to the latest version with ``conda`` - -.. code-block:: bash - - conda update compas_eve - -To switch to a specific version - -.. code-block:: bash - - conda install compas_eve=2.1.1 - - -Update with pip -=============== - -If you installed COMPAS EVE with ``pip`` the update command is the following - -.. code-block:: bash - - pip install --upgrade compas_eve - -Or to switch to a specific version - -.. code-block:: bash - - pip install compas_eve==2.1.1 - - -Working in Rhino -================ - -To make **COMPAS EVE** available inside Rhino, open the *command prompt*, -activate the appropriate environment, and type the following: - -:: - - python -m compas_rhino.install - -Open Rhino, start the Python script editor, type ``import compas_eve`` and -run it to verify that your installation is working. - diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 00000000..8187a4fc --- /dev/null +++ b/docs/license.md @@ -0,0 +1,3 @@ +# License + +--8<-- "LICENSE" \ No newline at end of file diff --git a/docs/license.rst b/docs/license.rst deleted file mode 100644 index e6a80ce0..00000000 --- a/docs/license.rst +++ /dev/null @@ -1,5 +0,0 @@ -******************************************************************************** -License -******************************************************************************** - -.. literalinclude:: ../LICENSE diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..688df3d4 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,145 @@ +site_name: COMPAS EVE +site_url: https://compas.dev/compas_eve +repo_url: https://github.com/compas-dev/compas_eve +repo_name: compas-dev/compas_eve +edit_uri: blob/main/docs/ + +copyright: Copyright © 2026, COMPAS Association + +extra: + homepage: https://compas.dev/compas_eve + version: + provider: mike + +theme: + name: material + palette: + - scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + - scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + font: + text: Roboto + code: Roboto Mono + logo: _logo/compas_logo_white_transparent.png + favicon: _logo/favicon.ico + features: + - content.code.copy + - content.footnote.tooltips + - navigation.expand + - navigation.footer + - navigation.indexes + - navigation.sections + - navigation.top + - search.highlight + - search.suggest + - toc.follow + +extra_css: + - assets/stylesheets/custom.css + +markdown_extensions: + - abbr + - attr_list + - admonition + - callouts: + strip_period: no + - footnotes + - md_in_html + - pymdownx.blocks.caption + - pymdownx.caret + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + - pymdownx.keys + - pymdownx.mark + - pymdownx.tasklist: + custom_checkbox: true + - pymdownx.tilde + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.superfences + - pymdownx.snippets: + check_paths: true + - toc: + permalink: "ยค" + - pymdownx.tabbed: + alternate_style: true + +plugins: + - search + - mkdocstrings: + default_handler: python + handlers: + python: + paths: [src] # search packages in the src folder + inventories: + - https://docs.python.org/3/objects.inv + - https://compas.dev/compas/latest/objects.inv + options: + allow_inspection: true + backlinks: tree + docstring_options: + ignore_init_summary: true + trim_doctest_flags: true + docstring_style: numpy + docstring_section_style: list + filters: public + group_by_category: true + heading_level: 2 + inheritance_diagram_direction: TD + inherited_members: false + line_length: 88 + merge_init_into_class: true + modernize_annotations: true + parameter_headings: false + preload_modules: [mkdocstrings, compas] + relative_crossrefs: true + scoped_crossrefs: true + separate_signature: true + show_bases: false + show_category_heading: true + show_docstring_attributes: true + show_docstring_functions: true + show_docstring_modules: false + show_if_no_docstring: false + show_inheritance_diagram: false + show_root_heading: true + show_root_full_path: true + show_signature: true + show_signature_annotations: true + show_signature_type_parameters: true + show_source: false + show_submodules: false + show_symbol_type_heading: true + show_symbol_type_toc: true + signature_crossrefs: true + summary: + modules: false + type_parameter_headings: true + unwrap_annotated: true + +nav: + - Home: index.md + - Installation: installation.md + - Examples: examples.md + - Grasshopper: grasshopper.md + - API Reference: + - compas_eve: api/compas_eve.md + - compas_eve.codecs: api/compas_eve.codecs.md + - compas_eve.memory: api/compas_eve.memory.md + - compas_eve.mqtt: api/compas_eve.mqtt.md + - compas_eve.zenoh: api/compas_eve.zenoh.md + - compas_eve.ghpython: api/compas_eve.ghpython.md + - License: license.md \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt index 1772fe59..fad6e5c8 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,4 +10,20 @@ ruff sphinx_compas2_theme twine wheel -pythonnet \ No newline at end of file +pythonnet +markdown-callouts >=0.4 +markdown-exec >=1.8 +mike +mkdocs >=1.6 +mkdocs-autorefs >=1.4 +mkdocs-coverage >=1.0 +mkdocs-git-revision-date-localized-plugin >=1.2 +mkdocs-llmstxt >=0.2 +mkdocs-material >=9.5 +mkdocs-minify-plugin >=0.8 +mkdocs-redirects >=1.2 +mkdocs-section-index >=0.3 +mkdocs-mermaid2-plugin +mkdocstrings[python] +pydantic >=2.10 +tomli >=2.0; python_version < '3.11' diff --git a/src/compas_eve/__init__.py b/src/compas_eve/__init__.py index 347552ec..ef1a0ef3 100644 --- a/src/compas_eve/__init__.py +++ b/src/compas_eve/__init__.py @@ -32,7 +32,7 @@ __author__ = ["Gonzalo Casas"] -__copyright__ = "Gramazio Kohler Research" +__copyright__ = "COMPAS Association" __license__ = "MIT License" __email__ = "casas@arch.ethz.ch" __version__ = "2.1.1" diff --git a/tasks.py b/tasks.py index 9cd84565..50ae0e3a 100644 --- a/tasks.py +++ b/tasks.py @@ -5,6 +5,7 @@ from compas_invocations2 import build from compas_invocations2 import docs +from compas_invocations2 import mkdocs from compas_invocations2 import style from compas_invocations2 import tests from compas_invocations2 import grasshopper @@ -18,7 +19,7 @@ style.check, style.lint, style.format, - docs.docs, + mkdocs.docs, docs.linkcheck, tests.test, tests.testdocs, From ef7b85f118250b52adecea6b1fe74e936a5d50e4 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Mon, 23 Mar 2026 17:20:52 +0100 Subject: [PATCH 02/10] Remove deprecated installation method to Rhino --- src/compas_eve/__init__.py | 1 - src/compas_eve/rhino/__init__.py | 0 src/compas_eve/rhino/install.py | 58 -------------------------------- 3 files changed, 59 deletions(-) delete mode 100644 src/compas_eve/rhino/__init__.py delete mode 100644 src/compas_eve/rhino/install.py diff --git a/src/compas_eve/__init__.py b/src/compas_eve/__init__.py index ef1a0ef3..c758ba59 100644 --- a/src/compas_eve/__init__.py +++ b/src/compas_eve/__init__.py @@ -69,4 +69,3 @@ "set_default_transport", "InMemoryTransport", ] -__all_plugins__ = ["compas_eve.rhino.install"] diff --git a/src/compas_eve/rhino/__init__.py b/src/compas_eve/rhino/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/compas_eve/rhino/install.py b/src/compas_eve/rhino/install.py deleted file mode 100644 index b3011de0..00000000 --- a/src/compas_eve/rhino/install.py +++ /dev/null @@ -1,58 +0,0 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import glob -import os - -import compas -import compas.plugins -from compas_ghpython.components import install_userobjects -from compas_ghpython.components import uninstall_userobjects - - -@compas.plugins.plugin(category="install") -def installable_rhino_packages(): - return ["compas_eve"] - - -@compas.plugins.plugin(category="install") -def after_rhino_install(installed_packages): - project = "compas_eve" - if project not in installed_packages: - return [] - - srcdir = os.path.join(os.path.dirname(__file__), "..", "ghpython", "components", "ghuser") - installed_objects = install_userobjects(srcdir) - msg = "Installed {} GH User Objects".format(len(installed_objects)) - - return [ - ( - project, - msg, - True, - ) - ] - - -@compas.plugins.plugin(category="install") -def after_rhino_uninstall(uninstalled_packages): - project = "compas_eve" - if project not in uninstalled_packages: - return [] - - srcdir = os.path.join(os.path.dirname(__file__), "..", "ghpython", "components", "ghuser") - userobjects = [os.path.basename(ghuser) for ghuser in glob.glob(os.path.join(srcdir, "*.ghuser"))] - uninstalled_objects = uninstall_userobjects(userobjects) - - uninstall_errors = [uo[0] for uo in uninstalled_objects if not uo[1]] - error_msg = "" if not uninstall_errors else "and {} failed to uninstall".format(len(uninstall_errors)) - msg = "Uninstalled {} GH User Objects {}".format(len(uninstalled_objects), error_msg) - - return [ - ( - project, - msg, - True, - ) - ] From 3549b80ebe758930b3bbef9de576ab3d1ab2a12b Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Mon, 23 Mar 2026 17:22:04 +0100 Subject: [PATCH 03/10] Tiny tweaks --- docs/grasshopper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/grasshopper.md b/docs/grasshopper.md index 942b9e0a..bb77e072 100644 --- a/docs/grasshopper.md +++ b/docs/grasshopper.md @@ -76,5 +76,5 @@ The following components are available in Grasshopper: | `ZenohConnect` | Connects to a Zenoh router. | | `Message` | Creates a new `compas_eve` message. | | `Publish` | Publishes a message to a specific topic. | -| `Subscribe` | Subscribes to a certain topic. | -| `BackgroundTask` | Runs a function continuously in the background,
useful for running any kind of long running tasks. | +| `Subscribe` | Subscribes to a specific topic. | +| `BackgroundTask` | Runs a function continuously in the background. | From 01a513de9f56cfd54f0021da7c14f89be67f361f Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Mon, 23 Mar 2026 17:24:56 +0100 Subject: [PATCH 04/10] Add component icons to docs --- docs/_images/background_task.png | Bin 0 -> 492 bytes docs/_images/message.png | Bin 0 -> 1505 bytes docs/_images/mqtt_connect.png | Bin 0 -> 1439 bytes docs/_images/publish.png | Bin 0 -> 1684 bytes docs/_images/subscribe.png | Bin 0 -> 1671 bytes docs/_images/zenoh_connect.png | Bin 0 -> 1781 bytes docs/grasshopper.md | 16 ++++++++-------- 7 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 docs/_images/background_task.png create mode 100644 docs/_images/message.png create mode 100644 docs/_images/mqtt_connect.png create mode 100644 docs/_images/publish.png create mode 100644 docs/_images/subscribe.png create mode 100644 docs/_images/zenoh_connect.png diff --git a/docs/_images/background_task.png b/docs/_images/background_task.png new file mode 100644 index 0000000000000000000000000000000000000000..3603fbb4278569a3fdaea5ca4bfcc72a3523619d GIT binary patch literal 492 zcmVmr-+ToW++_?ex&3b8fHe-g(cPxlg@4=bZO>e&6$_ z^Sp0}F&d|>8ml@cawCLL62BiySZoUH?@WwQ;Wk}C#O0FUzHT8Pi_c-}N&ybzDAwY8 z0_5i5TUbPtnl0T)v84Xmk+XUIc@1p0;hsb2)xmy+&`Sot38 zs`byO$($+$JisTc9|0J&fERdG0_flyt`~}DJDXO~9R{f1?Q;z#%W-NlU;je8uXax&wf7sslS#DBe|V=}qjw-X_3= zstk;}2X)(m*Z7F3l+(k^a)1o!EF;{(X}rf59Kl9a5nRMi9M8HTi-!=dVN=7bH{(!> zdx|qSiAw`8V?|;v8o;+9SDR!PJv9yFcCy&jWDw&y`VQPBz z<-z{G?}KcDlniWkLn%R)m*Xsl=0NXRS%9+m))cHyT5#vi9VsX%ko^37^UFR%eU9$ay;0CM~GZCPAgl(Moi zX=`f}5s^K6_DFksdlJjH`DM`_2Bs3BP}p?UG!3N`j~+duw6v6)H*eze`8aXn1nuqZ zhzNl|0EfdtOG^uT_wEH?X=w>v*ONL3OwA;;@XSQ;!=bxL1rafaeB;IqDl03wbmE!I$vz$70 zih+Rvyk0NcwrwLA4AS4EmuqLd;UjRN3uxll^c(9i(D=;$aO zkB2{Wb+dDaf7(dT&Q`iJo0V9&#AZt!`1jZdE~kTg_wF$`ILP$$H1qTGhzS2J5kV=1 z-EPP2cH{AQ@cDcs1adqDOifK}(o-EhSkm2C(%mQ{#u)}9k2LV zU%Zaht5>0bx!^p5Lql|4|1%$4`8EIed(TH0mhT#N=Wj$n%s)P#G%!n=upD0eDv9{_ zKwh%C-03ctOIHelbHPL`IJp3-58e2gVROEZtbkGoD#_qKy!`U0@gWp500000NkvXX Hu0mjf_7n1r literal 0 HcmV?d00001 diff --git a/docs/_images/mqtt_connect.png b/docs/_images/mqtt_connect.png new file mode 100644 index 0000000000000000000000000000000000000000..b5877cd8247fc79b8bda351c80c52f93fb51d89e GIT binary patch literal 1439 zcmV;Q1z`G#P)grcFvBpYU?HK!w#r3=38FRi zqQ+i`CMp=UYH*3QY7=X!Ce|uisTEhOOXJddp|MR|Nb4>(je^xNqM$HrZo@!fKxWR& z`FnvL#HDwAFTeNA_nhzl{{Kiq2=1J%`f$sfEmKTEvo0bcg69&dieLE2otdkAvSd86 zQRZcx-+f-cb#93+J}#bqQT+*o0z8fEQs?iOe=sdKy*vWIefrh}UvulU7$ixj)Aa%n z1ckzl6E{LJP8&G|Ij;bm?>hB%mqV(%Ux&-0M(z0;YNn_eynA($dfRUh$4 zG7w3aFfuY$te7#QM^6ha_~{@uX(*qrm_Y7lqx(PtZ2{JeU&r~2=NZHx{P>A8#RW^c zN(_<^0#Aj<-}O&dRQtnr9#%i3?wW^tj@JkjyO!1XrTokK1O7Xpm0|qj!Z`% z(C!(f9NBw>INrjQoK(NI_FI=EgaBvWj1q-gt{{U%bt+ z)yKH>$0cmo7{CCksb)yR5H{Z0h$-5nWmmrQH5Kkkl5kPQZI-NA!nm)-{Wsvb&h0tJ zwwc>dQL$sEnrbozXRzVk1})?B_?VkK7ZWBF6ihl3C2b`hlhxF-gFOX%*fDDdW!q2V zt$)%7kTWBPO%Go%^q zb<|m9WTJ^?WWh)Z))iohw`fh8|88dCu!R^fV8%=%jTE0LCT&z20D-Omh5ZT%hJ%C% zkz{vz);AUPOc3jcC6Po1GZ-^wj_*It9M^2zzqvJlJ=4yz{mby-LysOAnS-+qXuiR0 zAg3S)FJ5G1kb3I9|7LprKQb~Yq@V;tEFHg;o5yczepY@Kqq9fTNF#RaT)SVv_2bty zKYLC#QCLt=(98N)0nZgCOf0cjv9fl;H?%gjYTl%hNf=J~ z4z2cDz>5$sEgYJCP^&vLoxGPZ3@BPcdQSk@AR|%Qd|p@O~1{diDas;c!rT!!SmScxmD4FITl%%E(kKSP%#T zbo1c$1I>#~w)MP+Zbb%&08m3}q#r*K=ryzeq~})wLG6`Vt%)t!hJ`4e@{|BUS{r(I z6cuYBK!9Mk60HI9zR#Olx~z0cKefN4*J-CEgb;e63SBUyfL>@#O#Mv6IR?@Z=s>8T ti|jpN*(ehm7aPti%KHhPJ+%K*{{qXkFxK$2g!BLa002ovPDHLkV1i6jln4L- literal 0 HcmV?d00001 diff --git a/docs/_images/publish.png b/docs/_images/publish.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e24f703cb615c763830a7624a881a6a7bfecc1 GIT binary patch literal 1684 zcmV;F25b3=P)?O4 zsGG}VW?Z&VR73=$F>J{$X4w+h41~=Eh{+<7ku79*GC^ewahX%tbVEQW*S55@g_d&; zr#t&=lk5B-}4wd660eEhKDI1Tg5$dTj=ZW}JO&1D-u2euH@*ja zeeV24ENorC%o)?Dtf)W|on2i8C;st%-Jkz*bm`lN4{ina)Yeb=uID-J{4d#&SeBif zTyP>^1(IJ_`hbqT{a1Y&{q3E<>!B|{B48No)sehnuw&rdX3L9Wc~P8bF2H&~FYVkt z%E-|0kTyR5O)Z{&zwVvBOzWR{UboV>Mq&2u`=v0Rfai>t2X@W~d+{P71tkCvgQGW( z9J?dRQ>V`g5Y>o@2+)ZWAKaPEdnaVPn1B^-ei?3VF+>Z?adQic0qy_Yfh>5&+|TJZ zzyG5oDRt8q#P!hoE&9s2IRaPLx&|dle>m_bHO-il)qDNOTLNU~#4Eg_G1#FKAxK_- zdG+HP8aF@l6k!?eP+OW`DswCM*dzO{K1?;bxvCY@yWu3bbTk?eFnwzdrj zRskG5d#+uNt>379zuK>%jIVf6*+^6I6tAonZixWpEc6s#79Ek<#VQzABX9-Ci^VmV zNb10AhqSWo33YUK3ClZbc}11=b+r>%@#rHA`vFs?G|=4K%#DGYBvUu&I{P6S?xpK$ z5A%l}V0PzXE?m5V{60F)oFthXVDM&w1+A?#G)(1c&o$PpTEW=L3JS+oPcRdkW+y)S z=R3Io3Ks)B31eMmwG*KxRf=?Xy*P*XD=-yc~A#u!RVOS#hB$EL^E zP+U^PweD^#+h)dRTa%2P*pPT-?>0PWUnNy_$|d)ENoLDY^u%OkSzV>l&0-Q6npwFR=Cf zAN=rBSH+KC+$M}G6UkFvMUBd88)Wj!M|u-2?0}rY*K3^6_<=)ZC(N zJANwMT-8r$)Tz^_vj&4Olq8*NKd;7VGllWA<;889GHaen#?`3h-upGHb*XBb=7^l4 zV-7)ZsJFLoQEpxwNu-iVTsOkg>({Y-`Er)rw~V{%>v?L!Mh4P8zVA~KkFaC=b{eM5 z;=-j1R9989>*ueMN+d`nQy63D>+8e#{u@}a;wmRzrb*L3qvm;^SG=@RW#cC+@cnGm z+h5u>Qe!77H<~9vTetmWRH??-Hz~KcT+QDD{V0h>?+w;yj zlCV8*-C%$J!->)oT*tvO24h*H42&I9#^FN;?zkt}yJA^*u1hMFWU#*<%XQia102g3 zFs4Vg{k5)+&Q}Zb^XR{RorOyt#Bpp82w{vN8i`%G0) zxN(D9iJR*z*ZrCJZ~yLaU>Sq&`;)?P^WjhQG z43O^c-ESh^w{6!wDZ&4BkQG_N&jeF6Jp4sq0hV27Jg-;)Lqpe5*bX{^>;2ks-49R* e#vsfn2mb-R{GO?pn^->p0000EaNTzDf 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 X(ZA5-k^8`K00000NkvXXu0mjfIek@x literal 0 HcmV?d00001 diff --git a/docs/grasshopper.md b/docs/grasshopper.md index bb77e072..f1b4f625 100644 --- a/docs/grasshopper.md +++ b/docs/grasshopper.md @@ -70,11 +70,11 @@ in the Rhino Viewport even before the task has completed. The following components are available in Grasshopper: -| Component | Description | -| --------- | ----------- | -| `MqttConnect` | Connects to an MQTT broker. | -| `ZenohConnect` | Connects to a Zenoh router. | -| `Message` | Creates a new `compas_eve` message. | -| `Publish` | Publishes a message to a specific topic. | -| `Subscribe` | Subscribes to a specific topic. | -| `BackgroundTask` | Runs a function continuously in the background. | +| Icon | Component | Description | +| :---: | --------- | ----------- | +| ![](_images/mqtt_connect.png) | `MqttConnect` | Connects to an MQTT broker. | +| ![](_images/zenoh_connect.png) | `ZenohConnect` | Connects to a Zenoh router. | +| ![](_images/message.png) | `Message` | Creates a new `compas_eve` message. | +| ![](_images/publish.png) | `Publish` | Publishes a message to a specific topic. | +| ![](_images/subscribe.png) | `Subscribe` | Subscribes to a specific topic. | +| ![](_images/background_task.png) | `BackgroundTask` | Runs a function continuously in the background. | From 6fb64254a9e33bd4380dfd4e986cbd392e734f10 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Mon, 23 Mar 2026 17:26:18 +0100 Subject: [PATCH 05/10] Remove unused paths --- src/compas_eve/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/compas_eve/__init__.py b/src/compas_eve/__init__.py index c758ba59..17f10128 100644 --- a/src/compas_eve/__init__.py +++ b/src/compas_eve/__init__.py @@ -28,8 +28,6 @@ from __future__ import print_function -import os - __author__ = ["Gonzalo Casas"] __copyright__ = "COMPAS Association" @@ -51,13 +49,9 @@ from .codecs import MessageCodec from .memory import InMemoryTransport -HERE = os.path.dirname(__file__) -HOME = os.path.abspath(os.path.join(HERE, "../../")) - set_default_transport(InMemoryTransport()) __all__ = [ - "HOME", "Message", "Publisher", "Subscriber", From 75abfc511febc39e9c5d723be67fe8a68627cb43 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Tue, 24 Mar 2026 20:15:48 +0100 Subject: [PATCH 06/10] Update docstrings to mkdocs + typing hints --- docs/api/compas_eve.codecs.md | 4 +- docs/api/compas_eve.ghpython.md | 4 +- docs/api/compas_eve.md | 4 +- docs/api/compas_eve.memory.md | 4 +- docs/api/compas_eve.mqtt.md | 4 +- docs/api/compas_eve.zenoh.md | 4 +- docs/examples.md | 2 +- docs/grasshopper.md | 2 +- src/compas_eve/__init__.py | 31 ------ src/compas_eve/codecs/__init__.py | 75 ++++++-------- src/compas_eve/core.py | 112 ++++++++++---------- src/compas_eve/ghpython/__init__.py | 45 +++------ src/compas_eve/ghpython/background.py | 129 +++++++++++++----------- src/compas_eve/memory/__init__.py | 63 +++++------- src/compas_eve/mqtt/__init__.py | 19 ---- src/compas_eve/mqtt/mqtt_paho.py | 58 ++++++----- src/compas_eve/zenoh/__init__.py | 19 ---- src/compas_eve/zenoh/zenoh_transport.py | 58 ++++++----- 18 files changed, 272 insertions(+), 365 deletions(-) diff --git a/docs/api/compas_eve.codecs.md b/docs/api/compas_eve.codecs.md index 174f7f8c..b34ca990 100644 --- a/docs/api/compas_eve.codecs.md +++ b/docs/api/compas_eve.codecs.md @@ -1,3 +1 @@ -# compas_eve.codecs - -::: compas_eve.codecs \ No newline at end of file +# ::: compas_eve.codecs \ No newline at end of file diff --git a/docs/api/compas_eve.ghpython.md b/docs/api/compas_eve.ghpython.md index 2af4d529..32c3cf48 100644 --- a/docs/api/compas_eve.ghpython.md +++ b/docs/api/compas_eve.ghpython.md @@ -1,3 +1 @@ -# compas_eve.ghpython - -::: compas_eve.ghpython \ No newline at end of file +# ::: compas_eve.ghpython \ No newline at end of file diff --git a/docs/api/compas_eve.md b/docs/api/compas_eve.md index f8252279..f04e7675 100644 --- a/docs/api/compas_eve.md +++ b/docs/api/compas_eve.md @@ -1,3 +1 @@ -# compas_eve - -::: compas_eve \ No newline at end of file +# ::: compas_eve \ No newline at end of file diff --git a/docs/api/compas_eve.memory.md b/docs/api/compas_eve.memory.md index 5921d796..265208a3 100644 --- a/docs/api/compas_eve.memory.md +++ b/docs/api/compas_eve.memory.md @@ -1,3 +1 @@ -# compas_eve.memory - -::: compas_eve.memory \ No newline at end of file +# ::: compas_eve.memory \ No newline at end of file diff --git a/docs/api/compas_eve.mqtt.md b/docs/api/compas_eve.mqtt.md index 32d8dff0..68832915 100644 --- a/docs/api/compas_eve.mqtt.md +++ b/docs/api/compas_eve.mqtt.md @@ -1,3 +1 @@ -# compas_eve.mqtt - -::: compas_eve.mqtt \ No newline at end of file +# ::: compas_eve.mqtt \ No newline at end of file diff --git a/docs/api/compas_eve.zenoh.md b/docs/api/compas_eve.zenoh.md index a5fb7864..f4525f32 100644 --- a/docs/api/compas_eve.zenoh.md +++ b/docs/api/compas_eve.zenoh.md @@ -1,3 +1 @@ -# compas_eve.zenoh - -::: compas_eve.zenoh \ No newline at end of file +# ::: compas_eve.zenoh \ No newline at end of file diff --git a/docs/examples.md b/docs/examples.md index f53f405a..461f913f 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -32,7 +32,7 @@ in the same script and the same thread. ``` This example is the simplest possible, and it only shows the main concepts needed -to communicate. In particular, ``compas_eve`` uses by default an **in-memory transport** +to communicate. In particular, `compas_eve` uses by default an **in-memory transport** for the messages, this means that messages are can only be received within the same program. ## Hello Threaded World diff --git a/docs/grasshopper.md b/docs/grasshopper.md index f1b4f625..3531aece 100644 --- a/docs/grasshopper.md +++ b/docs/grasshopper.md @@ -16,7 +16,7 @@ background, so that the user can continue working with Grasshopper while the tas In order to use it, add a `Background task` component to your Grasshopper definition, and connect an input with a python function containing the code that needs to run in the background. The only requirement is that this function must accept a `worker` argument, which is an instance of -[BackgroundWorker](compas_eve.ghpython.BackgroundWorker). +[BackgroundWorker][compas_eve.ghpython.BackgroundWorker]. ![PubSub](_images/background-task.png) diff --git a/src/compas_eve/__init__.py b/src/compas_eve/__init__.py index 17f10128..0d3d2657 100644 --- a/src/compas_eve/__init__.py +++ b/src/compas_eve/__init__.py @@ -1,34 +1,3 @@ -""" -******************************************************************************** -compas_eve -******************************************************************************** - -.. currentmodule:: compas_eve - - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - Message - Topic - Publisher - Subscriber - EchoSubscriber - Transport - InMemoryTransport - MessageCodec - get_default_transport - set_default_transport - -""" - -from __future__ import print_function - - __author__ = ["Gonzalo Casas"] __copyright__ = "COMPAS Association" __license__ = "MIT License" diff --git a/src/compas_eve/codecs/__init__.py b/src/compas_eve/codecs/__init__.py index f23effef..99728395 100644 --- a/src/compas_eve/codecs/__init__.py +++ b/src/compas_eve/codecs/__init__.py @@ -1,26 +1,19 @@ -""" -******************************************************************************** -compas_eve.codecs -******************************************************************************** - -.. currentmodule:: compas_eve.codecs +from typing import Any +from typing import Optional +from typing import Union +from compas.data import json_dumps +from compas.data import json_loads -Classes -======= +from compas_eve.core import Message -.. autosummary:: - :toctree: generated/ - :nosignatures: +try: + import compas_pb - MessageCodec - JsonMessageCodec - ProtobufMessageCodec + COMPAS_PB_AVAILABLE = True +except ImportError: + COMPAS_PB_AVAILABLE = False -""" - -from compas.data import json_dumps -from compas.data import json_loads __all__ = ["MessageCodec", "JsonMessageCodec", "ProtobufMessageCodec"] @@ -32,12 +25,12 @@ class MessageCodec(object): to/from a specific representation format (e.g., JSON, Protocol Buffers). """ - def encode(self, message): + def encode(self, message: Union[Message, dict, Any]) -> Union[bytes, str]: """Encode a message to the codec's representation format. Parameters ---------- - message : :class:`Message` or dict or object + message Message to encode. Can be a Message instance, a dict, or an object implementing the COMPAS data framework. @@ -48,17 +41,17 @@ def encode(self, message): """ raise NotImplementedError("Subclasses must implement encode()") - def decode(self, encoded_data): + def decode(self, encoded_data: bytes) -> Union[Message, dict, Any]: """Decode data from the codec's representation format. Parameters ---------- - encoded_data : bytes + encoded_data Encoded data to decode. Returns ------- - :class:`Message` or dict or object + Union[Message, dict, Any] Decoded message after reconstruction from the encoded data. """ raise NotImplementedError("Subclasses must implement decode()") @@ -72,12 +65,12 @@ class JsonMessageCodec(MessageCodec): COMPAS Data objects, and regular dictionaries. """ - def encode(self, message): + def encode(self, message: Union[Message, dict, Any]) -> str: """Encode a message to JSON string. Parameters ---------- - message : :class:`Message` or dict or object + message Message to encode. Can be a Message instance, a dict, or an object implementing the COMPAS data framework. @@ -95,19 +88,19 @@ def encode(self, message): except (KeyError, AttributeError): return json_dumps(dict(message)) - def decode(self, encoded_data, message_type): + def decode(self, encoded_data: bytes, message_type: type) -> Message: """Decode JSON message payloads to message object. Parameters ---------- - encoded_data : bytes + encoded_data Message bytes to decode into a JSON string. - message_type : type + message_type The message type class to use for parsing. Returns ------- - :class:`Message` + Message Decoded message object. """ data = json_loads(encoded_data.decode()) @@ -117,14 +110,6 @@ def decode(self, encoded_data, message_type): return message_type.parse(data) -try: - import compas_pb - - COMPAS_PB_AVAILABLE = True -except ImportError: - COMPAS_PB_AVAILABLE = False - - class ProtobufMessageCodec(MessageCodec): """Protocol Buffers codec for message serialization. @@ -133,9 +118,9 @@ class ProtobufMessageCodec(MessageCodec): Note ---- - This codec requires the ``compas_pb`` package to be installed. - If ``compas_pb`` is not available, attempting to encode or decode - will raise an ImportError. + This codec requires the `compas_pb` package to be installed. + If `compas_pb` is not available, attempting to encode or decode + will raise an [ImportError][]. """ def __init__(self): @@ -143,12 +128,12 @@ def __init__(self): if not COMPAS_PB_AVAILABLE: raise ImportError("The ProtobufMessageCodec requires 'compas_pb' to be installed. Please install it with: pip install compas_pb") - def encode(self, message): + def encode(self, message: Union[Message, dict, Any]) -> bytes: """Encode a message to Protocol Buffers binary format. Parameters ---------- - message : :class:`Message` or dict or object + message Message to encode. Can be a Message instance, a dict, or an object implementing the COMPAS data framework. @@ -161,14 +146,14 @@ def encode(self, message): raise ImportError("The ProtobufMessageCodec requires 'compas_pb' to be installed. Please install it with: pip install compas_pb") return compas_pb.pb_dump_bts(message) - def decode(self, encoded_data, message_type=None): + def decode(self, encoded_data: bytes, message_type: Optional[type] = None) -> object: """Decode Protocol Buffers binary data to message object. Parameters ---------- - encoded_data : bytes + encoded_data Protocol Buffers binary data to decode. - message_type : type, optional + message_type The message type class (not used for protobuf as it's encoded in the data). Returns diff --git a/src/compas_eve/core.py b/src/compas_eve/core.py index ee1241c7..47d6445f 100644 --- a/src/compas_eve/core.py +++ b/src/compas_eve/core.py @@ -1,28 +1,34 @@ -from compas_eve.codecs import JsonMessageCodec +from typing import Any +from typing import Callable +from typing import Dict +from typing import Optional +from typing import Type +from typing import Union + DEFAULT_TRANSPORT = None -def get_default_transport(): +def get_default_transport() -> Optional["Transport"]: """Retrieve the default transport implementation to be used system-wide. Returns ------- - :class:`~compas_eve.Transport` - Instance of a transport class. By default, ``compas_eve`` uses - :class:`~compas_eve.memory.InMemoryTransport`. + Transport + Instance of a transport class. By default, `compas_eve` uses + [InMemoryTransport][compas_eve.memory.InMemoryTransport]. """ return DEFAULT_TRANSPORT -def set_default_transport(transport): +def set_default_transport(transport: Optional["Transport"]) -> None: """Assign a default transport implementation to be used system-wide. Parameters ---------- - transport : :class:`~compas_eve.Transport` - Instance of a transport class. By default, ``compas_eve`` uses - :class:`~compas_eve.memory.InMemoryTransport`. + transport + Instance of a transport class. By default, `compas_eve` uses + [InMemoryTransport][compas_eve.memory.InMemoryTransport]. """ global DEFAULT_TRANSPORT DEFAULT_TRANSPORT = transport @@ -33,37 +39,39 @@ class Transport(object): Parameters ---------- - codec : :class:`MessageCodec`, optional + codec The codec to use for encoding and decoding messages. - If not provided, defaults to :class:`JsonMessageCodec`. + If not provided, defaults to [JsonMessageCodec][compas_eve.codecs.JsonMessageCodec]. """ - def __init__(self, codec=None, *args, **kwargs): + def __init__(self, codec: Optional[Any] = None, *args: Any, **kwargs: Any) -> None: super(Transport, self).__init__(*args, **kwargs) + from compas_eve.codecs import JsonMessageCodec + self._id_counter = 0 if codec is None: codec = JsonMessageCodec() self.codec = codec @property - def id_counter(self): + def id_counter(self) -> int: """Generate an auto-incremental ID starting from 1.""" self._id_counter += 1 return self._id_counter - def publish(self, topic, message): + def publish(self, topic: "Topic", message: Union["Message", dict]) -> None: pass - def subscribe(self, topic, callback): + def subscribe(self, topic: "Topic", callback: Callable) -> Optional[str]: pass - def unsubscribe(self, topic): + def unsubscribe(self, topic: "Topic") -> None: pass - def advertise(self, topic): + def advertise(self, topic: "Topic") -> Optional[str]: pass - def unadvertise(self, topic): + def unadvertise(self, topic: "Topic") -> None: pass @@ -72,37 +80,37 @@ class Message(object): A message is fundamentally a dictionary and behaves as one.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args: Any, **kwargs: Any) -> None: super(Message, self).__init__() self.data = {} self.data.update(*args, **kwargs) - def ToString(self): + def ToString(self) -> str: return str(self) - def __str__(self): + def __str__(self) -> str: return str(self.data) - def __getattr__(self, name): + def __getattr__(self, name: str) -> Any: return self.data[name] - def __setattr__(self, key, value): + def __setattr__(self, key: str, value: Any) -> None: if key == "data" or key in self.__dict__: super(Message, self).__setattr__(key, value) else: self.data[key] = value - def __getitem__(self, key): + def __getitem__(self, key: str) -> Any: return self.data[key] - def __setitem__(self, key, value): + def __setitem__(self, key: str, value: Any) -> None: self.data[key] = value - def __jsondump__(self, minimal=False): + def __jsondump__(self, minimal: bool = False) -> Dict[str, Any]: return self.data @classmethod - def parse(cls, value): + def parse(cls, value: Dict[str, Any]) -> "Message": instance = cls(**value) return instance @@ -115,19 +123,19 @@ class Topic(object): Attributes ---------- - name : str + name Name of the topic. - message_type : type - Class defining the message structure. Use :class:`Message` for + message_type + Class defining the message structure. Use [Message][] for a generic, non-typed checked message implementation. - Defaults to :class:`Message`. - options : dict + Defaults to [Message][]. + options A dictionary of options. """ # TODO: Add documentation/examples of possible options - def __init__(self, name, message_type=None, **options): + def __init__(self, name: str, message_type: Optional[Type[Message]] = None, **options: Any) -> None: self.name = name self.message_type = message_type or Message self.options = options @@ -138,20 +146,20 @@ class Publisher(object): Parameters ---------- - topic : :class:`Topic` or str + topic The topic to publish messages to. If a string is provided, a new topic instance will be created using the string as topic name. - transport : :class:`Transport`, optional + transport The transport to use for publishing. If not provided, the default transport will be used. """ - def __init__(self, topic, transport=None): + def __init__(self, topic: Union[Topic, str], transport: Optional[Transport] = None) -> None: self.topic = topic if isinstance(topic, Topic) else Topic(topic) self.transport = transport or get_default_transport() self._advertise_id = None @property - def is_advertised(self): + def is_advertised(self) -> bool: """Indicate if the publisher has announced its topic as advertised or not. Returns @@ -161,16 +169,16 @@ def is_advertised(self): """ return self._advertise_id is not None - def message_published(self, message): + def message_published(self, message: Union[Message, dict]) -> None: """Handler called when a message has been published.""" pass - def publish(self, message): + def publish(self, message: Union[Message, dict]) -> None: """Publish a message to the topic. Parameters ---------- - message : :class:`Message` or dict + message The message to publish. """ # TODO: check if message type matches self.topic.message_type declared @@ -180,14 +188,14 @@ def publish(self, message): self.transport.publish(self.topic, message) self.message_published(message) - def advertise(self): + def advertise(self) -> None: """Advertise the publisher for the topic.""" if self.is_advertised: return self._advertise_id = self.transport.advertise(self.topic) - def unadvertise(self): + def unadvertise(self) -> None: """Unadvertise the publisher for the topic.""" if not self.is_advertised: return @@ -201,20 +209,20 @@ class Subscriber(object): Parameters ---------- - topic : :class:`Topic` or str + topic The topic to subscribe to. If a string is provided, a new topic instance will be created using the string as topic name. - transport : :class:`Transport`, optional + transport The transport to use for subscribing. If not provided, the default transport will be used. """ - def __init__(self, topic, callback=None, transport=None): + def __init__(self, topic: Union[Topic, str], callback: Optional[Callable] = None, transport: Optional[Transport] = None) -> None: self.transport = transport or get_default_transport() self.topic = topic if isinstance(topic, Topic) else Topic(topic) self._subscribe_id = None self._callback = callback - def message_received(self, message): + def message_received(self, message: Union[Message, dict]) -> None: """Handler called whenever a new message is received. By default, this implementation will simply invoke the callback @@ -223,17 +231,17 @@ def message_received(self, message): self._callback(message) @property - def is_subscribed(self): + def is_subscribed(self) -> bool: """Indicate if the instace is currently subscribed to its topic or not.""" return self._subscribe_id is not None - def subscribe(self): + def subscribe(self) -> None: if self._subscribe_id: return self._subscribe_id = self.transport.subscribe(self.topic, self.message_received) - def unsubscribe(self): + def unsubscribe(self) -> None: """Unregister the subscriber from its topic.""" if not self._subscribe_id: return @@ -247,14 +255,14 @@ class EchoSubscriber(Subscriber): Parameters ---------- - topic : :class:`Topic` or str + topic The topic to subscribe to. If a string is provided, a new topic instance will be created using the string as topic name. """ - def __init__(self, topic, transport=None): + def __init__(self, topic: Union[Topic, str], transport: Optional[Transport] = None) -> None: super(EchoSubscriber, self).__init__(topic, callback=self.echo, transport=transport) - def echo(self, message): + def echo(self, message: Union[Message, dict]) -> None: """Print received messages to the console.""" print(str(message)) diff --git a/src/compas_eve/ghpython/__init__.py b/src/compas_eve/ghpython/__init__.py index ad054b8b..c78e8382 100644 --- a/src/compas_eve/ghpython/__init__.py +++ b/src/compas_eve/ghpython/__init__.py @@ -1,77 +1,58 @@ -""" -******************************************************************************** -compas_eve.ghpython -******************************************************************************** - -.. currentmodule:: compas_eve.ghpython - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - BackgroundWorker - -""" - try: import Grasshopper # type: ignore except (ImportError, SyntaxError): pass - from .background import BackgroundWorker -def warning(component, message): +def warning(component: "Grasshopper.Kernel.IGH_Component", message: str): """Add a warning message to the component. Parameters ---------- - component : Grasshopper.Kernel.IGH_Component - The component instance. Pre-Rhino8 use `self`. Post-Rhino8 use `ghenv.Component`. - message : str + component + The component instance. Use `ghenv.Component`. + message The message to display. """ component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Warning, message) -def error(component, message): +def error(component: "Grasshopper.Kernel.IGH_Component", message: str): """Add an error message to the component. Parameters ---------- - component : Grasshopper.Kernel.IGH_Component + component The component instance. Pre-Rhino8 use `self`. Post-Rhino8 use `ghenv.Component`. - message : str + message The message to display. """ component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Error, message) -def remark(component, message): +def remark(component: "Grasshopper.Kernel.IGH_Component", message: str): """Add a remark message to the component. Parameters ---------- - component : Grasshopper.Kernel.IGH_Component + component The component instance. Pre-Rhino8 use `self`. Post-Rhino8 use `ghenv.Component`. - message : str + message The message to display. """ component.AddRuntimeMessage(Grasshopper.Kernel.GH_RuntimeMessageLevel.Remark, message) -def message(component, message): +def message(component: "Grasshopper.Kernel.IGH_Component", message: str): """Add a text that will appear under the component. Parameters ---------- - component : Grasshopper.Kernel.IGH_Component + component The component instance. Pre-Rhino8 use `self`. Post-Rhino8 use `ghenv.Component`. - message : str + message The message to display. """ component.Message = message diff --git a/src/compas_eve/ghpython/background.py b/src/compas_eve/ghpython/background.py index e72fc8aa..26f8d847 100644 --- a/src/compas_eve/ghpython/background.py +++ b/src/compas_eve/ghpython/background.py @@ -1,19 +1,16 @@ -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - import threading +from typing import Any +from typing import Callable +from typing import Optional -import Rhino -import scriptcontext -import System -from compas_ghpython import create_id - -# COMPAS 1.x compatibility try: + import Rhino + import scriptcontext + import System + from compas_ghpython import create_id from compas_ghpython.timer import update_component except ImportError: - from compas_ghpython import update_component + pass class BackgroundWorker(object): @@ -29,43 +26,49 @@ class BackgroundWorker(object): The following is an example of a long-running function that updates the progress while it runs. - .. code-block:: python - - import time - + ```python + import time - def do_something_long_and_complicated(worker): - # Result can be of any data type - result = 0 - for i in range(50): - worker.current_value = i - result += i - worker.display_progress(i / (50 - 1)) - time.sleep(0.01) + def do_something_long_and_complicated(worker): + # Result can be of any data type + result = 0 - worker.display_message("Done!") + for i in range(50): + worker.current_value = i + result += i + worker.display_progress(i / (50 - 1)) + time.sleep(0.01) - return result + worker.display_message("Done!") + return result + ``` Parameters ---------- - ghenv : ``GhPython.Component.PythonEnvironment`` + ghenv Grasshopper environment object - long_running_function : function, optional + long_running_function This function will be the main entry point for the long-running task. - dispose_function : function, optional + dispose_function If defined, this function will be called when the worker is disposed. It can be used for clean-up tasks and resource deallocation. - auto_set_done : bool, optional - If true, the worker state will be automatically set to ``Done`` after the function returns. - Defaults to ``True``. - args : tuple, optional - List or tuple of arguments for the invocation of the ``long_running_function``. Defaults to ``()``. + auto_set_done + If true, the worker state will be automatically set to `Done` after the function returns. + Defaults to `True`. + args + List or tuple of arguments for the invocation of the `long_running_function`. Defaults to `()`. """ - def __init__(self, ghenv, long_running_function=None, dispose_function=None, auto_set_done=True, args=()): + def __init__( + self, + ghenv: "GhPython.Component.PythonEnvironment", + long_running_function: Optional[Callable] = None, + dispose_function: Optional[Callable] = None, + auto_set_done: bool = True, + args=(), + ): super(BackgroundWorker, self).__init__() self.ghenv = ghenv self._is_working = False @@ -130,19 +133,19 @@ def dispose(self): self.dispose_function(self) def set_internal_state_to_working(self): - """Set the internal state to ``working``.""" + """Set the internal state to `working`.""" self._is_working = True self._is_done = False self._is_cancelled = False - def set_internal_state_to_done(self, result): - """Set the internal state to ``done``, which indicates the worker has completed.""" + def set_internal_state_to_done(self, result: Any): + """Set the internal state to `done`, which indicates the worker has completed.""" self._is_working = False self._is_done = True self._is_cancelled = False self.update_result(result, delay=1) - def update_result(self, result, delay=1): + def update_result(self, result: Any, delay: int = 1): """Update the result of the worker. This will update the result of the worker, and trigger a solution expiration @@ -150,36 +153,36 @@ def update_result(self, result, delay=1): Parameters ---------- - result : object + result Result of the worker. - delay : int, optional + delay Delay (in milliseconds) before updating the component, by default 1. """ self.result = result update_component(self.ghenv, delay) def set_internal_state_to_cancelled(self): - """Set the internal state to ``cancelled``.""" + """Set the internal state to `cancelled`.""" self._is_working = False self._is_done = False self._is_cancelled = True - def display_progress(self, progress): + def display_progress(self, progress: float): """Display a progress indicator in the component. Parameters ---------- - progress : float - Float between ``0..1`` indicating progress of completion. + progress + Float between `0..1` indicating progress of completion. """ self.display_message("Progress {:.1f}%".format(progress * 100)) - def display_message(self, message): + def display_message(self, message: str): """Display a message in the component without triggering a solution expiration. Parameters ---------- - message : str + message Message to display. """ @@ -191,7 +194,15 @@ def ui_callback(): Rhino.RhinoApp.InvokeOnUiThread(System.Action(ui_callback)) @classmethod - def instance_by_component(cls, ghenv, long_running_function=None, dispose_function=None, auto_set_done=True, force_new=False, args=()): + def instance_by_component( + cls, + ghenv: "GhPython.Component.PythonEnvironment", + long_running_function: Optional[Callable] = None, + dispose_function: Optional[Callable] = None, + auto_set_done: bool = True, + force_new: bool = False, + args=(), + ): """Get the worker instance assigned to the component. This will get a persistant instance of a background worker @@ -200,24 +211,24 @@ def instance_by_component(cls, ghenv, long_running_function=None, dispose_functi Parameters ---------- - ghenv : ``GhPython.Component.PythonEnvironment`` + ghenv Grasshopper environment object - long_running_function : function, optional + long_running_function This function will be the main entry point for the long-running task. - dispose_function : function, optional + dispose_function If defined, this function will be called when the worker is disposed. It can be used for clean-up tasks and resource deallocation. - auto_set_done : bool, optional - If true, the worker state will be automatically set to ``Done`` after the function returns. - Defaults to ``True``. - force_new : bool, optional - Force the creation of a new background worker, by default False. + auto_set_done + If true, the worker state will be automatically set to `Done` after the function returns. + Defaults to `True`. + force_new + Force the creation of a new background worker, by default `False`. args : tuple, optional - List or tuple of arguments for the invocation of the ``long_running_function``. Defaults to ``()``. + List or tuple of arguments for the invocation of the `long_running_function`. Defaults to `()`. Returns ------- - :class:`BackgroundWorker` + BackgroundWorker Instance of the background worker of the current component. """ @@ -243,14 +254,14 @@ def instance_by_component(cls, ghenv, long_running_function=None, dispose_functi return worker @classmethod - def stop_instance_by_component(cls, ghenv): + def stop_instance_by_component(cls, ghenv: "GhPython.Component.PythonEnvironment"): """Stops the worker instance assigned to the component. If there is no worker running, it will do nothing. Parameters ---------- - ghenv : ``GhPython.Component.PythonEnvironment`` + ghenv Grasshopper environment object """ diff --git a/src/compas_eve/memory/__init__.py b/src/compas_eve/memory/__init__.py index 4a40f509..58b070c4 100644 --- a/src/compas_eve/memory/__init__.py +++ b/src/compas_eve/memory/__init__.py @@ -1,24 +1,10 @@ -""" -******************************************************************************** -compas_eve.memory -******************************************************************************** - -.. currentmodule:: compas_eve.memory - - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - InMemoryTransport - -""" - +from typing import Callable +from typing import Optional +from compas_eve.codecs import MessageCodec from compas_eve.event_emitter import EventEmitterMixin from compas_eve.core import Transport +from compas_eve.core import Topic +from compas_eve.core import Message __all__ = ["InMemoryTransport"] @@ -30,27 +16,27 @@ class InMemoryTransport(Transport, EventEmitterMixin): Parameters ---------- - codec : :class:`MessageCodec`, optional + codec The codec to use for encoding and decoding messages. - If not provided, defaults to :class:`JsonMessageCodec`. + If not provided, defaults to [JsonMessageCodec][compas_eve.codecs.JsonMessageCodec]. """ - def __init__(self, codec=None, *args, **kwargs): + def __init__(self, codec: Optional[MessageCodec] = None, *args, **kwargs): super(InMemoryTransport, self).__init__(codec=codec, *args, **kwargs) self._local_callbacks = {} - def on_ready(self, callback): + def on_ready(self, callback: Callable): """In-memory transport is always ready, it will immediately trigger the callback.""" callback() - def publish(self, topic, message): + def publish(self, topic: Topic, message: Message): """Publish a message to a topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to publish to. - message : :class:`Message` + message Instance of the message to publish. """ event_key = "event:{}".format(topic.name) @@ -62,18 +48,18 @@ def _callback(**kwargs): self.on_ready(_callback) - def subscribe(self, topic, callback): + def subscribe(self, topic: Topic, callback: Callable) -> str: """Subscribe to a topic. Every time a new message is received on the topic, the callback will be invoked. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to subscribe to. - callback : function + callback Callback to invoke whenever a new message arrives. The callback should - receive only one `msg` argument, e.g. ``lambda msg: print(msg)``. + receive only one `msg` argument, e.g. `lambda msg: print(msg)`. Returns ------- @@ -96,7 +82,7 @@ def _callback(**kwargs): return subscribe_id - def unsubscribe_by_id(self, subscribe_id): + def unsubscribe_by_id(self, subscribe_id: str): """Unsubscribe from the specified topic based on the subscription id.""" ev_type, topic_name, _callback_id = subscribe_id.split(":") event_key = "{}:{}".format(ev_type, topic_name) @@ -105,12 +91,19 @@ def unsubscribe_by_id(self, subscribe_id): self.off(event_key, callback) del self._local_callbacks[subscribe_id] - def unsubscribe(self, topic): - """Unsubscribe from the specified topic.""" + def unsubscribe(self, topic: Topic): + """Unsubscribe from the specified topic. + + + Parameters + ---------- + topic + Instance of the topic to unsubscribe from. + """ event_key = "event:{}".format(topic.name) self.remove_all_listeners(event_key) - def advertise(self, topic): + def advertise(self, topic: Topic): """Announce this code will publish messages to the specified topic. This call has no effect on the in-memory transport.""" @@ -118,7 +111,7 @@ def advertise(self, topic): # in-memory does not need anything here return advertise_id - def unadvertise(self, topic): + def unadvertise(self, topic: Topic): """Announce that this code will stop publishing messages to the specified topic. This call has no effect on the in-memory transport.""" diff --git a/src/compas_eve/mqtt/__init__.py b/src/compas_eve/mqtt/__init__.py index 6a40e2b1..cc426ec9 100644 --- a/src/compas_eve/mqtt/__init__.py +++ b/src/compas_eve/mqtt/__init__.py @@ -1,22 +1,3 @@ -""" -******************************************************************************** -compas_eve.mqtt -******************************************************************************** - -.. currentmodule:: compas_eve.mqtt - - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - MqttTransport - -""" - from .mqtt_paho import MqttTransport __all__ = ["MqttTransport"] diff --git a/src/compas_eve/mqtt/mqtt_paho.py b/src/compas_eve/mqtt/mqtt_paho.py index a154e723..7a112efe 100644 --- a/src/compas_eve/mqtt/mqtt_paho.py +++ b/src/compas_eve/mqtt/mqtt_paho.py @@ -1,7 +1,11 @@ import uuid +from typing import Callable +from typing import Optional import paho.mqtt.client as mqtt +from ..core import Message +from ..core import Topic from ..core import Transport from ..event_emitter import EventEmitterMixin @@ -18,19 +22,19 @@ class MqttTransport(Transport, EventEmitterMixin): Parameters ---------- - host : str - Host name for the MQTT broker, e.g. ``broker.hivemq.com`` or ``localhost`` if + host + Host name for the MQTT broker, e.g. `broker.hivemq.com` or `localhost` if you are running a local broker on your machine. - port : int - MQTT broker port, defaults to ``1883``. - client_id : str, optional + port + MQTT broker port, defaults to `1883`. + client_id Client ID for the MQTT connection. If not provided, a unique ID will be generated. - codec : :class:`MessageCodec`, optional + codec The codec to use for encoding and decoding messages. - If not provided, defaults to :class:`JsonMessageCodec`. + If not provided, defaults to [JsonMessageCodec][compas_eve.codecs.JsonMessageCodec]. """ - def __init__(self, host, port=1883, client_id=None, codec=None, *args, **kwargs): + def __init__(self, host: str, port: int = 1883, client_id: Optional[str] = None, codec: Optional[MessageCodec] = None, *args, **kwargs): super(MqttTransport, self).__init__(codec=codec, *args, **kwargs) self.host = host self.port = port @@ -47,20 +51,20 @@ def __init__(self, host, port=1883, client_id=None, codec=None, *args, **kwargs) self.client.connect(self.host, self.port) self.client.loop_start() - def close(self): + def close(self) -> None: """Close the connection to the MQTT broker.""" self.client.loop_stop() - def _on_connect(self, client, userdata, flags, rc): + def _on_connect(self, client, userdata, flags, rc) -> None: self._is_connected = True self.emit("ready") - def on_ready(self, callback): + def on_ready(self, callback: Callable): """Allows to hook-up to the event triggered when the connection to MQTT broker is ready. Parameters ---------- - callback : function + callback Function to invoke when the connection is established. """ if self._is_connected: @@ -68,14 +72,14 @@ def on_ready(self, callback): else: self.once("ready", callback) - def publish(self, topic, message): + def publish(self, topic: Topic, message: Message): """Publish a message to a topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to publish to. - message : :class:`Message` + message Instance of the message to publish. """ @@ -85,18 +89,18 @@ def _callback(**kwargs): self.on_ready(_callback) - def subscribe(self, topic, callback): + def subscribe(self, topic: Topic, callback: Callable) -> str: """Subscribe to a topic. Every time a new message is received on the topic, the callback will be invoked. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to subscribe to. - callback : function + callback Callback to invoke whenever a new message arrives. The callback should - receive only one `msg` argument, e.g. ``lambda msg: print(msg)``. + receive only one `msg` argument, e.g. `lambda msg: print(msg)`. Returns ------- @@ -129,14 +133,14 @@ def _on_message(self, client, userdata, msg): event_key = "event:{}".format(msg.topic) self.emit(event_key, msg) - def advertise(self, topic): + def advertise(self, topic: Topic) -> str: """Announce this code will publish messages to the specified topic. This call has no effect on this transport implementation. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to advertise. Returns @@ -149,24 +153,24 @@ def advertise(self, topic): # mqtt does not need anything here return advertise_id - def unadvertise(self, topic): + def unadvertise(self, topic: Topic): """Announce that this code will stop publishing messages to the specified topic. This call has no effect on this transport implementation. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to stop publishing messages to. """ pass - def unsubscribe_by_id(self, subscribe_id): + def unsubscribe_by_id(self, subscribe_id: str): """Unsubscribe from the specified topic based on the subscription id. Parameters ---------- - subscribe_id : str + subscribe_id Identifier of the subscription. """ ev_type, topic_name, _callback_id = subscribe_id.split(":") @@ -178,12 +182,12 @@ def unsubscribe_by_id(self, subscribe_id): del self._local_callbacks[subscribe_id] - def unsubscribe(self, topic): + def unsubscribe(self, topic: Topic): """Unsubscribe from the specified topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to unsubscribe from. """ self.client.unsubscribe(topic.name) diff --git a/src/compas_eve/zenoh/__init__.py b/src/compas_eve/zenoh/__init__.py index 82f75b8a..1f317c70 100644 --- a/src/compas_eve/zenoh/__init__.py +++ b/src/compas_eve/zenoh/__init__.py @@ -1,22 +1,3 @@ -""" -******************************************************************************** -compas_eve.zenoh -******************************************************************************** - -.. currentmodule:: compas_eve.zenoh - - -Classes -======= - -.. autosummary:: - :toctree: generated/ - :nosignatures: - - ZenohTransport - -""" - from .zenoh_transport import ZenohTransport __all__ = ["ZenohTransport"] diff --git a/src/compas_eve/zenoh/zenoh_transport.py b/src/compas_eve/zenoh/zenoh_transport.py index 8307af41..288856e8 100644 --- a/src/compas_eve/zenoh/zenoh_transport.py +++ b/src/compas_eve/zenoh/zenoh_transport.py @@ -1,7 +1,13 @@ import threading +from typing import Any +from typing import Callable +from typing import Optional import zenoh +from ..codecs import MessageCodec +from ..core import Message +from ..core import Topic from ..core import Transport from ..event_emitter import EventEmitterMixin @@ -11,14 +17,14 @@ class ZenohTransport(Transport, EventEmitterMixin): Parameters ---------- - config : :class:`zenoh.Config`, optional + config The Zenoh configuration to use. If not provided, a default `zenoh.Config()` will be used. - codec : :class:`MessageCodec`, optional + codec The codec to use for encoding and decoding messages. - If not provided, defaults to :class:`JsonMessageCodec`. + If not provided, defaults to [JsonMessageCodec][compas_eve.codecs.JsonMessageCodec]. """ - def __init__(self, config=None, codec=None, *args, **kwargs): + def __init__(self, config: Optional[zenoh.Config] = None, codec: Optional[MessageCodec] = None, *args: Any, **kwargs: Any) -> None: super(ZenohTransport, self).__init__(codec=codec, *args, **kwargs) if config is None: self.config = zenoh.Config() @@ -33,24 +39,24 @@ def __init__(self, config=None, codec=None, *args, **kwargs): self.session = zenoh.open(self.config) self._is_connected = True - def emit_ready(): + def emit_ready() -> None: self.emit("ready") threading.Thread(target=emit_ready).start() - def close(self): + def close(self) -> None: """Close the Zenoh session.""" self.session.close() - def _get_topic_name(self, topic): + def _get_topic_name(self, topic: Topic) -> str: return topic.name.strip("/") - def on_ready(self, callback): + def on_ready(self, callback: Callable) -> None: """Allows to hook-up to the event triggered when the connection is established. Parameters ---------- - callback : function + callback Function to invoke when the connection is established. """ if self._is_connected: @@ -58,18 +64,18 @@ def on_ready(self, callback): else: self.once("ready", callback) - def publish(self, topic, message): + def publish(self, topic: Topic, message: Message) -> None: """Publish a message to a topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to publish to. - message : :class:`Message` + message Instance of the message to publish. """ - def _callback(**kwargs): + def _callback(**kwargs: Any) -> None: if self._get_topic_name(topic) not in self._publishers: self._publishers[self._get_topic_name(topic)] = self.session.declare_publisher(self._get_topic_name(topic)) @@ -78,33 +84,33 @@ def _callback(**kwargs): self.on_ready(_callback) - def subscribe(self, topic, callback): + def subscribe(self, topic: Topic, callback: Callable) -> str: """Subscribe to a topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to subscribe to. - callback : function + callback Callback to invoke whenever a new message arrives. Returns ------- str - Returns an identifier of the subscription. + Identifier of the subscription. """ event_key = "event:{}".format(self._get_topic_name(topic)) subscribe_id = "{}:{}".format(event_key, id(callback)) - def _local_callback(msg): + def _local_callback(msg: Any) -> None: callback(msg) - def _zenoh_handler(sample): + def _zenoh_handler(sample: Any) -> None: payload = sample.payload.to_bytes() if hasattr(sample.payload, "to_bytes") else bytes(sample.payload) message_obj = self.codec.decode(payload, topic.message_type) self.emit(event_key, message_obj) - def _subscribe_callback(**kwargs): + def _subscribe_callback(**kwargs: Any) -> None: if self._get_topic_name(topic) not in self._subscribers: self._subscribers[self._get_topic_name(topic)] = self.session.declare_subscriber(self._get_topic_name(topic), _zenoh_handler) @@ -116,12 +122,12 @@ def _subscribe_callback(**kwargs): return subscribe_id - def unsubscribe(self, topic): + def unsubscribe(self, topic: Topic) -> None: """Unsubscribe from a topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to unsubscribe from. """ event_key = "event:{}".format(self._get_topic_name(topic)) @@ -135,12 +141,12 @@ def unsubscribe(self, topic): self.remove_listener(event_key, self._local_callbacks[k]) del self._local_callbacks[k] - def advertise(self, topic): + def advertise(self, topic: Topic) -> str: """Announce this code will publish messages to the specified topic. Parameters ---------- - topic : :class:`Topic` + topic Instance of the topic to advertise. Returns @@ -151,12 +157,12 @@ def advertise(self, topic): advertise_id = "advertise:{}:{}".format(self._get_topic_name(topic), self.id_counter) return advertise_id - def unsubscribe_by_id(self, subscribe_id): + def unsubscribe_by_id(self, subscribe_id: str) -> None: """Unsubscribe from the specified topic based on the subscription id. Parameters ---------- - subscribe_id : str + subscribe_id The subscription identifier. """ # subscribe_id format: "event:topic_name:id(callback)" From 8ee94a1eafad0a13aa56a103f5d691e8b125c622 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Thu, 16 Apr 2026 11:26:39 +0200 Subject: [PATCH 07/10] Remove redundant deps from compas_invocations2[mkdocs] --- requirements-dev.txt | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index fad6e5c8..ba466395 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -2,7 +2,7 @@ attrs >=17.4 black >=22.12.0 build bump-my-version -compas_invocations2 +compas_invocations2[mkdocs] compas_pb >= 0.4.4 invoke >=0.14 pytest-mock @@ -11,19 +11,3 @@ sphinx_compas2_theme twine wheel pythonnet -markdown-callouts >=0.4 -markdown-exec >=1.8 -mike -mkdocs >=1.6 -mkdocs-autorefs >=1.4 -mkdocs-coverage >=1.0 -mkdocs-git-revision-date-localized-plugin >=1.2 -mkdocs-llmstxt >=0.2 -mkdocs-material >=9.5 -mkdocs-minify-plugin >=0.8 -mkdocs-redirects >=1.2 -mkdocs-section-index >=0.3 -mkdocs-mermaid2-plugin -mkdocstrings[python] -pydantic >=2.10 -tomli >=2.0; python_version < '3.11' From fdb8b96082466ad12097a278042f73adbc8b4941 Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Thu, 16 Apr 2026 11:26:52 +0200 Subject: [PATCH 08/10] Update docs workflow to trigger mkdocs --- .github/workflows/docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index d7f3b1c4..9068cef8 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -14,7 +14,9 @@ jobs: docs: runs-on: ubuntu-latest steps: - - uses: compas-dev/compas-actions.docs@v4 + - uses: compas-dev/compas-actions.docs@v5 with: github_token: ${{ secrets.GITHUB_TOKEN }} use_conda: false + generator: mkdocs + extras: dev,mkdocs From c831556822e3e08633cc2d915a848ad797a652cf Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Thu, 16 Apr 2026 11:32:38 +0200 Subject: [PATCH 09/10] Missing import (only for type hint) --- src/compas_eve/mqtt/mqtt_paho.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas_eve/mqtt/mqtt_paho.py b/src/compas_eve/mqtt/mqtt_paho.py index 7a112efe..1aac3292 100644 --- a/src/compas_eve/mqtt/mqtt_paho.py +++ b/src/compas_eve/mqtt/mqtt_paho.py @@ -4,6 +4,7 @@ import paho.mqtt.client as mqtt +from ..codecs import MessageCodec from ..core import Message from ..core import Topic from ..core import Transport From a2601ba599aefb56b43384a201d0bd3a284acd6d Mon Sep 17 00:00:00 2001 From: Gonzalo Casas Date: Thu, 16 Apr 2026 11:37:15 +0200 Subject: [PATCH 10/10] lint --- src/compas_eve/ghpython/background.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/compas_eve/ghpython/background.py b/src/compas_eve/ghpython/background.py index 26f8d847..b2d64f81 100644 --- a/src/compas_eve/ghpython/background.py +++ b/src/compas_eve/ghpython/background.py @@ -4,6 +4,7 @@ from typing import Optional try: + import GhPython import Rhino import scriptcontext import System