Skip to content

Commit

Permalink
Improvement: Add graceful handling for undefined App Store Connect mo…
Browse files Browse the repository at this point in the history
…del relationships and enumerations (#100)

* Handle unknown resource relationships gracefully

* Add graceful handling for undefined enumerations

* Update changelog and bump version

* Update comment

* Use the same base class for dynamically created resource enums
  • Loading branch information
priitlatt authored May 31, 2021
1 parent 3df35da commit dfe629f
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 5 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
Version 0.7.1
-------------

**Fixes**

- Ignore undefined model relationships in App Store Connect API responses instead of failing with `TypeError`.
- Dynamically generate enumerations for undefined values from App Store Connect API responses instead of failing with `ValueError`.

**Development / Docs**

- Make `SignignCertificate` model relationship `passTypeId` optional.

Version 0.7.0
-------------

Expand Down
2 changes: 1 addition & 1 deletion src/codemagic/__version__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__title__ = 'codemagic-cli-tools'
__description__ = 'CLI tools used in Codemagic builds'
__version__ = '0.7.0'
__version__ = '0.7.1'
__url__ = 'https://github.com/codemagic-ci-cd/cli-tools'
__licence__ = 'GNU General Public License v3.0'
27 changes: 25 additions & 2 deletions src/codemagic/apple/resources/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,31 @@
import enum
from typing import Optional


class _ResourceEnum(enum.Enum):
from codemagic.utilities import log


class _ResourceEnumMeta(enum.EnumMeta):
"""
Custom metaclass for Resource enumerations to accommodate the cases when
App Store Connect API returns such a value that our definitions do not describe.
For example, `BundleIdPlatform` should only have values `IOS` and `MAC_OS` as per
documentation https://developer.apple.com/documentation/appstoreconnectapi/bundleidplatform,
but it is known that in practice `UNIVERSAL` and `SERVICES` are just as valid values.
Without this graceful fallback to dynamically generated enumeration the program execution
fails unexpectedly, which is not desirable.
"""

def __call__(cls, value, *args, **kwargs): # noqa: N805
try:
return super().__call__(value, *args, **kwargs)
except ValueError as ve:
logger = log.get_logger(cls, log_to_stream=False)
logger.warning('Undefined Resource enumeration: %s', ve)
enum_class = _ResourceEnum(f'Graceful{cls.__name__}', {value: value})
return enum_class(value)


class _ResourceEnum(enum.Enum, metaclass=_ResourceEnumMeta):

def __str__(self):
return str(self.value)
Expand Down
17 changes: 16 additions & 1 deletion src/codemagic/apple/resources/resource.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations

import dataclasses
import enum
import re
from dataclasses import dataclass
Expand All @@ -9,12 +10,14 @@
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Union
from typing import overload

from codemagic.models import JsonSerializable
from codemagic.models import JsonSerializableMeta
from codemagic.utilities import log

from .enums import ResourceType

Expand Down Expand Up @@ -166,13 +169,25 @@ def __post_init__(self):
if not isinstance(value, (Relationship, type(None))):
setattr(self, field, Relationship(**value))

@classmethod
def get_fields(cls) -> Set[str]:
return {f.name for f in dataclasses.fields(cls)}

@classmethod
def _create_attributes(cls, api_response):
return cls.Attributes(**api_response['attributes'])

@classmethod
def _create_relationships(cls, api_response):
return cls.Relationships(**api_response['relationships'])
logger = log.get_logger(cls, log_to_stream=False)
known_relationships = cls.Relationships.get_fields()
relationships = {}
for relationship_name, relationship in api_response['relationships'].items():
if relationship_name in known_relationships:
relationships[relationship_name] = relationship
else:
logger.warning('Unknown relationship %r for resource %r', relationship_name, cls.__name__)
return cls.Relationships(**relationships)

def __init__(self, api_response: Dict, created: bool = False):
super().__init__(api_response)
Expand Down
2 changes: 1 addition & 1 deletion src/codemagic/apple/resources/signing_certificate.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __post_init__(self):
class Relationships(Resource.Relationships):
_OMIT_IF_NONE_KEYS = ('passTypeId',)

passTypeId: Optional[Relationship]
passTypeId: Optional[Relationship] = None

def get_display_info(self) -> str:
return f'{self.attributes.certificateType} certificate {self.attributes.serialNumber}'
Expand Down

0 comments on commit dfe629f

Please sign in to comment.