Repository: aws-powertools/powertools-lambda-python

Issue:
RFC: AppSync Resolver Event Utils [DRAFT]
## Key information

* RFC PR: https://github.com/awslabs/aws-lambda-powertools-python/pull/323
* Related issue(s), if known:
* Area: Utilities
* Meet [tenets](https://awslabs.github.io/aws-lambda-powertools-python/#tenets):  Yes

## Summary
[summary]: #summary

> One paragraph explanation of the feature.

## Motivation
[motivation]: #motivation

> Why are we doing this? What use cases does it support? What is the expected outcome?

Make it easier to map multiple AppSync resolvers to a single lambda. Also to have a collection of useful utility functions.

## Proposal
[proposal]: #proposal

Something like : https://github.com/twkiiim/sleemo


```python3
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
from aws_lambda_powertools.utilities.data_classes.appsync_resolver_utils import (
    AppSyncResolver,
    make_id,
    aws_date,
    aws_datetime,
    aws_time,
    aws_timestamp,
)

logger = Logger()
app = AppSyncResolver()


@app.resolver(field_name="locations", include_event=True, include_context: True)
def get_locations(event: AppSyncResolverEvent, context: LambdaContext, name: str = None):
    logger.info(event)
    return f"returning locations for {name}"


@app.resolver(type_name="Merchant", field_name="anotherField")
def another_field_resolver(count: int):
    return {
        "id": make_id(),
        "date": aws_date(),
        "date_time": aws_datetime(),
        "time": aws_time(),
        "ts": aws_timestamp(),
        "message": f"another_field_resolver with parameter count={count}",
    }


@app.resolver(type_name="Query", field_name="noParams")
def no_params():
    return "no_params has no params"


def handler(event: dict, context):
    return app.resolve(event, context)
```

> This is the bulk of the RFC.

> Explain the design in enough detail for somebody familiar with Powertools to understand it, and for somebody familiar with the implementation to implement it.

> This should get into specifics and corner-cases, and include examples of how the feature is used. Any new terminology should be defined here.

## Drawbacks
[drawbacks]: #drawbacks

> Why should we *not* do this?

> Do we need additional dependencies? Impact performance/package size?

## Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives

* **What other designs have been considered? Why not them?**
* **What is the impact of not doing this?**

## Unresolved questions
[unresolved-questions]: #unresolved-questions

> Optional, stash area for topics that need further development e.g. TBD



Discussion:
Hey @michaelbrewer - Could you let us know when the RFC body is ready to review? It's missing a few sections like summary, drawbacks, rationale, and any open questions you might have (if any), etc.
Sorry yes @heitorlessa  i will put that in. I was just mentally making sure i would look at this :)
@heitorlessa  i guess it is mostly implemented what i wanted to support initially, but i will try to describe it more

Resolving patch:
diff --git a/aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py b/aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py
--- a/aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py
+++ b/aws_lambda_powertools/utilities/data_classes/appsync/resolver_utils.py
@@ -1,35 +1,9 @@
-import datetime
-import time
-import uuid
 from typing import Any, Dict
 
 from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
 from aws_lambda_powertools.utilities.typing import LambdaContext
 
 
-def make_id():
-    return str(uuid.uuid4())
-
-
-def aws_date():
-    now = datetime.datetime.utcnow().date()
-    return now.strftime("%Y-%m-%d")
-
-
-def aws_time():
-    now = datetime.datetime.utcnow().time()
-    return now.strftime("%H:%M:%S")
-
-
-def aws_datetime():
-    now = datetime.datetime.utcnow()
-    return now.strftime("%Y-%m-%dT%H:%M:%SZ")
-
-
-def aws_timestamp():
-    return int(time.time())
-
-
 class AppSyncResolver:
     def __init__(self):
         self._resolvers: dict = {}
