capyy 0.5.2__tar.gz → 0.5.3__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.
- {capyy-0.5.2 → capyy-0.5.3}/PKG-INFO +14 -1
- {capyy-0.5.2 → capyy-0.5.3}/README.md +13 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/__main__.py +139 -2
- {capyy-0.5.2 → capyy-0.5.3}/capy/_project.py +1 -1
- {capyy-0.5.2 → capyy-0.5.3}/capy/_tls.py +3 -1
- {capyy-0.5.2 → capyy-0.5.3}/capyy.egg-info/PKG-INFO +14 -1
- {capyy-0.5.2 → capyy-0.5.3}/pyproject.toml +1 -1
- {capyy-0.5.2 → capyy-0.5.3}/tests/test_capy.py +54 -0
- {capyy-0.5.2 → capyy-0.5.3}/tests/test_domo.py +4 -1
- {capyy-0.5.2 → capyy-0.5.3}/LICENSE +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/__init__.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/__init__.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/__main__.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_base.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_homeassistant.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_hue.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_net.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_shelly.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_wiz.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/domo/_yeelight.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/network.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capy/wifi.py +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capyy.egg-info/SOURCES.txt +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capyy.egg-info/dependency_links.txt +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capyy.egg-info/entry_points.txt +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/capyy.egg-info/top_level.txt +0 -0
- {capyy-0.5.2 → capyy-0.5.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capyy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Easy, encrypted networking for Python: programs that talk (local, Wi-Fi, internet), home automation, and projects run with cy
|
|
5
5
|
Author: Izac
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,6 +101,19 @@ app.call("hello", nom="Izac") # client side
|
|
|
101
101
|
`mode` can be `local`, `wifi` or `network` (with `relay`, `key`, `fingerprint`). Strings starting with `$` are read
|
|
102
102
|
from environment variables, e.g. `"key": "$CAPY_KEY"`. Inside a project, the `cy` command runs its `scripts` like `npm run` (`cy test`, `cy start`) and its files (`cy server`); create a project with `capy new`.
|
|
103
103
|
|
|
104
|
+
## From the terminal
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
capy call localhost:4000 hello nom=Izac # any capy server (add --no-tls for a plain one)
|
|
108
|
+
capy wifi scan # capy.wifi servers on your network
|
|
109
|
+
capy wifi call home temperature
|
|
110
|
+
capy network call home test --relay my-vps.example:3879 --key "$CAPY_KEY"
|
|
111
|
+
cy call hello nom=Izac # inside a project: uses capy.config.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Parameters are `name=value`; numbers, `true`/`false` and lists are parsed (`age=14` sends the number 14).
|
|
115
|
+
The answer is printed as JSON; the exit code is 1 when the server answers `{"erreur": ...}`.
|
|
116
|
+
|
|
104
117
|
## Encryption (TLS)
|
|
105
118
|
|
|
106
119
|
Everything is encrypted by default, with nothing to configure.
|
|
@@ -79,6 +79,19 @@ app.call("hello", nom="Izac") # client side
|
|
|
79
79
|
`mode` can be `local`, `wifi` or `network` (with `relay`, `key`, `fingerprint`). Strings starting with `$` are read
|
|
80
80
|
from environment variables, e.g. `"key": "$CAPY_KEY"`. Inside a project, the `cy` command runs its `scripts` like `npm run` (`cy test`, `cy start`) and its files (`cy server`); create a project with `capy new`.
|
|
81
81
|
|
|
82
|
+
## From the terminal
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
capy call localhost:4000 hello nom=Izac # any capy server (add --no-tls for a plain one)
|
|
86
|
+
capy wifi scan # capy.wifi servers on your network
|
|
87
|
+
capy wifi call home temperature
|
|
88
|
+
capy network call home test --relay my-vps.example:3879 --key "$CAPY_KEY"
|
|
89
|
+
cy call hello nom=Izac # inside a project: uses capy.config.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Parameters are `name=value`; numbers, `true`/`false` and lists are parsed (`age=14` sends the number 14).
|
|
93
|
+
The answer is printed as JSON; the exit code is 1 when the server answers `{"erreur": ...}`.
|
|
94
|
+
|
|
82
95
|
## Encryption (TLS)
|
|
83
96
|
|
|
84
97
|
Everything is encrypted by default, with nothing to configure.
|
|
@@ -6,16 +6,21 @@
|
|
|
6
6
|
# cy scripts -> liste les scripts
|
|
7
7
|
# cy server / cy client -> lance un fichier de "files"
|
|
8
8
|
# cy run -> lance le serveur en fond, puis le client, puis arrête le serveur
|
|
9
|
+
# cy call ACTION [nom=valeur…] -> appelle une action du serveur du projet (local, wifi ou network)
|
|
9
10
|
#
|
|
10
11
|
# capy : tout le reste (et tout ce que fait cy)
|
|
11
12
|
# capy new NOM [mode] -> crée un dossier NOM avec capy.config.json, server.py, client.py
|
|
12
13
|
# capy init [nom] [mode] -> crée juste capy.config.json ici
|
|
13
14
|
# capy domo … -> la domotique (capy domo help)
|
|
14
15
|
# capy relay PORT [CLÉ] -> lance un relais capy.network (sur le VPS)
|
|
16
|
+
# capy call HÔTE:PORT ACTION [nom=valeur…] -> appelle une action d'un serveur capy
|
|
17
|
+
# capy wifi scan / capy wifi call NOM ACTION … -> les serveurs du wifi
|
|
18
|
+
# capy network call NOM ACTION … [--relay R --key K] / capy network relay PORT [CLÉ]
|
|
15
19
|
# mode : local (par défaut), wifi ou network
|
|
16
20
|
# python3 -m capy = capy (si la commande n'est pas dans le PATH)
|
|
17
21
|
|
|
18
22
|
import difflib
|
|
23
|
+
import json
|
|
19
24
|
import socket
|
|
20
25
|
import subprocess
|
|
21
26
|
import sys
|
|
@@ -30,11 +35,18 @@ USAGE = f"""Dans un projet capy ({FILE_NAME}) :
|
|
|
30
35
|
cy scripts liste les scripts
|
|
31
36
|
cy server / client lance un fichier de "files"
|
|
32
37
|
cy run serveur en fond + client, puis arrête le serveur
|
|
38
|
+
cy call ACTION [nom=valeur…] appelle une action du serveur du projet
|
|
33
39
|
Partout :
|
|
34
40
|
capy new NOM [mode] crée un projet complet (config, server.py, client.py)
|
|
35
41
|
capy init [nom] [mode] crée juste {FILE_NAME} ici
|
|
36
42
|
capy domo … la domotique (capy domo help)
|
|
37
43
|
capy relay PORT [CLÉ] lance un relais capy.network
|
|
44
|
+
capy call HÔTE:PORT ACTION [nom=valeur…] [--no-tls]
|
|
45
|
+
capy wifi scan les serveurs capy du wifi
|
|
46
|
+
capy wifi call NOM ACTION [nom=valeur…] [--no-tls]
|
|
47
|
+
capy network call NOM ACTION [nom=valeur…] [--relay HÔTE:PORT] [--key CLÉ] [--fingerprint …]
|
|
48
|
+
capy network relay PORT [CLÉ] (= capy relay)
|
|
49
|
+
Paramètres : nom=Izac age=14 actif=true liste=[1,2] (les nombres, true/false et listes sont compris)
|
|
38
50
|
modes : {", ".join(MODES)}"""
|
|
39
51
|
|
|
40
52
|
|
|
@@ -51,6 +63,118 @@ def _project():
|
|
|
51
63
|
sys.exit(str(e))
|
|
52
64
|
|
|
53
65
|
|
|
66
|
+
# Sortir les options "--nom valeur" (et "--drapeau") d'une liste d'arguments -> (options, reste)
|
|
67
|
+
def _options(args, flags=(), values=()):
|
|
68
|
+
options, rest, i = {}, [], 0
|
|
69
|
+
while i < len(args):
|
|
70
|
+
word = args[i]
|
|
71
|
+
if word in flags:
|
|
72
|
+
options[word] = True
|
|
73
|
+
elif word in values:
|
|
74
|
+
if i + 1 >= len(args):
|
|
75
|
+
sys.exit(f"capy : il manque une valeur après {word}")
|
|
76
|
+
options[word] = args[i + 1]
|
|
77
|
+
i += 1
|
|
78
|
+
elif word.startswith("--"):
|
|
79
|
+
sys.exit(f"capy : option {word} inconnue (possibles : {', '.join((*flags, *values)) or 'aucune'})")
|
|
80
|
+
else:
|
|
81
|
+
rest.append(word)
|
|
82
|
+
i += 1
|
|
83
|
+
return options, rest
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# "nom=Izac" "age=14" -> {"nom": "Izac", "age": 14} : une valeur qui est du JSON (14, true, [1, 2]) est convertie
|
|
87
|
+
def _params(words):
|
|
88
|
+
params = {}
|
|
89
|
+
for word in words:
|
|
90
|
+
key, sep, value = word.partition("=")
|
|
91
|
+
if not sep or not key:
|
|
92
|
+
sys.exit(f"capy : paramètre {word!r} invalide, écris nom=valeur (ex : nom=Izac age=14)")
|
|
93
|
+
try:
|
|
94
|
+
params[key] = json.loads(value)
|
|
95
|
+
except ValueError:
|
|
96
|
+
params[key] = value
|
|
97
|
+
return params
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# Faire l'appel et afficher la réponse en JSON ; code de sortie 1 si erreur (pratique dans un script)
|
|
101
|
+
def _call(function):
|
|
102
|
+
import capy
|
|
103
|
+
try:
|
|
104
|
+
answer = function()
|
|
105
|
+
except (ConnectionError, ValueError, KeyError, capy.SecurityError) as e:
|
|
106
|
+
sys.exit(str(e).strip("'\""))
|
|
107
|
+
print(json.dumps(answer, indent=2, ensure_ascii=False))
|
|
108
|
+
if isinstance(answer, dict) and "erreur" in answer:
|
|
109
|
+
sys.exit(1)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
USAGE_CALL = "Usage : capy call HÔTE:PORT ACTION [nom=valeur…] [--no-tls] (ex : capy call localhost:4000 hello nom=Izac)"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# capy call localhost:4000 hello nom=Izac
|
|
116
|
+
def call_direct(args):
|
|
117
|
+
import capy
|
|
118
|
+
options, rest = _options(args, flags=("--no-tls",))
|
|
119
|
+
if len(rest) < 2:
|
|
120
|
+
sys.exit(USAGE_CALL)
|
|
121
|
+
host, _, port = rest[0].rpartition(":")
|
|
122
|
+
if not host or not port.isdigit():
|
|
123
|
+
sys.exit(f"capy : adresse {rest[0]!r} invalide, écris HÔTE:PORT (ex : localhost:4000)\n{USAGE_CALL}")
|
|
124
|
+
_call(lambda: capy.call(host, int(port), rest[1], tls="--no-tls" not in options, **_params(rest[2:])))
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# capy wifi scan / capy wifi call maison hello nom=Izac
|
|
128
|
+
def wifi(args):
|
|
129
|
+
import capy.wifi
|
|
130
|
+
sub = args[0] if args else None
|
|
131
|
+
if sub == "scan":
|
|
132
|
+
options, _ = _options(args[1:], values=("--timeout",))
|
|
133
|
+
found = capy.wifi.scan(timeout=float(options.get("--timeout", 1)))
|
|
134
|
+
if not found:
|
|
135
|
+
sys.exit("Aucun serveur capy.wifi trouvé (même wifi ? wifi public qui isole les appareils ?)")
|
|
136
|
+
for name, (ip, port) in sorted(found.items()):
|
|
137
|
+
print(f" {name:<20} {ip}:{port}{' (cet appareil)' if ip == '127.0.0.1' else ''}")
|
|
138
|
+
elif sub == "call":
|
|
139
|
+
options, rest = _options(args[1:], flags=("--no-tls",))
|
|
140
|
+
if len(rest) < 2:
|
|
141
|
+
sys.exit("Usage : capy wifi call NOM ACTION [nom=valeur…] [--no-tls] (ex : capy wifi call maison hello)")
|
|
142
|
+
_call(lambda: capy.wifi.call(rest[0], rest[1], tls="--no-tls" not in options, **_params(rest[2:])))
|
|
143
|
+
else:
|
|
144
|
+
sys.exit("Usage : capy wifi scan [--timeout S] ou capy wifi call NOM ACTION [nom=valeur…] [--no-tls]")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# capy network call maison hello --relay mon-vps.fr:3879 --key … / capy network relay 3879 CLÉ
|
|
148
|
+
# Relais et clé : options, sinon le capy.config.json du dossier (mode network), sinon $CAPY_RELAY / $CAPY_KEY
|
|
149
|
+
def network(args):
|
|
150
|
+
import os
|
|
151
|
+
|
|
152
|
+
import capy.network
|
|
153
|
+
sub = args[0] if args else None
|
|
154
|
+
if sub == "relay" or (sub or "").isdigit(): # capy network relay 3879 CLÉ (ou l'ancien capy network 3879 CLÉ)
|
|
155
|
+
capy.network.cli(args[1:] if sub == "relay" else args)
|
|
156
|
+
elif sub == "call":
|
|
157
|
+
options, rest = _options(args[1:], values=("--relay", "--key", "--fingerprint"))
|
|
158
|
+
if len(rest) < 2:
|
|
159
|
+
sys.exit("Usage : capy network call NOM ACTION [nom=valeur…] [--relay HÔTE:PORT] [--key CLÉ] [--fingerprint …]")
|
|
160
|
+
relay, key, fingerprint = options.get("--relay"), options.get("--key"), options.get("--fingerprint")
|
|
161
|
+
path = find(".")
|
|
162
|
+
if path and not relay:
|
|
163
|
+
try:
|
|
164
|
+
app = project(path)
|
|
165
|
+
except ValueError:
|
|
166
|
+
app = None
|
|
167
|
+
if app and app.mode == "network":
|
|
168
|
+
relay, key, fingerprint = app.relay, key or app.key, fingerprint or app.fingerprint
|
|
169
|
+
relay, key = relay or os.environ.get("CAPY_RELAY"), key or os.environ.get("CAPY_KEY")
|
|
170
|
+
if not relay:
|
|
171
|
+
sys.exit("capy : quel relais ? Ajoute --relay mon-vps.fr:3879 (ou un capy.config.json en mode network, ou $CAPY_RELAY)")
|
|
172
|
+
capy.network.use(relay, key=key, fingerprint=fingerprint)
|
|
173
|
+
_call(lambda: capy.network.call(rest[0], rest[1], **_params(rest[2:])))
|
|
174
|
+
else:
|
|
175
|
+
sys.exit("Usage : capy network call NOM ACTION [nom=valeur…] [--relay …] [--key …] ou capy network relay PORT [CLÉ]")
|
|
176
|
+
|
|
177
|
+
|
|
54
178
|
# Lancer un fichier du projet avec le même Python, depuis le dossier du projet
|
|
55
179
|
def _start(app, name, args=(), **options):
|
|
56
180
|
try:
|
|
@@ -120,10 +244,19 @@ def main(args):
|
|
|
120
244
|
from .domo.__main__ import cli as domo_cli
|
|
121
245
|
domo_cli(args[1:])
|
|
122
246
|
return
|
|
123
|
-
if command
|
|
247
|
+
if command == "relay": # capy relay 3879 CLÉ
|
|
124
248
|
from .network import cli as relay_cli
|
|
125
249
|
relay_cli(args[1:])
|
|
126
250
|
return
|
|
251
|
+
if command == "network":
|
|
252
|
+
network(args[1:])
|
|
253
|
+
return
|
|
254
|
+
if command == "wifi":
|
|
255
|
+
wifi(args[1:])
|
|
256
|
+
return
|
|
257
|
+
if command == "call" and len(args) > 1 and ":" in args[1]: # capy call localhost:4000 hello
|
|
258
|
+
call_direct(args[1:])
|
|
259
|
+
return
|
|
127
260
|
if command in ("init", "new"):
|
|
128
261
|
rest = args[1:]
|
|
129
262
|
mode = next((a for a in rest if a in MODES), "local")
|
|
@@ -146,6 +279,10 @@ def main(args):
|
|
|
146
279
|
print(app.describe())
|
|
147
280
|
elif command == "run":
|
|
148
281
|
run(app, args[1:])
|
|
282
|
+
elif command == "call": # cy call hello nom=Izac : le serveur du projet, quel que soit son mode
|
|
283
|
+
if len(args) < 2:
|
|
284
|
+
sys.exit("Usage : cy call ACTION [nom=valeur…] (ex : cy call hello nom=Izac)")
|
|
285
|
+
_call(lambda: app.call(args[1], **_params(args[2:])))
|
|
149
286
|
elif command == "scripts":
|
|
150
287
|
if not app.scripts:
|
|
151
288
|
print('Pas de "scripts" dans ' + FILE_NAME + ', ex : "scripts": {"test": "python3 -m unittest"}')
|
|
@@ -173,7 +310,7 @@ def main(args):
|
|
|
173
310
|
# La commande cy : seulement les commandes d'un projet
|
|
174
311
|
def cli():
|
|
175
312
|
args = sys.argv[1:]
|
|
176
|
-
if args[:1] in (["new"], ["init"], ["domo"], ["relay"], ["network"]):
|
|
313
|
+
if args[:1] in (["new"], ["init"], ["domo"], ["relay"], ["network"], ["wifi"]):
|
|
177
314
|
sys.exit(f"cy sert dans un projet capy ; pour ça, c'est la commande capy : capy {' '.join(args)}")
|
|
178
315
|
main(args)
|
|
179
316
|
|
|
@@ -46,7 +46,7 @@ FIELDS = {
|
|
|
46
46
|
|
|
47
47
|
EXAMPLE = {"name": "mon-projet", "mode": "local", "host": "localhost", "port": 4000, "tls": True,
|
|
48
48
|
"files": {"server": "server.py", "client": "client.py"}}
|
|
49
|
-
COMMANDS = ("init", "new", "run", "scripts", "help", "domo", "relay") # Mots réservés de cy / capy (pas des noms de fichiers ni de scripts)
|
|
49
|
+
COMMANDS = ("init", "new", "run", "scripts", "help", "call", "domo", "relay", "wifi", "network") # Mots réservés de cy / capy (pas des noms de fichiers ni de scripts)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
# Dossier du script lancé (comme simplesuit.json) ; sans script (python3 -c, REPL) -> dossier actuel
|
|
@@ -12,6 +12,7 @@ import pathlib as pth
|
|
|
12
12
|
import socket
|
|
13
13
|
import ssl
|
|
14
14
|
import subprocess
|
|
15
|
+
import sys
|
|
15
16
|
import threading
|
|
16
17
|
|
|
17
18
|
CAPY_DIR = pth.Path.home() / ".capy"
|
|
@@ -119,7 +120,8 @@ def check(label, seen, pinned=None, who="capy", kind="serveur", local=False):
|
|
|
119
120
|
CAPY_DIR.mkdir(exist_ok=True)
|
|
120
121
|
known[label] = seen
|
|
121
122
|
KNOWN_FILE.write_text(json.dumps(known, indent=4))
|
|
122
|
-
|
|
123
|
+
# Sur stderr : la sortie normale reste propre (ex : capy call … | jq)
|
|
124
|
+
print(f"{who} : nouveau {kind} {label}, empreinte retenue {seen}", file=sys.stderr, flush=True)
|
|
123
125
|
return
|
|
124
126
|
if not hmac.compare_digest(known[label], seen):
|
|
125
127
|
raise SecurityError(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capyy
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Easy, encrypted networking for Python: programs that talk (local, Wi-Fi, internet), home automation, and projects run with cy
|
|
5
5
|
Author: Izac
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,6 +101,19 @@ app.call("hello", nom="Izac") # client side
|
|
|
101
101
|
`mode` can be `local`, `wifi` or `network` (with `relay`, `key`, `fingerprint`). Strings starting with `$` are read
|
|
102
102
|
from environment variables, e.g. `"key": "$CAPY_KEY"`. Inside a project, the `cy` command runs its `scripts` like `npm run` (`cy test`, `cy start`) and its files (`cy server`); create a project with `capy new`.
|
|
103
103
|
|
|
104
|
+
## From the terminal
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
capy call localhost:4000 hello nom=Izac # any capy server (add --no-tls for a plain one)
|
|
108
|
+
capy wifi scan # capy.wifi servers on your network
|
|
109
|
+
capy wifi call home temperature
|
|
110
|
+
capy network call home test --relay my-vps.example:3879 --key "$CAPY_KEY"
|
|
111
|
+
cy call hello nom=Izac # inside a project: uses capy.config.json
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Parameters are `name=value`; numbers, `true`/`false` and lists are parsed (`age=14` sends the number 14).
|
|
115
|
+
The answer is printed as JSON; the exit code is 1 when the server answers `{"erreur": ...}`.
|
|
116
|
+
|
|
104
117
|
## Encryption (TLS)
|
|
105
118
|
|
|
106
119
|
Everything is encrypted by default, with nothing to configure.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "capyy"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "Easy, encrypted networking for Python: programs that talk (local, Wi-Fi, internet), home automation, and projects run with cy"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -357,5 +357,59 @@ class TestProject(unittest.TestCase):
|
|
|
357
357
|
self.assertEqual(json.loads(path.read_text())["key"], "$CAPY_KEY")
|
|
358
358
|
|
|
359
359
|
|
|
360
|
+
# Les commandes capy / cy, lancées pour de vrai (dossier personnel temporaire : ~/.capy n'est pas touché)
|
|
361
|
+
class TestCommandLine(unittest.TestCase):
|
|
362
|
+
@classmethod
|
|
363
|
+
def setUpClass(cls):
|
|
364
|
+
cls.home = tempfile.TemporaryDirectory()
|
|
365
|
+
cls.server = start(demo_server(capy.Server(0, tls=False)))
|
|
366
|
+
cls.relay_port = free_port()
|
|
367
|
+
threading.Thread(target=capy.network.Relay(cls.relay_port, key="cle").run, daemon=True).start()
|
|
368
|
+
time.sleep(0.5)
|
|
369
|
+
capy.network.use(f"127.0.0.1:{cls.relay_port}", key="cle")
|
|
370
|
+
start(demo_server(capy.network.Server("cli")), wait=lambda s: True)
|
|
371
|
+
time.sleep(1)
|
|
372
|
+
|
|
373
|
+
@classmethod
|
|
374
|
+
def tearDownClass(cls):
|
|
375
|
+
cls.home.cleanup()
|
|
376
|
+
|
|
377
|
+
def run_capy(self, *args, cwd=None):
|
|
378
|
+
import subprocess
|
|
379
|
+
import sys
|
|
380
|
+
# Autre HOME : Python ne voit plus les paquets installés pour l'utilisateur, on lui montre capy
|
|
381
|
+
env = {**os.environ, "HOME": self.home.name, "PYTHONPATH": str(pathlib.Path(__file__).resolve().parent.parent)}
|
|
382
|
+
return subprocess.run([sys.executable, "-m", "capy", *args], cwd=cwd or self.home.name, env=env,
|
|
383
|
+
capture_output=True, text=True, timeout=30)
|
|
384
|
+
|
|
385
|
+
def test_call(self):
|
|
386
|
+
result = self.run_capy("call", f"localhost:{self.server.port}", "hello", "name=Zoé", "--no-tls")
|
|
387
|
+
self.assertEqual((result.returncode, json.loads(result.stdout)), (0, {"message": "Bonjour Zoé"}))
|
|
388
|
+
result = self.run_capy("call", f"localhost:{self.server.port}", "divide", "a=6", "b=3", "--no-tls")
|
|
389
|
+
self.assertEqual(json.loads(result.stdout), {"result": 2.0}) # "6" et "3" compris comme des nombres
|
|
390
|
+
result = self.run_capy("call", f"localhost:{self.server.port}", "nope", "--no-tls")
|
|
391
|
+
self.assertEqual(result.returncode, 1) # Réponse {"erreur": ...} -> code 1
|
|
392
|
+
self.assertIn("nom=valeur", self.run_capy("call", f"localhost:{self.server.port}", "hello", "name", "--no-tls").stderr)
|
|
393
|
+
|
|
394
|
+
def test_project_call(self):
|
|
395
|
+
folder = pathlib.Path(self.home.name) / "projet"
|
|
396
|
+
folder.mkdir(exist_ok=True)
|
|
397
|
+
(folder / "capy.config.json").write_text(json.dumps({"port": self.server.port, "tls": False}))
|
|
398
|
+
result = self.run_capy("call", "hello", "name=Projet", cwd=folder)
|
|
399
|
+
self.assertEqual(json.loads(result.stdout), {"message": "Bonjour Projet"})
|
|
400
|
+
|
|
401
|
+
def test_network_call(self):
|
|
402
|
+
result = self.run_capy("network", "call", "cli", "hello", "name=Léa", "--relay", f"127.0.0.1:{self.relay_port}", "--key", "cle")
|
|
403
|
+
self.assertEqual(json.loads(result.stdout), {"message": "Bonjour Léa"})
|
|
404
|
+
self.assertIn("quel relais", self.run_capy("network", "call", "cli", "hello").stderr)
|
|
405
|
+
|
|
406
|
+
def test_reserved_script_names(self):
|
|
407
|
+
for name in ("call", "wifi", "network"):
|
|
408
|
+
with self.assertRaisesRegex(ValueError, "nom d'une commande"):
|
|
409
|
+
path = pathlib.Path(self.home.name) / "capy.config.json"
|
|
410
|
+
path.write_text(json.dumps({"port": 1, "scripts": {name: "echo"}}))
|
|
411
|
+
capy.project(path)
|
|
412
|
+
|
|
413
|
+
|
|
360
414
|
if __name__ == "__main__":
|
|
361
415
|
unittest.main()
|
|
@@ -345,7 +345,10 @@ class TestBrands(unittest.TestCase):
|
|
|
345
345
|
self.assertFalse(found["switch.radiateur"].state()["on"])
|
|
346
346
|
|
|
347
347
|
def test_unreachable(self):
|
|
348
|
-
|
|
348
|
+
silent = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # Un port gardé, qui ne répond jamais
|
|
349
|
+
silent.bind(("127.0.0.1", 0))
|
|
350
|
+
self.addCleanup(silent.close)
|
|
351
|
+
lamp = domo._wiz.WizLight("fantome", "127.0.0.1", port=silent.getsockname()[1])
|
|
349
352
|
self.assertEqual(lamp.state()["reachable"], False)
|
|
350
353
|
with self.assertRaisesRegex(ConnectionError, "'fantome' \\(wiz\\) ne répond pas"):
|
|
351
354
|
lamp.on()
|
|
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
|