umap-project 2.6.2__py3-none-any.whl → 2.7.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.

Potentially problematic release.


This version of umap-project might be problematic. Click here for more details.

Files changed (211) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +64 -1
  3. umap/asgi.py +15 -0
  4. umap/context_processors.py +1 -0
  5. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/cs_CZ/LC_MESSAGES/django.po +96 -92
  7. umap/locale/de/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/de/LC_MESSAGES/django.po +19 -18
  9. umap/locale/en/LC_MESSAGES/django.po +47 -43
  10. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  11. umap/locale/es/LC_MESSAGES/django.po +134 -128
  12. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/fr/LC_MESSAGES/django.po +51 -47
  14. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/pt/LC_MESSAGES/django.po +64 -60
  16. umap/management/commands/clean_tilelayer.py +152 -0
  17. umap/management/commands/purge_purgatory.py +28 -0
  18. umap/models.py +27 -2
  19. umap/settings/base.py +3 -1
  20. umap/static/umap/base.css +4 -4
  21. umap/static/umap/css/contextmenu.css +6 -1
  22. umap/static/umap/css/icon.css +7 -2
  23. umap/static/umap/css/importers.css +4 -0
  24. umap/static/umap/img/16-white.svg +9 -2
  25. umap/static/umap/img/16.svg +1 -181
  26. umap/static/umap/img/24-white.svg +1 -0
  27. umap/static/umap/img/24.svg +1 -0
  28. umap/static/umap/img/importers/cadastrefr.svg +23 -0
  29. umap/static/umap/img/source/16-white.svg +10 -3
  30. umap/static/umap/img/source/16.svg +753 -197
  31. umap/static/umap/img/source/24-white.svg +3 -2
  32. umap/static/umap/img/source/24.svg +3 -2
  33. umap/static/umap/js/modules/autocomplete.js +7 -3
  34. umap/static/umap/js/modules/browser.js +55 -2
  35. umap/static/umap/js/modules/caption.js +16 -5
  36. umap/static/umap/js/modules/data/features.js +183 -8
  37. umap/static/umap/js/modules/data/layer.js +57 -40
  38. umap/static/umap/js/modules/formatter.js +3 -2
  39. umap/static/umap/js/modules/global.js +2 -0
  40. umap/static/umap/js/modules/importer.js +3 -0
  41. umap/static/umap/js/modules/importers/cadastrefr.js +62 -0
  42. umap/static/umap/js/modules/importers/communesfr.js +15 -3
  43. umap/static/umap/js/modules/permissions.js +123 -93
  44. umap/static/umap/js/modules/rendering/layers/classified.js +2 -0
  45. umap/static/umap/js/modules/rendering/ui.js +60 -213
  46. umap/static/umap/js/modules/share.js +1 -3
  47. umap/static/umap/js/modules/slideshow.js +1 -1
  48. umap/static/umap/js/modules/sync/engine.js +371 -14
  49. umap/static/umap/js/modules/sync/hlc.js +106 -0
  50. umap/static/umap/js/modules/sync/updaters.js +18 -6
  51. umap/static/umap/js/modules/sync/websocket.js +1 -1
  52. umap/static/umap/js/modules/tableeditor.js +1 -1
  53. umap/static/umap/js/modules/ui/base.js +2 -2
  54. umap/static/umap/js/modules/ui/contextmenu.js +51 -18
  55. umap/static/umap/js/modules/urls.js +5 -1
  56. umap/static/umap/js/modules/utils.js +28 -4
  57. umap/static/umap/js/umap.controls.js +76 -55
  58. umap/static/umap/js/umap.core.js +3 -3
  59. umap/static/umap/js/umap.forms.js +3 -1
  60. umap/static/umap/js/umap.js +115 -124
  61. umap/static/umap/locale/am_ET.js +2 -2
  62. umap/static/umap/locale/am_ET.json +2 -2
  63. umap/static/umap/locale/ar.js +2 -2
  64. umap/static/umap/locale/ar.json +2 -2
  65. umap/static/umap/locale/ast.js +2 -2
  66. umap/static/umap/locale/ast.json +2 -2
  67. umap/static/umap/locale/bg.js +2 -2
  68. umap/static/umap/locale/bg.json +2 -2
  69. umap/static/umap/locale/br.js +13 -4
  70. umap/static/umap/locale/br.json +13 -4
  71. umap/static/umap/locale/ca.js +30 -17
  72. umap/static/umap/locale/ca.json +30 -17
  73. umap/static/umap/locale/cs_CZ.js +89 -80
  74. umap/static/umap/locale/cs_CZ.json +89 -80
  75. umap/static/umap/locale/da.js +2 -2
  76. umap/static/umap/locale/da.json +2 -2
  77. umap/static/umap/locale/de.js +17 -8
  78. umap/static/umap/locale/de.json +17 -8
  79. umap/static/umap/locale/el.js +2 -2
  80. umap/static/umap/locale/el.json +2 -2
  81. umap/static/umap/locale/en.js +15 -4
  82. umap/static/umap/locale/en.json +15 -4
  83. umap/static/umap/locale/en_US.json +2 -2
  84. umap/static/umap/locale/es.js +338 -325
  85. umap/static/umap/locale/es.json +338 -325
  86. umap/static/umap/locale/et.js +2 -2
  87. umap/static/umap/locale/et.json +2 -2
  88. umap/static/umap/locale/eu.js +11 -4
  89. umap/static/umap/locale/eu.json +11 -4
  90. umap/static/umap/locale/fa_IR.js +11 -4
  91. umap/static/umap/locale/fa_IR.json +11 -4
  92. umap/static/umap/locale/fi.js +2 -2
  93. umap/static/umap/locale/fi.json +2 -2
  94. umap/static/umap/locale/fr.js +15 -4
  95. umap/static/umap/locale/fr.json +15 -4
  96. umap/static/umap/locale/gl.js +2 -2
  97. umap/static/umap/locale/gl.json +2 -2
  98. umap/static/umap/locale/he.js +2 -2
  99. umap/static/umap/locale/he.json +2 -2
  100. umap/static/umap/locale/hr.js +2 -2
  101. umap/static/umap/locale/hr.json +2 -2
  102. umap/static/umap/locale/hu.js +12 -5
  103. umap/static/umap/locale/hu.json +12 -5
  104. umap/static/umap/locale/id.js +2 -2
  105. umap/static/umap/locale/id.json +2 -2
  106. umap/static/umap/locale/is.js +2 -2
  107. umap/static/umap/locale/is.json +2 -2
  108. umap/static/umap/locale/it.js +2 -2
  109. umap/static/umap/locale/it.json +2 -2
  110. umap/static/umap/locale/ja.js +2 -2
  111. umap/static/umap/locale/ja.json +2 -2
  112. umap/static/umap/locale/ko.js +2 -2
  113. umap/static/umap/locale/ko.json +2 -2
  114. umap/static/umap/locale/lt.js +2 -2
  115. umap/static/umap/locale/lt.json +2 -2
  116. umap/static/umap/locale/ms.js +2 -2
  117. umap/static/umap/locale/ms.json +2 -2
  118. umap/static/umap/locale/nl.js +2 -2
  119. umap/static/umap/locale/nl.json +2 -2
  120. umap/static/umap/locale/no.js +2 -2
  121. umap/static/umap/locale/no.json +2 -2
  122. umap/static/umap/locale/pl.js +2 -2
  123. umap/static/umap/locale/pl.json +2 -2
  124. umap/static/umap/locale/pl_PL.json +2 -2
  125. umap/static/umap/locale/pt.js +19 -10
  126. umap/static/umap/locale/pt.json +19 -10
  127. umap/static/umap/locale/pt_BR.js +2 -2
  128. umap/static/umap/locale/pt_BR.json +2 -2
  129. umap/static/umap/locale/pt_PT.js +13 -4
  130. umap/static/umap/locale/pt_PT.json +13 -4
  131. umap/static/umap/locale/ro.js +2 -2
  132. umap/static/umap/locale/ro.json +2 -2
  133. umap/static/umap/locale/ru.js +2 -2
  134. umap/static/umap/locale/ru.json +2 -2
  135. umap/static/umap/locale/si.js +2 -2
  136. umap/static/umap/locale/si.json +2 -2
  137. umap/static/umap/locale/sk_SK.js +2 -2
  138. umap/static/umap/locale/sk_SK.json +2 -2
  139. umap/static/umap/locale/sl.js +2 -2
  140. umap/static/umap/locale/sl.json +2 -2
  141. umap/static/umap/locale/sr.js +2 -2
  142. umap/static/umap/locale/sr.json +2 -2
  143. umap/static/umap/locale/sv.js +2 -2
  144. umap/static/umap/locale/sv.json +2 -2
  145. umap/static/umap/locale/th_TH.js +2 -2
  146. umap/static/umap/locale/th_TH.json +2 -2
  147. umap/static/umap/locale/tr.js +2 -2
  148. umap/static/umap/locale/tr.json +2 -2
  149. umap/static/umap/locale/uk_UA.js +2 -2
  150. umap/static/umap/locale/uk_UA.json +2 -2
  151. umap/static/umap/locale/vi.js +2 -2
  152. umap/static/umap/locale/vi.json +2 -2
  153. umap/static/umap/locale/vi_VN.json +2 -2
  154. umap/static/umap/locale/zh.js +2 -2
  155. umap/static/umap/locale/zh.json +2 -2
  156. umap/static/umap/locale/zh_CN.json +2 -2
  157. umap/static/umap/locale/zh_TW.Big5.json +2 -2
  158. umap/static/umap/locale/zh_TW.js +13 -4
  159. umap/static/umap/locale/zh_TW.json +13 -4
  160. umap/static/umap/map.css +44 -29
  161. umap/static/umap/unittests/hlc.js +165 -0
  162. umap/static/umap/unittests/sync.js +321 -15
  163. umap/static/umap/unittests/utils.js +47 -0
  164. umap/static/umap/vars.css +2 -1
  165. umap/static/umap/vendors/colorbrewer/colorbrewer.js +309 -317
  166. umap/static/umap/vendors/dompurify/purify.es.js +15 -16
  167. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  168. umap/static/umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js +111 -80
  169. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js +2 -2
  170. umap/static/umap/vendors/locatecontrol/L.Control.Locate.min.js.map +1 -1
  171. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js +1 -1
  172. umap/static/umap/vendors/simple-statistics/simple-statistics.min.js.map +1 -1
  173. umap/templates/umap/css.html +0 -2
  174. umap/templates/umap/dashboard_menu.html +4 -2
  175. umap/templates/umap/js.html +0 -5
  176. umap/templates/umap/map_detail.html +2 -2
  177. umap/tests/fixtures/test_upload_data.csv +2 -2
  178. umap/tests/integration/test_anonymous_owned_map.py +1 -0
  179. umap/tests/integration/test_basics.py +1 -1
  180. umap/tests/integration/test_browser.py +69 -7
  181. umap/tests/integration/test_caption.py +3 -3
  182. umap/tests/integration/test_circles_layer.py +12 -0
  183. umap/tests/integration/test_cluster.py +53 -0
  184. umap/tests/integration/test_datalayer.py +2 -1
  185. umap/tests/integration/test_draw_polygon.py +17 -9
  186. umap/tests/integration/test_draw_polyline.py +84 -7
  187. umap/tests/integration/test_edit_datalayer.py +5 -8
  188. umap/tests/integration/test_edit_map.py +2 -2
  189. umap/tests/integration/test_edit_marker.py +1 -1
  190. umap/tests/integration/test_facets_browser.py +3 -3
  191. umap/tests/integration/test_import.py +1 -0
  192. umap/tests/integration/test_map.py +1 -0
  193. umap/tests/integration/test_owned_map.py +1 -1
  194. umap/tests/integration/test_view_marker.py +63 -0
  195. umap/tests/integration/test_view_polygon.py +12 -12
  196. umap/tests/integration/test_websocket_sync.py +65 -3
  197. umap/tests/test_clean_tilelayer.py +83 -0
  198. umap/tests/test_datalayer.py +24 -0
  199. umap/tests/test_map_views.py +20 -0
  200. umap/tests/test_purge_purgatory.py +25 -0
  201. umap/tests/test_websocket_server.py +22 -0
  202. umap/urls.py +5 -1
  203. umap/views.py +6 -3
  204. umap/websocket_server.py +130 -27
  205. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/METADATA +18 -14
  206. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/RECORD +209 -200
  207. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.css +0 -1
  208. umap/static/umap/vendors/contextmenu/leaflet.contextmenu.min.js +0 -7
  209. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/WHEEL +0 -0
  210. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/entry_points.txt +0 -0
  211. {umap_project-2.6.2.dist-info → umap_project-2.7.0.dist-info}/licenses/LICENSE +0 -0
umap/websocket_server.py CHANGED
@@ -1,70 +1,173 @@
1
1
  #!/usr/bin/env python
2
2
 
3
3
  import asyncio
4
+ import logging
5
+ import uuid
4
6
  from collections import defaultdict
5
- from typing import Literal, Optional
7
+ from typing import Literal, Optional, Union
6
8
 
7
9
  import websockets
8
10
  from django.conf import settings
9
11
  from django.core.signing import TimestampSigner
10
- from pydantic import BaseModel, ValidationError
12
+ from pydantic import BaseModel, Field, RootModel, ValidationError
11
13
  from websockets import WebSocketClientProtocol
12
14
  from websockets.server import serve
13
15
 
14
- from umap.models import Map, User # NOQA
16
+
17
+ class Connections:
18
+ def __init__(self) -> None:
19
+ self._connections: set[WebSocketClientProtocol] = set()
20
+ self._ids: dict[WebSocketClientProtocol, str] = dict()
21
+
22
+ def join(self, websocket: WebSocketClientProtocol) -> str:
23
+ self._connections.add(websocket)
24
+ _id = str(uuid.uuid4())
25
+ self._ids[websocket] = _id
26
+ return _id
27
+
28
+ def leave(self, websocket: WebSocketClientProtocol) -> None:
29
+ self._connections.remove(websocket)
30
+ del self._ids[websocket]
31
+
32
+ def get(self, id) -> WebSocketClientProtocol:
33
+ # use an iterator to stop iterating as soon as we found
34
+ return next(k for k, v in self._ids.items() if v == id)
35
+
36
+ def get_id(self, websocket: WebSocketClientProtocol):
37
+ return self._ids[websocket]
38
+
39
+ def get_other_peers(
40
+ self, websocket: WebSocketClientProtocol
41
+ ) -> set[WebSocketClientProtocol]:
42
+ return self._connections - {websocket}
43
+
44
+ def get_all_peers(self) -> set[WebSocketClientProtocol]:
45
+ return self._connections
46
+
15
47
 
16
48
  # Contains the list of websocket connections handled by this process.
17
49
  # It's a mapping of map_id to a set of the active websocket connections
18
- CONNECTIONS = defaultdict(set)
50
+ CONNECTIONS: defaultdict[int, Connections] = defaultdict(Connections)
19
51
 
20
52
 
21
- class JoinMessage(BaseModel):
22
- kind: str = "join"
53
+ class JoinRequest(BaseModel):
54
+ kind: Literal["JoinRequest"] = "JoinRequest"
23
55
  token: str
24
56
 
25
57
 
26
58
  class OperationMessage(BaseModel):
27
- kind: str = "operation"
28
- verb: str = Literal["upsert", "update", "delete"]
29
- subject: str = Literal["map", "layer", "feature"]
59
+ """Message sent from one peer to all the others"""
60
+
61
+ kind: Literal["OperationMessage"] = "OperationMessage"
62
+ verb: Literal["upsert", "update", "delete"]
63
+ subject: Literal["map", "datalayer", "feature"]
30
64
  metadata: Optional[dict] = None
31
65
  key: Optional[str] = None
32
66
 
33
67
 
68
+ class PeerMessage(BaseModel):
69
+ """Message sent from a specific peer to another one"""
70
+
71
+ kind: Literal["PeerMessage"] = "PeerMessage"
72
+ sender: str
73
+ recipient: str
74
+ # The message can be whatever the peers want. It's not checked by the server.
75
+ message: dict
76
+
77
+
78
+ class ServerRequest(BaseModel):
79
+ """A request towards the server"""
80
+
81
+ kind: Literal["Server"] = "Server"
82
+ action: Literal["list-peers"]
83
+
84
+
85
+ class Request(RootModel):
86
+ """Any message coming from the websocket should be one of these, and will be rejected otherwise."""
87
+
88
+ root: Union[ServerRequest, PeerMessage, OperationMessage] = Field(
89
+ discriminator="kind"
90
+ )
91
+
92
+
93
+ class JoinResponse(BaseModel):
94
+ """Server response containing the list of peers"""
95
+
96
+ kind: Literal["JoinResponse"] = "JoinResponse"
97
+ peers: list
98
+ uuid: str
99
+
100
+
101
+ class ListPeersResponse(BaseModel):
102
+ kind: Literal["ListPeersResponse"] = "ListPeersResponse"
103
+ peers: list
104
+
105
+
34
106
  async def join_and_listen(
35
107
  map_id: int, permissions: list, user: str | int, websocket: WebSocketClientProtocol
36
108
  ):
