sumo-wrapper-python 1.0.8__tar.gz → 1.0.9__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.

Potentially problematic release.


This version of sumo-wrapper-python might be problematic. Click here for more details.

Files changed (47) hide show
  1. sumo_wrapper_python-1.0.9/.github/workflows/build_docs.yaml +34 -0
  2. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.github/workflows/linting.yml +4 -4
  3. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.github/workflows/publish_release.yml +2 -2
  4. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.github/workflows/pytest.yml +3 -3
  5. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.gitignore +0 -1
  6. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/PKG-INFO +9 -154
  7. sumo_wrapper_python-1.0.9/README.md +10 -0
  8. sumo_wrapper_python-1.0.9/docs/_static/equinor-logo.png +0 -0
  9. sumo_wrapper_python-1.0.9/docs/_static/equinor-logo2.jpg +0 -0
  10. sumo_wrapper_python-1.0.9/docs/_static/equinor_logo.jpg +0 -0
  11. sumo_wrapper_python-1.0.9/docs/_static/equinor_logo_only.jpg +0 -0
  12. sumo_wrapper_python-1.0.9/docs/_templates/layout.html +17 -0
  13. sumo_wrapper_python-1.0.9/docs/api.rst +9 -0
  14. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/docs/conf.py +14 -12
  15. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/docs/index.rst +5 -14
  16. sumo_wrapper_python-1.0.9/docs/sumo-wrapper-python.rst +129 -0
  17. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/pyproject.toml +7 -1
  18. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_auth_provider.py +60 -20
  19. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_decorators.py +6 -0
  20. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_logging.py +3 -0
  21. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_version.py +2 -2
  22. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/login.py +5 -0
  23. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/sumo_client.py +21 -0
  24. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/PKG-INFO +9 -154
  25. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/SOURCES.txt +8 -0
  26. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/requires.txt +3 -0
  27. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/tests/testdata/surface.yml +4 -1
  28. sumo-wrapper-python-1.0.8/README.md +0 -158
  29. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.flake8 +0 -0
  30. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/.readthedocs.yaml +0 -0
  31. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/CONTRIBUTING.md +0 -0
  32. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/LICENSE +0 -0
  33. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/SECURITY.md +0 -0
  34. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/conftest.py +0 -0
  35. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/docs/Makefile +0 -0
  36. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/docs/make.bat +0 -0
  37. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/setup.cfg +0 -0
  38. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/__init__.py +0 -0
  39. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/__init__.py +0 -0
  40. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_blob_client.py +0 -0
  41. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/_retry_strategy.py +0 -0
  42. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo/wrapper/config.py +0 -0
  43. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/dependency_links.txt +0 -0
  44. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/entry_points.txt +0 -0
  45. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/src/sumo_wrapper_python.egg-info/top_level.txt +0 -0
  46. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/tests/test_sumo_thin_client.py +0 -0
  47. {sumo-wrapper-python-1.0.8 → sumo_wrapper_python-1.0.9}/tests/testdata/case.yml +0 -0
@@ -0,0 +1,34 @@
1
+ # Build the docs here, to show errors.
2
+ # The actual deployment of docs is configured in ReadTheDocs.org.
3
+
4
+ name: Build and deploy docs
5
+
6
+ on:
7
+ pull_request:
8
+ branches: [main]
9
+ push:
10
+ branches: [main]
11
+
12
+ jobs:
13
+ build_pywheels:
14
+ name: Build docs with Python ${{ matrix.python-version }} on ${{ matrix.os }}
15
+ runs-on: ${{ matrix.os }}
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.10"]
19
+ os: [ubuntu-latest]
20
+
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+
29
+ - name: Install and build docs
30
+ run: |
31
+ pip install pip -U && pip install wheel -U
32
+ pip install .[docs]
33
+ pip list
34
+ sphinx-build docs build/sphinx/html
@@ -11,9 +11,9 @@ jobs:
11
11
  matrix:
12
12
  python-version: ["3.8"]
13
13
  steps:
14
- - uses: actions/checkout@v3
14
+ - uses: actions/checkout@v4
15
15
  - name: Set up Python ${{ matrix.python-version }}
16
- uses: actions/setup-python@v3
16
+ uses: actions/setup-python@v5
17
17
  with:
18
18
  python-version: ${{ matrix.python-version }}
19
19
  - uses: psf/black@stable
@@ -26,9 +26,9 @@ jobs:
26
26
  matrix:
27
27
  python-version: ["3.8"]
28
28
  steps:
29
- - uses: actions/checkout@v3
29
+ - uses: actions/checkout@v4
30
30
  - name: Set up Python ${{ matrix.python-version }}
31
- uses: actions/setup-python@v3
31
+ uses: actions/setup-python@v5
32
32
  with:
33
33
  python-version: ${{ matrix.python-version }}
34
34
  - name: Install dependencies
@@ -13,9 +13,9 @@ jobs:
13
13
  environment: production
14
14
  runs-on: ubuntu-latest
15
15
  steps:
16
- - uses: actions/checkout@v3
16
+ - uses: actions/checkout@v4
17
17
 
18
- - uses: actions/setup-python@v4
18
+ - uses: actions/setup-python@v5
19
19
  with:
20
20
  python-version: "3.8"
21
21
 
@@ -21,17 +21,17 @@ jobs:
21
21
  id-token: write
22
22
 
23
23
  steps:
24
- - uses: actions/checkout@v3
24
+ - uses: actions/checkout@v4
25
25
 
26
26
  - name: Azure Login
