latch-asgi 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.
@@ -2,11 +2,10 @@ from http import HTTPStatus
2
2
  from typing import Any, Literal, TypeAlias, TypeVar, cast
3
3
 
4
4
  import opentelemetry.context as context
5
- import simdjson
5
+ import orjson
6
6
  from latch_data_validation.data_validation import DataValidationError, validate
7
7
  from latch_o11y.o11y import trace_function, trace_function_with_span
8
8
  from opentelemetry.trace.span import Span
9
- from orjson import dumps
10
9
 
11
10
  from ..asgi_iface import (
12
11
  HTTPReceiveCallable,
@@ -44,7 +43,6 @@ def current_http_request_span() -> Span:
44
43
 
45
44
  class HTTPErrorResponse(RuntimeError):
46
45
  def __init__(self, status: HTTPStatus, data: Any, *, headers: Headers = {}):
47
- super().__init__()
48
46
  self.status = status
49
47
  self.data = data
50
48
  self.headers = headers
@@ -91,13 +89,7 @@ async def receive_class(receive: HTTPReceiveCallable, cls: type[T]) -> T:
91
89
 
92
90
  @trace_function(tracer)
93
91
  async def receive_json(receive: HTTPReceiveCallable) -> Any:
94
- res = await receive_data(receive)
95
-
96
- p = simdjson.Parser()
97
- try:
98
- return p.parse(res, True)
99
- except ValueError as e:
100
- raise HTTPBadRequest("Failed to parse JSON") from e
92
+ return orjson.loads(await receive_data(receive))
101
93
 
102
94
 
103
95
  async def receive_data(receive: HTTPReceiveCallable):
@@ -174,7 +166,7 @@ async def send_json(
174
166
  headers: Headers = {},
175
167
  ):
176
168
  return await send_http_data(
177
- send, status, dumps(data), content_type=content_type, headers=headers
169
+ send, status, orjson.dumps(data), content_type=content_type, headers=headers
178
170
  )
179
171
 
180
172
 
@@ -2,10 +2,10 @@ from enum import Enum
2
2
  from typing import Any, TypeVar, cast
3
3
 
4
4
  import opentelemetry.context as context
5
- import simdjson
6
5
  from latch_data_validation.data_validation import DataValidationError, validate
7
6
  from latch_o11y.o11y import trace_function, trace_function_with_span
8
7
  from opentelemetry.trace.span import Span
8
+ import orjson
9
9
 
10
10
  from ..asgi_iface import (
11
11
  WebsocketAcceptEvent,
@@ -29,28 +29,26 @@ def current_websocket_request_span() -> Span:
29
29
 
30
30
 
31
31
  class WebsocketStatus(int, Enum):
32
+ """
33
+ https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
34
+ """
35
+
32
36
  normal = 1000
33
37
  """
34
38
  1000 indicates a normal closure, meaning that the purpose for
35
39
  which the connection was established has been fulfilled.
36
-
37
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
38
40
  """
39
41
 
40
42
  going_away = 1001
41
43
  """
42
44
  1001 indicates that an endpoint is "going away", such as a server
43
45
  going down or a browser having navigated away from a page.
44
-
45
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
46
46
  """
47
47
 
48
48
  protocol_error = 1002
49
49
  """
50
50
  1002 indicates that an endpoint is terminating the connection due
51
51
  to a protocol error.
52
-
53
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
54
52
  """
55
53
 
56
54
  unsupported = 1003
@@ -59,15 +57,11 @@ class WebsocketStatus(int, Enum):
59
57
  because it has received a type of data it cannot accept (e.g., an
60
58
  endpoint that understands only text data MAY send this if it
61
59
  receives a binary message).
62
-
63
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
64
60
  """
65
61
 
66
62
  reserved = 1004
67
63
  """
68
64
  Reserved. The specific meaning might be defined in the future.
69
-
70
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
71
65
  """
72
66
 
73
67
  no_status = 1005
@@ -76,8 +70,6 @@ class WebsocketStatus(int, Enum):
76
70
  Close control frame by an endpoint. It is designated for use in
77
71
  applications expecting a status code to indicate that no status
78
72
  code was actually present.
79
-
80
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
81
73
  """
82
74
 
83
75
  abnormal = 1006
@@ -87,8 +79,6 @@ class WebsocketStatus(int, Enum):
87
79
  applications expecting a status code to indicate that the
88
80
  connection was closed abnormally, e.g., without sending or
89
81
  receiving a Close control frame.
90
-
91
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
92
82
  """
93
83
 
94
84
  unsupported_payload = 1007
@@ -97,8 +87,6 @@ class WebsocketStatus(int, Enum):
97
87
  because it has received data within a message that was not
98
88
  consistent with the type of the message (e.g., non-UTF-8 [RFC3629]
99
89
  data within a text message).
100
-
101
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
102
90
  """
103
91
 
104
92
  policy_violation = 1008
@@ -108,8 +96,6 @@ class WebsocketStatus(int, Enum):
108
96
  is a generic status code that can be returned when there is no
109
97
  other more suitable status code (e.g., 1003 or 1009) or if there
110
98
  is a need to hide specific details about the policy.
111
-
112
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
113
99
  """
114
100
 
115
101
  too_large = 1009
@@ -117,8 +103,6 @@ class WebsocketStatus(int, Enum):
117
103
  1009 indicates that an endpoint is terminating the connection
118
104
  because it has received a message that is too big for it to
119
105
  process.
120
-
121
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
122
106
  """
123
107
 
124
108
  mandatory_extension = 1010
@@ -130,8 +114,6 @@ class WebsocketStatus(int, Enum):
130
114
  are needed SHOULD appear in the /reason/ part of the Close frame.
131
115
  Note that this status code is not used by the server, because it
132
116
  can fail the WebSocket handshake instead.
133
-
134
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
135
117
  """
136
118
 
137
119
  server_error = 1011
@@ -139,8 +121,6 @@ class WebsocketStatus(int, Enum):
139
121
  1011 indicates that a server is terminating the connection because
140
122
  it encountered an unexpected condition that prevented it from
141
123
  fulfilling the request.
142
-
143
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
144
124
  """
145
125
 
146
126
  tls_handshake_fail = 1015
@@ -150,12 +130,10 @@ class WebsocketStatus(int, Enum):
150
130
  applications expecting a status code to indicate that the
151
131
  connection was closed due to a failure to perform a TLS handshake
152
132
  (e.g., the server certificate can't be verified).
153
-
154
- https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1
155
133
  """
156
134
 
157
135
 
158
- class WebsocketError(RuntimeError):
136
+ class WebsocketErrorResponse(RuntimeError):
159
137
  def __init__(self, status: WebsocketStatus, data: Any, *, headers: Headers = {}):
160
138
  super().__init__()
161
139
  self.status = status
@@ -163,12 +141,6 @@ class WebsocketError(RuntimeError):
163
141
  self.headers = headers
164
142
 
165
143
 
166
- class WebsocketErrorResponse(WebsocketError): ...
167
-
168
-
169
- class WebsocketConnectionClosedError(RuntimeError): ...
170
-
171
-
172
144
  class WebsocketBadMessage(WebsocketErrorResponse):
173
145
  def __init__(self, data: Any, *, headers: Headers = {}):
174
146
  super().__init__(WebsocketStatus.policy_violation, data, headers=headers)
@@ -179,11 +151,13 @@ class WebsocketInternalServerError(WebsocketErrorResponse):
179
151
  super().__init__(WebsocketStatus.server_error, data, headers=headers)
180
152
 
181
153
 
154
+ class WebsocketConnectionClosedError(RuntimeError): ...
155
+
156
+
182
157
  # >>> I/O
183
158
 
184
159
 
185
160
  async def receive_websocket_data(receive: WebsocketReceiveCallable):
186
- res = b""
187
161
  with tracer.start_as_current_span("read websocket message") as s:
188
162
  msg = await receive()
189
163
 
@@ -203,19 +177,13 @@ async def receive_websocket_data(receive: WebsocketReceiveCallable):
203
177
  else:
204
178
  raise WebsocketBadMessage("empty message")
205
179
 
206
- s.set_attributes({"size": len(res)})
180
+ s.set_attribute("size", len(res))
207
181
  return res
208
182
 
209
183
 
210
184
  @trace_function(tracer)
211
185
  async def receive_websocket_json(receive: WebsocketReceiveCallable) -> Any:
212
- res = await receive_websocket_data(receive)
213
-
214
- p = simdjson.Parser()
215
- try:
216
- return p.parse(res, True)
217
- except ValueError as e:
218
- raise WebsocketBadMessage("Failed to parse JSON") from e
186
+ orjson.loads(await receive_websocket_data(receive))
219
187
 
220
188
 
221
189
  @trace_function(tracer)
@@ -253,9 +221,8 @@ async def send_websocket_data(
253
221
  )
254
222
 
255
223
 
256
- @trace_function_with_span(tracer)
224
+ @trace_function(tracer)
257
225
  async def accept_websocket_connection(
258
- s: Span,
259
226
  send: WebsocketSendCallable,
260
227
  receive: WebsocketReceiveCallable,
261
228
  /,
latch_asgi/server.py CHANGED
@@ -52,7 +52,6 @@ from .framework.websocket import (
52
52
  accept_websocket_connection,
53
53
  close_websocket_connection,
54
54
  current_websocket_request_span,
55
- send_websocket_data,
56
55
  websocket_request_span_key,
57
56
  )
58
57
 
@@ -295,7 +294,7 @@ class LatchASGIServer:
295
294
  if x["type"] != type_str(e):
296
295
  continue
297
296
 
298
- return validate(x, e)
297
+ return untraced_validate(x, e)
299
298
 
300
299
  raise RuntimeError(
301
300
  f"unknown websocket event type: {repr(x['type'])}"
@@ -306,7 +305,7 @@ class LatchASGIServer:
306
305
  await send(data)
307
306
 
308
307
  return await self.scope_websocket(
309
- validate(scope, WebsocketScope), ws_receive, ws_send
308
+ untraced_validate(scope, WebsocketScope), ws_receive, ws_send
310
309
  )
311
310
 
312
311
  if scope["type"] == "http":
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: latch-asgi
3
+ Version: 0.3.0
4
+ Summary: ASGI python server
5
+ Author-Email: Max Smolin <max@latch.bio>
6
+ License: CC0-1.0
7
+ Requires-Python: <4.0,>=3.11
8
+ Requires-Dist: hypercorn[uvloop]<1.0.0,>=0.14.3
9
+ Requires-Dist: latch-data-validation<1.0.0,>=0.1.3
10
+ Requires-Dist: latch-o11y<1.0.0,>=0.1.4
11
+ Requires-Dist: latch-config<1.0.0,>=0.1.6
12
+ Requires-Dist: PyJWT[crypto]<3.0.0,>=2.6.0
13
+ Requires-Dist: orjson<4.0.0,>=3.8.5
14
+ Requires-Dist: opentelemetry-sdk<2.0.0,>=1.15.0
15
+ Requires-Dist: opentelemetry-api<2.0.0,>=1.15.0
16
+ Requires-Dist: opentelemetry-instrumentation-asgi<1.0,>=0.36b0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # python-asgi
@@ -1,3 +1,6 @@
1
+ latch_asgi-0.3.0.dist-info/METADATA,sha256=x1WxOrLQcsLzJzLEaFCl6yCsK0AkZ8EHdhp82vIYYPo,643
2
+ latch_asgi-0.3.0.dist-info/WHEEL,sha256=N2J68yzZqJh3mI_Wg92rwhw0rtJDFpZj9bwQIMJgaVg,90
3
+ latch_asgi-0.3.0.dist-info/licenses/COPYING,sha256=ogEPNDSH0_dhiv_lT3ifVIdgIzHAqNA_SemnxUfPBJk,7048
1
4
  latch_asgi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
5
  latch_asgi/asgi_iface.py,sha256=8cltPG2YEf20RWcMpn2FE5g4wtauiUNNlrVnD4UYDqc,8397
3
6
  latch_asgi/auth.py,sha256=gXGVIzsvDt_kBlcS_Sx9939iGlNNs1i8KDOs-tpEBiw,4996
@@ -9,10 +12,8 @@ latch_asgi/context/websocket.py,sha256=NA0pJYdJI2kUnSu1AkPnf9heG8_B4M4_op7S8MPly
9
12
  latch_asgi/datadog_propagator.py,sha256=PFKaM87B8Ia_5RBNGIIDSgcgTjxhN0uJou2o8359kzs,3306
10
13
  latch_asgi/framework/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
14
  latch_asgi/framework/common.py,sha256=My4ePrmLD8aZd7_PC-ZJPZ9CUD90j5UZvHwA0jfTmFw,157
12
- latch_asgi/framework/http.py,sha256=4ux_CtLiBuv_MHp9IfyuwBTgC2t3vgAuQI4_3BHB9qI,5182
13
- latch_asgi/framework/websocket.py,sha256=mol3zAy4ku4Fy-XFl9qD_a5CV5FDyDr8fr4nJp406Ns,8974
15
+ latch_asgi/framework/http.py,sha256=8AMzA1FjyaFVexVPQbi1NBdbWaH6594R5AAsjoSL67Y,4992
16
+ latch_asgi/framework/websocket.py,sha256=Xyf4EUurKPxdHGV3CXUpCMLYCE41h_zz9GjJaR3NZgw,7993
14
17
  latch_asgi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- latch_asgi/server.py,sha256=YtG9ckKzMq8ej3Du26Xk7OcZysEUe3Gc211PgfM6QCo,12054
16
- latch_asgi-0.2.0.dist-info/METADATA,sha256=irapIxpHrfyhGYFg7GfXiqSJlZEjBosiV0H54adpzTo,870
17
- latch_asgi-0.2.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
18
- latch_asgi-0.2.0.dist-info/RECORD,,
18
+ latch_asgi/server.py,sha256=kMZ7ivOk7VkZ5HjrIHcveXd2ZMawnnW7QM5-JuHDXZA,12047
19
+ latch_asgi-0.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.6.1
2
+ Generator: pdm-backend (2.1.8)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -0,0 +1,121 @@
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
@@ -1,25 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: latch-asgi
3
- Version: 0.2.0
4
- Summary: ASGI python server
5
- License: CC0 1.0
6
- Author: Max Smolin
7
- Author-email: max@latch.bio
8
- Requires-Python: >=3.11,<4.0
9
- Classifier: License :: Other/Proprietary License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.11
12
- Requires-Dist: PyJWT[crypto] (>=2.6.0,<3.0.0)
13
- Requires-Dist: hypercorn[uvloop] (>=0.14.3,<0.15.0)
14
- Requires-Dist: latch-config (>=0.1.6,<0.2.0)
15
- Requires-Dist: latch-data-validation (>=0.1.3,<0.2.0)
16
- Requires-Dist: latch-o11y (>=0.1.4,<0.2.0)
17
- Requires-Dist: opentelemetry-api (>=1.15.0,<2.0.0)
18
- Requires-Dist: opentelemetry-instrumentation-asgi (>=0.36b0,<0.37)
19
- Requires-Dist: opentelemetry-sdk (>=1.15.0,<2.0.0)
20
- Requires-Dist: orjson (>=3.8.5,<4.0.0)
21
- Requires-Dist: pysimdjson (>=5.0.2,<6.0.0)
22
- Description-Content-Type: text/markdown
23
-
24
- # python-asgi
25
-