37
- """Join a "room" whith other connected peers.
109
+ """Join a "room" with other connected peers, and wait for messages."""
110
+ logging.debug(f"{user} joined room #{map_id}")
111
+ connections: Connections = CONNECTIONS[map_id]
112
+ _id: str = connections.join(websocket)
113
+
114
+ # Assign an ID to the joining peer and return it the list of connected peers.
115
+ peers: list[WebSocketClientProtocol] = [
116
+ connections.get_id(p) for p in connections.get_all_peers()
117
+ ]
118
+ response = JoinResponse(uuid=_id, peers=peers)
119
+ await websocket.send(response.model_dump_json())
120
+
121
+ # Notify all other peers of the new list of connected peers.
122
+ message = ListPeersResponse(peers=peers)
123
+ websockets.broadcast(
124
+ connections.get_other_peers(websocket), message.model_dump_json()
125
+ )
38
126
 
39
- New messages will be broadcasted to other connected peers.
40
- """
41
- print(f"{user} joined room #{map_id}")
42
- CONNECTIONS[map_id].add(websocket)
43
127
  try:
44
128
  async for raw_message in websocket:
45
- # recompute the peers-list at the time of message-sending.
129
+ # recompute the peers list at the time of message-sending.
46
130
  # as doing so beforehand would miss new connections
47
- peers = CONNECTIONS[map_id] - {websocket}
48
- # Only relay valid "operation" messages
131
+ other_peers = connections.get_other_peers(websocket)
49
132
  try:
50
- OperationMessage.model_validate_json(raw_message)
51
- websockets.broadcast(peers, raw_message)
133
+ incoming = Request.model_validate_json(raw_message)
52
134
  except ValidationError as e:
53
- error = f"An error occurred when receiving this message: {raw_message}"
54
- print(error, e)
135
+ error = f"An error occurred when receiving the following message: {raw_message!r}"
136
+ logging.error(error, e)
137
+ else:
138
+ match incoming.root:
139
+ # Broadcast all operation messages to connected peers
140
+ case OperationMessage():
141
+ websockets.broadcast(other_peers, raw_message)
142
+
143
+ # Send peer messages to the proper peer
144
+ case PeerMessage(recipient=_id):
145
+ peer = connections.get(_id)
146
+ if peer:
147
+ await peer.send(raw_message)
148
+
55
149
  finally:
56
- CONNECTIONS[map_id].remove(websocket)
150
+ # On disconnect, remove the connection from the pool
151
+ connections.leave(websocket)
152
+
153
+ # TODO: refactor this in a separate method.
154
+ # Notify all other peers of the new list of connected peers.
155
+ peers = [connections.get_id(p) for p in connections.get_all_peers()]
156
+ message = ListPeersResponse(peers=peers)
157
+ websockets.broadcast(
158
+ connections.get_other_peers(websocket), message.model_dump_json()
159
+ )
57
160
 
58
161
 
59
- async def handler(websocket):
162
+ async def handler(websocket: WebSocketClientProtocol):
60
163
  """Main WebSocket handler.
61
164
 
62
- If permissions are granted, let the peer enter a room.
165
+ Check if the permission is granted and let the peer enter a room.
63
166
  """
64
167
  raw_message = await websocket.recv()
65
168
 
66
169
  # The first event should always be 'join'
67
- message: JoinMessage = JoinMessage.model_validate_json(raw_message)
170
+ message: JoinRequest = JoinRequest.model_validate_json(raw_message)
68
171
  signed = TimestampSigner().unsign_object(message.token, max_age=30)
69
172
  user, map_id, permissions = signed.values()
70
173
 
@@ -73,7 +176,7 @@ async def handler(websocket):
73
176
  await join_and_listen(map_id, permissions, user, websocket)
74
177
 
75
178
 
76
- def run(host, port):
179
+ def run(host: str, port: int):
77
180
  if not settings.WEBSOCKET_ENABLED:
