besapi 3.9.8__tar.gz → 4.0.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.
Files changed (27) hide show
  1. {besapi-3.9.8/src/besapi.egg-info → besapi-4.0.2}/PKG-INFO +2 -1
  2. {besapi-3.9.8 → besapi-4.0.2}/setup.cfg +1 -0
  3. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/besapi.py +141 -47
  4. {besapi-3.9.8 → besapi-4.0.2/src/besapi.egg-info}/PKG-INFO +2 -1
  5. {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/requires.txt +1 -0
  6. {besapi-3.9.8 → besapi-4.0.2}/src/bescli/bescli.py +19 -5
  7. {besapi-3.9.8 → besapi-4.0.2}/tests/test_besapi.py +24 -0
  8. {besapi-3.9.8 → besapi-4.0.2}/LICENSE.txt +0 -0
  9. {besapi-3.9.8 → besapi-4.0.2}/MANIFEST.in +0 -0
  10. {besapi-3.9.8 → besapi-4.0.2}/README.md +0 -0
  11. {besapi-3.9.8 → besapi-4.0.2}/pyproject.toml +0 -0
  12. {besapi-3.9.8 → besapi-4.0.2}/setup.py +0 -0
  13. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/__init__.py +0 -0
  14. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/__main__.py +0 -0
  15. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/plugin_utilities.py +0 -0
  16. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/plugin_utilities_win.py +0 -0
  17. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BES.xsd +0 -0
  18. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BESAPI.xsd +0 -0
  19. {besapi-3.9.8 → besapi-4.0.2}/src/besapi/schemas/BESActionSettings.xsd +0 -0
  20. {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/SOURCES.txt +0 -0
  21. {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/dependency_links.txt +0 -0
  22. {besapi-3.9.8 → besapi-4.0.2}/src/besapi.egg-info/top_level.txt +0 -0
  23. {besapi-3.9.8 → besapi-4.0.2}/src/bescli/__init__.py +0 -0
  24. {besapi-3.9.8 → besapi-4.0.2}/src/bescli/__main__.py +0 -0
  25. {besapi-3.9.8 → besapi-4.0.2}/tests/test_plugin_utilities.py +0 -0
  26. {besapi-3.9.8 → besapi-4.0.2}/tests/test_plugin_utilities_logging.py +0 -0
  27. {besapi-3.9.8 → besapi-4.0.2}/tests/tests.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: besapi
3
- Version: 3.9.8
3
+ Version: 4.0.2
4
4
  Summary: Library for working with the BigFix REST API
5
5
  Home-page: https://github.com/jgstew/besapi
6
6
  Author: Matt Hansen, James Stewart
@@ -25,6 +25,7 @@ Requires-Dist: lxml
25
25
  Requires-Dist: requests
26
26
  Requires-Dist: setuptools
27
27
  Requires-Dist: pywin32; platform_system == "Windows"
28
+ Requires-Dist: urllib3>=2.2.3
28
29
  Dynamic: license-file
29
30
 
30
31
  # besapi
@@ -34,6 +34,7 @@ install_requires =
34
34
  requests
35
35
  setuptools
36
36
  pywin32; platform_system == "Windows"
37
+ urllib3 >= 2.2.3
37
38
 
38
39
  [options.package_data]
39
40
  besapi = schemas/*.xsd
@@ -12,6 +12,7 @@ Library for communicating with the BES (BigFix) REST API.
12
12
 
13
13
  import configparser
14
14
  import datetime
15
+ import getpass
15
16
  import hashlib
16
17
  import importlib.resources
17
18
  import json
@@ -20,13 +21,15 @@ import os
20
21
  import random
21
22
  import site
22
23
  import string
24
+ import sys
23
25
  import urllib.parse
24
26
 
25
27
  import lxml.etree
26
28
  import lxml.objectify
27
29
  import requests
30
+ import urllib3.poolmanager
28
31
 
29
- __version__ = "3.9.8"
32
+ __version__ = "4.0.2"
30
33
 
31
34
  besapi_logger = logging.getLogger("besapi")
32
35
 
@@ -117,44 +120,6 @@ def parse_bes_modtime(string_datetime):
117
120
  return datetime.datetime.strptime(string_datetime, "%a, %d %b %Y %H:%M:%S %z")
118
121
 
119
122
 
120
- # import urllib3.poolmanager
121
- # # https://docs.python-requests.org/en/latest/user/advanced/#transport-adapters
122
- # class HTTPAdapterBiggerBlocksize(requests.adapters.HTTPAdapter):
123
- # """custom HTTPAdapter for requests to override blocksize
124
- # for Uploading or Downloading large files"""
125
-
126
- # # override init_poolmanager from regular HTTPAdapter
127
- # # https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488
128
- # def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
129
- # """Initializes a urllib3 PoolManager.
130
-
131
- # This method should not be called from user code, and is only
132
- # exposed for use when subclassing the
133
- # :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
134
-
135
- # :param connections: The number of urllib3 connection pools to cache.
136
- # :param maxsize: The maximum number of connections to save in the pool.
137
- # :param block: Block when no free connections are available.
138
- # :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
139
- # """
140
- # # save these values for pickling
141
- # self._pool_connections = connections
142
- # self._pool_maxsize = maxsize
143
- # self._pool_block = block
144
-
145
- # # This doesn't work until urllib3 is updated to a future version:
146
- # # updating blocksize to be larger:
147
- # # pool_kwargs["blocksize"] = 8 * 1024 * 1024
148
-
149
- # self.poolmanager = urllib3.poolmanager.PoolManager(
150
- # num_pools=connections,
151
- # maxsize=maxsize,
152
- # block=block,
153
- # strict=True,
154
- # **pool_kwargs,
155
- # )
156
-
157
-
158
123
  def get_action_combined_relevance(relevances: list[str]):
159
124
  """Take array of ordered relevance clauses and return relevance string for
160
125
  action.
@@ -341,6 +306,119 @@ def get_bes_conn_using_config_file(conf_file=None):
341
306
  return None
342
307
 
343
308
 
309
+ def get_bes_conn_interactive(
310
+ user=None, password=None, root_server=None, force_prompt=False
311
+ ):
312
+ """Get BESConnection using interactive prompts."""
313
+
314
+ if not (force_prompt or sys.__stdin__.isatty()):
315
+ logging.error("No TTY available for interactive login!")
316
+ return None
317
+
318
+ print(
319
+ "Attempting BESAPI Connection using interactive prompts. Use Ctrl-C to cancel."
320
+ )
321
+ try:
322
+ if not user:
323
+ user = str(input("User [%s]: " % getpass.getuser())).strip()
324
+ if not user:
325
+ user = getpass.getuser()
326
+
327
+ if not root_server:
328
+ root_server = str(
329
+ input("Root Server (ex. %s): " % "https://localhost:52311")
330
+ ).strip()
331
+ if not root_server or root_server == "":
332
+ print("Root Server is required, try again!")
333
+ return get_bes_conn_interactive(
334
+ user=user,
335
+ password=password,
336
+ root_server=None,
337
+ force_prompt=force_prompt,
338
+ )
339
+
340
+ if not password:
341
+ password = str(
342
+ getpass.getpass(f"Password for {user}@{root_server}: ")
343
+ ).strip()
344
+
345
+ if not password or password == "":
346
+ print("Password is required, try again!")
347
+ return get_bes_conn_interactive(
348
+ user=user,
349
+ password=None,
350
+ root_server=root_server,
351
+ force_prompt=force_prompt,
352
+ )
353
+ except (KeyboardInterrupt, EOFError):
354
+ print("\nLogin cancelled.")
355
+ return None
356
+
357
+ try:
358
+ return BESConnection(user, password, root_server)
359
+ except requests.exceptions.HTTPError as err:
360
+ print("Bad Password, Try again!")
361
+ logging.debug(err)
362
+ return get_bes_conn_interactive(
363
+ user=user, password=None, root_server=root_server, force_prompt=force_prompt
364
+ )
365
+ except requests.exceptions.ConnectionError as err:
366
+ print("Bad Root Server Specified, Try again!")
367
+ logging.debug("Connection Error: %s", err)
368
+ return get_bes_conn_interactive(
369
+ user=user, password=password, root_server=None, force_prompt=force_prompt
370
+ )
371
+ except Exception as e: # pylint: disable=broad-except
372
+ logging.error("Error occurred while establishing BESConnection: %s", e)
373
+ return None
374
+
375
+
376
+ # https://docs.python-requests.org/en/latest/user/advanced/#transport-adapters
377
+ class HTTPAdapterBlocksize(requests.adapters.HTTPAdapter):
378
+ """Custom HTTPAdapter for requests to override blocksize
379
+ for Uploading or Downloading large files.
380
+ """
381
+
382
+ def __init__(self, blocksize=1000000, **kwargs):
383
+ self.blocksize = blocksize
384
+ super().__init__(**kwargs)
385
+
386
+ # override init_poolmanager from regular HTTPAdapter
387
+ # https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488
388
+ def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs):
389
+ """Initializes a urllib3 PoolManager.
390
+
391
+ This method should not be called from user code, and is only
392
+ exposed for use when subclassing the
393
+ :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
394
+
395
+ :param connections: The number of urllib3 connection pools to cache.
396
+ :param maxsize: The maximum number of connections to save in the pool.
397
+ :param block: Block when no free connections are available.
398
+ :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
399
+ """
400
+ # save these values for pickling
401
+ self._pool_connections = connections
402
+ self._pool_maxsize = maxsize
403
+ self._pool_block = block
404
+
405
+ try:
406
+ self.poolmanager = urllib3.poolmanager.PoolManager(
407
+ num_pools=connections,
408
+ maxsize=maxsize,
409
+ block=block,
410
+ blocksize=self.blocksize,
411
+ **pool_kwargs,
412
+ )
413
+ except Exception: # pylint: disable=broad-except
414
+ self.poolmanager = urllib3.poolmanager.PoolManager(
415
+ num_pools=connections,
416
+ maxsize=maxsize,
417
+ block=block,
418
+ **pool_kwargs,
419
+ )
420
+
421
+
344
422
  class BESConnection:
345
423
  """BigFix RESTAPI connection abstraction class."""
346
424
 
@@ -355,6 +433,16 @@ class BESConnection:
355
433
  self.session = requests.Session()
356
434
  self.session.auth = (username, password)
357
435
 
436
+ # # configure retries for requests
437
+ # retry = requests.adapters.Retry(
438
+ # total=2,
439
+ # backoff_factor=0.1,
440
+ # # status_forcelist=[500, 502, 503, 504],
441
+ # )
442
+
443
+ # # mount the HTTPAdapter with the retry configuration
444
+ # self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=retry))
445
+
358
446
  # store if connection user is main operator
359
447
  self.is_main_operator = None
360
448
 
@@ -564,7 +652,7 @@ class BESConnection:
564
652
  )
565
653
  return "\n".join(rel_result_array)
566
654
 
567
- def login(self):
655
+ def login(self, timeout=(3, 20)):
568
656
  """Do login."""
569
657
  if bool(self.last_connected):
570
658
  duration_obj = datetime.datetime.now() - self.last_connected
@@ -577,21 +665,24 @@ class BESConnection:
577
665
  # default timeout is 5 minutes
578
666
  # I'm not sure if this is required
579
667
  # or if 'requests' would handle this automatically anyway
580
- if int(duration_minutes) > 3:
581
- besapi_logger.info("Refreshing Login to prevent timeout.")
582
- self.last_connected = None
668
+ # if int(duration_minutes) > 3:
669
+ # besapi_logger.info("Refreshing Login to prevent timeout.")
670
+ # self.last_connected = None
583
671
 
584
672
  if not bool(self.last_connected):
585
- result_login = self.get("login")
673
+ result_login = self.get("login", timeout=timeout)
586
674
  if not result_login.request.status_code == 200:
587
675
  result_login.request.raise_for_status()
588
676
  if result_login.request.status_code == 200:
589
677
  # set time of connection
590
678
  self.last_connected = datetime.datetime.now()
591
679
 
592
- # This doesn't work until urllib3 is updated to a future version:
593
- # if self.connected():
594
- # self.session.mount(self.url("upload"), HTTPAdapterBiggerBlocksize())
680
+ # This doesn't work until urllib3 is at least ~v2:
681
+ if self.last_connected:
682
+ try:
683
+ self.session.mount(self.url("upload"), HTTPAdapterBlocksize())
684
+ except Exception: # pylint: disable=broad-except
685
+ pass
595
686
 
596
687
  return bool(self.last_connected)
597
688
 
@@ -872,6 +963,9 @@ class BESConnection:
872
963
 
873
964
  # Example Header:: Content-Disposition: attachment; filename="file.xml"
874
965
  headers = {"Content-Disposition": f'attachment; filename="{file_name}"'}
966
+ logging.warning(
967
+ "Uploading `%s` to BigFix Server, this could take a while.", file_name
968
+ )
875
969
  with open(file_path, "rb") as f:
876
970
  return self.post(self.url("upload"), data=f, headers=headers)
877
971
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: besapi
3
- Version: 3.9.8
3
+ Version: 4.0.2
4
4
  Summary: Library for working with the BigFix REST API
5
5
  Home-page: https://github.com/jgstew/besapi
6
6
  Author: Matt Hansen, James Stewart
@@ -25,6 +25,7 @@ Requires-Dist: lxml
25
25
  Requires-Dist: requests
26
26
  Requires-Dist: setuptools
27
27
  Requires-Dist: pywin32; platform_system == "Windows"
28
+ Requires-Dist: urllib3>=2.2.3
28
29
  Dynamic: license-file
29
30
 
30
31
  # besapi
@@ -2,6 +2,7 @@ cmd2
2
2
  lxml
3
3
  requests
4
4
  setuptools
5
+ urllib3>=2.2.3
5
6
 
6
7
  [:platform_system == "Windows"]
7
8
  pywin32
@@ -216,6 +216,20 @@ class BESCLInterface(Cmd):
216
216
  self.pfeedback(" - attempt login using config parameters - ")
217
217
  self.do_login()
218
218
 
219
+ def do_login_new(self, user=None):
220
+ """Login to BigFix Server."""
221
+ if not user or str(user).strip() == "":
222
+ user = self.BES_USER_NAME
223
+ self.bes_conn = besapi.get_bes_conn_interactive(
224
+ user=user,
225
+ password=self.BES_PASSWORD,
226
+ root_server=self.BES_ROOT_SERVER,
227
+ )
228
+ if self.bes_conn:
229
+ self.pfeedback("Login Successful!")
230
+ (self.BES_USER_NAME, self.BES_PASSWORD) = self.bes_conn.session.auth
231
+ self.BES_ROOT_SERVER = self.bes_conn.rootserver
232
+
219
233
  def do_login(self, user=None):
220
234
  """Login to BigFix Server."""
221
235
 
@@ -227,7 +241,7 @@ class BESCLInterface(Cmd):
227
241
  if not user:
228
242
  user = getpass.getuser()
229
243
 
230
- self.BES_USER_NAME = user
244
+ self.BES_USER_NAME = user.strip()
231
245
  if not self.CONFPARSER.has_section("besapi"):
232
246
  self.CONFPARSER.add_section("besapi")
233
247
  self.CONFPARSER.set("besapi", "BES_USER_NAME", user)
@@ -237,14 +251,14 @@ class BESCLInterface(Cmd):
237
251
  if not root_server:
238
252
  root_server = str(input("Root Server [%s]: " % self.BES_ROOT_SERVER))
239
253
 
240
- self.BES_ROOT_SERVER = root_server
254
+ self.BES_ROOT_SERVER = root_server.strip()
241
255
 
242
256
  else:
243
257
  root_server = str(
244
258
  input("Root Server (ex. %s): " % "https://server.institution.edu:52311")
245
259
  )
246
260
  if root_server:
247
- self.BES_ROOT_SERVER = root_server
261
+ self.BES_ROOT_SERVER = root_server.strip()
248
262
  if not self.CONFPARSER.has_section("besapi"):
249
263
  self.CONFPARSER.add_section("besapi")
250
264
  self.CONFPARSER.set("besapi", "BES_ROOT_SERVER", root_server)
@@ -390,7 +404,7 @@ class BESCLInterface(Cmd):
390
404
  if not self.bes_conn:
391
405
  self.do_login()
392
406
  if not self.bes_conn:
393
- self.poutput("ERROR: can't check without login")
407
+ self.perror("ERROR: can't check without login")
394
408
  else:
395
409
  self.poutput(f"Am I Main Operator? {self.bes_conn.am_i_main_operator()}")
396
410
 
@@ -399,7 +413,7 @@ class BESCLInterface(Cmd):
399
413
  if not self.bes_conn:
400
414
  self.do_login()
401
415
  if not self.bes_conn:
402
- self.poutput("ERROR: can't query without login")
416
+ self.perror("ERROR: can't query without login")
403
417
  else:
404
418
  if statement.raw:
405
419
  # get everything after `query `
@@ -316,6 +316,30 @@ def test_bes_conn_json():
316
316
  pytest.skip("Skipping BESConnection test, no config file or login failed.")
317
317
 
318
318
 
319
+ def test_bes_conn_upload_always():
320
+ """Test the BESConnection class with JSON output."""
321
+ file_name = "LICENSE.txt"
322
+ file_path = "../" + file_name
323
+ if not os.path.isfile(os.path.abspath(file_path)):
324
+ # handle case where not running from src or tests folder.
325
+ file_path = "./" + file_name
326
+ assert os.path.isfile(os.path.abspath(file_path))
327
+
328
+ bes_conn = besapi.plugin_utilities.get_besapi_connection(None)
329
+ if bes_conn and bes_conn.login():
330
+ # test upload
331
+ # Example Header:: Content-Disposition: attachment; filename="file.xml"
332
+ headers = {"Content-Disposition": f'attachment; filename="{file_name}"'}
333
+ with open(file_path, "rb") as f:
334
+ result = bes_conn.post(bes_conn.url("upload"), data=f, headers=headers)
335
+ print(result)
336
+ assert result is not None
337
+ assert result.besobj.FileUpload.Available == 1
338
+ assert result.besdict["FileUpload"]["Available"] == "1"
339
+ else:
340
+ pytest.skip("Skipping BESConnection upload test, login failed.")
341
+
342
+
319
343
  def test_plugin_utilities_logging():
320
344
  """Test the plugin_utilities module."""
321
345
  print(besapi.plugin_utilities.get_invoke_folder())
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes