Repository: aws/aws-sdk-pandas

Issue:
cloudwatch `start_query` and `run_query` `end_time` goes stale
### Describe the bug

The `cloudwatch.start_query` and `cloudwatch.run_query` APIs provide a default value for the `end_time` parameter of `datetime.datetime.utcnow()`. However, this is evaluated at import time not at run time. Therefore if you have a long running piece of code the `end_time` default can go stale. This is especially pronounced when using AWS Lambda - Lambda keeps the execution environment running between calls so even for short running code snippets the `end_time` can go stale.

Of course one can easily (and probably should) provide their own `end_time` values, but this is not very user friendly as is. I think we should either:

1. Make `end_time` (and `start_time`?) required parameters.
2. Set `end_time` default to `None` and set it to `datetime.datetime.utcnow()` at runtime if it's still `None` (aka wasn't passed).

### How to Reproduce

```
wr.cloudwatch.run_query(log_group_names=log_groups, query=query)
time.sleep(60) # during sleep add log events to the stream that should be picked up by `query`
wr.cloudwatch.run_query(log_group_names=log_groups, query=query)
```


### Expected behavior

The new events should be picked up by the second query after sleeping for 60 seconds (i.e. `end_time` should be the time the second query ran).

### Your project

_No response_

### Screenshots

_No response_

### OS

Mac

### Python version

3.9

### AWS SDK for pandas version

arn:aws:lambda:us-east-1:336392948345:layer:AWSSDKPandas-Python39-Arm64:5

### Additional context

_No response_


Discussion:

Resolving patch:
diff --git a/awswrangler/cloudwatch.py b/awswrangler/cloudwatch.py
--- a/awswrangler/cloudwatch.py
+++ b/awswrangler/cloudwatch.py
@@ -29,8 +29,8 @@ def _validate_args(
 def start_query(
     query: str,
     log_group_names: List[str],
-    start_time: datetime.datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=datetime.timezone.utc),
-    end_time: datetime.datetime = datetime.datetime.utcnow(),
+    start_time: Optional[datetime.datetime] = None,
+    end_time: Optional[datetime.datetime] = None,
     limit: Optional[int] = None,
     boto3_session: Optional[boto3.Session] = None,
 ) -> str:
@@ -68,10 +68,17 @@ def start_query(
 
     """
     _logger.debug("log_group_names: %s", log_group_names)
+
+    start_time = (
+        start_time if start_time else datetime.datetime(year=1970, month=1, day=1, tzinfo=datetime.timezone.utc)
+    )
+    end_time = end_time if end_time else datetime.datetime.utcnow()
+
     start_timestamp: int = int(1000 * start_time.timestamp())
     end_timestamp: int = int(1000 * end_time.timestamp())
     _logger.debug("start_timestamp: %s", start_timestamp)
     _logger.debug("end_timestamp: %s", end_timestamp)
+
     _validate_args(start_timestamp=start_timestamp, end_timestamp=end_timestamp)
     args: Dict[str, Any] = {
         "logGroupNames": log_group_names,
@@ -139,8 +146,8 @@ def wait_query(
 def run_query(
     query: str,
     log_group_names: List[str],
-    start_time: datetime.datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=datetime.timezone.utc),
-    end_time: datetime.datetime = datetime.datetime.utcnow(),
+    start_time: Optional[datetime.datetime] = None,
+    end_time: Optional[datetime.datetime] = None,
     limit: Optional[int] = None,
     boto3_session: Optional[boto3.Session] = None,
 ) -> List[List[Dict[str, str]]]:
@@ -192,8 +199,8 @@ def run_query(
 def read_logs(
     query: str,
     log_group_names: List[str],
-    start_time: datetime.datetime = datetime.datetime(year=1970, month=1, day=1, tzinfo=datetime.timezone.utc),
-    end_time: datetime.datetime = datetime.datetime.utcnow(),
+    start_time: Optional[datetime.datetime] = None,
+    end_time: Optional[datetime.datetime] = None,
     limit: Optional[int] = None,
     boto3_session: Optional[boto3.Session] = None,
 ) -> pd.DataFrame:


Test patch: