spaces 0.50.dev2__tar.gz → 0.50.dev3__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 (71) hide show
  1. {spaces-0.50.dev2 → spaces-0.50.dev3}/PKG-INFO +1 -1
  2. {spaces-0.50.dev2 → spaces-0.50.dev3}/pyproject.toml +1 -1
  3. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/client.py +10 -5
  4. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/context.py +12 -0
  5. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/middleware.py +11 -2
  6. {spaces-0.50.dev2 → spaces-0.50.dev3}/README.md +0 -0
  7. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/__init__.py +0 -0
  8. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/__init__.py +0 -0
  9. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/asgiref/__init__.py +0 -0
  10. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/asgiref/typing.py +0 -0
  11. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/codefind/LICENSE +0 -0
  12. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/codefind/__init__.py +0 -0
  13. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/codefind/registry.py +0 -0
  14. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/codefind/version.py +0 -0
  15. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/LICENSE +0 -0
  16. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/__init__.py +0 -0
  17. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/codetools.py +0 -0
  18. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/parse.py +0 -0
  19. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/register.py +0 -0
  20. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/utils.py +0 -0
  21. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/jurigged/version.py +0 -0
  22. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/LICENSE +0 -0
  23. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/__init__.py +0 -0
  24. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/abc.py +0 -0
  25. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/codegen.py +0 -0
  26. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/core.py +0 -0
  27. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/dependent.py +0 -0
  28. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/medley.py +0 -0
  29. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/mro.py +0 -0
  30. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/py.typed +0 -0
  31. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/recode.py +0 -0
  32. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/signatures.py +0 -0
  33. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/typemap.py +0 -0
  34. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/types.py +0 -0
  35. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/utils.py +0 -0
  36. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/ovld/version.py +0 -0
  37. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sse_starlette/LICENSE +0 -0
  38. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sse_starlette/__init__.py +0 -0
  39. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sse_starlette/event.py +0 -0
  40. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sse_starlette/py.typed +0 -0
  41. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sse_starlette/sse.py +0 -0
  42. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sseclient/LICENSE +0 -0
  43. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/_vendor/sseclient/__init__.py +0 -0
  44. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/config.py +0 -0
  45. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/reloading/__init__.py +0 -0
  46. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/reloading/client.py +0 -0
  47. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/reloading/server.py +0 -0
  48. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/reloading/types.py +0 -0
  49. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/reloading/types.ts +0 -0
  50. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/utils.py +0 -0
  51. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/__init__.py +0 -0
  52. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/api.py +0 -0
  53. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/decorator.py +0 -0
  54. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/gradio.py +0 -0
  55. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/lifecycle.py +0 -0
  56. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/mmap.py +0 -0
  57. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/__init__.py +0 -0
  58. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/aoti.py +0 -0
  59. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/bitsandbytes.py +0 -0
  60. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/bitsandbytes_legacy.py +0 -0
  61. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/cudart.py +0 -0
  62. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/packing.py +0 -0
  63. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/patching.py +0 -0
  64. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/patching_legacy.py +0 -0
  65. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/static.py +0 -0
  66. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/types.py +0 -0
  67. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/torch/utils.py +0 -0
  68. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/tqdm.py +0 -0
  69. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/types.py +0 -0
  70. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/utils.py +0 -0
  71. {spaces-0.50.dev2 → spaces-0.50.dev3}/spaces/zero/wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaces
3
- Version: 0.50.dev2
3
+ Version: 0.50.dev3
4
4
  Summary: Utilities for Hugging Face Spaces
5
5
  Author: Charles Bensimon
6
6
  Author-email: Charles Bensimon <charles@huggingface.co>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spaces"
3
- version = "0.50.dev2"
3
+ version = "0.50.dev3"
4
4
  description = "Utilities for Hugging Face Spaces"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<3.15"
@@ -60,6 +60,7 @@ def html_string(html_contents: str, text_contents: str): # pragma: no cover
60
60
  return HTMLString(html_contents)
61
61
 
62
62
 
