python-ipware 2.0.2__tar.gz → 2.0.4__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.1
2
2
  Name: python-ipware
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: A Python package to retrieve user's IP address
5
5
  Author-email: Val Neekman <info@neekware.com>
6
6
  License: MIT
@@ -134,6 +134,7 @@ request_headers_precedence_order = (
134
134
  "HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
135
135
  "HTTP_FORWARDED_FOR", # RFC 7239
136
136
  "HTTP_FORWARDED", # RFC 7239
137
+ "HTTP_CF_CONNECTING_IP", # CloudFlare
137
138
  "X-CLIENT-IP", # Microsoft Azure
138
139
  "X-REAL-IP", # NGINX
139
140
  "X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
@@ -215,23 +216,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
215
216
  You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
216
217
 
217
218
  ```python
218
- # In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
219
219
  from python_ipware import IpWare
220
220
 
221
- # enforce proxy count
222
- ipw = IpWare(proxy_count=1)
221
+ # Enforce proxy count
222
+ # proxy_count=0 is valid
223
+ # proxy_count=None to disable proxy_count check
224
+ ipw = IpWare(proxy_count=2)
223
225
 
224
- # enforce proxy count and trusted proxies
225
- ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
226
+ # Example usage in non-strict mode:
227
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
228
+ # At least `proxy_count` number of proxies
229
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
226
230
 
231
+ # Example usage in strict mode:
232
+ # X-Forwarded-For format: <client>, <proxy1>, <proxy2>
233
+ # Exact `proxy_count` number of proxies
234
+ ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
235
+ ```
227
236
 
228
- # usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
229
- # total number of ip addresses are greater than the total count
230
- ip, trusted_route = ipw.get_client_ip(meta=request.META)
237
+ ### Proxy Count & Trusted Proxy List Combo
238
+ In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
231
239
 
240
+ ```python
241
+ from python_ipware import IpWare
232
242
 
233
- # usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
234
- # total number of ip addresses are exactly equal to client ip + proxy_count
243
+ # Enforce both proxy count and trusted proxies
244
+ ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
245
+
246
+ # Example usage in non-strict mode:
247
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
248
+ # At least `proxy_count` number of proxies
249
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
250
+
251
+ # Example usage in strict mode:
252
+ # X-Forwarded-For format: <client>, <proxy1>
253
+ # Exact `proxy_count` number of proxies
235
254
  ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
236
255
  ```
237
256
 
@@ -103,6 +103,7 @@ request_headers_precedence_order = (
103
103
  "HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
104
104
  "HTTP_FORWARDED_FOR", # RFC 7239
105
105
  "HTTP_FORWARDED", # RFC 7239
106
+ "HTTP_CF_CONNECTING_IP", # CloudFlare
106
107
  "X-CLIENT-IP", # Microsoft Azure
107
108
  "X-REAL-IP", # NGINX
108
109
  "X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
@@ -184,23 +185,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
184
185
  You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
185
186
 
186
187
  ```python
187
- # In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
188
188
  from python_ipware import IpWare
189
189
 
190
- # enforce proxy count
191
- ipw = IpWare(proxy_count=1)
190
+ # Enforce proxy count
191
+ # proxy_count=0 is valid
192
+ # proxy_count=None to disable proxy_count check
193
+ ipw = IpWare(proxy_count=2)
192
194
 
193
- # enforce proxy count and trusted proxies
194
- ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
195
+ # Example usage in non-strict mode:
196
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
197
+ # At least `proxy_count` number of proxies
198
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
195
199
 
200
+ # Example usage in strict mode:
201
+ # X-Forwarded-For format: <client>, <proxy1>, <proxy2>
202
+ # Exact `proxy_count` number of proxies
203
+ ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
204
+ ```
196
205
 
197
- # usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
198
- # total number of ip addresses are greater than the total count
199
- ip, trusted_route = ipw.get_client_ip(meta=request.META)
206
+ ### Proxy Count & Trusted Proxy List Combo
207
+ In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
200
208
 
209
+ ```python
210
+ from python_ipware import IpWare
201
211
 
202
- # usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
203
- # total number of ip addresses are exactly equal to client ip + proxy_count
212
+ # Enforce both proxy count and trusted proxies
213
+ ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
214
+
215
+ # Example usage in non-strict mode:
216
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
217
+ # At least `proxy_count` number of proxies
218
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
219
+
220
+ # Example usage in strict mode:
221
+ # X-Forwarded-For format: <client>, <proxy1>
222
+ # Exact `proxy_count` number of proxies
204
223
  ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
205
224
  ```
206
225
 
@@ -0,0 +1 @@
1
+ __version__ = "2.0.4"
@@ -28,6 +28,7 @@ class IpWareMeta:
28
28
  "HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
29
29
  "HTTP_FORWARDED_FOR", # RFC 7239
30
30
  "HTTP_FORWARDED", # RFC 7239
31
+ "HTTP_CF_CONNECTING_IP", # CloudFlare
31
32
  "X-CLIENT-IP", # Microsoft Azure
32
33
  "X-REAL-IP", # NGINX
33
34
  "X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
@@ -143,12 +144,9 @@ class IpWareProxy:
143
144
 
144
145
  def __init__(
145
146
  self,
146
- proxy_count: int = 0,
147
+ proxy_count: Optional[int] = None,
147
148
  proxy_list: Optional[List[str]] = None,
148
149
  ) -> None:
149
- if proxy_count is None or proxy_count < 0:
150
- raise ValueError("proxy_count must be a positive integer")
151
-
152
150
  self.proxy_count = proxy_count
153
151
  self.proxy_list = self._is_valid_proxy_trusted_list(proxy_list or [])
154
152
 
@@ -171,15 +169,14 @@ class IpWareProxy:
171
169
  """
172
170
  Checks if the proxy count is valid
173
171
  @param ip_list: list of ip addresses
174
- @param strict: if True, we must have exactly proxy_count proxies
172
+ @param strict: if True, we must have exactly proxy_count proxies, including `0` proxies
175
173
  @return: True if the proxy count is valid, False otherwise
176
174
  """
177
- if self.proxy_count < 1:
175
+ # No proxy count check is required
176
+ if self.proxy_count is None:
178
177
  return True
179
178
 
180
179
  ip_count: int = len(ip_list)
181
- if ip_count < 1:
182
- return False
183
180
 
184
181
  if strict:
185
182
  # our first proxy takes the last ip address and treats it as client ip
@@ -220,10 +217,6 @@ class IpWareProxy:
220
217
  if not str(value).startswith(self.proxy_list[index]):
221
218
  return False
222
219
 
223
- # now all we need is to return the first ip in the list that is not in the trusted proxy list
224
- # best_client_ip_index = proxy_list_count + 1
225
- # best_client_ip = ip_list[-best_client_ip_index]
226
-
227
220
  return True
228
221
 
229
222
 
@@ -236,11 +229,11 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
236
229
  self,
237
230
  precedence: Optional[Tuple[str, ...]] = None,
238
231
  leftmost: bool = True,
239
- proxy_count: int = 0,
232
+ proxy_count: Optional[int] = None,
240
233
  proxy_list: Optional[List[str]] = None,
241
234
  ) -> None:
242
235
  IpWareMeta.__init__(self, precedence, leftmost)
243
- IpWareProxy.__init__(self, proxy_count or 0, proxy_list or [])
236
+ IpWareProxy.__init__(self, proxy_count, proxy_list)
244
237
 
245
238
  def get_meta_value(self, meta: Dict[str, str], key: str) -> str:
246
239
  """
@@ -256,7 +249,13 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
256
249
  Given a list of keys, it returns a list of cleaned up values
257
250
  @return: a list of values
258
251
  """
259
- return [self.get_meta_value(meta, key) for key in self.precedence]
252
+ meta_list: List[str] = []
253
+ for key in self.precedence:
254
+ value = self.get_meta_value(meta, key).strip()
255
+ if value:
256
+ meta_list.append(value)
257
+
258
+ return meta_list
260
259
 
261
260
  def get_client_ip(
262
261
  self,
@@ -271,8 +270,6 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
271
270
  private_list: List[OptionalIpAddressType] = []
272
271
 
273
272
  for ip_str in self.get_meta_values(meta):
274
- if not ip_str:
275
- continue
276
273
 
277
274
  ip_list = self.get_ips_from_string(ip_str)
278
275
  if not ip_list:
@@ -336,7 +333,11 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
336
333
  return best_client_ip, True
337
334
 
338
335
  # the incoming ips match our proxy count
339
- if self.proxy_count > 0 and proxy_count_validated:
336
+ if (
337
+ self.proxy_count is not None
338
+ and self.proxy_count > 0
339
+ and proxy_count_validated
340
+ ):
340
341
  best_client_ip_index = self.proxy_count + 1
341
342
  best_client_ip = ip_list[-best_client_ip_index]
342
343
  return best_client_ip, True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-ipware
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: A Python package to retrieve user's IP address
5
5
  Author-email: Val Neekman <info@neekware.com>
6
6
  License: MIT
@@ -134,6 +134,7 @@ request_headers_precedence_order = (
134
134
  "HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
135
135
  "HTTP_FORWARDED_FOR", # RFC 7239
136
136
  "HTTP_FORWARDED", # RFC 7239
137
+ "HTTP_CF_CONNECTING_IP", # CloudFlare
137
138
  "X-CLIENT-IP", # Microsoft Azure
138
139
  "X-REAL-IP", # NGINX
139
140
  "X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
@@ -215,23 +216,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
215
216
  You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
216
217
 
217
218
  ```python
218
- # In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
219
219
  from python_ipware import IpWare
220
220
 
221
- # enforce proxy count
222
- ipw = IpWare(proxy_count=1)
221
+ # Enforce proxy count
222
+ # proxy_count=0 is valid
223
+ # proxy_count=None to disable proxy_count check
224
+ ipw = IpWare(proxy_count=2)
223
225
 
224
- # enforce proxy count and trusted proxies
225
- ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
226
+ # Example usage in non-strict mode:
227
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
228
+ # At least `proxy_count` number of proxies
229
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
226
230
 
231
+ # Example usage in strict mode:
232
+ # X-Forwarded-For format: <client>, <proxy1>, <proxy2>
233
+ # Exact `proxy_count` number of proxies
234
+ ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
235
+ ```
227
236
 
228
- # usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
229
- # total number of ip addresses are greater than the total count
230
- ip, trusted_route = ipw.get_client_ip(meta=request.META)
237
+ ### Proxy Count & Trusted Proxy List Combo
238
+ In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
231
239
 
240
+ ```python
241
+ from python_ipware import IpWare
232
242
 
233
- # usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
234
- # total number of ip addresses are exactly equal to client ip + proxy_count
243
+ # Enforce both proxy count and trusted proxies
244
+ ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
245
+
246
+ # Example usage in non-strict mode:
247
+ # X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
248
+ # At least `proxy_count` number of proxies
249
+ ip, trusted_route = ipw.get_client_ip(meta=request.META)
250
+
251
+ # Example usage in strict mode:
252
+ # X-Forwarded-For format: <client>, <proxy1>
253
+ # Exact `proxy_count` number of proxies
235
254
  ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
236
255
  ```
237
256
 
@@ -175,39 +175,69 @@ class TestIPv4Common(unittest.TestCase):
175
175
  class TestIPv4ProxyCount(unittest.TestCase):
176
176
  """IPv4 Proxy Count Test"""
177
177
 
178
- def setUp(self):
179
- self.ipware = IpWare(proxy_count=1)
180
-
181
- def tearDown(self):
182
- self.ipware = None
183
-
184
- def test_singleton_proxy_count(self):
178
+ def test_proxy_count_one_missing_proxy_fail(self):
179
+ ipware = IpWare(proxy_count=1)
185
180
  meta = {
186
181
  "HTTP_X_FORWARDED_FOR": "177.139.233.139",
187
182
  }
188
- r = self.ipware.get_client_ip(meta)
183
+ r = ipware.get_client_ip(meta)
184
+
189
185
  self.assertEqual(r, (None, False))
190
186
 
191
- def test_singleton_proxy_count_private(self):
187
+ def test_proxy_count_one_at_least_one_proxy_pass(self):
188
+ ipware = IpWare(proxy_count=1)
192
189
  meta = {
193
- "HTTP_X_FORWARDED_FOR": "10.0.0.0",
194
- "HTTP_X_REAL_IP": "177.139.233.139",
190
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
195
191
  }
196
- r = self.ipware.get_client_ip(meta)
192
+ r = ipware.get_client_ip(meta)
193
+ self.assertEqual(r, (IPv4Address("198.84.193.157"), True))
194
+
195
+ def test_proxy_count_one_exactly_one_proxy_fail(self):
196
+ ipware = IpWare(proxy_count=1)
197
+ meta = {
198
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
199
+ }
200
+ r = ipware.get_client_ip(meta, strict=True)
197
201
  self.assertEqual(r, (None, False))
198
202
 
199
- def test_proxy_count_relax(self):
203
+ def test_proxy_count_one_exactly_one_proxy_pass(self):
204
+ ipware = IpWare(proxy_count=1)
205
+ meta = {
206
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157",
207
+ }
208
+ r = ipware.get_client_ip(meta, strict=True)
209
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
210
+
211
+ def test_proxy_count_one_dont_care_proxy_pass(self):
212
+ ipware = IpWare()
200
213
  meta = {
201
214
  "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
202
215
  }
203
- r = self.ipware.get_client_ip(meta, strict=False)
204
- self.assertEqual(r, (IPv4Address("198.84.193.157"), True))
216
+ r = ipware.get_client_ip(meta)
217
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
205
218
 
206
- def test_proxy_count_strict(self):
219
+ def test_proxy_count_zero_dont_care_proxy_pass(self):
220
+ ipware = IpWare(proxy_count=0)
207
221
  meta = {
208
- "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.158",
222
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
209
223
  }
210
- r = self.ipware.get_client_ip(meta, strict=True)
224
+ r = ipware.get_client_ip(meta)
225
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
226
+
227
+ def test_proxy_count_zero_exact_zero_proxy_pass(self):
228
+ ipware = IpWare(proxy_count=0)
229
+ meta = {
230
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139",
231
+ }
232
+ r = ipware.get_client_ip(meta, strict=True)
233
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
234
+
235
+ def test_proxy_count_zero_exact_zero_proxy_fail(self):
236
+ ipware = IpWare(proxy_count=0)
237
+ meta = {
238
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
239
+ }
240
+ r = ipware.get_client_ip(meta, strict=True)
211
241
  self.assertEqual(r, (None, False))
212
242
 
213
243
 
@@ -245,26 +275,68 @@ class TestIPv4ProxyList(unittest.TestCase):
245
275
  class TestIPv4ProxyCountProxyList(unittest.TestCase):
246
276
  """IPv4 Proxy Count Test"""
247
277
 
248
- def setUp(self):
249
- self.ipware = IpWare(
250
- proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"]
251
- )
278
+ def test_proxy_list_relax(self):
279
+ ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
280
+ meta = {
281
+ "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
282
+ }
283
+ r = ipware.get_client_ip(meta)
284
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
252
285
 
253
- def tearDown(self):
254
- self.ipware = None
286
+ def test_proxy_list_strict_pass(self):
287
+ ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
288
+ meta = {
289
+ "HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
290
+ }
291
+ r = ipware.get_client_ip(meta, strict=True)
292
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
255
293
 
256
- def test_proxy_list_relax(self):
294
+ def test_proxy_list_strict_fail(self):
295
+ ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
257
296
  meta = {
258
297
  "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
259
298
  }
260
- r = self.ipware.get_client_ip(meta)
299
+ r = ipware.get_client_ip(meta, strict=True)
300
+ self.assertEqual(r, (None, False))
301
+
302
+ def test_proxy_list_relax_exact_pass(self):
303
+ ipware = IpWare(proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"])
304
+ meta = {
305
+ "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
306
+ }
307
+ r = ipware.get_client_ip(meta)
261
308
  self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
262
309
 
263
- def test_proxy_list_strict(self):
310
+ def test_proxy_list_relax_count_under_pass(self):
311
+ ipware = IpWare(proxy_count=1, proxy_list=["198.84.193.157", "198.84.193.158"])
264
312
  meta = {
265
313
  "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
266
314
  }
267
- r = self.ipware.get_client_ip(meta, strict=True)
315
+ r = ipware.get_client_ip(meta)
316
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
317
+
318
+ def test_proxy_list_relax_count_over_pass(self):
319
+ ipware = IpWare(proxy_count=5, proxy_list=["198.84.193.157", "198.84.193.158"])
320
+ meta = {
321
+ "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
322
+ }
323
+ r = ipware.get_client_ip(meta)
324
+ self.assertEqual(r, (None, False))
325
+
326
+ def test_proxy_list_count_exact_pass(self):
327
+ ipware = IpWare(proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"])
328
+ meta = {
329
+ "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
330
+ }
331
+ r = ipware.get_client_ip(meta)
332
+ self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
333
+
334
+ def test_proxy_list_count_exact_fail(self):
335
+ ipware = IpWare(proxy_count=4, proxy_list=["198.84.193.157", "198.84.193.158"])
336
+ meta = {
337
+ "HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
338
+ }
339
+ r = ipware.get_client_ip(meta)
268
340
  self.assertEqual(r, (None, False))
269
341
 
270
342
 
@@ -1 +0,0 @@
1
- __version__ = "2.0.2"
File without changes
File without changes