tina4-python 0.2.72__tar.gz → 0.2.74__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.
- {tina4_python-0.2.72 → tina4_python-0.2.74}/PKG-INFO +15 -1
- {tina4_python-0.2.72 → tina4_python-0.2.74}/README.md +12 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/pyproject.toml +3 -1
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/DatabaseResult.py +8 -5
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/ORM.py +2 -2
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Request.py +3 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Router.py +2 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Webserver.py +37 -21
- tina4_python-0.2.74/tina4_python/Websocket.py +39 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/__init__.py +35 -23
- tina4_python-0.2.74/tina4_python/public/js/reconnecting-websocket.js +365 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Auth.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Constant.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Database.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Debug.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Env.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Localization.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Messages.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/MiddleWare.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Migration.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Queue.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Response.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Session.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/ShellColors.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Swagger.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/Template.py +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/messages.pot +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/css/readme.md +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/403.png +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/404.png +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/500.png +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/logo.png +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/images/readme.md +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/js/readme.md +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/js/tina4helper.js +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/templates/readme.md +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: tina4-python
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.74
|
|
4
4
|
Summary: Tina4Python - This is not another framework for Python
|
|
5
5
|
Author: Andre van Zuydam
|
|
6
6
|
Author-email: andrevanzuydam@gmail.com
|
|
@@ -16,8 +16,10 @@ Requires-Dist: cryptography (>=42.0.5,<43.0.0)
|
|
|
16
16
|
Requires-Dist: libsass (>=0.22.0,<0.23.0)
|
|
17
17
|
Requires-Dist: litequeue (>=0.9,<0.10)
|
|
18
18
|
Requires-Dist: mypy (>=0.991,<0.992)
|
|
19
|
+
Requires-Dist: poetry-plugin-export (>=1.9.0,<2.0.0)
|
|
19
20
|
Requires-Dist: pyjwt (>=2.8.0,<3.0.0)
|
|
20
21
|
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
22
|
+
Requires-Dist: simple-websocket (>=1.1.0,<2.0.0)
|
|
21
23
|
Requires-Dist: watchdog (>=4.0.0,<5.0.0)
|
|
22
24
|
Description-Content-Type: text/markdown
|
|
23
25
|
|
|
@@ -432,3 +434,15 @@ docker run -d --hostname=my-kafka --name=some-kafka -p 9092:9092 apache/kafka
|
|
|
432
434
|
docker run -d --hostname=my-rabbit --name=some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3
|
|
433
435
|
```
|
|
434
436
|
|
|
437
|
+
## Websocket Testing
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
brew install websocat
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
```python
|
|
444
|
+
websocat http://localhost:7145/websocket
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
|
|
@@ -408,3 +408,15 @@ docker run -d --hostname=my-kafka --name=some-kafka -p 9092:9092 apache/kafka
|
|
|
408
408
|
```bash
|
|
409
409
|
docker run -d --hostname=my-rabbit --name=some-rabbit -p 15672:15672 -p 5672:5672 rabbitmq:3
|
|
410
410
|
```
|
|
411
|
+
|
|
412
|
+
## Websocket Testing
|
|
413
|
+
|
|
414
|
+
```bash
|
|
415
|
+
brew install websocat
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
```python
|
|
419
|
+
websocat http://localhost:7145/websocket
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "tina4-python"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.74"
|
|
4
4
|
description = "Tina4Python - This is not another framework for Python"
|
|
5
5
|
authors = ["Andre van Zuydam <andrevanzuydam@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -18,6 +18,8 @@ cryptography = "^42.0.5"
|
|
|
18
18
|
watchdog = "^4.0.0"
|
|
19
19
|
bcrypt = "^4.1.3"
|
|
20
20
|
litequeue = "^0.9"
|
|
21
|
+
simple-websocket = "^1.1.0"
|
|
22
|
+
poetry-plugin-export = "^1.9.0"
|
|
21
23
|
|
|
22
24
|
[tool.poetry.group.dev.dependencies]
|
|
23
25
|
flake8 = "^7.0.0"
|
|
@@ -54,7 +54,7 @@ class DatabaseResult:
|
|
|
54
54
|
|
|
55
55
|
return {"recordsTotal": self.total_count, "recordsOffset": self.skip, "recordCount": self.count, "recordsFiltered": self.total_count, "fields": self.columns, "data": self.to_array(), "dataError": self.error}
|
|
56
56
|
|
|
57
|
-
def to_array(self):
|
|
57
|
+
def to_array(self, _filter=None):
|
|
58
58
|
"""
|
|
59
59
|
Creates an array or list of the items
|
|
60
60
|
:return:
|
|
@@ -78,17 +78,20 @@ class DatabaseResult:
|
|
|
78
78
|
else:
|
|
79
79
|
json_record[key] = record[key]
|
|
80
80
|
|
|
81
|
+
if _filter is not None:
|
|
82
|
+
json_record = _filter(json_record)
|
|
83
|
+
|
|
81
84
|
json_records.append(json_record)
|
|
82
85
|
|
|
83
86
|
return json_records
|
|
84
87
|
else:
|
|
85
88
|
return []
|
|
86
89
|
|
|
87
|
-
def to_list(self):
|
|
88
|
-
return self.to_array()
|
|
90
|
+
def to_list(self, _filter=None):
|
|
91
|
+
return self.to_array(_filter)
|
|
89
92
|
|
|
90
|
-
def to_json(self):
|
|
91
|
-
return json.dumps(self.to_array())
|
|
93
|
+
def to_json(self, _filter=None):
|
|
94
|
+
return json.dumps(self.to_array(_filter))
|
|
92
95
|
|
|
93
96
|
def __getitem__(self, item):
|
|
94
97
|
if item < len(self.records):
|
|
@@ -280,7 +280,7 @@ class ORM:
|
|
|
280
280
|
if init_object is not None:
|
|
281
281
|
self.__populate_orm(init_object)
|
|
282
282
|
|
|
283
|
-
Debug("Checking for", self.__table_name__, TINA4_LOG_INFO)
|
|
283
|
+
# Debug("Checking for", self.__table_name__, TINA4_LOG_INFO)
|
|
284
284
|
if self.__dba__:
|
|
285
285
|
self.__table_exists = self.__dba__.table_exists(self.__table_name__)
|
|
286
286
|
if not self.__table_exists:
|
|
@@ -290,7 +290,7 @@ class ORM:
|
|
|
290
290
|
with open(filename, "w") as f:
|
|
291
291
|
f.write(sql)
|
|
292
292
|
f.close()
|
|
293
|
-
Debug("Table Exists", self.__table_exists, TINA4_LOG_INFO)
|
|
293
|
+
# Debug("Table Exists", self.__table_exists, TINA4_LOG_INFO)
|
|
294
294
|
else:
|
|
295
295
|
self.__table_exists = False
|
|
296
296
|
|
|
@@ -148,6 +148,8 @@ class Router:
|
|
|
148
148
|
Request.raw_request = request["raw_request"] if "raw_request" in request else None
|
|
149
149
|
Request.raw_content = request["raw_content"] if "raw_content" in request else None
|
|
150
150
|
Request.url = url
|
|
151
|
+
Request.transport = request["transport"] if "transport" in request else None
|
|
152
|
+
Request.asgi_response = request["asgi_response"] if "asgi_response" in request else None
|
|
151
153
|
|
|
152
154
|
tina4_python.tina4_current_request = Request
|
|
153
155
|
|
|
@@ -23,7 +23,7 @@ from tina4_python.Template import Template
|
|
|
23
23
|
|
|
24
24
|
def is_int(v):
|
|
25
25
|
try:
|
|
26
|
-
f=int(v)
|
|
26
|
+
f = int(v)
|
|
27
27
|
except ValueError:
|
|
28
28
|
return False
|
|
29
29
|
return True
|
|
@@ -107,7 +107,7 @@ class Webserver:
|
|
|
107
107
|
self.send_header("Connection", "Keep-Alive", headers)
|
|
108
108
|
self.send_header("Keep-Alive", "timeout=5, max=30", headers)
|
|
109
109
|
|
|
110
|
-
async def get_response(self, method, asgi_response=False):
|
|
110
|
+
async def get_response(self, method, transport, asgi_response=False):
|
|
111
111
|
"""
|
|
112
112
|
Get response
|
|
113
113
|
:param method: GET, POST, PATCH, DELETE, PUT
|
|
@@ -152,13 +152,13 @@ class Webserver:
|
|
|
152
152
|
if isinstance(start_var, dict) and var_name in start_var:
|
|
153
153
|
start_var = start_var[var_name]
|
|
154
154
|
else:
|
|
155
|
-
if counter+1 < len(var_names) and is_int(var_names[counter+1])
|
|
155
|
+
if counter + 1 < len(var_names) and is_int(var_names[counter + 1]):
|
|
156
156
|
if var_name not in start_var:
|
|
157
157
|
start_var[var_name] = []
|
|
158
158
|
start_var = start_var[var_name]
|
|
159
159
|
else:
|
|
160
|
-
if counter-1 > 0 and is_int(var_names[counter-1]):
|
|
161
|
-
index = int(var_names[counter-1])
|
|
160
|
+
if counter - 1 > 0 and is_int(var_names[counter - 1]):
|
|
161
|
+
index = int(var_names[counter - 1])
|
|
162
162
|
new_value = {var_name: value}
|
|
163
163
|
if index in range(len(start_var)):
|
|
164
164
|
start_var[index].update(new_value)
|
|
@@ -169,7 +169,7 @@ class Webserver:
|
|
|
169
169
|
start_var = start_var[index]
|
|
170
170
|
else:
|
|
171
171
|
if isinstance(start_var, dict):
|
|
172
|
-
if counter+1 == len(var_names):
|
|
172
|
+
if counter + 1 == len(var_names):
|
|
173
173
|
start_var[var_name] = value
|
|
174
174
|
else:
|
|
175
175
|
start_var[var_name] = {}
|
|
@@ -186,7 +186,8 @@ class Webserver:
|
|
|
186
186
|
body = None
|
|
187
187
|
|
|
188
188
|
request = {"params": params, "body": body, "raw_data": self.request, "url": self.path, "session": self.session,
|
|
189
|
-
"headers": self.lowercase_headers, "raw_request": self.request_raw, "raw_content": self.content_raw
|
|
189
|
+
"headers": self.lowercase_headers, "raw_request": self.request_raw, "raw_content": self.content_raw,
|
|
190
|
+
"transport": transport, "asgi_response": asgi_response}
|
|
190
191
|
|
|
191
192
|
tina4_python.tina4_current_request = request
|
|
192
193
|
|
|
@@ -230,9 +231,8 @@ class Webserver:
|
|
|
230
231
|
return headers.encode()
|
|
231
232
|
|
|
232
233
|
async def run_server(self):
|
|
233
|
-
server = await asyncio.start_server(self.handle_client, self.host_name, self.port)
|
|
234
|
-
|
|
235
|
-
await server.serve_forever()
|
|
234
|
+
self.server = await asyncio.start_server(self.handle_client, self.host_name, self.port)
|
|
235
|
+
await self.server.serve_forever()
|
|
236
236
|
|
|
237
237
|
async def get_data(self, reader):
|
|
238
238
|
try:
|
|
@@ -291,6 +291,7 @@ class Webserver:
|
|
|
291
291
|
self.method = protocol[0]
|
|
292
292
|
self.path = protocol[1]
|
|
293
293
|
|
|
294
|
+
|
|
294
295
|
method_list = [Constant.TINA4_GET, Constant.TINA4_DELETE, Constant.TINA4_PUT, Constant.TINA4_ANY,
|
|
295
296
|
Constant.TINA4_POST, Constant.TINA4_PATCH, Constant.TINA4_OPTIONS]
|
|
296
297
|
|
|
@@ -298,7 +299,7 @@ class Webserver:
|
|
|
298
299
|
|
|
299
300
|
request_handled = False
|
|
300
301
|
# return static content asap
|
|
301
|
-
if self.method == "GET":
|
|
302
|
+
if self.method == "GET" and "sec-websocket-key" not in self.lowercase_headers:
|
|
302
303
|
# split URL and extract query string
|
|
303
304
|
url = Router.clean_url(self.path)
|
|
304
305
|
url_parts = url.split('?')
|
|
@@ -314,14 +315,16 @@ class Webserver:
|
|
|
314
315
|
self.send_header("Content-Type", mime_type, headers)
|
|
315
316
|
await self.send_basic_headers(headers)
|
|
316
317
|
content = file.read()
|
|
317
|
-
headers =
|
|
318
|
+
headers = await self.get_headers(headers, self.response_protocol, HTTP_OK)
|
|
318
319
|
writer.write(headers + content)
|
|
319
320
|
await writer.drain()
|
|
321
|
+
await writer.close()
|
|
320
322
|
request_handled = True
|
|
321
323
|
|
|
322
324
|
if not request_handled:
|
|
323
325
|
# parse cookies
|
|
324
326
|
cookie_list = {}
|
|
327
|
+
content = ""
|
|
325
328
|
if "cookie" in self.lowercase_headers:
|
|
326
329
|
cookie_list_temp = self.lowercase_headers["cookie"].split(";")
|
|
327
330
|
for cookie_value in cookie_list_temp:
|
|
@@ -341,17 +344,27 @@ class Webserver:
|
|
|
341
344
|
else:
|
|
342
345
|
self.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = self.session.start()
|
|
343
346
|
|
|
344
|
-
if
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
347
|
+
if "sec-websocket-key" not in self.lowercase_headers:
|
|
348
|
+
try:
|
|
349
|
+
if self.method != "" and contains_method:
|
|
350
|
+
content = await (self.get_response(self.method, writer))
|
|
351
|
+
if content != "":
|
|
352
|
+
writer.write(content)
|
|
353
|
+
await writer.drain()
|
|
354
|
+
writer.close()
|
|
355
|
+
except BrokenPipeError as e:
|
|
356
|
+
# socket got terminated
|
|
357
|
+
pass
|
|
358
|
+
else:
|
|
359
|
+
# for sockets
|
|
360
|
+
await (self.get_response(self.method, writer))
|
|
361
|
+
|
|
348
362
|
|
|
349
|
-
writer.close()
|
|
350
363
|
except Exception as e:
|
|
351
364
|
error_string = tina4_python.global_exception_handler(e)
|
|
352
365
|
headers = []
|
|
353
366
|
await self.send_basic_headers(headers)
|
|
354
|
-
headers =
|
|
367
|
+
headers = await self.get_headers(headers, self.response_protocol, HTTP_SERVER_ERROR)
|
|
355
368
|
url = Router.clean_url(self.path)
|
|
356
369
|
|
|
357
370
|
content_type = "text/html"
|
|
@@ -359,9 +372,12 @@ class Webserver:
|
|
|
359
372
|
content_type = self.lowercase_headers["content-type"].lower()
|
|
360
373
|
|
|
361
374
|
if content_type == "application/json":
|
|
362
|
-
html = json.dumps({"error": "500 - Internal Server Error",
|
|
375
|
+
html = json.dumps({"error": "500 - Internal Server Error",
|
|
376
|
+
"data": {"server": {"url": url}, "error_message": error_string}})
|
|
363
377
|
else:
|
|
364
|
-
html = Template.render_twig_template("errors/500.twig",
|
|
378
|
+
html = Template.render_twig_template("errors/500.twig",
|
|
379
|
+
{"server": {"url": url}, "error_message": error_string})
|
|
380
|
+
|
|
365
381
|
writer.write(headers + html.encode())
|
|
366
382
|
await writer.drain()
|
|
367
383
|
writer.close()
|
|
@@ -382,6 +398,7 @@ class Webserver:
|
|
|
382
398
|
self.port = port
|
|
383
399
|
self.router_handler = None
|
|
384
400
|
self.running = False
|
|
401
|
+
self.server = None
|
|
385
402
|
|
|
386
403
|
async def serve_forever(self):
|
|
387
404
|
await self.run_server()
|
|
@@ -389,4 +406,3 @@ class Webserver:
|
|
|
389
406
|
def server_close(self):
|
|
390
407
|
self.running = False
|
|
391
408
|
self.server_socket.close()
|
|
392
|
-
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Tina4 - This is not a 4ramework.
|
|
3
|
+
# Copy-right 2007 - current Tina4
|
|
4
|
+
# License: MIT https://opensource.org/licenses/MIT
|
|
5
|
+
#
|
|
6
|
+
# flake8: noqa: E501
|
|
7
|
+
import importlib
|
|
8
|
+
import os
|
|
9
|
+
from tina4_python.Debug import Debug
|
|
10
|
+
|
|
11
|
+
class Websocket:
|
|
12
|
+
"""
|
|
13
|
+
Websocket class which wraps simple_websocket library
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, request):
|
|
16
|
+
try:
|
|
17
|
+
self.server = importlib.import_module("simple_websocket")
|
|
18
|
+
self.request = request
|
|
19
|
+
except Exception as e:
|
|
20
|
+
Debug.error("Error creating Websocket, perhaps you need to install simple_websocket ?", e)
|
|
21
|
+
|
|
22
|
+
async def connection(self):
|
|
23
|
+
"""
|
|
24
|
+
Returns a websocket connection
|
|
25
|
+
:return:
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
if self.request.asgi_response:
|
|
29
|
+
connection = await self.server.AioServer.accept(asgi=self.request.transport)
|
|
30
|
+
else:
|
|
31
|
+
if os.name == "nt":
|
|
32
|
+
connection = await self.server.AioServer.accept(sock=self.request.transport.get_extra_info('socket'), headers=self.request.headers)
|
|
33
|
+
else:
|
|
34
|
+
connection = await self.server.AioServer.accept(sock=self.request.transport.get_extra_info('socket').dup(), headers=self.request.headers)
|
|
35
|
+
|
|
36
|
+
return connection
|
|
37
|
+
except Exception as e:
|
|
38
|
+
Debug.error("Could not establish a socket connection:", str(e))
|
|
39
|
+
return None
|
|
@@ -13,6 +13,8 @@ import sys
|
|
|
13
13
|
import traceback
|
|
14
14
|
import sass
|
|
15
15
|
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from safety.scan.command import scan_system_app
|
|
16
18
|
from watchdog.observers import Observer
|
|
17
19
|
from watchdog.events import FileSystemEventHandler, FileSystemEvent
|
|
18
20
|
from tina4_python.Router import get
|
|
@@ -263,8 +265,12 @@ async def app(scope, receive, send):
|
|
|
263
265
|
:return:
|
|
264
266
|
"""
|
|
265
267
|
body = b""
|
|
266
|
-
while True and scope['type'] == 'http':
|
|
267
|
-
|
|
268
|
+
while True and scope['type'] == 'http' or scope['type'] == 'websocket':
|
|
269
|
+
if scope['type'] != 'websocket':
|
|
270
|
+
message = await receive()
|
|
271
|
+
else:
|
|
272
|
+
message = {'type': 'websocket'}
|
|
273
|
+
|
|
268
274
|
if "body" in message:
|
|
269
275
|
body += message["body"]
|
|
270
276
|
if message['type'] == 'lifespan.startup':
|
|
@@ -273,7 +279,7 @@ async def app(scope, receive, send):
|
|
|
273
279
|
elif message['type'] == 'lifespan.shutdown':
|
|
274
280
|
await send({'type': 'lifespan.shutdown.complete'})
|
|
275
281
|
return
|
|
276
|
-
elif message["type"] == "http.disconnect":
|
|
282
|
+
elif message["type"] == "http.disconnect" or message["type"] == "websocket.disconnect":
|
|
277
283
|
return
|
|
278
284
|
elif not message.get("more_body"):
|
|
279
285
|
webserver = Webserver(scope["server"][0], scope["server"][1])
|
|
@@ -293,8 +299,13 @@ async def app(scope, receive, send):
|
|
|
293
299
|
|
|
294
300
|
webserver.headers = parsed_headers
|
|
295
301
|
webserver.lowercase_headers = parsed_headers_lowercase
|
|
302
|
+
|
|
296
303
|
webserver.path = scope["path"]+"?"+scope["query_string"].decode()
|
|
297
|
-
|
|
304
|
+
|
|
305
|
+
if "method" in scope:
|
|
306
|
+
webserver.method = scope["method"]
|
|
307
|
+
else:
|
|
308
|
+
webserver.method = "GET"
|
|
298
309
|
|
|
299
310
|
if message["type"] == "http.request":
|
|
300
311
|
webserver.content_raw = body
|
|
@@ -324,26 +335,27 @@ async def app(scope, receive, send):
|
|
|
324
335
|
else:
|
|
325
336
|
webserver.cookies[os.getenv("TINA4_SESSION", "PY_SESS")] = webserver.session.start()
|
|
326
337
|
|
|
327
|
-
tina4_response, tina4_headers = await webserver.get_response(
|
|
338
|
+
tina4_response, tina4_headers = await webserver.get_response(webserver.method, (scope,receive,send),True)
|
|
328
339
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
header
|
|
332
|
-
|
|
340
|
+
if message["type"] != "websocket":
|
|
341
|
+
response_headers = []
|
|
342
|
+
for header in tina4_headers:
|
|
343
|
+
header = header.split(":")
|
|
344
|
+
response_headers.append([header[0].strip().encode(), header[1].strip().encode()])
|
|
333
345
|
|
|
334
|
-
await send({
|
|
335
|
-
'type': 'http.response.start',
|
|
336
|
-
'status': tina4_response.http_code,
|
|
337
|
-
'headers': response_headers,
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
if isinstance(tina4_response.content, str):
|
|
341
346
|
await send({
|
|
342
|
-
'type': 'http.response.
|
|
343
|
-
'
|
|
344
|
-
|
|
345
|
-
else:
|
|
346
|
-
await send({
|
|
347
|
-
'type': 'http.response.body',
|
|
348
|
-
'body': tina4_response.content,
|
|
347
|
+
'type': 'http.response.start',
|
|
348
|
+
'status': tina4_response.http_code,
|
|
349
|
+
'headers': response_headers,
|
|
349
350
|
})
|
|
351
|
+
|
|
352
|
+
if isinstance(tina4_response.content, str):
|
|
353
|
+
await send({
|
|
354
|
+
'type': 'http.response.body',
|
|
355
|
+
'body': tina4_response.content.encode(),
|
|
356
|
+
})
|
|
357
|
+
else:
|
|
358
|
+
await send({
|
|
359
|
+
'type': 'http.response.body',
|
|
360
|
+
'body': tina4_response.content,
|
|
361
|
+
})
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// MIT License:
|
|
2
|
+
//
|
|
3
|
+
// Copyright (c) 2010-2012, Joe Walnes
|
|
4
|
+
//
|
|
5
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
// of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
// in the Software without restriction, including without limitation the rights
|
|
8
|
+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
// copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
// furnished to do so, subject to the following conditions:
|
|
11
|
+
//
|
|
12
|
+
// The above copyright notice and this permission notice shall be included in
|
|
13
|
+
// all copies or substantial portions of the Software.
|
|
14
|
+
//
|
|
15
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
// THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* This behaves like a WebSocket in every way, except if it fails to connect,
|
|
25
|
+
* or it gets disconnected, it will repeatedly poll until it successfully connects
|
|
26
|
+
* again.
|
|
27
|
+
*
|
|
28
|
+
* It is API compatible, so when you have:
|
|
29
|
+
* ws = new WebSocket('ws://....');
|
|
30
|
+
* you can replace with:
|
|
31
|
+
* ws = new ReconnectingWebSocket('ws://....');
|
|
32
|
+
*
|
|
33
|
+
* The event stream will typically look like:
|
|
34
|
+
* onconnecting
|
|
35
|
+
* onopen
|
|
36
|
+
* onmessage
|
|
37
|
+
* onmessage
|
|
38
|
+
* onclose // lost connection
|
|
39
|
+
* onconnecting
|
|
40
|
+
* onopen // sometime later...
|
|
41
|
+
* onmessage
|
|
42
|
+
* onmessage
|
|
43
|
+
* etc...
|
|
44
|
+
*
|
|
45
|
+
* It is API compatible with the standard WebSocket API, apart from the following members:
|
|
46
|
+
*
|
|
47
|
+
* - `bufferedAmount`
|
|
48
|
+
* - `extensions`
|
|
49
|
+
* - `binaryType`
|
|
50
|
+
*
|
|
51
|
+
* Latest version: https://github.com/joewalnes/reconnecting-websocket/
|
|
52
|
+
* - Joe Walnes
|
|
53
|
+
*
|
|
54
|
+
* Syntax
|
|
55
|
+
* ======
|
|
56
|
+
* var socket = new ReconnectingWebSocket(url, protocols, options);
|
|
57
|
+
*
|
|
58
|
+
* Parameters
|
|
59
|
+
* ==========
|
|
60
|
+
* url - The url you are connecting to.
|
|
61
|
+
* protocols - Optional string or array of protocols.
|
|
62
|
+
* options - See below
|
|
63
|
+
*
|
|
64
|
+
* Options
|
|
65
|
+
* =======
|
|
66
|
+
* Options can either be passed upon instantiation or set after instantiation:
|
|
67
|
+
*
|
|
68
|
+
* var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 });
|
|
69
|
+
*
|
|
70
|
+
* or
|
|
71
|
+
*
|
|
72
|
+
* var socket = new ReconnectingWebSocket(url);
|
|
73
|
+
* socket.debug = true;
|
|
74
|
+
* socket.reconnectInterval = 4000;
|
|
75
|
+
*
|
|
76
|
+
* debug
|
|
77
|
+
* - Whether this instance should log debug messages. Accepts true or false. Default: false.
|
|
78
|
+
*
|
|
79
|
+
* automaticOpen
|
|
80
|
+
* - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close().
|
|
81
|
+
*
|
|
82
|
+
* reconnectInterval
|
|
83
|
+
* - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000.
|
|
84
|
+
*
|
|
85
|
+
* maxReconnectInterval
|
|
86
|
+
* - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000.
|
|
87
|
+
*
|
|
88
|
+
* reconnectDecay
|
|
89
|
+
* - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5.
|
|
90
|
+
*
|
|
91
|
+
* timeoutInterval
|
|
92
|
+
* - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000.
|
|
93
|
+
*
|
|
94
|
+
*/
|
|
95
|
+
(function (global, factory) {
|
|
96
|
+
if (typeof define === 'function' && define.amd) {
|
|
97
|
+
define([], factory);
|
|
98
|
+
} else if (typeof module !== 'undefined' && module.exports){
|
|
99
|
+
module.exports = factory();
|
|
100
|
+
} else {
|
|
101
|
+
global.ReconnectingWebSocket = factory();
|
|
102
|
+
}
|
|
103
|
+
})(this, function () {
|
|
104
|
+
|
|
105
|
+
if (!('WebSocket' in window)) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function ReconnectingWebSocket(url, protocols, options) {
|
|
110
|
+
|
|
111
|
+
// Default settings
|
|
112
|
+
var settings = {
|
|
113
|
+
|
|
114
|
+
/** Whether this instance should log debug messages. */
|
|
115
|
+
debug: false,
|
|
116
|
+
|
|
117
|
+
/** Whether or not the websocket should attempt to connect immediately upon instantiation. */
|
|
118
|
+
automaticOpen: true,
|
|
119
|
+
|
|
120
|
+
/** The number of milliseconds to delay before attempting to reconnect. */
|
|
121
|
+
reconnectInterval: 1000,
|
|
122
|
+
/** The maximum number of milliseconds to delay a reconnection attempt. */
|
|
123
|
+
maxReconnectInterval: 30000,
|
|
124
|
+
/** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */
|
|
125
|
+
reconnectDecay: 1.5,
|
|
126
|
+
|
|
127
|
+
/** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */
|
|
128
|
+
timeoutInterval: 2000,
|
|
129
|
+
|
|
130
|
+
/** The maximum number of reconnection attempts to make. Unlimited if null. */
|
|
131
|
+
maxReconnectAttempts: null,
|
|
132
|
+
|
|
133
|
+
/** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */
|
|
134
|
+
binaryType: 'blob'
|
|
135
|
+
}
|
|
136
|
+
if (!options) { options = {}; }
|
|
137
|
+
|
|
138
|
+
// Overwrite and define settings with options if they exist.
|
|
139
|
+
for (var key in settings) {
|
|
140
|
+
if (typeof options[key] !== 'undefined') {
|
|
141
|
+
this[key] = options[key];
|
|
142
|
+
} else {
|
|
143
|
+
this[key] = settings[key];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// These should be treated as read-only properties
|
|
148
|
+
|
|
149
|
+
/** The URL as resolved by the constructor. This is always an absolute URL. Read only. */
|
|
150
|
+
this.url = url;
|
|
151
|
+
|
|
152
|
+
/** The number of attempted reconnects since starting, or the last successful connection. Read only. */
|
|
153
|
+
this.reconnectAttempts = 0;
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The current state of the connection.
|
|
157
|
+
* Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED
|
|
158
|
+
* Read only.
|
|
159
|
+
*/
|
|
160
|
+
this.readyState = WebSocket.CONNECTING;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* A string indicating the name of the sub-protocol the server selected; this will be one of
|
|
164
|
+
* the strings specified in the protocols parameter when creating the WebSocket object.
|
|
165
|
+
* Read only.
|
|
166
|
+
*/
|
|
167
|
+
this.protocol = null;
|
|
168
|
+
|
|
169
|
+
// Private state variables
|
|
170
|
+
|
|
171
|
+
var self = this;
|
|
172
|
+
var ws;
|
|
173
|
+
var forcedClose = false;
|
|
174
|
+
var timedOut = false;
|
|
175
|
+
var eventTarget = document.createElement('div');
|
|
176
|
+
|
|
177
|
+
// Wire up "on*" properties as event handlers
|
|
178
|
+
|
|
179
|
+
eventTarget.addEventListener('open', function(event) { self.onopen(event); });
|
|
180
|
+
eventTarget.addEventListener('close', function(event) { self.onclose(event); });
|
|
181
|
+
eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); });
|
|
182
|
+
eventTarget.addEventListener('message', function(event) { self.onmessage(event); });
|
|
183
|
+
eventTarget.addEventListener('error', function(event) { self.onerror(event); });
|
|
184
|
+
|
|
185
|
+
// Expose the API required by EventTarget
|
|
186
|
+
|
|
187
|
+
this.addEventListener = eventTarget.addEventListener.bind(eventTarget);
|
|
188
|
+
this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget);
|
|
189
|
+
this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget);
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* This function generates an event that is compatible with standard
|
|
193
|
+
* compliant browsers and IE9 - IE11
|
|
194
|
+
*
|
|
195
|
+
* This will prevent the error:
|
|
196
|
+
* Object doesn't support this action
|
|
197
|
+
*
|
|
198
|
+
* http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563
|
|
199
|
+
* @param s String The name that the event should use
|
|
200
|
+
* @param args Object an optional object that the event will use
|
|
201
|
+
*/
|
|
202
|
+
function generateEvent(s, args) {
|
|
203
|
+
var evt = document.createEvent("CustomEvent");
|
|
204
|
+
evt.initCustomEvent(s, false, false, args);
|
|
205
|
+
return evt;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
this.open = function (reconnectAttempt) {
|
|
209
|
+
ws = new WebSocket(self.url, protocols || []);
|
|
210
|
+
ws.binaryType = this.binaryType;
|
|
211
|
+
|
|
212
|
+
if (reconnectAttempt) {
|
|
213
|
+
if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
eventTarget.dispatchEvent(generateEvent('connecting'));
|
|
218
|
+
this.reconnectAttempts = 0;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
222
|
+
console.debug('ReconnectingWebSocket', 'attempt-connect', self.url);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
var localWs = ws;
|
|
226
|
+
var timeout = setTimeout(function() {
|
|
227
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
228
|
+
console.debug('ReconnectingWebSocket', 'connection-timeout', self.url);
|
|
229
|
+
}
|
|
230
|
+
timedOut = true;
|
|
231
|
+
localWs.close();
|
|
232
|
+
timedOut = false;
|
|
233
|
+
}, self.timeoutInterval);
|
|
234
|
+
|
|
235
|
+
ws.onopen = function(event) {
|
|
236
|
+
clearTimeout(timeout);
|
|
237
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
238
|
+
console.debug('ReconnectingWebSocket', 'onopen', self.url);
|
|
239
|
+
}
|
|
240
|
+
self.protocol = ws.protocol;
|
|
241
|
+
self.readyState = WebSocket.OPEN;
|
|
242
|
+
self.reconnectAttempts = 0;
|
|
243
|
+
var e = generateEvent('open');
|
|
244
|
+
e.isReconnect = reconnectAttempt;
|
|
245
|
+
reconnectAttempt = false;
|
|
246
|
+
eventTarget.dispatchEvent(e);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
ws.onclose = function(event) {
|
|
250
|
+
clearTimeout(timeout);
|
|
251
|
+
ws = null;
|
|
252
|
+
if (forcedClose) {
|
|
253
|
+
self.readyState = WebSocket.CLOSED;
|
|
254
|
+
eventTarget.dispatchEvent(generateEvent('close'));
|
|
255
|
+
} else {
|
|
256
|
+
self.readyState = WebSocket.CONNECTING;
|
|
257
|
+
var e = generateEvent('connecting');
|
|
258
|
+
e.code = event.code;
|
|
259
|
+
e.reason = event.reason;
|
|
260
|
+
e.wasClean = event.wasClean;
|
|
261
|
+
eventTarget.dispatchEvent(e);
|
|
262
|
+
if (!reconnectAttempt && !timedOut) {
|
|
263
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
264
|
+
console.debug('ReconnectingWebSocket', 'onclose', self.url);
|
|
265
|
+
}
|
|
266
|
+
eventTarget.dispatchEvent(generateEvent('close'));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
|
|
270
|
+
setTimeout(function() {
|
|
271
|
+
self.reconnectAttempts++;
|
|
272
|
+
self.open(true);
|
|
273
|
+
}, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
ws.onmessage = function(event) {
|
|
277
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
278
|
+
console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data);
|
|
279
|
+
}
|
|
280
|
+
var e = generateEvent('message');
|
|
281
|
+
e.data = event.data;
|
|
282
|
+
eventTarget.dispatchEvent(e);
|
|
283
|
+
};
|
|
284
|
+
ws.onerror = function(event) {
|
|
285
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
286
|
+
console.debug('ReconnectingWebSocket', 'onerror', self.url, event);
|
|
287
|
+
}
|
|
288
|
+
eventTarget.dispatchEvent(generateEvent('error'));
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Whether or not to create a websocket upon instantiation
|
|
293
|
+
if (this.automaticOpen == true) {
|
|
294
|
+
this.open(false);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Transmits data to the server over the WebSocket connection.
|
|
299
|
+
*
|
|
300
|
+
* @param data a text string, ArrayBuffer or Blob to send to the server.
|
|
301
|
+
*/
|
|
302
|
+
this.send = function(data) {
|
|
303
|
+
if (ws) {
|
|
304
|
+
if (self.debug || ReconnectingWebSocket.debugAll) {
|
|
305
|
+
console.debug('ReconnectingWebSocket', 'send', self.url, data);
|
|
306
|
+
}
|
|
307
|
+
return ws.send(data);
|
|
308
|
+
} else {
|
|
309
|
+
throw 'INVALID_STATE_ERR : Pausing to reconnect websocket';
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Closes the WebSocket connection or connection attempt, if any.
|
|
315
|
+
* If the connection is already CLOSED, this method does nothing.
|
|
316
|
+
*/
|
|
317
|
+
this.close = function(code, reason) {
|
|
318
|
+
// Default CLOSE_NORMAL code
|
|
319
|
+
if (typeof code == 'undefined') {
|
|
320
|
+
code = 1000;
|
|
321
|
+
}
|
|
322
|
+
forcedClose = true;
|
|
323
|
+
if (ws) {
|
|
324
|
+
ws.close(code, reason);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Additional public API method to refresh the connection if still open (close, re-open).
|
|
330
|
+
* For example, if the app suspects bad data / missed heart beats, it can try to refresh.
|
|
331
|
+
*/
|
|
332
|
+
this.refresh = function() {
|
|
333
|
+
if (ws) {
|
|
334
|
+
ws.close();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* An event listener to be called when the WebSocket connection's readyState changes to OPEN;
|
|
341
|
+
* this indicates that the connection is ready to send and receive data.
|
|
342
|
+
*/
|
|
343
|
+
ReconnectingWebSocket.prototype.onopen = function(event) {};
|
|
344
|
+
/** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */
|
|
345
|
+
ReconnectingWebSocket.prototype.onclose = function(event) {};
|
|
346
|
+
/** An event listener to be called when a connection begins being attempted. */
|
|
347
|
+
ReconnectingWebSocket.prototype.onconnecting = function(event) {};
|
|
348
|
+
/** An event listener to be called when a message is received from the server. */
|
|
349
|
+
ReconnectingWebSocket.prototype.onmessage = function(event) {};
|
|
350
|
+
/** An event listener to be called when an error occurs. */
|
|
351
|
+
ReconnectingWebSocket.prototype.onerror = function(event) {};
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Whether all instances of ReconnectingWebSocket should log debug messages.
|
|
355
|
+
* Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true.
|
|
356
|
+
*/
|
|
357
|
+
ReconnectingWebSocket.debugAll = false;
|
|
358
|
+
|
|
359
|
+
ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING;
|
|
360
|
+
ReconnectingWebSocket.OPEN = WebSocket.OPEN;
|
|
361
|
+
ReconnectingWebSocket.CLOSING = WebSocket.CLOSING;
|
|
362
|
+
ReconnectingWebSocket.CLOSED = WebSocket.CLOSED;
|
|
363
|
+
|
|
364
|
+
return ReconnectingWebSocket;
|
|
365
|
+
});
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-0.2.72 → tina4_python-0.2.74}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|