python-service-builder 0.1.0__tar.gz → 1.0.1__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 (23) hide show
  1. python-service-builder-1.0.1/PKG-INFO +86 -0
  2. python-service-builder-1.0.1/README.md +73 -0
  3. python-service-builder-1.0.1/pyproject.toml +43 -0
  4. python-service-builder-1.0.1/python_service_builder.egg-info/PKG-INFO +86 -0
  5. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/python_service_builder.egg-info/SOURCES.txt +0 -2
  6. python-service-builder-1.0.1/service_framework/__init__.py +115 -0
  7. python-service-builder-1.0.1/service_framework/__init_test.py +116 -0
  8. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/service_framework/list_services.py +2 -1
  9. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/service_framework/service_builder.py +19 -36
  10. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/service_framework/services.py +22 -5
  11. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/service_framework/services_test.py +4 -7
  12. python-service-builder-0.1.0/PKG-INFO +0 -19
  13. python-service-builder-0.1.0/README.md +0 -2
  14. python-service-builder-0.1.0/pyproject.toml +0 -3
  15. python-service-builder-0.1.0/python_service_builder.egg-info/PKG-INFO +0 -19
  16. python-service-builder-0.1.0/python_service_builder.egg-info/not-zip-safe +0 -1
  17. python-service-builder-0.1.0/service_framework/__init__.py +0 -67
  18. python-service-builder-0.1.0/service_framework/__init_test.py +0 -29
  19. python-service-builder-0.1.0/setup.py +0 -58
  20. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/python_service_builder.egg-info/dependency_links.txt +0 -0
  21. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/python_service_builder.egg-info/requires.txt +0 -0
  22. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/python_service_builder.egg-info/top_level.txt +0 -0
  23. {python-service-builder-0.1.0 → python-service-builder-1.0.1}/setup.cfg +0 -0
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-service-builder
3
+ Version: 1.0.1
4
+ Summary: Helper library for easier creation of Google services.
5
+ Author-email: David Harcombe <david.harcombe@gmail.com>
6
+ License: Apache 2.0
7
+ Project-URL: Homepage, https://github.com/google/python-service-builder
8
+ Project-URL: Bug Tracker, https://github.com/google/python-service-builder/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Python Library for helping to create Python services from Google APIs
15
+
16
+ Something I have to do a lot is create connections to Google Service
17
+ endpoints. This generally requires a fair amount of boiler-plate code that io
18
+ just copied and pasted from other locations. Also any time a new service is
19
+ needed, I need to find that specific implementation of the service builder and
20
+ add the new endpoint.
21
+
22
+ Obviously this can lead to potential errors and if a bug is found, I have to
23
+ remember where I created these services and fix it in many projects.
24
+
25
+ This has led me to create a small helper library to make the process easier.
26
+ Included in the library is a complete and updated (at time of publishing) list
27
+ of all the Google APIs along with their versions and endpoints as `Enum`s
28
+ ready for use.
29
+
30
+ ## Usage
31
+
32
+ `service_builder.build_service()` needs two mandatory pieces of information:
33
+
34
+ | Parameter | Description |
35
+ | :-: | - |
36
+ | key | The credentials to use to authenticate the user to the service it has to create |
37
+ | service | The Google API endpoint it is trying to connect to. |
38
+
39
+ ### `key`
40
+
41
+ These can be supplied in one of 4 ways.
42
+
43
+ 1. A valid `google.oauth2.Credentials` object, created and passed to the
44
+ service builder.
45
+ 1. An `str` which is the name of a local file containing the `json`
46
+ representation of an OAuth credentials object.
47
+ 1. A `Mapping` object which is a `json` keyfile `dict`
48
+ 2. An `OAuthKey` object from this package, which is a `dataclass` object
49
+ comprising of the minimum fields necessary to perform the OAuth connection.
50
+
51
+ ### `service`
52
+
53
+ This is an Enum in the `service_framework.service` module, which defines
54
+ the Google service you're planning to connect to. All of these services are
55
+ dynamically built, but their names match the Google-defined name.
56
+
57
+ ### Example
58
+
59
+ ```
60
+ from service_framework.services import Service
61
+ from service_framework import service_builder
62
+
63
+ key = service_builder.OAuthKey(
64
+ access_token="<YOUR ACCESS TOKEN>",
65
+ refresh_token="<YOUR REFRESH TOKEN>",
66
+ token_uri="https://oauth2.googleapis.com/token",
67
+ client_id="<YOUR CLIENT ID>",
68
+ client_secret="<YOUR CLIENT SECRET>"
69
+ )
70
+
71
+ service = service_builder.build_service(service=Service.GMAIL, key=key)
72
+ ```
73
+
74
+ Here, we are creating the key as an `OAuthKey` object as we have clearly got the
75
+ tokens and client_[id|secret] from elsewhere. Creating the service is then as
76
+ simple as invoking the `service_builder.build_service` method which will return
77
+ you a service you can use.
78
+
79
+ ### Other parameters
80
+
81
+ `service_builder.build_service()` can also take 2 other optional parameters.
82
+
83
+ | Parameter | Description |
84
+ | :-: | - |
85
+ | api_key | Some APIs require an API key as well as the OAuth credentials |
86
+ | extra_scopes | Any extra OAUth scopes required for the operation |
@@ -0,0 +1,73 @@
1
+ # Python Library for helping to create Python services from Google APIs
2
+
3
+ Something I have to do a lot is create connections to Google Service
4
+ endpoints. This generally requires a fair amount of boiler-plate code that io
5
+ just copied and pasted from other locations. Also any time a new service is
6
+ needed, I need to find that specific implementation of the service builder and
7
+ add the new endpoint.
8
+
9
+ Obviously this can lead to potential errors and if a bug is found, I have to
10
+ remember where I created these services and fix it in many projects.
11
+
12
+ This has led me to create a small helper library to make the process easier.
13
+ Included in the library is a complete and updated (at time of publishing) list
14
+ of all the Google APIs along with their versions and endpoints as `Enum`s
15
+ ready for use.
16
+
17
+ ## Usage
18
+
19
+ `service_builder.build_service()` needs two mandatory pieces of information:
20
+
21
+ | Parameter | Description |
22
+ | :-: | - |
23
+ | key | The credentials to use to authenticate the user to the service it has to create |
24
+ | service | The Google API endpoint it is trying to connect to. |
25
+
26
+ ### `key`
27
+
28
+ These can be supplied in one of 4 ways.
29
+
30
+ 1. A valid `google.oauth2.Credentials` object, created and passed to the
31
+ service builder.
32
+ 1. An `str` which is the name of a local file containing the `json`
33
+ representation of an OAuth credentials object.
34
+ 1. A `Mapping` object which is a `json` keyfile `dict`
35
+ 2. An `OAuthKey` object from this package, which is a `dataclass` object
36
+ comprising of the minimum fields necessary to perform the OAuth connection.
37
+
38
+ ### `service`
39
+
40
+ This is an Enum in the `service_framework.service` module, which defines
41
+ the Google service you're planning to connect to. All of these services are
42
+ dynamically built, but their names match the Google-defined name.
43
+
44
+ ### Example
45
+
46
+ ```
47
+ from service_framework.services import Service
48
+ from service_framework import service_builder
49
+
50
+ key = service_builder.OAuthKey(
51
+ access_token="<YOUR ACCESS TOKEN>",
52
+ refresh_token="<YOUR REFRESH TOKEN>",
53
+ token_uri="https://oauth2.googleapis.com/token",
54
+ client_id="<YOUR CLIENT ID>",
55
+ client_secret="<YOUR CLIENT SECRET>"
56
+ )
57
+
58
+ service = service_builder.build_service(service=Service.GMAIL, key=key)
59
+ ```
60
+
61
+ Here, we are creating the key as an `OAuthKey` object as we have clearly got the
62
+ tokens and client_[id|secret] from elsewhere. Creating the service is then as
63
+ simple as invoking the `service_builder.build_service` method which will return
64
+ you a service you can use.
65
+
66
+ ### Other parameters
67
+
68
+ `service_builder.build_service()` can also take 2 other optional parameters.
69
+
70
+ | Parameter | Description |
71
+ | :-: | - |
72
+ | api_key | Some APIs require an API key as well as the OAuth credentials |
73
+ | extra_scopes | Any extra OAUth scopes required for the operation |
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ['setuptools>=61.0.0', 'wheel']
3
+ build-backend = 'setuptools.build_meta'
4
+
5
+ [project]
6
+ name = "python-service-builder"
7
+ version = "1.0.1"
8
+ authors = [{ name = "David Harcombe", email = "david.harcombe@gmail.com" }]
9
+ description = "Helper library for easier creation of Google services."
10
+ readme = "README.md"
11
+ requires-python = ">=3.10"
12
+ classifiers = [
13
+ "Programming Language :: Python :: 3",
14
+ "Operating System :: OS Independent",
15
+ ]
16
+ license = { text = "Apache 2.0" }
17
+ dependencies = [
18
+ 'absl-py>=0.12.0',
19
+ 'dataclasses-json>=0.5.2',
20
+ 'gcs-oauth2-boto-plugin>=2.7',
21
+ 'google-api-core>=1.26.1',
22
+ 'google-api-python-client>=2.0.2',
23
+ 'google-auth-httplib2>=0.1.0',
24
+ 'google-auth-oauthlib>=0.4.3',
25
+ 'google-auth>=1.28.0',
26
+ 'google-reauth>=0.1.1',
27
+ 'googleapis-common-protos>=1.53.0',
28
+ 'httplib2>=0.19.0',
29
+ 'immutabledict>=2.2.1',
30
+ 'oauth2client>=4.1.3',
31
+ 'oauthlib>=3.1.0',
32
+ ]
33
+
34
+ [tool.setuptools]
35
+ include-package-data = false
36
+
37
+ [tool.setuptools.packages.find]
38
+ include = ["service_framework*"]
39
+ exclude = ["*_test.py"]
40
+
41
+ [project.urls]
42
+ "Homepage" = "https://github.com/google/python-service-builder"
43
+ "Bug Tracker" = "https://github.com/google/python-service-builder/issues"
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.1
2
+ Name: python-service-builder
3
+ Version: 1.0.1
4
+ Summary: Helper library for easier creation of Google services.
5
+ Author-email: David Harcombe <david.harcombe@gmail.com>
6
+ License: Apache 2.0
7
+ Project-URL: Homepage, https://github.com/google/python-service-builder
8
+ Project-URL: Bug Tracker, https://github.com/google/python-service-builder/issues
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Python Library for helping to create Python services from Google APIs
15
+
16
+ Something I have to do a lot is create connections to Google Service
17
+ endpoints. This generally requires a fair amount of boiler-plate code that io
18
+ just copied and pasted from other locations. Also any time a new service is
19
+ needed, I need to find that specific implementation of the service builder and
20
+ add the new endpoint.
21
+
22
+ Obviously this can lead to potential errors and if a bug is found, I have to
23
+ remember where I created these services and fix it in many projects.
24
+
25
+ This has led me to create a small helper library to make the process easier.
26
+ Included in the library is a complete and updated (at time of publishing) list
27
+ of all the Google APIs along with their versions and endpoints as `Enum`s
28
+ ready for use.
29
+
30
+ ## Usage
31
+
32
+ `service_builder.build_service()` needs two mandatory pieces of information:
33
+
34
+ | Parameter | Description |
35
+ | :-: | - |
36
+ | key | The credentials to use to authenticate the user to the service it has to create |
37
+ | service | The Google API endpoint it is trying to connect to. |
38
+
39
+ ### `key`
40
+
41
+ These can be supplied in one of 4 ways.
42
+
43
+ 1. A valid `google.oauth2.Credentials` object, created and passed to the
44
+ service builder.
45
+ 1. An `str` which is the name of a local file containing the `json`
46
+ representation of an OAuth credentials object.
47
+ 1. A `Mapping` object which is a `json` keyfile `dict`
48
+ 2. An `OAuthKey` object from this package, which is a `dataclass` object
49
+ comprising of the minimum fields necessary to perform the OAuth connection.
50
+
51
+ ### `service`
52
+
53
+ This is an Enum in the `service_framework.service` module, which defines
54
+ the Google service you're planning to connect to. All of these services are
55
+ dynamically built, but their names match the Google-defined name.
56
+
57
+ ### Example
58
+
59
+ ```
60
+ from service_framework.services import Service
61
+ from service_framework import service_builder
62
+
63
+ key = service_builder.OAuthKey(
64
+ access_token="<YOUR ACCESS TOKEN>",
65
+ refresh_token="<YOUR REFRESH TOKEN>",
66
+ token_uri="https://oauth2.googleapis.com/token",
67
+ client_id="<YOUR CLIENT ID>",
68
+ client_secret="<YOUR CLIENT SECRET>"
69
+ )
70
+
71
+ service = service_builder.build_service(service=Service.GMAIL, key=key)
72
+ ```
73
+
74
+ Here, we are creating the key as an `OAuthKey` object as we have clearly got the
75
+ tokens and client_[id|secret] from elsewhere. Creating the service is then as
76
+ simple as invoking the `service_builder.build_service` method which will return
77
+ you a service you can use.
78
+
79
+ ### Other parameters
80
+
81
+ `service_builder.build_service()` can also take 2 other optional parameters.
82
+
83
+ | Parameter | Description |
84
+ | :-: | - |
85
+ | api_key | Some APIs require an API key as well as the OAuth credentials |
86
+ | extra_scopes | Any extra OAUth scopes required for the operation |
@@ -1,10 +1,8 @@
1
1
  README.md
