runtimepy 5.4.1__tar.gz → 5.4.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. {runtimepy-5.4.1/runtimepy.egg-info → runtimepy-5.4.3}/PKG-INFO +6 -6
  2. {runtimepy-5.4.1 → runtimepy-5.4.3}/README.md +3 -3
  3. {runtimepy-5.4.1 → runtimepy-5.4.3}/pyproject.toml +1 -1
  4. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/__init__.py +2 -2
  5. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/tftp.py +10 -6
  6. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/css/bootstrap_extra.css +4 -0
  7. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/tftp_server.yaml +1 -1
  8. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/entry.py +5 -3
  9. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/tab/html.py +4 -2
  10. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/connection.py +8 -5
  11. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/tftp/__init__.py +30 -26
  12. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/tftp/base.py +64 -7
  13. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/tftp/endpoint.py +9 -2
  14. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/util.py +18 -0
  15. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/serializable/base.py +5 -1
  16. {runtimepy-5.4.1 → runtimepy-5.4.3/runtimepy.egg-info}/PKG-INFO +6 -6
  17. {runtimepy-5.4.1 → runtimepy-5.4.3}/LICENSE +0 -0
  18. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/__main__.py +0 -0
  19. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/app.py +0 -0
  20. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/__init__.py +0 -0
  21. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/__init__.py +0 -0
  22. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/array.py +0 -0
  23. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/base.py +0 -0
  24. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/command/__init__.py +0 -0
  25. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/command/parser.py +0 -0
  26. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/command/processor.py +0 -0
  27. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/command/result.py +0 -0
  28. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/create.py +0 -0
  29. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/file.py +0 -0
  30. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/sample.py +0 -0
  31. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/environment/telemetry.py +0 -0
  32. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/event/__init__.py +0 -0
  33. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/event/header.py +0 -0
  34. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/channel/registry.py +0 -0
  35. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/codec/__init__.py +0 -0
  36. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/codec/protocol/__init__.py +0 -0
  37. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/codec/protocol/base.py +0 -0
  38. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/codec/protocol/json.py +0 -0
  39. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/codec/system/__init__.py +0 -0
  40. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/__init__.py +0 -0
  41. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/all.py +0 -0
  42. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/arbiter.py +0 -0
  43. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/common.py +0 -0
  44. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/server.py +0 -0
  45. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/task.py +0 -0
  46. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/commands/tui.py +0 -0
  47. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/control/__init__.py +0 -0
  48. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/control/env/__init__.py +0 -0
  49. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/control/source.py +0 -0
  50. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/control/step.py +0 -0
  51. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/browser.yaml +0 -0
  52. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/css/main.css +0 -0
  53. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/dummy_load.yaml +0 -0
  54. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/factories.yaml +0 -0
  55. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/favicon.ico +0 -0
  56. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/DataConnection.js +0 -0
  57. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/JsonConnection.js +0 -0
  58. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/audio.js +0 -0
  59. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/App.js +0 -0
  60. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/ChannelTable.js +0 -0
  61. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/DataConnection.js +0 -0
  62. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/JsonConnection.js +0 -0
  63. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/OverlayManager.js +0 -0
  64. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/Plot.js +0 -0
  65. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/PlotDrawer.js +0 -0
  66. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/PlotManager.js +0 -0
  67. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/PlotModalManager.js +0 -0
  68. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/PointBuffer.js +0 -0
  69. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/PointManager.js +0 -0
  70. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/TabFilter.js +0 -0
  71. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/TabInterface.js +0 -0
  72. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/UnitSystem.js +0 -0
  73. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/WindowHashManager.js +0 -0
  74. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/classes/WorkerInterface.js +0 -0
  75. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/events.js +0 -0
  76. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/init.js +0 -0
  77. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/main.js +0 -0
  78. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/tab/env.js +0 -0
  79. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/tab/sound.js +0 -0
  80. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/third-party/webgl-debug.js +0 -0
  81. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/unused/pyodide.js +0 -0
  82. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/util.js +0 -0
  83. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/js/worker.js +0 -0
  84. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/BitFields.yaml +0 -0
  85. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/Channel.yaml +0 -0
  86. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/ChannelCommand.yaml +0 -0
  87. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/ChannelRegistry.yaml +0 -0
  88. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/ClientConnectionConfig.yaml +0 -0
  89. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/ConnectionArbiterConfig.yaml +0 -0
  90. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/EnumRegistry.yaml +0 -0
  91. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/FindFile.yaml +0 -0
  92. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/PeerProcessConfig.yaml +0 -0
  93. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/RuntimeEnum.yaml +0 -0
  94. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/ServerConnectionConfig.yaml +0 -0
  95. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/StructConfig.yaml +0 -0
  96. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/TaskConfig.yaml +0 -0
  97. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/channel_controls.yaml +0 -0
  98. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/has_config.yaml +0 -0
  99. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/has_factory.yaml +0 -0
  100. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/has_name.yaml +0 -0
  101. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/schemas/has_request_flag.yaml +0 -0
  102. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/server.yaml +0 -0
  103. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/server_base.yaml +0 -0
  104. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/data/server_dev.yaml +0 -0
  105. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/dev_requirements.txt +0 -0
  106. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/enum/__init__.py +0 -0
  107. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/enum/registry.py +0 -0
  108. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/enum/types.py +0 -0
  109. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mapping.py +0 -0
  110. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/message/__init__.py +0 -0
  111. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/message/handlers.py +0 -0
  112. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/message/interface.py +0 -0
  113. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/message/types.py +0 -0
  114. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/metrics/__init__.py +0 -0
  115. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/metrics/channel.py +0 -0
  116. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/metrics/connection.py +0 -0
  117. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/metrics/task.py +0 -0
  118. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/__init__.py +0 -0
  119. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/async_command.py +0 -0
  120. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/enum.py +0 -0
  121. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/environment.py +0 -0
  122. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/finalize.py +0 -0
  123. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/logging.py +0 -0
  124. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/psutil.py +0 -0
  125. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/regex.py +0 -0
  126. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/mixins/trig.py +0 -0
  127. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/__init__.py +0 -0
  128. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/apps/__init__.py +0 -0
  129. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/__init__.py +0 -0
  130. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/base.py +0 -0
  131. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/config/__init__.py +0 -0
  132. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/config/codec.py +0 -0
  133. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/config/util.py +0 -0
  134. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/factory/__init__.py +0 -0
  135. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/factory/connection.py +0 -0
  136. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/factory/task.py +0 -0
  137. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/housekeeping/__init__.py +0 -0
  138. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/imports/__init__.py +0 -0
  139. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/imports/util.py +0 -0
  140. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/info.py +0 -0
  141. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/result.py +0 -0
  142. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/task.py +0 -0
  143. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/tcp/__init__.py +0 -0
  144. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/tcp/json.py +0 -0
  145. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/udp.py +0 -0
  146. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/arbiter/websocket.py +0 -0
  147. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/backoff.py +0 -0
  148. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/connection.py +0 -0
  149. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/factories/__init__.py +0 -0
  150. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/__init__.py +0 -0
  151. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/common.py +0 -0
  152. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/header.py +0 -0
  153. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/request_target.py +0 -0
  154. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/response.py +0 -0
  155. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/state.py +0 -0
  156. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/http/version.py +0 -0
  157. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/manager.py +0 -0
  158. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/mixin.py +0 -0
  159. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/__init__.py +0 -0
  160. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/__init__.py +0 -0
  161. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/base.py +0 -0
  162. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/bootstrap/__init__.py +0 -0
  163. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/bootstrap/elements.py +0 -0
  164. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/bootstrap/tabs.py +0 -0
  165. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/create.py +0 -0
  166. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/elements.py +0 -0
  167. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/__init__.py +0 -0
  168. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/modal.py +0 -0
  169. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/tab/__init__.py +0 -0
  170. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/tab/base.py +0 -0
  171. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/tab/controls.py +0 -0
  172. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/tab/message.py +0 -0
  173. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/env/widgets.py +0 -0
  174. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/files.py +0 -0
  175. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/landing_page.py +0 -0
  176. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/placeholder.py +0 -0
  177. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/pyodide.py +0 -0
  178. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/sound.py +0 -0
  179. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/app/tab.py +0 -0
  180. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/html.py +0 -0
  181. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/json.py +0 -0
  182. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/struct/__init__.py +0 -0
  183. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/websocket/__init__.py +0 -0
  184. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/server/websocket/state.py +0 -0
  185. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/stream/__init__.py +0 -0
  186. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/stream/base.py +0 -0
  187. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/stream/json/__init__.py +0 -0
  188. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/stream/string.py +0 -0
  189. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/__init__.py +0 -0
  190. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/connection.py +0 -0
  191. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/create.py +0 -0
  192. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/http/__init__.py +0 -0
  193. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/protocol.py +0 -0
  194. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/scpi/__init__.py +0 -0
  195. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/telnet/__init__.py +0 -0
  196. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/telnet/codes.py +0 -0
  197. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/tcp/telnet/np_05b.py +0 -0
  198. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/__init__.py +0 -0
  199. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/create.py +0 -0
  200. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/protocol.py +0 -0
  201. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/queue.py +0 -0
  202. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/tftp/enums.py +0 -0
  203. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/udp/tftp/io.py +0 -0
  204. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/websocket/__init__.py +0 -0
  205. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/net/websocket/connection.py +0 -0
  206. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/noise/__init__.py +0 -0
  207. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/__init__.py +0 -0
  208. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/array/__init__.py +0 -0
  209. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/base.py +0 -0
  210. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/bool.py +0 -0
  211. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/byte_order.py +0 -0
  212. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/evaluation.py +0 -0
  213. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/field/__init__.py +0 -0
  214. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/field/fields.py +0 -0
  215. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/field/manager/__init__.py +0 -0
  216. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/field/manager/base.py +0 -0
  217. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/float.py +0 -0
  218. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/int.py +0 -0
  219. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/scaling.py +0 -0
  220. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/serializable/__init__.py +0 -0
  221. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/serializable/fixed.py +0 -0
  222. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/serializable/prefixed.py +0 -0
  223. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/string.py +0 -0
  224. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/__init__.py +0 -0
  225. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/base.py +0 -0
  226. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/bool.py +0 -0
  227. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/bounds.py +0 -0
  228. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/float.py +0 -0
  229. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/primitives/types/int.py +0 -0
  230. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/py.typed +0 -0
  231. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/registry/__init__.py +0 -0
  232. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/registry/bool.py +0 -0
  233. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/registry/item.py +0 -0
  234. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/registry/name.py +0 -0
  235. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/requirements.txt +0 -0
  236. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/sample/__init__.py +0 -0
  237. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/sample/peer.py +0 -0
  238. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/sample/program.py +0 -0
  239. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/schemas.py +0 -0
  240. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/struct/__init__.py +0 -0
  241. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/subprocess/__init__.py +0 -0
  242. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/subprocess/interface.py +0 -0
  243. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/subprocess/peer.py +0 -0
  244. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/subprocess/program.py +0 -0
  245. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/subprocess/protocol.py +0 -0
  246. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/__init__.py +0 -0
  247. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/asynchronous.py +0 -0
  248. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/basic/__init__.py +0 -0
  249. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/basic/manager.py +0 -0
  250. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/basic/periodic.py +0 -0
  251. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/sample.py +0 -0
  252. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/task/trig/__init__.py +0 -0
  253. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/telemetry/__init__.py +0 -0
  254. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/__init__.py +0 -0
  255. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/channels/__init__.py +0 -0
  256. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/cursor.py +0 -0
  257. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/mixin.py +0 -0
  258. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/mock.py +0 -0
  259. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/tui/task.py +0 -0
  260. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/ui/__init__.py +0 -0
  261. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/ui/controls.py +0 -0
  262. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy/util.py +0 -0
  263. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy.egg-info/SOURCES.txt +0 -0
  264. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy.egg-info/dependency_links.txt +0 -0
  265. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy.egg-info/entry_points.txt +0 -0
  266. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy.egg-info/requires.txt +2 -2
  267. {runtimepy-5.4.1 → runtimepy-5.4.3}/runtimepy.egg-info/top_level.txt +0 -0
  268. {runtimepy-5.4.1 → runtimepy-5.4.3}/setup.cfg +0 -0
  269. {runtimepy-5.4.1 → runtimepy-5.4.3}/setup.py +0 -0
  270. {runtimepy-5.4.1 → runtimepy-5.4.3}/tests/test_entry.py +0 -0
  271. {runtimepy-5.4.1 → runtimepy-5.4.3}/tests/test_mapping.py +0 -0
  272. {runtimepy-5.4.1 → runtimepy-5.4.3}/tests/test_resources.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: runtimepy
3
- Version: 5.4.1
3
+ Version: 5.4.3
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,10 +17,10 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: websockets
21
- Requires-Dist: psutil
22
20
  Requires-Dist: svgen>=0.6.8
23
21
  Requires-Dist: vcorelib>=3.3.1
22
+ Requires-Dist: psutil
23
+ Requires-Dist: websockets
24
24
  Provides-Extra: test
25
25
  Requires-Dist: pylint; extra == "test"
26
26
  Requires-Dist: flake8; extra == "test"
@@ -44,11 +44,11 @@ Requires-Dist: uvloop; (sys_platform != "win32" and sys_platform != "cygwin") an
44
44
  =====================================
45
45
  generator=datazen
46
46
  version=3.1.4
47
- hash=039855eb758d9eb1ea70df0654e31b61
47
+ hash=76ca13ab011b845de4580fd0bc1f9deb
48
48
  =====================================
49
49
  -->
50
50
 
51
- # runtimepy ([5.4.1](https://pypi.org/project/runtimepy/))
51
+ # runtimepy ([5.4.3](https://pypi.org/project/runtimepy/))
52
52
 
53
53
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
54
54
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -197,7 +197,7 @@ options:
197
197
  $ ./venv3.12/bin/runtimepy tftp -h
198
198
 
199
199
  usage: runtimepy tftp [-h] [-p PORT] [-m MODE] [-t TIMEOUT] [-r REEMIT]
200
- {read,write} host our_file their_file
200
+ {read,write} host our_file [their_file]
201
201
 
202
202
  positional arguments:
203
203
  {read,write} action to perform
@@ -2,11 +2,11 @@
2
2
  =====================================
3
3
  generator=datazen
4
4
  version=3.1.4
5
- hash=039855eb758d9eb1ea70df0654e31b61
5
+ hash=76ca13ab011b845de4580fd0bc1f9deb
6
6
  =====================================
7
7
  -->
8
8
 
9
- # runtimepy ([5.4.1](https://pypi.org/project/runtimepy/))
9
+ # runtimepy ([5.4.3](https://pypi.org/project/runtimepy/))
10
10
 
11
11
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
12
12
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -155,7 +155,7 @@ options:
155
155
  $ ./venv3.12/bin/runtimepy tftp -h
156
156
 
157
157
  usage: runtimepy tftp [-h] [-p PORT] [-m MODE] [-t TIMEOUT] [-r REEMIT]
158
- {read,write} host our_file their_file
158
+ {read,write} host our_file [their_file]
159
159
 
160
160
  positional arguments:
161
161
  {read,write} action to perform
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta:__legacy__"
4
4
 
