arthexis 0.1.9__py3-none-any.whl → 0.1.26__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 arthexis might be problematic. Click here for more details.

Files changed (112) hide show
  1. arthexis-0.1.26.dist-info/METADATA +272 -0
  2. arthexis-0.1.26.dist-info/RECORD +111 -0
  3. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +674 -674
  4. config/__init__.py +5 -5
  5. config/active_app.py +15 -15
  6. config/asgi.py +29 -29
  7. config/auth_app.py +7 -7
  8. config/celery.py +32 -25
  9. config/context_processors.py +67 -68
  10. config/horologia_app.py +7 -7
  11. config/loadenv.py +11 -11
  12. config/logging.py +59 -48
  13. config/middleware.py +71 -25
  14. config/offline.py +49 -49
  15. config/settings.py +676 -492
  16. config/settings_helpers.py +109 -0
  17. config/urls.py +228 -159
  18. config/wsgi.py +17 -17
  19. core/admin.py +4052 -2066
  20. core/admin_history.py +50 -50
  21. core/admindocs.py +192 -151
  22. core/apps.py +350 -223
  23. core/auto_upgrade.py +72 -0
  24. core/backends.py +311 -124
  25. core/changelog.py +403 -0
  26. core/entity.py +149 -133
  27. core/environment.py +60 -43
  28. core/fields.py +168 -75
  29. core/form_fields.py +75 -0
  30. core/github_helper.py +188 -25
  31. core/github_issues.py +183 -172
  32. core/github_repos.py +72 -0
  33. core/lcd_screen.py +78 -78
  34. core/liveupdate.py +25 -25
  35. core/log_paths.py +114 -100
  36. core/mailer.py +89 -83
  37. core/middleware.py +91 -91
  38. core/models.py +5041 -2195
  39. core/notifications.py +105 -105
  40. core/public_wifi.py +267 -227
  41. core/reference_utils.py +107 -0
  42. core/release.py +940 -346
  43. core/rfid_import_export.py +113 -0
  44. core/sigil_builder.py +149 -131
  45. core/sigil_context.py +20 -20
  46. core/sigil_resolver.py +250 -284
  47. core/system.py +1425 -230
  48. core/tasks.py +538 -199
  49. core/temp_passwords.py +181 -0
  50. core/test_system_info.py +202 -43
  51. core/tests.py +2673 -1069
  52. core/tests_liveupdate.py +17 -17
  53. core/urls.py +11 -11
  54. core/user_data.py +681 -495
  55. core/views.py +2484 -789
  56. core/widgets.py +213 -51
  57. nodes/admin.py +2236 -445
  58. nodes/apps.py +98 -70
  59. nodes/backends.py +160 -53
  60. nodes/dns.py +203 -0
  61. nodes/feature_checks.py +133 -0
  62. nodes/lcd.py +165 -165
  63. nodes/models.py +2375 -870
  64. nodes/reports.py +411 -0
  65. nodes/rfid_sync.py +210 -0
  66. nodes/signals.py +18 -0
  67. nodes/tasks.py +141 -46
  68. nodes/tests.py +5045 -1489
  69. nodes/urls.py +29 -13
  70. nodes/utils.py +172 -73
  71. nodes/views.py +1768 -304
  72. ocpp/admin.py +1775 -481
  73. ocpp/apps.py +25 -25
  74. ocpp/consumers.py +1843 -630
  75. ocpp/evcs.py +844 -928
  76. ocpp/evcs_discovery.py +158 -0
  77. ocpp/models.py +1417 -640
  78. ocpp/network.py +398 -0
  79. ocpp/reference_utils.py +42 -0
  80. ocpp/routing.py +11 -9
  81. ocpp/simulator.py +745 -368
  82. ocpp/status_display.py +26 -0
  83. ocpp/store.py +603 -403
  84. ocpp/tasks.py +479 -31
  85. ocpp/test_export_import.py +131 -130
  86. ocpp/test_rfid.py +1072 -540
  87. ocpp/tests.py +5494 -2296
  88. ocpp/transactions_io.py +197 -165
  89. ocpp/urls.py +50 -50
  90. ocpp/views.py +2024 -912
  91. pages/admin.py +1123 -396
  92. pages/apps.py +45 -10
  93. pages/checks.py +40 -40
  94. pages/context_processors.py +151 -85
  95. pages/defaults.py +13 -0
  96. pages/forms.py +221 -0
  97. pages/middleware.py +213 -153
  98. pages/models.py +720 -252
  99. pages/module_defaults.py +156 -0
  100. pages/site_config.py +137 -0
  101. pages/tasks.py +74 -0
  102. pages/tests.py +4009 -1389
  103. pages/urls.py +38 -20
  104. pages/utils.py +93 -12
  105. pages/views.py +1736 -762
  106. arthexis-0.1.9.dist-info/METADATA +0 -168
  107. arthexis-0.1.9.dist-info/RECORD +0 -92
  108. core/workgroup_urls.py +0 -17
  109. core/workgroup_views.py +0 -94
  110. nodes/actions.py +0 -70
  111. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
  112. {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
nodes/tasks.py CHANGED
@@ -1,46 +1,141 @@
1
- import logging
2
- from pathlib import Path
3
-
4
- import pyperclip
5
- from pyperclip import PyperclipException
6
- from celery import shared_task
7
-
8
- from .models import ContentSample, Node
9
- from .utils import capture_screenshot, save_screenshot
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- @shared_task
15
- def sample_clipboard() -> None:
16
- """Save current clipboard contents to a :class:`ContentSample` entry."""
17
- try:
18
- content = pyperclip.paste()
19
- except PyperclipException as exc: # pragma: no cover - depends on OS clipboard
20
- logger.error("Clipboard error: %s", exc)
21
- return
22
- if not content:
23
- logger.info("Clipboard is empty")
24
- return
25
- if ContentSample.objects.filter(content=content, kind=ContentSample.TEXT).exists():
26
- logger.info("Duplicate clipboard content; sample not created")
27
- return
28
- node = Node.get_local()
29
- ContentSample.objects.create(content=content, node=node, kind=ContentSample.TEXT)
30
-
31
-
32
- @shared_task
33
- def capture_node_screenshot(
34
- url: str | None = None, port: int = 8000, method: str = "TASK"
35
- ) -> str:
36
- """Capture a screenshot of ``url`` and record it as a :class:`ContentSample`."""
37
- if url is None:
38
- url = f"http://localhost:{port}"
39
- try:
40
- path: Path = capture_screenshot(url)
41
- except Exception as exc: # pragma: no cover - depends on selenium setup
42
- logger.error("Screenshot capture failed: %s", exc)
43
- return ""
44
- node = Node.get_local()
45
- save_screenshot(path, node=node, method=method)
46
- return str(path)
1
+ import base64
2
+ import json
3
+ import logging
4
+ from pathlib import Path
5
+
6
+ import pyperclip
7
+ import requests
8
+ from celery import shared_task
9
+ from cryptography.hazmat.primitives import hashes, serialization
10
+ from cryptography.hazmat.primitives.asymmetric import padding
11
+ from pyperclip import PyperclipException
12
+
13
+ from .models import ContentSample, NetMessage, Node
14
+ from .utils import capture_screenshot, save_screenshot
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @shared_task
20
+ def sample_clipboard() -> None:
21
+ """Save current clipboard contents to a :class:`ContentSample` entry."""
22
+ try:
23
+ content = pyperclip.paste()
24
+ except PyperclipException as exc: # pragma: no cover - depends on OS clipboard
25
+ logger.error("Clipboard error: %s", exc)
26
+ return
27
+ if not content:
28
+ logger.info("Clipboard is empty")
29
+ return
30
+ if ContentSample.objects.filter(content=content, kind=ContentSample.TEXT).exists():
31
+ logger.info("Duplicate clipboard content; sample not created")
32
+ return
33
+ node = Node.get_local()
34
+ ContentSample.objects.create(content=content, node=node, kind=ContentSample.TEXT)
35
+
36
+
37
+ @shared_task
38
+ def capture_node_screenshot(
39
+ url: str | None = None, port: int = 8000, method: str = "TASK"
40
+ ) -> str:
41
+ """Capture a screenshot of ``url`` and record it as a :class:`ContentSample`."""
42
+ if url is None:
43
+ url = f"http://localhost:{port}"
44
+ try:
45
+ path: Path = capture_screenshot(url)
46
+ except Exception as exc: # pragma: no cover - depends on selenium setup
47
+ logger.error("Screenshot capture failed: %s", exc)
48
+ return ""
49
+ node = Node.get_local()
50
+ save_screenshot(path, node=node, method=method)
51
+ return str(path)
52
+
53
+
54
+ @shared_task
55
+ def poll_unreachable_upstream() -> None:
56
+ """Poll upstream nodes for queued NetMessages."""
57
+
58
+ local = Node.get_local()
59
+ if not local or not local.has_feature("celery-queue"):
60
+ return
61
+
62
+ private_key = local.get_private_key()
63
+ if not private_key:
64
+ logger.warning("Node %s cannot sign upstream polls", getattr(local, "pk", None))
65
+ return
66
+
67
+ requester_payload = {"requester": str(local.uuid)}
68
+ payload_json = json.dumps(requester_payload, separators=(",", ":"), sort_keys=True)
69
+ try:
70
+ signature = base64.b64encode(
71
+ private_key.sign(
72
+ payload_json.encode(),
73
+ padding.PKCS1v15(),
74
+ hashes.SHA256(),
75
+ )
76
+ ).decode()
77
+ except Exception as exc:
78
+ logger.warning("Failed to sign upstream poll request: %s", exc)
79
+ return
80
+
81
+ headers = {"Content-Type": "application/json", "X-Signature": signature}
82
+ upstream_nodes = Node.objects.filter(current_relation=Node.Relation.UPSTREAM)
83
+ for upstream in upstream_nodes:
84
+ if not upstream.public_key:
85
+ continue
86
+ response = None
87
+ for url in upstream.iter_remote_urls("/nodes/net-message/pull/"):
88
+ try:
89
+ response = requests.post(
90
+ url, data=payload_json, headers=headers, timeout=5
91
+ )
92
+ except Exception as exc:
93
+ logger.warning("Polling upstream node %s via %s failed: %s", upstream.pk, url, exc)
94
+ continue
95
+ if response.ok:
96
+ break
97
+ logger.warning(
98
+ "Upstream node %s returned status %s", upstream.pk, response.status_code
99
+ )
100
+ response = None
101
+ if response is None or not response.ok:
102
+ continue
103
+ try:
104
+ body = response.json()
105
+ except ValueError:
106
+ logger.warning("Upstream node %s returned invalid JSON", upstream.pk)
107
+ continue
108
+ messages = body.get("messages", [])
109
+ if not isinstance(messages, list) or not messages:
110
+ continue
111
+ try:
112
+ public_key = serialization.load_pem_public_key(upstream.public_key.encode())
113
+ except Exception:
114
+ logger.warning("Upstream node %s has invalid public key", upstream.pk)
115
+ continue
116
+ for item in messages:
117
+ if not isinstance(item, dict):
118
+ continue
119
+ payload = item.get("payload")
120
+ payload_signature = item.get("signature")
121
+ if not isinstance(payload, dict) or not payload_signature:
122
+ continue
123
+ payload_text = json.dumps(payload, separators=(",", ":"), sort_keys=True)
124
+ try:
125
+ public_key.verify(
126
+ base64.b64decode(payload_signature),
127
+ payload_text.encode(),
128
+ padding.PKCS1v15(),
129
+ hashes.SHA256(),
130
+ )
131
+ except Exception:
132
+ logger.warning(
133
+ "Signature verification failed for upstream node %s", upstream.pk
134
+ )
135
+ continue
136
+ try:
137
+ NetMessage.receive_payload(payload, sender=upstream)
138
+ except ValueError as exc:
139
+ logger.warning(
140
+ "Discarded upstream message from node %s: %s", upstream.pk, exc
141
+ )