27
- uses: Azure/login@v1
27
+ uses: Azure/login@v2
28
28
  with:
29
29
  client-id: f96c150d-cacf-4257-9cc9-54b2c68ec4ce
30
30
  tenant-id: 3aa4a235-b6e2-48d5-9195-7fcf05b459b0
31
31
  subscription-id: 87897772-fb27-495f-ae40-486a2df57baa
32
32
 
33
33
  - name: Set up Python
34
- uses: actions/setup-python@v3
34
+ uses: actions/setup-python@v5
35
35
  with:
36
36
  python-version: ${{ matrix.python-version }}
37
37
 
@@ -11,5 +11,4 @@ build
11
11
  .cache/
12
12
  /src/testing.py
13
13
  /docs/_build
14
- /docs/_static
15
14
  /src/sumo/wrapper/version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sumo-wrapper-python
3
- Version: 1.0.8
3
+ Version: 1.0.9
4
4
  Summary: Python wrapper for the Sumo API
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -223,162 +223,17 @@ Requires-Dist: PyYAML; extra == "test"
223
223
  Provides-Extra: docs
224
224
  Requires-Dist: sphinx; extra == "docs"
225
225
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
226
+ Requires-Dist: autoapi; extra == "docs"
227
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
228
+ Requires-Dist: sphinxcontrib-apidoc; extra == "docs"
226
229
 
227
230
  # sumo-wrapper-python
228
231
 