5
5
  [project]
6
6
  name = "runtimepy"
7
- version = "5.4.1"
7
+ version = "5.4.3"
8
8
  description = "A framework for implementing Python services."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=acff0a8ea3e1379494862c3188de7a76
4
+ # hash=d1fd99eada85e38ef836802f8c0a3c50
5
5
  # =====================================
6
6
 
7
7
  """
@@ -10,7 +10,7 @@ Useful defaults and other package metadata.
10
10
 
11
11
  DESCRIPTION = "A framework for implementing Python services."
12
12
  PKG_NAME = "runtimepy"
13
- VERSION = "5.4.1"
13
+ VERSION = "5.4.3"
14
14
 
15
15
  # runtimepy-specific content.
16
16
  METRICS_NAME = "metrics"
@@ -6,14 +6,14 @@ An entry-point for the 'tftp' command.
6
6
  import argparse
7
7
  import asyncio
8
8
  from pathlib import Path
9
-
10
- from vcorelib.args import CommandFunction
9
+ from socket import getaddrinfo
11
10
 
12
11
  # third-party
12
+ from vcorelib.args import CommandFunction
13
13
  from vcorelib.asyncio import run_handle_stop
14
14
 
15
15
  # internal
16
- from runtimepy.net.udp.tftp import tftp_read, tftp_write
16
+ from runtimepy.net.udp.tftp import TFTP_PORT, tftp_read, tftp_write
17
17
  from runtimepy.net.udp.tftp.base import DEFAULT_TIMEOUT_S, REEMIT_PERIOD_S
18
18
  from runtimepy.net.udp.tftp.enums import DEFAULT_MODE
19
19
 
@@ -21,7 +21,8 @@ from runtimepy.net.udp.tftp.enums import DEFAULT_MODE
21
21
  def tftp_cmd(args: argparse.Namespace) -> int:
22
22
  """Execute the tftp command."""
23
23
 
24
- addr = (args.host, args.port)
24
+ # Resolve hostname as early as possible.
25
+ addr = (getaddrinfo(args.host, None)[0][4][0], args.port)
25
26
 
26
27
  stop_sig = asyncio.Event()
27
28
  kwargs = {
@@ -31,6 +32,9 @@ def tftp_cmd(args: argparse.Namespace) -> int:
31
32
  "process_kwargs": {"stop_sig": stop_sig},
32
33
  }
33
34
 
35
+ if not args.their_file:
36
+ args.their_file = str(args.our_file)
37
+
34
38
  if args.operation == "read":
35
39
  task = tftp_read(addr, args.our_file, args.their_file, **kwargs)
36
40
  else:
@@ -48,7 +52,7 @@ def add_tftp_cmd(parser: argparse.ArgumentParser) -> CommandFunction:
48
52
  "-p",
49
53
  "--port",
50
54
  type=int,
51
- default=69,
55
+ default=TFTP_PORT,
52
56
  help="port to message (default: %(default)s)",
53
57
  )
54
58
 
@@ -81,6 +85,6 @@ def add_tftp_cmd(parser: argparse.ArgumentParser) -> CommandFunction:
81
85
  parser.add_argument("host", help="host to message")
82
86
 
83
87
  parser.add_argument("our_file", type=Path, help="path to our file")
84
- parser.add_argument("their_file", type=str, help="path to their file")
88
+ parser.add_argument("their_file", nargs="?", help="path to their file")
85
89
 
86
90
  return tftp_cmd
@@ -40,6 +40,10 @@
40
40
  width: auto;
41
41
  }
42
42
 
43
+ .table-container {
44
+ overflow-x: scroll;
45
+ }
46
+
43
47
  .channel-column {
44
48
  overflow-y: scroll;
45
49
  overflow-x: hidden;
@@ -9,4 +9,4 @@ clients:
9
9
  - factory: tftp
10
10
  name: tftp_server
11
11
  kwargs:
12
- local_addr: [localhost, "$tftp_server"]
12
+ local_addr: [127.0.0.1, "$tftp_server"]
@@ -1,7 +1,7 @@
1
1
  # =====================================
2
2
  # generator=datazen
3
3
  # version=3.1.4
4
- # hash=c2bc26deadfa7cc275e815f499693863
4
+ # hash=79c31d1280a6e97b5d326aecb758c597
5
5
  # =====================================
6
6
 
7
7
  """
