Skip to content
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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release v5.11.0 #361

Merged
merged 6 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/workflows/e2e-test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
runs-on: ubuntu-latest
if:
github.event_name == 'workflow_dispatch' && inputs.sha != ''
env:
EXIT_STATUS: 0

steps:
- uses: actions-ecosystem/action-regex-match@v2
Expand Down Expand Up @@ -83,20 +85,17 @@ jobs:
timestamp=$(date +'%Y%m%d%H%M')
report_filename="${timestamp}_sdk_test_report.xml"
status=0
if ! python3 -m pytest test/integration/${INTEGRATION_TEST_PATH} --junitxml="${report_filename}"; then
echo "Tests failed, but attempting to upload results anyway"
if ! python3 -m pytest test/integration/${INTEGRATION_TEST_PATH} --disable-warnings --junitxml="${report_filename}"; then
echo "EXIT_STATUS=1" >> $GITHUB_ENV
fi
env:
LINODE_TOKEN: ${{ secrets.LINODE_TOKEN }}

- name: Set release version env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: Add additional information to XML report
run: |
filename=$(ls | grep -E '^[0-9]{12}_sdk_test_report\.xml$')
python test/script/add_to_xml_test_report.py \
--branch_name "${{ env.RELEASE_VERSION }}" \
--branch_name "${GITHUB_REF#refs/*/}" \
--gha_run_id "$GITHUB_RUN_ID" \
--gha_run_number "$GITHUB_RUN_NUMBER" \
--xmlfile "${filename}"
Expand Down Expand Up @@ -135,4 +134,13 @@ jobs:
status: 'completed',
conclusion: process.env.conclusion
});
return result;
return result;

- name: Test Execution Status Handler
run: |
if [[ "$EXIT_STATUS" != 0 ]]; then
echo "Test execution contains failure(s)"
exit $EXIT_STATUS
else
echo "Tests passed!"
fi
4 changes: 2 additions & 2 deletions linode_api4/groups/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ def enrolled_betas(self, *filters):
"""
Returns a list of all Beta Programs an account is enrolled in.

API doc: TBD
API doc: https://www.linode.com/docs/api/beta-programs/#enrolled-beta-programs-list

:returns: a list of Beta Programs.
:rtype: PaginatedList of AccountBetaProgram
Expand All @@ -468,7 +468,7 @@ def join_beta_program(self, beta: Union[str, BetaProgram]):
"""
Enrolls an account into a beta program.

API doc: TBD
API doc: https://www.linode.com/docs/api/beta-programs/#beta-program-enroll

:param beta: The object or id of a beta program to join.
:type beta: BetaProgram or str
Expand Down
2 changes: 1 addition & 1 deletion linode_api4/groups/beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def betas(self, *filters):
"""
Returns a list of available active Beta Programs.

API Documentation: TBD
API Documentation: https://www.linode.com/docs/api/beta-programs/#beta-programs-list

:param filters: Any number of filters to apply to this query.
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
Expand Down
20 changes: 20 additions & 0 deletions linode_api4/groups/region.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from linode_api4.groups import Group
from linode_api4.objects import Region
from linode_api4.objects.region import RegionAvailabilityEntry


class RegionGroup(Group):
Expand All @@ -23,3 +24,22 @@ def __call__(self, *filters):
"""

return self.client._get_and_filter(Region, *filters)

def availability(self, *filters):
"""
Returns the availability of Linode plans within a Region.


API Documentation: https://www.linode.com/docs/api/regions/#regions-availability-list

:param filters: Any number of filters to apply to this query.
See :doc:`Filtering Collections</linode_api4/objects/filtering>`
for more details on filtering.

:returns: A list of entries describing the availability of a plan in a region.
:rtype: PaginatedList of RegionAvailabilityEntry
"""

