dcicutils 8.7.2.1b2__py3-none-any.whl → 8.7.2.1b3__py3-none-any.whl

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.
@@ -0,0 +1,70 @@
1
+ from collections import namedtuple
2
+ from contextlib import contextmanager
3
+ import io
4
+ import sys
5
+ from typing import Optional
6
+
7
+ _real_stdout = sys.stdout
8
+ _real_stderr = sys.stderr
9
+
10
+
11
+ @contextmanager
12
+ def captured_output(capture: bool = True):
13
+ """
14
+ Context manager to capture any/all output to stdout or stderr, and not actually output it to stdout
15
+ or stderr. Yields and object with a get_captured_output() method to get the output captured thus far,
16
+ and another uncaptured_print() method to actually print the given output to stdout, even though output
17
+ to stdout is being captured. Can be useful, for example, in creating command-line scripts which invoke
18
+ code which outputs a lot of info, warning, error, etc to stdout or stderr, and we want to suprress that
19
+ output; but with the yielded uncaptured_print() method output specific to the script can actually be
20
+ output (to stdout); and/or can also optionally output any/all captured output, e.g. for debugging or
21
+ troubleshooting purposes. Disable this capture, without having to restructure your code WRT the usage
22
+ of the with-clause with this context manager, pass False as an argument to this context manager.
23
+ """
24
+
25
+ original_stdout = _real_stdout
26
+ original_stderr = _real_stderr
27
+ captured_output = io.StringIO()
28
+
29
+ def set_original_output() -> None:
30
+ sys.stdout = original_stdout
31
+ sys.stderr = original_stderr
32
+
33
+ def set_captured_output() -> None:
34
+ if capture:
35
+ sys.stdout = captured_output
36
+ sys.stderr = captured_output
37
+
38
+ def uncaptured_print(*args, **kwargs) -> None:
39
+ set_original_output()
40
+ print(*args, **kwargs)
41
+ set_captured_output()
42
+
43
+ def uncaptured_input(message: str) -> str:
44
+ set_original_output()
45
+ value = input(message)
46
+ set_captured_output()
47
+ return value
48
+
49
+ def get_captured_output() -> Optional[str]:
50
+ return captured_output.getvalue() if capture else None
51
+
52
+ try:
53
+ set_captured_output()
54
+ Result = namedtuple("Result", ["get_captured_output", "uncaptured_print", "uncaptured_input"])
55
+ yield Result(get_captured_output, uncaptured_print, uncaptured_input)
56
+ finally:
57
+ set_original_output()
58
+
59
+
60
+ @contextmanager
61
+ def uncaptured_output():
62
+ original_stdout = sys.stdout
63
+ original_stderr = sys.stderr
64
+ sys.stdout = _real_stdout
65
+ sys.stderr = _real_stderr
66
+ try:
67
+ yield
68
+ finally:
69
+ sys.stdout = original_stdout
70
+ sys.stderr = original_stderr
dcicutils/data_readers.py CHANGED
@@ -152,9 +152,10 @@ class ExcelSheetReader(RowReader):
152
152
 
153
153
  class Excel:
154
154
 
155
- def __init__(self, file: str, reader_class: Optional[Type] = None) -> None:
155
+ def __init__(self, file: str, reader_class: Optional[Type] = None, include_hidden_sheets: bool = False) -> None:
156
156
  self._file = file
157
157
  self._workbook = None
158
+ self._include_hidden_sheets = include_hidden_sheets
158
159
  self.sheet_names = None
159
160
  if isinstance(reader_class, Type) and issubclass(reader_class, ExcelSheetReader):
160
161
  self._reader_class = reader_class
@@ -169,7 +170,7 @@ class Excel:
169
170
  if self._workbook is None:
170
171
  self._workbook = openpyxl.load_workbook(self._file, data_only=True)
171
172
  self.sheet_names = [sheet_name for sheet_name in self._workbook.sheetnames
172
- if self._workbook[sheet_name].sheet_state != "hidden"]
173
+ if self._include_hidden_sheets or (self._workbook[sheet_name].sheet_state != "hidden")]
173
174
 
174
175
  def __del__(self) -> None:
175
176
  if (workbook := self._workbook) is not None:
@@ -65,7 +65,7 @@ def normalize_datetime_string(value: str) -> Optional[str]:
65
65
  or if ill-formated then returns None. The given string is assumed to be in the format "YYYY-MM-DD hh:mm:ss"
66
66
  and with an optional timezone suffix in format "+hh:mm" or "+hh". Also allowed is just a date of the
67
67
  format "YYYY-MM-DD" in which case a time of "00:00:00" is assumed. If no timezone is specified then
68
- the local timezone is assumed. The returned format looks like this: "2024-02-08T10:37:51-05:00"
68
+ the local timezone is assumed. The returned format looks like this: "2024-02-08T10:37:51-05:00"
69
69
  """
70
70
  dt = parse_datetime_string(value)
71
71
  return dt.isoformat() if dt else None
@@ -0,0 +1,155 @@
1
+ # ------------------------------------------------------------------------------------------------------
2
+ # Command-line utility to retrieve and print the given object (UUID) from a SMaHT/CGAP/Fourfront Portal.
3
+ # ------------------------------------------------------------------------------------------------------
4
+ # Example command:
5
+ # view-portal-object 4483b19d-62e7-4e7f-a211-0395343a35df --yaml
6
+ #
7
+ # Example output:
8
+ # '@context': /terms/
9
+ # '@id': /access-keys/3968e38e-c11f-472e-8531-8650e2e296d4/
10
+ # '@type':
11
+ # - AccessKey
12
+ # - Item
13
+ # access_key_id: NSVCZ75O
14
+ # date_created: '2023-09-06T13:11:59.704005+00:00'
15
+ # description: Manually generated local access-key for testing.
16
+ # display_title: AccessKey from 2023-09-06
17
+ # expiration_date: '2023-12-05T13:11:59.714106'
18
+ # last_modified:
19
+ # date_modified: '2023-09-06T13:11:59.711367+00:00'
20
+ # modified_by:
21
+ # '@id': /users/3202fd57-44d2-44fb-a131-afb1e43d8ae5/
22
+ # '@type':
23
+ # - User
24
+ # - Item
25
+ # status: current
26
+ # uuid: 3202fd57-44d2-44fb-a131-afb1e43d8ae5
27
+ # principals_allowed:
28
+ # edit:
29
+ # - group.admin
30
+ # - userid.74fef71a-dfc1-4aa4-acc0-cedcb7ac1d68
31
+ # view:
32
+ # - group.admin
33
+ # - group.read-only-admin
34
+ # - userid.74fef71a-dfc1-4aa4-acc0-cedcb7ac1d68
35
+ # schema_version: '1'
36
+ # status: current
37
+ # user:
38
+ # '@id': /users/74fef71a-dfc1-4aa4-acc0-cedcb7ac1d68/
39
+ # '@type':
40
+ # - User
41
+ # - Item
42
+ # display_title: David Michaels
43
+ # principals_allowed:
44
+ # edit:
45
+ # - group.admin
46
+ # view:
47
+ # - group.admin
48
+ # - group.read-only-admin
49
+ # status: current
50
+ # uuid: 74fef71a-dfc1-4aa4-acc0-cedcb7ac1d68
51
+ # uuid: 3968e38e-c11f-472e-8531-8650e2e296d4
52
+ #
53
+ # Note that instead of a uuid you can also actually use a path, for example:
54
+ # view-local-object /file-formats/vcf_gz_tbi
55
+ #
56
+ # --------------------------------------------------------------------------------------------------
57
+
58
+ import argparse
59
+ import json
60
+ import pyperclip
61
+ import sys
62
+ from typing import Optional
63
+ import yaml
64
+ from dcicutils.misc_utils import get_error_message
65
+ from dcicutils.portal_utils import Portal
66
+ from dcicutils.captured_output import captured_output, uncaptured_output
67
+
68
+
69
+ def main():
70
+
71
+ parser = argparse.ArgumentParser(description="View Portal object.")
72
+ parser.add_argument("uuid", type=str,
73
+ help=f"The uuid (or path) of the object to fetch and view. ")
74
+ parser.add_argument("--ini", type=str, required=False, default=None,
75
+ help=f"Name of the application .ini file.")
76
+ parser.add_argument("--env", "-e", type=str, required=False, default=None,
77
+ help=f"Environment name (key from ~/.smaht-keys.json).")
78
+ parser.add_argument("--server", "-s", type=str, required=False, default=None,
79
+ help=f"Environment server name (server from key in ~/.smaht-keys.json).")
80
+ parser.add_argument("--app", type=str, required=False, default=None,
81
+ help=f"Application name (one of: smaht, cgap, fourfront).")
82
+ parser.add_argument("--raw", action="store_true", required=False, default=False, help="Raw output.")
83
+ parser.add_argument("--database", action="store_true", required=False, default=False,
84
+ help="Read from database output.")
85
+ parser.add_argument("--yaml", action="store_true", required=False, default=False, help="YAML output.")
86
+ parser.add_argument("--copy", "-c", action="store_true", required=False, default=False,
87
+ help="Copy object data to clipboard.")
88
+ parser.add_argument("--verbose", action="store_true", required=False, default=False, help="Verbose output.")
89
+ parser.add_argument("--debug", action="store_true", required=False, default=False, help="Debugging output.")
90
+ args = parser.parse_args()
91
+
92
+ portal = _create_portal(ini=args.ini, env=args.env, server=args.server, app=args.app, debug=args.debug)
93
+ data = _get_portal_object(portal=portal, uuid=args.uuid, raw=args.raw, database=args.database, verbose=args.verbose)
94
+
95
+ if args.copy:
96
+ pyperclip.copy(json.dumps(data, indent=4))
97
+ if args.yaml:
98
+ _print(yaml.dump(data))
99
+ else:
100
+ _print(json.dumps(data, default=str, indent=4))
101
+
102
+
103
+ def _create_portal(ini: str, env: Optional[str] = None,
104
+ server: Optional[str] = None, app: Optional[str] = None, debug: bool = False) -> Portal:
105
+ with captured_output(not debug):
106
+ return Portal(env, server=server, app=app) if env or app else Portal(ini)
107
+
108
+
109
+ def _get_portal_object(portal: Portal, uuid: str,
110
+ raw: bool = False, database: bool = False, verbose: bool = False) -> dict:
111
+ if verbose:
112
+ _print(f"Getting object ({uuid}) from portal ... ", end="")
113
+ response = None
114
+ try:
115
+ if not uuid.startswith("/"):
116
+ path = f"/{uuid}"
117
+ else:
118
+ path = uuid
119
+ response = portal.get(path, raw=raw, database=database)
120
+ except Exception as e:
121
+ if "404" in str(e) and "not found" in str(e).lower():
122
+ if verbose:
123
+ _print("Not found!")
124
+ else:
125
+ _print(f"Object ({uuid}) not found!")
126
+ _exit_without_action()
127
+ _exit_without_action(f"Exception getting object ({uuid}) -> {get_error_message(e)}", newline=verbose)
128
+ if not response:
129
+ _exit_without_action(f"Null response getting object {uuid}).")
130
+ if response.status_code not in [200, 307]:
131
+ # TODO: Understand why the /me endpoint returns HTTP status code 307, which is only why we mention it above.
132
+ _exit_without_action(f"Invalid status code ({response.status_code}) getting object: {uuid}")
133
+ if not response.json:
134
+ _exit_without_action(f"Invalid JSON getting object {uuid}).")
135
+ if verbose:
136
+ _print("OK")
137
+ return response.json()
138
+
139
+
140
+ def _print(*args, **kwargs):
141
+ with uncaptured_output():
142
+ print(*args, **kwargs)
143
+ sys.stdout.flush()
144
+
145
+
146
+ def _exit_without_action(message: Optional[str] = None, newline: bool = True) -> None:
147
+ if message:
148
+ if newline:
149
+ _print()
150
+ _print(f"ERROR: {message}")
151
+ exit(1)
152
+
153
+
154
+ if __name__ == "__main__":
155
+ main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dcicutils
3
- Version: 8.7.2.1b2
3
+ Version: 8.7.2.1b3
4
4
  Summary: Utility package for interacting with the 4DN Data Portal and other 4DN resources
5
5
  Home-page: https://github.com/4dn-dcic/utils
6
6
  License: MIT
@@ -36,6 +36,7 @@ Requires-Dist: jsonschema (>=4.19.0,<5.0.0)
36
36
  Requires-Dist: openpyxl (>=3.1.2,<4.0.0)
37
37
  Requires-Dist: opensearch-py (>=2.0.1,<3.0.0)
38
38
  Requires-Dist: pyOpenSSL (>=23.1.1,<24.0.0)
39
+ Requires-Dist: pyperclip (>=1.8.2,<2.0.0)
39
40
  Requires-Dist: pyramid (==1.10.4)
40
41
  Requires-Dist: pytz (>=2020.4)
41
42
  Requires-Dist: redis (>=4.5.1,<5.0.0)
@@ -2,6 +2,7 @@ dcicutils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  dcicutils/base.py,sha256=gxNEv3DSVUfoX3NToWw7pcCdguxsJ75NDqsPi3wdFG4,5115
3
3
  dcicutils/beanstalk_utils.py,sha256=nHMWfFnZAXFiJh60oVouwbAPMKsQfHnDtkwz_PDE6S4,51434
4
4
  dcicutils/bundle_utils.py,sha256=ZVQcqlt7Yly8-YbL3A-5DW859_hMWpTL6dXtknEYZIw,34669
5
+ dcicutils/captured_output.py,sha256=9UorUoA-RSGf7fUILHy73JiZ-mJ1IV-or_JGMyncaMs,2522
5
6
  dcicutils/cloudformation_utils.py,sha256=MtWJrSTXyiImgbPHgRvfH9bWso20ZPLTFJAfhDQSVj4,13786
6
7
  dcicutils/codebuild_utils.py,sha256=CKpmhJ-Z8gYbkt1I2zyMlKtFdsg7T8lqrx3V5ieta-U,1155
7
8
  dcicutils/command_utils.py,sha256=JExll5TMqIcmuiGvuS8q4XDUvoEfi2oSH0E2FVF6suU,15285
@@ -9,9 +10,9 @@ dcicutils/common.py,sha256=YE8Mt5-vaZWWz4uaChSVhqGFbFtW5QKtnIyOr4zG4vM,3955
9
10
  dcicutils/contribution_scripts.py,sha256=0k5Gw1TumcD5SAcXVkDd6-yvuMEw-jUp5Kfb7FJH6XQ,2015
10
11
  dcicutils/contribution_utils.py,sha256=vYLS1JUB3sKd24BUxZ29qUBqYeQBLK9cwo8x3k64uPg,25653
11
12
  dcicutils/creds_utils.py,sha256=xrLekD49Ex0GOpL9n7LlJA4gvNcY7txTVFOSYD7LvEU,11113
12
- dcicutils/data_readers.py,sha256=ooEoFkYDWJMPdvnpIb9-Wpacyx--XkB8_fwnbPYLJK4,6239
13
+ dcicutils/data_readers.py,sha256=6k2Pgcw62JbKcAYFUkMUUxoqAw2OTQTYyTW0qAYhSNA,6369
13
14
  dcicutils/data_utils.py,sha256=k2OxOlsx7AJ6jF-YNlMyGus_JqSUBe4_n1s65Mv1gQQ,3098
14
- dcicutils/datetime_utils.py,sha256=NBl7MNdUx7u4f1CMCwws-sSUSXth8ZLeepk9rYwuZeM,4667
15
+ dcicutils/datetime_utils.py,sha256=EODDGAngp1yh2ZlDIuI7tB74JBJucw2DljqfPknzK0Y,4666
15
16
  dcicutils/deployment_utils.py,sha256=rcNUFMe_tsrG4CHEtgBe41cZx4Pk4JqISPsjrJRMoEs,68891
16
17
  dcicutils/diff_utils.py,sha256=sQx-yz56DHAcQWOChYbAG3clXu7TbiZKlw-GggeveO0,8118
17
18
  dcicutils/docker_utils.py,sha256=30gUiqz7X9rJwSPXTPn4ewjQibUgoSJqhP9o9vn5X-A,1747
@@ -56,6 +57,7 @@ dcicutils/s3_utils.py,sha256=LauLFQGvZLfpBJ81tYMikjLd3SJRz2R_FrL1n4xSlyI,28868
56
57
  dcicutils/schema_utils.py,sha256=Ky1KCrHYbDR4qd1prHBKJvO8Z_1x1xVUup1SsQsVP24,10002
57
58
  dcicutils/scripts/publish_to_pypi.py,sha256=LFzNHIQK2EXFr88YcfctyA_WKEBFc1ElnSjWrCXedPM,13889
58
59
  dcicutils/scripts/run_license_checker.py,sha256=z2keYnRDZsHQbTeo1XORAXSXNJK5axVzL5LjiNqZ7jE,4184
60
+ dcicutils/scripts/view_portal_object.py,sha256=XC6a-5QnEUJHNviPa40JckwwvQnfltZeioletxnnPLY,6163
59
61
  dcicutils/secrets_utils.py,sha256=8dppXAsiHhJzI6NmOcvJV5ldvKkQZzh3Fl-cb8Wm7MI,19745
60
62
  dcicutils/sheet_utils.py,sha256=VlmzteONW5VF_Q4vo0yA5vesz1ViUah1MZ_yA1rwZ0M,33629
61
63
  dcicutils/snapshot_utils.py,sha256=ymP7PXH6-yEiXAt75w0ldQFciGNqWBClNxC5gfX2FnY,22961
@@ -67,8 +69,8 @@ dcicutils/trace_utils.py,sha256=g8kwV4ebEy5kXW6oOrEAUsurBcCROvwtZqz9fczsGRE,1769
67
69
  dcicutils/validation_utils.py,sha256=cMZIU2cY98FYtzK52z5WUYck7urH6JcqOuz9jkXpqzg,14797
68
70
  dcicutils/variant_utils.py,sha256=2H9azNx3xAj-MySg-uZ2SFqbWs4kZvf61JnK6b-h4Qw,4343
69
71
  dcicutils/zip_utils.py,sha256=rnjNv_k6L9jT2SjDSgVXp4BEJYLtz9XN6Cl2Fy-tqnM,2027
70
- dcicutils-8.7.2.1b2.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
71
- dcicutils-8.7.2.1b2.dist-info/METADATA,sha256=9oYsYMIW6ROCdNsGL2gXJWZeDgde4L6H9d78fLMRmZs,3314
72
- dcicutils-8.7.2.1b2.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
73
- dcicutils-8.7.2.1b2.dist-info/entry_points.txt,sha256=8wbw5csMIgBXhkwfgsgJeuFcoUc0WsucUxmOyml2aoA,209
74
- dcicutils-8.7.2.1b2.dist-info/RECORD,,
72
+ dcicutils-8.7.2.1b3.dist-info/LICENSE.txt,sha256=qnwSmfnEWMl5l78VPDEzAmEbLVrRqQvfUQiHT0ehrOo,1102
73
+ dcicutils-8.7.2.1b3.dist-info/METADATA,sha256=b0rm54SK4Vo5VVnvegPx7Cd3P0Nz1OoQOgW2BGoR81g,3356
74
+ dcicutils-8.7.2.1b3.dist-info/WHEEL,sha256=7Z8_27uaHI_UZAc4Uox4PpBhQ9Y5_modZXWMxtUi4NU,88
75
+ dcicutils-8.7.2.1b3.dist-info/entry_points.txt,sha256=51Q4F_2V10L0282W7HFjP4jdzW4K8lnWDARJQVFy_hw,270
76
+ dcicutils-8.7.2.1b3.dist-info/RECORD,,
@@ -2,4 +2,5 @@
2
2
  publish-to-pypi=dcicutils.scripts.publish_to_pypi:main
3
3
  run-license-checker=dcicutils.scripts.run_license_checker:main
4
4
  show-contributors=dcicutils.contribution_scripts:show_contributors_main
5
+ view-portal-object=dcicutils.scripts.view_portal_object:main
5
6