Compare commits
13 Commits
059552eb81
...
902a626776
| Author | SHA1 | Date | |
|---|---|---|---|
| 902a626776 | |||
| fea6467396 | |||
| 23f7f78178 | |||
| 0ea3e846f1 | |||
| 41312b0f75 | |||
| 0ab04110ad | |||
| cc0365a2e1 | |||
| 904d5e3475 | |||
| 9c33d832b7 | |||
| e7a55b4788 | |||
|
|
b7ef783790 | ||
| 0dfc9356fe | |||
| 4f2d3018b5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.pem
|
*.pem
|
||||||
.env
|
.env
|
||||||
|
venv
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from machine import Pin,ADC
|
|
||||||
|
|
||||||
|
|
||||||
ldr_sensor_pin = 35
|
|
||||||
adc = ADC(Pin(ldr_sensor_pin))
|
|
||||||
adc.width(ADC.WIDTH_10BIT)
|
|
||||||
adc.atten(ADC.ATTN_11DB)
|
|
||||||
|
|
||||||
def luminosite_detection():
|
|
||||||
while True:
|
|
||||||
luminosite = adc.read()
|
|
||||||
print (luminosite)
|
|
||||||
if luminosite > 300:
|
|
||||||
led_verte_luminosite.on()
|
|
||||||
else:
|
|
||||||
led_verte_luminosite.off()
|
|
||||||
utime.sleep(0.5)
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
from machine import Pin,PWM
|
|
||||||
|
|
||||||
buzzer_pin = 11
|
|
||||||
|
|
||||||
buzzer = PWM(Pin(buzzer_pin), freq=440, duty=0)
|
|
||||||
|
|
||||||
def activate_alarm():
|
|
||||||
for _ in range(3):
|
|
||||||
buzzer.duty(512)
|
|
||||||
utime.sleep(0.5)
|
|
||||||
buzzer.duty(0)
|
|
||||||
utime.sleep(0.5)
|
|
||||||
|
|
||||||
243
composants/byPanda/ALARM_V1.py
Normal file
243
composants/byPanda/ALARM_V1.py
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
# ── Numérotation BCM ────────────────────────────────────────────────────────
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
# ── Broches ─────────────────────────────────────────────────────────────────
|
||||||
|
PIN_LED_R = 17
|
||||||
|
PIN_LED_G = 22
|
||||||
|
PIN_LED_B = 27
|
||||||
|
PIN_PIR = 15
|
||||||
|
PIN_BUZZER = 18
|
||||||
|
|
||||||
|
# Keypad 4x4 — 4 lignes (sorties) + 4 colonnes (entrées pull-up)
|
||||||
|
ROWS = [5, 6, 13, 19] # R1 R2 R3 R4
|
||||||
|
COLS = [26, 12, 16, 20] # C1 C2 C3 C4
|
||||||
|
|
||||||
|
KEYPAD_MAP = [
|
||||||
|
['1', '2', '3', 'A'],
|
||||||
|
['4', '5', '6', 'B'],
|
||||||
|
['7', '8', '9', 'C'],
|
||||||
|
['*', '0', '#', 'D'],
|
||||||
|
]
|
||||||
|
|
||||||
|
# ── Code secret (modifiable ici) ─────────────────────────────────────────────
|
||||||
|
CODE_SECRET = "1234"
|
||||||
|
|
||||||
|
# ── Configuration GPIO ───────────────────────────────────────────────────────
|
||||||
|
GPIO.setup(PIN_LED_R, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_LED_G, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_LED_B, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_BUZZER, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_PIR, GPIO.IN)
|
||||||
|
|
||||||
|
for row in ROWS:
|
||||||
|
GPIO.setup(row, GPIO.OUT, initial=GPIO.HIGH)
|
||||||
|
for col in COLS:
|
||||||
|
GPIO.setup(col, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
|
# ── État global ──────────────────────────────────────────────────────────────
|
||||||
|
etat = "desarmee"
|
||||||
|
etat_lock = threading.Lock()
|
||||||
|
|
||||||
|
_stop_buzzer = threading.Event()
|
||||||
|
_thread_buzzer = None
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# LED RGB
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def led(r=False, g=False, b=False):
|
||||||
|
"""Allume la LED RGB avec la couleur voulue."""
|
||||||
|
GPIO.output(PIN_LED_R, GPIO.HIGH if r else GPIO.LOW)
|
||||||
|
GPIO.output(PIN_LED_G, GPIO.HIGH if g else GPIO.LOW)
|
||||||
|
GPIO.output(PIN_LED_B, GPIO.HIGH if b else GPIO.LOW)
|
||||||
|
|
||||||
|
def led_bleu(): led(b=True)
|
||||||
|
def led_vert(): led(g=True)
|
||||||
|
def led_rouge(): led(r=True)
|
||||||
|
def led_off(): led()
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Buzzer
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def bip(nb=1, duree=0.08, pause=0.12):
|
||||||
|
"""Émet nb bip(s) courts."""
|
||||||
|
for _ in range(nb):
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.HIGH)
|
||||||
|
time.sleep(duree)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
time.sleep(pause)
|
||||||
|
|
||||||
|
def _buzzer_continu(stop_event: threading.Event):
|
||||||
|
"""Boucle interne : buzzer ON/OFF jusqu'à stop_event."""
|
||||||
|
while not stop_event.is_set():
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.HIGH)
|
||||||
|
time.sleep(0.5)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
time.sleep(0.5)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Keypad 4x4
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def lire_touche():
|
||||||
|
"""
|
||||||
|
Scan matriciel : met chaque ligne à LOW tour à tour
|
||||||
|
et lit les colonnes. Retourne la touche ou None.
|
||||||
|
"""
|
||||||
|
for i, row in enumerate(ROWS):
|
||||||
|
GPIO.output(row, GPIO.LOW)
|
||||||
|
for j, col in enumerate(COLS):
|
||||||
|
if GPIO.input(col) == GPIO.LOW:
|
||||||
|
time.sleep(0.05) # anti-rebond
|
||||||
|
while GPIO.input(col) == GPIO.LOW:
|
||||||
|
pass # attente relâchement
|
||||||
|
GPIO.output(row, GPIO.HIGH)
|
||||||
|
return KEYPAD_MAP[i][j]
|
||||||
|
GPIO.output(row, GPIO.HIGH)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def lire_code(nb_chiffres=4, timeout=30):
|
||||||
|
"""
|
||||||
|
Attend nb_chiffres touches numériques sur le keypad.
|
||||||
|
Retourne la chaîne saisie ou '' si timeout.
|
||||||
|
"""
|
||||||
|
saisi = ""
|
||||||
|
debut = time.time()
|
||||||
|
print(" Code : ", end="", flush=True)
|
||||||
|
while len(saisi) < nb_chiffres:
|
||||||
|
if time.time() - debut > timeout:
|
||||||
|
print("\n [Timeout — saisie annulée]")
|
||||||
|
return ""
|
||||||
|
touche = lire_touche()
|
||||||
|
if touche and touche.isdigit():
|
||||||
|
saisi += touche
|
||||||
|
print("*", end="", flush=True)
|
||||||
|
time.sleep(0.05)
|
||||||
|
print()
|
||||||
|
return saisi
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Transitions d'état
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def passer_en_desarmee():
|
||||||
|
global etat, _thread_buzzer
|
||||||
|
_stop_buzzer.set()
|
||||||
|
if _thread_buzzer and _thread_buzzer.is_alive():
|
||||||
|
_thread_buzzer.join()
|
||||||
|
with etat_lock:
|
||||||
|
etat = "desarmee"
|
||||||
|
led_bleu()
|
||||||
|
print("[ÉTAT] ● DÉSARMÉE — LED bleue")
|
||||||
|
|
||||||
|
def passer_en_armee():
|
||||||
|
global etat
|
||||||
|
with etat_lock:
|
||||||
|
etat = "armee"
|
||||||
|
led_vert()
|
||||||
|
bip(nb=2) # 2 petits bips = armée avec succès
|
||||||
|
print("[ÉTAT] ● ARMÉE — LED verte — PIR actif")
|
||||||
|
|
||||||
|
def passer_en_declenchee():
|
||||||
|
global etat, _thread_buzzer
|
||||||
|
with etat_lock:
|
||||||
|
etat = "declenchee"
|
||||||
|
led_rouge()
|
||||||
|
print("[ÉTAT] ● DÉCLENCHÉE — LED rouge — buzzer actif")
|
||||||
|
_stop_buzzer.clear()
|
||||||
|
_thread_buzzer = threading.Thread(
|
||||||
|
target=_buzzer_continu, args=(_stop_buzzer,), daemon=True
|
||||||
|
)
|
||||||
|
_thread_buzzer.start()
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Thread : surveillance PIR
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def _surveiller_pir(stop_evt: threading.Event):
|
||||||
|
"""Lit le PIR toutes les 100 ms. Déclenche si mouvement et armée."""
|
||||||
|
print("[PIR] Surveillance démarrée")
|
||||||
|
while not stop_evt.is_set():
|
||||||
|
with etat_lock:
|
||||||
|
etat_local = etat
|
||||||
|
if etat_local == "armee" and GPIO.input(PIN_PIR) == GPIO.HIGH:
|
||||||
|
print("[PIR] ⚠ Mouvement détecté !")
|
||||||
|
passer_en_declenchee()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
# Boucle principale
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def boucle_principale():
|
||||||
|
global etat
|
||||||
|
|
||||||
|
# Démarrage : LED bleue (désarmée)
|
||||||
|
passer_en_desarmee()
|
||||||
|
|
||||||
|
# Thread PIR en arrière-plan
|
||||||
|
stop_pir = threading.Event()
|
||||||
|
thread_pir = threading.Thread(
|
||||||
|
target=_surveiller_pir, args=(stop_pir,), daemon=True
|
||||||
|
)
|
||||||
|
thread_pir.start()
|
||||||
|
|
||||||
|
print("\n=== Système d'alarme démarré ===")
|
||||||
|
print(" Tapez le code sur le keypad pour armer / désarmer.\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
with etat_lock:
|
||||||
|
etat_local = etat
|
||||||
|
|
||||||
|
# ── DÉSARMÉE : attente d'un code pour armer ──────────────────────
|
||||||
|
if etat_local == "desarmee":
|
||||||
|
print(" → Saisir le code pour ARMER :")
|
||||||
|
code = lire_code(nb_chiffres=len(CODE_SECRET))
|
||||||
|
if code == CODE_SECRET:
|
||||||
|
print(" ✔ Code correct → armement")
|
||||||
|
passer_en_armee()
|
||||||
|
elif code != "":
|
||||||
|
print(" ✘ Code incorrect")
|
||||||
|
bip(nb=1, duree=0.4) # 1 bip long = erreur
|
||||||
|
|
||||||
|
# ── ARMÉE : le thread PIR gère le déclenchement ──────────────────
|
||||||
|
elif etat_local == "armee":
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
# ── DÉCLENCHÉE : attente du code pour désarmer ───────────────────
|
||||||
|
elif etat_local == "declenchee":
|
||||||
|
print(" → Saisir le code pour DÉSARMER :")
|
||||||
|
code = lire_code(nb_chiffres=len(CODE_SECRET))
|
||||||
|
if code == CODE_SECRET:
|
||||||
|
print(" ✔ Code correct → désarmement")
|
||||||
|
passer_en_desarmee()
|
||||||
|
elif code != "":
|
||||||
|
print(" ✘ Code incorrect — alarme maintenue")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[INFO] Arrêt demandé (Ctrl+C)")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
stop_pir.set()
|
||||||
|
_stop_buzzer.set()
|
||||||
|
led_off()
|
||||||
|
GPIO.cleanup()
|
||||||
|
print("[INFO] GPIO libérés. Fin du programme.")
|
||||||
|
|
||||||
|
|
||||||
|
# ════════════════════════════════════════════════════════════════════════════
|
||||||
@@ -1,19 +1,14 @@
|
|||||||
import Adafruit_DHT as dht
|
import Adafruit_DHT as dht
|
||||||
import time as t
|
|
||||||
import RPi.GPIO as GPIO
|
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BOARD)
|
# On définit juste le capteur et la broche (Rappel : 25 en BCM = broche physique 22)
|
||||||
capteur = dht.DHT11
|
capteur = dht.DHT11
|
||||||
pin = 25
|
pin = 25
|
||||||
|
|
||||||
def lire_temperature():
|
def lire_temperature():
|
||||||
humidite, temperature = dht.read_retry(capteur, pin)
|
humidite, temperature = dht.read_retry(capteur, pin)
|
||||||
|
|
||||||
|
# On renvoie la température au script principal !
|
||||||
if temperature is not None:
|
if temperature is not None:
|
||||||
print("Temp :", temperature, "°C")
|
return temperature
|
||||||
else:
|
else:
|
||||||
print("Erreur")
|
return 0 # Sécurité si le capteur bugge, pour ne pas faire planter l'affichage
|
||||||
|
|
||||||
t.sleep(2)
|
|
||||||
|
|
||||||
lire_temperature()
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import time
|
import time
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BOARD)
|
GPIO.setmode(GPIO.BCM)
|
||||||
GPIO.setwarnings(False)
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
|
||||||
@@ -22,17 +22,17 @@ class SystemeAlarme:
|
|||||||
# -----------------------------
|
# -----------------------------
|
||||||
# Définition des pins physiques
|
# Définition des pins physiques
|
||||||
# -----------------------------
|
# -----------------------------
|
||||||
self.pinPir = 22
|
self.pinPir = 15
|
||||||
self.pinBuzzer = 12
|
self.pinBuzzer = 18
|
||||||
|
|
||||||
self.pinLedRouge = 11
|
self.pinLedRouge = 17
|
||||||
self.pinLedVerte = 13
|
self.pinLedVerte = 27
|
||||||
self.pinLedBleue = 15
|
self.pinLedBleue = 22
|
||||||
|
|
||||||
# Clavier 4x4
|
# Clavier 4x4
|
||||||
# 4 lignes + 4 colonnes
|
# 4 lignes + 4 colonnes
|
||||||
self.lignes = [29, 31, 33, 35]
|
self.lignes = [5, 6, 13, 19]
|
||||||
self.colonnes = [37, 32, 36, 38]
|
self.colonnes = [26, 12, 16, 20]
|
||||||
|
|
||||||
# Disposition classique d'un clavier 4x4
|
# Disposition classique d'un clavier 4x4
|
||||||
self.touches = [
|
self.touches = [
|
||||||
@@ -243,4 +243,4 @@ class SystemeAlarme:
|
|||||||
Remet les sorties dans un état propre à la fermeture.
|
Remet les sorties dans un état propre à la fermeture.
|
||||||
"""
|
"""
|
||||||
GPIO.output(self.pinBuzzer, GPIO.LOW)
|
GPIO.output(self.pinBuzzer, GPIO.LOW)
|
||||||
self.definirCouleur(False, False, False)
|
self.definirCouleur(False, False, False)
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import time
|
import time
|
||||||
from alarme import SystemeAlarme
|
from ALARM_V1 import *
|
||||||
from porterfid import SystemePorteRFID
|
from porterfid import SystemePorteRFID
|
||||||
|
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
@@ -15,17 +15,22 @@ from porterfid import SystemePorteRFID
|
|||||||
alarme = SystemeAlarme()
|
alarme = SystemeAlarme()
|
||||||
porte = SystemePorteRFID()
|
porte = SystemePorteRFID()
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
# Mise à jour des deux modules locaux
|
|
||||||
alarme.mettreAJour()
|
|
||||||
porte.mettreAJour()
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print("\nArrêt manuel du programme.")
|
|
||||||
|
|
||||||
finally:
|
def call_board1():
|
||||||
# On remet les sorties dans un état propre avant de quitter
|
try:
|
||||||
alarme.cleanup()
|
while True:
|
||||||
porte.cleanup()
|
# Mise à jour des deux modules locaux
|
||||||
|
ALARM_V1.boucle_principale()
|
||||||
|
porte.mettreAJour()
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
porte.cleanup()
|
||||||
|
alarme.cleanup()
|
||||||
|
print("\nArrêt manuel du programme.")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# On remet les sorties dans un état propre avant de quitter
|
||||||
|
alarme.cleanup()
|
||||||
|
porte.cleanup()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
import time as t
|
import time as t
|
||||||
from septsegments import afficher_temperature
|
from septsegments import afficher_temperature
|
||||||
|
from DHT11 import lire_temperature
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
GPIO.setmode(GPIO.BCM)
|
||||||
GPIO.setwarnings(False)
|
GPIO.setwarnings(False)
|
||||||
@@ -11,32 +12,38 @@ GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
|||||||
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
def test_boutons():
|
def test_boutons():
|
||||||
temperature = 18
|
temperature_DHT = lire_temperature()
|
||||||
|
temperature_cible = 18
|
||||||
|
|
||||||
etatPrecedent_up = GPIO.input(bouton_up)
|
etatPrecedent_up = GPIO.input(bouton_up)
|
||||||
etatPrecedent_down = GPIO.input(bouton_down)
|
etatPrecedent_down = GPIO.input(bouton_down)
|
||||||
|
|
||||||
|
print("Thermostat lancé ! Appuie sur UP (23) ou DOWN (24).")
|
||||||
|
|
||||||
print("Test lancé ! Appuie sur UP (23) pour monter, DOWN (24) pour descendre.")
|
afficher_temperature(temperature_DHT, temperature_cible)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
etat_up = GPIO.input(bouton_up)
|
etat_up = GPIO.input(bouton_up)
|
||||||
etat_down = GPIO.input(bouton_down)
|
etat_down = GPIO.input(bouton_down)
|
||||||
|
|
||||||
if etat_up != etatPrecedent_up:
|
if etat_up != etatPrecedent_up:
|
||||||
if etat_up == 0:
|
if etat_up == 0:
|
||||||
print("Bouton UP Appuyé ⬆️")
|
print("Bouton UP Appuyé ⬆️")
|
||||||
temperature += 1
|
temperature_cible += 1
|
||||||
if temperature >= 40:
|
if temperature_cible >= 40:
|
||||||
temperature = 40
|
temperature_cible = 40
|
||||||
afficher_temperature(21,temperature)
|
|
||||||
|
afficher_temperature(temperature_DHT, temperature_cible)
|
||||||
etatPrecedent_up = etat_up
|
etatPrecedent_up = etat_up
|
||||||
|
-
|
||||||
if etat_down != etatPrecedent_down:
|
if etat_down != etatPrecedent_down:
|
||||||
if etat_down == 0:
|
if etat_down == 0:
|
||||||
print("Bouton DOWN Appuyé ⬇️")
|
print("Bouton DOWN Appuyé ⬇️")
|
||||||
temperature -= 1
|
temperature_cible -= 1
|
||||||
if temperature <= 0:
|
if temperature_cible <= 0:
|
||||||
temperature = 0
|
temperature_cible = 0
|
||||||
afficher_temperature(21,temperature)
|
|
||||||
|
afficher_temperature(temperature_DHT, temperature_cible)
|
||||||
etatPrecedent_down = etat_down
|
etatPrecedent_down = etat_down
|
||||||
|
|
||||||
t.sleep(0.05)
|
t.sleep(0.05)
|
||||||
|
|||||||
43
composants/byPanda/bouton_servo.py
Normal file
43
composants/byPanda/bouton_servo.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time as t
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
servo = 12
|
||||||
|
GPIO.setup(servo, GPIO.OUT)
|
||||||
|
|
||||||
|
pwm = GPIO.PWM(servo, 50)
|
||||||
|
pwm.start(0)
|
||||||
|
bouton_up = 13
|
||||||
|
bouton_down = 36
|
||||||
|
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
|
def test_boutons():
|
||||||
|
etatPrecedent_up = GPIO.input(bouton_up)
|
||||||
|
etatPrecedent_down = GPIO.input(bouton_down)
|
||||||
|
|
||||||
|
print("Test lancé ! Appuie sur UP (23) pour monter, DOWN (24) pour descendre.")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
etat_up = GPIO.input(bouton_up)
|
||||||
|
etat_down = GPIO.input(bouton_down)
|
||||||
|
if etat_up != etatPrecedent_up:
|
||||||
|
if etat_up == 0:
|
||||||
|
print("Bouton UP Appuyé ⬆️")
|
||||||
|
print("Volet ouvert")
|
||||||
|
pwm.ChangeDutyCycle(2)
|
||||||
|
etatPrecedent_up = etat_up
|
||||||
|
if etat_down != etatPrecedent_down:
|
||||||
|
if etat_down == 0:
|
||||||
|
print("Bouton DOWN Appuyé ⬇️")
|
||||||
|
print("Volet fermé")
|
||||||
|
pwm.ChangeDutyCycle(7)
|
||||||
|
etatPrecedent_down = etat_down
|
||||||
|
t.sleep(0.05)
|
||||||
|
|
||||||
|
if name == "main":
|
||||||
|
try:
|
||||||
|
test_boutons()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\nFin du test")
|
||||||
@@ -8,7 +8,7 @@ import urllib3
|
|||||||
# On cache le gros texte d'avertissement orange (InsecureRequestWarning)
|
# On cache le gros texte d'avertissement orange (InsecureRequestWarning)
|
||||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BOARD)
|
GPIO.setmode(GPIO.BCM)
|
||||||
GPIO.setwarnings(False)
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ class SystemePorteRFID:
|
|||||||
Gère le lecteur RFID et la LED de la porte.
|
Gère le lecteur RFID et la LED de la porte.
|
||||||
L'authentification est maintenant gérée par le serveur Flask et MariaDB.
|
L'authentification est maintenant gérée par le serveur Flask et MariaDB.
|
||||||
"""
|
"""
|
||||||
self.pinLed = 40
|
self.pinLed = 4
|
||||||
GPIO.setup(self.pinLed, GPIO.OUT, initial=GPIO.LOW)
|
GPIO.setup(self.pinLed, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
|
||||||
self.lecteur = SimpleMFRC522()
|
self.lecteur = SimpleMFRC522()
|
||||||
@@ -54,15 +54,11 @@ class SystemePorteRFID:
|
|||||||
GPIO.output(self.pinLed, GPIO.LOW)
|
GPIO.output(self.pinLed, GPIO.LOW)
|
||||||
|
|
||||||
def traiterBadge(self, badgeId):
|
def traiterBadge(self, badgeId):
|
||||||
"""Envoie le numéro du badge à Flask pour vérification dans MariaDB."""
|
|
||||||
print(f"Badge détecté : {badgeId}")
|
print(f"Badge détecté : {badgeId}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Vérifie bien que l'URL correspond à ta route Flask (avec ou sans /api)
|
|
||||||
url = "https://127.0.0.1/rfid-scan"
|
url = "https://127.0.0.1/rfid-scan"
|
||||||
donnees = {"badge_id": str(badgeId)}
|
donnees = {"badge_id": str(badgeId)}
|
||||||
|
|
||||||
# verify=False est nécessaire car le serveur local utilise un certificat auto-signé
|
|
||||||
reponse = requests.post(url, json=donnees, timeout=2, verify=False)
|
reponse = requests.post(url, json=donnees, timeout=2, verify=False)
|
||||||
data = reponse.json()
|
data = reponse.json()
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,32 @@
|
|||||||
import tm1637
|
import tm1637
|
||||||
import time as t
|
import time as t
|
||||||
|
|
||||||
display = tm1637.TM1637(clk=4, dio=17)
|
_display = None
|
||||||
display.brightness(2)
|
|
||||||
|
def get_display():
|
||||||
|
global _display
|
||||||
|
if _display is None:
|
||||||
|
_display = tm1637.TM1637(clk=4, dio=17)
|
||||||
|
_display.brightness(2)
|
||||||
|
return _display
|
||||||
|
|
||||||
def afficher_temperature(temperature, temperature_moyenne):
|
def afficher_temperature(temperature, temperature_moyenne):
|
||||||
print(f"Test affichage: Cible {temperature} | Moyenne {temperature_moyenne}")
|
print(f"Test affichage: Cible {temperature} : Moyenne {temperature_moyenne}")
|
||||||
try:
|
try:
|
||||||
temp1 = int(temperature)
|
temp1 = int(temperature)
|
||||||
temp2 = int(temperature_moyenne)
|
temp2 = int(temperature_moyenne)
|
||||||
texte_ecran = f"{temp1:02d}{temp2:02d}"
|
|
||||||
|
|
||||||
display.show(texte_ecran)
|
disp = get_display()
|
||||||
|
|
||||||
|
|
||||||
|
texte_ecran = f"{temp1:02d}{temp2:02d}"
|
||||||
|
|
||||||
|
if hasattr(disp, 'show_doublepoint'):
|
||||||
|
disp.show_doublepoint(True)
|
||||||
|
elif hasattr(disp, 'point'):
|
||||||
|
disp.point(True)
|
||||||
|
|
||||||
|
disp.show(texte_ecran)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erreur d'affichage : {e}")
|
print(f"Erreur d'affichage : {e}")
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
|
|
||||||
from gpiozero import LED
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
led = LED(17)
|
|
||||||
|
|
||||||
while True:
|
|
||||||
led.on()
|
|
||||||
sleep(1)
|
|
||||||
led.off()
|
|
||||||
sleep(1)
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
"""
|
|
||||||
code adapté un une utilisation avec mcp3008 pour ldr
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
import time
|
|
||||||
from gpiozero import LED, Button, PWMOutputDevice, AngularServo
|
|
||||||
from gpiozero import MCP3008
|
|
||||||
|
|
||||||
|
|
||||||
led_verte = LED(12)
|
|
||||||
led_verte_luminosite = LED(25)
|
|
||||||
led_rouge = LED(13)
|
|
||||||
led_bleue = LED(14)
|
|
||||||
led_rouge_gas = LED(10)
|
|
||||||
servo = AngularServo(32, min_angle=0, max_angle=180)
|
|
||||||
pir_sensor = Button(33)
|
|
||||||
gas_sensor = Button(34)
|
|
||||||
buzzer = PWMOutputDevice(11)
|
|
||||||
ldr_sensor = MCP3008(channel=0)
|
|
||||||
|
|
||||||
|
|
||||||
def activate_alarm():
|
|
||||||
for _ in range(3):
|
|
||||||
buzzer.value = 0.5
|
|
||||||
time.sleep(0.5)
|
|
||||||
buzzer.value = 0
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
|
|
||||||
def pir_detection():
|
|
||||||
while True:
|
|
||||||
if pir_sensor.is_pressed:
|
|
||||||
led_bleue.on()
|
|
||||||
time.sleep(3)
|
|
||||||
led_bleue.off()
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
def luminosite_detection():
|
|
||||||
while True:
|
|
||||||
luminosite = ldr_sensor.value * 1023
|
|
||||||
print(luminosite)
|
|
||||||
if luminosite > 300:
|
|
||||||
led_verte_luminosite.on()
|
|
||||||
else:
|
|
||||||
led_verte_luminosite.off()
|
|
||||||
time.sleep(0.5)
|
|
||||||
|
|
||||||
import threading
|
|
||||||
threading.Thread(target=pir_detection, daemon=True).start()
|
|
||||||
threading.Thread(target=luminosite_detection, daemon=True).start()
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from machine import Pin
|
|
||||||
|
|
||||||
pir_sensor = Pin(33, Pin.IN)
|
|
||||||
|
|
||||||
|
|
||||||
def pir_detection():
|
|
||||||
while True:
|
|
||||||
if pir_sensor.value() == 1:
|
|
||||||
led_bleue.on()
|
|
||||||
utime.sleep(3)
|
|
||||||
led_bleue.off()
|
|
||||||
utime.sleep(0.1)
|
|
||||||
205
composants/test/ALARM_V2.py
Normal file
205
composants/test/ALARM_V2.py
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
|
PIN_LED_R = 17
|
||||||
|
PIN_LED_G = 22
|
||||||
|
PIN_LED_B = 27
|
||||||
|
PIN_PIR = 15
|
||||||
|
PIN_BUZZER = 18
|
||||||
|
|
||||||
|
ROWS = [5, 6, 13, 19]
|
||||||
|
COLS = [26, 12, 16, 20]
|
||||||
|
|
||||||
|
KEYPAD_MAP = [
|
||||||
|
['1', '2', '3', 'A'],
|
||||||
|
['4', '5', '6', 'B'],
|
||||||
|
['7', '8', '9', 'C'],
|
||||||
|
['*', '0', '#', 'D'],
|
||||||
|
]
|
||||||
|
|
||||||
|
CODE_SECRET = "1234"
|
||||||
|
|
||||||
|
GPIO.setup(PIN_LED_R, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_LED_G, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_LED_B, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_BUZZER, GPIO.OUT, initial=GPIO.LOW)
|
||||||
|
GPIO.setup(PIN_PIR, GPIO.IN)
|
||||||
|
|
||||||
|
for row in ROWS:
|
||||||
|
GPIO.setup(row, GPIO.OUT, initial=GPIO.HIGH)
|
||||||
|
for col in COLS:
|
||||||
|
GPIO.setup(col, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
|
etat = "desarmee"
|
||||||
|
etat_lock = threading.Lock()
|
||||||
|
|
||||||
|
_stop_buzzer = threading.Event()
|
||||||
|
_thread_buzzer = None
|
||||||
|
|
||||||
|
def led(r=False, g=False, b=False):
|
||||||
|
"""Allume la LED RGB avec la couleur voulue."""
|
||||||
|
GPIO.output(PIN_LED_R, GPIO.HIGH if r else GPIO.LOW)
|
||||||
|
GPIO.output(PIN_LED_G, GPIO.HIGH if g else GPIO.LOW)
|
||||||
|
GPIO.output(PIN_LED_B, GPIO.HIGH if b else GPIO.LOW)
|
||||||
|
|
||||||
|
def led_bleu(): led(b=True)
|
||||||
|
def led_vert(): led(g=True)
|
||||||
|
def led_rouge(): led(r=True)
|
||||||
|
def led_off(): led()
|
||||||
|
|
||||||
|
def bip(nb=1, duree=0.08, pause=0.12):
|
||||||
|
"""Émet nb bip(s) courts."""
|
||||||
|
for _ in range(nb):
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.HIGH)
|
||||||
|
time.sleep(duree)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
time.sleep(pause)
|
||||||
|
|
||||||
|
def _buzzer_continu(stop_event: threading.Event):
|
||||||
|
"""Boucle interne : buzzer ON/OFF jusqu'à stop_event."""
|
||||||
|
while not stop_event.is_set():
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.HIGH)
|
||||||
|
time.sleep(0.5)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
time.sleep(0.5)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
|
||||||
|
def lire_touche():
|
||||||
|
"""
|
||||||
|
Scan matriciel : met chaque ligne à LOW tour à tour
|
||||||
|
et lit les colonnes. Retourne la touche ou None.
|
||||||
|
"""
|
||||||
|
for i, row in enumerate(ROWS):
|
||||||
|
GPIO.output(row, GPIO.LOW)
|
||||||
|
for j, col in enumerate(COLS):
|
||||||
|
if GPIO.input(col) == GPIO.LOW:
|
||||||
|
time.sleep(0.05)
|
||||||
|
while GPIO.input(col) == GPIO.LOW:
|
||||||
|
pass
|
||||||
|
GPIO.output(row, GPIO.HIGH)
|
||||||
|
return KEYPAD_MAP[i][j]
|
||||||
|
GPIO.output(row, GPIO.HIGH)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def lire_code(timeout=30):
|
||||||
|
saisi = ""
|
||||||
|
debut = time.time()
|
||||||
|
print(" Code (# pour valider, * pour effacer) : ", end="", flush=True)
|
||||||
|
while True:
|
||||||
|
if time.time() - debut > timeout:
|
||||||
|
print("\n [Timeout — saisie annulée]")
|
||||||
|
return ""
|
||||||
|
touche = lire_touche()
|
||||||
|
if touche is None:
|
||||||
|
time.sleep(0.05)
|
||||||
|
continue
|
||||||
|
if touche == '#':
|
||||||
|
print()
|
||||||
|
return saisi
|
||||||
|
elif touche == '*':
|
||||||
|
if saisi:
|
||||||
|
saisi = saisi[:-1]
|
||||||
|
print("\b \b", end="", flush=True)
|
||||||
|
elif touche.isdigit():
|
||||||
|
saisi += touche
|
||||||
|
print("*", end="", flush=True)
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
def passer_en_desarmee():
|
||||||
|
global etat, _thread_buzzer
|
||||||
|
_stop_buzzer.set()
|
||||||
|
if _thread_buzzer and _thread_buzzer.is_alive():
|
||||||
|
_thread_buzzer.join()
|
||||||
|
with etat_lock:
|
||||||
|
etat = "desarmee"
|
||||||
|
led_bleu()
|
||||||
|
print("[ÉTAT] ● DÉSARMÉE — LED bleue")
|
||||||
|
|
||||||
|
def passer_en_armee():
|
||||||
|
global etat
|
||||||
|
with etat_lock:
|
||||||
|
etat = "armee"
|
||||||
|
led_vert()
|
||||||
|
bip(nb=2)
|
||||||
|
print("[ÉTAT] ● ARMÉE — LED verte — PIR actif")
|
||||||
|
|
||||||
|
def passer_en_declenchee():
|
||||||
|
global etat, _thread_buzzer
|
||||||
|
with etat_lock:
|
||||||
|
etat = "declenchee"
|
||||||
|
led_rouge()
|
||||||
|
print("[ÉTAT] ● DÉCLENCHÉE — LED rouge — buzzer actif")
|
||||||
|
_stop_buzzer.clear()
|
||||||
|
_thread_buzzer = threading.Thread(
|
||||||
|
target=_buzzer_continu, args=(_stop_buzzer,), daemon=True
|
||||||
|
)
|
||||||
|
_thread_buzzer.start()
|
||||||
|
|
||||||
|
def _surveiller_pir(stop_evt: threading.Event):
|
||||||
|
"""Lit le PIR toutes les 100 ms. Déclenche si mouvement et armée."""
|
||||||
|
print("[PIR] Surveillance démarrée")
|
||||||
|
while not stop_evt.is_set():
|
||||||
|
with etat_lock:
|
||||||
|
etat_local = etat
|
||||||
|
if etat_local == "armee" and GPIO.input(PIN_PIR) == GPIO.HIGH:
|
||||||
|
print("[PIR] ⚠ Mouvement détecté !")
|
||||||
|
passer_en_declenchee()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
def boucle_principale():
|
||||||
|
global etat
|
||||||
|
|
||||||
|
passer_en_desarmee()
|
||||||
|
|
||||||
|
stop_pir = threading.Event()
|
||||||
|
thread_pir = threading.Thread(
|
||||||
|
target=_surveiller_pir, args=(stop_pir,), daemon=True
|
||||||
|
)
|
||||||
|
thread_pir.start()
|
||||||
|
|
||||||
|
print("\n=== Système d'alarme démarré ===")
|
||||||
|
print(" Tapez le code sur le keypad pour armer / désarmer.\n")
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
with etat_lock:
|
||||||
|
etat_local = etat
|
||||||
|
|
||||||
|
if etat_local == "desarmee":
|
||||||
|
print(" → Saisir le code pour ARMER :")
|
||||||
|
code = lire_code() # CORRECTION ICI
|
||||||
|
if code == CODE_SECRET:
|
||||||
|
print(" ✔ Code correct → armement")
|
||||||
|
passer_en_armee()
|
||||||
|
elif code != "":
|
||||||
|
print(" ✘ Code incorrect")
|
||||||
|
bip(nb=1, duree=0.4)
|
||||||
|
|
||||||
|
elif etat_local == "armee":
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
elif etat_local == "declenchee":
|
||||||
|
print(" → Saisir le code pour DÉSARMER :")
|
||||||
|
code = lire_code() # CORRECTION ICI
|
||||||
|
if code == CODE_SECRET:
|
||||||
|
print(" ✔ Code correct → désarmement")
|
||||||
|
passer_en_desarmee() # AJOUT : pour que l'alarme s'arrête vraiment
|
||||||
|
elif code != "":
|
||||||
|
print(" ✘ Code incorrect — alarme maintenue")
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[INFO] Arrêt demandé (Ctrl+C)")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
stop_pir.set()
|
||||||
|
_stop_buzzer.set()
|
||||||
|
led_off()
|
||||||
|
GPIO.cleanup()
|
||||||
|
print("[INFO] GPIO libérés. Fin du programme.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
boucle_principale()
|
||||||
26
composants/test/LDR.py
Normal file
26
composants/test/LDR.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import RPi.GPIO as GPIO
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
LDR_PIN = 20# Broche GPIO connectée au circuit LDR
|
||||||
|
SEUIL = 500 # Valeur de seuil à ajuster (0-1024)
|
||||||
|
|
||||||
|
GPIO.setmode(GPIO.BCM)
|
||||||
|
GPIO.setup(LDR_PIN, GPIO.IN)
|
||||||
|
|
||||||
|
|
||||||
|
def lire_ldr():
|
||||||
|
# Simulation de lecture analogique (nécessite MCP3008 ou circuit RC)
|
||||||
|
# Pour cet exemple, on simplifie par une lecture numérique
|
||||||
|
return GPIO.input(LDR_PIN)
|
||||||
|
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
luminosite = lire_ldr()
|
||||||
|
if luminosite < SEUIL:
|
||||||
|
print("Nuit : Allumage lumière")
|
||||||
|
else:
|
||||||
|
print("Jour : Extinction lumière")
|
||||||
|
time.sleep(1)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
GPIO.cleanup()
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
GPIO.setmode(GPIO.BCM)
|
GPIO.setmode(GPIO.BCM)
|
||||||
GPIO.setwarnings(False)
|
GPIO.setwarnings(False)
|
||||||
@@ -13,10 +15,17 @@ sys.path.insert(0, composants)
|
|||||||
from lumieres import SystemeLumieres
|
from lumieres import SystemeLumieres
|
||||||
from thermostat import SystemeThermostat
|
from thermostat import SystemeThermostat
|
||||||
#from volets import SystemeVolets
|
#from volets import SystemeVolets
|
||||||
from etatsystemes import EtatSysteme
|
from etatsysteme import EtatSysteme
|
||||||
from septsegments import afficher_temperature
|
from septsegments import afficher_temperature
|
||||||
|
|
||||||
app = FastAPI(title="Loustiques API - Pi 2")
|
app = FastAPI(title="L'API des loustiques")
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_credentials=False,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
controleur_lumieres = SystemeLumieres()
|
controleur_lumieres = SystemeLumieres()
|
||||||
controleur_thermostat = SystemeThermostat()
|
controleur_thermostat = SystemeThermostat()
|
||||||
@@ -56,13 +65,23 @@ async def read_temp():
|
|||||||
return {"success": False, "message": "Impossible de lire le capteur DHT11"}
|
return {"success": False, "message": "Impossible de lire le capteur DHT11"}
|
||||||
|
|
||||||
etatSysteme.signalerOk()
|
etatSysteme.signalerOk()
|
||||||
afficher_temperature(temp)
|
afficher_temperature(temp, 18)
|
||||||
return {"success": True, "temperature": temp}
|
return {"success": True, "temperature": temp}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
etatSysteme.signalerProbleme()
|
etatSysteme.signalerProbleme()
|
||||||
return {"success": False, "message": str(e)}
|
return {"success": False, "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
chemin_cle = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'key.pem')
|
||||||
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
chemin_cert = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'cert.pem')
|
||||||
|
uvicorn.run(
|
||||||
|
"main:app",
|
||||||
|
host="0.0.0.0",
|
||||||
|
port=8000,
|
||||||
|
ssl_keyfile=chemin_cle,
|
||||||
|
ssl_certfile=chemin_cert
|
||||||
|
)
|
||||||
183
fastapi/main.sh
Executable file
183
fastapi/main.sh
Executable file
@@ -0,0 +1,183 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cat << 'EOF'
|
||||||
|
|
||||||
|
_______ ________ ___ ___ _______ ________
|
||||||
|
|\ ___ \ |\ __ \|\ \|\ \|\ ___ \ |\ ____\
|
||||||
|
\ \ __/|\ \ \|\ \ \ \\\ \ \ __/|\ \ \___|
|
||||||
|
\ \ \_|/_\ \ ____\ \ __ \ \ \_|/_\ \ \
|
||||||
|
\ \ \_|\ \ \ \___|\ \ \ \ \ \ \_|\ \ \ \____
|
||||||
|
\ \_______\ \__\ \ \__\ \__\ \_______\ \_______\
|
||||||
|
\|_______|\|__| \|__|\|__|\|_______|\|_______|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ==============================================
|
||||||
|
# Script de configuration automatique Raspberry Pi - Projet IoT
|
||||||
|
# ==============================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SEPARATOR="=============================================="
|
||||||
|
|
||||||
|
print_step() {
|
||||||
|
echo ""
|
||||||
|
echo "$SEPARATOR"
|
||||||
|
echo " $1"
|
||||||
|
echo "$SEPARATOR"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vérification des droits sudo
|
||||||
|
if [ "$EUID" -ne 0 ]; then
|
||||||
|
echo " Ce script doit être exécuté avec sudo"
|
||||||
|
echo " Utilisation : sudo ./main.sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_step " Lancement du programme de configuration IoT"
|
||||||
|
print_step " Lancement du programme de configuration IoT"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 1. Mise à jour du système
|
||||||
|
# ----------------------------
|
||||||
|
print_step " Mise à jour du système (apt update & upgrade)"
|
||||||
|
print_step " Mise à jour du système (apt update & upgrade)"
|
||||||
|
if ! apt update && apt upgrade -y; then
|
||||||
|
echo " Erreur lors de la mise à jour du système"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Système mis à jour"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
|
||||||
|
#----------------------------
|
||||||
|
# 2. Installation de MariaDB x phpmyadmin
|
||||||
|
#------------------------------
|
||||||
|
read -p "Installer mariadb et phpMyAdmin ? (y/n)" db
|
||||||
|
if [ "$db" = 'y'];then
|
||||||
|
bash DB/main.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
#-------------------------------
|
||||||
|
# 3. Exportation des DB
|
||||||
|
#--------------------------------
|
||||||
|
read -p "Voulez-vous exporter des DB existantes ? (y/n) : " choix
|
||||||
|
|
||||||
|
if [ "$choix" = "y" ]; then
|
||||||
|
bash "$(dirname "$0")/export_db.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo 'je sais ya un problème de numéro mais wola aller lire le script db/export_db.sh je suis claquax au sol sur terre'
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 2. Installation de Python
|
||||||
|
# ----------------------------
|
||||||
|
print_step "Vérification / Installation de Python3"
|
||||||
|
if ! apt install python3 python3-pip python3-venv -y; then
|
||||||
|
echo "Erreur lors de l'installation de Python3"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
PYTHON_VERSION=$(python3 --version 2>&1)
|
||||||
|
echo " $PYTHON_VERSION installé"
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 3. Recherche des venvs existants
|
||||||
|
# ----------------------------
|
||||||
|
print_step "Recherche des environnements virtuels (venv) existants..."
|
||||||
|
|
||||||
|
SEARCH_DIRS=("$(pwd)")
|
||||||
|
VENV_LIST=()
|
||||||
|
|
||||||
|
for dir in "${SEARCH_DIRS[@]}"; do
|
||||||
|
if [ -d "$dir" ]; then
|
||||||
|
while IFS= read -r -d '' activate_path; do
|
||||||
|
venv_dir=$(dirname "$(dirname "$activate_path")")
|
||||||
|
if [ -f "$venv_dir/bin/python" ]; then
|
||||||
|
VENV_LIST+=("$venv_dir")
|
||||||
|
fi
|
||||||
|
done < <(find "$dir" -name "activate" -path "*/bin/activate" 2>/dev/null -print0)
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [ ${#VENV_LIST[@]} -eq 0 ]; then
|
||||||
|
echo " Aucun environnement virtuel trouvé dans : $(pwd)"
|
||||||
|
else
|
||||||
|
echo "${#VENV_LIST[@]} environnement(s) virtuel(s) trouvé(s) :"
|
||||||
|
for i in "${!VENV_LIST[@]}"; do
|
||||||
|
venv="${VENV_LIST[$i]}"
|
||||||
|
python_ver=$("$venv/bin/python" --version 2>&1)
|
||||||
|
echo ""
|
||||||
|
echo " [$((i+1))] Chemin : $venv"
|
||||||
|
echo " Python : $python_ver"
|
||||||
|
echo " ▶ Activer : source $venv/bin/activate"
|
||||||
|
done
|
||||||
|
SELECTED_VENV="${VENV_LIST[0]}"
|
||||||
|
echo "$SELECTED_VENV" > ./.venv_path
|
||||||
|
echo ""
|
||||||
|
echo "Venv sélectionné et enregistré : $SELECTED_VENV"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# 4. Créer un nouveau venv ?
|
||||||
|
# ----------------------------
|
||||||
|
print_step " Créer un nouvel environnement virtuel ?"
|
||||||
|
echo "Voulez-vous créer un nouveau venv ? (o/n)"
|
||||||
|
read -r CREATE_VENV
|
||||||
|
|
||||||
|
if [[ "$CREATE_VENV" =~ ^[oO]$ ]]; then
|
||||||
|
VENV_PATH="$SEARCH_DIRS/venv"
|
||||||
|
if python3 -m venv $SEARCH_DIRS/venv; then
|
||||||
|
echo ""
|
||||||
|
echo "Venv créé avec succès !"
|
||||||
|
echo " Chemin : $VENV_PATH"
|
||||||
|
echo " ▶ Activer : source $VENV_PATH/bin/activate"
|
||||||
|
|
||||||
|
|
||||||
|
echo "$VENV_PATH" > ./.venv_path
|
||||||
|
echo "Chemin enregistré dans .venv_path"
|
||||||
|
|
||||||
|
|
||||||
|
"$VENV_PATH/bin/pip" install --upgrade pip
|
||||||
|
|
||||||
|
|
||||||
|
if [ -f "./requirements.txt" ]; then
|
||||||
|
echo "Installation des dépendances depuis requirements.txt..."
|
||||||
|
"$VENV_PATH/bin/pip" install -r ./requirements.txt
|
||||||
|
echo " Dépendances installées"
|
||||||
|
|
||||||
|
else
|
||||||
|
echo " Aucun requirements.txt trouvé, installation des dépendances ignorée"
|
||||||
|
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Erreur lors de la création du venv à : $VENV_PATH"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " Création ignorée"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# Fin
|
||||||
|
# ----------------------------
|
||||||
|
print_step " Configuration terminée"
|
||||||
|
echo ""
|
||||||
|
if [ -f "./.venv_path" ]; then
|
||||||
|
echo "Venv configuré : $(cat ./.venv_path)"
|
||||||
|
echo " Pour l'activer manuellement : source $(cat ./.venv_path)/bin/activate"
|
||||||
|
else
|
||||||
|
echo " Aucun venv enregistré — relancez le script et créez un venv"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
fastapi
|
fastapi
|
||||||
uvicorn
|
uvicorn
|
||||||
rpi.gpio
|
rpi.gpio
|
||||||
|
python-tm1637
|
||||||
|
Adafruit_DHT
|
||||||
69
fastapi/run_api.sh
Executable file
69
fastapi/run_api.sh
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cat << 'EOF'
|
||||||
|
=============================
|
||||||
|
_______ ________ ___ ___ _______ ________
|
||||||
|
|\ ___ \ |\ __ \|\ \|\ \|\ ___ \ |\ ____\
|
||||||
|
\ \ __/|\ \ \|\ \ \ \\\ \ \ __/|\ \ \___|
|
||||||
|
\ \ \_|/_\ \ ____\ \ __ \ \ \_|/_\ \ \
|
||||||
|
\ \ \_|\ \ \ \___|\ \ \ \ \ \ \_|\ \ \ \____
|
||||||
|
\ \_______\ \__\ \ \__\ \__\ \_______\ \_______\
|
||||||
|
\|_______|\|__| \|__|\|__|\|_______|\|_______|
|
||||||
|
=============================
|
||||||
|
EOF
|
||||||
|
cat << 'EOF'
|
||||||
|
===============================
|
||||||
|
Vérification de la présence de python
|
||||||
|
=================================
|
||||||
|
EOF
|
||||||
|
|
||||||
|
VERSION_PYTHON=$(python3 --version 2>&1)
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Python est installé"
|
||||||
|
echo "Voici la version : $VERSION_PYTHON"
|
||||||
|
else
|
||||||
|
apt install -y python3
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << 'EOF'
|
||||||
|
===================================================
|
||||||
|
Vérification de la présence de la bibliothèque FLASK
|
||||||
|
====================================================
|
||||||
|
EOF
|
||||||
|
|
||||||
|
if venv/bin/python -m pip list | grep -qi 'flask'; then
|
||||||
|
echo "Flask existe bien"
|
||||||
|
else
|
||||||
|
echo "Flask n'est pas installé..."
|
||||||
|
echo "Lancement de l'installation..."
|
||||||
|
sleep 1
|
||||||
|
venv/bin/python -m pip install flask
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat << 'EOF'
|
||||||
|
=================================
|
||||||
|
Vérification des certificats SSL
|
||||||
|
==================================
|
||||||
|
EOF
|
||||||
|
|
||||||
|
bash ../web_secu/ssl.sh
|
||||||
|
|
||||||
|
cat << 'EOF'
|
||||||
|
=============================
|
||||||
|
Vérification du daemin Avahi
|
||||||
|
============================
|
||||||
|
EOF
|
||||||
|
bash ../web_secu/avahi.sh
|
||||||
|
|
||||||
|
|
||||||
|
cat << 'EOF'
|
||||||
|
================================
|
||||||
|
Lancement du serveur FLASK
|
||||||
|
================================
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
touch /var/log/loustique.log
|
||||||
|
chown ${SUDO_USER}:${SUDO_USER} /var/log/loustique.log
|
||||||
|
venv/bin/python composants/test/bouton.py
|
||||||
|
venv/bin/python main.py
|
||||||
222
flask/index.html
Normal file
222
flask/index.html
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
<title>Loustiques Home - Connexion</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: system-ui, sans-serif;
|
||||||
|
background: #1f1f1f;
|
||||||
|
color: #f0f0f0;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carte {
|
||||||
|
width: 380px;
|
||||||
|
max-width: 95vw;
|
||||||
|
background: #2a2a2a;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 40px 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.soustitre {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.champ {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 11px 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
border: 1px solid #3a3a3a;
|
||||||
|
border-radius: 6px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s;
|
||||||
|
color: #f0f0f0;
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
border-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #2563eb;
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: inherit;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover { background: #1d4ed8; }
|
||||||
|
button:active { background: #1e40af; }
|
||||||
|
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.message.error {
|
||||||
|
display: block;
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
.message.success {
|
||||||
|
display: block;
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 28px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="carte">
|
||||||
|
<h1>Loustiques Home</h1>
|
||||||
|
<p class="soustitre">Connectez-vous via mot de passe ou avec votre bagde pour accéder à votre espace.</p>
|
||||||
|
|
||||||
|
<div class="champ">
|
||||||
|
<label for="username">Identifiant</label>
|
||||||
|
<input type="text" id="username" placeholder="Nom du loustique" autocomplete="username" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="champ">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
<input type="password" id="password" placeholder="Wola ouais" autocomplete="current-password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="btn" onclick="handleLogin()">Se connecter</button>
|
||||||
|
|
||||||
|
<div class="message" id="msg"></div>
|
||||||
|
|
||||||
|
<p class="footer">Accès réservé aux utilisateurs ajoutés par les loustiques.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 1. Écoute de la touche Entrée
|
||||||
|
["username", "password"].forEach(id => {
|
||||||
|
document.getElementById(id).addEventListener("keydown", e => {
|
||||||
|
if (e.key === "Enter") handleLogin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Fonction de connexion manuelle (Mot de passe)
|
||||||
|
async function handleLogin() {
|
||||||
|
const username = document.getElementById("username").value.trim();
|
||||||
|
const password = document.getElementById("password").value;
|
||||||
|
const btn = document.getElementById("btn");
|
||||||
|
const msg = document.getElementById("msg");
|
||||||
|
|
||||||
|
msg.className = "message";
|
||||||
|
|
||||||
|
if (!username || !password) {
|
||||||
|
msg.className = "message error";
|
||||||
|
msg.textContent = "Veuillez remplir tous les champs.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.textContent = "Vérification...";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch("/login", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ username, password })
|
||||||
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
msg.className = "message success";
|
||||||
|
msg.textContent = "Connexion réussie !";
|
||||||
|
window.location.href = "/dashboard";
|
||||||
|
} else {
|
||||||
|
msg.className = "message error";
|
||||||
|
msg.textContent = data.message || "Identifiants incorrects.";
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
msg.className = "message error";
|
||||||
|
msg.textContent = "Impossible de contacter le serveur.";
|
||||||
|
} finally {
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.textContent = "Se connecter";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Écoute automatique du badge (RFID) - SORTI DE LA FONCTION !
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
// Attention : Vérifie que cette route correspond bien à celle dans main.py
|
||||||
|
// (J'avais mis /check-rfid-login dans mon exemple précédent)
|
||||||
|
const res = await fetch('/check-rfid-login');
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
const msg = document.getElementById("msg");
|
||||||
|
const btn = document.getElementById("btn");
|
||||||
|
const inputs = document.querySelectorAll("input");
|
||||||
|
|
||||||
|
btn.disabled = true;
|
||||||
|
inputs.forEach(input => input.disabled = true);
|
||||||
|
|
||||||
|
msg.className = "message success";
|
||||||
|
msg.textContent = "Badge reconnu ! Bienvenue " + data.username + "...";
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.href = "/dashboard";
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Erreurs ignorées silencieusement
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
flask/led.py
22
flask/led.py
@@ -1,22 +0,0 @@
|
|||||||
from time import *
|
|
||||||
from gpiozero import *
|
|
||||||
from signal import pause
|
|
||||||
from log import log
|
|
||||||
|
|
||||||
def led(utilisateur):
|
|
||||||
print('led allumé')
|
|
||||||
log.info(f'led allumé par {utilisateur}')
|
|
||||||
"""
|
|
||||||
print("One button to turn on or off")
|
|
||||||
led = LED(17)
|
|
||||||
btn = Button(20, bounce_time=None)
|
|
||||||
while True:
|
|
||||||
print('turn on')
|
|
||||||
led.on()
|
|
||||||
sleep(0.2) # to avoid bouncing
|
|
||||||
btn.wait_for_press()
|
|
||||||
print('turn off')
|
|
||||||
led.off()
|
|
||||||
sleep(0.2) # to avoid bouncing
|
|
||||||
btn.wait_for_press()
|
|
||||||
"""
|
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
from flask import Flask, render_template, request, jsonify
|
from flask import Flask, render_template, request, jsonify
|
||||||
|
import requests
|
||||||
from flask_talisman import Talisman
|
from flask_talisman import Talisman
|
||||||
from led import led
|
from led import led
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
import sys
|
import sys
|
||||||
import log
|
import log
|
||||||
from add_user import add_user
|
from add_user import add_user
|
||||||
@@ -10,14 +12,14 @@ import re
|
|||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
Talisman(app, force_https=True,
|
Talisman(app, force_https=True,
|
||||||
content_security_policy=False )
|
content_security_policy=False)
|
||||||
current_user = None
|
current_user = None
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
composants = os.path.join(BASE_DIR, "composants", "byPanda")
|
composants = os.path.join(BASE_DIR, "composants", "byPanda")
|
||||||
sys.path.insert(0, composants)
|
sys.path.insert(0, composants)
|
||||||
from alarme import SystemeAlarme
|
from alarme import SystemeAlarme
|
||||||
from lumiere import SystemeLumieres
|
from lumieres import SystemeLumieres
|
||||||
from board1main import *
|
from board1main import *
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
@@ -48,31 +50,36 @@ def call_led():
|
|||||||
else:
|
else:
|
||||||
SystemeLumieres.eteindreLumieres()
|
SystemeLumieres.eteindreLumieres()
|
||||||
return jsonify({"success": True})
|
return jsonify({"success": True})
|
||||||
|
dernier_badge_scanne = None
|
||||||
|
|
||||||
@app.route("/rfid-scan", methods=["POST"])
|
@app.route("/rfid-scan", methods=["POST"])
|
||||||
def rfid_scan():
|
def rfid_scan():
|
||||||
global dernier_badge_scanne
|
global dernier_badge_scanne
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
badge_id = str(data.get("badge_id"))
|
badge_id = data.get("badge_id")
|
||||||
username = auth.get_user_by_rfid(badge_id)
|
username = auth.get_user_by_rfid(badge_id)
|
||||||
|
|
||||||
if username:
|
if username:
|
||||||
|
|
||||||
dernier_badge_scanne = username
|
dernier_badge_scanne = username
|
||||||
return jsonify({"success": True, "username": username})
|
return jsonify({"success": True, "username": username})
|
||||||
else:
|
else:
|
||||||
# Badge inconnu dans la BDD
|
|
||||||
return jsonify({"success": False})
|
return jsonify({"success": False})
|
||||||
|
|
||||||
|
@app.route("/check-rfid-login", methods=["GET"])
|
||||||
|
def check_rfid_login():
|
||||||
|
global dernier_badge_scanne
|
||||||
|
global current_user
|
||||||
|
if dernier_badge_scanne:
|
||||||
|
user = dernier_badge_scanne
|
||||||
|
current_user = user
|
||||||
|
dernier_badge_scanne = None
|
||||||
|
|
||||||
|
return jsonify({"success": True, "username": user})
|
||||||
|
|
||||||
|
return jsonify({"success": False})
|
||||||
@app.route("/alarme",methods=["POST"])
|
@app.route("/alarme",methods=["POST"])
|
||||||
def armer_alarme():
|
def armer_alarme():
|
||||||
SystemeAlarme.armer()
|
SystemeAlarme.armer()
|
||||||
return jsonify({"success": True})
|
return jsonify({"success": True})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/admin")
|
@app.route("/admin")
|
||||||
def admin_page():
|
def admin_page():
|
||||||
return render_template("admin.html")
|
return render_template("admin.html")
|
||||||
@@ -107,6 +114,33 @@ def get_users():
|
|||||||
users = auth.get_users()
|
users = auth.get_users()
|
||||||
return jsonify({"success": True, "users": users})
|
return jsonify({"success": True, "users": users})
|
||||||
|
|
||||||
|
@app.route("/api/<action>", methods=["GET"])
|
||||||
|
def relais_pi2(action):
|
||||||
|
url_pi2 = f"https://pi32.local:8000/{action}"
|
||||||
|
print(f"\n[RELAIS] 1. Tentative de contact avec le Pi 2 : {url_pi2}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
reponse = requests.get(url_pi2, timeout=5, verify=False)
|
||||||
|
print(f"[RELAIS] 2. Code HTTP reçu du Pi 2 : {reponse.status_code}")
|
||||||
|
print(f"[RELAIS] 3. Texte brut reçu du Pi 2 : {reponse.text}")
|
||||||
|
if not reponse.ok:
|
||||||
|
return jsonify({
|
||||||
|
"success": False,
|
||||||
|
"message": f"Le Pi 2 a refusé la requête (Code {reponse.status_code})"
|
||||||
|
}), reponse.status_code
|
||||||
|
|
||||||
|
# Si tout va bien, on tente d'extraire le JSON
|
||||||
|
try:
|
||||||
|
data = reponse.json()
|
||||||
|
return jsonify(data)
|
||||||
|
except ValueError:
|
||||||
|
print("[RELAIS] 4. ERREUR : Le Pi 2 n'a pas renvoyé de JSON valide.")
|
||||||
|
return jsonify({"success": False, "message": "Réponse invalide du Pi 2"}), 502
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[RELAIS] ERREUR CRITIQUE : Impossible de joindre le Pi 2. Raison : {e}")
|
||||||
|
return jsonify({"success": False, "message": f"Erreur de connexion : {str(e)}"}), 500
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print("[*] Démarrage du lecteur RFID et de l'alarme en arrière-plan...")
|
print("[*] Démarrage du lecteur RFID et de l'alarme en arrière-plan...")
|
||||||
|
|||||||
@@ -58,7 +58,11 @@ sleep 1
|
|||||||
#----------------------------
|
#----------------------------
|
||||||
# 2. Installation de MariaDB x phpmyadmin
|
# 2. Installation de MariaDB x phpmyadmin
|
||||||
#------------------------------
|
#------------------------------
|
||||||
|
|
||||||
|
read -p "Installer mariadb et phpMyAdmin ? (y/n)" db
|
||||||
|
if [ "$db" = 'y'];then
|
||||||
bash DB/main.sh
|
bash DB/main.sh
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
#-------------------------------
|
#-------------------------------
|
||||||
@@ -98,7 +102,6 @@ VENV_LIST=()
|
|||||||
|
|
||||||
for dir in "${SEARCH_DIRS[@]}"; do
|
for dir in "${SEARCH_DIRS[@]}"; do
|
||||||
if [ -d "$dir" ]; then
|
if [ -d "$dir" ]; then
|
||||||
# Un venv valide contient bin/activate et bin/python
|
|
||||||
while IFS= read -r -d '' activate_path; do
|
while IFS= read -r -d '' activate_path; do
|
||||||
venv_dir=$(dirname "$(dirname "$activate_path")")
|
venv_dir=$(dirname "$(dirname "$activate_path")")
|
||||||
if [ -f "$venv_dir/bin/python" ]; then
|
if [ -f "$venv_dir/bin/python" ]; then
|
||||||
@@ -154,27 +157,26 @@ if [[ "$CREATE_VENV" =~ ^[oO]$ ]]; then
|
|||||||
echo "Installation des dépendances depuis requirements.txt..."
|
echo "Installation des dépendances depuis requirements.txt..."
|
||||||
"$VENV_PATH/bin/pip" install -r ./requirements.txt
|
"$VENV_PATH/bin/pip" install -r ./requirements.txt
|
||||||
echo " Dépendances installées"
|
echo " Dépendances installées"
|
||||||
echo " Dépendances installées"
|
|
||||||
else
|
else
|
||||||
echo " Aucun requirements.txt trouvé, installation des dépendances ignorée"
|
echo " Aucun requirements.txt trouvé, installation des dépendances ignorée"
|
||||||
echo " Aucun requirements.txt trouvé, installation des dépendances ignorée"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " Erreur lors de la création du venv à : $VENV_PATH"
|
|
||||||
echo " Erreur lors de la création du venv à : $VENV_PATH"
|
echo " Erreur lors de la création du venv à : $VENV_PATH"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo " Création ignorée"
|
echo " Création ignorée"
|
||||||
echo " Création ignorée"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Fin
|
# Fin
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
print_step " Configuration terminée"
|
print_step " Configuration terminée"
|
||||||
print_step " Configuration terminée"
|
|
||||||
echo ""
|
echo ""
|
||||||
if [ -f "./.venv_path" ]; then
|
if [ -f "./.venv_path" ]; then
|
||||||
echo "Venv configuré : $(cat ./.venv_path)"
|
echo "Venv configuré : $(cat ./.venv_path)"
|
||||||
@@ -46,14 +46,14 @@ Vérification des certificats SSL
|
|||||||
==================================
|
==================================
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
bash web_secu/ssl.sh
|
bash ../web_secu/ssl.sh
|
||||||
|
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
=============================
|
=============================
|
||||||
Vérification du daemin Avahi
|
Vérification du daemon Avahi
|
||||||
============================
|
============================
|
||||||
EOF
|
EOF
|
||||||
bash web_secu/avahi.sh
|
bash ../web_secu/avahi.sh
|
||||||
|
|
||||||
|
|
||||||
cat << 'EOF'
|
cat << 'EOF'
|
||||||
@@ -65,4 +65,4 @@ EOF
|
|||||||
sleep 1
|
sleep 1
|
||||||
touch /var/log/loustique.log
|
touch /var/log/loustique.log
|
||||||
chown ${SUDO_USER}:${SUDO_USER} /var/log/loustique.log
|
chown ${SUDO_USER}:${SUDO_USER} /var/log/loustique.log
|
||||||
venv/bin/python ./flask/main.py
|
venv/bin/python main.py
|
||||||
@@ -236,7 +236,7 @@
|
|||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
Loustiques
|
SUPER Loustiques
|
||||||
<span>Panneau admin</span>
|
<span>Panneau admin</span>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
Système
|
Système
|
||||||
</a>
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sidebar-footer">v0.1.0 — local</div>
|
<div class="sidebar-footer">Version 1.0</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
@@ -234,18 +234,33 @@
|
|||||||
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
|
||||||
<div class="card-label">Heure locale</div>
|
<div class="card-label">Heure locale</div>
|
||||||
<div class="card-value" id="clock">--:--</div>
|
<div class="card-value" id="clock">--:--</div>
|
||||||
|
<div class="card-sub" id="date-display">--</div></div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-label">Température</div>
|
||||||
|
<div class="card-value" id="temp-display">-- °C</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="card"></div>
|
||||||
|
<div class="card-label">Porte</div>
|
||||||
<div class="card-sub" id="date-display">--</div>
|
<div class="card-sub" id="date-display">--</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||||
<div class="card-label">Raspberry Pi</div>
|
<div class="card-label">Raspberry Pi 1 (actuelle)</div>
|
||||||
<div class="card-value" style="color:#2563eb;">Actif</div>
|
<div class="card-value" style="color:green;">Actif</div>
|
||||||
<div class="card-sub">Flask 3.1</div>
|
<div class="card-sub">Flask 3.1</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
|
||||||
|
<div class="card-label">Raspberry Pi 2 distant </div>
|
||||||
|
<div class="card-value" style="color:green;">Actif</div>
|
||||||
|
<div class="card-sub">FastAPi</div>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
|
||||||
<div class="card-label">Session</div>
|
<div class="card-label">Session</div>
|
||||||
<div class="card-value">Authentifiée</div>
|
<div class="card-value" style="color: green;">Authentifiée</div>
|
||||||
<div class="card-sub">Accès autorisé</div>
|
<div class="card-sub">Accès autorisé</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,31 +268,19 @@
|
|||||||
<div class="section-title">Actions rapides</div>
|
<div class="section-title">Actions rapides</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="action-btn" onclick="callLed()">
|
<button class="action-btn" onclick="call_led_up()">
|
||||||
<span class="a-label">💡 LED</span>
|
<span class="a-label">💡 UP LED</span>
|
||||||
<span class="a-sub">Contrôler la LED</span>
|
<span class="a-sub">Allumer la LED</span>
|
||||||
<span class="a-arrow">›</span>
|
|
||||||
</button>
|
|
||||||
<button class="action-btn" onclick="callAlarm()">
|
|
||||||
<span class="a-label">💡 Alarme</span>
|
|
||||||
<span class="a-sub">Contrôler la alarme</span>
|
|
||||||
<span class="a-arrow">›</span>
|
|
||||||
</button>
|
|
||||||
<button class="action-btn" onclick="callBoard1()">
|
|
||||||
<span class="a-label">💡 Board1</span>
|
|
||||||
<span class="a-sub">Contrôler la board1</span>
|
|
||||||
<span class="a-arrow">›</span>
|
|
||||||
</button>
|
|
||||||
<button class="action-btn" onclick="showToast('Fonction à venir...')">
|
|
||||||
<span class="a-label">⚙️ Paramètres</span>
|
|
||||||
<span class="a-sub">Configuration système</span>
|
|
||||||
<span class="a-arrow">›</span>
|
|
||||||
</button>
|
|
||||||
<button class="action-btn" onclick="showToast('Fonction à venir...')">
|
|
||||||
<span class="a-label">📡 Réseau</span>
|
|
||||||
<span class="a-sub">État de la connexion</span>
|
|
||||||
<span class="a-arrow">›</span>
|
<span class="a-arrow">›</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="action-btn" onclick="call_led_down()">
|
||||||
|
<span class="a-label">DOWN led</span>
|
||||||
|
<span class="a-sub">Eteindre la led</span>
|
||||||
|
<span class="a-arrow">›</span></button>
|
||||||
|
<button class="action-btn" onclick="callAlarm()">
|
||||||
|
<span class="a-label">Alarme</span>
|
||||||
|
<span class="a-sub">Désarmer l'alarme</span>
|
||||||
|
<span class="a-arrow">›</span></button>
|
||||||
<button class="action-btn" onclick="go_admin()">
|
<button class="action-btn" onclick="go_admin()">
|
||||||
<span class="a-label">Administration</span>
|
<span class="a-label">Administration</span>
|
||||||
<span class="a-sub">Administrer les loustiques</span>
|
<span class="a-sub">Administrer les loustiques</span>
|
||||||
@@ -303,17 +306,7 @@
|
|||||||
setInterval(updateClock, 1000);
|
setInterval(updateClock, 1000);
|
||||||
|
|
||||||
|
|
||||||
async function callLed() {
|
/*
|
||||||
try {
|
|
||||||
const res = await fetch('/led', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
showToast("LED activée !");
|
|
||||||
} catch {
|
|
||||||
showToast("Erreur lors de l'appel LED.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function callAlarm() {
|
async function callAlarm() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/alarme', {
|
const res = await fetch('/alarme', {
|
||||||
@@ -323,10 +316,34 @@
|
|||||||
showToast("alarme activée !");
|
showToast("alarme activée !");
|
||||||
} catch {
|
} catch {
|
||||||
showToast("Erreur lors de l'appel alarme.");
|
showToast("Erreur lors de l'appel alarme.");
|
||||||
|
}*/
|
||||||
|
|
||||||
|
async function get_temperature() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/temperature', {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
document.getElementById("temp-display").textContent = data.temperature + " °C";
|
||||||
|
} else {
|
||||||
|
document.getElementById("temp-display").textContent = "Erreur";
|
||||||
|
console.error("Erreur de température :", data.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById("temp-display").textContent = "Hors ligne";
|
||||||
|
console.error("Impossible de joindre le relais pour la température.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
get_temperature();
|
||||||
|
setInterval(get_temperature, 60000);
|
||||||
async function call_led_down() {
|
async function call_led_down() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://pi32.local/down_led', {
|
const res = await fetch('/api/down_led', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
@@ -334,19 +351,17 @@
|
|||||||
} catch {
|
} catch {
|
||||||
showToast("Erreur lors de l'appel board1.");
|
showToast("Erreur lors de l'appel board1.");
|
||||||
}}
|
}}
|
||||||
}
|
|
||||||
async function call_led_up() {
|
async function call_led_up() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('http://pi32.local/up_led', {
|
const res = await fetch('/api/up_led', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
showToast("led activée !");
|
showToast("LED allumée !");
|
||||||
} catch {
|
} catch {
|
||||||
showToast("Erreur lors de l'appel board1.");
|
showToast("Erreur lors de l'appel Pi 2.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@
|
|||||||
|
|
||||||
<div class="champ">
|
<div class="champ">
|
||||||
<label for="password">Mot de passe</label>
|
<label for="password">Mot de passe</label>
|
||||||
<input type="password" id="password" placeholder="Wola ouais" autocomplete="current-password" />
|
<input type="password" id="password" placeholder="Mot de passe du loustique" autocomplete="current-password" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="btn" onclick="handleLogin()">Se connecter</button>
|
<button id="btn" onclick="handleLogin()">Se connecter</button>
|
||||||
@@ -140,14 +140,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// 1. Écoute de la touche Entrée
|
|
||||||
["username", "password"].forEach(id => {
|
["username", "password"].forEach(id => {
|
||||||
document.getElementById(id).addEventListener("keydown", e => {
|
document.getElementById(id).addEventListener("keydown", e => {
|
||||||
if (e.key === "Enter") handleLogin();
|
if (e.key === "Enter") handleLogin();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Fonction de connexion manuelle (Mot de passe)
|
-
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
const username = document.getElementById("username").value.trim();
|
const username = document.getElementById("username").value.trim();
|
||||||
const password = document.getElementById("password").value;
|
const password = document.getElementById("password").value;
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const res = await fetch('/api/check-rfid-login');
|
const res = await fetch('/rfid-scan');
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: system-ui, sans-serif;
|
font-family: system-ui, sans-serif;
|
||||||
background: #1f1f1f;
|
background: rgb(32, 31, 31);
|
||||||
color: #f0f0f0;
|
color: white;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
aside {
|
aside {
|
||||||
width: 220px;
|
width: 220px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #1a1a1a;
|
background: rgb(34, 34, 34);
|
||||||
border-right: 1px solid #2e2e2e;
|
border-right: 1px solid #2e2e2e;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
.logo {
|
.logo {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #f0f0f0;
|
color: white;
|
||||||
margin-bottom: 2.5rem;
|
margin-bottom: 2.5rem;
|
||||||
padding-bottom: 1.5rem;
|
padding-bottom: 1.5rem;
|
||||||
border-bottom: 1px solid #2e2e2e;
|
border-bottom: 1px solid #2e2e2e;
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
.logo span {
|
.logo span {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
color: #666;
|
color: gray;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #888;
|
color: gray;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
transition: all 0.15s;
|
transition: all 0.15s;
|
||||||
@@ -227,7 +227,7 @@
|
|||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
Loustiques
|
SUPER Loustiques
|
||||||
<span>Panneau admin</span>
|
<span>Panneau admin</span>
|
||||||
</div>
|
</div>
|
||||||
<nav>
|
<nav>
|
||||||
@@ -239,12 +239,9 @@
|
|||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8h6M5 5h6M5 11h4"/></svg>
|
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="2" width="12" height="12" rx="2"/><path d="M5 8h6M5 5h6M5 11h4"/></svg>
|
||||||
Logs
|
Logs
|
||||||
</a>
|
</a>
|
||||||
<a href="#">
|
|
||||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="8" cy="8" r="3"/><path d="M8 1v2M8 13v2M1 8h2M13 8h2"/></svg>
|
|
||||||
Système
|
|
||||||
</a>
|
|
||||||
</nav>
|
</nav>
|
||||||
<div class="sidebar-footer">v0.1.0 — local</div>
|
<div class="sidebar-footer">Version 1.0</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
@@ -1,247 +0,0 @@
|
|||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Activate a Python virtual environment for the current PowerShell session.
|
|
||||||
|
|
||||||
.Description
|
|
||||||
Pushes the python executable for a virtual environment to the front of the
|
|
||||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
|
||||||
in a Python virtual environment. Makes use of the command line switches as
|
|
||||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
|
||||||
|
|
||||||
.Parameter VenvDir
|
|
||||||
Path to the directory that contains the virtual environment to activate. The
|
|
||||||
default value for this is the parent of the directory that the Activate.ps1
|
|
||||||
script is located within.
|
|
||||||
|
|
||||||
.Parameter Prompt
|
|
||||||
The prompt prefix to display when this virtual environment is activated. By
|
|
||||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
|
||||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Verbose
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and shows extra information about the activation as it executes.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
|
||||||
Activates the Python virtual environment located in the specified location.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Prompt "MyPython"
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and prefixes the current prompt with the specified string (surrounded in
|
|
||||||
parentheses) while the virtual environment is active.
|
|
||||||
|
|
||||||
.Notes
|
|
||||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
|
||||||
execution policy for the user. You can do this by issuing the following PowerShell
|
|
||||||
command:
|
|
||||||
|
|
||||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
|
|
||||||
For more information on Execution Policies:
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
|
||||||
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$VenvDir,
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$Prompt
|
|
||||||
)
|
|
||||||
|
|
||||||
<# Function declarations --------------------------------------------------- #>
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Remove all shell session elements added by the Activate script, including the
|
|
||||||
addition of the virtual environment's Python executable from the beginning of
|
|
||||||
the PATH variable.
|
|
||||||
|
|
||||||
.Parameter NonDestructive
|
|
||||||
If present, do not remove this function from the global namespace for the
|
|
||||||
session.
|
|
||||||
|
|
||||||
#>
|
|
||||||
function global:deactivate ([switch]$NonDestructive) {
|
|
||||||
# Revert to original values
|
|
||||||
|
|
||||||
# The prior prompt:
|
|
||||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
|
||||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
|
||||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PYTHONHOME:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PATH:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the VIRTUAL_ENV altogether:
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
|
||||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
|
||||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Leave deactivate function in the global namespace if requested:
|
|
||||||
if (-not $NonDestructive) {
|
|
||||||
Remove-Item -Path function:deactivate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Description
|
|
||||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
|
||||||
given folder, and returns them in a map.
|
|
||||||
|
|
||||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
|
||||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
|
||||||
then it is considered a `key = value` line. The left hand string is the key,
|
|
||||||
the right hand is the value.
|
|
||||||
|
|
||||||
If the value starts with a `'` or a `"` then the first and last character is
|
|
||||||
stripped from the value before being captured.
|
|
||||||
|
|
||||||
.Parameter ConfigDir
|
|
||||||
Path to the directory that contains the `pyvenv.cfg` file.
|
|
||||||
#>
|
|
||||||
function Get-PyVenvConfig(
|
|
||||||
[String]
|
|
||||||
$ConfigDir
|
|
||||||
) {
|
|
||||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
|
||||||
|
|
||||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
|
||||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
|
||||||
|
|
||||||
# An empty map will be returned if no config file is found.
|
|
||||||
$pyvenvConfig = @{ }
|
|
||||||
|
|
||||||
if ($pyvenvConfigPath) {
|
|
||||||
|
|
||||||
Write-Verbose "File exists, parse `key = value` lines"
|
|
||||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
|
||||||
|
|
||||||
$pyvenvConfigContent | ForEach-Object {
|
|
||||||
$keyval = $PSItem -split "\s*=\s*", 2
|
|
||||||
if ($keyval[0] -and $keyval[1]) {
|
|
||||||
$val = $keyval[1]
|
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value.
|
|
||||||
if ("'""".Contains($val.Substring(0, 1))) {
|
|
||||||
$val = $val.Substring(1, $val.Length - 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
$pyvenvConfig[$keyval[0]] = $val
|
|
||||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $pyvenvConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<# Begin Activate script --------------------------------------------------- #>
|
|
||||||
|
|
||||||
# Determine the containing directory of this script
|
|
||||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
||||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
|
||||||
|
|
||||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
|
||||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
|
||||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
|
||||||
|
|
||||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
|
||||||
# First, get the location of the virtual environment, it might not be
|
|
||||||
# VenvExecDir if specified on the command line.
|
|
||||||
if ($VenvDir) {
|
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
|
||||||
Write-Verbose "VenvDir=$VenvDir"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
|
||||||
# as `prompt`.
|
|
||||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
|
||||||
|
|
||||||
# Next, set the prompt from the command line, or the config file, or
|
|
||||||
# just use the name of the virtual environment folder.
|
|
||||||
if ($Prompt) {
|
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
|
||||||
$Prompt = $pyvenvCfg['prompt'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
|
||||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
|
||||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Verbose "Prompt = '$Prompt'"
|
|
||||||
Write-Verbose "VenvDir='$VenvDir'"
|
|
||||||
|
|
||||||
# Deactivate any currently active virtual environment, but leave the
|
|
||||||
# deactivate function in place.
|
|
||||||
deactivate -nondestructive
|
|
||||||
|
|
||||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
|
||||||
# that there is an activated venv.
|
|
||||||
$env:VIRTUAL_ENV = $VenvDir
|
|
||||||
|
|
||||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
|
||||||
|
|
||||||
Write-Verbose "Setting prompt to '$Prompt'"
|
|
||||||
|
|
||||||
# Set the prompt to include the env name
|
|
||||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
|
||||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
|
||||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
|
||||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
|
||||||
|
|
||||||
function global:prompt {
|
|
||||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
|
||||||
_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear PYTHONHOME
|
|
||||||
if (Test-Path -Path Env:PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
Remove-Item -Path Env:PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the venv to the PATH
|
|
||||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
|
||||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate" *from bash*
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
deactivate () {
|
|
||||||
# reset old environment variables
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
|
||||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
|
||||||
export PATH
|
|
||||||
unset _OLD_VIRTUAL_PATH
|
|
||||||
fi
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
|
||||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
|
||||||
export PYTHONHOME
|
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
unset VIRTUAL_ENV_PROMPT
|
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV=/home/maxime/Documents/loustiques-home/venv
|
|
||||||
export VIRTUAL_ENV
|
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
PATH="$VIRTUAL_ENV/"bin":$PATH"
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
|
||||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
|
||||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
|
||||||
unset PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
|
||||||
PS1='(venv) '"${PS1:-}"
|
|
||||||
export PS1
|
|
||||||
VIRTUAL_ENV_PROMPT='(venv) '
|
|
||||||
export VIRTUAL_ENV_PROMPT
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate.csh" *from csh*.
|
|
||||||
# You cannot run it directly.
|
|
||||||
# Created by Davide Di Blasi <davidedb@gmail.com>.
|
|
||||||
# Ported to Python 3.3 venv by Andrew Svetlov <andrew.svetlov@gmail.com>
|
|
||||||
|
|
||||||
alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate'
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
setenv VIRTUAL_ENV /home/maxime/Documents/loustiques-home/venv
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
setenv PATH "$VIRTUAL_ENV/"bin":$PATH"
|
|
||||||
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT="$prompt"
|
|
||||||
|
|
||||||
if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
|
||||||
set prompt = '(venv) '"$prompt"
|
|
||||||
setenv VIRTUAL_ENV_PROMPT '(venv) '
|
|
||||||
endif
|
|
||||||
|
|
||||||
alias pydoc python -m pydoc
|
|
||||||
|
|
||||||
rehash
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# This file must be used with "source <venv>/bin/activate.fish" *from fish*
|
|
||||||
# (https://fishshell.com/); you cannot run it directly.
|
|
||||||
|
|
||||||
function deactivate -d "Exit virtual environment and return to normal shell environment"
|
|
||||||
# reset old environment variables
|
|
||||||
if test -n "$_OLD_VIRTUAL_PATH"
|
|
||||||
set -gx PATH $_OLD_VIRTUAL_PATH
|
|
||||||
set -e _OLD_VIRTUAL_PATH
|
|
||||||
end
|
|
||||||
if test -n "$_OLD_VIRTUAL_PYTHONHOME"
|
|
||||||
set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
set -e _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -n "$_OLD_FISH_PROMPT_OVERRIDE"
|
|
||||||
set -e _OLD_FISH_PROMPT_OVERRIDE
|
|
||||||
# prevents error when using nested fish instances (Issue #93858)
|
|
||||||
if functions -q _old_fish_prompt
|
|
||||||
functions -e fish_prompt
|
|
||||||
functions -c _old_fish_prompt fish_prompt
|
|
||||||
functions -e _old_fish_prompt
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
set -e VIRTUAL_ENV
|
|
||||||
set -e VIRTUAL_ENV_PROMPT
|
|
||||||
if test "$argv[1]" != "nondestructive"
|
|
||||||
# Self-destruct!
|
|
||||||
functions -e deactivate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Unset irrelevant variables.
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
set -gx VIRTUAL_ENV /home/maxime/Documents/loustiques-home/venv
|
|
||||||
|
|
||||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
|
||||||
set -gx PATH "$VIRTUAL_ENV/"bin $PATH
|
|
||||||
|
|
||||||
# Unset PYTHONHOME if set.
|
|
||||||
if set -q PYTHONHOME
|
|
||||||
set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME
|
|
||||||
set -e PYTHONHOME
|
|
||||||
end
|
|
||||||
|
|
||||||
if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
|
||||||
# fish uses a function instead of an env var to generate the prompt.
|
|
||||||
|
|
||||||
# Save the current fish_prompt function as the function _old_fish_prompt.
|
|
||||||
functions -c fish_prompt _old_fish_prompt
|
|
||||||
|
|
||||||
# With the original prompt function renamed, we can override with our own.
|
|
||||||
function fish_prompt
|
|
||||||
# Save the return status of the last command.
|
|
||||||
set -l old_status $status
|
|
||||||
|
|
||||||
# Output the venv prompt; color taken from the blue of the Python logo.
|
|
||||||
printf "%s%s%s" (set_color 4B8BBE) '(venv) ' (set_color normal)
|
|
||||||
|
|
||||||
# Restore the return status of the previous command.
|
|
||||||
echo "exit $old_status" | .
|
|
||||||
# Output the original/"old" prompt.
|
|
||||||
_old_fish_prompt
|
|
||||||
end
|
|
||||||
|
|
||||||
set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV"
|
|
||||||
set -gx VIRTUAL_ENV_PROMPT '(venv) '
|
|
||||||
end
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from dotenv.__main__ import cli
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(cli())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from flask.cli import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from gpiozerocli.pinout import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from gpiozerocli.pintest import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
#!/home/maxime/Documents/loustiques-home/venv/bin/python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pip._internal.cli.main import main
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
python3
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/usr/bin/python3
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
python3
|
|
||||||
@@ -1,260 +0,0 @@
|
|||||||
Metadata-Version: 2.1
|
|
||||||
Name: RPi.GPIO
|
|
||||||
Version: 0.7.1
|
|
||||||
Summary: A module to control Raspberry Pi GPIO channels
|
|
||||||
Home-page: http://sourceforge.net/projects/raspberry-gpio-python/
|
|
||||||
Author: Ben Croston
|
|
||||||
Author-email: ben@croston.org
|
|
||||||
License: MIT
|
|
||||||
Keywords: Raspberry Pi GPIO
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Operating System :: POSIX :: Linux
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: Programming Language :: Python :: 2.7
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Topic :: Software Development
|
|
||||||
Classifier: Topic :: Home Automation
|
|
||||||
Classifier: Topic :: System :: Hardware
|
|
||||||
License-File: LICENCE.txt
|
|
||||||
|
|
||||||
This package provides a Python module to control the GPIO on a Raspberry Pi.
|
|
||||||
|
|
||||||
Note that this module is unsuitable for real-time or timing critical applications. This is because you
|
|
||||||
can not predict when Python will be busy garbage collecting. It also runs under the Linux kernel which
|
|
||||||
is not suitable for real time applications - it is multitasking O/S and another process may be given
|
|
||||||
priority over the CPU, causing jitter in your program. If you are after true real-time performance and
|
|
||||||
predictability, buy yourself an Arduino http://www.arduino.cc !
|
|
||||||
|
|
||||||
Note that the current release does not support SPI, I2C, hardware PWM or serial functionality on the RPi yet.
|
|
||||||
This is planned for the near future - watch this space! One-wire functionality is also planned.
|
|
||||||
|
|
||||||
Although hardware PWM is not available yet, software PWM is available to use on all channels.
|
|
||||||
|
|
||||||
For examples and documentation, visit http://sourceforge.net/p/raspberry-gpio-python/wiki/Home/
|
|
||||||
|
|
||||||
Change Log
|
|
||||||
==========
|
|
||||||
|
|
||||||
0.7.1
|
|
||||||
-------
|
|
||||||
- Better RPi board + peri_addr detection (issue 190 / 191)
|
|
||||||
- Fix PyEval_InitThreads deprecation warning for Python 3.9 (issue 188)
|
|
||||||
- Fix build using GCC 10 (issue 187)
|
|
||||||
- Fix docstrings to not include licence
|
|
||||||
- Remove Debian/Raspbian stretch packaging support
|
|
||||||
- Use setuptools instead of distutils
|
|
||||||
- Added detection of Zero 2 W
|
|
||||||
- Tested and working with Python 2.7, 3.7, 3.8, 3.9, 3.10
|
|
||||||
|
|
||||||
0.7.0
|
|
||||||
-----
|
|
||||||
- Updated RPI_INFO to include RPi 4B
|
|
||||||
- Fixed pull up/down for Pi4 (issue 168)
|
|
||||||
- Fix spelling mistake in docstrings
|
|
||||||
- Tested and working on Raspbian Buster + Python 3.8.0b2
|
|
||||||
- Fix board detection for aarch64 (Issues 161 / 165)
|
|
||||||
- Fix checking mmap return value in c_gpio.c (issue 166)
|
|
||||||
|
|
||||||
0.6.5
|
|
||||||
-----
|
|
||||||
- Fix exception on re-export of /sys/class/gpio/gpioNN
|
|
||||||
|
|
||||||
0.6.4
|
|
||||||
-----
|
|
||||||
- Event cleanup bug (issue 145)
|
|
||||||
- Raise exception for duplicate PWM objects (issue 54 - Thijs Schreijer <thijs@thijsschreijer.nl>)
|
|
||||||
- Fix build warnings (Issue 146 - Dominik George)
|
|
||||||
- test.py runs unchanged for both python 2+3
|
|
||||||
- Soft PWM stops running fix (Issues 94, 111, 154)
|
|
||||||
- Soft PWM segfault fix (Luke Allen pull request)
|
|
||||||
|
|
||||||
0.6.3
|
|
||||||
-----
|
|
||||||
- Fix code so it builds under PyPy (Gasper Zejn)
|
|
||||||
- os.system breaks event detection - Matt Kimball (issue 127)
|
|
||||||
|
|
||||||
0.6.2
|
|
||||||
-----
|
|
||||||
- Rewrote Debian packaging mechanism
|
|
||||||
- RPI_INFO reports Pi 3
|
|
||||||
- Changed module layout - moved C components to RPi._GPIO
|
|
||||||
|
|
||||||
0.6.1
|
|
||||||
-----
|
|
||||||
- Update RPI_INFO to detect more board types
|
|
||||||
- Issue 118 - add_event_detect sometimes gives runtime error with unpriv user
|
|
||||||
- Issue 120 - setmode() remembers invalid mode
|
|
||||||
|
|
||||||
0.6.0a3
|
|
||||||
-------
|
|
||||||
- Now uses /dev/gpiomem if available to avoid being run as root
|
|
||||||
- Fix warnings with pull up/down on pins 3/5
|
|
||||||
- Correct base address on Pi 2 when devicetree is disabled
|
|
||||||
- caddr_t error on compile (Issue 109)
|
|
||||||
- Error on invalid parameters to setup() (issue 93)
|
|
||||||
- Add timeout parameter to wait_for_edge() (issue 91)
|
|
||||||
|
|
||||||
0.5.11
|
|
||||||
------
|
|
||||||
- Fix - pins > 26 missing when using BOARD mode
|
|
||||||
- Add getmode()
|
|
||||||
- Raise exception when a mix of modes is used
|
|
||||||
- GPIO.cleanaup() unsets the current pin mode
|
|
||||||
|
|
||||||
0.5.10
|
|
||||||
------
|
|
||||||
- Issue 95 - support RPi 2 boards
|
|
||||||
- Introduce RPI_INFO
|
|
||||||
- Deprecate RPI_REVISION
|
|
||||||
- Issue 97 - fixed docstring for setup()
|
|
||||||
|
|
||||||
0.5.9
|
|
||||||
-----
|
|
||||||
- Issue 87 - warn about pull up/down on i2c pins
|
|
||||||
- Issue 86/75 - wait_for_edge() bugfix
|
|
||||||
- Issue 84 - recognise RPi properly when using a custom kernel
|
|
||||||
- Issue 90 - cleanup() on a list/tuple of channels
|
|
||||||
|
|
||||||
0.5.8
|
|
||||||
-----
|
|
||||||
- Allow lists/tuples of channels in GPIO.setup()
|
|
||||||
- GPIO.output() now allows lists/tuples of values
|
|
||||||
- GPIO.wait_for_edge() bug fixes (issue 78)
|
|
||||||
|
|
||||||
0.5.7
|
|
||||||
-----
|
|
||||||
- Issue 67 - speed up repeated calls to GPIO.wait_for_event()
|
|
||||||
- Added bouncetime keyword to GPIO.wait_for_event()
|
|
||||||
- Added extra edge/interrupt unit tests
|
|
||||||
- GPIO.wait_for_event() can now be mixed with GPIO.add_event_detect()
|
|
||||||
- Improved cleanups of events
|
|
||||||
- Issue 69 resolved
|
|
||||||
|
|
||||||
0.5.6
|
|
||||||
-----
|
|
||||||
- Issue 68 - support for RPi Model B+
|
|
||||||
- Fix gpio_function()
|
|
||||||
|
|
||||||
0.5.5
|
|
||||||
-----
|
|
||||||
- Issue 52 - 'unallocate' a channel
|
|
||||||
- Issue 35 - use switchbounce with GPIO.event_detected()
|
|
||||||
- Refactored events code
|
|
||||||
- Rewrote tests to use unittest mechanism and new test board with loopbacks
|
|
||||||
- Fixed adding events after a GPIO.cleanup()
|
|
||||||
- Issue 64 - misleading /dev/mem permissions error
|
|
||||||
- Issue 59 - name collision with PWM constant and class
|
|
||||||
|
|
||||||
0.5.4
|
|
||||||
-----
|
|
||||||
- Changed release status (from alpha to full release)
|
|
||||||
- Warn when GPIO.cleanup() used with nothing to clean up (issue 44)
|
|
||||||
- Avoid collisions in constants (e.g. HIGH / RISING / PUD_DOWN)
|
|
||||||
- Accept BOARD numbers in gpio_function (issue 34)
|
|
||||||
- More return values for gpio_function (INPUT, OUTPUT, SPI, I2C, PWM, SERIAL, UNKNOWN)
|
|
||||||
- Tidy up docstrings
|
|
||||||
- Fix /dev/mem access error with gpio_function
|
|
||||||
|
|
||||||
0.5.3a
|
|
||||||
------
|
|
||||||
- Allow pydoc for non-root users (issue 27)
|
|
||||||
- Fix add_event_detect error when run as daemon (issue 32)
|
|
||||||
- Simplified exception types
|
|
||||||
- Changed from distribute to pip
|
|
||||||
|
|
||||||
0.5.2a
|
|
||||||
------
|
|
||||||
- Added software PWM (experimental)
|
|
||||||
- Added switch bounce handling to event callbacks
|
|
||||||
- Added channel number parameter to event callbacks (issue 31)
|
|
||||||
- Internal refactoring and code tidy up
|
|
||||||
|
|
||||||
0.5.1a
|
|
||||||
------
|
|
||||||
- Fixed callbacks for multiple GPIOs (issue 28)
|
|
||||||
|
|
||||||
0.5.0a
|
|
||||||
------
|
|
||||||
- Added new edge detection events (interrupt handling)
|
|
||||||
- Added add_event_detect()
|
|
||||||
- Added remove_event_detect()
|
|
||||||
- Added add_event_callback()
|
|
||||||
- Added wait_for_edge()
|
|
||||||
- Removed old experimental event functions
|
|
||||||
- Removed set_rising_event()
|
|
||||||
- Removed set_falling_event()
|
|
||||||
- Removed set_high_event()
|
|
||||||
- Removed set_low_event()
|
|
||||||
- Changed event_detected() for new edge detection functionality
|
|
||||||
- input() now returns 0/LOW == False or 1/HIGH == True (integers) instead of False or True (booleans).
|
|
||||||
- Fix error on repeated import (issue 3)
|
|
||||||
- Change SetupException to a RuntimeError so it can be caught on import (issue 25, Chris Hager <chris@linuxuser.at>)
|
|
||||||
- Improved docstrings of functions
|
|
||||||
|
|
||||||
0.4.2a
|
|
||||||
------
|
|
||||||
- Fix for installing on Arch Linux (Python 3.3) (issue 20)
|
|
||||||
- Initial value when setting a channel as an output (issue 19)
|
|
||||||
|
|
||||||
0.4.1a
|
|
||||||
------
|
|
||||||
- Added VERSION
|
|
||||||
- Permit input() of channels set as outputs (Eric Ptak <trouch@trouch.com>)
|
|
||||||
|
|
||||||
0.4.0a
|
|
||||||
------
|
|
||||||
- Added support for Revision 2 boards
|
|
||||||
- Added RPI_REVISION
|
|
||||||
- Added cleanup() function and removed automatic reset functionality on program exit
|
|
||||||
- Added get_function() to read existing GPIO channel functionality (suggestion from Eric Ptak <trouch@trouch.com>)
|
|
||||||
- Added set_rising_event()
|
|
||||||
- Added set_falling_event()
|
|
||||||
- Added set_high_event()
|
|
||||||
- Added set_low_event()
|
|
||||||
- Added event_detected()
|
|
||||||
- Added test/test.py
|
|
||||||
- Converted debian to armhf
|
|
||||||
- Fixed C function short_wait() (thanks to Thibault Porteboeuf <thibaultporteboeuf@gmail.com>)
|
|
||||||
|
|
||||||
0.3.1a
|
|
||||||
------
|
|
||||||
- Fixed critical bug with swapped high/low state on outputs
|
|
||||||
- Added pull-up / pull-down setup functionality for inputs
|
|
||||||
|
|
||||||
0.3.0a
|
|
||||||
------
|
|
||||||
- Rewritten as a C extension
|
|
||||||
- Now uses /dev/mem and SoC registers instead of /sys/class/gpio
|
|
||||||
- Faster!
|
|
||||||
- Make call to GPIO.setmode() mandatory
|
|
||||||
- Added GPIO.HIGH and GPIO.LOW constants
|
|
||||||
|
|
||||||
0.2.0
|
|
||||||
-----
|
|
||||||
- Changed status from alpha to beta
|
|
||||||
- Added setmode() to be able to use BCM GPIO 00.nn channel numbers
|
|
||||||
- Renamed InvalidPinException to InvalidChannelException
|
|
||||||
|
|
||||||
0.1.0
|
|
||||||
------
|
|
||||||
- Fixed direction bug
|
|
||||||
- Added MANIFEST.in (to include missing file)
|
|
||||||
- Changed GPIO channel number to pin number
|
|
||||||
- Tested and working!
|
|
||||||
|
|
||||||
0.0.3a
|
|
||||||
------
|
|
||||||
- Added GPIO table
|
|
||||||
- Refactored
|
|
||||||
- Fixed a few critical bugs
|
|
||||||
- Still completely untested!
|
|
||||||
|
|
||||||
0.0.2a
|
|
||||||
------
|
|
||||||
- Internal refactoring. Still completely untested!
|
|
||||||
|
|
||||||
0.0.1a
|
|
||||||
------
|
|
||||||
- First version. Completely untested until I can get hold of a Raspberry Pi!
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
CHANGELOG.txt
|
|
||||||
INSTALL.txt
|
|
||||||
LICENCE.txt
|
|
||||||
MANIFEST.in
|
|
||||||
README.txt
|
|
||||||
create_gpio_user_permissions.py
|
|
||||||
setup.cfg
|
|
||||||
setup.py
|
|
||||||
RPi/__init__.py
|
|
||||||
RPi.GPIO.egg-info/PKG-INFO
|
|
||||||
RPi.GPIO.egg-info/SOURCES.txt
|
|
||||||
RPi.GPIO.egg-info/dependency_links.txt
|
|
||||||
RPi.GPIO.egg-info/top_level.txt
|
|
||||||
RPi/GPIO/__init__.py
|
|
||||||
source/c_gpio.c
|
|
||||||
source/c_gpio.h
|
|
||||||
source/common.c
|
|
||||||
source/common.h
|
|
||||||
source/constants.c
|
|
||||||
source/constants.h
|
|
||||||
source/cpuinfo.c
|
|
||||||
source/cpuinfo.h
|
|
||||||
source/event_gpio.c
|
|
||||||
source/event_gpio.h
|
|
||||||
source/py_gpio.c
|
|
||||||
source/py_pwm.c
|
|
||||||
source/py_pwm.h
|
|
||||||
source/soft_pwm.c
|
|
||||||
source/soft_pwm.h
|
|
||||||
test/issue_94_111_154.py
|
|
||||||
test/test.py
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
../RPi/GPIO/__init__.py
|
|
||||||
../RPi/GPIO/__pycache__/__init__.cpython-311.pyc
|
|
||||||
../RPi/_GPIO.cpython-311-x86_64-linux-gnu.so
|
|
||||||
../RPi/__init__.py
|
|
||||||
../RPi/__pycache__/__init__.cpython-311.pyc
|
|
||||||
PKG-INFO
|
|
||||||
SOURCES.txt
|
|
||||||
dependency_links.txt
|
|
||||||
top_level.txt
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
RPi
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
# Copyright (c) 2012-2021 Ben Croston
|
|
||||||
#
|
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
# this software and associated documentation files (the "Software"), to deal in
|
|
||||||
# the Software without restriction, including without limitation the rights to
|
|
||||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
||||||
# of the Software, and to permit persons to whom the Software is furnished to do
|
|
||||||
# so, subject to the following conditions:
|
|
||||||
#
|
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
|
||||||
# copies or substantial portions of the Software.
|
|
||||||
#
|
|
||||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
# SOFTWARE.
|
|
||||||
|
|
||||||
"""This package provides a Python module to control the GPIO on a Raspberry Pi"""
|
|
||||||
|
|
||||||
from RPi._GPIO import *
|
|
||||||
|
|
||||||
VERSION = '0.7.1'
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,222 +0,0 @@
|
|||||||
# don't import any costly modules
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
is_pypy = '__pypy__' in sys.builtin_module_names
|
|
||||||
|
|
||||||
|
|
||||||
def warn_distutils_present():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
if is_pypy and sys.version_info < (3, 7):
|
|
||||||
# PyPy for 3.6 unconditionally imports distutils, so bypass the warning
|
|
||||||
# https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250
|
|
||||||
return
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"Distutils was imported before Setuptools, but importing Setuptools "
|
|
||||||
"also replaces the `distutils` module in `sys.modules`. This may lead "
|
|
||||||
"to undesirable behaviors or errors. To avoid these issues, avoid "
|
|
||||||
"using distutils directly, ensure that setuptools is installed in the "
|
|
||||||
"traditional way (e.g. not an editable install), and/or make sure "
|
|
||||||
"that setuptools is always imported before distutils."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def clear_distutils():
|
|
||||||
if 'distutils' not in sys.modules:
|
|
||||||
return
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn("Setuptools is replacing distutils.")
|
|
||||||
mods = [
|
|
||||||
name
|
|
||||||
for name in sys.modules
|
|
||||||
if name == "distutils" or name.startswith("distutils.")
|
|
||||||
]
|
|
||||||
for name in mods:
|
|
||||||
del sys.modules[name]
|
|
||||||
|
|
||||||
|
|
||||||
def enabled():
|
|
||||||
"""
|
|
||||||
Allow selection of distutils by environment variable.
|
|
||||||
"""
|
|
||||||
which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local')
|
|
||||||
return which == 'local'
|
|
||||||
|
|
||||||
|
|
||||||
def ensure_local_distutils():
|
|
||||||
import importlib
|
|
||||||
|
|
||||||
clear_distutils()
|
|
||||||
|
|
||||||
# With the DistutilsMetaFinder in place,
|
|
||||||
# perform an import to cause distutils to be
|
|
||||||
# loaded from setuptools._distutils. Ref #2906.
|
|
||||||
with shim():
|
|
||||||
importlib.import_module('distutils')
|
|
||||||
|
|
||||||
# check that submodules load as expected
|
|
||||||
core = importlib.import_module('distutils.core')
|
|
||||||
assert '_distutils' in core.__file__, core.__file__
|
|
||||||
assert 'setuptools._distutils.log' not in sys.modules
|
|
||||||
|
|
||||||
|
|
||||||
def do_override():
|
|
||||||
"""
|
|
||||||
Ensure that the local copy of distutils is preferred over stdlib.
|
|
||||||
|
|
||||||
See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401
|
|
||||||
for more motivation.
|
|
||||||
"""
|
|
||||||
if enabled():
|
|
||||||
warn_distutils_present()
|
|
||||||
ensure_local_distutils()
|
|
||||||
|
|
||||||
|
|
||||||
class _TrivialRe:
|
|
||||||
def __init__(self, *patterns):
|
|
||||||
self._patterns = patterns
|
|
||||||
|
|
||||||
def match(self, string):
|
|
||||||
return all(pat in string for pat in self._patterns)
|
|
||||||
|
|
||||||
|
|
||||||
class DistutilsMetaFinder:
|
|
||||||
def find_spec(self, fullname, path, target=None):
|
|
||||||
# optimization: only consider top level modules and those
|
|
||||||
# found in the CPython test suite.
|
|
||||||
if path is not None and not fullname.startswith('test.'):
|
|
||||||
return
|
|
||||||
|
|
||||||
method_name = 'spec_for_{fullname}'.format(**locals())
|
|
||||||
method = getattr(self, method_name, lambda: None)
|
|
||||||
return method()
|
|
||||||
|
|
||||||
def spec_for_distutils(self):
|
|
||||||
if self.is_cpython():
|
|
||||||
return
|
|
||||||
|
|
||||||
import importlib
|
|
||||||
import importlib.abc
|
|
||||||
import importlib.util
|
|
||||||
|
|
||||||
try:
|
|
||||||
mod = importlib.import_module('setuptools._distutils')
|
|
||||||
except Exception:
|
|
||||||
# There are a couple of cases where setuptools._distutils
|
|
||||||
# may not be present:
|
|
||||||
# - An older Setuptools without a local distutils is
|
|
||||||
# taking precedence. Ref #2957.
|
|
||||||
# - Path manipulation during sitecustomize removes
|
|
||||||
# setuptools from the path but only after the hook
|
|
||||||
# has been loaded. Ref #2980.
|
|
||||||
# In either case, fall back to stdlib behavior.
|
|
||||||
return
|
|
||||||
|
|
||||||
class DistutilsLoader(importlib.abc.Loader):
|
|
||||||
def create_module(self, spec):
|
|
||||||
mod.__name__ = 'distutils'
|
|
||||||
return mod
|
|
||||||
|
|
||||||
def exec_module(self, module):
|
|
||||||
pass
|
|
||||||
|
|
||||||
return importlib.util.spec_from_loader(
|
|
||||||
'distutils', DistutilsLoader(), origin=mod.__file__
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def is_cpython():
|
|
||||||
"""
|
|
||||||
Suppress supplying distutils for CPython (build and tests).
|
|
||||||
Ref #2965 and #3007.
|
|
||||||
"""
|
|
||||||
return os.path.isfile('pybuilddir.txt')
|
|
||||||
|
|
||||||
def spec_for_pip(self):
|
|
||||||
"""
|
|
||||||
Ensure stdlib distutils when running under pip.
|
|
||||||
See pypa/pip#8761 for rationale.
|
|
||||||
"""
|
|
||||||
if self.pip_imported_during_build():
|
|
||||||
return
|
|
||||||
clear_distutils()
|
|
||||||
self.spec_for_distutils = lambda: None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def pip_imported_during_build(cls):
|
|
||||||
"""
|
|
||||||
Detect if pip is being imported in a build script. Ref #2355.
|
|
||||||
"""
|
|
||||||
import traceback
|
|
||||||
|
|
||||||
return any(
|
|
||||||
cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None)
|
|
||||||
)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def frame_file_is_setup(frame):
|
|
||||||
"""
|
|
||||||
Return True if the indicated frame suggests a setup.py file.
|
|
||||||
"""
|
|
||||||
# some frames may not have __file__ (#2940)
|
|
||||||
return frame.f_globals.get('__file__', '').endswith('setup.py')
|
|
||||||
|
|
||||||
def spec_for_sensitive_tests(self):
|
|
||||||
"""
|
|
||||||
Ensure stdlib distutils when running select tests under CPython.
|
|
||||||
|
|
||||||
python/cpython#91169
|
|
||||||
"""
|
|
||||||
clear_distutils()
|
|
||||||
self.spec_for_distutils = lambda: None
|
|
||||||
|
|
||||||
sensitive_tests = (
|
|
||||||
[
|
|
||||||
'test.test_distutils',
|
|
||||||
'test.test_peg_generator',
|
|
||||||
'test.test_importlib',
|
|
||||||
]
|
|
||||||
if sys.version_info < (3, 10)
|
|
||||||
else [
|
|
||||||
'test.test_distutils',
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
for name in DistutilsMetaFinder.sensitive_tests:
|
|
||||||
setattr(
|
|
||||||
DistutilsMetaFinder,
|
|
||||||
f'spec_for_{name}',
|
|
||||||
DistutilsMetaFinder.spec_for_sensitive_tests,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DISTUTILS_FINDER = DistutilsMetaFinder()
|
|
||||||
|
|
||||||
|
|
||||||
def add_shim():
|
|
||||||
DISTUTILS_FINDER in sys.meta_path or insert_shim()
|
|
||||||
|
|
||||||
|
|
||||||
class shim:
|
|
||||||
def __enter__(self):
|
|
||||||
insert_shim()
|
|
||||||
|
|
||||||
def __exit__(self, exc, value, tb):
|
|
||||||
remove_shim()
|
|
||||||
|
|
||||||
|
|
||||||
def insert_shim():
|
|
||||||
sys.meta_path.insert(0, DISTUTILS_FINDER)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_shim():
|
|
||||||
try:
|
|
||||||
sys.meta_path.remove(DISTUTILS_FINDER)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
__import__('_distutils_hack').do_override()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,343 +0,0 @@
|
|||||||
Metadata-Version: 2.4
|
|
||||||
Name: bcrypt
|
|
||||||
Version: 5.0.0
|
|
||||||
Summary: Modern password hashing for your software and your servers
|
|
||||||
Author-email: The Python Cryptographic Authority developers <cryptography-dev@python.org>
|
|
||||||
License: Apache-2.0
|
|
||||||
Project-URL: homepage, https://github.com/pyca/bcrypt/
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: License :: OSI Approved :: Apache Software License
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
||||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
||||||
Classifier: Programming Language :: Python :: 3
|
|
||||||
Classifier: Programming Language :: Python :: 3 :: Only
|
|
||||||
Classifier: Programming Language :: Python :: 3.8
|
|
||||||
Classifier: Programming Language :: Python :: 3.9
|
|
||||||
Classifier: Programming Language :: Python :: 3.10
|
|
||||||
Classifier: Programming Language :: Python :: 3.11
|
|
||||||
Classifier: Programming Language :: Python :: 3.12
|
|
||||||
Classifier: Programming Language :: Python :: 3.13
|
|
||||||
Classifier: Programming Language :: Python :: 3.14
|
|
||||||
Classifier: Programming Language :: Python :: Free Threading :: 3 - Stable
|
|
||||||
Requires-Python: >=3.8
|
|
||||||
Description-Content-Type: text/x-rst
|
|
||||||
License-File: LICENSE
|
|
||||||
Provides-Extra: tests
|
|
||||||
Requires-Dist: pytest!=3.3.0,>=3.2.1; extra == "tests"
|
|
||||||
Provides-Extra: typecheck
|
|
||||||
Requires-Dist: mypy; extra == "typecheck"
|
|
||||||
Dynamic: license-file
|
|
||||||
|
|
||||||
bcrypt
|
|
||||||
======
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/bcrypt.svg
|
|
||||||
:target: https://pypi.org/project/bcrypt/
|
|
||||||
:alt: Latest Version
|
|
||||||
|
|
||||||
.. image:: https://github.com/pyca/bcrypt/workflows/CI/badge.svg?branch=main
|
|
||||||
:target: https://github.com/pyca/bcrypt/actions?query=workflow%3ACI+branch%3Amain
|
|
||||||
|
|
||||||
Acceptable password hashing for your software and your servers (but you should
|
|
||||||
really use argon2id or scrypt)
|
|
||||||
|
|
||||||
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
To install bcrypt, simply:
|
|
||||||
|
|
||||||
.. code:: console
|
|
||||||
|
|
||||||
$ pip install bcrypt
|
|
||||||
|
|
||||||
Note that bcrypt should build very easily on Linux provided you have a C
|
|
||||||
compiler and a Rust compiler (the minimum supported Rust version is 1.56.0).
|
|
||||||
|
|
||||||
For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
|
|
||||||
|
|
||||||
.. code:: console
|
|
||||||
|
|
||||||
$ sudo apt-get install build-essential cargo
|
|
||||||
|
|
||||||
For Fedora and RHEL-derivatives, the following command will ensure that the required dependencies are installed:
|
|
||||||
|
|
||||||
.. code:: console
|
|
||||||
|
|
||||||
$ sudo yum install gcc cargo
|
|
||||||
|
|
||||||
For Alpine, the following command will ensure that the required dependencies are installed:
|
|
||||||
|
|
||||||
.. code:: console
|
|
||||||
|
|
||||||
$ apk add --update musl-dev gcc cargo
|
|
||||||
|
|
||||||
|
|
||||||
Alternatives
|
|
||||||
============
|
|
||||||
|
|
||||||
While bcrypt remains an acceptable choice for password storage, depending on your specific use case you may also want to consider using scrypt (either via `standard library`_ or `cryptography`_) or argon2id via `argon2_cffi`_.
|
|
||||||
|
|
||||||
Changelog
|
|
||||||
=========
|
|
||||||
|
|
||||||
5.0.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Bumped MSRV to 1.74.
|
|
||||||
* Added support for Python 3.14 and free-threaded Python 3.14.
|
|
||||||
* Added support for Windows on ARM.
|
|
||||||
* Passing ``hashpw`` a password longer than 72 bytes now raises a
|
|
||||||
``ValueError``. Previously the password was silently truncated, following the
|
|
||||||
behavior of the original OpenBSD ``bcrypt`` implementation.
|
|
||||||
|
|
||||||
4.3.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Dropped support for Python 3.7.
|
|
||||||
* We now support free-threaded Python 3.13.
|
|
||||||
* We now support PyPy 3.11.
|
|
||||||
* We now publish wheels for free-threaded Python 3.13, for PyPy 3.11 on
|
|
||||||
``manylinux``, and for ARMv7l on ``manylinux``.
|
|
||||||
|
|
||||||
4.2.1
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Bump Rust dependency versions - this should resolve crashes on Python 3.13
|
|
||||||
free-threaded builds.
|
|
||||||
* We no longer build ``manylinux`` wheels for PyPy 3.9.
|
|
||||||
|
|
||||||
4.2.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Bump Rust dependency versions
|
|
||||||
* Removed the ``BCRYPT_ALLOW_RUST_163`` environment variable.
|
|
||||||
|
|
||||||
4.1.3
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Bump Rust dependency versions
|
|
||||||
|
|
||||||
4.1.2
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Publish both ``py37`` and ``py39`` wheels. This should resolve some errors
|
|
||||||
relating to initializing a module multiple times per process.
|
|
||||||
|
|
||||||
4.1.1
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Fixed the type signature on the ``kdf`` method.
|
|
||||||
* Fixed packaging bug on Windows.
|
|
||||||
* Fixed incompatibility with passlib package detection assumptions.
|
|
||||||
|
|
||||||
4.1.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Dropped support for Python 3.6.
|
|
||||||
* Bumped MSRV to 1.64. (Note: Rust 1.63 can be used by setting the ``BCRYPT_ALLOW_RUST_163`` environment variable)
|
|
||||||
|
|
||||||
4.0.1
|
|
||||||
-----
|
|
||||||
|
|
||||||
* We now build PyPy ``manylinux`` wheels.
|
|
||||||
* Fixed a bug where passing an invalid ``salt`` to ``checkpw`` could result in
|
|
||||||
a ``pyo3_runtime.PanicException``. It now correctly raises a ``ValueError``.
|
|
||||||
|
|
||||||
4.0.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* ``bcrypt`` is now implemented in Rust. Users building from source will need
|
|
||||||
to have a Rust compiler available. Nothing will change for users downloading
|
|
||||||
wheels.
|
|
||||||
* We no longer ship ``manylinux2010`` wheels. Users should upgrade to the latest
|
|
||||||
``pip`` to ensure this doesn’t cause issues downloading wheels on their
|
|
||||||
platform. We now ship ``manylinux_2_28`` wheels for users on new enough platforms.
|
|
||||||
* ``NUL`` bytes are now allowed in inputs.
|
|
||||||
|
|
||||||
|
|
||||||
3.2.2
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Fixed packaging of ``py.typed`` files in wheels so that ``mypy`` works.
|
|
||||||
|
|
||||||
3.2.1
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Added support for compilation on z/OS
|
|
||||||
* The next release of ``bcrypt`` with be 4.0 and it will require Rust at
|
|
||||||
compile time, for users building from source. There will be no additional
|
|
||||||
requirement for users who are installing from wheels. Users on most
|
|
||||||
platforms will be able to obtain a wheel by making sure they have an up to
|
|
||||||
date ``pip``. The minimum supported Rust version will be 1.56.0.
|
|
||||||
* This will be the final release for which we ship ``manylinux2010`` wheels.
|
|
||||||
Going forward the minimum supported manylinux ABI for our wheels will be
|
|
||||||
``manylinux2014``. The vast majority of users will continue to receive
|
|
||||||
``manylinux`` wheels provided they have an up to date ``pip``.
|
|
||||||
|
|
||||||
|
|
||||||
3.2.0
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Added typehints for library functions.
|
|
||||||
* Dropped support for Python versions less than 3.6 (2.7, 3.4, 3.5).
|
|
||||||
* Shipped ``abi3`` Windows wheels (requires pip >= 20).
|
|
||||||
|
|
||||||
3.1.7
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Set a ``setuptools`` lower bound for PEP517 wheel building.
|
|
||||||
* We no longer distribute 32-bit ``manylinux1`` wheels. Continuing to produce
|
|
||||||
them was a maintenance burden.
|
|
||||||
|
|
||||||
3.1.6
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Added support for compilation on Haiku.
|
|
||||||
|
|
||||||
3.1.5
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Added support for compilation on AIX.
|
|
||||||
* Dropped Python 2.6 and 3.3 support.
|
|
||||||
* Switched to using ``abi3`` wheels for Python 3. If you are not getting a
|
|
||||||
wheel on a compatible platform please upgrade your ``pip`` version.
|
|
||||||
|
|
||||||
3.1.4
|
|
||||||
-----
|
|
||||||
|
|
||||||
* Fixed compilation with mingw and on illumos.
|
|
||||||
|
|
||||||
3.1.3
|
|
||||||
-----
|
|
||||||
* Fixed a compilation issue on Solaris.
|
|
||||||
* Added a warning when using too few rounds with ``kdf``.
|
|
||||||
|
|
||||||
3.1.2
|
|
||||||
-----
|
|
||||||
* Fixed a compile issue affecting big endian platforms.
|
|
||||||
* Fixed invalid escape sequence warnings on Python 3.6.
|
|
||||||
* Fixed building in non-UTF8 environments on Python 2.
|
|
||||||
|
|
||||||
3.1.1
|
|
||||||
-----
|
|
||||||
* Resolved a ``UserWarning`` when used with ``cffi`` 1.8.3.
|
|
||||||
|
|
||||||
3.1.0
|
|
||||||
-----
|
|
||||||
* Added support for ``checkpw``, a convenience method for verifying a password.
|
|
||||||
* Ensure that you get a ``$2y$`` hash when you input a ``$2y$`` salt.
|
|
||||||
* Fixed a regression where ``$2a`` hashes were vulnerable to a wraparound bug.
|
|
||||||
* Fixed compilation under Alpine Linux.
|
|
||||||
|
|
||||||
3.0.0
|
|
||||||
-----
|
|
||||||
* Switched the C backend to code obtained from the OpenBSD project rather than
|
|
||||||
openwall.
|
|
||||||
* Added support for ``bcrypt_pbkdf`` via the ``kdf`` function.
|
|
||||||
|
|
||||||
2.0.0
|
|
||||||
-----
|
|
||||||
* Added support for an adjustible prefix when calling ``gensalt``.
|
|
||||||
* Switched to CFFI 1.0+
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
|
|
||||||
Password Hashing
|
|
||||||
~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Hashing and then later checking that a password matches the previous hashed
|
|
||||||
password is very simple:
|
|
||||||
|
|
||||||
.. code:: pycon
|
|
||||||
|
|
||||||
>>> import bcrypt
|
|
||||||
>>> password = b"super secret password"
|
|
||||||
>>> # Hash a password for the first time, with a randomly-generated salt
|
|
||||||
>>> hashed = bcrypt.hashpw(password, bcrypt.gensalt())
|
|
||||||
>>> # Check that an unhashed password matches one that has previously been
|
|
||||||
>>> # hashed
|
|
||||||
>>> if bcrypt.checkpw(password, hashed):
|
|
||||||
... print("It Matches!")
|
|
||||||
... else:
|
|
||||||
... print("It Does not Match :(")
|
|
||||||
|
|
||||||
KDF
|
|
||||||
~~~
|
|
||||||
|
|
||||||
As of 3.0.0 ``bcrypt`` now offers a ``kdf`` function which does ``bcrypt_pbkdf``.
|
|
||||||
This KDF is used in OpenSSH's newer encrypted private key format.
|
|
||||||
|
|
||||||
.. code:: pycon
|
|
||||||
|
|
||||||
>>> import bcrypt
|
|
||||||
>>> key = bcrypt.kdf(
|
|
||||||
... password=b'password',
|
|
||||||
... salt=b'salt',
|
|
||||||
... desired_key_bytes=32,
|
|
||||||
... rounds=100)
|
|
||||||
|
|
||||||
|
|
||||||
Adjustable Work Factor
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
One of bcrypt's features is an adjustable logarithmic work factor. To adjust
|
|
||||||
the work factor merely pass the desired number of rounds to
|
|
||||||
``bcrypt.gensalt(rounds=12)`` which defaults to 12):
|
|
||||||
|
|
||||||
.. code:: pycon
|
|
||||||
|
|
||||||
>>> import bcrypt
|
|
||||||
>>> password = b"super secret password"
|
|
||||||
>>> # Hash a password for the first time, with a certain number of rounds
|
|
||||||
>>> hashed = bcrypt.hashpw(password, bcrypt.gensalt(14))
|
|
||||||
>>> # Check that a unhashed password matches one that has previously been
|
|
||||||
>>> # hashed
|
|
||||||
>>> if bcrypt.checkpw(password, hashed):
|
|
||||||
... print("It Matches!")
|
|
||||||
... else:
|
|
||||||
... print("It Does not Match :(")
|
|
||||||
|
|
||||||
|
|
||||||
Adjustable Prefix
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Another one of bcrypt's features is an adjustable prefix to let you define what
|
|
||||||
libraries you'll remain compatible with. To adjust this, pass either ``2a`` or
|
|
||||||
``2b`` (the default) to ``bcrypt.gensalt(prefix=b"2b")`` as a bytes object.
|
|
||||||
|
|
||||||
As of 3.0.0 the ``$2y$`` prefix is still supported in ``hashpw`` but deprecated.
|
|
||||||
|
|
||||||
Maximum Password Length
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
The bcrypt algorithm only handles passwords up to 72 characters, any characters
|
|
||||||
beyond that are ignored. To work around this, a common approach is to hash a
|
|
||||||
password with a cryptographic hash (such as ``sha256``) and then base64
|
|
||||||
encode it to prevent NULL byte problems before hashing the result with
|
|
||||||
``bcrypt``:
|
|
||||||
|
|
||||||
.. code:: pycon
|
|
||||||
|
|
||||||
>>> password = b"an incredibly long password" * 10
|
|
||||||
>>> hashed = bcrypt.hashpw(
|
|
||||||
... base64.b64encode(hashlib.sha256(password).digest()),
|
|
||||||
... bcrypt.gensalt()
|
|
||||||
... )
|
|
||||||
|
|
||||||
Compatibility
|
|
||||||
-------------
|
|
||||||
|
|
||||||
This library should be compatible with py-bcrypt and it will run on Python
|
|
||||||
3.8+ (including free-threaded builds), and PyPy 3.
|
|
||||||
|
|
||||||
Security
|
|
||||||
--------
|
|
||||||
|
|
||||||
``bcrypt`` follows the `same security policy as cryptography`_, if you
|
|
||||||
identify a vulnerability, we ask you to contact us privately.
|
|
||||||
|
|
||||||
.. _`same security policy as cryptography`: https://cryptography.io/en/latest/security.html
|
|
||||||
.. _`standard library`: https://docs.python.org/3/library/hashlib.html#hashlib.scrypt
|
|
||||||
.. _`argon2_cffi`: https://argon2-cffi.readthedocs.io
|
|
||||||
.. _`cryptography`: https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#cryptography.hazmat.primitives.kdf.scrypt.Scrypt
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
bcrypt-5.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
bcrypt-5.0.0.dist-info/METADATA,sha256=yV1BfLlI6udlVy23eNbzDa62DSEbUrlWvlLBCI6UAdI,10524
|
|
||||||
bcrypt-5.0.0.dist-info/RECORD,,
|
|
||||||
bcrypt-5.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
bcrypt-5.0.0.dist-info/WHEEL,sha256=WieEZvWpc0Erab6-NfTu9412g-GcE58js6gvBn3Q7B4,111
|
|
||||||
bcrypt-5.0.0.dist-info/licenses/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
|
|
||||||
bcrypt-5.0.0.dist-info/top_level.txt,sha256=BkR_qBzDbSuycMzHWE1vzXrfYecAzUVmQs6G2CukqNI,7
|
|
||||||
bcrypt/__init__.py,sha256=cv-NupIX6P7o6A4PK_F0ur6IZoDr3GnvyzFO9k16wKQ,1000
|
|
||||||
bcrypt/__init__.pyi,sha256=ITUCB9mPVU8sKUbJQMDUH5YfQXZb1O55F9qvKZR_o8I,333
|
|
||||||
bcrypt/__pycache__/__init__.cpython-311.pyc,,
|
|
||||||
bcrypt/_bcrypt.abi3.so,sha256=oFwJu4Gq44FqJDttx_oWpypfuUQ30BkCWzD2FhojdYw,631768
|
|
||||||
bcrypt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: setuptools (80.9.0)
|
|
||||||
Root-Is-Purelib: false
|
|
||||||
Tag: cp39-abi3-manylinux_2_34_x86_64
|
|
||||||
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright [yyyy] [name of copyright owner]
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
bcrypt
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
|
|
||||||
from ._bcrypt import (
|
|
||||||
__author__,
|
|
||||||
__copyright__,
|
|
||||||
__email__,
|
|
||||||
__license__,
|
|
||||||
__summary__,
|
|
||||||
__title__,
|
|
||||||
__uri__,
|
|
||||||
checkpw,
|
|
||||||
gensalt,
|
|
||||||
hashpw,
|
|
||||||
kdf,
|
|
||||||
)
|
|
||||||
from ._bcrypt import (
|
|
||||||
__version_ex__ as __version__,
|
|
||||||
)
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"__author__",
|
|
||||||
"__copyright__",
|
|
||||||
"__email__",
|
|
||||||
"__license__",
|
|
||||||
"__summary__",
|
|
||||||
"__title__",
|
|
||||||
"__uri__",
|
|
||||||
"__version__",
|
|
||||||
"checkpw",
|
|
||||||
"gensalt",
|
|
||||||
"hashpw",
|
|
||||||
"kdf",
|
|
||||||
]
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes: ...
|
|
||||||
def hashpw(password: bytes, salt: bytes) -> bytes: ...
|
|
||||||
def checkpw(password: bytes, hashed_password: bytes) -> bool: ...
|
|
||||||
def kdf(
|
|
||||||
password: bytes,
|
|
||||||
salt: bytes,
|
|
||||||
desired_key_bytes: int,
|
|
||||||
rounds: int,
|
|
||||||
ignore_few_rounds: bool = False,
|
|
||||||
) -> bytes: ...
|
|
||||||
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
Copyright 2010 Jason Kirtland
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included
|
|
||||||
in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
||||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
||||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
||||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
||||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
||||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
Metadata-Version: 2.3
|
|
||||||
Name: blinker
|
|
||||||
Version: 1.9.0
|
|
||||||
Summary: Fast, simple object-to-object and broadcast signaling
|
|
||||||
Author: Jason Kirtland
|
|
||||||
Maintainer-email: Pallets Ecosystem <contact@palletsprojects.com>
|
|
||||||
Requires-Python: >=3.9
|
|
||||||
Description-Content-Type: text/markdown
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: License :: OSI Approved :: MIT License
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Typing :: Typed
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Project-URL: Documentation, https://blinker.readthedocs.io
|
|
||||||
Project-URL: Source, https://github.com/pallets-eco/blinker/
|
|
||||||
|
|
||||||
# Blinker
|
|
||||||
|
|
||||||
Blinker provides a fast dispatching system that allows any number of
|
|
||||||
interested parties to subscribe to events, or "signals".
|
|
||||||
|
|
||||||
|
|
||||||
## Pallets Community Ecosystem
|
|
||||||
|
|
||||||
> [!IMPORTANT]\
|
|
||||||
> This project is part of the Pallets Community Ecosystem. Pallets is the open
|
|
||||||
> source organization that maintains Flask; Pallets-Eco enables community
|
|
||||||
> maintenance of related projects. If you are interested in helping maintain
|
|
||||||
> this project, please reach out on [the Pallets Discord server][discord].
|
|
||||||
>
|
|
||||||
> [discord]: https://discord.gg/pallets
|
|
||||||
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
Signal receivers can subscribe to specific senders or receive signals
|
|
||||||
sent by any sender.
|
|
||||||
|
|
||||||
```pycon
|
|
||||||
>>> from blinker import signal
|
|
||||||
>>> started = signal('round-started')
|
|
||||||
>>> def each(round):
|
|
||||||
... print(f"Round {round}")
|
|
||||||
...
|
|
||||||
>>> started.connect(each)
|
|
||||||
|
|
||||||
>>> def round_two(round):
|
|
||||||
... print("This is round two.")
|
|
||||||
...
|
|
||||||
>>> started.connect(round_two, sender=2)
|
|
||||||
|
|
||||||
>>> for round in range(1, 4):
|
|
||||||
... started.send(round)
|
|
||||||
...
|
|
||||||
Round 1!
|
|
||||||
Round 2!
|
|
||||||
This is round two.
|
|
||||||
Round 3!
|
|
||||||
```
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
blinker-1.9.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
blinker-1.9.0.dist-info/LICENSE.txt,sha256=nrc6HzhZekqhcCXSrhvjg5Ykx5XphdTw6Xac4p-spGc,1054
|
|
||||||
blinker-1.9.0.dist-info/METADATA,sha256=uIRiM8wjjbHkCtbCyTvctU37IAZk0kEe5kxAld1dvzA,1633
|
|
||||||
blinker-1.9.0.dist-info/RECORD,,
|
|
||||||
blinker-1.9.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
||||||
blinker/__init__.py,sha256=I2EdZqpy4LyjX17Hn1yzJGWCjeLaVaPzsMgHkLfj_cQ,317
|
|
||||||
blinker/__pycache__/__init__.cpython-311.pyc,,
|
|
||||||
blinker/__pycache__/_utilities.cpython-311.pyc,,
|
|
||||||
blinker/__pycache__/base.cpython-311.pyc,,
|
|
||||||
blinker/_utilities.py,sha256=0J7eeXXTUx0Ivf8asfpx0ycVkp0Eqfqnj117x2mYX9E,1675
|
|
||||||
blinker/base.py,sha256=QpDuvXXcwJF49lUBcH5BiST46Rz9wSG7VW_p7N_027M,19132
|
|
||||||
blinker/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: flit 3.10.1
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from .base import ANY
|
|
||||||
from .base import default_namespace
|
|
||||||
from .base import NamedSignal
|
|
||||||
from .base import Namespace
|
|
||||||
from .base import Signal
|
|
||||||
from .base import signal
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"ANY",
|
|
||||||
"default_namespace",
|
|
||||||
"NamedSignal",
|
|
||||||
"Namespace",
|
|
||||||
"Signal",
|
|
||||||
"signal",
|
|
||||||
]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,64 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import collections.abc as c
|
|
||||||
import inspect
|
|
||||||
import typing as t
|
|
||||||
from weakref import ref
|
|
||||||
from weakref import WeakMethod
|
|
||||||
|
|
||||||
T = t.TypeVar("T")
|
|
||||||
|
|
||||||
|
|
||||||
class Symbol:
|
|
||||||
"""A constant symbol, nicer than ``object()``. Repeated calls return the
|
|
||||||
same instance.
|
|
||||||
|
|
||||||
>>> Symbol('foo') is Symbol('foo')
|
|
||||||
True
|
|
||||||
>>> Symbol('foo')
|
|
||||||
foo
|
|
||||||
"""
|
|
||||||
|
|
||||||
symbols: t.ClassVar[dict[str, Symbol]] = {}
|
|
||||||
|
|
||||||
def __new__(cls, name: str) -> Symbol:
|
|
||||||
if name in cls.symbols:
|
|
||||||
return cls.symbols[name]
|
|
||||||
|
|
||||||
obj = super().__new__(cls)
|
|
||||||
cls.symbols[name] = obj
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
|
||||||
self.name = name
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def __getnewargs__(self) -> tuple[t.Any, ...]:
|
|
||||||
return (self.name,)
|
|
||||||
|
|
||||||
|
|
||||||
def make_id(obj: object) -> c.Hashable:
|
|
||||||
"""Get a stable identifier for a receiver or sender, to be used as a dict
|
|
||||||
key or in a set.
|
|
||||||
"""
|
|
||||||
if inspect.ismethod(obj):
|
|
||||||
# The id of a bound method is not stable, but the id of the unbound
|
|
||||||
# function and instance are.
|
|
||||||
return id(obj.__func__), id(obj.__self__)
|
|
||||||
|
|
||||||
if isinstance(obj, (str, int)):
|
|
||||||
# Instances with the same value always compare equal and have the same
|
|
||||||
# hash, even if the id may change.
|
|
||||||
return obj
|
|
||||||
|
|
||||||
# Assume other types are not hashable but will always be the same instance.
|
|
||||||
return id(obj)
|
|
||||||
|
|
||||||
|
|
||||||
def make_ref(obj: T, callback: c.Callable[[ref[T]], None] | None = None) -> ref[T]:
|
|
||||||
if inspect.ismethod(obj):
|
|
||||||
return WeakMethod(obj, callback) # type: ignore[arg-type, return-value]
|
|
||||||
|
|
||||||
return ref(obj, callback)
|
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import collections.abc as c
|
|
||||||
import sys
|
|
||||||
import typing as t
|
|
||||||
import weakref
|
|
||||||
from collections import defaultdict
|
|
||||||
from contextlib import contextmanager
|
|
||||||
from functools import cached_property
|
|
||||||
from inspect import iscoroutinefunction
|
|
||||||
|
|
||||||
from ._utilities import make_id
|
|
||||||
from ._utilities import make_ref
|
|
||||||
from ._utilities import Symbol
|
|
||||||
|
|
||||||
F = t.TypeVar("F", bound=c.Callable[..., t.Any])
|
|
||||||
|
|
||||||
ANY = Symbol("ANY")
|
|
||||||
"""Symbol for "any sender"."""
|
|
||||||
|
|
||||||
ANY_ID = 0
|
|
||||||
|
|
||||||
|
|
||||||
class Signal:
|
|
||||||
"""A notification emitter.
|
|
||||||
|
|
||||||
:param doc: The docstring for the signal.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ANY = ANY
|
|
||||||
"""An alias for the :data:`~blinker.ANY` sender symbol."""
|
|
||||||
|
|
||||||
set_class: type[set[t.Any]] = set
|
|
||||||
"""The set class to use for tracking connected receivers and senders.
|
|
||||||
Python's ``set`` is unordered. If receivers must be dispatched in the order
|
|
||||||
they were connected, an ordered set implementation can be used.
|
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def receiver_connected(self) -> Signal:
|
|
||||||
"""Emitted at the end of each :meth:`connect` call.
|
|
||||||
|
|
||||||
The signal sender is the signal instance, and the :meth:`connect`
|
|
||||||
arguments are passed through: ``receiver``, ``sender``, and ``weak``.
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
"""
|
|
||||||
return Signal(doc="Emitted after a receiver connects.")
|
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def receiver_disconnected(self) -> Signal:
|
|
||||||
"""Emitted at the end of each :meth:`disconnect` call.
|
|
||||||
|
|
||||||
The sender is the signal instance, and the :meth:`disconnect` arguments
|
|
||||||
are passed through: ``receiver`` and ``sender``.
|
|
||||||
|
|
||||||
This signal is emitted **only** when :meth:`disconnect` is called
|
|
||||||
explicitly. This signal cannot be emitted by an automatic disconnect
|
|
||||||
when a weakly referenced receiver or sender goes out of scope, as the
|
|
||||||
instance is no longer be available to be used as the sender for this
|
|
||||||
signal.
|
|
||||||
|
|
||||||
An alternative approach is available by subscribing to
|
|
||||||
:attr:`receiver_connected` and setting up a custom weakref cleanup
|
|
||||||
callback on weak receivers and senders.
|
|
||||||
|
|
||||||
.. versionadded:: 1.2
|
|
||||||
"""
|
|
||||||
return Signal(doc="Emitted after a receiver disconnects.")
|
|
||||||
|
|
||||||
def __init__(self, doc: str | None = None) -> None:
|
|
||||||
if doc:
|
|
||||||
self.__doc__ = doc
|
|
||||||
|
|
||||||
self.receivers: dict[
|
|
||||||
t.Any, weakref.ref[c.Callable[..., t.Any]] | c.Callable[..., t.Any]
|
|
||||||
] = {}
|
|
||||||
"""The map of connected receivers. Useful to quickly check if any
|
|
||||||
receivers are connected to the signal: ``if s.receivers:``. The
|
|
||||||
structure and data is not part of the public API, but checking its
|
|
||||||
boolean value is.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.is_muted: bool = False
|
|
||||||
self._by_receiver: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
|
|
||||||
self._by_sender: dict[t.Any, set[t.Any]] = defaultdict(self.set_class)
|
|
||||||
self._weak_senders: dict[t.Any, weakref.ref[t.Any]] = {}
|
|
||||||
|
|
||||||
def connect(self, receiver: F, sender: t.Any = ANY, weak: bool = True) -> F:
|
|
||||||
"""Connect ``receiver`` to be called when the signal is sent by
|
|
||||||
``sender``.
|
|
||||||
|
|
||||||
:param receiver: The callable to call when :meth:`send` is called with
|
|
||||||
the given ``sender``, passing ``sender`` as a positional argument
|
|
||||||
along with any extra keyword arguments.
|
|
||||||
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
|
||||||
called when :meth:`send` is called with this sender. If ``ANY``, the
|
|
||||||
receiver will be called for any sender. A receiver may be connected
|
|
||||||
to multiple senders by calling :meth:`connect` multiple times.
|
|
||||||
:param weak: Track the receiver with a :mod:`weakref`. The receiver will
|
|
||||||
be automatically disconnected when it is garbage collected. When
|
|
||||||
connecting a receiver defined within a function, set to ``False``,
|
|
||||||
otherwise it will be disconnected when the function scope ends.
|
|
||||||
"""
|
|
||||||
receiver_id = make_id(receiver)
|
|
||||||
sender_id = ANY_ID if sender is ANY else make_id(sender)
|
|
||||||
|
|
||||||
if weak:
|
|
||||||
self.receivers[receiver_id] = make_ref(
|
|
||||||
receiver, self._make_cleanup_receiver(receiver_id)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.receivers[receiver_id] = receiver
|
|
||||||
|
|
||||||
self._by_sender[sender_id].add(receiver_id)
|
|
||||||
self._by_receiver[receiver_id].add(sender_id)
|
|
||||||
|
|
||||||
if sender is not ANY and sender_id not in self._weak_senders:
|
|
||||||
# store a cleanup for weakref-able senders
|
|
||||||
try:
|
|
||||||
self._weak_senders[sender_id] = make_ref(
|
|
||||||
sender, self._make_cleanup_sender(sender_id)
|
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if "receiver_connected" in self.__dict__ and self.receiver_connected.receivers:
|
|
||||||
try:
|
|
||||||
self.receiver_connected.send(
|
|
||||||
self, receiver=receiver, sender=sender, weak=weak
|
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
# TODO no explanation or test for this
|
|
||||||
self.disconnect(receiver, sender)
|
|
||||||
raise
|
|
||||||
|
|
||||||
return receiver
|
|
||||||
|
|
||||||
def connect_via(self, sender: t.Any, weak: bool = False) -> c.Callable[[F], F]:
|
|
||||||
"""Connect the decorated function to be called when the signal is sent
|
|
||||||
by ``sender``.
|
|
||||||
|
|
||||||
The decorated function will be called when :meth:`send` is called with
|
|
||||||
the given ``sender``, passing ``sender`` as a positional argument along
|
|
||||||
with any extra keyword arguments.
|
|
||||||
|
|
||||||
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
|
||||||
called when :meth:`send` is called with this sender. If ``ANY``, the
|
|
||||||
receiver will be called for any sender. A receiver may be connected
|
|
||||||
to multiple senders by calling :meth:`connect` multiple times.
|
|
||||||
:param weak: Track the receiver with a :mod:`weakref`. The receiver will
|
|
||||||
be automatically disconnected when it is garbage collected. When
|
|
||||||
connecting a receiver defined within a function, set to ``False``,
|
|
||||||
otherwise it will be disconnected when the function scope ends.=
|
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
|
||||||
"""
|
|
||||||
|
|
||||||
def decorator(fn: F) -> F:
|
|
||||||
self.connect(fn, sender, weak)
|
|
||||||
return fn
|
|
||||||
|
|
||||||
return decorator
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def connected_to(
|
|
||||||
self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY
|
|
||||||
) -> c.Generator[None, None, None]:
|
|
||||||
"""A context manager that temporarily connects ``receiver`` to the
|
|
||||||
signal while a ``with`` block executes. When the block exits, the
|
|
||||||
receiver is disconnected. Useful for tests.
|
|
||||||
|
|
||||||
:param receiver: The callable to call when :meth:`send` is called with
|
|
||||||
the given ``sender``, passing ``sender`` as a positional argument
|
|
||||||
along with any extra keyword arguments.
|
|
||||||
:param sender: Any object or :data:`ANY`. ``receiver`` will only be
|
|
||||||
called when :meth:`send` is called with this sender. If ``ANY``, the
|
|
||||||
receiver will be called for any sender.
|
|
||||||
|
|
||||||
.. versionadded:: 1.1
|
|
||||||
"""
|
|
||||||
self.connect(receiver, sender=sender, weak=False)
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield None
|
|
||||||
finally:
|
|
||||||
self.disconnect(receiver)
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def muted(self) -> c.Generator[None, None, None]:
|
|
||||||
"""A context manager that temporarily disables the signal. No receivers
|
|
||||||
will be called if the signal is sent, until the ``with`` block exits.
|
|
||||||
Useful for tests.
|
|
||||||
"""
|
|
||||||
self.is_muted = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
yield None
|
|
||||||
finally:
|
|
||||||
self.is_muted = False
|
|
||||||
|
|
||||||
def send(
|
|
||||||
self,
|
|
||||||
sender: t.Any | None = None,
|
|
||||||
/,
|
|
||||||
*,
|
|
||||||
_async_wrapper: c.Callable[
|
|
||||||
[c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]], c.Callable[..., t.Any]
|
|
||||||
]
|
|
||||||
| None = None,
|
|
||||||
**kwargs: t.Any,
|
|
||||||
) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
|
|
||||||
"""Call all receivers that are connected to the given ``sender``
|
|
||||||
or :data:`ANY`. Each receiver is called with ``sender`` as a positional
|
|
||||||
argument along with any extra keyword arguments. Return a list of
|
|
||||||
``(receiver, return value)`` tuples.
|
|
||||||
|
|
||||||
The order receivers are called is undefined, but can be influenced by
|
|
||||||
setting :attr:`set_class`.
|
|
||||||
|
|
||||||
If a receiver raises an exception, that exception will propagate up.
|
|
||||||
This makes debugging straightforward, with an assumption that correctly
|
|
||||||
implemented receivers will not raise.
|
|
||||||
|
|
||||||
:param sender: Call receivers connected to this sender, in addition to
|
|
||||||
those connected to :data:`ANY`.
|
|
||||||
:param _async_wrapper: Will be called on any receivers that are async
|
|
||||||
coroutines to turn them into sync callables. For example, could run
|
|
||||||
the receiver with an event loop.
|
|
||||||
:param kwargs: Extra keyword arguments to pass to each receiver.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
|
||||||
Added the ``_async_wrapper`` argument.
|
|
||||||
"""
|
|
||||||
if self.is_muted:
|
|
||||||
return []
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for receiver in self.receivers_for(sender):
|
|
||||||
if iscoroutinefunction(receiver):
|
|
||||||
if _async_wrapper is None:
|
|
||||||
raise RuntimeError("Cannot send to a coroutine function.")
|
|
||||||
|
|
||||||
result = _async_wrapper(receiver)(sender, **kwargs)
|
|
||||||
else:
|
|
||||||
result = receiver(sender, **kwargs)
|
|
||||||
|
|
||||||
results.append((receiver, result))
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
async def send_async(
|
|
||||||
self,
|
|
||||||
sender: t.Any | None = None,
|
|
||||||
/,
|
|
||||||
*,
|
|
||||||
_sync_wrapper: c.Callable[
|
|
||||||
[c.Callable[..., t.Any]], c.Callable[..., c.Coroutine[t.Any, t.Any, t.Any]]
|
|
||||||
]
|
|
||||||
| None = None,
|
|
||||||
**kwargs: t.Any,
|
|
||||||
) -> list[tuple[c.Callable[..., t.Any], t.Any]]:
|
|
||||||
"""Await all receivers that are connected to the given ``sender``
|
|
||||||
or :data:`ANY`. Each receiver is called with ``sender`` as a positional
|
|
||||||
argument along with any extra keyword arguments. Return a list of
|
|
||||||
``(receiver, return value)`` tuples.
|
|
||||||
|
|
||||||
The order receivers are called is undefined, but can be influenced by
|
|
||||||
setting :attr:`set_class`.
|
|
||||||
|
|
||||||
If a receiver raises an exception, that exception will propagate up.
|
|
||||||
This makes debugging straightforward, with an assumption that correctly
|
|
||||||
implemented receivers will not raise.
|
|
||||||
|
|
||||||
:param sender: Call receivers connected to this sender, in addition to
|
|
||||||
those connected to :data:`ANY`.
|
|
||||||
:param _sync_wrapper: Will be called on any receivers that are sync
|
|
||||||
callables to turn them into async coroutines. For example,
|
|
||||||
could call the receiver in a thread.
|
|
||||||
:param kwargs: Extra keyword arguments to pass to each receiver.
|
|
||||||
|
|
||||||
.. versionadded:: 1.7
|
|
||||||
"""
|
|
||||||
if self.is_muted:
|
|
||||||
return []
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
for receiver in self.receivers_for(sender):
|
|
||||||
if not iscoroutinefunction(receiver):
|
|
||||||
if _sync_wrapper is None:
|
|
||||||
raise RuntimeError("Cannot send to a non-coroutine function.")
|
|
||||||
|
|
||||||
result = await _sync_wrapper(receiver)(sender, **kwargs)
|
|
||||||
else:
|
|
||||||
result = await receiver(sender, **kwargs)
|
|
||||||
|
|
||||||
results.append((receiver, result))
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def has_receivers_for(self, sender: t.Any) -> bool:
|
|
||||||
"""Check if there is at least one receiver that will be called with the
|
|
||||||
given ``sender``. A receiver connected to :data:`ANY` will always be
|
|
||||||
called, regardless of sender. Does not check if weakly referenced
|
|
||||||
receivers are still live. See :meth:`receivers_for` for a stronger
|
|
||||||
search.
|
|
||||||
|
|
||||||
:param sender: Check for receivers connected to this sender, in addition
|
|
||||||
to those connected to :data:`ANY`.
|
|
||||||
"""
|
|
||||||
if not self.receivers:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if self._by_sender[ANY_ID]:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if sender is ANY:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return make_id(sender) in self._by_sender
|
|
||||||
|
|
||||||
def receivers_for(
|
|
||||||
self, sender: t.Any
|
|
||||||
) -> c.Generator[c.Callable[..., t.Any], None, None]:
|
|
||||||
"""Yield each receiver to be called for ``sender``, in addition to those
|
|
||||||
to be called for :data:`ANY`. Weakly referenced receivers that are not
|
|
||||||
live will be disconnected and skipped.
|
|
||||||
|
|
||||||
:param sender: Yield receivers connected to this sender, in addition
|
|
||||||
to those connected to :data:`ANY`.
|
|
||||||
"""
|
|
||||||
# TODO: test receivers_for(ANY)
|
|
||||||
if not self.receivers:
|
|
||||||
return
|
|
||||||
|
|
||||||
sender_id = make_id(sender)
|
|
||||||
|
|
||||||
if sender_id in self._by_sender:
|
|
||||||
ids = self._by_sender[ANY_ID] | self._by_sender[sender_id]
|
|
||||||
else:
|
|
||||||
ids = self._by_sender[ANY_ID].copy()
|
|
||||||
|
|
||||||
for receiver_id in ids:
|
|
||||||
receiver = self.receivers.get(receiver_id)
|
|
||||||
|
|
||||||
if receiver is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if isinstance(receiver, weakref.ref):
|
|
||||||
strong = receiver()
|
|
||||||
|
|
||||||
if strong is None:
|
|
||||||
self._disconnect(receiver_id, ANY_ID)
|
|
||||||
continue
|
|
||||||
|
|
||||||
yield strong
|
|
||||||
else:
|
|
||||||
yield receiver
|
|
||||||
|
|
||||||
def disconnect(self, receiver: c.Callable[..., t.Any], sender: t.Any = ANY) -> None:
|
|
||||||
"""Disconnect ``receiver`` from being called when the signal is sent by
|
|
||||||
``sender``.
|
|
||||||
|
|
||||||
:param receiver: A connected receiver callable.
|
|
||||||
:param sender: Disconnect from only this sender. By default, disconnect
|
|
||||||
from all senders.
|
|
||||||
"""
|
|
||||||
sender_id: c.Hashable
|
|
||||||
|
|
||||||
if sender is ANY:
|
|
||||||
sender_id = ANY_ID
|
|
||||||
else:
|
|
||||||
sender_id = make_id(sender)
|
|
||||||
|
|
||||||
receiver_id = make_id(receiver)
|
|
||||||
self._disconnect(receiver_id, sender_id)
|
|
||||||
|
|
||||||
if (
|
|
||||||
"receiver_disconnected" in self.__dict__
|
|
||||||
and self.receiver_disconnected.receivers
|
|
||||||
):
|
|
||||||
self.receiver_disconnected.send(self, receiver=receiver, sender=sender)
|
|
||||||
|
|
||||||
def _disconnect(self, receiver_id: c.Hashable, sender_id: c.Hashable) -> None:
|
|
||||||
if sender_id == ANY_ID:
|
|
||||||
if self._by_receiver.pop(receiver_id, None) is not None:
|
|
||||||
for bucket in self._by_sender.values():
|
|
||||||
bucket.discard(receiver_id)
|
|
||||||
|
|
||||||
self.receivers.pop(receiver_id, None)
|
|
||||||
else:
|
|
||||||
self._by_sender[sender_id].discard(receiver_id)
|
|
||||||
self._by_receiver[receiver_id].discard(sender_id)
|
|
||||||
|
|
||||||
def _make_cleanup_receiver(
|
|
||||||
self, receiver_id: c.Hashable
|
|
||||||
) -> c.Callable[[weakref.ref[c.Callable[..., t.Any]]], None]:
|
|
||||||
"""Create a callback function to disconnect a weakly referenced
|
|
||||||
receiver when it is garbage collected.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def cleanup(ref: weakref.ref[c.Callable[..., t.Any]]) -> None:
|
|
||||||
# If the interpreter is shutting down, disconnecting can result in a
|
|
||||||
# weird ignored exception. Don't call it in that case.
|
|
||||||
if not sys.is_finalizing():
|
|
||||||
self._disconnect(receiver_id, ANY_ID)
|
|
||||||
|
|
||||||
return cleanup
|
|
||||||
|
|
||||||
def _make_cleanup_sender(
|
|
||||||
self, sender_id: c.Hashable
|
|
||||||
) -> c.Callable[[weakref.ref[t.Any]], None]:
|
|
||||||
"""Create a callback function to disconnect all receivers for a weakly
|
|
||||||
referenced sender when it is garbage collected.
|
|
||||||
"""
|
|
||||||
assert sender_id != ANY_ID
|
|
||||||
|
|
||||||
def cleanup(ref: weakref.ref[t.Any]) -> None:
|
|
||||||
self._weak_senders.pop(sender_id, None)
|
|
||||||
|
|
||||||
for receiver_id in self._by_sender.pop(sender_id, ()):
|
|
||||||
self._by_receiver[receiver_id].discard(sender_id)
|
|
||||||
|
|
||||||
return cleanup
|
|
||||||
|
|
||||||
def _cleanup_bookkeeping(self) -> None:
|
|
||||||
"""Prune unused sender/receiver bookkeeping. Not threadsafe.
|
|
||||||
|
|
||||||
Connecting & disconnecting leaves behind a small amount of bookkeeping
|
|
||||||
data. Typical workloads using Blinker, for example in most web apps,
|
|
||||||
Flask, CLI scripts, etc., are not adversely affected by this
|
|
||||||
bookkeeping.
|
|
||||||
|
|
||||||
With a long-running process performing dynamic signal routing with high
|
|
||||||
volume, e.g. connecting to function closures, senders are all unique
|
|
||||||
object instances. Doing all of this over and over may cause memory usage
|
|
||||||
to grow due to extraneous bookkeeping. (An empty ``set`` for each stale
|
|
||||||
sender/receiver pair.)
|
|
||||||
|
|
||||||
This method will prune that bookkeeping away, with the caveat that such
|
|
||||||
pruning is not threadsafe. The risk is that cleanup of a fully
|
|
||||||
disconnected receiver/sender pair occurs while another thread is
|
|
||||||
connecting that same pair. If you are in the highly dynamic, unique
|
|
||||||
receiver/sender situation that has lead you to this method, that failure
|
|
||||||
mode is perhaps not a big deal for you.
|
|
||||||
"""
|
|
||||||
for mapping in (self._by_sender, self._by_receiver):
|
|
||||||
for ident, bucket in list(mapping.items()):
|
|
||||||
if not bucket:
|
|
||||||
mapping.pop(ident, None)
|
|
||||||
|
|
||||||
def _clear_state(self) -> None:
|
|
||||||
"""Disconnect all receivers and senders. Useful for tests."""
|
|
||||||
self._weak_senders.clear()
|
|
||||||
self.receivers.clear()
|
|
||||||
self._by_sender.clear()
|
|
||||||
self._by_receiver.clear()
|
|
||||||
|
|
||||||
|
|
||||||
class NamedSignal(Signal):
|
|
||||||
"""A named generic notification emitter. The name is not used by the signal
|
|
||||||
itself, but matches the key in the :class:`Namespace` that it belongs to.
|
|
||||||
|
|
||||||
:param name: The name of the signal within the namespace.
|
|
||||||
:param doc: The docstring for the signal.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name: str, doc: str | None = None) -> None:
|
|
||||||
super().__init__(doc)
|
|
||||||
|
|
||||||
#: The name of this signal.
|
|
||||||
self.name: str = name
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
base = super().__repr__()
|
|
||||||
return f"{base[:-1]}; {self.name!r}>" # noqa: E702
|
|
||||||
|
|
||||||
|
|
||||||
class Namespace(dict[str, NamedSignal]):
|
|
||||||
"""A dict mapping names to signals."""
|
|
||||||
|
|
||||||
def signal(self, name: str, doc: str | None = None) -> NamedSignal:
|
|
||||||
"""Return the :class:`NamedSignal` for the given ``name``, creating it
|
|
||||||
if required. Repeated calls with the same name return the same signal.
|
|
||||||
|
|
||||||
:param name: The name of the signal.
|
|
||||||
:param doc: The docstring of the signal.
|
|
||||||
"""
|
|
||||||
if name not in self:
|
|
||||||
self[name] = NamedSignal(name, doc)
|
|
||||||
|
|
||||||
return self[name]
|
|
||||||
|
|
||||||
|
|
||||||
class _PNamespaceSignal(t.Protocol):
|
|
||||||
def __call__(self, name: str, doc: str | None = None) -> NamedSignal: ...
|
|
||||||
|
|
||||||
|
|
||||||
default_namespace: Namespace = Namespace()
|
|
||||||
"""A default :class:`Namespace` for creating named signals. :func:`signal`
|
|
||||||
creates a :class:`NamedSignal` in this namespace.
|
|
||||||
"""
|
|
||||||
|
|
||||||
signal: _PNamespaceSignal = default_namespace.signal
|
|
||||||
"""Return a :class:`NamedSignal` in :data:`default_namespace` with the given
|
|
||||||
``name``, creating it if required. Repeated calls with the same name return the
|
|
||||||
same signal.
|
|
||||||
"""
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pip
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
Metadata-Version: 2.4
|
|
||||||
Name: click
|
|
||||||
Version: 8.3.1
|
|
||||||
Summary: Composable command line interface toolkit
|
|
||||||
Maintainer-email: Pallets <contact@palletsprojects.com>
|
|
||||||
Requires-Python: >=3.10
|
|
||||||
Description-Content-Type: text/markdown
|
|
||||||
License-Expression: BSD-3-Clause
|
|
||||||
Classifier: Development Status :: 5 - Production/Stable
|
|
||||||
Classifier: Intended Audience :: Developers
|
|
||||||
Classifier: Operating System :: OS Independent
|
|
||||||
Classifier: Programming Language :: Python
|
|
||||||
Classifier: Typing :: Typed
|
|
||||||
License-File: LICENSE.txt
|
|
||||||
Requires-Dist: colorama; platform_system == 'Windows'
|
|
||||||
Project-URL: Changes, https://click.palletsprojects.com/page/changes/
|
|
||||||
Project-URL: Chat, https://discord.gg/pallets
|
|
||||||
Project-URL: Documentation, https://click.palletsprojects.com/
|
|
||||||
Project-URL: Donate, https://palletsprojects.com/donate
|
|
||||||
Project-URL: Source, https://github.com/pallets/click/
|
|
||||||
|
|
||||||
<div align="center"><img src="https://raw.githubusercontent.com/pallets/click/refs/heads/stable/docs/_static/click-name.svg" alt="" height="150"></div>
|
|
||||||
|
|
||||||
# Click
|
|
||||||
|
|
||||||
Click is a Python package for creating beautiful command line interfaces
|
|
||||||
in a composable way with as little code as necessary. It's the "Command
|
|
||||||
Line Interface Creation Kit". It's highly configurable but comes with
|
|
||||||
sensible defaults out of the box.
|
|
||||||
|
|
||||||
It aims to make the process of writing command line tools quick and fun
|
|
||||||
while also preventing any frustration caused by the inability to
|
|
||||||
implement an intended CLI API.
|
|
||||||
|
|
||||||
Click in three points:
|
|
||||||
|
|
||||||
- Arbitrary nesting of commands
|
|
||||||
- Automatic help page generation
|
|
||||||
- Supports lazy loading of subcommands at runtime
|
|
||||||
|
|
||||||
|
|
||||||
## A Simple Example
|
|
||||||
|
|
||||||
```python
|
|
||||||
import click
|
|
||||||
|
|
||||||
@click.command()
|
|
||||||
@click.option("--count", default=1, help="Number of greetings.")
|
|
||||||
@click.option("--name", prompt="Your name", help="The person to greet.")
|
|
||||||
def hello(count, name):
|
|
||||||
"""Simple program that greets NAME for a total of COUNT times."""
|
|
||||||
for _ in range(count):
|
|
||||||
click.echo(f"Hello, {name}!")
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
hello()
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
$ python hello.py --count=3
|
|
||||||
Your name: Click
|
|
||||||
Hello, Click!
|
|
||||||
Hello, Click!
|
|
||||||
Hello, Click!
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Donate
|
|
||||||
|
|
||||||
The Pallets organization develops and supports Click and other popular
|
|
||||||
packages. In order to grow the community of contributors and users, and
|
|
||||||
allow the maintainers to devote more time to the projects, [please
|
|
||||||
donate today][].
|
|
||||||
|
|
||||||
[please donate today]: https://palletsprojects.com/donate
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
See our [detailed contributing documentation][contrib] for many ways to
|
|
||||||
contribute, including reporting issues, requesting features, asking or answering
|
|
||||||
questions, and making PRs.
|
|
||||||
|
|
||||||
[contrib]: https://palletsprojects.com/contributing/
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
click-8.3.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
|
||||||
click-8.3.1.dist-info/METADATA,sha256=XZeBrMAE0ghTE88SjfrSDuSyNCpBPplxJR1tbwD9oZg,2621
|
|
||||||
click-8.3.1.dist-info/RECORD,,
|
|
||||||
click-8.3.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
||||||
click-8.3.1.dist-info/licenses/LICENSE.txt,sha256=morRBqOU6FO_4h9C9OctWSgZoigF2ZG18ydQKSkrZY0,1475
|
|
||||||
click/__init__.py,sha256=6YyS1aeyknZ0LYweWozNZy0A9nZ_11wmYIhv3cbQrYo,4473
|
|
||||||
click/__pycache__/__init__.cpython-311.pyc,,
|
|
||||||
click/__pycache__/_compat.cpython-311.pyc,,
|
|
||||||
click/__pycache__/_termui_impl.cpython-311.pyc,,
|
|
||||||
click/__pycache__/_textwrap.cpython-311.pyc,,
|
|
||||||
click/__pycache__/_utils.cpython-311.pyc,,
|
|
||||||
click/__pycache__/_winconsole.cpython-311.pyc,,
|
|
||||||
click/__pycache__/core.cpython-311.pyc,,
|
|
||||||
click/__pycache__/decorators.cpython-311.pyc,,
|
|
||||||
click/__pycache__/exceptions.cpython-311.pyc,,
|
|
||||||
click/__pycache__/formatting.cpython-311.pyc,,
|
|
||||||
click/__pycache__/globals.cpython-311.pyc,,
|
|
||||||
click/__pycache__/parser.cpython-311.pyc,,
|
|
||||||
click/__pycache__/shell_completion.cpython-311.pyc,,
|
|
||||||
click/__pycache__/termui.cpython-311.pyc,,
|
|
||||||
click/__pycache__/testing.cpython-311.pyc,,
|
|
||||||
click/__pycache__/types.cpython-311.pyc,,
|
|
||||||
click/__pycache__/utils.cpython-311.pyc,,
|
|
||||||
click/_compat.py,sha256=v3xBZkFbvA1BXPRkFfBJc6-pIwPI7345m-kQEnpVAs4,18693
|
|
||||||
click/_termui_impl.py,sha256=rgCb3On8X5A4200rA5L6i13u5iapmFer7sru57Jy6zA,27093
|
|
||||||
click/_textwrap.py,sha256=BOae0RQ6vg3FkNgSJyOoGzG1meGMxJ_ukWVZKx_v-0o,1400
|
|
||||||
click/_utils.py,sha256=kZwtTf5gMuCilJJceS2iTCvRvCY-0aN5rJq8gKw7p8g,943
|
|
||||||
click/_winconsole.py,sha256=_vxUuUaxwBhoR0vUWCNuHY8VUefiMdCIyU2SXPqoF-A,8465
|
|
||||||
click/core.py,sha256=U6Bfxt8GkjNDqyJ0HqXvluJHtyZ4sY5USAvM1Cdq7mQ,132105
|
|
||||||
click/decorators.py,sha256=5P7abhJtAQYp_KHgjUvhMv464ERwOzrv2enNknlwHyQ,18461
|
|
||||||
click/exceptions.py,sha256=8utf8w6V5hJXMnO_ic1FNrtbwuEn1NUu1aDwV8UqnG4,9954
|
|
||||||
click/formatting.py,sha256=RVfwwr0rwWNpgGr8NaHodPzkIr7_tUyVh_nDdanLMNc,9730
|
|
||||||
click/globals.py,sha256=gM-Nh6A4M0HB_SgkaF5M4ncGGMDHc_flHXu9_oh4GEU,1923
|
|
||||||
click/parser.py,sha256=Q31pH0FlQZEq-UXE_ABRzlygEfvxPTuZbWNh4xfXmzw,19010
|
|
||||||
click/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
||||||
click/shell_completion.py,sha256=Cc4GQUFuWpfQBa9sF5qXeeYI7n3tI_1k6ZdSn4BZbT0,20994
|
|
||||||
click/termui.py,sha256=hqCEjNndU-nzW08nRAkBaVgfZp_FdCA9KxfIWlKYaMc,31037
|
|
||||||
click/testing.py,sha256=EERbzcl1br0mW0qBS9EqkknfNfXB9WQEW0ELIpkvuSs,19102
|
|
||||||
click/types.py,sha256=ek54BNSFwPKsqtfT7jsqcc4WHui8AIFVMKM4oVZIXhc,39927
|
|
||||||
click/utils.py,sha256=gCUoewdAhA-QLBUUHxrLh4uj6m7T1WjZZMNPvR0I7YA,20257
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
Wheel-Version: 1.0
|
|
||||||
Generator: flit 3.12.0
|
|
||||||
Root-Is-Purelib: true
|
|
||||||
Tag: py3-none-any
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
Copyright 2014 Pallets
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
1. Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
2. Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
3. Neither the name of the copyright holder nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
|
|
||||||
TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
||||||
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
||||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
||||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
"""
|
|
||||||
Click is a simple Python module inspired by the stdlib optparse to make
|
|
||||||
writing command line scripts fun. Unlike other modules, it's based
|
|
||||||
around a simple API that does not come with too much magic and is
|
|
||||||
composable.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
from .core import Argument as Argument
|
|
||||||
from .core import Command as Command
|
|
||||||
from .core import CommandCollection as CommandCollection
|
|
||||||
from .core import Context as Context
|
|
||||||
from .core import Group as Group
|
|
||||||
from .core import Option as Option
|
|
||||||
from .core import Parameter as Parameter
|
|
||||||
from .decorators import argument as argument
|
|
||||||
from .decorators import command as command
|
|
||||||
from .decorators import confirmation_option as confirmation_option
|
|
||||||
from .decorators import group as group
|
|
||||||
from .decorators import help_option as help_option
|
|
||||||
from .decorators import make_pass_decorator as make_pass_decorator
|
|
||||||
from .decorators import option as option
|
|
||||||
from .decorators import pass_context as pass_context
|
|
||||||
from .decorators import pass_obj as pass_obj
|
|
||||||
from .decorators import password_option as password_option
|
|
||||||
from .decorators import version_option as version_option
|
|
||||||
from .exceptions import Abort as Abort
|
|
||||||
from .exceptions import BadArgumentUsage as BadArgumentUsage
|
|
||||||
from .exceptions import BadOptionUsage as BadOptionUsage
|
|
||||||
from .exceptions import BadParameter as BadParameter
|
|
||||||
from .exceptions import ClickException as ClickException
|
|
||||||
from .exceptions import FileError as FileError
|
|
||||||
from .exceptions import MissingParameter as MissingParameter
|
|
||||||
from .exceptions import NoSuchOption as NoSuchOption
|
|
||||||
from .exceptions import UsageError as UsageError
|
|
||||||
from .formatting import HelpFormatter as HelpFormatter
|
|
||||||
from .formatting import wrap_text as wrap_text
|
|
||||||
from .globals import get_current_context as get_current_context
|
|
||||||
from .termui import clear as clear
|
|
||||||
from .termui import confirm as confirm
|
|
||||||
from .termui import echo_via_pager as echo_via_pager
|
|
||||||
from .termui import edit as edit
|
|
||||||
from .termui import getchar as getchar
|
|
||||||
from .termui import launch as launch
|
|
||||||
from .termui import pause as pause
|
|
||||||
from .termui import progressbar as progressbar
|
|
||||||
from .termui import prompt as prompt
|
|
||||||
from .termui import secho as secho
|
|
||||||
from .termui import style as style
|
|
||||||
from .termui import unstyle as unstyle
|
|
||||||
from .types import BOOL as BOOL
|
|
||||||
from .types import Choice as Choice
|
|
||||||
from .types import DateTime as DateTime
|
|
||||||
from .types import File as File
|
|
||||||
from .types import FLOAT as FLOAT
|
|
||||||
from .types import FloatRange as FloatRange
|
|
||||||
from .types import INT as INT
|
|
||||||
from .types import IntRange as IntRange
|
|
||||||
from .types import ParamType as ParamType
|
|
||||||
from .types import Path as Path
|
|
||||||
from .types import STRING as STRING
|
|
||||||
from .types import Tuple as Tuple
|
|
||||||
from .types import UNPROCESSED as UNPROCESSED
|
|
||||||
from .types import UUID as UUID
|
|
||||||
from .utils import echo as echo
|
|
||||||
from .utils import format_filename as format_filename
|
|
||||||
from .utils import get_app_dir as get_app_dir
|
|
||||||
from .utils import get_binary_stream as get_binary_stream
|
|
||||||
from .utils import get_text_stream as get_text_stream
|
|
||||||
from .utils import open_file as open_file
|
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(name: str) -> object:
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
if name == "BaseCommand":
|
|
||||||
from .core import _BaseCommand
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'BaseCommand' is deprecated and will be removed in Click 9.0. Use"
|
|
||||||
" 'Command' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return _BaseCommand
|
|
||||||
|
|
||||||
if name == "MultiCommand":
|
|
||||||
from .core import _MultiCommand
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'MultiCommand' is deprecated and will be removed in Click 9.0. Use"
|
|
||||||
" 'Group' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return _MultiCommand
|
|
||||||
|
|
||||||
if name == "OptionParser":
|
|
||||||
from .parser import _OptionParser
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"'OptionParser' is deprecated and will be removed in Click 9.0. The"
|
|
||||||
" old parser is available in 'optparse'.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return _OptionParser
|
|
||||||
|
|
||||||
if name == "__version__":
|
|
||||||
import importlib.metadata
|
|
||||||
import warnings
|
|
||||||
|
|
||||||
warnings.warn(
|
|
||||||
"The '__version__' attribute is deprecated and will be removed in"
|
|
||||||
" Click 9.1. Use feature detection or"
|
|
||||||
" 'importlib.metadata.version(\"click\")' instead.",
|
|
||||||
DeprecationWarning,
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
return importlib.metadata.version("click")
|
|
||||||
|
|
||||||
raise AttributeError(name)
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user