http2py 0.1.29__tar.gz → 0.1.31__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 (33) hide show
  1. http2py-0.1.31/PKG-INFO +88 -0
  2. {http2py-0.1.29 → http2py-0.1.31}/http2py/api_pkg_maker.py +30 -30
  3. {http2py-0.1.29 → http2py-0.1.31}/http2py/authentication.py +7 -7
  4. {http2py-0.1.29 → http2py-0.1.31}/http2py/cli_maker.py +23 -22
  5. {http2py-0.1.29 → http2py-0.1.31}/http2py/client.py +44 -43
  6. http2py-0.1.31/http2py/constants.py +4 -0
  7. {http2py-0.1.29 → http2py-0.1.31}/http2py/decorators.py +4 -4
  8. {http2py-0.1.29 → http2py-0.1.31}/http2py/example_cli.py +4 -4
  9. {http2py-0.1.29 → http2py-0.1.31}/http2py/py2request.py +205 -133
  10. {http2py-0.1.29 → http2py-0.1.31}/http2py/testing_utils.py +6 -6
  11. http2py-0.1.31/http2py/tests/api_pkg_maker_test.py +161 -0
  12. {http2py-0.1.29 → http2py-0.1.31}/http2py/tests/conftest.py +5 -5
  13. {http2py-0.1.29 → http2py-0.1.31}/http2py/util.py +5 -9
  14. http2py-0.1.31/http2py.egg-info/PKG-INFO +88 -0
  15. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/entry_points.txt +0 -1
  16. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/requires.txt +1 -0
  17. {http2py-0.1.29 → http2py-0.1.31}/setup.cfg +2 -2
  18. http2py-0.1.29/PKG-INFO +0 -80
  19. http2py-0.1.29/http2py/constants.py +0 -4
  20. http2py-0.1.29/http2py/tests/api_pkg_maker_test.py +0 -153
  21. http2py-0.1.29/http2py.egg-info/PKG-INFO +0 -80
  22. {http2py-0.1.29 → http2py-0.1.31}/LICENSE +0 -0
  23. {http2py-0.1.29 → http2py-0.1.31}/README.md +0 -0
  24. {http2py-0.1.29 → http2py-0.1.31}/http2py/__init__.py +0 -0
  25. {http2py-0.1.29 → http2py-0.1.31}/http2py/default_configs.py +0 -0
  26. {http2py-0.1.29 → http2py-0.1.31}/http2py/global_state.py +0 -0
  27. {http2py-0.1.29 → http2py-0.1.31}/http2py/http2py_cls.py +0 -0
  28. {http2py-0.1.29 → http2py-0.1.31}/http2py/tests/__init__.py +0 -0
  29. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/SOURCES.txt +0 -0
  30. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/dependency_links.txt +0 -0
  31. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/not-zip-safe +0 -0
  32. {http2py-0.1.29 → http2py-0.1.31}/http2py.egg-info/top_level.txt +0 -0
  33. {http2py-0.1.29 → http2py-0.1.31}/setup.py +0 -0
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: http2py
3
+ Version: 0.1.31
4
+ Summary: Tools to create python binders to http web services.
5
+ Home-page: https://github.com/i2mint/http2py
6
+ License: Apache
7
+ Keywords: webservice,http,requests,API
8
+ Platform: any
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: glom
12
+ Requires-Dist: i2
13
+ Requires-Dist: ju
14
+ Requires-Dist: requests
15
+ Requires-Dist: argh
16
+ Requires-Dist: PyYAML
17
+ Requires-Dist: importlib_resources
18
+ Dynamic: license-file
19
+
20
+ [Documentation here](https://i2mint.github.io/http2py/)
21
+
22
+ `the_one_where_we_have_an_http_service_that_we_want_call_through_normal_looking_python_functions`
23
+
24
+ # (Http) Requests for humans
25
+
26
+ Tools to create python binders to http web services.
27
+ Here, we develop python data access to web content through http request such as web services or web pages.
28
+
29
+ The story is as follows: You found something on the web that you'd like to interact with.
30
+
31
+ * This could be data you'd like to have access to,
32
+ * or some actions you'd like to perform -- such as search, posting an article, buying some bitcoin, etc..
33
+ * If you're really lucky, someone's already written a python layer to do what you want to do.
34
+ * If you're just lucky, the resource comes with a clear interface definition that makes it
35
+ easy for you to write your own python for.
36
+ * If you're less lucky, you'll have to decipher some bad documentation of the API,
37
+ or even write your own bots, crawlers and parsers to get what you want.
38
+
39
+ We want to make that all easier for you.
40
+
41
+ What we'll focus on for now is the fundamental common aspect of making a python layer to the http requests.
42
+
43
+ "But... the excellent [`requests` library](https://requests.readthedocs.io/en/master/)
44
+ already provides the python wrapper to http!" you say. Yes, and we thank the `requests` developers for that.
45
+ It is indeed an excellent package that we won't have to write now!
46
+
47
+ We'd like to build from there, cover more annoying boilerplate, and get you closer, faster,
48
+ to the functionality you actually need:
49
+ Functions with the python types you're using in your code,
50
+ that responds with python objects you can directly use in your code.
51
+
52
+ The development of `requests`
53
+ (including the [recommended add-ons](https://requests.readthedocs.io/en/master/community/recommended/))
54
+ could be said to be facing the metal (raw http mechanics) and building interfaces with humans in mind.
55
+ By contrast, we want to face the human and build adapters to communicate with the metal.
56
+ That is, in our case, the fact that http is under the hood shouldn't even be visible (unless it needs to).
57
+
58
+ It's in honor of the inspiration of "designing for humans" that we extend requests' tag-line,
59
+ to "requests for humans".
60
+
61
+ # The approach
62
+
63
+ As always, we favor expressiveness, boilerplate minimization, separation of concerns, and a layered approach that
64
+ incrementally transforms and enhance to get us from where we are to where we want to be.
65
+
66
+ So for py2request, where are we and where do we want to be?
67
+
68
+ **What we have**: We have some http accessible resources. For instance, a perfectly well structured API
69
+ specification of a REST webservice, a very messy website, or anything in between.
70
+
71
+ **What we'd like**: A Python interface to these web stuff.
72
+ That is, we want to talk python to the resources, and have it talk python back to us.
73
+ Further, we want this conversation to be to the point; we don't want to have to say more than the exact necessary,
74
+ and don't want responses containing anything but exactly what we want and how we want it.
75
+ Finally, we'd like to get this with minimum effort -- letting default choices be made for us, but being able to make
76
+ our own choices if we want.
77
+
78
+ Is that too much to ask?
79
+
80
+ In the layered approach we'll go from function specifications, to functions returning python
81
+ Request objects, which we can then wrap further to create functions actually executing the request,
82
+ getting back a Response object, and wrap further to get the final python output type.
83
+
84
+ So the objects in this pipeline are:
85
+ * Specification
86
+ * Request
87
+ * Response
88
+ * Final python object
@@ -6,12 +6,12 @@ from http2py.client import HttpClient
6
6
  from datetime import datetime, timezone
7
7
  from setuptools import sandbox
8
8
 
9
- OUTPUT_DIR = os.path.join(os.environ['HOME'], 'http2py', 'api_pkgs')
9
+ OUTPUT_DIR = os.path.join(os.environ["HOME"], "http2py", "api_pkgs")
10
10
 
11
- INIT_FILE_TPL = '''from .funcs import {funcs}
12
- '''
11
+ INIT_FILE_TPL = """from .funcs import {funcs}
12
+ """
13
13
 
14
- FUNCS_FILE_TPL = '''from i2 import Sig
14
+ FUNCS_FILE_TPL = """from i2 import Sig
15
15
  from http2py.client import HttpClient
16
16
 
17
17
  api = HttpClient(openapi_spec={openapi_spec}, url={openapi_url})
@@ -22,21 +22,21 @@ def apply_sig(meth):
22
22
  sig = Sig(meth) - 'self'
23
23
  return sig(func)
24
24
  return wrapper
25
- {funcs}'''
25
+ {funcs}"""
26
26
 
27
- FUNC_TPL = '''
27
+ FUNC_TPL = """
28
28
 
29
29
  @apply_sig(api.{func_name})
30
30
  def {func_name}(*args, **kwargs):
31
31
  return api.{func_name}(*args, **kwargs)
32
- '''
32
+ """
33
33
 
34
- SETUP_PY = '''from setuptools import setup
34
+ SETUP_PY = """from setuptools import setup
35
35
 
36
36
  setup()
37
- '''
37
+ """
38
38
 
39
- SETUP_CFG_TPL = '''[metadata]
39
+ SETUP_CFG_TPL = """[metadata]
40
40
  name = {name}
41
41
  version = {version}
42
42
  platforms = any
@@ -51,7 +51,7 @@ zip_safe = False
51
51
  install_requires =
52
52
  i2
53
53
  http2py
54
- '''
54
+ """
55
55
 
56
56
 
57
57
  def mk_api_pkg(
@@ -94,55 +94,55 @@ def mk_api_pkg(
94
94
  dt = datetime.now(timezone.utc)
95
95
  utc_time = dt.replace(tzinfo=timezone.utc)
96
96
  utc_timestamp = int(utc_time.timestamp())
97
- return f'apipkg{utc_timestamp}'
97
+ return f"apipkg{utc_timestamp}"
98
98
 
99
99
  def create_file(filepath, content):
100
- with open(filepath, 'w') as file:
100
+ with open(filepath, "w") as file:
101
101
  print(content, file=file)
102
102
 
103
103
  tempdir = tempfile.mkdtemp()
104
104
  try:
105
105
  api = HttpClient(openapi_spec=openapi_spec, url=openapi_url)
106
- paths_spec = api.openapi_spec.get('paths')
106
+ paths_spec = api.openapi_spec.get("paths")
107
107
  if not paths_spec:
108
- raise RuntimeError('The API is empty')
108
+ raise RuntimeError("The API is empty")
109
109
  func_names = [path[1:] for path in paths_spec]
110
110
  funcs_code = [FUNC_TPL.format(func_name=func_name) for func_name in func_names]
111
111
  funcs_file_code = FUNCS_FILE_TPL.format(
112
112
  openapi_spec=mk_str_element(openapi_spec),
113
113
  openapi_url=mk_str_element(openapi_url),
114
- funcs=''.join(funcs_code),
114
+ funcs="".join(funcs_code),
115
115
  )
116
- init_file_code = INIT_FILE_TPL.format(funcs=', '.join(func_names))
116
+ init_file_code = INIT_FILE_TPL.format(funcs=", ".join(func_names))
117
117
  pkg_name = pkg_name or mk_pkg_name()
118
118
  module_dir = os.path.join(tempdir, pkg_name)
119
119
  os.makedirs(module_dir)
120
120
  create_file(
121
- filepath=os.path.join(module_dir, f'__init__.py'), content=init_file_code
121
+ filepath=os.path.join(module_dir, f"__init__.py"), content=init_file_code
122
122
  )
123
123
  create_file(
124
- filepath=os.path.join(module_dir, f'funcs.py'), content=funcs_file_code
124
+ filepath=os.path.join(module_dir, f"funcs.py"), content=funcs_file_code
125
125
  )
126
- server_url = api.openapi_spec['servers'][0]['url']
127
- pkg_version = pkg_version or '1.0.0'
126
+ server_url = api.openapi_spec["servers"][0]["url"]
127
+ pkg_version = pkg_version or "1.0.0"
128
128
  setup_cfg_content = SETUP_CFG_TPL.format(
129
129
  name=pkg_name,
130
130
  version=pkg_version,
131
131
  description=pkg_description
132
- or f'A client API to consume the webservices exposed at {server_url}',
133
- author=pkg_author or 'API Package Maker',
134
- license=pkg_license or 'Apache',
132
+ or f"A client API to consume the webservices exposed at {server_url}",
133
+ author=pkg_author or "API Package Maker",
134
+ license=pkg_license or "Apache",
135
135
  )
136
136
  create_file(
137
- filepath=os.path.join(tempdir, f'setup.cfg'), content=setup_cfg_content
137
+ filepath=os.path.join(tempdir, f"setup.cfg"), content=setup_cfg_content
138
138
  )
139
- create_file(filepath=os.path.join(tempdir, f'setup.py'), content=SETUP_PY)
139
+ create_file(filepath=os.path.join(tempdir, f"setup.py"), content=SETUP_PY)
140
140
  os.chdir(tempdir)
141
- sandbox.run_setup('setup.py', ['sdist'])
142
- pkg_filename = f'{pkg_name}-{pkg_version}.tar.gz'
141
+ sandbox.run_setup("setup.py", ["sdist"])
142
+ pkg_filename = f"{pkg_name}-{pkg_version}.tar.gz"
143
143
  if not os.path.exists(OUTPUT_DIR):
144
144
  os.makedirs(OUTPUT_DIR)
145
- shutil.copy(src=os.path.join(tempdir, 'dist', pkg_filename), dst=OUTPUT_DIR)
145
+ shutil.copy(src=os.path.join(tempdir, "dist", pkg_filename), dst=OUTPUT_DIR)
146
146
 
147
147
  return os.path.join(OUTPUT_DIR, pkg_filename)
148
148
 
@@ -154,5 +154,5 @@ def main():
154
154
  argh.dispatch_command(mk_api_pkg)
155
155
 
156
156
 
157
- if __name__ == '__main__':
157
+ if __name__ == "__main__":
158
158
  main()
@@ -1,29 +1,29 @@
1
1
  import json
2
2
  import os
3
3
  from pathlib import Path
4
- from typing import Iterable
4
+ from collections.abc import Iterable
5
5
 
6
- DFLT_CONFIG_FILENAME = os.path.join(Path.home(), '.http2py', 'credentials.json')
6
+ DFLT_CONFIG_FILENAME = os.path.join(Path.home(), ".http2py", "credentials.json")
7
7
 
8
8
 
9
9
  def mk_auth(
10
10
  auth_kwargs: dict,
11
11
  expected_auth_kwargs: Iterable[str],
12
12
  config_filename=DFLT_CONFIG_FILENAME,
13
- profile: str = '',
13
+ profile: str = "",
14
14
  ):
15
15
  output = {}
16
16
  stored_auth = {}
17
17
  for kwarg in expected_auth_kwargs:
18
- if not auth_kwargs.get(kwarg, ''):
18
+ if not auth_kwargs.get(kwarg, ""):
19
19
  if not stored_auth:
20
20
  with open(config_filename) as fp:
21
21
  stored_config = json.load(fp)
22
22
  if profile and profile in stored_config:
23
23
  stored_auth = stored_config[profile]
24
- elif 'default' in stored_config:
25
- stored_auth = stored_config['default']
24
+ elif "default" in stored_config:
25
+ stored_auth = stored_config["default"]
26
26
  else:
27
27
  stored_auth = stored_config
28
- output[kwarg] = stored_auth.get(kwarg, '')
28
+ output[kwarg] = stored_auth.get(kwarg, "")
29
29
  return dict(auth_kwargs, **output)
@@ -6,7 +6,7 @@ import json
6
6
  import os
7
7
  from pathlib import Path
8
8
  import requests
9
- from typing import Callable, Iterable
9
+ from collections.abc import Callable, Iterable
10
10
  import yaml
11
11
 
12
12
  from i2.io_trans import JSONAnnotAndDfltIoTrans
@@ -19,16 +19,17 @@ def mk_sig_argparse_friendly(sig):
19
19
  """Modifies a signature to change all leading underscores in param names
20
20
  to trailing underscores, to ensure compatibility with argparse"""
21
21
  for argname, argspec in sig.parameters.items():
22
- if argname.startswith('_'):
23
- argname = argname[1:] + '_'
22
+ if argname.startswith("_"):
23
+ argname = argname[1:] + "_"
24
24
  argspec = argspec.replace(name=argname)
25
- assert argname not in sig.names, f'{argname} was already in {sig.names}'
25
+ assert argname not in sig.names, f"{argname} was already in {sig.names}"
26
26
  yield argname, argspec
27
27
 
28
28
 
29
29
  def mk_argparse_friendly(func):
30
30
  """Wraps a function to expose a signature that is compatible with argparse by stripping leading
31
- underscores from all keyword arguments, but does not mutate the signature of the original function"""
31
+ underscores from all keyword arguments, but does not mutate the signature of the original function
32
+ """
32
33
  orig_sig = Sig(func)
33
34
  new_params = dict(mk_sig_argparse_friendly(orig_sig))
34
35
  new_sig = orig_sig.replace(
@@ -40,7 +41,7 @@ def mk_argparse_friendly(func):
40
41
  mapped_kwargs = {}
41
42
  for argname, argvalue in kwargs.items():
42
43
  if argname not in [param.name for param in orig_sig.params]:
43
- mapped_argname = '_' + argname[:-1]
44
+ mapped_argname = "_" + argname[:-1]
44
45
  else:
45
46
  mapped_argname = argname
46
47
  mapped_kwargs[mapped_argname] = argvalue
@@ -52,9 +53,9 @@ def mk_argparse_friendly(func):
52
53
 
53
54
 
54
55
  def mk_cli(
55
- openapi_spec: dict = '',
56
- url: str = '',
57
- filename: str = '',
56
+ openapi_spec: dict = "",
57
+ url: str = "",
58
+ filename: str = "",
58
59
  parse_yaml: bool = False,
59
60
  config_filename: str = DFLT_CONFIG_FILENAME,
60
61
  ):
@@ -75,32 +76,32 @@ def mk_cli(
75
76
  with open(filename) as fp:
76
77
  raw_spec = fp.read()
77
78
  else:
78
- raise ValueError('You must provide an OpenAPI spec dict, url, or filename.')
79
+ raise ValueError("You must provide an OpenAPI spec dict, url, or filename.")
79
80
  if raw_spec:
80
81
  if parse_yaml:
81
82
  openapi_spec = yaml.safe_load(raw_spec)
82
83
  else:
83
84
  openapi_spec = json.loads(raw_spec)
84
85
  else:
85
- raise ValueError('No valid OpenAPI spec found.')
86
- security = openapi_spec.get('security', None)
86
+ raise ValueError("No valid OpenAPI spec found.")
87
+ security = openapi_spec.get("security", None)
87
88
  expected_auth_kwargs = []
88
89
  if security:
89
90
  auth_type = list(security.keys())[0]
90
- if auth_type == 'apiKey':
91
- expected_auth_kwargs = ['api_key']
92
- elif auth_type == 'bearerAuth':
91
+ if auth_type == "apiKey":
92
+ expected_auth_kwargs = ["api_key"]
93
+ elif auth_type == "bearerAuth":
93
94
  login_details = glom(
94
95
  openapi_spec,
95
- 'components.securitySchemes.bearerAuth.x-login',
96
+ "components.securitySchemes.bearerAuth.x-login",
96
97
  default={},
97
98
  )
98
- expected_auth_kwargs = login_details.get('login_inputs', [])
99
+ expected_auth_kwargs = login_details.get("login_inputs", [])
99
100
  client_details = HttpClient(openapi_spec)
100
101
  cli_methods = [
101
102
  register_cli_method(openapi_spec, method, expected_auth_kwargs)
102
103
  for methodname, method in client_details.__dict__.items()
103
- if getattr(method, 'method_spec', None)
104
+ if getattr(method, "method_spec", None)
104
105
  ]
105
106
  parser = argh.ArghParser()
106
107
  parser.add_commands(cli_methods)
@@ -121,7 +122,7 @@ def register_cli_method(
121
122
  client_method: Callable,
122
123
  expected_auth_kwargs: Iterable[str] = None,
123
124
  config_filename: str = DFLT_CONFIG_FILENAME,
124
- profile: str = '',
125
+ profile: str = "",
125
126
  ):
126
127
  """Creates a CLI-friendly function to instantiate an HttpClient with appropriate authentication
127
128
  arguments and call a particular method of the client instance
@@ -141,15 +142,15 @@ def register_cli_method(
141
142
  method_sig = method_sig.merge_with_sig(
142
143
  [
143
144
  *[
144
- {'name': kwarg, 'kind': KO, 'default': ''}
145
+ {"name": kwarg, "kind": KO, "default": ""}
145
146
  for kwarg in expected_auth_kwargs
146
147
  ],
147
- {'name': 'config', 'kind': KO, 'default': config_filename},
148
+ {"name": "config", "kind": KO, "default": config_filename},
148
149
  ]
149
150
  )
150
151
 
151
152
  def cli_method(*args, **kwargs):
152
- config_filename = kwargs.pop('config')
153
+ config_filename = kwargs.pop("config")
153
154
  auth_kwargs = {key: kwargs.pop(key) for key in expected_auth_kwargs}
154
155
  auth_kwargs = mk_auth(
155
156
  auth_kwargs, expected_auth_kwargs, config_filename, profile
@@ -16,11 +16,11 @@ class HttpClient:
16
16
  defined with an OpenAPI spec.
17
17
  """
18
18
 
19
- auth_type = ''
19
+ auth_type = ""
20
20
  login_input_keys = []
21
- login_url = ''
21
+ login_url = ""
22
22
  openapi_spec = {}
23
- refresh_url = ''
23
+ refresh_url = ""
24
24
  refresh_input_keys = []
25
25
  session = None
26
26
 
@@ -53,63 +53,64 @@ class HttpClient:
53
53
  if url and not openapi_spec:
54
54
  openapi_spec = get(url, verify=self.verify_cert).json()
55
55
  self.openapi_spec = openapi_spec
56
- server_info = openapi_spec['info']
57
- self.title = server_info['title']
58
- self.version = server_info['version']
56
+ server_info = openapi_spec["info"]
57
+ self.title = server_info["title"]
58
+ self.version = server_info["version"]
59
59
  if base_url:
60
60
  self.base_url = base_url
61
61
  else:
62
- self.base_url = glom(openapi_spec, 'servers.0.url')
63
- security = openapi_spec.get('security', None)
62
+ self.base_url = glom(openapi_spec, "servers.0.url")
63
+ security = openapi_spec.get("security", None)
64
64
  self.session = Session()
65
65
  if security:
66
66
  self.init_security(openapi_spec, **auth_kwargs)
67
67
  if not session_state:
68
68
  session_state = get_global_state(
69
- 'session_state', {'session': self.session, 'refresh_inputs': {}},
69
+ "session_state",
70
+ {"session": self.session, "refresh_inputs": {}},
70
71
  )
71
- self.session = session_state.get('session')
72
+ self.session = session_state.get("session")
72
73
  if not self.session:
73
- raise ValueError('No session provided when instantiating HttpClient')
74
- self.refresh_inputs = session_state.get('refresh_inputs')
74
+ raise ValueError("No session provided when instantiating HttpClient")
75
+ self.refresh_inputs = session_state.get("refresh_inputs")
75
76
  if self.refresh_inputs is None:
76
- raise ValueError('No refresh inputs provided when instantiating HttpClient')
77
- for pathname, path_spec in openapi_spec['paths'].items():
77
+ raise ValueError("No refresh inputs provided when instantiating HttpClient")
78
+ for pathname, path_spec in openapi_spec["paths"].items():
78
79
  url_template = self.base_url + pathname
79
80
  for http_method, openapi_method_spec in path_spec.items():
80
81
  self.register_method(url_template, http_method, openapi_method_spec)
81
82
 
82
83
  def init_security(self, openapi_spec, **auth_kwargs):
83
- security = openapi_spec['security']
84
+ security = openapi_spec["security"]
84
85
  auth_type = list(security.keys())[0]
85
- if auth_type == 'apiKey':
86
- self.auth_type = 'api_key'
87
- api_key = auth_kwargs.get('api_key', None)
88
- self.set_header({'Authorization': api_key})
89
- elif auth_type == 'bearerAuth':
90
- self.auth_type = 'login'
86
+ if auth_type == "apiKey":
87
+ self.auth_type = "api_key"
88
+ api_key = auth_kwargs.get("api_key", None)
89
+ self.set_header({"Authorization": api_key})
90
+ elif auth_type == "bearerAuth":
91
+ self.auth_type = "login"
91
92
  login_details = glom(
92
93
  openapi_spec,
93
- 'components.securitySchemes.bearerAuth.x-login',
94
+ "components.securitySchemes.bearerAuth.x-login",
94
95
  default={},
95
96
  )
96
- self.login_url = login_details.get('login_url', None)
97
- self.login_input_keys = login_details.get('login_inputs', [])
97
+ self.login_url = login_details.get("login_url", None)
98
+ self.login_input_keys = login_details.get("login_inputs", [])
98
99
  self.login_args = {}
99
100
  for key in auth_kwargs:
100
101
  if key in self.login_input_keys:
101
102
  self.login_args[key] = auth_kwargs[key] or False
102
- self.refresh_input_keys = login_details.get('refresh_inputs', [])
103
- self.login_response_keys = login_details.get('outputs', [])
104
- jwt = auth_kwargs.get('jwt', None)
103
+ self.refresh_input_keys = login_details.get("refresh_inputs", [])
104
+ self.login_response_keys = login_details.get("outputs", [])
105
+ jwt = auth_kwargs.get("jwt", None)
105
106
  if jwt:
106
- self.set_header({'Authorization': f'Bearer {jwt}'})
107
+ self.set_header({"Authorization": f"Bearer {jwt}"})
107
108
 
108
109
  def register_method(self, url_template, http_method, openapi_method_spec):
109
110
  content_type = None
110
- if 'requestBody' in openapi_method_spec:
111
+ if "requestBody" in openapi_method_spec:
111
112
  content_type = next(
112
- iter(glom(openapi_method_spec, 'requestBody.content').keys())
113
+ iter(glom(openapi_method_spec, "requestBody.content").keys())
113
114
  )
114
115
  method_spec = mk_method_spec_from_openapi_method_spec(
115
116
  openapi_method_spec,
@@ -133,42 +134,42 @@ class HttpClient:
133
134
  self.session.headers.update(header)
134
135
 
135
136
  def ensure_login(self):
136
- if self.auth_type != 'login':
137
+ if self.auth_type != "login":
137
138
  return True
138
- if not self.session.headers.get('Authorization', None):
139
+ if not self.session.headers.get("Authorization", None):
139
140
  return self._login()
140
141
  return True
141
142
 
142
143
  def _login(self):
143
144
  if not self.login_url:
144
145
  raise ValueError(
145
- 'Login was called without a login url. '
146
- 'Check your initialization arguments for HttpClient.'
146
+ "Login was called without a login url. "
147
+ "Check your initialization arguments for HttpClient."
147
148
  )
148
149
  if not self.login_args:
149
150
  raise ValueError(
150
- 'Login was called without any login inputs. '
151
- 'Check your initialization arguments for HttpClient.'
151
+ "Login was called without any login inputs. "
152
+ "Check your initialization arguments for HttpClient."
152
153
  )
153
- login_result = request('post', self.login_url, json=self.login_args).json()
154
+ login_result = request("post", self.login_url, json=self.login_args).json()
154
155
  return self.receive_login(login_result)
155
156
 
156
157
  def refresh_login(self):
157
158
  if not self.refresh_url or not self.refresh_inputs:
158
159
  return self._login()
159
160
  refresh_result = request(
160
- 'post', self.refresh_url, json=self.refresh_inputs
161
+ "post", self.refresh_url, json=self.refresh_inputs
161
162
  ).json()
162
163
  return self.receive_login(refresh_result)
163
164
 
164
165
  def receive_login(self, login_result):
165
- error = login_result.get('error', None)
166
+ error = login_result.get("error", None)
166
167
  if error:
167
168
  raise AuthorizationError(error)
168
169
  for key in self.login_response_keys:
169
170
  value = login_result.get(key, None)
170
- if key == 'jwt':
171
- auth_header = {'Authorization': f'Bearer {value}'}
171
+ if key == "jwt":
172
+ auth_header = {"Authorization": f"Bearer {value}"}
172
173
  self.set_header(auth_header)
173
174
  elif key in self.refresh_input_keys:
174
175
  self.refresh_inputs[key] = value
@@ -177,7 +178,7 @@ class HttpClient:
177
178
  new_auth = mk_auth({}, self.login_input_keys, config, profile)
178
179
  if not new_auth:
179
180
  raise KeyError(
180
- f'Could not find authentication credentials for profile {profile} in file {config}'
181
+ f"Could not find authentication credentials for profile {profile} in file {config}"
181
182
  )
182
183
  for key in new_auth:
183
184
  if key in self.login_input_keys:
@@ -186,4 +187,4 @@ class HttpClient:
186
187
 
187
188
  def is_request_method(self, methodname):
188
189
  method = getattr(self, methodname)
189
- return method and hasattr(method, 'method_spec')
190
+ return method and hasattr(method, "method_spec")
@@ -0,0 +1,4 @@
1
+ JSON_CONTENT_TYPE = "application/json"
2
+ BINARY_CONTENT_TYPE = "application/octet-stream"
3
+ FORM_CONTENT_TYPE = "multipart/form-data"
4
+ RAW_CONTENT_TYPE = "text/plain"
@@ -13,11 +13,11 @@ from http2py.constants import BINARY_CONTENT_TYPE, JSON_CONTENT_TYPE, RAW_CONTEN
13
13
 
14
14
  def _handle_error(resp):
15
15
  if resp.status_code == 400:
16
- if resp.reason == 'AuthorizationError':
16
+ if resp.reason == "AuthorizationError":
17
17
  raise AuthorizationError(resp.text)
18
- if resp.reason == 'InputError':
18
+ if resp.reason == "InputError":
19
19
  raise InputError(resp.text)
20
- if resp.reason == 'DuplicateRecordError':
20
+ if resp.reason == "DuplicateRecordError":
21
21
  raise DuplicateRecordError(resp.text)
22
22
  if resp.status_code == 403:
23
23
  raise ForbiddenError(resp.text)
@@ -37,7 +37,7 @@ def _handle_resp(func, content_type):
37
37
  output = resp.text
38
38
  else:
39
39
  raise NotImplementedError(
40
- f'Response of type {content_type} is not supported yet.'
40
+ f"Response of type {content_type} is not supported yet."
41
41
  )
42
42
  return func(output)
43
43
  else:
@@ -1,16 +1,16 @@
1
1
  import argh
2
- from typing import Iterable
2
+ from collections.abc import Iterable
3
3
 
4
4
  from http2py.cli_maker import mk_argparse_friendly
5
5
  from i2.io_trans import AnnotAndDfltIoTrans
6
6
 
7
7
 
8
8
  def myfun(a: int, b: Iterable[int]):
9
- print(f'a: {a} {type(a)}')
10
- print(f'b: {b} {type(b)}')
9
+ print(f"a: {a} {type(a)}")
10
+ print(f"b: {b} {type(b)}")
11
11
 
12
12
 
13
- if __name__ == '__main__':
13
+ if __name__ == "__main__":
14
14
  parser = argh.ArghParser()
15
15
  parser.add_commands([mk_argparse_friendly(AnnotAndDfltIoTrans()(myfun))])
16
16
  parser.dispatch()