-
Notifications
You must be signed in to change notification settings - Fork 87
new: Add support for LKE Control Plane ACLs #406
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ed0357a
2749f20
73f321c
c05a193
3703faa
dbd22a4
5716469
832d506
94bb114
abd0659
2fe52cb
8c4c59f
c355ff7
c823fef
deec4d7
33b7f16
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,14 @@ | ||
| import inspect | ||
| from dataclasses import asdict, dataclass | ||
| from dataclasses import dataclass | ||
| from types import SimpleNamespace | ||
| from typing import ( | ||
| Any, | ||
| ClassVar, | ||
| Dict, | ||
| List, | ||
| Optional, | ||
| Set, | ||
| Union, | ||
| get_args, | ||
| get_origin, | ||
| get_type_hints, | ||
|
|
@@ -54,28 +57,57 @@ class JSONObject(metaclass=JSONFilterableMetaclass): | |
| ) | ||
| """ | ||
|
|
||
| always_include: ClassVar[Set[str]] = {} | ||
| """ | ||
| A set of keys corresponding to fields that should always be | ||
| included in the generated output regardless of whether their values | ||
| are None. | ||
| """ | ||
|
Comment on lines
+60
to
+65
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was considering implementing something more similar to
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I agree that in the most of the case we just want to omit empty values. |
||
|
|
||
| def __init__(self): | ||
| raise NotImplementedError( | ||
| "JSONObject is not intended to be constructed directly" | ||
| ) | ||
|
|
||
| # TODO: Implement __repr__ | ||
| @staticmethod | ||
| def _unwrap_type(field_type: type) -> type: | ||
| args = get_args(field_type) | ||
| origin_type = get_origin(field_type) | ||
|
|
||
| # We don't want to try to unwrap Dict, List, Set, etc. values | ||
| if origin_type is not Union: | ||
| return field_type | ||
|
|
||
| if len(args) == 0: | ||
| raise TypeError("Expected type to have arguments, got none") | ||
|
|
||
| # Use the first type in the Union's args | ||
| return JSONObject._unwrap_type(args[0]) | ||
|
|
||
| @staticmethod | ||
| def _try_from_json(json_value: Any, field_type: type): | ||
| """ | ||
| Determines whether a JSON dict is an instance of a field type. | ||
| """ | ||
|
|
||
| field_type = JSONObject._unwrap_type(field_type) | ||
|
|
||
| if inspect.isclass(field_type) and issubclass(field_type, JSONObject): | ||
| return field_type.from_json(json_value) | ||
|
|
||
| return json_value | ||
|
|
||
| @classmethod | ||
| def _parse_attr_list(cls, json_value, field_type): | ||
| def _parse_attr_list(cls, json_value: Any, field_type: type): | ||
| """ | ||
| Attempts to parse a list attribute with a given value and field type. | ||
| """ | ||
|
|
||
| # Edge case for optional list values | ||
| if json_value is None: | ||
| return None | ||
|
|
||
| type_hint_args = get_args(field_type) | ||
|
|
||
| if len(type_hint_args) < 1: | ||
|
|
@@ -86,11 +118,13 @@ def _parse_attr_list(cls, json_value, field_type): | |
| ] | ||
|
|
||
| @classmethod | ||
| def _parse_attr(cls, json_value, field_type): | ||
| def _parse_attr(cls, json_value: Any, field_type: type): | ||
| """ | ||
| Attempts to parse an attribute with a given value and field type. | ||
| """ | ||
|
|
||
| field_type = JSONObject._unwrap_type(field_type) | ||
|
|
||
| if list in (field_type, get_origin(field_type)): | ||
| return cls._parse_attr_list(json_value, field_type) | ||
|
|
||
|
|
@@ -117,7 +151,55 @@ def _serialize(self) -> Dict[str, Any]: | |
| """ | ||
| Serializes this object into a JSON dict. | ||
| """ | ||
| return asdict(self) | ||
| cls = type(self) | ||
| type_hints = get_type_hints(cls) | ||
|
|
||
| def attempt_serialize(value: Any) -> Any: | ||
| """ | ||
| Attempts to serialize the given value, else returns the value unchanged. | ||
| """ | ||
| if issubclass(type(value), JSONObject): | ||
| return value._serialize() | ||
|
|
||
| return value | ||
|
|
||
| def should_include(key: str, value: Any) -> bool: | ||
| """ | ||
| Returns whether the given key/value pair should be included in the resulting dict. | ||
| """ | ||
|
|
||
| if key in cls.always_include: | ||
| return True | ||
|
|
||
| hint = type_hints.get(key) | ||
|
|
||
| # We want to exclude any Optional values that are None | ||
| # NOTE: We need to check for Union here because Optional is an alias of Union. | ||
| if ( | ||
| hint is None | ||
| or get_origin(hint) is not Union | ||
| or type(None) not in get_args(hint) | ||
| ): | ||
| return True | ||
|
|
||
| return value is not None | ||
|
|
||
| result = {} | ||
|
|
||
| for k, v in vars(self).items(): | ||
| if not should_include(k, v): | ||
| continue | ||
|
|
||
| if isinstance(v, List): | ||
| v = [attempt_serialize(j) for j in v] | ||
| elif isinstance(v, Dict): | ||
| v = {k: attempt_serialize(j) for k, j in v.items()} | ||
| else: | ||
| v = attempt_serialize(v) | ||
|
|
||
| result[k] = v | ||
|
|
||
| return result | ||
|
Comment on lines
+154
to
+202
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately we have to reinvent the wheel here because we don't have access to type hints or Ideally I'd want to break this out into a separate PR but excluding optional fields from generated request bodies is a requirement for |
||
|
|
||
| @property | ||
| def dict(self) -> Dict[str, Any]: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -100,7 +100,7 @@ def subnet_create( | |
| return d | ||
|
|
||
| @property | ||
| def ips(self, *filters) -> PaginatedList: | ||
| def ips(self) -> PaginatedList: | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This isn't directly related to this change but it was causing the linter to fail. This shouldn't be a breaking change since arguments could already not be passed into ips 馃憤 |
||
| """ | ||
| Get all the IP addresses under this VPC. | ||
|
|
||
|
|
@@ -116,5 +116,5 @@ def ips(self, *filters) -> PaginatedList: | |
| ) | ||
|
|
||
| return self._client._get_and_filter( | ||
| VPCIPAddress, *filters, endpoint="/vpcs/{}/ips".format(self.id) | ||
| VPCIPAddress, endpoint="/vpcs/{}/ips".format(self.id) | ||
| ) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This pattern is not consistent with the rest of the codebase, but unfortunately we cannot implement it using the traditional properties because
ACLis not returned from the LKE cluster GET endpoint.