229
- Python wrappers for Sumo APIs
232
+ [![Documentation Status](https://readthedocs.org/projects/sumo-wrapper-python/badge/?version=latest)](https://sumo-wrapper-python.readthedocs.io/en/latest/?badge=latest)
230
233
 
231
- Want to contribute? Read our [contributing](./CONTRIBUTING.md) guidelines
234
+ ## Documentation and guidelines
235
+ [sumo-wrapper-python documentation](https://sumo-wrapper-python.readthedocs.io/en/latest/)
232
236
 
233
- ## Install:
237
+ ## Contribute
238
+ [Contribution guidelines](./CONTRIBUTING.md)
234
239
 
235
- pip install sumo-wrapper-python
236
-
237
- For internal Equinor users, this package is available through the Komodo
238
- distribution.
239
-
240
- # Table of contents
241
-
242
- - [sumo-wrapper-python](#sumo-wrapper-python)
243
- - [Install:](#install)
244
- - [Table of contents](#table-of-contents)
245
- - [SumoClient](#sumoclient)
246
- - [Initialization](#initialization)
247
- - [Parameters](#parameters)
248
- - [`token` logic](#token-logic)
249
- - [Methods](#methods)
250
- - [get(path, \*\*params)](#getpath-params)
251
- - [post(path, json, blob, params)](#postpath-json-blob-params)
252
- - [put(path, json, blob)](#putpath-json-blob)
253
- - [delete(path)](#deletepath)
254
- - [Async methods](#async-methods)
255
-
256
- # SumoClient
257
-
258
- A thin wrapper class for the Sumo API.
259
-
260
- ### Initialization
261
-
262
- ```python
263
- from sumo.wrapper import SumoClient
264
-
265
- sumo = SumoClient(env="dev")
266
- ```
267
-
268
- ### Parameters
269
-
270
- ```python
271
- class SumoClient:
272
- def __init__(
273
- self,
274
- env:str,
275
- token:str=None,
276
- interactive:bool=False,
277
- verbosity:str="CRITICAL"
278
- ):
279
- ```
280
-
281
- - `env`: sumo environment
282
- - `token`: bearer token or refresh token
283
- - `interactive`: use interactive flow when authenticating
284
- - `verbosity`: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
285
-
286
- ###### `token` logic
287
-
288
- If an access token is provided in the `token` parameter, it will be used as long
289
- as it's valid. An error will be raised when it expires.
290
-
291
- If we are unable to decode the provided `token` as a JWT, we treat it as a
292
- refresh token and attempt to use it to retrieve an access token.
293
-
294
- If no `token` is provided, an authentication code flow/interactive flow is
295
- triggered to retrieve a token.
296
-
297
- ## Methods
298
-
299
- `SumoClient` has one method for each HTTP-method that is used in the sumo-core
300
- API. See examples of how to use these methods below.
301
-
302
- All methods accepts a path argument. Path parameters can be interpolated into
303
- the path string. Example:
304
-
305
- ```python
306
- object_id = "1234"
307
-
308
- # GET/objects('{obejctid}')
309
- sumo.get(f"/objects('{object_id}')")
310
- ```
311
-
312
- ### get(path, \*\*params)
313
-
314
- Performs a GET-request to sumo-core. Accepts query parameters as keyword
315
- arguments.
316
-
317
- ```python
318
- # Retrieve userdata
319
- user_data = sumo.get("/userdata")
320
-
321
- # Search for objects
322
- results = sumo.get("/search",
323
- query="class:surface",
324
- size:3,
325
- select=["_id"]
326
- )
327
-
328
- # Get object by id
329
- object_id = "159405ba-0046-b321-55ce-542f383ba5c7"
330
-
331
- obj = sumo.get(f"/objects('{object_id}')")
332
- ```
333
-
334
- ### post(path, json, blob, params)
335
-
336
- Performs a POST-request to sumo-core. Accepts json and blob, but not both at the
337
- same time.
338
-
339
- ```python
340
- # Upload new parent object
341
- parent_object = sumo.post("/objects", json=parent_meta_data)
342
-
343
- # Upload child object
344
- parent_id = parent_object["_id"]
345
-
346
- child_object = sumo.post(f"/objects('{parent_id}')", json=child_meta_data)
347
- ```
348
-
349
- ### put(path, json, blob)
350
-
351
- Performs a PUT-request to sumo-core. Accepts json and blob, but not both at the
352
- same time.
353
-
354
- ```python
355
- # Upload blob to child object
356
- child_id = child_object["_id"]
357
-
358
- sumo.put(f"/objects('{child_id}')/blob", blob=blob)
359
- ```
360
-
361
- ### delete(path)
362
-
363
- Performs a DELETE-request to sumo-core.
364
-
365
- ```python
366
- # Delete blob
367
- sumo.delete(f"/objects('{child_id}')/blob")
368
-
369
- # Delete child object
370
- sumo.delete(f"/objects('{child_id}')")
371
-
372
- # Delete parent object
373
- sumo.delete(f"/objects('{parent_id}')")
374
- ```
375
-
376
- ## Async methods
377
-
378
- `SumoClient` also has *async* alternatives `get_async`, `post_async`, `put_async` and `delete_async`.
379
- These accept the same parameters as their synchronous counterparts, but have to be *awaited*.
380
-
381
- ```python
382
- # Retrieve userdata
383
- user_data = await sumo.get_async("/userdata")
384
- ```
@@ -0,0 +1,10 @@
1
+ # sumo-wrapper-python
2
+
3
+ [![Documentation Status](https://readthedocs.org/projects/sumo-wrapper-python/badge/?version=latest)](https://sumo-wrapper-python.readthedocs.io/en/latest/?badge=latest)
4
+
5
+ ## Documentation and guidelines
6
+ [sumo-wrapper-python documentation](https://sumo-wrapper-python.readthedocs.io/en/latest/)
7
+
8
+ ## Contribute
9
+ [Contribution guidelines](./CONTRIBUTING.md)
10
+
@@ -0,0 +1,17 @@
1
+ {% extends "!layout.html" %}
2
+ {% block footer %} {{ super() }}
3
+
4
+ <style>
5
+ /* Sidebar header (and topbar for mobile) */
6
+ .wy-side-nav-search, .wy-nav-top {
7
+ background: #ff1243;
8
+ }
9
+ /* Sidebar */
10
+ .wy-nav-side {
11
+ background: #474747;
12
+ }
13
+ .wy-side-nav-search > div.version {
14
+ color: white;
15
+ }
16
+ </style>
17
+ {% endblock %}
@@ -0,0 +1,9 @@
1
+ API
2
+ ===
3
+
4
+ The details of classes and methods
5
+
6
+ .. automodule:: sumo.wrapper.sumo_client
7
+ :members:
8
+ :undoc-members:
9
+ :show-inheritance:
@@ -13,14 +13,14 @@
13
13
  import os
14
14
  import sys
15
15
 
16
- sys.path.insert(0, os.path.abspath('../src/'))
16
+ sys.path.insert(0, os.path.abspath("../src/"))
17
17
 
18
18
 
19
19
  # -- Project information -----------------------------------------------------
20
20
 
21
- project = 'sumo-wrapper-python'
22
- copyright = '2022, Sudo Team @ Equinor'
23
- author = 'Sudo Team @ Equinor'
21
+ project = "sumo-wrapper-python"
22
+ copyright = "2024, Sudo Team @ Equinor"
23
+ author = "Sudo Team @ Equinor"
24
24
 
25
25
 
26
26
  # -- General configuration ---------------------------------------------------
@@ -28,15 +28,18 @@ author = 'Sudo Team @ Equinor'
28
28
  # Add any Sphinx extension module names here, as strings. They can be
29
29
  # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
30
30
  # ones.
31
- extensions = ['sphinx.ext.napoleon', 'sphinx.ext.autodoc']
31
+ extensions = [
32
+ "sphinx.ext.napoleon",
33
+ "sphinx.ext.autodoc",
34
+ ]
32
35
 
33
36
  # Add any paths that contain templates here, relative to this directory.
34
- templates_path = ['_templates']
37
+ templates_path = ["_templates"]
35
38
 
36
39
  # List of patterns, relative to source directory, that match files and
37
40
  # directories to ignore when looking for source files.
38
41
  # This pattern also affects html_static_path and html_extra_path.
39
- exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
42
+ exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
40
43
 
41
44
 
42
45
  # -- Options for HTML output -------------------------------------------------
@@ -44,13 +47,12 @@ exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
44
47
  # The theme to use for HTML and HTML Help pages. See the documentation for
45
48
  # a list of builtin themes.
46
49
  #
47
- html_theme = 'sphinx_rtd_theme'
50
+ html_theme = "sphinx_rtd_theme"
51
+ html_logo = "_static/equinor-logo2.jpg"
48
52
 
49
- html_theme_options = {
50
- "style_nav_header_background": "#C0C0C0",
51
- }
52
53
 
53
54
  # Add any paths that contain custom static files (such as style sheets) here,
54
55
  # relative to this directory. They are copied after the builtin static files,
55
56
  # so a file named "default.css" will overwrite the builtin "default.css".
56
- html_static_path = ['_static']
57
+ html_static_path = ["_static"]
58
+
@@ -3,22 +3,13 @@
3
3
  You can adapt this file completely to your liking, but it should at least
4
4
  contain the root `toctree` directive.
5
5
 
6
- sumo-wrapper-python
7
- ===================
6
+ Welcome to sumo-wrapper-python's documentation!
7
+ ===============================================
8
8
 
9
9
  .. toctree::
10
10
  :maxdepth: 2
11
11
  :caption: Contents:
12
12
 
13
- .. automodule:: sumo.wrapper.sumo_client
14
- :members:
15
- :undoc-members:
16
- :show-inheritance:
17
-
18
- ..
19
- Indices and tables
20
- ==================
21
-
22
- * :ref:`genindex`
23
- * :ref:`modindex`
24
- * :ref:`search`
13
+ self
14
+ sumo-wrapper-python
15
+ api
@@ -0,0 +1,129 @@
1
+ sumo-wrapper-python
2
+ ###################
3
+
4
+ Short introduction
5
+ *******************
6
+
7
+ A thin python wrapper class that can be used by Sumo client applications to
8
+ communicate with the Sumo core server. It has methods for GET, PUT, POST and DELETE,
9
+ and handles authentication and automatic network retries.
10
+
11
+ This is low-level and close to the Sumo API and primarily intended for developers.
12
+ For usage in the FMU context, the higher-level alternative
13
+ `fmu-sumo <https://fmu-sumo.readthedocs.io>`_ is recommended.
14
+
15
+ The Sumo API is described at
16
+ `https://main-sumo-prod.radix.equinor.com/swagger-ui/ <https://main-sumo-prod.radix.equinor.com/swagger-ui/>`_
17
+
18
+ The data model and schema is described at
19
+ `https://fmu-dataio.readthedocs.io/en/latest/datamodel.html <https://fmu-dataio.readthedocs.io/en/latest/datamodel.html>`_
20
+
21
+ Information on Sumo can be found `here <https://doc-sumo-doc-prod.radix.equinor.com/>`_
22
+
23
+ Preconditions
24
+ *************
25
+
26
+ Access
27
+ ------
28
+
29
+ For internal Equinor users: Apply for access to Sumo in Equinor AccessIT, search for Sumo.
30
+
31
+ Install
32
+ *******
33
+
34
+ For internal Equinor users, this package is included in the Komodo distribution.
35
+ For other use cases it can be pip installed directly from PyPI:
36
+
37
+ .. code-block::
38
+
39
+ pip install sumo-wrapper-python
40
+
41
+ Initialization
42
+ **************
43
+
44
+ .. code-block:: python
45
+
46
+ from sumo.wrapper import SumoClient
47
+
48
+ Sumo = SumoClient()
49
+
50
+ `token` logic
51
+ *************
52
+
53
+ No token provided: this will trigger
54
+ an authentication process and then handles token, token refresh and
55
+ re-authentication as automatic as possible. This would be the most common
56
+ usecase.
57
+
58
+ If an access token is provided in the `token` parameter, it will be used as long
59
+ as it's valid. An error will be raised when it expires.
60
+
61
+ If we are unable to decode the provided `token` as a JWT, we treat it as a
62
+ refresh token and attempt to use it to retrieve an access token.
63
+
64
+
65
+ Methods
66
+ *******
67
+
68
+ `SumoClient` has one method for each HTTP-method that is used in the Sumo
69
+ API: GET, PUT, POST and DELETE. In addition a method to get a blob client
70
+ which handles blob contents.
71
+
72
+ The methods accepts a path argument. A path is the path to a
73
+ Sumo `API <https://main-sumo-prod.radix.equinor.com/swagger-ui/>`_ method, for
74
+ example "/search" or "/smda/countries". Path parameters can be added into
75
+ the path string, for example
76
+
77
+ .. code-block:: python
78
+
79
+ f"/objects('{case_uuid}')/search"
80
+
81
+ The Sumo API documentation is available from the Swagger button in
82
+ the Sumo frontend, or you can use this link:
83
+ `https://main-sumo-prod.radix.equinor.com/swagger-ui/ <https://main-sumo-prod.radix.equinor.com/swagger-ui/>`_.
84
+
85
+ Async methods
86
+ *************
87
+
88
+ `SumoClient` also has *async* alternatives `get_async`, `post_async`, `put_async` and `delete_async`.
89
+ These accept the same parameters as their synchronous counterparts, but have to be *awaited*.
90
+
91
+ Usage and examples
92
+ ******************
93
+
94
+ .. code-block:: python
95
+
96
+ from sumo.wrapper import SumoClient
97
+ sumo = SumoClient()
98
+
99
+ # The line above will trigger the authentication process, and
100
+ # the behaviour depends on how long since your last login.
101
+ # It could re-use existing login or it could take you through
102
+ # a full Microsoft authentication process including
103
+ # username, password, two-factor.
104
+
105
+
106
+ # List your Sumo permissions:
107
+ print("My permissions:", sumo.get("/userpermissions").json())
108
+
109
+ # Get the first case from the list of cases you have access to:
110
+ case = sumo.get("/searchroot").json()["hits"]["hits"][0]
111
+ print("Case metadata:", case["_source"])
112
+ case_uuid = case["_source"]["fmu"]["case"]["uuid"]
113
+ print("Case uuid: ", case_uuid)
114
+
115
+ # Get the first child object:
116
+ child = sumo.get(f"/objects('{case_uuid}')/search").json()["hits"]["hits"][0]
117
+ print("Child metadata", child["_source"])
118
+ child_uuid = child["_id"]
119
+ print("Child uuid: ", child_uuid)
120
+
121
+ # Get the binary of the child
122
+ binary_object = sumo.get(f"/objects('{child_uuid}')/blob").content
123
+ print("Size of child binary object:", len(binary_object))
124
+
125
+
126
+ .. toctree::
127
+ :maxdepth: 2
128
+ :caption: Contents:
129
+
@@ -32,7 +32,13 @@ dependencies = [
32
32
 
33
33
  [project.optional-dependencies]
34
34
  test = ["pytest", "PyYAML"]
35
- docs = ["sphinx", "sphinx-rtd-theme"]
35
+ docs = [
36
+ "sphinx",
37
+ "sphinx-rtd-theme",
38
+ "autoapi",
39
+ "sphinx-autodoc-typehints",
40
+ "sphinxcontrib-apidoc",
41
+ ]
36
42
 
37
43
  [project.urls]
38
44
  Repository = "https://github.com/equinor/sumo-wrapper-python"
@@ -1,3 +1,5 @@
1
+ import platform
2
+ from pathlib import Path
1
3
  import msal
2
4
  import os
3
5
  from datetime import datetime, timedelta
@@ -34,6 +36,9 @@ class AuthProvider:
34
36
  self._resource_id = resource_id
35
37
  self._scope = scope_for_resource(resource_id)
36
38
  self._app = None
39
+ self._login_timeout_minutes = 5
40
+ os.system("") # Ensure color init on all platforms (win10)
41
+
37
42
  return
38
43
 
39
44
  @tn.retry(
@@ -57,7 +62,11 @@ class AuthProvider:
57
62
  return result["access_token"]
58
63
 
59
64
  def get_authorization(self):
60
- return {"Authorization": "Bearer " + self.get_token()}
65
+ token = self.get_token()
66
+ if token is None:
67
+ return ""
68
+
69
+ return {"Authorization": "Bearer " + token}
61
70
 
62
71
  pass
63
72
 
@@ -195,25 +204,24 @@ class AuthProviderInteractive(AuthProvider):
195
204
  )
196
205
  def login(self):
197
206
  scopes = [self._scope + " offline_access"]
198
- login_timeout_minutes = 7
199
- os.system("") # Ensure color init on all platforms (win10)
200
207
  print(
201
208
  "\n\n \033[31m NOTE! \033[0m"
202
209
  + " Please login to Equinor Azure to enable Sumo access: "
203
- + "we opened a login web-page for you in your browser."
210
+ + "we are opening a login web-page for you in your browser."
204
211
  + "\nYou should complete your login within "
205
- + str(login_timeout_minutes)
212
+ + str(self._login_timeout_minutes)
206
213
  + " minutes, "
207
214
  + "that is before "
208
215
  + str(
209
216
  (
210
- datetime.now() + timedelta(minutes=login_timeout_minutes)
217
+ datetime.now()
218
+ + timedelta(minutes=self._login_timeout_minutes)
211
219
  ).strftime("%H:%M:%S")
212
220
  )
213
221
  )
214
222
  try:
215
223
  result = self._app.acquire_token_interactive(
216
- scopes, timeout=(login_timeout_minutes * 60)
224
+ scopes, timeout=(self._login_timeout_minutes * 60)
217
225
  )
218
226
  if "error" in result:
219
227
  print(
@@ -230,7 +238,9 @@ class AuthProviderInteractive(AuthProvider):
230
238
  return
231
239
 
232
240
  protect_token_cache(self._resource_id, ".token")
233
- print("Equinor Azure login for Sumo access was successful")
241
+ print(
242
+ "Equinor Azure login for Sumo access was successful (interactive)"
243
+ )
234
244
  return
235
245
 
236
246
  pass
@@ -261,24 +271,47 @@ class AuthProviderDeviceCode(AuthProvider):
261
271
  before_sleep=_log_retry_info,
262
272
  )
263
273
  def login(self):
264
- flow = self._app.initiate_device_flow([self._scope])
265
-
266
- if "error" in flow:
267
- raise ValueError(
268
- "Failed to create device flow. Err: %s"
269
- % json.dumps(flow, indent=4)
274
+ try:
275
+ scopes = [self._scope + " offline_access"]
276
+ flow = self._app.initiate_device_flow(scopes)
277
+ if "error" in flow:
278
+ print(
279
+ "\n\n \033[31m"
280
+ + "Failed to initiate device-code login. Err: %s"
281
+ + "\033[0m" % json.dumps(flow, indent=4)
282
+ )
283
+ return
284
+ flow["expires_at"] = (
285
+ int(time.time()) + self._login_timeout_minutes * 60
270
286
  )
271
287
 
272
- print(flow["message"])
273
- result = self._app.acquire_token_by_device_flow(flow)
288
+ print(
289
+ "\033[31m"
290
+ + " NOTE! Please login to Equinor Azure to enable Sumo access:"
291
+ + flow["message"]
292
+ + " \033[0m"
293
+ + "\nYou should complete your login within a few minutes"
294
+ )
295
+ result = self._app.acquire_token_by_device_flow(flow)
274
296
 
275
- if "error" in result:
276
- raise ValueError(
277
- "Failed to acquire token by device flow. Err: %s"
278
- % json.dumps(result, indent=4)
297
+ if "error" in result:
298
+ print(
299
+ "\n\n \033[31m Error during Equinor Azure login "
300
+ "for Sumo access: \033[0m"
301
+ )
302
+ print("Err: ", json.dumps(result, indent=4))
303
+ return
304
+ except Exception:
305
+ print(
306
+ "\n\n \033[31m Failed Equinor Azure login for Sumo access, "
307
+ "one possible reason is timeout \033[0m"
279
308
  )
309
+ return
280
310
 
281
311
  protect_token_cache(self._resource_id, ".token")
312
+ print(
313
+ "Equinor Azure login for Sumo access was successful (device-code)"
314
+ )
282
315
 
283
316
  return
284
317
 
@@ -384,4 +417,11 @@ def get_auth_provider(
384
417
  ):
385
418
  return AuthProviderManaged(resource_id)
386
419
  # ELSE
420
+ lockfile_path = Path.home() / ".config/chromium/SingletonLock"
421
+ if Path(lockfile_path).is_symlink() and not str(
422
+ Path(lockfile_path).resolve()
423
+ ).__contains__(platform.node()):
424
+ # https://github.com/equinor/sumo-wrapper-python/issues/193
425
+ return AuthProviderDeviceCode(client_id, authority, resource_id)
426
+ # ELSE
387
427
  return AuthProviderInteractive(client_id, authority, resource_id)
@@ -1,4 +1,9 @@
1
+ # For sphinx:
2
+ from functools import wraps
3
+
4
+
1
5
  def raise_for_status(func):
6
+ @wraps(func)
2
7
  def wrapper(*args, **kwargs):
3
8
  # FIXME: in newer versions of httpx, raise_for_status() is chainable,
4
9
  # so we could simply write
@@ -11,6 +16,7 @@ def raise_for_status(func):
11
16
 
12
17
 
13
18
  def raise_for_status_async(func):
19
+ @wraps(func)
14
20
  async def wrapper(*args, **kwargs):
15
21
  # FIXME: in newer versions of httpx, raise_for_status() is chainable,
16
22
  # so we could simply write
@@ -20,6 +20,9 @@ class LogHandlerSumo(logging.Handler):
20
20
  "funcname": record.funcName,
21
21
  "linenumber": record.lineno,
22
22
  }
23
+ if "objectUuid" in record.__dict__.keys():
24
+ json["objectUuid"] = record.__dict__.get("objectUuid")
25
+
23
26
  self._sumoClient.post("/message-log/new", json=json)
24
27
  except Exception:
25
28
  # Never fail on logging
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '1.0.8'
16
- __version_tuple__ = version_tuple = (1, 0, 8)
15
+ __version__ = version = '1.0.9'
16
+ __version_tuple__ = version_tuple = (1, 0, 9)
@@ -74,6 +74,11 @@ def main():
74
74
  if args.print_token:
75
75
  print(f"TOKEN: {token}")
76
76
 
77
+ if token is not None:
78
+ print("Successfully logged in to Sumo environment: " + env)
79
+ else:
80
+ print("Failed login to Sumo environment: " + env)
81
+
77
82
 
78
83
  if __name__ == "__main__":
79
84
  main()
@@ -84,6 +84,27 @@ class SumoClient:
84
84
  access_token=access_token,
85
85
  devicecode=devicecode,
86
86
  )
87
+ if (
88
+ self.auth.get_token() is None
89
+ and refresh_token is None
90
+ and access_token is None
91
+ ):
92
+ print("\n \033[31m !!! Falling back to device-code login:\033[0m")
93
+ self.auth = get_auth_provider(
94
+ client_id=APP_REGISTRATION[env]["CLIENT_ID"],
95
+ authority=f"{AUTHORITY_HOST_URI}/{TENANT_ID}",
96
+ resource_id=APP_REGISTRATION[env]["RESOURCE_ID"],
97
+ interactive=False,
98
+ refresh_token=None,
99
+ access_token=None,
100
+ devicecode=True,
101
+ )
102
+ if self.auth.get_token() is None:
103
+ print(
104
+ "\n\n \033[31m "
105
+ + "NOTE! Login failed/timed out. Giving up."
106
+ + "\033[0m"
107
+ )
87
108
 
88
109
  if env == "localhost":
89
110
  self.base_url = "http://localhost:8084/api/v1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sumo-wrapper-python
3
- Version: 1.0.8
3
+ Version: 1.0.9
4
4
  Summary: Python wrapper for the Sumo API
5
5
  Author: Equinor
6
6
  License: Apache License
@@ -223,162 +223,17 @@ Requires-Dist: PyYAML; extra == "test"
223
223
  Provides-Extra: docs
224
224
  Requires-Dist: sphinx; extra == "docs"
225
225
  Requires-Dist: sphinx-rtd-theme; extra == "docs"
226
+ Requires-Dist: autoapi; extra == "docs"
227
+ Requires-Dist: sphinx-autodoc-typehints; extra == "docs"
228
+ Requires-Dist: sphinxcontrib-apidoc; extra == "docs"
226
229
 
227
230
  # sumo-wrapper-python
228
231
 
229
- Python wrappers for Sumo APIs
232
+ [![Documentation Status](https://readthedocs.org/projects/sumo-wrapper-python/badge/?version=latest)](https://sumo-wrapper-python.readthedocs.io/en/latest/?badge=latest)
230
233
 
231
- Want to contribute? Read our [contributing](./CONTRIBUTING.md) guidelines
234
+ ## Documentation and guidelines
235
+ [sumo-wrapper-python documentation](https://sumo-wrapper-python.readthedocs.io/en/latest/)
232
236
 
233
- ## Install:
237
+ ## Contribute
238
+ [Contribution guidelines](./CONTRIBUTING.md)
234
239
 
235
- pip install sumo-wrapper-python
236
-
237
- For internal Equinor users, this package is available through the Komodo
238
- distribution.
239
-
240
- # Table of contents
241
-
242
- - [sumo-wrapper-python](#sumo-wrapper-python)
243
- - [Install:](#install)
244
- - [Table of contents](#table-of-contents)
245
- - [SumoClient](#sumoclient)
246
- - [Initialization](#initialization)
247
- - [Parameters](#parameters)
248
- - [`token` logic](#token-logic)
249
- - [Methods](#methods)
250
- - [get(path, \*\*params)](#getpath-params)
251
- - [post(path, json, blob, params)](#postpath-json-blob-params)
252
- - [put(path, json, blob)](#putpath-json-blob)
253
- - [delete(path)](#deletepath)
254
- - [Async methods](#async-methods)
255
-
256
- # SumoClient
257
-
258
- A thin wrapper class for the Sumo API.
259
-
260
- ### Initialization
261
-
262
- ```python
263
- from sumo.wrapper import SumoClient
264
-
265
- sumo = SumoClient(env="dev")
266
- ```
267
-
268
- ### Parameters
269
-
270
- ```python
271
- class SumoClient:
272
- def __init__(
273
- self,
274
- env:str,
275
- token:str=None,
276
- interactive:bool=False,
277
- verbosity:str="CRITICAL"
278
- ):
279
- ```
280
-
281
- - `env`: sumo environment
282
- - `token`: bearer token or refresh token
283
- - `interactive`: use interactive flow when authenticating
284
- - `verbosity`: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
285
-
286
- ###### `token` logic
287
-
288
- If an access token is provided in the `token` parameter, it will be used as long
289
- as it's valid. An error will be raised when it expires.
290
-
291
- If we are unable to decode the provided `token` as a JWT, we treat it as a
292
- refresh token and attempt to use it to retrieve an access token.
293
-
294
- If no `token` is provided, an authentication code flow/interactive flow is
295
- triggered to retrieve a token.
296
-
297
- ## Methods
298
-
299
- `SumoClient` has one method for each HTTP-method that is used in the sumo-core
300
- API. See examples of how to use these methods below.
301
-
302
- All methods accepts a path argument. Path parameters can be interpolated into
303
- the path string. Example:
304
-
305
- ```python
306
- object_id = "1234"
307
-
308
- # GET/objects('{obejctid}')
309
- sumo.get(f"/objects('{object_id}')")
310
- ```
311
-
312
- ### get(path, \*\*params)
313
-
314
- Performs a GET-request to sumo-core. Accepts query parameters as keyword
315
- arguments.
316
-
317
- ```python
318
- # Retrieve userdata
319
- user_data = sumo.get("/userdata")
320
-
321
- # Search for objects
322
- results = sumo.get("/search",
323
- query="class:surface",
324
- size:3,
325
- select=["_id"]
326
- )
327
-
328
- # Get object by id
329
- object_id = "159405ba-0046-b321-55ce-542f383ba5c7"
330
-
331
- obj = sumo.get(f"/objects('{object_id}')")
332
- ```
333
-
334
- ### post(path, json, blob, params)
335
-
336
- Performs a POST-request to sumo-core. Accepts json and blob, but not both at the
337
- same time.
338
-
339
- ```python
340
- # Upload new parent object
341
- parent_object = sumo.post("/objects", json=parent_meta_data)
342
-
343
- # Upload child object
344
- parent_id = parent_object["_id"]
345
-
346
- child_object = sumo.post(f"/objects('{parent_id}')", json=child_meta_data)
347
- ```
348
-
349
- ### put(path, json, blob)
350
-
351
- Performs a PUT-request to sumo-core. Accepts json and blob, but not both at the
352
- same time.
353
-
354
- ```python
355
- # Upload blob to child object
356
- child_id = child_object["_id"]
357
-
358
- sumo.put(f"/objects('{child_id}')/blob", blob=blob)
359
- ```
360
-
361
- ### delete(path)
362
-
363
- Performs a DELETE-request to sumo-core.
364
-
365
- ```python
366
- # Delete blob
367
- sumo.delete(f"/objects('{child_id}')/blob")
368
-
369
- # Delete child object
370
- sumo.delete(f"/objects('{child_id}')")
371
-
372
- # Delete parent object
373
- sumo.delete(f"/objects('{parent_id}')")
374
- ```
375
-
376
- ## Async methods
377
-
378
- `SumoClient` also has *async* alternatives `get_async`, `post_async`, `put_async` and `delete_async`.
379
- These accept the same parameters as their synchronous counterparts, but have to be *awaited*.
380
-
381
- ```python
382
- # Retrieve userdata
383
- user_data = await sumo.get_async("/userdata")
384
- ```
@@ -7,13 +7,21 @@ README.md
7
7
  SECURITY.md
8
8
  conftest.py
9
9
  pyproject.toml
10
+ .github/workflows/build_docs.yaml
10
11
  .github/workflows/linting.yml
11
12
  .github/workflows/publish_release.yml
12
13
  .github/workflows/pytest.yml
13
14
  docs/Makefile
15
+ docs/api.rst
14
16
  docs/conf.py
15
17
  docs/index.rst
16
18
  docs/make.bat
19
+ docs/sumo-wrapper-python.rst
20
+ docs/_static/equinor-logo.png
21
+ docs/_static/equinor-logo2.jpg
22
+ docs/_static/equinor_logo.jpg
23
+ docs/_static/equinor_logo_only.jpg
24
+ docs/_templates/layout.html
17
25
  src/sumo/__init__.py
18
26
  src/sumo/wrapper/__init__.py
19
27
  src/sumo/wrapper/_auth_provider.py
@@ -8,6 +8,9 @@ azure-identity>=1.13.0
8
8
  [docs]
9
9
  sphinx
10
10
  sphinx-rtd-theme
11
+ autoapi
12
+ sphinx-autodoc-typehints
13
+ sphinxcontrib-apidoc
11
14
 
12
15
  [test]
13
16
  pytest
@@ -24,7 +24,10 @@ fmu:
24
24
  description:
25
25
  - detailed description
26
26
  - optional
27
-
27
+
28
+ context:
29
+ stage: "realization"
30
+
28
31
  workflow: # not sure, but a reference to the workflow / job that made this. Making it expandable.
29
32
  reference: rms/structural_model
30
33
 
@@ -1,158 +0,0 @@
1
- # sumo-wrapper-python
2
-
3
- Python wrappers for Sumo APIs
4
-
5
- Want to contribute? Read our [contributing](./CONTRIBUTING.md) guidelines
6
-
7
- ## Install:
8
-
9
- pip install sumo-wrapper-python
10
-
11
- For internal Equinor users, this package is available through the Komodo
12
- distribution.
13
-
14
- # Table of contents
15
-
16
- - [sumo-wrapper-python](#sumo-wrapper-python)
17
- - [Install:](#install)
18
- - [Table of contents](#table-of-contents)
19
- - [SumoClient](#sumoclient)
20
- - [Initialization](#initialization)
21
- - [Parameters](#parameters)
22
- - [`token` logic](#token-logic)
23
- - [Methods](#methods)
24
- - [get(path, \*\*params)](#getpath-params)
25
- - [post(path, json, blob, params)](#postpath-json-blob-params)
26
- - [put(path, json, blob)](#putpath-json-blob)
27
- - [delete(path)](#deletepath)
28
- - [Async methods](#async-methods)
29
-
30
- # SumoClient
31
-
32
- A thin wrapper class for the Sumo API.
33
-
34
- ### Initialization
35
-
36
- ```python
37
- from sumo.wrapper import SumoClient
38
-
39
- sumo = SumoClient(env="dev")
40
- ```
41
-
42
- ### Parameters
43
-
44
- ```python
45
- class SumoClient:
46
- def __init__(
47
- self,
48
- env:str,
49
- token:str=None,
50
- interactive:bool=False,
51
- verbosity:str="CRITICAL"
52
- ):
53
- ```
54
-
55
- - `env`: sumo environment
56
- - `token`: bearer token or refresh token
57
- - `interactive`: use interactive flow when authenticating
58
- - `verbosity`: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
59
-
60
- ###### `token` logic
61
-
62
- If an access token is provided in the `token` parameter, it will be used as long
63
- as it's valid. An error will be raised when it expires.
64
-
65
- If we are unable to decode the provided `token` as a JWT, we treat it as a
66
- refresh token and attempt to use it to retrieve an access token.
67
-
68
- If no `token` is provided, an authentication code flow/interactive flow is
69
- triggered to retrieve a token.
70
-
71
- ## Methods
72
-
73
- `SumoClient` has one method for each HTTP-method that is used in the sumo-core
74
- API. See examples of how to use these methods below.
75
-
76
- All methods accepts a path argument. Path parameters can be interpolated into
77
- the path string. Example:
78
-
79
- ```python
80
- object_id = "1234"
81
-
82
- # GET/objects('{obejctid}')
83
- sumo.get(f"/objects('{object_id}')")
84
- ```
85
-
86
- ### get(path, \*\*params)
87
-
88
- Performs a GET-request to sumo-core. Accepts query parameters as keyword
89
- arguments.
90
-
91
- ```python
92
- # Retrieve userdata
93
- user_data = sumo.get("/userdata")
94
-
95
- # Search for objects
96
- results = sumo.get("/search",
97
- query="class:surface",
98
- size:3,
99
- select=["_id"]
100
- )
101
-
102
- # Get object by id
103
- object_id = "159405ba-0046-b321-55ce-542f383ba5c7"
104
-
105
- obj = sumo.get(f"/objects('{object_id}')")
106
- ```
107
-
108
- ### post(path, json, blob, params)
109
-
110
- Performs a POST-request to sumo-core. Accepts json and blob, but not both at the
111
- same time.
112
-
113
- ```python
114
- # Upload new parent object
115
- parent_object = sumo.post("/objects", json=parent_meta_data)
116
-
117
- # Upload child object
118
- parent_id = parent_object["_id"]
119
-
120
- child_object = sumo.post(f"/objects('{parent_id}')", json=child_meta_data)
121
- ```
122
-
123
- ### put(path, json, blob)
124
-
125
- Performs a PUT-request to sumo-core. Accepts json and blob, but not both at the
126
- same time.
127
-
128
- ```python
129
- # Upload blob to child object
130
- child_id = child_object["_id"]
131
-
132
- sumo.put(f"/objects('{child_id}')/blob", blob=blob)
133
- ```
134
-
135
- ### delete(path)
136
-
137
- Performs a DELETE-request to sumo-core.
138
-
139
- ```python
140
- # Delete blob
141
- sumo.delete(f"/objects('{child_id}')/blob")
142
-
143
- # Delete child object
144
- sumo.delete(f"/objects('{child_id}')")
145
-
146
- # Delete parent object
147
- sumo.delete(f"/objects('{parent_id}')")
148
- ```
149
-
150
- ## Async methods
151
-
152
- `SumoClient` also has *async* alternatives `get_async`, `post_async`, `put_async` and `delete_async`.
153
- These accept the same parameters as their synchronous counterparts, but have to be *awaited*.
154
-
155
- ```python
156
- # Retrieve userdata
157
- user_data = await sumo.get_async("/userdata")
158
- ```