inpython-package 1.1.7__tar.gz → 1.1.9__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- inpython_package-1.1.9/PKG-INFO +79 -0
- inpython_package-1.1.9/README.md +41 -0
- inpython_package-1.1.9/inpython/injcd.py +15 -0
- {inpython_package-1.1.7/inpython/inxml → inpython_package-1.1.9/inpython/inqrcode}/__init__.py +1 -1
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/inqrcode/paymentqrcode.py +20 -11
- inpython_package-1.1.9/inpython/inrest/__init__.py +6 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/inrest/in_jwt.py +55 -44
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/intools/Servalux-devis.py +15 -11
- inpython_package-1.1.9/inpython/intools/__init__.py +6 -0
- inpython_package-1.1.9/inpython/intools/in_cron.py +66 -0
- inpython_package-1.1.9/inpython/intools/in_docx.py +162 -0
- inpython_package-1.1.7/inpython/intools/in_docx.py → inpython_package-1.1.9/inpython/intools/in_docx_extract.py +23 -16
- inpython_package-1.1.9/inpython/intools/in_docx_extract_canvas.py +257 -0
- inpython_package-1.1.9/inpython/intools/in_docx_protect.py +73 -0
- inpython_package-1.1.9/inpython/intools/in_geo_calc.py +25 -0
- inpython_package-1.1.9/inpython/intools/in_log.py +81 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/intools/in_projet.py +9 -7
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/intools/in_test.py +1 -2
- inpython_package-1.1.9/inpython/intools/xls_to_csv.py +126 -0
- inpython_package-1.1.9/inpython/servalux/__init__.py +6 -0
- inpython_package-1.1.9/inpython/servalux/docx_ext_schema_svl.py +16 -0
- inpython_package-1.1.9/inpython/servalux/servalux-devis.py +202 -0
- inpython_package-1.1.9/inpython_package.egg-info/PKG-INFO +79 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython_package.egg-info/SOURCES.txt +10 -2
- inpython_package-1.1.9/inpython_package.egg-info/requires.txt +18 -0
- inpython_package-1.1.9/setup.py +41 -0
- inpython_package-1.1.7/PKG-INFO +0 -57
- inpython_package-1.1.7/README.md +0 -30
- inpython_package-1.1.7/inpython/inqrcode/__init__.py +0 -6
- inpython_package-1.1.7/inpython/inrest/__init__.py +0 -6
- inpython_package-1.1.7/inpython/intools/__init__.py +0 -6
- inpython_package-1.1.7/inpython/intools/xls_to_csv.py +0 -45
- inpython_package-1.1.7/inpython/inxml/validator.py +0 -43
- inpython_package-1.1.7/inpython_package.egg-info/PKG-INFO +0 -57
- inpython_package-1.1.7/inpython_package.egg-info/requires.txt +0 -5
- inpython_package-1.1.7/setup.py +0 -29
- {inpython_package-1.1.7 → inpython_package-1.1.9}/LICENSE.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/__main__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/aotools/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/aotools/aogeo.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/ingraph/__init__.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/ingraph/ingraph.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython/inqrcode/inqrcode.py +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython_package.egg-info/dependency_links.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/inpython_package.egg-info/top_level.txt +0 -0
- {inpython_package-1.1.7 → inpython_package-1.1.9}/setup.cfg +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: inpython-package
|
|
3
|
+
Version: 1.1.9
|
|
4
|
+
Summary: # Infodata's IN-Tools U2Python Package
|
|
5
|
+
Home-page: https://bitbucket.org/infodata-dev/inpython
|
|
6
|
+
Author: infodata
|
|
7
|
+
Author-email: jcd@infodata.lu
|
|
8
|
+
License: MIT
|
|
9
|
+
Requires-Python: >=3.4
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE.txt
|
|
12
|
+
Requires-Dist: msal>=1.22
|
|
13
|
+
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: qrcode[pil]
|
|
15
|
+
Requires-Dist: python-dotenv
|
|
16
|
+
Requires-Dist: lxml
|
|
17
|
+
Requires-Dist: openpyxl
|
|
18
|
+
Requires-Dist: latlon3
|
|
19
|
+
Requires-Dist: geopy
|
|
20
|
+
Requires-Dist: python-docx
|
|
21
|
+
Requires-Dist: docxcompose
|
|
22
|
+
Requires-Dist: docxtpl
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: cron-converter
|
|
25
|
+
Requires-Dist: cryptography
|
|
26
|
+
Requires-Dist: PyJWT
|
|
27
|
+
Requires-Dist: pywin32; platform_system == "Windows"
|
|
28
|
+
Dynamic: author
|
|
29
|
+
Dynamic: author-email
|
|
30
|
+
Dynamic: description
|
|
31
|
+
Dynamic: description-content-type
|
|
32
|
+
Dynamic: home-page
|
|
33
|
+
Dynamic: license
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: requires-dist
|
|
36
|
+
Dynamic: requires-python
|
|
37
|
+
Dynamic: summary
|
|
38
|
+
|
|
39
|
+
# README Production – inpython-package
|
|
40
|
+
|
|
41
|
+
Ce document explique comment installer et utiliser le package **inpython-package** en production.
|
|
42
|
+
|
|
43
|
+
## Présentation
|
|
44
|
+
|
|
45
|
+
**inpython-package** regroupe des modules Python pour l'intégration MS-Graph, la génération de QR code pour les paiements via ayden, la gestion de fichiers, la manipulation de documents, etc., utilisés par Integrix.
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
Installer le package depuis PyPI :
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install inpython-package
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Structure du package
|
|
56
|
+
|
|
57
|
+
- `inpython.aotools` : outils avancés (géolocalisation, etc.)
|
|
58
|
+
- `inpython.ingraph` : gestion de l'authentification et des accès MS-Graph (utilise MSAL)
|
|
59
|
+
- `inpython.inqrcode` : génération de QR codes, gestion des liens de paiement, intégration de logos
|
|
60
|
+
- `inpython.inrest` : outils pour l'authentification JWT et REST
|
|
61
|
+
- `inpython.intools` : outils divers (conversion de fichiers, gestion de projets, cron, etc.)
|
|
62
|
+
- `inpython.inxml` : validation et manipulation de fichiers XML
|
|
63
|
+
|
|
64
|
+
## Utilisation
|
|
65
|
+
|
|
66
|
+
Importer les modules nécessaires dans vos scripts Python :
|
|
67
|
+
```python
|
|
68
|
+
from inpython.ingraph import ...
|
|
69
|
+
from inpython.inqrcode import ...
|
|
70
|
+
# etc.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Configuration
|
|
74
|
+
|
|
75
|
+
Certains modules nécessitent un fichier `.env` pour les clés API et paramètres sensibles. Placez ce fichier à la racine de votre projet ou fournissez son chemin lors de l'appel des fonctions (pour inqrcode par exemple).
|
|
76
|
+
|
|
77
|
+
## Support
|
|
78
|
+
|
|
79
|
+
Pour toute question ou problème en production, contactez le support Integrix ou consultez la documentation officielle sur PyPI.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# README Production – inpython-package
|
|
2
|
+
|
|
3
|
+
Ce document explique comment installer et utiliser le package **inpython-package** en production.
|
|
4
|
+
|
|
5
|
+
## Présentation
|
|
6
|
+
|
|
7
|
+
**inpython-package** regroupe des modules Python pour l'intégration MS-Graph, la génération de QR code pour les paiements via ayden, la gestion de fichiers, la manipulation de documents, etc., utilisés par Integrix.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
Installer le package depuis PyPI :
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install inpython-package
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Structure du package
|
|
18
|
+
|
|
19
|
+
- `inpython.aotools` : outils avancés (géolocalisation, etc.)
|
|
20
|
+
- `inpython.ingraph` : gestion de l'authentification et des accès MS-Graph (utilise MSAL)
|
|
21
|
+
- `inpython.inqrcode` : génération de QR codes, gestion des liens de paiement, intégration de logos
|
|
22
|
+
- `inpython.inrest` : outils pour l'authentification JWT et REST
|
|
23
|
+
- `inpython.intools` : outils divers (conversion de fichiers, gestion de projets, cron, etc.)
|
|
24
|
+
- `inpython.inxml` : validation et manipulation de fichiers XML
|
|
25
|
+
|
|
26
|
+
## Utilisation
|
|
27
|
+
|
|
28
|
+
Importer les modules nécessaires dans vos scripts Python :
|
|
29
|
+
```python
|
|
30
|
+
from inpython.ingraph import ...
|
|
31
|
+
from inpython.inqrcode import ...
|
|
32
|
+
# etc.
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Certains modules nécessitent un fichier `.env` pour les clés API et paramètres sensibles. Placez ce fichier à la racine de votre projet ou fournissez son chemin lors de l'appel des fonctions (pour inqrcode par exemple).
|
|
38
|
+
|
|
39
|
+
## Support
|
|
40
|
+
|
|
41
|
+
Pour toute question ou problème en production, contactez le support Integrix ou consultez la documentation officielle sur PyPI.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from inrest import in_jwt
|
|
3
|
+
|
|
4
|
+
def main(argv):
|
|
5
|
+
#
|
|
6
|
+
try:
|
|
7
|
+
token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJTd3JnRjIyV1FVaHZBZUxpQTBhQnhmV2hqNGgtX0hLSXFuVzRpd3JTWnFVIn0.eyJleHAiOjE3ODc0MjM5MzksImlhdCI6MTc4NzQyMzYzOSwiYXV0aF90aW1lIjoxNzg3NDE5OTc2LCJqdGkiOiJiZGUxNjZmZS0wMDc3LTIyNWMtNDRkMi0yOWJkNzlkZTBjZDEiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL215cmVhbG0iLCJhdWQiOiJteWNsaWVudC13ZWIiLCJzdWIiOiI0MGM1MGI5MS1iYjlmLTQ5MjEtYjg2YS1hYzQ1YWZiYTE1NWYiLCJ0eXAiOiJJRCIsImF6cCI6Im15Y2xpZW50LXdlYiIsInNpZCI6InQwZi1qWm5nVjYxVklQT29RQTJWa3p1VCIsImF0X2hhc2giOiJNN1VoMG12SFZKczdDaDVfN1F1bDZRIiwiYWNyIjoiMCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkpDIERldyIsInByZWZlcnJlZF91c2VybmFtZSI6Im15dXNlciIsImdpdmVuX25hbWUiOiJKQyIsImZhbWlseV9uYW1lIjoiRGV3IiwiZW1haWwiOiJqY2RAaW5mb2RhdGEubHUifQ.AuQWfzPHZjHzdtwMipDsR_VrxR2dl1Q3aHD1Ym5h9hZfHwiVtPH3prZ9ptDAmXkagz29uphx_X--yfOmqGlZiC4ygHsGXBkmohNRteIHRO_hu72iBM9kEaU8QakX_HTZXmhC99HHrSNGjYcdl9koNz9cZSvCd9EysGLGF1Bs2NWV0hwQzQVfYIpeDCgm2vyvLSLhJYH4UJariM4H0D9KhX7tgi5qRFxEn2lYlSam1pBuDTrNFrXoUvE_iN68e-wMQql0ib_WWQKVTmeXvxoLzHM2K_544MqqssWO2gZ5JnnV2ZlB1KgxqHATQxFLyGGZzsWMLLdHb91yIb_lfBIV7Q"
|
|
8
|
+
claims = in_jwt.validate_keycloak_jwt(token, "http://localhost:8080/realms/myrealm","myclient-web")
|
|
9
|
+
print(claims)
|
|
10
|
+
except ValueError as e:
|
|
11
|
+
print(f"Erreur: {e}")
|
|
12
|
+
|
|
13
|
+
if __name__ == "__main__":
|
|
14
|
+
main(sys.argv[1:])
|
|
15
|
+
|
|
@@ -15,8 +15,8 @@ HEADERS = {
|
|
|
15
15
|
"Content-Type": "application/json"
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
def create_payment_link_adyen(amount_cents:
|
|
19
|
-
"""Crée un lien de paiement via l'API Adyen. Si mode=2, génère aussi un QR code (avec logo si fourni). Si env_path est fourni, charge les variables d'environnement depuis ce fichier."""
|
|
18
|
+
def create_payment_link_adyen(amount_cents: float, currency: str, reference: str, mode: str = 1, logo_path: str = None, env_path: str = None, qrcode_path: str = None, jpg_filename: str = None ) -> str:
|
|
19
|
+
"""Crée un lien de paiement via l'API Adyen. Si mode=2, génère aussi un QR code (avec logo si fourni). Si env_path est fourni, charge les variables d'environnement depuis ce fichier. jpg_filename permet de choisir le nom du fichier jpg du QR code."""
|
|
20
20
|
if env_path and os.path.exists(env_path):
|
|
21
21
|
from dotenv import dotenv_values
|
|
22
22
|
env_vars = dotenv_values(env_path)
|
|
@@ -33,7 +33,7 @@ def create_payment_link_adyen(amount_cents: int, currency: str, reference: str,
|
|
|
33
33
|
}
|
|
34
34
|
payload = {
|
|
35
35
|
"amount": {
|
|
36
|
-
"value": amount_cents,
|
|
36
|
+
"value": int(round(amount_cents)),
|
|
37
37
|
"currency": currency
|
|
38
38
|
},
|
|
39
39
|
"reference": reference,
|
|
@@ -45,23 +45,31 @@ def create_payment_link_adyen(amount_cents: int, currency: str, reference: str,
|
|
|
45
45
|
data = response.json()
|
|
46
46
|
payment_url = data["url"]
|
|
47
47
|
if mode == 2:
|
|
48
|
-
qr_path = generate_qr_code(payment_url, reference, logo_path,qrcode_path)
|
|
48
|
+
qr_path = generate_qr_code(payment_url, reference, logo_path, qrcode_path, jpg_filename)
|
|
49
49
|
return qr_path
|
|
50
50
|
return payment_url
|
|
51
51
|
|
|
52
|
-
def generate_qr_code(url: str, reference: str, logo_path: str = None, qrcode_path: str = None) -> None:
|
|
53
|
-
"""Génère un QR code dans le dossier ./qrcode/ (au même niveau que paymentqrcode.py), avec logo si fourni. Crée/écrase un fichier qrcode_logs.txt pour le diagnostic."""
|
|
52
|
+
def generate_qr_code(url: str, reference: str, logo_path: str = None, qrcode_path: str = None, jpg_filename: str = None) -> None:
|
|
53
|
+
"""Génère un QR code dans le dossier ./qrcode/ (au même niveau que paymentqrcode.py), avec logo si fourni. Crée/écrase un fichier qrcode_logs.txt pour le diagnostic. jpg_filename permet de choisir le nom du fichier jpg du QR code (avec ou sans extension)."""
|
|
54
54
|
import traceback
|
|
55
55
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
56
56
|
output_dir = os.path.join(base_dir, "qrcode")
|
|
57
57
|
|
|
58
58
|
os.makedirs(output_dir, exist_ok=True)
|
|
59
59
|
print("qrcode_path : ",qrcode_path)
|
|
60
|
+
if jpg_filename:
|
|
61
|
+
filename_base = jpg_filename
|
|
62
|
+
else:
|
|
63
|
+
filename_base = f"{reference}.jpg"
|
|
64
|
+
# Ajoute .jpg seulement si jpg_filename est vide ou ne contient pas déjà une extension
|
|
65
|
+
if jpg_filename:
|
|
66
|
+
if not (jpg_filename.lower().endswith('.jpg') or jpg_filename.lower().endswith('.jpeg')):
|
|
67
|
+
filename_base = jpg_filename + ".jpg"
|
|
60
68
|
if qrcode_path:
|
|
61
|
-
filename = os.path.join(qrcode_path,
|
|
69
|
+
filename = os.path.join(qrcode_path, filename_base)
|
|
62
70
|
log_file = os.path.join(qrcode_path, "qrcode_debug.log")
|
|
63
71
|
else:
|
|
64
|
-
filename = os.path.join(output_dir,
|
|
72
|
+
filename = os.path.join(output_dir, filename_base)
|
|
65
73
|
log_file = os.path.join(output_dir, "qrcode_debug.log")
|
|
66
74
|
|
|
67
75
|
# Supprime le fichier s’il existe
|
|
@@ -115,15 +123,16 @@ def clean(s):
|
|
|
115
123
|
if __name__ == "__main__":
|
|
116
124
|
import sys
|
|
117
125
|
try:
|
|
118
|
-
# Utilisation : python paymentqrcode.py <mode> <montant> <devise> <reference> [chemin_logo] [chemin_env]
|
|
126
|
+
# Utilisation : python paymentqrcode.py <mode> <montant> <devise> <reference> [chemin_logo] [chemin_env] [qrcode_path] [jpg_filename]
|
|
119
127
|
mode = int(clean(sys.argv[1]))
|
|
120
|
-
amount =
|
|
128
|
+
amount = float(clean(sys.argv[2]))
|
|
121
129
|
currency = sys.argv[3]
|
|
122
130
|
reference = sys.argv[4]
|
|
123
131
|
logo_path = sys.argv[5] if len(sys.argv) > 5 else ''
|
|
124
132
|
env_path = sys.argv[6] if len(sys.argv) > 6 else ''
|
|
125
133
|
qrcode_path = sys.argv[7] if len(sys.argv) > 7 else ''
|
|
126
|
-
|
|
134
|
+
jpg_filename = sys.argv[8] if len(sys.argv) > 8 else ''
|
|
135
|
+
result = create_payment_link_adyen(amount, currency, reference, mode, logo_path, env_path, qrcode_path, jpg_filename if jpg_filename else None)
|
|
127
136
|
print(result)
|
|
128
137
|
except Exception as e:
|
|
129
138
|
print("Erreur :", e)
|
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
#
|
|
4
4
|
#--------------------------------------------------------
|
|
5
5
|
# 22/08/2024 (JCD) : Creation fonction generateJwtClientAssertion
|
|
6
|
+
# 21/08/2026 (JCD) : ajout validate_keycloak_jwt et validate_microsoft_jwt
|
|
6
7
|
#--------------------------------------------------------
|
|
7
8
|
from cryptography.hazmat.primitives.serialization import pkcs12
|
|
8
9
|
from cryptography.hazmat.primitives import serialization
|
|
9
10
|
from cryptography.hazmat.backends import default_backend
|
|
10
11
|
import jwt
|
|
11
12
|
import datetime
|
|
12
|
-
import
|
|
13
|
-
import
|
|
13
|
+
from jwt import PyJWKClient
|
|
14
|
+
import json
|
|
14
15
|
|
|
15
16
|
# Generates a jwt client assertion from a private key
|
|
16
17
|
def generateJwtClientAssertion(mode,path_certificate,private_key,client_id,audience,expirationTime=10):
|
|
@@ -84,48 +85,58 @@ def generateJwtClientAssertion(mode,path_certificate,private_key,client_id,audie
|
|
|
84
85
|
except:
|
|
85
86
|
raise # so that the caller receives the exception
|
|
86
87
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
#client_id = "self_service_chaman_109660_shr5ppy83v"
|
|
92
|
-
|
|
93
|
-
#mode = "2"
|
|
94
|
-
#path_certificate = "/tmp/jcd/CermiaRest.pfx"
|
|
95
|
-
#private_key = "2024infodd1648!" # Use b'' for an empty password
|
|
96
|
-
#client_id = "self_service_chaman_108306_safnfiag3h"
|
|
97
|
-
#audience = "https://services.socialsecurity.be/REST/oauth/v5/token"
|
|
98
|
-
mode = ''
|
|
99
|
-
private_key = ''
|
|
100
|
-
path_certificate = ''
|
|
101
|
-
client_id = ''
|
|
102
|
-
audience = ''
|
|
103
|
-
expirationTime = None
|
|
88
|
+
def validate_keycloak_jwt(token: str,keycloak_url: str,client_id: str):
|
|
89
|
+
"""
|
|
90
|
+
Valide un JWT Keycloak.
|
|
91
|
+
"""
|
|
104
92
|
try:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
93
|
+
jwks_url = (f"{keycloak_url}/protocol/openid-connect/certs")
|
|
94
|
+
signing_key = PyJWKClient(jwks_url).get_signing_key_from_jwt(token)
|
|
95
|
+
payload = jwt.decode(
|
|
96
|
+
token,
|
|
97
|
+
signing_key.key,
|
|
98
|
+
algorithms=["RS256"],
|
|
99
|
+
issuer=keycloak_url,
|
|
100
|
+
audience=client_id
|
|
101
|
+
)
|
|
102
|
+
return json.dumps(payload)
|
|
103
|
+
except jwt.ExpiredSignatureError:
|
|
104
|
+
raise ValueError("JWT expiré")
|
|
105
|
+
except jwt.InvalidIssuerError:
|
|
106
|
+
raise ValueError("Issuer Keycloak incorrect")
|
|
107
|
+
except jwt.InvalidAudienceError:
|
|
108
|
+
raise ValueError("Audience Keycloak incorrect")
|
|
109
|
+
except jwt.InvalidTokenError as e:
|
|
110
|
+
raise ValueError(
|
|
111
|
+
f"JWT Keycloak invalide: {str(e)}"
|
|
112
|
+
)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
raise ValueError(
|
|
115
|
+
f"Erreur validation Keycloak: {str(e)}"
|
|
116
|
+
)
|
|
127
117
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
118
|
+
def validate_microsoft_jwt(token: str, issuer: str = None, audience: str = None):
|
|
119
|
+
"""
|
|
120
|
+
Valide un JWT Microsoft en récupérant les clés JWKS depuis:
|
|
121
|
+
https://login.microsoftonline.com/common/discovery/v2.0/keys
|
|
122
|
+
"""
|
|
123
|
+
try:
|
|
124
|
+
jwks_url = "https://login.microsoftonline.com/common/discovery/v2.0/keys"
|
|
125
|
+
signing_key = PyJWKClient(jwks_url).get_signing_key_from_jwt(token)
|
|
131
126
|
|
|
127
|
+
options = {"verify_aud": bool(audience), "verify_iss": bool(issuer)}
|
|
128
|
+
claims = jwt.decode(
|
|
129
|
+
token,
|
|
130
|
+
signing_key.key,
|
|
131
|
+
algorithms=["RS256"],
|
|
132
|
+
audience=audience,
|
|
133
|
+
issuer=issuer,
|
|
134
|
+
options=options
|
|
135
|
+
)
|
|
136
|
+
return json.dumps(claims)
|
|
137
|
+
except jwt.ExpiredSignatureError:
|
|
138
|
+
raise ValueError("JWT Microsoft expiré")
|
|
139
|
+
except jwt.InvalidTokenError as e:
|
|
140
|
+
raise ValueError(f"JWT Microsoft invalide: {str(e)}")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise ValueError(f"Erreur validation Microsoft: {str(e)}")
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from docx import Document
|
|
2
2
|
|
|
3
|
+
|
|
3
4
|
def chk_br(par):
|
|
4
5
|
for run in par.r_lst:
|
|
5
6
|
if len(run.br_lst) > 0:
|
|
6
|
-
return(True)
|
|
7
|
-
return(False)
|
|
7
|
+
return (True)
|
|
8
|
+
return (False)
|
|
9
|
+
|
|
8
10
|
|
|
9
|
-
def build_word_file(doc_directory,fab_filename,skipped_pg_br,keeped_pg_br):
|
|
11
|
+
def build_word_file(doc_directory, fab_filename, skipped_pg_br, keeped_pg_br):
|
|
10
12
|
tmp_file = doc_directory + "/schemas.docx"
|
|
11
13
|
|
|
12
14
|
print(fab_filename)
|
|
@@ -36,23 +38,25 @@ def build_word_file(doc_directory,fab_filename,skipped_pg_br,keeped_pg_br):
|
|
|
36
38
|
if num_page_breaks == 3:
|
|
37
39
|
state = 2
|
|
38
40
|
doc_tmp.element.body.remove(elt)
|
|
39
|
-
#doc_tmp.add_paragraph()
|
|
40
|
-
#elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
41
|
-
|
|
41
|
+
# doc_tmp.add_paragraph()
|
|
42
|
+
# elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
43
|
+
# table_copy(elt, doc_fab, doc_tmp)
|
|
42
44
|
elif state == 2:
|
|
43
45
|
if elt.tag != "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}sectPr":
|
|
44
46
|
doc_tmp.element.body.remove(elt)
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
print("save " + tmp_file)
|
|
47
49
|
doc_tmp.save(tmp_file)
|
|
48
50
|
|
|
49
|
-
return(tmp_file)
|
|
51
|
+
return (tmp_file)
|
|
50
52
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
|
|
54
|
+
def build_devis(doc_directory, fab_filename, startdoc, end_doc):
|
|
55
|
+
tmp_doc = build_word_file(doc_directory, fab_filename, 2, 1)
|
|
53
56
|
print(tmp_doc)
|
|
54
57
|
|
|
58
|
+
|
|
55
59
|
doc_dir = "Q:/is/chantier/1000/"
|
|
56
60
|
fab_fn = "Q:/is/chantier/1000/FABRIQUANT/1000.docx"
|
|
57
61
|
|
|
58
|
-
build_devis(doc_dir,fab_fn,"","")
|
|
62
|
+
build_devis(doc_dir, fab_fn, "", "")
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# compute cron value ; used by IN.JOB
|
|
2
|
+
# efv 01.12.2025 create
|
|
3
|
+
# ------------------------------------------
|
|
4
|
+
import sys
|
|
5
|
+
from cron_converter import Cron
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def next(cronexp: str, dtiso: str):
|
|
10
|
+
"""Compute next datetime from crontab expression, starting from dtiso else now().
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
cronexp (str): a crontab expression string like : */5 * * * *
|
|
14
|
+
dtiso (str): a datetime iso format string like : 2025-12-01T09:00:23
|
|
15
|
+
if null use now()
|
|
16
|
+
|
|
17
|
+
Raises:
|
|
18
|
+
Exception: forward the exception to the caller
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
_type_: datetime.isoformat YYYY-MM-DDTHH:MM:SS
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if len(cronexp) == 0:
|
|
25
|
+
raise Exception("cronexp is not provided.")
|
|
26
|
+
if len(dtiso) == 0:
|
|
27
|
+
reference = datetime.now()
|
|
28
|
+
else:
|
|
29
|
+
reference = datetime.fromisoformat(dtiso)
|
|
30
|
+
cron_instance = Cron() # Create a new instance
|
|
31
|
+
cron_instance.from_string(cronexp) # load the cron expression
|
|
32
|
+
schedule = cron_instance.schedule(reference) # load the datetime reference
|
|
33
|
+
thenext = schedule.next() # compute the next runtime datetime
|
|
34
|
+
return thenext.isoformat() # return isoformat YYYY-MM-DDTHH:MM:SS
|
|
35
|
+
except:
|
|
36
|
+
raise # raise the error to the parent caller
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
def validate(cronexp: str):
|
|
41
|
+
try:
|
|
42
|
+
if cronexp == '':
|
|
43
|
+
raise Exception("cronexp is not provided.")
|
|
44
|
+
cron_instance = Cron() # Create a new instance
|
|
45
|
+
cron_instance.from_string(cronexp)
|
|
46
|
+
return cron_instance.to_string()
|
|
47
|
+
except:
|
|
48
|
+
raise
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
"""
|
|
54
|
+
test and sample of cron validate(), cron next()
|
|
55
|
+
python inpython\intools\in_cron.py '*/5 * * * *' '2025-12-01T09:00:23'
|
|
56
|
+
python inpython\intools\in_cron.py '*/5 * * * *'
|
|
57
|
+
"""
|
|
58
|
+
cronexp = sys.argv[1]
|
|
59
|
+
dtiso = ''
|
|
60
|
+
if len(sys.argv) == 3:
|
|
61
|
+
dtiso = sys.argv[2]
|
|
62
|
+
res = validate(cronexp)
|
|
63
|
+
print(res, type(res))
|
|
64
|
+
res = next(cronexp, dtiso)
|
|
65
|
+
print(res, type(res))
|
|
66
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# infodata > inpythom > intools
|
|
2
|
+
# module in_docx
|
|
3
|
+
# Author: [Th] 23/02/2024
|
|
4
|
+
from docx import Document
|
|
5
|
+
from docxcompose.composer import Composer
|
|
6
|
+
from docxtpl import DocxTemplate
|
|
7
|
+
import datetime as dt
|
|
8
|
+
import inpython.intools.in_log as inlog
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def chk_br(par):
|
|
13
|
+
# renvoi true en de présence d'un saut de page
|
|
14
|
+
for run in par.r_lst:
|
|
15
|
+
if len(run.br_lst) > 0:
|
|
16
|
+
return (True)
|
|
17
|
+
return (False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def doc_replace_var(input_filename, output_filename, context_str):
|
|
21
|
+
import json
|
|
22
|
+
|
|
23
|
+
context = json.loads(context_str)
|
|
24
|
+
tpl = DocxTemplate(input_filename)
|
|
25
|
+
tpl.render(context)
|
|
26
|
+
tpl.save(output_filename)
|
|
27
|
+
return ("")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def doc_extract(doc_directory, extract_name, input_filename, skipped_pg_br, keeped_pg_br, final_br=True):
|
|
31
|
+
# permet d'extraire une partie d'un document word docx
|
|
32
|
+
# fonctionne à la manière d'une group extract
|
|
33
|
+
#
|
|
34
|
+
# doc_directory : chemin complet vers le répertoire où sera enregsitré le résultat
|
|
35
|
+
# extract_name : sera utilisé pour créér le nom du fichier final si "" on prend "extract"
|
|
36
|
+
# input_filename : chemin complet vers le fichier d'origine duquel sera fait l'extraction
|
|
37
|
+
# skipped_pg_br : nombre de sauts de page à passer avant de commencer l'extraction
|
|
38
|
+
# keeped_pg:br : nombre de sauts correspondant à l'extraction
|
|
39
|
+
#
|
|
40
|
+
# input : retourne le chemin complet vers le fichier résultat.
|
|
41
|
+
# le nom du fihier prend la forme yyyymmdd-hhmmss-extract_name.docx
|
|
42
|
+
inlog.init_log("in_docx", f"Extraction des pages entre les break {skipped_pg_br} et {keeped_pg_br}")
|
|
43
|
+
|
|
44
|
+
if len(extract_name) == 0:
|
|
45
|
+
extract_name = "extract"
|
|
46
|
+
|
|
47
|
+
tmp_file = f"{doc_directory}{dt.datetime.now().strftime('%Y%m%d-%H%M%S-')}{extract_name}.docx"
|
|
48
|
+
|
|
49
|
+
doc_fab = Document(input_filename)
|
|
50
|
+
doc_fab.save(tmp_file)
|
|
51
|
+
doc_tmp = Document(tmp_file)
|
|
52
|
+
|
|
53
|
+
# extraction des pages du docuemnt fabriquant
|
|
54
|
+
num_page_breaks = 0
|
|
55
|
+
|
|
56
|
+
state = 0
|
|
57
|
+
|
|
58
|
+
if type(skipped_pg_br) is str:
|
|
59
|
+
skipped_pg_br = int(skipped_pg_br)
|
|
60
|
+
if type(keeped_pg_br) is str:
|
|
61
|
+
keeped_pg_br = int(keeped_pg_br)
|
|
62
|
+
|
|
63
|
+
for elt_idx, elt in enumerate(doc_tmp.element.body):
|
|
64
|
+
if state == 0:
|
|
65
|
+
if elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p":
|
|
66
|
+
if chk_br(elt):
|
|
67
|
+
num_page_breaks += 1
|
|
68
|
+
if num_page_breaks == skipped_pg_br:
|
|
69
|
+
num_page_breaks = 0
|
|
70
|
+
doc_tmp.save(tmp_file)
|
|
71
|
+
state = 1
|
|
72
|
+
doc_tmp.element.body.remove(elt)
|
|
73
|
+
elif state == 1:
|
|
74
|
+
if elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p":
|
|
75
|
+
if chk_br(elt):
|
|
76
|
+
num_page_breaks += 1
|
|
77
|
+
if num_page_breaks == keeped_pg_br:
|
|
78
|
+
state = 2
|
|
79
|
+
if not (final_br):
|
|
80
|
+
doc_tmp.element.body.remove(elt)
|
|
81
|
+
# elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
82
|
+
# table_copy(elt, doc_fab, doc_tmp)
|
|
83
|
+
elif state == 2:
|
|
84
|
+
if elt.tag != "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}sectPr":
|
|
85
|
+
doc_tmp.element.body.remove(elt)
|
|
86
|
+
|
|
87
|
+
doc_tmp.save(tmp_file)
|
|
88
|
+
|
|
89
|
+
return (tmp_file)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def doc_br_count(input_filename):
|
|
93
|
+
input_doc = Document(input_filename)
|
|
94
|
+
|
|
95
|
+
# extraction des pages du docuemnt fabriquant
|
|
96
|
+
num_page_breaks = 0
|
|
97
|
+
|
|
98
|
+
state = 0
|
|
99
|
+
|
|
100
|
+
for elt_idx, elt in enumerate(input_doc.element.body):
|
|
101
|
+
if elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p":
|
|
102
|
+
if chk_br(elt):
|
|
103
|
+
num_page_breaks += 1
|
|
104
|
+
|
|
105
|
+
return (num_page_breaks)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def doc_str_find(input_filename, subString, occNumber):
|
|
109
|
+
inlog.init_log("in_docx", f"Recherche de {subString}")
|
|
110
|
+
|
|
111
|
+
input_doc = Document(input_filename)
|
|
112
|
+
occNumber = int(occNumber)
|
|
113
|
+
# extraction des pages du docuemnt fabriquant
|
|
114
|
+
num_page_breaks = 0
|
|
115
|
+
|
|
116
|
+
occurence = 0
|
|
117
|
+
found = False
|
|
118
|
+
for elt_idx, elt in enumerate(input_doc.element.body):
|
|
119
|
+
if found:
|
|
120
|
+
break
|
|
121
|
+
if elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}p":
|
|
122
|
+
if chk_br(elt):
|
|
123
|
+
num_page_breaks += 1
|
|
124
|
+
if subString in elt.text:
|
|
125
|
+
break
|
|
126
|
+
elif elt.tag == "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}tbl":
|
|
127
|
+
for trlst in elt.tr_lst:
|
|
128
|
+
for tclst in trlst.tc_lst:
|
|
129
|
+
for plst in tclst.p_lst:
|
|
130
|
+
for rlst in plst.r_lst:
|
|
131
|
+
if subString in rlst.text:
|
|
132
|
+
occurence += 1
|
|
133
|
+
if occurence == occNumber:
|
|
134
|
+
found = True
|
|
135
|
+
inlog.debug(
|
|
136
|
+
f"Occurenc {occNumber} of \"{subString}\" found after {num_page_breaks} page breaks!")
|
|
137
|
+
break
|
|
138
|
+
if not (found):
|
|
139
|
+
num_page_breaks = 0
|
|
140
|
+
return (num_page_breaks)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def compose_doc(base_file, files_list, final_doc):
|
|
144
|
+
inlog.init_log("composer", "Assemblage des documents", True)
|
|
145
|
+
inlog.debug(os.path.normpath(base_file))
|
|
146
|
+
inlog.debug(files_list)
|
|
147
|
+
inlog.debug(final_doc)
|
|
148
|
+
files_list = files_list.split(";")
|
|
149
|
+
master = Document(os.path.normpath(base_file))
|
|
150
|
+
composer = Composer(master)
|
|
151
|
+
for filename in files_list:
|
|
152
|
+
doc_temp = Document(os.path.normpath(filename))
|
|
153
|
+
composer.append(doc_temp)
|
|
154
|
+
composer.save(os.path.normpath(final_doc))
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == '__main__':
|
|
159
|
+
flist = ['//winprod/commun/is/chantier/1000/schemas.docx', '//winprod/commun/is/chantier/1000/1000_DETAIL.docx',
|
|
160
|
+
'//winprod/commun/is/chantier/_doc_base/Formulaire_ALU_cgv.docx']
|
|
161
|
+
compose_doc('//winprod/commun/is/chantier/_doc_base/Formulaire_ALU_1-3.docx', flist,
|
|
162
|
+
'//winprod/commun/is/chantier/1000/Devis.docx')
|