2
2
  pyproject.toml
3
- setup.py
4
3
  python_service_builder.egg-info/PKG-INFO
5
4
  python_service_builder.egg-info/SOURCES.txt
6
5
  python_service_builder.egg-info/dependency_links.txt
7
- python_service_builder.egg-info/not-zip-safe
8
6
  python_service_builder.egg-info/requires.txt
9
7
  python_service_builder.egg-info/top_level.txt
10
8
  service_framework/__init__.py
@@ -0,0 +1,115 @@
1
+ # Copyright 2022 David Harcombe
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import dataclasses
15
+ from typing import Any, Callable, Mapping
16
+
17
+ import dataclasses_json
18
+
19
+
20
+ def lazy_property(f: Callable):
21
+ """Decorator that makes a property lazy-evaluated.
22
+
23
+ Args:
24
+ f: the function to convert to a lazy property.
25
+ """
26
+ attr_name = '_lazy_' + f.__name__
27
+
28
+ @property
29
+ def _lazy_property(self) -> Any:
30
+ if not hasattr(self, attr_name):
31
+ setattr(self, attr_name, f(self))
32
+ return getattr(self, attr_name)
33
+ return _lazy_property
34
+
35
+
36
+ def metadata(base: Mapping[str, Any], **custom) -> Mapping[str, Any]:
37
+ """Merges properties into the metadata and returns the merged result.
38
+
39
+ Args:
40
+ base (Mapping[str, Any]): the existing metadata
41
+
42
+ Returns:
43
+ Mapping[str, Any]: the merged result
44
+ """
45
+ for key in custom:
46
+ if not custom[key] and key in base:
47
+ del base[key]
48
+
49
+ elif custom[key]:
50
+ base.update({key: custom[key]})
51
+
52
+ return base
53
+
54
+
55
+ def field(default: Any = None,
56
+ default_factory: Any = None, **kwargs) -> dataclasses.field:
57
+ """A generic dataclass field.
58
+
59
+ Only one of `default` or `default_factory` should be supplied. If both are
60
+ given, `default_factory` will take precedence.
61
+
62
+ Args:
63
+ default (Any, optional): the default field value. Defaults to None.
64
+ default_factory (Any, optional): the default field factory. Defaults to None.
65
+ **kwargs (Any...): any extra metadata args.
66
+
67
+ Returns:
68
+ dataclasses.field: the field
69
+ """
70
+ base = {
71
+ 'exclude': lambda x: not x
72
+ }
73
+
74
+ if default_factory:
75
+ f = dataclasses.field(default_factory=default_factory,
76
+ metadata=dataclasses_json.config(
77
+ **metadata(base=base, **kwargs)))
78
+ else:
79
+ f = dataclasses.field(default=default,
80
+ metadata=dataclasses_json.config(
81
+ **metadata(base=base, **kwargs)))
82
+
83
+ return f
84
+
85
+
86
+ def snake_field(default: Any = None,
87
+ default_factory: Any = None, **kwargs) -> dataclasses.field:
88
+ """Defines a generic dataclass field with a `camelCase` rendered name.
89
+
90
+ Args:
91
+ default (Any, optional): the default field value. Defaults to None.
92
+ default_factory (Any, optional): the default field factory. Defaults to None.
93
+ **kwargs (Any...): any extra metadata args.
94
+
95
+ Returns:
96
+ dataclasses.field: the field
97
+ """
98
+ return field(default=default, default_factory=default_factory,
99
+ letter_case=dataclasses_json.LetterCase.SNAKE, **kwargs)
100
+
101
+
102
+ def camel_field(default: Any = None,
103
+ default_factory: Any = None, **kwargs) -> dataclasses.field:
104
+ """Defines a generic dataclass field with a `camelCase` rendered name.
105
+
106
+ Args:
107
+ default (Any, optional): the default field value. Defaults to None.
108
+ default_factory (Any, optional): the default field factory. Defaults to None.
109
+ **kwargs (Any...): any extra metadata args.
110
+
111
+ Returns:
112
+ dataclasses.field: the field
113
+ """
114
+ return field(default=default, default_factory=default_factory,
115
+ letter_case=dataclasses_json.LetterCase.CAMEL, **kwargs)
@@ -0,0 +1,116 @@
1
+ # Copyright 2022 David Harcombe
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ import unittest
15
+
16
+ from service_framework import lazy_property, camel_field
17
+ import dataclasses
18
+ from dataclasses import Field, dataclass
19
+
20
+ from dataclasses_json import dataclass_json
21
+
22
+
23
+ class LazyPropertyTest(unittest.TestCase):
24
+ class Foo(object):
25
+ @lazy_property
26
+ def lazy_thing(self) -> str:
27
+ return 'lazy'
28
+
29
+ def test_lazy_thing(self):
30
+ foo = LazyPropertyTest.Foo()
31
+ self.assertFalse(hasattr(foo, '_lazy_lazy_thing'))
32
+ self.assertEqual('lazy', foo.lazy_thing)
33
+ self.assertTrue(hasattr(foo, '_lazy_lazy_thing'))
34
+
35
+
36
+ class CamelFieldTest(unittest.TestCase):
37
+ def test_base_camel_field(self) -> None:
38
+ @dataclass_json
39
+ @dataclass
40
+ class Base(object):
41
+ _field: str = camel_field()
42
+
43
+ base = Base()
44
+ base._field = 'foo'
45
+
46
+ f: Field = base.__dataclass_fields__.get('_field')
47
+ self.assertIsNone(f.default)
48
+ self.assertTrue(isinstance(f.default_factory, dataclasses._MISSING_TYPE))
49
+ self.assertIsNotNone(f.metadata)
50
+ self.assertIn('letter_case', f.metadata['dataclasses_json'])
51
+ self.assertIn('exclude', f.metadata['dataclasses_json'])
52
+
53
+ def test_field_with_metadata_removed(self) -> None:
54
+ @dataclass_json
55
+ @dataclass
56
+ class Base(object):
57
+ _field: str = camel_field(exclude=None)
58
+
59
+ base = Base()
60
+ base._field = 'foo'
61
+
62
+ f = base.__dataclass_fields__.get('_field')
63
+ self.assertIsNone(f.default)
64
+ self.assertTrue(isinstance(f.default_factory, dataclasses._MISSING_TYPE))
65
+ self.assertIsNotNone(f.metadata)
66
+ self.assertIn('letter_case', f.metadata['dataclasses_json'])
67
+ self.assertNotIn('exclude', f.metadata['dataclasses_json'])
68
+
69
+ def test_field_with_metadata_edited(self) -> None:
70
+ @dataclass_json
71
+ @dataclass
72
+ class Base(object):
73
+ _field: str = camel_field(exclude=True)
74
+
75
+ base = Base()
76
+ base._field = 'foo'
77
+
78
+ f = base.__dataclass_fields__.get('_field')
79
+ self.assertIsNone(f.default)
80
+ self.assertTrue(isinstance(f.default_factory, dataclasses._MISSING_TYPE))
81
+ self.assertIsNotNone(f.metadata)
82
+ self.assertIn('letter_case', f.metadata['dataclasses_json'])
83
+ self.assertTrue(f.metadata['dataclasses_json']['exclude'])
84
+
85
+ def test_field_with_default(self) -> None:
86
+ @dataclass_json
87
+ @dataclass
88
+ class Base(object):
89
+ _field: str = camel_field(default='Princess Buttercup')
90
+
91
+ base = Base()
92
+
93
+ f = base.__dataclass_fields__.get('_field')
94
+ self.assertIsNotNone(f.default)
95
+ self.assertTrue(isinstance(f.default_factory, dataclasses._MISSING_TYPE))
96
+ self.assertEquals(base._field, 'Princess Buttercup')
97
+
98
+ def test_field_with_no_value_is_excluded(self) -> None:
99
+ @dataclass_json
100
+ @dataclass
101
+ class Base(object):
102
+ _field: str = camel_field()
103
+ _other_field: str = camel_field()
104
+
105
+ base = Base()
106
+ base._field = None
107
+ base._other_field = 'foo'
108
+
109
+ print(base.to_json())
110
+
111
+ f = base.__dataclass_fields__.get('_field')
112
+ self.assertIsNone(f.default)
113
+ self.assertTrue(isinstance(f.default_factory, dataclasses._MISSING_TYPE))
114
+ self.assertIsNotNone(f.metadata)
115
+ self.assertIn('letter_case', f.metadata['dataclasses_json'])
116
+ self.assertTrue(f.metadata['dataclasses_json']['exclude'])
@@ -21,7 +21,8 @@ from urllib.request import urlopen
21
21
  import urllib.parse
