opensafely 2.1.0__tar.gz → 2.1.2__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 (239) hide show
  1. {opensafely-2.1.0 → opensafely-2.1.2}/PKG-INFO +1 -1
  2. opensafely-2.1.2/opensafely/VERSION +1 -0
  3. opensafely-2.1.2/opensafely/_vendor/pipeline/constants.py +19 -0
  4. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/features.py +2 -0
  5. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/loading.py +1 -1
  6. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/models.py +23 -86
  7. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/validation.py +52 -42
  8. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/PKG-INFO +1 -1
  9. opensafely-2.1.0/opensafely/VERSION +0 -1
  10. opensafely-2.1.0/opensafely/_vendor/pipeline/constants.py +0 -21
  11. {opensafely-2.1.0 → opensafely-2.1.2}/LICENSE +0 -0
  12. {opensafely-2.1.0 → opensafely-2.1.2}/README.md +0 -0
  13. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/__init__.py +0 -0
  14. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/certifi/__init__.py +0 -0
  15. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/certifi/__main__.py +0 -0
  16. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/certifi/cacert.pem +0 -0
  17. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/certifi/core.py +0 -0
  18. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/certifi/py.typed +0 -0
  19. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/__init__.py +0 -0
  20. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/__main__.py +0 -0
  21. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/api.py +0 -0
  22. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/cd.py +0 -0
  23. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/cli/__init__.py +0 -0
  24. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/cli/__main__.py +0 -0
  25. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/constant.py +0 -0
  26. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/legacy.py +0 -0
  27. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/md.py +0 -0
  28. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/models.py +0 -0
  29. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/py.typed +0 -0
  30. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/utils.py +0 -0
  31. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/charset_normalizer/version.py +0 -0
  32. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/distro/__init__.py +0 -0
  33. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/distro/__main__.py +0 -0
  34. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/distro/distro.py +0 -0
  35. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/distro/py.typed +0 -0
  36. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/__init__.py +0 -0
  37. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/__main__.py +0 -0
  38. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/cli.py +0 -0
  39. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/codec.py +0 -0
  40. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/compat.py +0 -0
  41. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/core.py +0 -0
  42. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/idnadata.py +0 -0
  43. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/intranges.py +0 -0
  44. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/package_data.py +0 -0
  45. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/py.typed +0 -0
  46. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/idna/uts46data.py +0 -0
  47. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/_logs/__init__.py +0 -0
  48. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/_logs/_internal/__init__.py +0 -0
  49. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/_logs/py.typed +0 -0
  50. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/_logs/severity/__init__.py +0 -0
  51. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/attributes/__init__.py +0 -0
  52. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/attributes/py.typed +0 -0
  53. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/baggage/__init__.py +0 -0
  54. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/baggage/propagation/__init__.py +0 -0
  55. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/baggage/py.typed +0 -0
  56. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/context/__init__.py +0 -0
  57. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/context/context.py +0 -0
  58. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/context/contextvars_context.py +0 -0
  59. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/context/py.typed +0 -0
  60. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/environment_variables/__init__.py +0 -0
  61. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/environment_variables/py.typed +0 -0
  62. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/metrics/__init__.py +0 -0
  63. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/metrics/_internal/__init__.py +0 -0
  64. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/metrics/_internal/instrument.py +0 -0
  65. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/metrics/_internal/observation.py +0 -0
  66. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/metrics/py.typed +0 -0
  67. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagate/__init__.py +0 -0
  68. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagate/py.typed +0 -0
  69. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagators/_envcarrier.py +0 -0
  70. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagators/composite.py +0 -0
  71. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagators/py.typed +0 -0
  72. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/propagators/textmap.py +0 -0
  73. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/py.typed +0 -0
  74. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/__init__.py +0 -0
  75. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/propagation/__init__.py +0 -0
  76. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/propagation/tracecontext.py +0 -0
  77. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/py.typed +0 -0
  78. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/span.py +0 -0
  79. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/trace/status.py +0 -0
  80. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/_decorator.py +0 -0
  81. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/_importlib_metadata.py +0 -0
  82. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/_once.py +0 -0
  83. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/_providers.py +0 -0
  84. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/py.typed +0 -0
  85. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/re.py +0 -0
  86. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/util/types.py +0 -0
  87. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/version/__init__.py +0 -0
  88. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/opentelemetry/version/py.typed +0 -0
  89. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/__init__.py +1 -1
  90. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/__main__.py +0 -0
  91. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/exceptions.py +0 -0
  92. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/legacy.py +0 -0
  93. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/main.py +0 -0
  94. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline/outputs.py +0 -0
  95. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/pipeline.pyi +0 -0
  96. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/__init__.py +0 -0
  97. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/__version__.py +0 -0
  98. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/_internal_utils.py +0 -0
  99. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/_types.py +0 -0
  100. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/adapters.py +0 -0
  101. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/api.py +0 -0
  102. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/auth.py +0 -0
  103. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/certs.py +0 -0
  104. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/compat.py +0 -0
  105. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/cookies.py +0 -0
  106. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/exceptions.py +0 -0
  107. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/help.py +0 -0
  108. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/hooks.py +0 -0
  109. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/models.py +0 -0
  110. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/packages.py +0 -0
  111. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/py.typed +0 -0
  112. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/sessions.py +0 -0
  113. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/status_codes.py +0 -0
  114. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/structures.py +0 -0
  115. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/requests/utils.py +0 -0
  116. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/__init__.py +0 -0
  117. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/anchor.py +0 -0
  118. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/comments.py +0 -0
  119. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/compat.py +0 -0
  120. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/composer.py +0 -0
  121. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/configobjwalker.py +0 -0
  122. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/constructor.py +0 -0
  123. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/cyaml.py +0 -0
  124. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/dumper.py +0 -0
  125. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/emitter.py +0 -0
  126. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/error.py +0 -0
  127. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/events.py +0 -0
  128. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/loader.py +0 -0
  129. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/main.py +0 -0
  130. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/nodes.py +0 -0
  131. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/parser.py +0 -0
  132. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/py.typed +0 -0
  133. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/reader.py +0 -0
  134. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/representer.py +0 -0
  135. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/resolver.py +0 -0
  136. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/scalarbool.py +0 -0
  137. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/scalarfloat.py +0 -0
  138. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/scalarint.py +0 -0
  139. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/scalarstring.py +0 -0
  140. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/scanner.py +0 -0
  141. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/serializer.py +0 -0
  142. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/timestamp.py +0 -0
  143. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/tokens.py +0 -0
  144. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/ruyaml/util.py +0 -0
  145. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/typing_extensions.py +0 -0
  146. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/typing_extensions.pyi +0 -0
  147. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/__init__.py +0 -0
  148. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/_base_connection.py +0 -0
  149. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/_collections.py +0 -0
  150. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/_request_methods.py +0 -0
  151. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/_version.py +0 -0
  152. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/connection.py +0 -0
  153. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/connectionpool.py +0 -0
  154. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/__init__.py +0 -0
  155. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/emscripten/__init__.py +0 -0
  156. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/emscripten/connection.py +0 -0
  157. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/emscripten/fetch.py +0 -0
  158. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/emscripten/request.py +0 -0
  159. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/emscripten/response.py +0 -0
  160. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/pyopenssl.py +0 -0
  161. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/contrib/socks.py +0 -0
  162. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/exceptions.py +0 -0
  163. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/fields.py +0 -0
  164. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/filepost.py +0 -0
  165. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/http2/__init__.py +0 -0
  166. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/http2/connection.py +0 -0
  167. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/http2/probe.py +0 -0
  168. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/poolmanager.py +0 -0
  169. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/py.typed +0 -0
  170. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/response.py +0 -0
  171. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/__init__.py +0 -0
  172. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/connection.py +0 -0
  173. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/proxy.py +0 -0
  174. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/request.py +0 -0
  175. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/response.py +0 -0
  176. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/retry.py +0 -0
  177. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/ssl_.py +0 -0
  178. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/ssl_match_hostname.py +0 -0
  179. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/ssltransport.py +0 -0
  180. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/timeout.py +0 -0
  181. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/url.py +0 -0
  182. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/util.py +0 -0
  183. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/_vendor/urllib3/util/wait.py +0 -0
  184. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/clean.py +0 -0
  185. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/codelists.py +0 -0
  186. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/execute.py +0 -0
  187. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/info.py +0 -0
  188. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/__init__.py +0 -0
  189. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/actions.py +0 -0
  190. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/cli/__init__.py +0 -0
  191. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/cli/local_run.py +0 -0
  192. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/cli/migrate.py +0 -0
  193. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/config.py +0 -0
  194. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/create_or_update_jobs.py +0 -0
  195. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/executors/__init__.py +0 -0
  196. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/executors/local.py +0 -0
  197. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/executors/volumes.py +0 -0
  198. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/job_executor.py +0 -0
  199. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/__init__.py +0 -0
  200. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/database.py +0 -0
  201. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/docker.py +0 -0
  202. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/git.py +0 -0
  203. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/github_validators.py +0 -0
  204. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/log_utils.py +0 -0
  205. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/lru_dict.py +0 -0
  206. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/path_utils.py +0 -0
  207. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/string_utils.py +0 -0
  208. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/subprocess_utils.py +0 -0
  209. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/lib/yaml_utils.py +0 -0
  210. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/models.py +0 -0
  211. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/queries.py +0 -0
  212. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/reusable_actions.py +0 -0
  213. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/run.py +0 -0
  214. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jobrunner/tracing.py +0 -0
  215. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/jupyter.py +0 -0
  216. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/launch.py +0 -0
  217. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/pull.py +0 -0
  218. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/rstudio.py +0 -0
  219. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/unzip.py +0 -0
  220. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/upgrade.py +0 -0
  221. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely/utils.py +0 -0
  222. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/SOURCES.txt +0 -0
  223. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/dependency_links.txt +0 -0
  224. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/entry_points.txt +0 -0
  225. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/requires.txt +0 -0
  226. {opensafely-2.1.0 → opensafely-2.1.2}/opensafely.egg-info/top_level.txt +0 -0
  227. {opensafely-2.1.0 → opensafely-2.1.2}/pyproject.toml +0 -0
  228. {opensafely-2.1.0 → opensafely-2.1.2}/setup.cfg +0 -0
  229. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_clean.py +0 -0
  230. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_codelists.py +0 -0
  231. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_execute.py +0 -0
  232. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_info.py +0 -0
  233. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_init.py +0 -0
  234. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_launch.py +0 -0
  235. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_packaging.py +0 -0
  236. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_pull.py +0 -0
  237. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_unzip.py +0 -0
  238. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_upgrade.py +0 -0
  239. {opensafely-2.1.0 → opensafely-2.1.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensafely
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Command line tool for running OpenSAFELY studies locally.
5
5
  Author-email: OpenSAFELY <tech@opensafely.org>
6
6
  License: GPL-3.0
@@ -0,0 +1 @@
1
+ v2.1.2
@@ -0,0 +1,19 @@
1
+ # The magic action name which means "run every action"
2
+ RUN_ALL_COMMAND = "run_all"
3
+
4
+ LEVEL4_FILE_TYPES = {
5
+ # tables
6
+ ".csv",
7
+ # images
8
+ ".jpg",
9
+ ".jpeg",
10
+ ".png",
11
+ ".svg",
12
+ ".svgz",
13
+ # reports
14
+ ".html",
15
+ ".txt",
16
+ ".log",
17
+ ".json",
18
+ ".md",
19
+ }
@@ -15,6 +15,8 @@ FEATURE_FLAGS_BY_VERSION = {
15
15
 
16
16
  LATEST_VERSION = max([v[0] for v in FEATURE_FLAGS_BY_VERSION.values()])
17
17
 
18
+ MINIMUM_VERSION = 4
19
+
18
20
 
19
21
  def get_feature_flags_for_version(version: float) -> SimpleNamespace:
20
22
  if version > LATEST_VERSION:
@@ -80,7 +80,7 @@ def parse_yaml_file(data: str | Path, filename: str | None = None) -> dict[str,
80
80
  if FAST_PARSER is not None: # pragma: no cover
81
81
  try:
82
82
  return FAST_PARSER.load(data) # type: ignore[no-any-return]
83
- except Exception:
83
+ except Exception: # noqa: S110, BLE001
84
84
  pass
85
85
 
86
86
  try:
@@ -4,16 +4,15 @@ import pathlib
4
4
  import re
5
5
  import shlex
6
6
  import warnings
7
- from collections import defaultdict
8
7
  from dataclasses import dataclass
9
8
  from typing import Any
10
9
 
11
10
  from .constants import RUN_ALL_COMMAND
12
11
  from .exceptions import InvalidPatternError, ValidationError
13
- from .features import LATEST_VERSION, get_feature_flags_for_version
12
+ from .features import LATEST_VERSION, MINIMUM_VERSION, get_feature_flags_for_version
14
13
  from .validation import (
15
14
  validate_action_config,
16
- validate_cohortextractor_outputs,
15
+ validate_actions_config,
17
16
  validate_ehrql_outputs,
18
17
  validate_glob_pattern,
19
18
  validate_no_kwargs,
@@ -21,6 +20,7 @@ from .validation import (
21
20
  validate_not_latest_tag,
22
21
  validate_not_run_all_action,
23
22
  validate_type,
23
+ validate_unique_output_paths,
24
24
  )
25
25
 
26
26
 
@@ -28,7 +28,6 @@ from .validation import (
28
28
  DB_COMMANDS = {
29
29
  "ehrql": ("generate-dataset", "generate-measures"),
30
30
  "sqlrunner": "*", # all commands are valid
31
- "cohortextractor": ("generate_cohort", "generate_codelist_report"),
32
31
  "databuilder": ("generate-dataset",),
33
32
  }
34
33
 
@@ -54,26 +53,6 @@ def is_database_action(args: list[str]) -> bool:
54
53
  return args[1] in db_commands
55
54
 
56
55
 
57
- @dataclass(frozen=True)
58
- class Expectations:
59
- population_size: int
60
-
61
- @classmethod
62
- def build(
63
- cls,
64
- population_size: Any = None,
65
- **kwargs: Any,
66
- ) -> Expectations:
67
- validate_no_kwargs(kwargs, "project `expectations` section")
68
- try:
69
- population_size = int(population_size)
70
- except (TypeError, ValueError):
71
- raise ValidationError(
72
- "Project expectations population size must be a number",
73
- )
74
- return cls(population_size)
75
-
76
-
77
56
  @dataclass(frozen=True)
78
57
  class Outputs:
79
58
  highly_sensitive: dict[str, str] | None
@@ -95,7 +74,7 @@ class Outputs:
95
74
  and minimally_sensitive is None
96
75
  ):
97
76
  raise ValidationError(
98
- f"must specify at least one output of: {', '.join(['highly_sensitive', 'moderately_sensitive', 'minimally_sensitive'])}"
77
+ "must specify at least one output of: highly_sensitive, moderately_sensitive, minimally_sensitive"
99
78
  )
100
79
 
101
80
  validate_no_kwargs(kwargs, f"`outputs` section for action {action_id}")
@@ -210,10 +189,11 @@ class Action:
210
189
  )
211
190
  action = cls(action_id, outputs, run, needs, config, dummy_data_file)
212
191
 
213
- if re.match(r"cohortextractor:\S+ generate_cohort", run.raw):
214
- validate_cohortextractor_outputs(action_id, action)
215
- if re.match(r"(ehrql|databuilder):\S+ generate[-_]dataset", run.raw):
216
- validate_ehrql_outputs(action_id, action)
192
+ if match := re.match(
193
+ r"(ehrql|databuilder):\S+ generate[-_](?P<ehrql_type>dataset|measures)",
194
+ run.raw,
195
+ ):
196
+ validate_ehrql_outputs(action_id, action, match.group("ehrql_type"))
217
197
 
218
198
  return action
219
199
 
@@ -249,17 +229,14 @@ class Action:
249
229
  class Pipeline:
250
230
  version: float
251
231
  actions: dict[str, Action]
252
- expectations: Expectations | None
253
232
 
254
233
  @classmethod
255
234
  def build(
256
235
  cls,
257
236
  version: Any = None,
258
237
  actions: Any = None,
259
- expectations: Any = None,
260
238
  **kwargs: Any,
261
239
  ) -> Pipeline:
262
- validate_no_kwargs(kwargs, "project")
263
240
  if version is None:
264
241
  raise ValidationError(
265
242
  f"Project file must have a `version` attribute specifying which "
@@ -271,84 +248,44 @@ class Pipeline:
271
248
  version = float(version)
272
249
  except (TypeError, ValueError):
273
250
  raise ValidationError(
274
- f"`version` must be a number between 1 and {LATEST_VERSION}"
251
+ f"`version` must be a number between {MINIMUM_VERSION} and {LATEST_VERSION}"
275
252
  )
276
253
  else:
254
+ if version < MINIMUM_VERSION:
255
+ raise ValidationError(
256
+ f"Your project file is using a deprecated version ({version}); update to at least version {MINIMUM_VERSION}"
257
+ )
277
258
  if version != LATEST_VERSION:
278
259
  warnings.warn(
279
260
  f"ProjectWarning: Your project file is using an old version ({version}); consider updating to version {LATEST_VERSION}",
280
261
  stacklevel=2,
281
262
  )
282
263
 
264
+ validate_no_kwargs(kwargs, "project")
265
+
283
266
  feat = get_feature_flags_for_version(version)
284
267
 
285
268
  validate_type(actions, dict, "Project `actions` section")
286
269
 
287
- _actions = dict()
270
+ _actions = {}
288
271
  for action_id, action_config in actions.items():
289
272
  validate_not_run_all_action(action_id, feat)
290
273
  validate_action_config(action_id, action_config)
291
274
  _actions[action_id] = Action.build(action_id, **action_config)
292
275
  actions = _actions
293
276
 
294
- if feat.REMOVE_SUPPORT_FOR_COHORT_EXTRACTOR:
295
- for config in actions.values():
296
- validate_not_cohort_extractor_action(config)
277
+ for config in actions.values():
278
+ validate_not_cohort_extractor_action(config)
297
279
 
298
280
  if feat.REMOVE_SUPPORT_FOR_LATEST_TAG:
299
281
  for config in actions.values():
300
282
  validate_not_latest_tag(config)
301
283
 
302
- seen: dict[Command, list[str]] = defaultdict(list)
303
- for name, config in actions.items():
304
- run = config.run
305
- if run in seen:
306
- raise ValidationError(
307
- f"Action {name} has the same 'run' command as other actions: {' ,'.join(seen[run])}"
308
- )
309
- seen[run].append(name)
310
-
311
- if feat.UNIQUE_OUTPUT_PATH:
312
- # find duplicate paths defined in the outputs section
313
- seen_files = []
314
- for config in actions.values():
315
- for output in config.outputs.dict().values():
316
- for filename in output.values():
317
- if filename in seen_files:
318
- raise ValidationError(
319
- f"Output path {filename} is not unique"
320
- )
321
-
322
- seen_files.append(filename)
323
-
324
- for a in actions.values():
325
- for n in a.needs:
326
- if n not in actions:
327
- raise ValidationError(
328
- f"Action `{a.action_id}` references an unknown action in its `needs` list: {n}"
329
- )
330
-
331
- if feat.REMOVE_SUPPORT_FOR_COHORT_EXTRACTOR:
332
- if expectations is not None:
333
- raise ValidationError(
334
- "Project includes `expectations` section, which is not supported in this version. "
335
- "This section is only applicable to deprecated cohortextractor actions; you can safely remove it."
336
- )
337
- elif feat.EXPECTATIONS_POPULATION:
338
- if expectations is None:
339
- raise ValidationError("Project must include `expectations` section")
340
- else:
341
- expectations = {"population_size": 1000}
284
+ validate_actions_config(actions)
342
285
 
343
- if expectations is not None:
344
- validate_type(expectations, dict, "Project `expectations` section")
345
- if "population_size" not in expectations:
346
- raise ValidationError(
347
- "Project `expectations` section must include `population_size` section",
348
- )
349
- expectations = Expectations.build(**expectations)
286
+ validate_unique_output_paths(actions)
350
287
 
351
- return cls(version, actions, expectations)
288
+ return cls(version, actions)
352
289
 
353
290
  @property
354
291
  def all_actions(self) -> list[str]:
@@ -360,7 +297,7 @@ class Pipeline:
360
297
  We use a list comprehension rather than set operators as previously so we preserve
361
298
  the original order.
362
299
  """
363
- return [action for action in self.actions.keys() if action != RUN_ALL_COMMAND]
300
+ return [action for action in self.actions if action != RUN_ALL_COMMAND]
364
301
 
365
302
  @property
366
303
  def action_images(self) -> set[str]:
@@ -3,17 +3,17 @@ from __future__ import annotations
3
3
  import fnmatch
4
4
  import posixpath
5
5
  import warnings
6
+ from collections import defaultdict
6
7
  from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath
7
8
  from types import SimpleNamespace
8
9
  from typing import TYPE_CHECKING, Any
9
10
 
10
11
  from .constants import LEVEL4_FILE_TYPES
11
12
  from .exceptions import InvalidPatternError, ValidationError
12
- from .outputs import get_output_dirs
13
13
 
14
14
 
15
15
  if TYPE_CHECKING: # pragma: no cover
16
- from .models import Action
16
+ from .models import Action, Command
17
17
 
18
18
 
19
19
  def validate_type(val: Any, exp_type: type, loc: str, optional: bool = False) -> None:
@@ -59,11 +59,10 @@ def validate_glob_pattern(pattern: str, privacy_level: str) -> None:
59
59
  "output paths must have a file type extension at the end"
60
60
  )
61
61
 
62
- if privacy_level == "moderately_sensitive":
63
- if path.suffix not in LEVEL4_FILE_TYPES:
64
- raise InvalidPatternError(
65
- f"{path} is not an allowed file type for moderately_sensitive outputs"
66
- )
62
+ if privacy_level == "moderately_sensitive" and path.suffix not in LEVEL4_FILE_TYPES:
63
+ raise InvalidPatternError(
64
+ f"{path} is not an allowed file type for moderately_sensitive outputs"
65
+ )
67
66
 
68
67
  # Check that the path is in normal form
69
68
  if posixpath.normpath(pattern) != pattern:
@@ -92,6 +91,29 @@ def validate_action_config(action_id: str, action_config: Any) -> None:
92
91
  )
93
92
 
94
93
 
94
+ def validate_actions_config(actions: dict[str, Action]) -> None:
95
+ """
96
+ Validates that actions are consistent and valid with respect to other actions
97
+ - run commands are not duplicated
98
+ - Specified `needs` are valid actions
99
+ """
100
+ seen: dict[Command, list[str]] = defaultdict(list)
101
+ for name, config in actions.items():
102
+ run = config.run
103
+ if run in seen:
104
+ raise ValidationError(
105
+ f"Action {name} has the same 'run' command as other actions: {' ,'.join(seen[run])}"
106
+ )
107
+ seen[run].append(name)
108
+
109
+ for a in actions.values():
110
+ for n in a.needs:
111
+ if n not in actions:
112
+ raise ValidationError(
113
+ f"Action `{a.action_id}` references an unknown action in its `needs` list: {n}"
114
+ )
115
+
116
+
95
117
  def validate_not_cohort_extractor_action(action: Action) -> None:
96
118
  if action.run.parts[0].startswith("cohortextractor"):
97
119
  raise ValidationError(
@@ -121,49 +143,30 @@ def validate_not_latest_tag(action: Action) -> None:
121
143
  )
122
144
 
123
145
 
124
- def validate_cohortextractor_outputs(action_id: str, action: Action) -> None:
146
+ def validate_unique_output_paths(actions: dict[str, Action]) -> None:
125
147
  """
126
- Check cohortextractor's output config is valid for this command
127
-
128
- We can't validate outputs in the Action or Outputs models because we need
129
- to look up other fields (eg run).
148
+ Validates that paths defined in the outputs section are unique
130
149
  """
131
- # ensure we only have output level defined.
132
- num_output_levels = len(action.outputs)
133
- if num_output_levels != 1:
134
- raise ValidationError(
135
- "A `generate_cohort` action must have exactly one output; "
136
- f"{action_id} had {num_output_levels}"
137
- )
150
+ seen_files = []
151
+ for config in actions.values():
152
+ for output in config.outputs.dict().values():
153
+ for filename in output.values():
154
+ if filename in seen_files:
155
+ raise ValidationError(f"Output path {filename} is not unique")
138
156
 
139
- output_dirs = get_output_dirs(action.outputs)
140
- if len(output_dirs) == 1:
141
- return
142
-
143
- # If we detect multiple output directories but the command explicitly
144
- # specifies an output directory then we assume the user knows what
145
- # they're doing and don't attempt to modify the output directory or
146
- # throw an error
147
- flag = "--output-dir"
148
- has_output_dir = any(
149
- arg == flag or arg.startswith(f"{flag}=") for arg in action.run.parts
150
- )
151
- if not has_output_dir:
152
- raise ValidationError(
153
- f"generate_cohort command should produce output in only one "
154
- f"directory, found {len(output_dirs)}:\n"
155
- + "\n".join([f" - {d}/" for d in output_dirs])
156
- )
157
+ seen_files.append(filename)
157
158
 
158
159
 
159
- def validate_ehrql_outputs(action_id: str, action: Action) -> None:
160
+ def validate_ehrql_outputs(action_id: str, action: Action, ehrql_type: str) -> None:
160
161
  """
161
162
  Check ehrQL's output config is valid for this command
162
163
 
163
164
  We can't validate outputs in the Action or Outputs models because we need
164
165
  to look up other fields (eg run).
165
166
  """
166
- if action.outputs.moderately_sensitive or action.outputs.minimally_sensitive:
167
+ if ehrql_type == "dataset" and (
168
+ action.outputs.moderately_sensitive or action.outputs.minimally_sensitive
169
+ ):
167
170
  raise ValidationError(
168
171
  f"`{action_id}` action uses `generate-dataset` and so all outputs must "
169
172
  f"be labelled `highly_sensitive`"
@@ -173,11 +176,18 @@ def validate_ehrql_outputs(action_id: str, action: Action) -> None:
173
176
  if not output_spec:
174
177
  raise ValidationError(
175
178
  f"`{action_id}` action does not provide an `--output` argument specifying "
176
- f"where the results of `generate-dataset` should be stored"
179
+ f"where the results of `generate-{ehrql_type}` should be stored"
180
+ )
181
+
182
+ if ehrql_type == "dataset":
183
+ output_patterns = list((action.outputs.highly_sensitive or {}).values())
184
+ else:
185
+ assert ehrql_type == "measures"
186
+ output_patterns = list((action.outputs.highly_sensitive or {}).values()) + list(
187
+ (action.outputs.moderately_sensitive or {}).values()
177
188
  )
178
189
 
179
- output_patterns = (action.outputs.highly_sensitive or {}).values()
180
- if not output_patterns_match_spec(output_spec, list(output_patterns)):
190
+ if not output_patterns_match_spec(output_spec, output_patterns):
181
191
  expected = expected_pattern_for_spec(output_spec)
182
192
  formatted_patterns = " \n".join(output_patterns)
183
193
  raise ValidationError(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: opensafely
3
- Version: 2.1.0
3
+ Version: 2.1.2
4
4
  Summary: Command line tool for running OpenSAFELY studies locally.
5
5
  Author-email: OpenSAFELY <tech@opensafely.org>
6
6
  License: GPL-3.0
@@ -1 +0,0 @@
1
- v2.1.0
@@ -1,21 +0,0 @@
1
- # The magic action name which means "run every action"
2
- RUN_ALL_COMMAND = "run_all"
3
-
4
- LEVEL4_FILE_TYPES = set(
5
- [
6
- # tables
7
- ".csv",
8
- # images
9
- ".jpg",
10
- ".jpeg",
11
- ".png",
12
- ".svg",
13
- ".svgz",
14
- # reports
15
- ".html",
16
- ".txt",
17
- ".log",
18
- ".json",
19
- ".md",
20
- ]
21
- )
File without changes
File without changes