pdnd-python-client 0.1.0__tar.gz → 0.1.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdnd-python-client
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Client Python per autenticazione e interazione con le API della Piattaforma Digitale Nazionale Dati (PDND).
5
5
  Author-email: Francesco Loreti <francesco.loreti@isprambiente.it>
6
6
  License-Expression: MIT
@@ -22,45 +22,60 @@ class PDNDClient:
22
22
  self.filters = {}
23
23
  self.debug = False
24
24
  self.token = ""
25
- self.token_file = "pdnd_token.json"
25
+ self.token_file = "tmp/pdnd_token.json"
26
26
  self.token_exp = None # Token expiration time, if applicable
27
27
 
28
28
  # Questo metodo recupera l'URL dell'API, che può essere sovrascritto dall'utente.
29
- def get_api_url(self):
29
+ def get_api_url(self) -> str:
30
30
  return self.api_url if hasattr(self, 'api_url') else None
31
31
 
32
32
  # Questo metodo imposta l'URL dell'API per le richieste successive.
33
- def set_api_url(self, api_url):
33
+ def set_api_url(self, api_url) -> bool:
34
34
  self.api_url = api_url
35
-
36
- # Questo metodo imposta i filtri da utilizzare nelle richieste API.
37
- def set_filters(self, filters):
38
- self.filters = filters
35
+ return True
36
+
37
+ # Imposta i filtri da utilizzare nelle richieste API.
38
+ # Se viene fornita una stringa, la converte in un dizionario.
39
+ def set_filters(self, filters) -> bool:
40
+ if isinstance(filters, str):
41
+ # Analizza la stringa nel formato "chiave1=val1&chiave2=val2"
42
+ self.filters = dict(pair.split("=", 1) for pair in filters.split("&") if "=" in pair)
43
+ elif isinstance(filters, dict):
44
+ self.filters = filters
45
+ else:
46
+ raise ValueError("I filtri devono essere una stringa o un dizionario.")
47
+ return True
39
48
 
40
49
  # Questo metodo imposta la modalità di debug, che controlla se stampare un output dettagliato.
41
- def set_debug(self, debug):
50
+ def set_debug(self, debug) -> bool:
42
51
  self.debug = debug
52
+ return True
43
53
 
44
54
  # Questo metodo imposta il tempo di scadenza per il token.
45
55
  # Può essere una stringa nel formato "YYYY-MM-DD HH:MM:SS" oppure un oggetto datetime.
46
56
  # Se non viene fornito, il valore predefinito è None.
47
- def set_expiration(self, exp):
57
+ def set_expiration(self, exp) -> bool:
48
58
  self.token_exp = exp
59
+ return True
49
60
 
50
61
  # Questo metodo imposta l'URL di stato per le richieste GET.
51
- def set_status_url(self, status_url):
62
+ def set_status_url(self, status_url) -> bool:
52
63
  self.status_url = status_url
64
+ return True
53
65
 
54
- def set_token(self, token):
66
+ def set_token(self, token) -> bool:
55
67
  self.token = token
68
+ return True
56
69
 
57
- def set_token_file(self, token_file):
70
+ def set_token_file(self, token_file) -> bool:
58
71
  self.token_file = token_file
72
+ return True
59
73
 
60
- def set_verify_ssl(self, verify_ssl):
74
+ def set_verify_ssl(self, verify_ssl) -> bool:
61
75
  self.verify_ssl = verify_ssl
76
+ return True
62
77
 
63
- def get_api(self, token: str):
78
+ def get_api(self, token: str) -> [int, str]:
64
79
  url = self.api_url if hasattr(self, 'api_url') and self.api_url else self.get_api_url()
65
80
 
66
81
  # Aggiunta dei filtri come query string
@@ -95,12 +110,12 @@ class PDNDClient:
95
110
  return status_code, body
96
111
 
97
112
  # Questo metodo esegue una richiesta GET all'URL specificato e restituisce il codice di stato e il testo della risposta
98
- def get_status(self, url):
113
+ def get_status(self, url) -> [int, str]:
99
114
  headers = {"Authorization": f"Bearer {self.token}"}
100
115
  response = requests.get(url, headers=headers, verify=self.verify_ssl)