22
22
  import json
23
23
 
24
-
24
+ """ _summary_
25
+ """
25
26
  def main(unused):
26
27
  del unused
27
28
 
@@ -28,22 +28,24 @@ from google.auth import exceptions
28
28
  from google.oauth2 import credentials as oauth
29
29
  from httplib2 import Http
30
30
  from oauth2client import service_account
31
+ from oauth2client import client
31
32
 
32
- from .services import Service
33
- from . import field
33
+ from service_framework.services import Service
34
+ from service_framework import snake_field
34
35
 
35
- @dataclasses_json.dataclass_json(letter_case=dataclasses_json.LetterCase.CAMEL)
36
+
37
+ @dataclasses_json.dataclass_json(undefined=dataclasses_json.Undefined.EXCLUDE)
36
38
  @dataclasses.dataclass
37
39
  class OAuthKey(object):
38
40
  """Dataclass from the json key.
39
41
 
40
42
  NOTE: This is by no means the entire key; just the fields neccessary for OAuth.
41
43
  """
42
- access_token: Optional[str] = field(field_name='token')
43
- refresh_token: Optional[str] = field()
44
- token_uri: Optional[str] = field()
45
- client_id: Optional[str] = field()
46
- client_secret: Optional[str] = field()
44
+ access_token: Optional[str] = snake_field(field_name='token')
45
+ refresh_token: Optional[str] = snake_field()
46
+ token_uri: Optional[str] = snake_field()
47
+ client_id: Optional[str] = snake_field()
48
+ client_secret: Optional[str] = snake_field()
47
49
 