78
181
  msg = (
79
182
  "WEBSOCKET_ENABLED should be set to True to run the WebSocket Server. "
@@ -86,7 +189,7 @@ def run(host, port):
86
189
 
87
190
  async def _serve():
88
191
  async with serve(handler, host, port):
89
- print(f"Waiting for connections on {host}:{port}")
192
+ logging.debug(f"Waiting for connections on {host}:{port}")
90
193
  await asyncio.Future() # run forever
91
194
 
92
195
  asyncio.run(_serve())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: umap-project
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Create maps with OpenStreetMap layers in a minute and embed them in your site.
5
5
  Author-email: Yohan Boniface <yb@enix.org>
6
6
  Maintainer-email: David Larlet <david@larlet.fr>
@@ -19,35 +19,39 @@ Requires-Python: >=3.10
19
19
  Requires-Dist: django-agnocomplete==2.2.0
20
20
  Requires-Dist: django-environ==0.11.2
21
21
  Requires-Dist: django-probes==1.7.0
22
- Requires-Dist: django==5.1.1
23
- Requires-Dist: pillow==10.4.0
24
- Requires-Dist: psycopg==3.2.1
25
- Requires-Dist: pydantic==2.9.1
26
- Requires-Dist: rcssmin==1.1.2
22
+ Requires-Dist: django==5.1.2
23
+ Requires-Dist: pillow==11.0.0
24
+ Requires-Dist: psycopg==3.2.3
25
+ Requires-Dist: rcssmin==1.1.3
27
26
  Requires-Dist: requests==2.32.3
28
- Requires-Dist: rjsmin==1.2.2
27
+ Requires-Dist: rjsmin==1.2.3
29
28
  Requires-Dist: social-auth-app-django==5.4.2
30
29
  Requires-Dist: social-auth-core==4.5.4
31
- Requires-Dist: websockets==13.0.1
32
30
  Provides-Extra: dev
33
31
  Requires-Dist: djlint==1.35.2; extra == 'dev'
34
- Requires-Dist: hatch==1.12.0; extra == 'dev'
32
+ Requires-Dist: hatch==1.13.0; extra == 'dev'
35
33
  Requires-Dist: isort==5.13.2; extra == 'dev'
36
- Requires-Dist: mkdocs-material==9.5.34; extra == 'dev'
34
+ Requires-Dist: mkdocs-material==9.5.42; extra == 'dev'
37
35
  Requires-Dist: mkdocs-static-i18n==1.2.3; extra == 'dev'
38
36
  Requires-Dist: mkdocs==1.6.1; extra == 'dev'
39
- Requires-Dist: pymdown-extensions==10.9; extra == 'dev'
40
- Requires-Dist: ruff==0.6.4; extra == 'dev'
37
+ Requires-Dist: pymdown-extensions==10.11.2; extra == 'dev'
38
+ Requires-Dist: ruff==0.7.0; extra == 'dev'
41
39
  Requires-Dist: vermin==1.6.0; extra == 'dev'
42
40
  Provides-Extra: docker
43
- Requires-Dist: uwsgi==2.0.26; extra == 'docker'
41
+ Requires-Dist: uwsgi==2.0.27; extra == 'docker'
42
+ Provides-Extra: sync
43
+ Requires-Dist: channels==4.1.0; extra == 'sync'
44
+ Requires-Dist: daphne==4.1.2; extra == 'sync'
45
+ Requires-Dist: pydantic==2.9.2; extra == 'sync'
46
+ Requires-Dist: websockets==13.1; extra == 'sync'
44
47
  Provides-Extra: test
45
48
  Requires-Dist: factory-boy==3.3.1; extra == 'test'
46
49
  Requires-Dist: playwright>=1.39; extra == 'test'
47
50
  Requires-Dist: pytest-django==4.9.0; extra == 'test'
48
51
  Requires-Dist: pytest-playwright==0.5.2; extra == 'test'
52
+ Requires-Dist: pytest-rerunfailures==14.0; extra == 'test'
49
53
  Requires-Dist: pytest-xdist<4,>=3.5.0; extra == 'test'
50
- Requires-Dist: pytest==8.3.2; extra == 'test'
54
+ Requires-Dist: pytest==8.3.3; extra == 'test'
51
55
  Description-Content-Type: text/markdown
52
56
 
53
57
  # uMap project