@@ -10,13 +10,14 @@ This package's command-line entry-point (boilerplate).
10
10
 
11
11
  # built-in
12
12
  import argparse
13
+ from logging import getLogger
13
14
  import os
14
15
  from pathlib import Path
15
16
  import sys
16
17
  from typing import List
17
18
 
18
19
  # third-party
19
- from vcorelib.logging import init_logging, logging_args
20
+ from vcorelib.logging import init_logging, log_time, logging_args
20
21
 
21
22
  # internal
22
23
  from runtimepy import DESCRIPTION, VERSION
@@ -68,7 +69,8 @@ def main(argv: List[str] = None) -> int:
68
69
  os.chdir(args.dir)
69
70
 
70
71
  # run the application
71
- result = entry(args)
72
+ with log_time(getLogger(__name__), "Command"):
73
+ result = entry(args)
72
74
  except SystemExit as exc:
73
75
  result = 1
74
76
  if exc.code is not None and isinstance(exc.code, int):
@@ -134,7 +134,9 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
134
134
  def channel_table(self, parent: Element) -> None:
135
135
  """Create the channel table."""
136
136
 
137
- table = div(tag="table", parent=parent)
137
+ table = div(
138
+ tag="table", parent=div(parent=parent, class_str="table-container")
139
+ )
138
140
  table.add_class("table", TEXT)
139
141
 
140
142
  header = div(tag="thead", parent=table)
@@ -248,7 +250,7 @@ class ChannelEnvironmentTabHtml(ChannelEnvironmentTabControls):
248
250
  # table doesn't take up full vertical space, few channels).
249
251
  under_construction(
250
252
  vert_container,
251
- class_str="border-start border-end",
253
+ class_str="border-start border-top border-end",
252
254
  note="unused space",
253
255
  )
254
256
 
@@ -126,14 +126,17 @@ class UdpConnection(_Connection, _TransportMixin):
126
126
  )
127
127
  return result is not None
128
128
 
129
+ should_connect: bool = True
130
+
129
131
  @classmethod
130
- async def create_connection(
131
- cls: type[T], connect: bool = True, **kwargs
132
- ) -> T:
132
+ async def create_connection(cls: type[T], **kwargs) -> T:
133
133
  """Create a UDP connection."""
134
134
 
135
135
  LOG.debug("kwargs: %s", kwargs)
136
136
 
137
+ # Allows certain connections to have more sane defaults.
138
+ connect = kwargs.pop("connect", cls.should_connect)
139
+
137
140
  # If the caller specifies a remote address but doesn't want a connected
138
141
  # socket, handle this after initial creation.
139
142
  remote_addr = None
@@ -173,8 +176,8 @@ class UdpConnection(_Connection, _TransportMixin):
173
176
  sock1.connect(("localhost", sock2.getsockname()[1]))
174
177
  sock2.connect(("localhost", sock1.getsockname()[1]))
175
178
 
176
- conn1 = await cls.create_connection(sock=sock1)
177
- conn2 = await cls.create_connection(sock=sock2)
179
+ conn1 = await cls.create_connection(sock=sock1, connect=True)
180
+ conn2 = await cls.create_connection(sock=sock2, connect=True)
178
181
  assert conn1.remote_address is not None
179
182
  assert conn2.remote_address is not None
180
183
 
@@ -38,19 +38,19 @@ class TftpConnection(BaseTftpConnection):
38
38
  ) -> bool:
39
39
  """Request a tftp read operation."""
40
40
 
41
- endpoint = self.endpoint(addr)
42
41
  end_of_data = False
43
42
  idx = 1
44
43
 
45
- def ack_sender() -> None:
46
- """Send acks."""
47
- nonlocal idx
48
- self.send_ack(block=idx - 1, addr=addr)
49
-
50
44
  async with AsyncExitStack() as stack:
51
45
  # Claim read lock and ignore cancellation.
52
46
  stack.enter_context(suppress(asyncio.CancelledError))
53
- await stack.enter_async_context(endpoint.lock)
47
+
48
+ endpoint, event = await self._await_first_block(stack, addr=addr)
49
+
50
+ def ack_sender() -> None:
51
+ """Send acks."""
52
+ nonlocal idx
53
+ endpoint.ack_sender(idx - 1, endpoint.addr)
54
54
 
55
55
  def send_rrq() -> None:
56
56
  """Send request"""
@@ -60,9 +60,6 @@ class TftpConnection(BaseTftpConnection):
60
60
  "Requesting '%s' (%s) -> %s.", filename, mode, destination
61
61
  )
62
62
 
63
- event = asyncio.Event()
64
- endpoint.awaiting_blocks[idx] = event
65
-
66
63
  with self.log_time("Awaiting first data block", reminder=True):