63
+ # TODO: Remove (make it like ZeroGPU quota exceeded errors)
63
64
  def _toast_action(
64
65
  auth: AuthLevel | None,
65
66
  pro_message: str,
@@ -126,7 +127,7 @@ def schedule(
126
127
  f"is larger than the maximum allowed"
127
128
  )
128
129
  elif token is None:
129
- title = "ZeroGPU quota exceeded"
130
+ title = "ZeroGPU quota exceeded" # TODO: different title?
130
131
  message = (
131
132
  f"Space app has reached its GPU limit. "
132
133
  f"{EXAMPLES_RETRY_MESSAGE}"
@@ -189,11 +190,13 @@ def schedule(
189
190
  raise RuntimeError("Error while re-trying after queue disconnect")
190
191
  return schedule(task_id, duration, _first_attempt=False)
191
192
  if event.event == 'ping':
192
- if connection_state is not None and not connection_state.alive:
193
+ print('ping', getattr(connection_state, 'id', None))
194
+ if ctx.disconnected:
193
195
  res.close()
194
- raise RuntimeError("Connection closed by visitor while queueing")
196
+ raise RuntimeError(f"Connection closed by visitor while queueing ({getattr(connection_state, 'id', None)})")
195
197
  continue
196
198
  if event.event == 'failed':
199
+ print('failed', getattr(connection_state, 'id', None))
197
200
  if token is None:
198
201
  message = f"{NO_GPU_MESSAGE_INQUEUE}. {EXAMPLES_RETRY_MESSAGE}"
199
202
  else:
@@ -205,16 +208,18 @@ def schedule(
205
208
  ending="priority in ZeroGPU queues",
206
209
  )
207
210
  message = f"{NO_GPU_MESSAGE_INQUEUE}. {details}"
211
+ # TODO: API vs GUI message commme pour quota exceeded non ?
208
212
  raise ctx.exception(
209
213
  title="ZeroGPU queue timeout",
210
214
  message=message,
211
215
  status_code=503,
212
216
  )
213
217
  if event.event == 'succeeded':
218
+ print('succeeded', getattr(connection_state, 'id', None))
214
219
  assert event.data is not None
215
- if connection_state is not None and not connection_state.alive:
220
+ if ctx.disconnected:
216
221
  release(event.data.allowToken)
217
- raise RuntimeError("Connection closed by visitor on queue success")
222
+ raise RuntimeError(f"Connection closed by visitor on queue success ({getattr(connection_state, 'id', None)})")
218
223
  ctx.success(
219
224
  title="ZeroGPU queue",
220
225
  message="Successfully acquired a GPU",
@@ -1,6 +1,7 @@
1
1
  """
2
2
  """
3
3
 
4
+ import secrets
4
5
  from contextvars import ContextVar
5
6
  from typing import Callable
6
7
  from typing import Literal
@@ -54,6 +55,7 @@ class WorkerContextualizerDefault:
54
55
 
55
56
  class ConnectionState:
56
57
  def __init__(self) -> None:
58
+ self.id = secrets.token_hex(2)
57
59
  self.alive = True
58
60
 
59
61
 
@@ -63,6 +65,7 @@ class ZeroGPUContext:
63
65
  exception_mapper: Callable[[ZeroGPUErrorResponse, Exception | None], Exception] | None = None,
64
66
  log_emitter: Callable[[ZeroGPULog], None] | None = None,
65
67
  worker_contextualizer: Callable[[], Picklable[Callable[[], None]]] | None = None,
68
+ disconnect_detector: Callable[[], bool] | None = None,
66
69
  connection_state: ConnectionState | None = None,
67
70
  server_port: int | None = None,
68
71
  headers: dict[str, str] | None = None,
@@ -70,6 +73,7 @@ class ZeroGPUContext:
70
73
  self.exception_mapper = exception_mapper or exception_mapper_default
71
74
  self.log_emitter = log_emitter or log_emitter_default
72
75
  self.worker_contextualizer = worker_contextualizer or WorkerContextualizerDefault
76
+ self.disconnect_detector = disconnect_detector
73
77
  self.connection_state = connection_state
74
78
  self.server_port = server_port
75
79
  self.headers = headers or {}
@@ -110,5 +114,13 @@ class ZeroGPUContext:
110
114
  res = {self.server_port} if self.server_port is not None else set()
111
115
  return res | set(GlobalContext.server_ports.values())
112
116
 
117
+ @property
118
+ def disconnected(self):
119
+ if self.disconnect_detector is not None:
120
+ return self.disconnect_detector()
121
+ if self.connection_state is not None:
122
+ return not self.connection_state.alive
123
+ return False
124
+
113
125
 
114
126
  ctx_var: ContextVar[ZeroGPUContext] = ContextVar('zerogpu_context', default=ZeroGPUContext())
@@ -40,11 +40,14 @@ class ZeroGPUMiddleware:
40
40
  exception_mapper: Callable[[ZeroGPUErrorResponse, Exception | None], Exception] | None = None,
41
41
  log_emitter: Callable[[ZeroGPULog], None] | None = None,
42
42
  worker_contextualizer: Callable[[], Picklable[Callable[[], None]]] | None = None,
43
+ disconnect_detector: Callable[[], bool] | None = None,
43
44
  ):
45
+ print("ZeroGPUMiddleware.__init__")
44
46
  self.app = app
45
47
  self.exception_mapper = exception_mapper
46
48
  self.log_emitter = log_emitter
47
49
  self.worker_contextualizer = worker_contextualizer
50
+ self.disconnect_detector = disconnect_detector
48
51
 
49
52
  async def __call__(self, scope: Scope, receive: Receive, send: Send):
50
53
 
@@ -69,27 +72,33 @@ class ZeroGPUMiddleware:
69
72
  connection_state = ConnectionState()
70
73
 
71
74
  async def wrapped_receive() -> ASGIReceiveEvent:
75
+ print('wrapped_receive', connection_state.id)
72
76
  message = await asgi_receive()
77
+ print('wrapped_receive', connection_state.id, message['type'])
73
78
  if (False
74
79
  or message['type'] == 'http.disconnect'
75
80
  or message['type'] == 'websocket.disconnect'
76
81
  ):
82
+ print("\n==== disconnect =====\n", connection_state.id)
77
83
  connection_state.alive = False
78
84
  return message
79
85
 
80
- ctx_var.set(ZeroGPUContext(
86
+ token = ctx_var.set(ZeroGPUContext(
81
87
  exception_mapper=self.exception_mapper,
82
88
  log_emitter=self.log_emitter,
83
89
  worker_contextualizer=self.worker_contextualizer,
90
+ disconnect_detector=self.disconnect_detector,
84
91
  connection_state=connection_state,
85
92
  server_port=_server_port(asgi_scope),
86
93
  headers=_decode_headers(asgi_scope.get('headers', [])),
87
94
  ))
88
95
 
89
96
  try:
97
+ print("\n==== await =====\n", connection_state.id)
90
98
  await asgi_app(asgi_scope, wrapped_receive, asgi_send)
91
99
  finally:
92
- connection_state.alive = False
100
+ print("\n==== finally =====\n", connection_state.id)
101
+ ctx_var.reset(token)
93
102
 
94
103
 
95
104
  def _server_port(scope: ASGIScope):
File without changes
File without changes
File without changes