return self.client._get_and_filter(
RegionAvailabilityEntry, *filters, endpoint="/regions/availability"
)
5 changes: 4 additions & 1 deletion linode_api4/linode_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,10 @@ def _api_call(
body = json.dumps(data)

response = method(
url, headers=headers, data=body, verify=self.ca_path or True
url,
headers=headers,
data=body,
verify=self.ca_path or self.session.verify,
)

warning = response.headers.get("Warning", None)
Expand Down
10 changes: 6 additions & 4 deletions linode_api4/login_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -490,9 +490,10 @@ def refresh_oauth_token(self, refresh_token):

def expire_token(self, token):
"""
Given a token, makes a request to the authentication server to expire
it immediately. This is considered a responsible way to log out a
user. If you simply remove the session your application has for the
Given a token, makes a request to the authentication server to expire both
access token and refresh token.
This is considered a responsible way to log out a user.
If you remove only the session your application has for the
user without expiring their token, the user is not _really_ logged out.

:param token: The OAuth token you wish to expire
Expand All @@ -504,8 +505,9 @@ def expire_token(self, token):
:raises ApiError: If the expiration attempt failed.
"""
r = requests.post(
self._login_uri("/oauth/token/expire"),
self._login_uri("/oauth/revoke"),
data={
"token_type_hint": "access_token",
"client_id": self.client_id,
"client_secret": self.client_secret,
"token": token,
Expand Down
2 changes: 2 additions & 0 deletions linode_api4/objects/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,8 @@ def save(self):
class AccountBetaProgram(Base):
"""
The details and enrollment information of a Beta program that an account is enrolled in.

API Documentation: https://www.linode.com/docs/api/beta-programs/#enrolled-beta-program-view
"""

api_endpoint = "/account/betas/{id}"
Expand Down
2 changes: 1 addition & 1 deletion linode_api4/objects/beta.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class BetaProgram(Base):
Beta program is a new product or service that's not generally available to all customers.
User with permissions can enroll into a beta program and access the functionalities.

API Documentation: TBD
API Documentation: https://www.linode.com/docs/api/beta-programs/#beta-program-view
"""

api_endpoint = "/betas/{id}"
Expand Down
34 changes: 33 additions & 1 deletion linode_api4/objects/region.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
from linode_api4.objects import Base, Property
from dataclasses import dataclass
from typing import List

from linode_api4.errors import UnexpectedResponseError
from linode_api4.objects.base import Base, JSONObject, Property
from linode_api4.objects.filtering import FilterableAttribute
from linode_api4.objects.serializable import JSONFilterableMetaclass


class Region(Base):
Expand All @@ -17,3 +23,29 @@ class Region(Base):
"resolvers": Property(),
"label": Property(),
}

@property
def availability(self) -> List["RegionAvailabilityEntry"]:
result = self._client.get(
f"{self.api_endpoint}/availability", model=self
)

if result is None:
raise UnexpectedResponseError(
"Expected availability data, got None."
)

return [RegionAvailabilityEntry.from_json(v) for v in result]


@dataclass
class RegionAvailabilityEntry(JSONObject):
"""
Represents the availability of a Linode type within a region.

API Documentation: https://www.linode.com/docs/api/regions/#region-availability-view
"""

region: str = None
plan: str = None
available: bool = False
45 changes: 43 additions & 2 deletions linode_api4/objects/serializable.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
import inspect
from dataclasses import asdict, dataclass
from typing import Any, Dict, Optional, get_args, get_origin, get_type_hints
from types import SimpleNamespace
from typing import (
Any,
ClassVar,
Dict,
Optional,
get_args,
get_origin,
get_type_hints,
)

from linode_api4.objects.filtering import FilterableAttribute

# Wraps the SimpleNamespace class and allows for
# SQLAlchemy-style filter generation on JSONObjects.
JSONFilterGroup = SimpleNamespace


class JSONFilterableMetaclass(type):
def __init__(cls, name, bases, dct):
setattr(
cls,
"filters",
JSONFilterGroup(
**{
k: FilterableAttribute(k)
for k in cls.__annotations__.keys()
}
),
)

super().__init__(name, bases, dct)


@dataclass
class JSONObject:
class JSONObject(metaclass=JSONFilterableMetaclass):
"""
A simple helper class for serializable API objects.
This is typically used for nested object values.
Expand All @@ -13,6 +44,16 @@ class JSONObject:
fields and static typing.
"""

filters: ClassVar[JSONFilterGroup] = None
"""
A group containing FilterableAttributes used to create SQLAlchemy-style filters.

Example usage::
self.client.regions.availability(
RegionAvailabilityEntry.filters.plan == "premium4096.7"
)
"""

def __init__(self):
raise NotImplementedError(
"JSONObject is not intended to be constructed directly"
Expand Down
6 changes: 6 additions & 0 deletions linode_api4/paginated_list.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import math

from linode_api4.objects.serializable import JSONObject


class PaginatedList(object):
"""
Expand Down Expand Up @@ -205,6 +207,10 @@ def make_list(json_arr, client, cls, parent_id=None):

for obj in json_arr:
id_val = None
# Special handling for JSON objects
if issubclass(cls, JSONObject):
result.append(cls.from_json(obj))
continue

if "id" in obj:
id_val = obj["id"]
Expand Down
Loading
Loading