101
116
  return response.status_code, response.text
102
117
 
103
- def get_token(self):
118
+ def get_token(self) -> str:
104
119
  return self.token
105
120
 
106
121
  def is_token_valid(self, exp) -> bool:
@@ -112,28 +127,48 @@ class PDNDClient:
112
127
  raise ValueError("L'exp deve essere una stringa o un oggetto datetime")
113
128
  return time.time() < exp.timestamp()
114
129
 
115
- def load_token(self, file: str = None):
130
+ def load_token(self, file: str = None) -> [str, str]:
116
131
  file = file or self.token_file # Usa il file passato o quello di default
117
132
 
118
133
  if not os.path.exists(file):
119
- return None
134
+ return [None, None]
120
135
 
121
136
  try:
122
137
  with open(file, "r", encoding="utf-8") as f:
123
138
  data = json.load(f)
124
139
  except (json.JSONDecodeError, IOError):
125
- return None
140
+ return [None, None]
126
141
 
127
142
  if not data or "token" not in data or "exp" not in data:
128
- return None
143
+ return [None, None]
129
144
 
145
+ self.token = data["token"]
130
146
  self.token_exp = data["exp"]
131
147
  return data["token"], data["exp"]
132
148
 
133
149
 
134
- def save_token(self, token: str, exp: str, file: str = None):
150
+ def save_token(self, token: str, exp: str, file: str = None) -> bool:
151
+ if not token:
152
+ raise ValueError("Il token non può essere vuoto")
153
+ if not exp:
154
+ raise ValueError("L'exp non può essere vuoto")
155
+ if not isinstance(token, str):
156
+ raise ValueError("Il token deve essere una stringa")
157
+ if not isinstance(exp, str) and not isinstance(exp, datetime):
158
+ raise ValueError("L'exp deve essere una stringa o un oggetto datetime")
159
+
135
160
  file = file or self.token_file # Usa il file passato o quello di default
136
161
  exp = exp or self.token_exp # Usa l'exp passato o quello corrente