diff --git a/aws_lambda_powertools/utilities/data_classes/appsync/scalar_types_utils.py b/aws_lambda_powertools/utilities/data_classes/appsync/scalar_types_utils.py
new file mode 100644
--- /dev/null
+++ b/aws_lambda_powertools/utilities/data_classes/appsync/scalar_types_utils.py
@@ -0,0 +1,89 @@
+import datetime
+import time
+import uuid
+
+
+def _formatted_time(now: datetime.date, fmt: str, timezone_offset: int) -> str:
+    """String formatted time with optional timezone offset
+
+    Parameters
+    ----------
+    now : datetime.date
+        Current datetime with zero timezone offset
+    fmt : str
+        Data format before adding timezone offset
+    timezone_offset : int
+        Timezone offset in hours, defaults to 0
+    Returns
+    -------
+    str
+        Returns string formatted time with optional timezone offset
+    """
+    if timezone_offset == 0:
+        return now.strftime(fmt + "Z")
+
+    now = now + datetime.timedelta(hours=timezone_offset)
+    fmt += "+" if timezone_offset > 0 else "-"
+    fmt += str(abs(timezone_offset)).zfill(2)
+    fmt += ":00:00"
+
+    return now.strftime(fmt)
+
+
+def make_id() -> str:
+    """ID - A unique identifier for an object. This scalar is serialized like a String but isn't meant to be
+    human-readable."""
+    return str(uuid.uuid4())
+
+
+def aws_date(timezone_offset: int = 0) -> str:
+    """AWSDate - An extended ISO 8601 date string in the format YYYY-MM-DD.
+
+    Parameters
+    ----------
+    timezone_offset : int
+        Timezone offset, defaults to 0
+
+    Returns
+    -------
+    str
+        Returns current time as AWSDate scalar string with optional timezone offset
+    """
+    return _formatted_time(datetime.datetime.utcnow(), "%Y-%m-%d", timezone_offset)
+
+
+def aws_time(timezone_offset: int = 0) -> str:
+    """AWSTime - An extended ISO 8601 time string in the format hh:mm:ss.sss.
+
+    Parameters
+    ----------
+    timezone_offset : int
+        Timezone offset, defaults to 0
+
+    Returns
+    -------
+    str
+        Returns current time as AWSTime scalar string with optional timezone offset
+    """
+    return _formatted_time(datetime.datetime.utcnow(), "%H:%M:%S", timezone_offset)
+
+
+def aws_datetime(timezone_offset: int = 0) -> str:
+    """AWSDateTime - An extended ISO 8601 date and time string in the format YYYY-MM-DDThh:mm:ss.sssZ.
+
+    Parameters
+    ----------
+    timezone_offset : int
+        Timezone offset, defaults to 0
+
+    Returns
+    -------
+    str
+        Returns current time as AWSDateTime scalar string with optional timezone offset
+    """
+    return _formatted_time(datetime.datetime.utcnow(), "%Y-%m-%dT%H:%M:%S", timezone_offset)
+
+
+def aws_timestamp() -> int:
+    """AWSTimestamp - An integer value representing the number of seconds before or after 1970-01-01-T00:00Z."""
+    return int(time.time())


Test patch:
diff --git a/tests/functional/appsync/test_appsync_resolver_utils.py b/tests/functional/appsync/test_appsync_resolver_utils.py
--- a/tests/functional/appsync/test_appsync_resolver_utils.py
+++ b/tests/functional/appsync/test_appsync_resolver_utils.py
@@ -7,8 +7,9 @@
 import pytest
 
 from aws_lambda_powertools.utilities.data_classes import AppSyncResolverEvent
-from aws_lambda_powertools.utilities.data_classes.appsync.resolver_utils import (
-    AppSyncResolver,
+from aws_lambda_powertools.utilities.data_classes.appsync.resolver_utils import AppSyncResolver
+from aws_lambda_powertools.utilities.data_classes.appsync.scalar_types_utils import (
+    _formatted_time,
     aws_date,
     aws_datetime,
     aws_time,
@@ -196,19 +197,19 @@ def test_make_id():
     assert len(uuid) == 36
 
 
-def test_aws_date():
+def test_aws_date_utc():
     date_str = aws_date()
     assert isinstance(date_str, str)
-    assert datetime.datetime.strptime(date_str, "%Y-%m-%d")
+    assert datetime.datetime.strptime(date_str, "%Y-%m-%dZ")
 
 
-def test_aws_time():
+def test_aws_time_utc():
     time_str = aws_time()
     assert isinstance(time_str, str)
-    assert datetime.datetime.strptime(time_str, "%H:%M:%S")
+    assert datetime.datetime.strptime(time_str, "%H:%M:%SZ")
 
 
-def test_aws_datetime():
+def test_aws_datetime_utc():
     datetime_str = aws_datetime()
     assert isinstance(datetime_str, str)
     assert datetime.datetime.strptime(datetime_str, "%Y-%m-%dT%H:%M:%SZ")
@@ -217,3 +218,17 @@ def test_aws_datetime():
 def test_aws_timestamp():
     timestamp = aws_timestamp()
     assert isinstance(timestamp, int)
+
+
+def test_format_time_positive():
+    now = datetime.datetime(2022, 1, 22)
+    datetime_str = _formatted_time(now, "%Y-%m-%d", 8)
+    assert isinstance(datetime_str, str)
+    assert datetime_str == "2022-01-22+08:00:00"
+
+
+def test_format_time_negative():
+    now = datetime.datetime(2022, 1, 22, 14, 22, 33)
+    datetime_str = _formatted_time(now, "%H:%M:%S", -12)
+    assert isinstance(datetime_str, str)
+    assert datetime_str == "02:22:33-12:00:00"