## Apple Music HTTP Client

### Installation
```shell script
pip install am_http_client
```

### Getting started

Configure credentials for working with Apple Music API. Official guide can be found
[here](https://developer.apple.com/documentation/applemusicapi/getting_keys_and_creating_tokens).
```python
from am_http_client.client import ClientCredentials

credentials = ClientCredentials(
    secret_key='''
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEbVzfPnZPxfAyxqE
ZV05laAoJAl+/6Xt2O4mOB611sOhRANCAASgFTKjwJAAU95g++/vzKWHkzAVmNMI
tB5vTjZOOIwnEb70MsWZFIyUFD1P9Gwstz4+akHX7vI8BH6hHmBmfeQl
-----END PRIVATE KEY-----
    ''',
    key_id='DEADBEAF',
    team_id='AABBCCDD',
)
```

Make a client instance:
```python
from am_http_client.client import Client

client = Client(
    credentials=credentials,
    retry_interval=1,
    max_retries=3,
    request_timeout=10,
)
```

and start using it with a simple request:
```python
album = client.get_album(album_id='1460462589', storefront='us')
print(album.attributes.name)
print(album.attributes.artistName)
print(album.attributes.genreNames)
```

Take into consideration that attribute keys have a camel case notation due to the API compatibility.

### Advanced level

#### Requests retrying

The client will retry failed attempts only if following conditions are matched:
* API returned either 429 or 5xx HTTP status code;
* `max_retries` parameter is greater than zero;
* The amount of failed attempts is less than `max_retries` parameter;

In other cases the original exception (mostly `requests.exceptions.HTTPError`) will be raised.

#### Response serializing

With a help of [dataclasses-json](https://pypi.org/project/dataclasses-json/) library you can serialize a given
entity (Playlist, Album, Artist, Song) to JSON format:
```python
playlist = client.get_playlist(playlist_id, storefront='us')
with open('result.json', 'a') as fpa:
    fpa.write(playlist.to_json())
```

More than that, if you need a Python dict object to use custom serialization (YAML, ProtoBuf, etc) you
can use `.to_dict()` method.

### Logging
Optionally client supports [structlog](https://www.structlog.org/en/stable/) library for logging, basic configuration can
be done in this way:
```python
def configure_logging():
    def save_traceback(logger: structlog.stdlib.BoundLogger, method_name: str, event_dict: Dict) -> Dict:
        if method_name == 'error' and 'exception' in event_dict and logger.name != 'tasks':
            event_dict['traceback'] = event_dict['exception']
        return event_dict

    config = {
        'version': 1,
        'disable_existing_loggers': True,
        'formatters': {
            'generic': {
                'format': '[%(levelname)s]\t %(asctime)s %(name)s.%(funcName)s: %(message)s',
                'datefmt': '%Y-%m-%d %H:%M:%S'
            }
        },
        'handlers': {
            'console': {
                'class': 'logging.StreamHandler', 'stream': 'ext://sys.stdout', 'formatter': 'generic'
            }
        },
        'base_log': {
            'level': 'DEBUG', 'handlers': ['console'], 'formatter': 'generic'
        },
        'loggers': {
            '': {
                'level': 'DEBUG', 'handlers': ['console'], 'formatter': 'generic'
            }
        }
    }

    message_renderer_processor = structlog.dev.ConsoleRenderer(colors=True)

    logging_config.dictConfig(config)
    structlog.configure_once(
        processors=[
            structlog.processors.StackInfoRenderer(),
            structlog.processors.format_exc_info,
            save_traceback,
            message_renderer_processor,
        ],
        logger_factory=structlog.stdlib.LoggerFactory(),
        wrapper_class=CustomBoundLogger,
        cache_logger_on_first_use=True,
        context_class=structlog.threadlocal.wrap_dict(dict),
    )
```


### Developing guide

Install virtual environment and activate it:
```shell script
make install
```

Check tests:
```shell script
make test
```

Check linter and type checker:
```shell script
make lint && make typecheck
```

Build a distribution:
```shell script
make build
```