67
64
  # Wait for first data block.
68
65
  if not await repeat_until(
@@ -112,21 +109,22 @@ class TftpConnection(BaseTftpConnection):
112
109
  if success:
113
110
  write_block()
114
111
 
115
- # Repeat last ack in the background.
116
- if end_of_data:
117
- self._conn_tasks.append(
118
- asyncio.create_task(
119
- repeat_until( # type: ignore
120
- ack_sender,
121
- asyncio.Event(),
122
- endpoint.period.value,
123
- endpoint.timeout.value,
112
+ # Repeat last ack in the background.
113
+ if end_of_data:
114
+ self._conn_tasks.append(
115
+ asyncio.create_task(
116
+ repeat_until( # type: ignore
117
+ ack_sender,
118
+ asyncio.Event(),
119
+ endpoint.period.value,
120
+ endpoint.timeout.value,
121
+ )
124
122
  )
125
123
  )
126
- )
127
124
 
128
- # Make a to-string or log method for vcorelib FileInfo?
129
- #
125
+ # Ensure at least one ack sends.
126
+ await asyncio.sleep(0.01)
127
+
130
128
  self.logger.info(
131
129
  "Read %s (%s).",
132
130
  FileInfo.from_file(destination),
@@ -146,16 +144,14 @@ class TftpConnection(BaseTftpConnection):
146
144
  """Request a tftp write operation."""
147
145
 
148
146
  result = False
149
- endpoint = self.endpoint(addr)
150
147
 
151
148
  with as_path(source) as src:
152
149
  async with AsyncExitStack() as stack:
153
150
  # Claim write lock and ignore cancellation.
154
151
  stack.enter_context(suppress(asyncio.CancelledError))
155
- await stack.enter_async_context(endpoint.lock)
156
152
 
157
- event = asyncio.Event()
158
- endpoint.awaiting_acks[0] = event
153
+ # Set up first-ack handling.
154
+ endpoint, event = await self._await_first_ack(stack, addr=addr)
159
155
 
160
156
  def send_wrq() -> None:
161
157
  """Send request."""
@@ -183,6 +179,11 @@ class TftpConnection(BaseTftpConnection):
183
179
  )
184
180
 
185
181
  # Compare hashes.
182
+ self.logger.info(
183
+ "Reading '%s' %s.",
184
+ filename,
185
+ "succeeded" if result else "failed",
186
+ )
186
187
  if result:
187
188
  result = file_md5_hex(src) == file_md5_hex(tmp)
188
189
  self.logger.info(
@@ -193,6 +194,9 @@ class TftpConnection(BaseTftpConnection):
193
194
  return result
194
195
 
195
196
 
197
+ TFTP_PORT = 69
198
+
199
+
196
200
  @asynccontextmanager
197
201
  async def tftp(
198
202
  addr: Union[IpHost, tuple[str, int]],
@@ -3,6 +3,8 @@ A module implementing a base tftp (RFC 1350) connection interface.
3
3
  """
4
4
 
5
5
  # built-in
6
+ import asyncio
7
+ from contextlib import AsyncExitStack
6
8
  from io import BytesIO
7
9
  import logging
8
10
  from pathlib import Path
@@ -22,6 +24,7 @@ from runtimepy.net.udp.tftp.enums import (
22
24
  encode_filename_mode,
23
25
  parse_filename_mode,
24
26
  )
27
+ from runtimepy.net.util import normalize_host
25
28
  from runtimepy.primitives import Double, Uint16
26
29
 
27
30
  REEMIT_PERIOD_S = 0.20
@@ -36,6 +39,7 @@ class BaseTftpConnection(UdpConnection):
36
39
  _path: Path
37
40
 
38
41
  default_auto_restart = True
42
+ should_connect = False
39
43
 
40
44
  def set_root(self, path: Path) -> None:
41
45
  """Set a new root path for this instance."""
@@ -123,9 +127,39 @@ class BaseTftpConnection(UdpConnection):
123
127
 
124
128
  self.error_sender = error_sender
125
129
 
126
- self._endpoints: dict[str, TftpEndpoint] = {}
130
+ self._endpoints: dict[IpHost, TftpEndpoint] = {}
131
+ self._awaiting_first_ack: dict[str, TftpEndpoint] = {}
132
+ self._awaiting_first_block: dict[str, TftpEndpoint] = {}
127
133
  # self._self = self.endpoint(self.local_address)
128
134
 
135
+ async def _await_first_ack(
136
+ self,
137
+ stack: AsyncExitStack,
138
+ addr: Union[IpHost, tuple[str, int]] = None,
139
+ ) -> tuple[TftpEndpoint, asyncio.Event]:
140
+ """Set up an endpoint to wait for an initial ack from a server."""
141
+
142
+ endpoint = self.endpoint(addr)
143
+ await stack.enter_async_context(endpoint.lock)
144
+ event = asyncio.Event()
145
+ endpoint.awaiting_acks[0] = event
146
+ self._awaiting_first_ack[endpoint.addr.hostname] = endpoint
147
+ return endpoint, event
148
+
149
+ async def _await_first_block(
150
+ self,
151
+ stack: AsyncExitStack,
152
+ addr: Union[IpHost, tuple[str, int]] = None,
153
+ ) -> tuple[TftpEndpoint, asyncio.Event]:
154
+ """Set up an endpoint to wait for an initial block from a server."""
155
+
156
+ endpoint = self.endpoint(addr)
157
+ await stack.enter_async_context(endpoint.lock)
158
+ event = asyncio.Event()
159
+ endpoint.awaiting_blocks[1] = event
160
+ self._awaiting_first_block[endpoint.addr.hostname] = endpoint
161
+ return endpoint, event
162
+
129
163
  def endpoint(
130
164
  self, addr: Union[IpHost, tuple[str, int]] = None
131
165
  ) -> TftpEndpoint:
@@ -135,10 +169,10 @@ class BaseTftpConnection(UdpConnection):
135
169
  addr = self.remote_address
136
170
 
137
171
  assert addr is not None
138
- key = f"{addr[0]}:{addr[1]}"
172
+ addr = normalize_host(*addr)
139
173
 
140
- if key not in self._endpoints:
141
- self._endpoints[key] = TftpEndpoint(
174
+ if addr not in self._endpoints:
175
+ self._endpoints[addr] = TftpEndpoint(
142
176
  self._path,
143
177
  self.logger,
144
178
  addr,
@@ -149,7 +183,7 @@ class BaseTftpConnection(UdpConnection):
149
183
  self.endpoint_timeout,
150
184
  )
151
185
 
152
- return self._endpoints[key]
186
+ return self._endpoints[addr]
153
187
 
154
188
  def send_rrq(
155
189
  self,
@@ -269,15 +303,38 @@ class BaseTftpConnection(UdpConnection):
269
303
  ) -> None:
270
304
  """Handle a data message."""
271
305
 
306
+ endpoint = self.endpoint(addr)
272
307
  block = self._read_block_number(stream)
273
- self.endpoint(addr).handle_data(block, stream.read())
308
+
309
+ # Check if we're currently waiting for an initial block.
310
+ hostname = endpoint.addr.hostname
311
+ if block == 1 and hostname in self._awaiting_first_block:
312
+ to_update = self._awaiting_first_block[hostname]
313
+ del self._awaiting_first_block[hostname]
314
+ self._endpoints[endpoint.addr] = to_update
315
+ endpoint = to_update.update_from_other(endpoint)
316
+
317
+ endpoint.handle_data(block, stream.read())
274
318
 
275
319
  async def _handle_ack(
276
320
  self, stream: BinaryIO, addr: tuple[str, int]
277
321
  ) -> None:
278
322
  """Handle an acknowledge message."""
279
323
 
280
- self.endpoint(addr).handle_ack(self._read_block_number(stream))
324
+ endpoint = self.endpoint(addr)
325
+ block = self._read_block_number(stream)
326
+
327
+ # Check if we're currently waiting for an initial acknowledgement. This
328
+ # will come from the same host but a different port, so update
329
+ # references when this is detected.
330
+ hostname = endpoint.addr.hostname
331
+ if block == 0 and hostname in self._awaiting_first_ack:
332
+ to_update = self._awaiting_first_ack[hostname]
333
+ del self._awaiting_first_ack[hostname]
334
+ self._endpoints[endpoint.addr] = to_update
335
+ endpoint = to_update.update_from_other(endpoint)
336
+
337
+ endpoint.handle_ack(block)
281
338
 
282
339
  def _read_block_number(self, stream: BinaryIO) -> int:
283
340
  """Read block number from the stream."""
@@ -40,7 +40,7 @@ class TftpEndpoint(LoggerMixin):
40
40
  self,
41
41
  root: Path,
42
42
  logger: LoggerType,
43
- addr: Union[IpHost, tuple[str, int]],
43
+ addr: IpHost,
44
44
  data_sender: TftpDataSender,
45
45
  ack_sender: TftpAckSender,
46
46
  error_sender: TftpErrorSender,
@@ -75,6 +75,13 @@ class TftpEndpoint(LoggerMixin):
75
75
  self.timeout = timeout
76
76
  self.log_limiter = RateLimiter.from_s(1.0)
77
77
 
78
+ def update_from_other(self, other: "TftpEndpoint") -> "TftpEndpoint":
79
+ """Update this endpoint's attributes with attributes of another's."""
80
+
81
+ self.logger.info("Updating address to '%s'.", other.addr)
82
+ self.addr = other.addr
83
+ return self
84
+
78
85
  def chunk_sender(self, block: int, data: bytes) -> Callable[[], None]:
79
86
  """Create a method that sends a specific block of data."""
80
87
 
@@ -135,7 +142,7 @@ class TftpEndpoint(LoggerMixin):
135
142
 
136
143
  def __str__(self) -> str:
137
144
  """Get this instance as a string."""
138
- return f"{self.addr[0]}:{self.addr[1]}"
145
+ return str(self.addr)
139
146
 
140
147
  def handle_error(self, error_code: TftpErrorCode, message: str) -> None:
141
148
  """Handle a tftp error message."""
@@ -19,6 +19,11 @@ class IPv4Host(NamedTuple):
19
19
  name: str = ""
20
20
  port: int = 0
21
21
 
22
+ @property
23
+ def hostname(self) -> str:
24
+ """Get a hostname for this instance."""
25
+ return hostname(self.name)
26
+
22
27
  @property
23
28
  def address(self) -> ipaddress.IPv4Address:
24
29
  """Get an address object for this hostname."""
@@ -28,6 +33,10 @@ class IPv4Host(NamedTuple):
28
33
  """Get this host as a string."""
29
34
  return hostname_port(self.name, self.port)
30
35
 
36
+ def __hash__(self) -> int:
37
+ """Get a hash for this instance."""
38
+ return hash(str(self))
39
+
31
40
 
32
41
  class IPv6Host(NamedTuple):
33
42
  """See: https://docs.python.org/3/library/socket.html#socket-families."""
@@ -37,6 +46,11 @@ class IPv6Host(NamedTuple):
37
46
  flowinfo: int = 0
38
47
  scope_id: int = 0
39
48
 
49
+ @property
50
+ def hostname(self) -> str:
51
+ """Get a hostname for this instance."""
52
+ return hostname(self.name)
53
+
40
54
  @property
41
55
  def address(self) -> ipaddress.IPv6Address:
42
56
  """Get an address object for this hostname."""
@@ -46,6 +60,10 @@ class IPv6Host(NamedTuple):
46
60
  """Get this host as a string."""
47
61
  return hostname_port(self.name, self.port)
48
62
 
63
+ def __hash__(self) -> int:
64
+ """Get a hash for this instance."""
65
+ return hash(str(self))
66
+
49
67
 
50
68
  IpHost = _Union[IPv4Host, IPv6Host]
51
69
  IpHostlike = _Union[str, int, IpHost, None]
@@ -134,7 +134,11 @@ class Serializable(ABC):
134
134
  """Assign a next serializable."""
135
135
 
136
136
  assert self.chain is None, self.chain
137
- self.chain = chain
137
+
138
+ # mypy regression?
139
+ self.chain = chain # type: ignore
140
+ assert self.chain is not None
141
+
138
142
  return self.chain.size
139
143
 
140
144
  def add_to_end(self, chain: T, array_length: int = None) -> int:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: runtimepy
3
- Version: 5.4.1
3
+ Version: 5.4.3
4
4
  Summary: A framework for implementing Python services.
5
5
  Home-page: https://github.com/vkottler/runtimepy
6
6
  Author: Vaughn Kottler
@@ -17,10 +17,10 @@ Classifier: License :: OSI Approved :: MIT License
17
17
  Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
- Requires-Dist: websockets
21
- Requires-Dist: psutil
22
20
  Requires-Dist: svgen>=0.6.8
23
21
  Requires-Dist: vcorelib>=3.3.1
22
+ Requires-Dist: psutil
23
+ Requires-Dist: websockets
24
24
  Provides-Extra: test
25
25
  Requires-Dist: pylint; extra == "test"
26
26
  Requires-Dist: flake8; extra == "test"
@@ -44,11 +44,11 @@ Requires-Dist: uvloop; (sys_platform != "win32" and sys_platform != "cygwin") an
44
44
  =====================================
45
45
  generator=datazen
46
46
  version=3.1.4
47
- hash=039855eb758d9eb1ea70df0654e31b61
47
+ hash=76ca13ab011b845de4580fd0bc1f9deb
48
48
  =====================================
49
49
  -->
50
50
 
51
- # runtimepy ([5.4.1](https://pypi.org/project/runtimepy/))
51
+ # runtimepy ([5.4.3](https://pypi.org/project/runtimepy/))
52
52
 
53
53
  [![python](https://img.shields.io/pypi/pyversions/runtimepy.svg)](https://pypi.org/project/runtimepy/)
54
54
  ![Build Status](https://github.com/vkottler/runtimepy/workflows/Python%20Package/badge.svg)
@@ -197,7 +197,7 @@ options:
197
197
  $ ./venv3.12/bin/runtimepy tftp -h
198
198
 
199
199
  usage: runtimepy tftp [-h] [-p PORT] [-m MODE] [-t TIMEOUT] [-r REEMIT]
200
- {read,write} host our_file their_file
200
+ {read,write} host our_file [their_file]
201
201
 
202
202
  positional arguments:
203
203
  {read,write} action to perform
File without changes
File without changes