162
+ if not isinstance(exp, str):
163
+ raise ValueError("L'exp deve essere una stringa nel formato 'YYYY-MM-DD HH:MM:SS'")
164
+ try:
165
+ datetime.strptime(exp, "%Y-%m-%d %H:%M:%S") # Verifica che l'exp sia nel formato corretto
166
+ except ValueError:
167
+ raise ValueError("L'exp deve essere una stringa nel formato 'YYYY-MM-DD HH:MM:SS'")
168
+
169
+ if not os.path.exists(os.path.dirname(file)):
170
+ os.makedirs(os.path.dirname(file))
171
+
137
172
  data = {
138
173
  "token": token,
139
174
  "exp": exp
@@ -141,7 +176,6 @@ class PDNDClient:
141
176
  with open(file, "w", encoding="utf-8") as f:
142
177
  json.dump(data, f, ensure_ascii=False)
143
178
 
144
-
145
-
146
-
147
-
179
+ self.token = token
180
+ self.token_exp = exp
181
+ return True
@@ -8,7 +8,7 @@ import os
8
8
  # Il metodo get consente di recuperare valori specifici della configurazione, con un valore predefinito opzionale.
9
9
  class Config:
10
10
  # Questo metodo inizializza l'oggetto Config caricando la configurazione da un file JSON.
11
- def __init__(self, config_path, env):
11
+ def __init__(self, config_path, env: "produzione"):
12
12
  self.env = env
13
13
  with open(config_path, "r") as f:
14
14
  full_config = json.load(f)
@@ -17,5 +17,5 @@ class Config:
17
17
  self.config = full_config[env]
18
18
 
19
19
  # Questo metodo recupera un valore di configurazione tramite una chiave, restituendo un valore predefinito se la chiave non viene trovata.
20
- def get(self, key, default=None):
20
+ def get(self, key, default=None) -> str:
21
21
  return self.config.get(key, default)
@@ -6,6 +6,7 @@ import base64
6
6
  import requests
7
7
  import jwt # PyJWT
8
8
  import secrets
9
+ import os
9
10
  from datetime import datetime, timezone
10
11
  from jwt import exceptions as jwt_exceptions
11
12
 
@@ -22,23 +23,46 @@ class JWTGenerator:
22
23
  self.client_id = config.get("clientId")
23
24
  self.endpoint = config.get("endpoint")
24
25
  self.env = config.get("env", "produzione")
26
+ self.privKeyPath = config.get("privKeyPath")
27
+ self.issuer = self.config.get("issuer")
28
+ self.clientId = self.config.get("clientId")
29
+ self.kid = self.config.get("kid")
30
+ self.purposeId = self.config.get("purposeId")
25
31
  self.token_exp = None
26
32
  self.endpoint = "https://auth.interop.pagopa.it/token.oauth2"
27
33
  self.aud = "auth.interop.pagopa.it/client-assertion"
28
34
 
29
- def set_debug(self, debug):
30
- self.debug = debug
31
35
 
36
+ def set_debug(self, debug) -> bool:
37
+ self.debug = debug
38
+ return True
32
39
 
33
- def set_env(self, env):
40
+ def set_env(self, env: "produzione") -> bool:
34
41
  self.env = env
35
42
  if self.env == "collaudo":
36
43
  self.endpoint = "https://auth.uat.interop.pagopa.it/token.oauth2"
37
44
  self.aud = "auth.uat.interop.pagopa.it/client-assertion"
38
-
39
- def request_token(self):
40
-
41
- with open(self.config.get("privKeyPath"), "rb") as key_file:
45
+ return True
46
+
47
+ def request_token(self) -> str:
48
+ if not self.client_id:
49
+ raise ValueError("Client ID non specificato nella configurazione.")
50
+ if not self.privKeyPath:
51
+ raise ValueError("Percorso della chiave privata non specificato nella configurazione.")
52
+ if not os.path.exists(self.privKeyPath):
53
+ raise FileNotFoundError(f"File della chiave privata non trovato: {self.privKeyPath}")
54
+ if not self.endpoint:
55
+ raise ValueError("Endpoint non specificato nella configurazione.")
56
+ if not self.issuer:
57
+ raise ValueError("Issuer non specificato nella configurazione.")
58
+ if not self.clientId:
59
+ raise ValueError("Client ID non specificato nella configurazione.")
60
+ if not self.kid:
61
+ raise ValueError("KID non specificato nella configurazione.")
62
+ if not self.purposeId:
63
+ raise ValueError("Purpose ID non specificato nella configurazione.")
64
+
65
+ with open(self.privKeyPath, "rb") as key_file:
42
66
  private_key = key_file.read()
43
67
 
44
68
  issued_at = int(time.time())
@@ -46,17 +70,17 @@ class JWTGenerator:
46
70
  jti = secrets.token_hex(16)
47
71
 
48
72
  payload = {
49
- "iss": self.config.get("issuer"),
50
- "sub": self.config.get("clientId"),
73
+ "iss": self.issuer,
74
+ "sub": self.clientId,
51
75
  "aud": self.aud,
52
- "purposeId": self.config.get("purposeId"),
76
+ "purposeId": self.purposeId,
53
77
  "jti": jti,
54
78
  "iat": issued_at,
55
79
  "exp": expiration_time
56
80
  }
57
81
 
58
82
  headers = {
59
- "kid": self.config.get("kid"),
83
+ "kid": self.kid,
60
84
  "alg": "RS256",
61
85
  "typ": "JWT"
62
86
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdnd-python-client
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Client Python per autenticazione e interazione con le API della Piattaforma Digitale Nazionale Dati (PDND).
5
5
  Author-email: Francesco Loreti <francesco.loreti@isprambiente.it>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pdnd-python-client"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Client Python per autenticazione e interazione con le API della Piattaforma Digitale Nazionale Dati (PDND)."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,9 +1,10 @@
1
1
 
2
2
  import pytest
3
3
  from unittest.mock import patch, Mock
4
+ from pdnd_client.config import Config
5
+ from pdnd_client.jwt_generator import JWTGenerator
4
6
  from pdnd_client.client import PDNDClient
5
7
 
6
-
7
8
  # Il codice seguente è una suite di test per la classe PDNDClient,
8
9
  # che verifica il funzionamento dei metodi get_status e get_api.
9
10
  # Questi test utilizzano la libreria unittest.mock per simulare le risposte delle richieste HTTP