48
50
 
49
51
  def build_service(service: Service,
@@ -74,22 +76,10 @@ def build_service(service: Service,
74
76
  if extra_scopes:
75
77
  kwargs['scopes'] = extra_scopes
76
78
 
77
- try:
78
- credentials = oauth.Credentials(**kwargs)
79
- https = google_auth_httplib2.AuthorizedHttp(credentials)
80
-
81
- except KeyError:
82
- logging.exception(
83
- 'Invalid credentials received: missing a part of the credentials')
84
- raise
85
-
86
- except exceptions.RefreshError as error:
87
- logging.exception('Invalid credentials received')
88
- raise
79
+ credentials = oauth.Credentials(**kwargs)
89
80
 
90
81
  elif isinstance(key, oauth.Credentials):
91
82
  credentials = key
92
- https = google_auth_httplib2.AuthorizedHttp(credentials)
93
83
 
94
84
  elif isinstance(key, str):
95
85
  kwargs = {
@@ -98,32 +88,25 @@ def build_service(service: Service,
98
88
  if extra_scopes:
99
89
  kwargs['scopes'] = extra_scopes
100
90
 
101
- credentials = \
102
- service_account.ServiceAccountCredentials.from_json_keyfile_name(
103
- **kwargs)
104
- https = credentials.authorize(Http())
91
+ credentials = oauth.Credentials.from_authorized_user_file(**kwargs)
105
92
 
106
93
  elif isinstance(key, Mapping):
107
- kwargs = {
108
- 'keyfile_dict': key,
109
- }
94
+ kwargs = OAuthKey(**key).to_dict()
110
95
 
111
96
  if extra_scopes:
112
97
  kwargs['scopes'] = extra_scopes
113
98
 
114
- credentials = \
115
- service_account.ServiceAccountCredentials.from_json_keyfile_dict(
116
- key, scopes=extra_scopes)
117
- https = credentials.authorize(Http())
99
+ credentials = oauth.Credentials(**kwargs)
118
100
 
119
101
  else:
120
102
  raise Exception('No valid credentials provided.')
121
103
 
104
+ https = google_auth_httplib2.AuthorizedHttp(credentials)
122
105
  discovery_args = {
123
- 'http': https,
124
- 'developerKey': api_key,
125
- 'cache_discovery': False,
126
- **definition.to_dict()
106
+ 'http': https,
107
+ 'developerKey': api_key,
108
+ 'cache_discovery': False,
109
+ **definition.to_dict()
127
110
  }
128
111
 
129
112
  return discovery.build(**discovery_args)
@@ -20,18 +20,24 @@ from typing import Any, Optional
20
20
  import dataclasses_json
21
21
  import immutabledict
22
22
 
23
- from . import field, lazy_property
23
+ from . import camel_field, lazy_property
24
24
 
25
25
 
26
26
  @dataclasses_json.dataclass_json
27
27
  @dataclasses.dataclass
28
28
  class ServiceDefinition(object):
29
- service_name: Optional[str] = field()
30
- version: Optional[str] = field()
31
- discovery_service_url: Optional[str] = field()
29
+ """Defines a Google Service for the builder."""
30
+ service_name: Optional[str] = camel_field()
31
+ version: Optional[str] = camel_field()
32
+ discovery_service_url: Optional[str] = camel_field()
32
33
 
33
34
 
34
35
  class S(enum.Enum):
36
+ """Defines the generic Enum for any service.
37
+
38
+ Raises:
39
+ ValueError: if no enum is defined.
40
+ """
35
41
  @lazy_property
36
42
  def definition(self) -> ServiceDefinition:
37
43
  """Fetch the ServiceDefinition.
@@ -52,7 +58,18 @@ class S(enum.Enum):
52
58
  f'?version={version}'))
53
59
 
54
60
  @classmethod
55
- def from_value(cls, value) -> S:
61
+ def from_value(cls, value: str) -> S:
62
+ """Creates a service enum from the name of the service.
63
+
64
+ Args:
65
+ value (str): the service name
66
+
67
+ Raises:
68
+ ValueError: no service found
69
+
70
+ Returns:
71
+ S: the service definition
72
+ """
56
73
  for k, v in cls.__members__.items():
57
74
  if k == value.upper():
58
75
  return v
@@ -17,8 +17,8 @@ from service_framework import services
17
17
 
18
18
  SA360_DEFINITION = \
19
19
  services.ServiceDefinition(
20
- name='doubleclicksearch',
21
- uri='https://doubleclicksearch.googleapis.com/$discovery/rest?version=v2',
20
+ service_name='doubleclicksearch',
21
+ discovery_service_url='https://doubleclicksearch.googleapis.com/$discovery/rest?version=v2',
22
22
  version='v2')
23
23
  GMAIL_ARGS = {
24
24
  'serviceName': 'gmail',
@@ -29,10 +29,7 @@ GMAIL_ARGS = {
29
29
 
30
30
  class ServicesTest(unittest.TestCase):
31
31
  def test_valid_service(self):
32
- self.assertGreater(services.Service.SA360.value, 0)
32
+ self.assertGreater(services.Service.DOUBLECLICKSEARCH.value, 0)
33
33
 
34
34
  def test_single_definition(self):
35
- self.assertEqual(SA360_DEFINITION, services.Service.SA360.definition)
36
-
37
- def test_single_to_args(self):
38
- self.assertEqual(GMAIL_ARGS, services.Service.GMAIL.definition.to_args)
35
+ self.assertEqual(SA360_DEFINITION, services.Service.DOUBLECLICKSEARCH.definition)
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: python-service-builder
3
- Version: 0.1.0
4
- Summary: Helper library for easier creation of Google services.
5
- Home-page:
6
- Author: David Harcombe
7
- Author-email: david.harcombe@gmail.com
8
- License: Apache 2.0
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Topic :: Software Development
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
- Requires-Python: >=3.9
16
- Description-Content-Type: text/markdown
17
-
18
- # Python Library for helping to create Python services from Google APIs
19
-
@@ -1,2 +0,0 @@
1
- # Python Library for helping to create Python services from Google APIs
2
-
@@ -1,3 +0,0 @@
1
- [build-system]
2
- requires = ['setuptools>=61.0.0', 'wheel']
3
- build-backend = 'setuptools.build_meta'
@@ -1,19 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: python-service-builder
3
- Version: 0.1.0
4
- Summary: Helper library for easier creation of Google services.
5
- Home-page:
6
- Author: David Harcombe
7
- Author-email: david.harcombe@gmail.com
8
- License: Apache 2.0
9
- Classifier: Development Status :: 4 - Beta
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Topic :: Software Development
12
- Classifier: License :: OSI Approved :: Apache Software License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.9
15
- Requires-Python: >=3.9
16
- Description-Content-Type: text/markdown
17
-
18
- # Python Library for helping to create Python services from Google APIs
19
-
@@ -1,67 +0,0 @@
1
- # Copyright 2022 David Harcombe
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # https://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- import dataclasses
15
- from typing import Any, Callable, Mapping
16
-
17
- import dataclasses_json
18
-
19
-
20
- def lazy_property(f: Callable):
21
- """Decorator that makes a property lazy-evaluated.
22
-
23
- Args:
24
- f: the function to convert to a lazy property.
25
- """
26
- attr_name = '_lazy_' + f.__name__
27
-
28
- @property
29
- def _lazy_property(self) -> Any:
30
- if not hasattr(self, attr_name):
31
- setattr(self, attr_name, f(self))
32
- return getattr(self, attr_name)
33
- return _lazy_property
34
-
35
-
36
- def metadata(base: Mapping[str, Any], **custom) -> Mapping[str, Any]:
37
- for key in custom:
38
- if not custom[key] and key in base:
39
- del base[key]
40
-
41
- elif custom[key]:
42
- base.update({key: custom[key]})
43
-
44
- return base
45
-
46
-
47
- def field(default: Any = None, default_factory: Any = None, **kwargs):
48
- base = {
49
- 'letter_case': dataclasses_json.LetterCase.CAMEL,
50
- 'exclude': lambda x: not x
51
- }
52
-
53
- if kwargs:
54
- exclude = kwargs.get('exclude', lambda x: not x)
55
- else:
56
- def exclude(x): return not x
57
-
58
- if default_factory:
59
- f = dataclasses.field(default_factory=default_factory,
60
- metadata=dataclasses_json.config(
61
- **metadata(base=base, **kwargs)))
62
- else:
63
- f = dataclasses.field(default=default,
64
- metadata=dataclasses_json.config(
65
- **metadata(base=base, **kwargs)))
66
-
67
- return f
@@ -1,29 +0,0 @@
1
- # Copyright 2022 David Harcombe
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # https://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- import unittest
15
-
16
- from . import lazy_property
17
-
18
-
19
- class LazyPropertyTest(unittest.TestCase):
20
- class Foo(object):
21
- @lazy_property
22
- def lazy_thing(self) -> str:
23
- return 'lazy'
24
-
25
- def test_lazy_thing(self):
26
- foo = LazyPropertyTest.Foo()
27
- self.assertFalse(hasattr(foo, '_lazy_lazy_thing'))
28
- self.assertEqual('lazy', foo.lazy_thing)
29
- self.assertTrue(hasattr(foo, '_lazy_lazy_thing'))
@@ -1,58 +0,0 @@
1
- # Copyright 2022 David Harcombe. All Rights Reserved.
2
- #
3
- # Licensed under the Apache License, Version 2.0 (the "License");
4
- # you may not use this file except in compliance with the License.
5
- # You may obtain a copy of the License at
6
- #
7
- # http://www.apache.org/licenses/LICENSE-2.0
8
- #
9
- # Unless required by applicable law or agreed to in writing, software
10
- # distributed under the License is distributed on an "AS IS" BASIS,
11
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- # See the License for the specific language governing permissions and
13
- # limitations under the License.
14
- import setuptools
15
-
16
-
17
- with open("README.md", "r", encoding="utf-8") as fh:
18
- long_description = fh.read()
19
-
20
- setuptools.setup(
21
- name='python-service-builder',
22
- version='0.1.0',
23
- description='Helper library for easier creation of Google services.',
24
- long_description=long_description,
25
- long_description_content_type='text/markdown',
26
- url='',
27
- author='David Harcombe',
28
- author_email='david.harcombe@gmail.com',
29
- license='Apache 2.0',
30
- zip_safe=False,
31
- include_package_data=True,
32
- packages=setuptools.find_packages(),
33
- classifiers=[
34
- 'Development Status :: 4 - Beta',
35
- 'Intended Audience :: Developers',
36
- 'Topic :: Software Development',
37
- 'License :: OSI Approved :: Apache Software License',
38
- 'Programming Language :: Python :: 3',
39
- 'Programming Language :: Python :: 3.9',
40
- ],
41
- python_requires=">=3.9",
42
- install_requires=[
43
- 'absl-py>=0.12.0',
44
- 'dataclasses-json>=0.5.2',
45
- 'gcs-oauth2-boto-plugin>=2.7',
46
- 'google-api-core>=1.26.1',
47
- 'google-api-python-client>=2.0.2',
48
- 'google-auth-httplib2>=0.1.0',
49
- 'google-auth-oauthlib>=0.4.3',
50
- 'google-auth>=1.28.0',
51
- 'google-reauth>=0.1.1',
52
- 'googleapis-common-protos>=1.53.0',
53
- 'httplib2>=0.19.0',
54
- 'immutabledict>=2.2.1',
55
- 'oauth2client>=4.1.3',
56
- 'oauthlib>=3.1.0',
57
- ],
58
- )