Repository: boto/botocore
Pull request #2600 (2022-01-26 01:09:56)

Tolerate custom IMDS endpoints without trailing slash
When using `AWS_EC2_METADATA_SERVICE_ENDPOINT` or `ec2_metadata_service_endpoint`, we use the url as-is without checking how it's formatted. This leads to issues with appending an IMDS path for URLs that don't end in a trailing slash. Most users aren't going to think to add that when using the override and this causes unnecessary failures.

This fix will maintain the backwards compatibility of `get_base_url` while ensuring paths are constructed correctly.

codecov-commenter: # [Codecov](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) Report
> Merging [#2600](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) (7105e4a) into [develop](https://codecov.io/gh/boto/botocore/commit/d9d5ade65eeaf01178f6a3f74865ace07164f0f3?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) (d9d5ade) will **decrease** coverage by `0.13%`.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://codecov.io/gh/boto/botocore/pull/2600/graphs/tree.svg?width=650&height=150&src=pr&token=QOLXw6XSza&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)

```diff
@@             Coverage Diff             @@
##           develop    #2600      +/-   ##
===========================================
- Coverage    95.43%   95.30%   -0.14%     
===========================================
  Files           59       59              
  Lines        11842    11708     -134     
===========================================
- Hits         11302    11158     -144     
- Misses         540      550      +10     
```


| [Impacted Files](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) | Coverage Δ | |
|---|---|---|
| [botocore/utils.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvdXRpbHMucHk=) | `95.32% <100.00%> (-0.32%)` | :arrow_down: |
| [botocore/compat.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvY29tcGF0LnB5) | `71.82% <0.00%> (-1.41%)` | :arrow_down: |
| [botocore/httpsession.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvaHR0cHNlc3Npb24ucHk=) | `90.04% <0.00%> (-1.32%)` | :arrow_down: |
| [botocore/hooks.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvaG9va3MucHk=) | `95.27% <0.00%> (-0.40%)` | :arrow_down: |
| [botocore/model.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvbW9kZWwucHk=) | `98.03% <0.00%> (-0.23%)` | :arrow_down: |
| [botocore/docs/bcdoc/restdoc.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvZG9jcy9iY2RvYy9yZXN0ZG9jLnB5) | `96.26% <0.00%> (-0.17%)` | :arrow_down: |
| [botocore/retries/bucket.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvcmV0cmllcy9idWNrZXQucHk=) | `98.48% <0.00%> (-0.09%)` | :arrow_down: |
| [botocore/loaders.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvbG9hZGVycy5weQ==) | `99.24% <0.00%> (-0.04%)` | :arrow_down: |
| [botocore/paginate.py](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto#diff-Ym90b2NvcmUvcGFnaW5hdGUucHk=) | `97.80% <0.00%> (-0.03%)` | :arrow_down: |
| ... and [13 more](https://codecov.io/gh/boto/botocore/pull/2600/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto). Last update [d9d5ade...7105e4a](https://codecov.io/gh/boto/botocore/pull/2600?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=boto).


Patch:
diff --git a/.changes/1.23.44.json b/.changes/1.23.44.json
new file mode 100644
index 000000000..f1f169e19
--- /dev/null
+++ b/.changes/1.23.44.json
@@ -0,0 +1,27 @@
+[
+  {
+    "category": "``frauddetector``",
+    "description": "Added new APIs for viewing past predictions and obtaining prediction metadata including prediction explanations: ListEventPredictions and GetEventPredictionMetadata",
+    "type": "api-change"
+  },
+  {
+    "category": "``ebs``",
+    "description": "Documentation updates for Amazon EBS Direct APIs.",
+    "type": "api-change"
+  },
+  {
+    "category": "``codeguru-reviewer``",
+    "description": "Added failure state and adjusted timeout in waiter",
+    "type": "api-change"
+  },
+  {
+    "category": "``securityhub``",
+    "description": "Adding top level Sample boolean field",
+    "type": "api-change"
+  },
+  {
+    "category": "``sagemaker``",
+    "description": "API changes relating to Fail steps in model building pipeline and add PipelineExecutionFailureReason in PipelineExecutionSummary.",
+    "type": "api-change"
+  }
+]
\ No newline at end of file
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index a6e71a850..52a9644f9 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -2,6 +2,16 @@
 CHANGELOG
 =========
 
+1.23.44
+=======
+
+* api-change:``frauddetector``: Added new APIs for viewing past predictions and obtaining prediction metadata including prediction explanations: ListEventPredictions and GetEventPredictionMetadata
+* api-change:``ebs``: Documentation updates for Amazon EBS Direct APIs.
+* api-change:``codeguru-reviewer``: Added failure state and adjusted timeout in waiter
+* api-change:``securityhub``: Adding top level Sample boolean field
+* api-change:``sagemaker``: API changes relating to Fail steps in model building pipeline and add PipelineExecutionFailureReason in PipelineExecutionSummary.
+
+
 1.23.43
 =======
 
diff --git a/botocore/__init__.py b/botocore/__init__.py
index e2d77501f..a343d2fa5 100644
--- a/botocore/__init__.py
+++ b/botocore/__init__.py
@@ -16,7 +16,7 @@ import os
 import re
 import logging
 
-__version__ = '1.23.43'
+__version__ = '1.23.44'
 
 
 class NullHandler(logging.Handler):
diff --git a/botocore/auth.py b/botocore/auth.py
index 1f760fa24..47d9fdfb8 100644
--- a/botocore/auth.py
+++ b/botocore/auth.py
@@ -27,7 +27,7 @@ from botocore.compat import (
     six, unquote, urlsplit, urlunsplit, HAS_CRT
 )
 from botocore.exceptions import NoCredentialsError
-from botocore.utils import normalize_url_path, percent_encode_sequence
+from botocore.utils import is_valid_ipv6_endpoint_url, normalize_url_path, percent_encode_sequence
 
 # Imports for backwards compatibility
 from botocore.compat import MD5_AVAILABLE # noqa
@@ -59,6 +59,8 @@ def _host_from_url(url):
     # 3) excludes userinfo
     url_parts = urlsplit(url)
     host = url_parts.hostname  # urlsplit's hostname is always lowercase
+    if is_valid_ipv6_endpoint_url(url):
+        host = '[%s]' % (host)
     default_ports = {
         'http': 80,
         'https': 443
diff --git a/botocore/data/codeguru-reviewer/2019-09-19/service-2.json b/botocore/data/codeguru-reviewer/2019-09-19/service-2.json
index 20b3993fb..7186a4d1a 100644
--- a/botocore/data/codeguru-reviewer/2019-09-19/service-2.json
+++ b/botocore/data/codeguru-reviewer/2019-09-19/service-2.json
@@ -895,6 +895,11 @@
         }
       }
     },
+    "ListRecommendationsMaxResults":{
+      "type":"integer",
+      "max":300,
+      "min":1
+    },
     "ListRecommendationsRequest":{
       "type":"structure",
       "required":["CodeReviewArn"],
@@ -906,7 +911,7 @@
           "locationName":"NextToken"
         },
         "MaxResults":{
-          "shape":"MaxResults",
+          "shape":"ListRecommendationsMaxResults",
           "documentation":"<p> The maximum number of results that are returned per call. The default is 100. </p>",
           "location":"querystring",
           "locationName":"MaxResults"
@@ -1666,7 +1671,7 @@
     },
     "Text":{
       "type":"string",
-      "max":2048,
+      "max":5000,
       "min":1
     },
     "ThirdPartySourceRepository":{
diff --git a/botocore/data/codeguru-reviewer/2019-09-19/waiters-2.json b/botocore/data/codeguru-reviewer/2019-09-19/waiters-2.json
index 7a1fd0ef9..cd1730fab 100644
--- a/botocore/data/codeguru-reviewer/2019-09-19/waiters-2.json
+++ b/botocore/data/codeguru-reviewer/2019-09-19/waiters-2.json
@@ -7,7 +7,7 @@
             "description": "Wait until a repository association is complete.",
             "operation": "DescribeRepositoryAssociation",
             "delay": 10,
-            "maxAttempts": 20,
+            "maxAttempts": 30,
             "acceptors": [
             {
                 "state": "success",
@@ -15,6 +15,12 @@
                 "argument": "RepositoryAssociation.State",
                 "expected": "Associated"
             },
+            {
+                "state": "failure",
+                "matcher": "path",
+                "argument": "RepositoryAssociation.State",
+                "expected": "Failed"
+            },
             {
                 "state": "retry",
                 "matcher": "path",
@@ -27,7 +33,7 @@
             "description": "Wait until a code review is complete.",
             "operation": "DescribeCodeReview",
             "delay": 10,
-            "maxAttempts": 60,
+            "maxAttempts": 180,
             "acceptors": [
             {
                 "state": "success",
@@ -35,6 +41,12 @@
                 "argument": "CodeReview.State",
                 "expected": "Completed"
             },
+            {
+                "state": "failure",
+                "matcher": "path",
+                "argument": "CodeReview.State",
+                "expected": "Failed"
+            },
             {
                 "state": "retry",
                 "matcher": "path",
diff --git a/botocore/data/ebs/2019-11-02/service-2.json b/botocore/data/ebs/2019-11-02/service-2.json
index 0e4010172..880e4822d 100644
--- a/botocore/data/ebs/2019-11-02/service-2.json
+++ b/botocore/data/ebs/2019-11-02/service-2.json
@@ -400,19 +400,19 @@
         },
         "NextToken":{
           "shape":"PageToken",
-          "documentation":"<p>The token to request the next page of results.</p>",
+          "documentation":"<p>The token to request the next page of results.</p> <p>If you specify <b>NextToken</b>, then <b>StartingBlockIndex</b> is ignored.</p>",
           "location":"querystring",
           "locationName":"pageToken"
         },
         "MaxResults":{
           "shape":"MaxResults",
-          "documentation":"<p>The number of results to return.</p>",
+          "documentation":"<p>The maximum number of blocks to be returned by the request.</p> <p>Even if additional blocks can be retrieved from the snapshot, the request can return less blocks than <b>MaxResults</b> or an empty array of blocks.</p> <p>To retrieve the next set of blocks from the snapshot, make another request with the returned <b>NextToken</b> value. The value of <b>NextToken</b> is <code>null</code> when there are no more blocks to return.</p>",
           "location":"querystring",
           "locationName":"maxResults"
         },
         "StartingBlockIndex":{
           "shape":"BlockIndex",
-          "documentation":"<p>The block index from which the comparison should start.</p> <p>The list in the response will start from this block index or the next valid block index in the snapshots.</p>",
+          "documentation":"<p>The block index from which the comparison should start.</p> <p>The list in the response will start from this block index or the next valid block index in the snapshots.</p> <p>If you specify <b>NextToken</b>, then <b>StartingBlockIndex</b> is ignored.</p>",
           "location":"querystring",
           "locationName":"startingBlockIndex"
         }
@@ -455,19 +455,19 @@
         },
         "NextToken":{
           "shape":"PageToken",
-          "documentation":"<p>The token to request the next page of results.</p>",
+          "documentation":"<p>The token to request the next page of results.</p> <p>If you specify <b>NextToken</b>, then <b>StartingBlockIndex</b> is ignored.</p>",
           "location":"querystring",
           "locationName":"pageToken"
         },
         "MaxResults":{
           "shape":"MaxResults",
-          "documentation":"<p>The number of results to return.</p>",
+          "documentation":"<p>The maximum number of blocks to be returned by the request.</p> <p>Even if additional blocks can be retrieved from the snapshot, the request can return less blocks than <b>MaxResults</b> or an empty array of blocks.</p> <p>To retrieve the next set of blocks from the snapshot, make another request with the returned <b>NextToken</b> value. The value of <b>NextToken</b> is <code>null</code> when there are no more blocks to return.</p>",
           "location":"querystring",
           "locationName":"maxResults"
         },
         "StartingBlockIndex":{
           "shape":"BlockIndex",
-          "documentation":"<p>The block index from which the list should start. The list in the response will start from this block index or the next valid block index in the snapshot.</p>",
+          "documentation":"<p>The block index from which the list should start. The list in the response will start from this block index or the next valid block index in the snapshot.</p> <p>If you specify <b>NextToken</b>, then <b>StartingBlockIndex</b> is ignored.</p>",
           "location":"querystring",
           "locationName":"startingBlockIndex"
         }
diff --git a/botocore/data/frauddetector/2019-11-15/service-2.json b/botocore/data/frauddetector/2019-11-15/service-2.json
index 670c9782d..30d0118d9 100644
--- a/botocore/data/frauddetector/2019-11-15/service-2.json
+++ b/botocore/data/frauddetector/2019-11-15/service-2.json
@@ -619,6 +619,23 @@
       ],
       "documentation":"<p>Evaluates an event against a detector version. If a version ID is not provided, the detector’s (<code>ACTIVE</code>) version is used.</p>"
     },
+    "GetEventPredictionMetadata":{
+      "name":"GetEventPredictionMetadata",
+      "http":{
+        "method":"POST",
+        "requestUri":"/"
+      },
+      "input":{"shape":"GetEventPredictionMetadataRequest"},
+      "output":{"shape":"GetEventPredictionMetadataResult"},
+      "errors":[
+        {"shape":"ValidationException"},
+        {"shape":"ResourceNotFoundException"},
+        {"shape":"AccessDeniedException"},
+        {"shape":"ThrottlingException"},
+        {"shape":"InternalServerException"}
+      ],
+      "documentation":"<p> Gets details of the past fraud predictions for the specified event ID, event type, detector ID, and detector version ID that was generated in the specified time period. </p>"
+    },
     "GetEventTypes":{
       "name":"GetEventTypes",
       "http":{
@@ -770,6 +787,22 @@
       ],
       "documentation":"<p>Gets all of the variables or the specific variable. This is a paginated API. Providing null <code>maxSizePerPage</code> results in retrieving maximum of 100 records per page. If you provide <code>maxSizePerPage</code> the value must be between 50 and 100. To get the next page result, a provide a pagination token from <code>GetVariablesResult</code> as part of your request. Null pagination token fetches the records from the beginning. </p>"
     },
+    "ListEventPredictions":{
+      "name":"ListEventPredictions",
+      "http":{
+        "method":"POST",
+        "requestUri":"/"
+      },
+      "input":{"shape":"ListEventPredictionsRequest"},
+      "output":{"shape":"ListEventPredictionsResult"},
+      "errors":[
+        {"shape":"ValidationException"},
+        {"shape":"AccessDeniedException"},
+        {"shape":"ThrottlingException"},
+        {"shape":"InternalServerException"}
+      ],
+      "documentation":"<p>Gets a list of past predictions. The list can be filtered by detector ID, detector version ID, event ID, event type, or by specifying a time period. If filter is not specified, the most recent prediction is returned.</p> <p>For example, the following filter lists all past predictions for <code>xyz</code> event type - <code>{ \"eventType\":{ \"value\": \"xyz\" }” } </code> </p> <p>This is a paginated API. If you provide a null <code>maxResults</code>, this action will retrieve a maximum of 10 records per page. If you provide a <code>maxResults</code>, the value must be between 50 and 100. To get the next page results, provide the <code>nextToken</code> from the response as part of your request. A null <code>nextToken</code> fetches the records from the beginning. </p>"
+    },
     "ListTagsForResource":{
       "name":"ListTagsForResource",
       "http":{
@@ -1379,6 +1412,7 @@
       "type":"list",
       "member":{"shape":"BatchPrediction"}
     },
+    "Boolean":{"type":"boolean"},
     "CancelBatchImportJobRequest":{
       "type":"structure",
       "required":["jobId"],
@@ -2257,6 +2291,88 @@
       },
       "documentation":"<p>The entity type details.</p>"
     },
+    "EvaluatedExternalModel":{
+      "type":"structure",
+      "members":{
+        "modelEndpoint":{
+          "shape":"string",
+          "documentation":"<p> The endpoint of the external (Amazon Sagemaker) model. </p>"
+        },
+        "useEventVariables":{
+          "shape":"Boolean",
+          "documentation":"<p> Indicates whether event variables were used to generate predictions. </p>"
+        },
+        "inputVariables":{
+          "shape":"MapOfStrings",
+          "documentation":"<p> Input variables use for generating predictions. </p>"
+        },
+        "outputVariables":{
+          "shape":"MapOfStrings",
+          "documentation":"<p> Output variables. </p>"
+        }
+      },
+      "documentation":"<p> The details of the external (Amazon Sagemaker) model evaluated for generating predictions. </p>"
+    },
+    "EvaluatedModelVersion":{
+      "type":"structure",
+      "members":{
+        "modelId":{
+          "shape":"string",
+          "documentation":"<p> The model ID. </p>"
+        },
+        "modelVersion":{
+          "shape":"string",
+          "documentation":"<p> The model version. </p>"
+        },
+        "modelType":{
+          "shape":"string",
+          "documentation":"<p>The model type. </p> <p>Valid values: <code>ONLINE_FRAUD_INSIGHTS</code> | <code>TRANSACTION_FRAUD_INSIGHTS</code> </p>"
+        },
+        "evaluations":{
+          "shape":"ListOfModelVersionEvaluations",
+          "documentation":"<p> Evaluations generated for the model version. </p>"
+        }
+      },
+      "documentation":"<p> The model version evaluated for generating prediction. </p>"
+    },
+    "EvaluatedRule":{
+      "type":"structure",
+      "members":{
+        "ruleId":{
+          "shape":"identifier",
+          "documentation":"<p> The rule ID. </p>"
+        },
+        "ruleVersion":{
+          "shape":"wholeNumberVersionString",
+          "documentation":"<p> The rule version. </p>"
+        },
+        "expression":{
+          "shape":"sensitiveString",
+          "documentation":"<p> The rule expression. </p>"
+        },
+        "expressionWithValues":{
+          "shape":"sensitiveString",
+          "documentation":"<p> The rule expression value. </p>"
+        },
+        "outcomes":{
+          "shape":"ListOfStrings",
+          "documentation":"<p> The rule outcome. </p>"
+        },
+        "evaluated":{
+          "shape":"Boolean",
+          "documentation":"<p> Indicates whether the rule was evaluated. </p>"
+        },
+        "matched":{
+          "shape":"Boolean",
+          "documentation":"<p> Indicates whether the rule matched. </p>"
+        }
+      },
+      "documentation":"<p> The details of the rule used for evaluating variable values. </p>"
+    },
+    "EvaluatedRuleList":{
+      "type":"list",
+      "member":{"shape":"EvaluatedRule"}
+    },
     "Event":{
       "type":"structure",
       "members":{
@@ -2303,6 +2419,42 @@
         "DISABLED"
       ]
     },
+    "EventPredictionSummary":{
+      "type":"structure",
+      "members":{
+        "eventId":{
+          "shape":"identifier",
+          "documentation":"<p> The event ID. </p>"
+        },
+        "eventTypeName":{
+          "shape":"identifier",
+          "documentation":"<p> The event type. </p>"
+        },
+        "eventTimestamp":{
+          "shape":"time",
+          "documentation":"<p> The timestamp of the event. </p>"
+        },
+        "predictionTimestamp":{
+          "shape":"time",
+          "documentation":"<p> The timestamp when the prediction was generated. </p>"
+        },
+        "detectorId":{
+          "shape":"identifier",
+          "documentation":"<p> The detector ID. </p>"
+        },
+        "detectorVersionId":{
+          "shape":"wholeNumberVersionString",
+          "documentation":"<p> The detector version ID. </p>"
+        }
+      },
+      "documentation":"<p> Information about the summary of an event prediction. </p>"
+    },
+    "EventPredictionsMaxResults":{
+      "type":"integer",
+      "box":true,
+      "max":100,
+      "min":50
+    },
     "EventType":{
       "type":"structure",
       "members":{
@@ -2356,6 +2508,24 @@
       "value":{"shape":"variableValue"},
       "min":1
     },
+    "EventVariableSummary":{
+      "type":"structure",
+      "members":{
+        "name":{
+          "shape":"sensitiveString",
+          "documentation":"<p> The event variable name. </p>"
+        },
+        "value":{
+          "shape":"sensitiveString",
+          "documentation":"<p> The value of the event variable. </p>"
+        },
+        "source":{
+          "shape":"sensitiveString",
+          "documentation":"<p> The event variable source. </p>"
+        }
+      },
+      "documentation":"<p> Information about the summary of an event variable that was evaluated for generating prediction. </p>"
+    },
     "ExternalEventsDetail":{
       "type":"structure",
       "required":[
@@ -2509,6 +2679,16 @@
       },
       "documentation":"<p>The message details.</p>"
     },
+    "FilterCondition":{
+      "type":"structure",
+      "members":{
+        "value":{
+          "shape":"filterString",
+          "documentation":"<p> A statement containing a resource property and a value to specify filter condition. </p>"
+        }
+      },
+      "documentation":"<p> A conditional statement for filtering a list of past predictions. </p>"
+    },
     "GetBatchImportJobsRequest":{
       "type":"structure",
       "members":{
@@ -2718,6 +2898,103 @@
         }
       }
     },
+    "GetEventPredictionMetadataRequest":{
+      "type":"structure",
+      "required":[
+        "eventId",
+        "eventTypeName",
+        "detectorId",
+        "detectorVersionId",
+        "predictionTimestamp"
+      ],
+      "members":{
+        "eventId":{
+          "shape":"identifier",
+          "documentation":"<p> The event ID. </p>"
+        },
+        "eventTypeName":{
+          "shape":"identifier",
+          "documentation":"<p> The event type associated with the detector specified for the prediction. </p>"
+        },
+        "detectorId":{
+          "shape":"identifier",
+          "documentation":"<p> The detector ID. </p>"
+        },
+        "detectorVersionId":{
+          "shape":"wholeNumberVersionString",
+          "documentation":"<p> The detector version ID. </p>"
+        },
+        "predictionTimestamp":{
+          "shape":"time",
+          "documentation":"<p> The timestamp that defines when the prediction was generated. </p>"
+        }
+      }
+    },
+    "GetEventPredictionMetadataResult":{
+      "type":"structure",
+      "members":{
+        "eventId":{
+          "shape":"identifier",
+          "documentation":"<p> The event ID. </p>"
+        },
+        "eventTypeName":{
+          "shape":"identifier",
+          "documentation":"<p> The event type associated with the detector specified for this prediction. </p>"
+        },
+        "entityId":{
+          "shape":"string",
+          "documentation":"<p> The entity ID. </p>"
+        },
+        "entityType":{
+          "shape":"string",
+          "documentation":"<p> The entity type. </p>"
+        },
+        "eventTimestamp":{
+          "shape":"time",
+          "documentation":"<p> The timestamp for when the prediction was generated for the associated event ID. </p>"
+        },
+        "detectorId":{
+          "shape":"identifier",
+          "documentation":"<p> The detector ID. </p>"
+        },
+        "detectorVersionId":{
+          "shape":"wholeNumberVersionString",
+          "documentation":"<p> The detector version ID. </p>"
+        },
+        "detectorVersionStatus":{
+          "shape":"string",
+          "documentation":"<p> The status of the detector version. </p>"
+        },
+        "eventVariables":{
+          "shape":"ListOfEventVariableSummaries",
+          "documentation":"<p> A list of event variables that influenced the prediction scores. </p>"
+        },
+        "rules":{
+          "shape":"EvaluatedRuleList",
+          "documentation":"<p> List of rules associated with the detector version that were used for evaluating variable values. </p>"
+        },
+        "ruleExecutionMode":{
+          "shape":"RuleExecutionMode",
+          "documentation":"<p> The execution mode of the rule used for evaluating variable values. </p>"
+        },
+        "outcomes":{
+          "shape":"ListOfStrings",
+          "documentation":"<p> The outcomes of the matched rule, based on the rule execution mode. </p>"
+        },
+        "evaluatedModelVersions":{
+          "shape":"ListOfEvaluatedModelVersions",
+          "documentation":"<p> Model versions that were evaluated for generating predictions. </p>"
+        },
+        "evaluatedExternalModels":{
+          "shape":"ListOfEvaluatedExternalModels",
+          "documentation":"<p> External (Amazon SageMaker) models that were evaluated for generating predictions. </p>"
+        },
+        "predictionTimestamp":{
+          "shape":"time",
+          "documentation":"<p>The timestamp that defines when the prediction was generated. </p>"
+        }
+      }
+    },
     "GetEventPredictionRequest":{
       "type":"structure",
       "required":[
@@ -3233,6 +3510,68 @@
       "type":"string",
       "enum":["DETECTORPL"]
     },
+    "ListEventPredictionsRequest":{
+      "type":"structure",
+      "members":{
+        "eventId":{
+          "shape":"FilterCondition",
+          "documentation":"<p> The event ID. </p>"
+        },
+        "eventType":{
+          "shape":"FilterCondition",
+          "documentation":"<p> The event type associated with the detector. </p>"
+        },
+        "detectorId":{
+          "shape":"FilterCondition",
+          "documentation":"<p> The detector ID. </p>"
+        },
+        "detectorVersionId":{
+          "shape":"FilterCondition",
+          "documentation":"<p> The detector version ID. </p>"
+        },
+        "predictionTimeRange":{
+          "shape":"PredictionTimeRange",
+          "documentation":"<p> The time period for when the predictions were generated. </p>"
+        },
+        "nextToken":{
+          "shape":"string",
+          "documentation":"<p> Identifies the next page of results to return. Use the token to make the call again to retrieve the next page. Keep all other arguments unchanged. Each pagination token expires after 24 hours. </p>"
+        },
+        "maxResults":{
+          "shape":"EventPredictionsMaxResults",
+          "documentation":"<p> The maximum number of predictions to return for the request. </p>"
+        }
+      }
+    },
+    "ListEventPredictionsResult":{
+      "type":"structure",
+      "members":{
+        "eventPredictionSummaries":{
+          "shape":"ListOfEventPredictionSummaries",
+          "documentation":"<p> The summary of the past predictions. </p>"
+        },
+        "nextToken":{
+          "shape":"string",
+          "documentation":"<p> Identifies the next page of results to return. Use the token to make the call again to retrieve the next page. Keep all other arguments unchanged. Each pagination token expires after 24 hours. </p>"
+        }
+      }
+    },
+    "ListOfEvaluatedExternalModels":{
+      "type":"list",
+      "member":{"shape":"EvaluatedExternalModel"}
+    },
+    "ListOfEvaluatedModelVersions":{
+      "type":"list",
+      "member":{"shape":"EvaluatedModelVersion"}
+    },
+    "ListOfEventPredictionSummaries":{
+      "type":"list",
+      "member":{"shape":"EventPredictionSummary"}
+    },
+    "ListOfEventVariableSummaries":{
+      "type":"list",
+      "member":{"shape":"EventVariableSummary"}
+    },
     "ListOfExternalModelOutputs":{
       "type":"list",
       "member":{"shape":"ExternalModelOutputs"}
@@ -3245,6 +3584,10 @@
       "type":"list",
       "member":{"shape":"ModelScores"}
     },
+    "ListOfModelVersionEvaluations":{
+      "type":"list",
+      "member":{"shape":"ModelVersionEvaluation"}
+    },
     "ListOfModelVersions":{
       "type":"list",
       "member":{"shape":"ModelVersion"}
@@ -3312,6 +3655,12 @@
       "documentation":"<p>The log odds metric details.</p>"
     },
     "Long":{"type":"long"},
+    "MapOfStrings":{
+      "type":"map",
+      "key":{"shape":"string"},
+      "value":{"shape":"string"},
+      "sensitive":true
+    },
     "MetricDataPoint":{
       "type":"structure",
       "members":{
@@ -3560,6 +3909,24 @@
       },
       "documentation":"<p>The details of the model version.</p>"
     },
+    "ModelVersionEvaluation":{
+      "type":"structure",
+      "members":{
+        "outputVariableName":{
+          "shape":"string",
+          "documentation":"<p> The output variable name. </p>"
+        },
+        "evaluationScore":{
+          "shape":"string",
+          "documentation":"<p> The evaluation score generated for the model version. </p>"
+        },
+        "predictionExplanations":{
+          "shape":"PredictionExplanations",
+          "documentation":"<p> The prediction explanations generated for the model version. </p>"
+        }
+      },
+      "documentation":"<p> The model version evalutions. </p>"
+    },
     "ModelVersionStatus":{
       "type":"string",
       "enum":[
@@ -3615,6 +3982,34 @@
       "max":100,
       "min":50
     },
+    "PredictionExplanations":{
+      "type":"structure",
+      "members":{
+        "variableImpactExplanations":{
+          "shape":"listOfVariableImpactExplanations",
+          "documentation":"<p> The details of the event variable's impact on the prediction score. </p>"
+        }
+      },
+      "documentation":"<p> The prediction explanations that provide insight into how each event variable impacted the model version's fraud prediction score. </p>"
+    },
+    "PredictionTimeRange":{
+      "type":"structure",
+      "required":[
+        "startTime",
+        "endTime"
+      ],
+      "members":{
+        "startTime":{
+          "shape":"time",
+          "documentation":"<p> The start time of the time period for when the predictions were generated. </p>"
+        },
+        "endTime":{
+          "shape":"time",
+          "documentation":"<p> The end time of the time period for when the predictions were generated. </p>"
+        }
+      },
+      "documentation":"<p> The time period for when the predictions were generated. </p>"
+    },
     "PutDetectorRequest":{
       "type":"structure",
       "required":[
@@ -4547,6 +4942,24 @@
       "max":25,
       "min":1
     },
+    "VariableImpactExplanation":{
+      "type":"structure",
+      "members":{
+        "eventVariableName":{
+          "shape":"string",
+          "documentation":"<p> The event variable name. </p>"
+        },
+        "relativeImpact":{
+          "shape":"string",
+          "documentation":"<p> The event variable's relative impact in terms of magnitude on the prediction scores. The relative impact values consist of a numerical rating (0-5, 5 being the highest) and direction (increased/decreased) impact of the fraud risk. </p>"
+        },
+        "logOddsImpact":{
+          "shape":"float",
+          "documentation":"<p> The raw, uninterpreted value represented as log-odds of the fraud. These values are usually between -10 to +10, but range from - infinity to + infinity.</p> <ul> <li> <p>A positive value indicates that the variable drove the risk score up.</p> </li> <li> <p>A negative value indicates that the variable drove the risk score down.</p> </li> </ul>"
+        }
+      },
+      "documentation":"<p> The details of the event variable's impact on the prediction score. </p>"
+    },
     "VariableImportanceMetrics":{
       "type":"structure",
       "members":{
@@ -4635,6 +5048,12 @@
       "type":"list",
       "member":{"shape":"FileValidationMessage"}
     },
+    "filterString":{
+      "type":"string",
+      "max":256,
+      "min":1,
+      "pattern":"^[0-9A-Za-z_-]+$"
+    },
     "float":{"type":"float"},
     "floatVersionString":{
       "type":"string",
@@ -4680,6 +5099,10 @@
       "type":"list",
       "member":{"shape":"Entity"}
     },
+    "listOfVariableImpactExplanations":{
+      "type":"list",
+      "member":{"shape":"VariableImpactExplanation"}
+    },
     "metricDataPointsList":{
       "type":"list",
       "member":{"shape":"MetricDataPoint"}
@@ -4727,6 +5150,10 @@
       "min":1,
       "pattern":"^[0-9A-Za-z_-]+$"
     },
+    "sensitiveString":{
+      "type":"string",
+      "sensitive":true
+    },
     "string":{"type":"string"},
     "tagKey":{
       "type":"string",
diff --git a/botocore/data/sagemaker/2017-07-24/service-2.json b/botocore/data/sagemaker/2017-07-24/service-2.json
index c520eca0f..5785b03f1 100644
--- a/botocore/data/sagemaker/2017-07-24/service-2.json
+++ b/botocore/data/sagemaker/2017-07-24/service-2.json
@@ -2669,7 +2669,7 @@
       "errors":[
         {"shape":"ResourceNotFound"}
       ],
-      "documentation":"<p>Stops a pipeline execution.</p> <p> <b>Callback Step</b> </p> <p>A pipeline execution won't stop while a callback step is running. When you call <code>StopPipelineExecution</code> on a pipeline execution with a running callback step, Amazon SageMaker Pipelines sends an additional Amazon SQS message to the specified SQS queue. The body of the SQS message contains a \"Status\" field which is set to \"Stopping\".</p> <p>You should add logic to your Amazon SQS message consumer to take any needed action (for example, resource cleanup) upon receipt of the message followed by a call to <code>SendPipelineExecutionStepSuccess</code> or <code>SendPipelineExecutionStepFailure</code>.</p> <p>Only when Amazon SageMaker Pipelines receives one of these calls will it stop the pipeline execution.</p> <p> <b>Lambda Step</b> </p> <p>A pipeline execution can't be stopped while a lambda step is running because the Lambda function invoked by the lambda step can't be stopped. If you attempt to stop the execution while the Lambda function is running, the pipeline waits for the Lambda function to finish or until the timeout is hit, whichever occurs first, and then stops. If the Lambda function finishes, the pipeline execution status is <code>Stopped</code>. If the timeout is hit the pipeline execution status is <code>Failed</code>.</p>"
+      "documentation":"<p>Stops a pipeline execution.</p> <p> <b>Callback Step</b> </p> <p>A pipeline execution won't stop while a callback step is running. When you call <code>StopPipelineExecution</code> on a pipeline execution with a running callback step, SageMaker Pipelines sends an additional Amazon SQS message to the specified SQS queue. The body of the SQS message contains a \"Status\" field which is set to \"Stopping\".</p> <p>You should add logic to your Amazon SQS message consumer to take any needed action (for example, resource cleanup) upon receipt of the message followed by a call to <code>SendPipelineExecutionStepSuccess</code> or <code>SendPipelineExecutionStepFailure</code>.</p> <p>Only when SageMaker Pipelines receives one of these calls will it stop the pipeline execution.</p> <p> <b>Lambda Step</b> </p> <p>A pipeline execution can't be stopped while a lambda step is running because the Lambda function invoked by the lambda step can't be stopped. If you attempt to stop the execution while the Lambda function is running, the pipeline waits for the Lambda function to finish or until the timeout is hit, whichever occurs first, and then stops. If the Lambda function finishes, the pipeline execution status is <code>Stopped</code>. If the timeout is hit the pipeline execution status is <code>Failed</code>.</p>"
     },
     "StopProcessingJob":{
       "name":"StopProcessingJob",
@@ -6153,7 +6153,7 @@
         },
         "AsyncInferenceConfig":{
           "shape":"AsyncInferenceConfig",
-          "documentation":"<p>Specifies configuration for how an endpoint performs asynchronous inference. This is a required field in order for your Endpoint to be invoked using <a href=\"https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpoint.html\"> <code>InvokeEndpointAsync</code> </a>.</p>"
+          "documentation":"<p>Specifies configuration for how an endpoint performs asynchronous inference. This is a required field in order for your Endpoint to be invoked using <a href=\"https://docs.aws.amazon.com/sagemaker/latest/APIReference/API_runtime_InvokeEndpointAsync.html\">InvokeEndpointAsync</a>.</p>"
         }
       }
     },
@@ -13087,6 +13087,16 @@
       "type":"string",
       "min":1
     },
+    "FailStepMetadata":{
+      "type":"structure",
+      "members":{
+        "ErrorMessage":{
+          "shape":"String3072",
+          "documentation":"<p>A message that you define and then is processed and rendered by the Fail step when the error occurs.</p>"
+        }
+      },
+      "documentation":"<p>The container for the metadata for Fail step.</p>"
+    },
     "FailureReason":{
       "type":"string",
       "max":1024
@@ -17193,7 +17203,7 @@
         },
         "ModelPackageType":{
           "shape":"ModelPackageType",
-          "documentation":"<p>A filter that returns onlyl the model packages of the specified type. This can be one of the following values.</p> <ul> <li> <p> <code>VERSIONED</code> - List only versioned models.</p> </li> <li> <p> <code>UNVERSIONED</code> - List only unversioined models.</p> </li> <li> <p> <code>BOTH</code> - List both versioned and unversioned models.</p> </li> </ul>"
+          "documentation":"<p>A filter that returns only the model packages of the specified type. This can be one of the following values.</p> <ul> <li> <p> <code>UNVERSIONED</code> - List only unversioined models. This is the default value if no <code>ModelPackageType</code> is specified.</p> </li> <li> <p> <code>VERSIONED</code> - List only versioned models.</p> </li> <li> <p> <code>BOTH</code> - List both versioned and unversioned models.</p> </li> </ul>"
         },
         "NextToken":{
           "shape":"NextToken",
@@ -21121,7 +21131,7 @@
         },
         "AttemptCount":{
           "shape":"IntegerValue",
-          "documentation":"<p>The current attempt of the execution step. For more information, see <a href=\"https://docs.aws.amazon.com/https:/docs.aws.amazon.com/sagemaker/latest/dg/pipelines-retry-policy.html\">Retry Policy for Amazon SageMaker Pipelines steps</a>.</p>"
+          "documentation":"<p>The current attempt of the execution step. For more information, see <a href=\"https://docs.aws.amazon.com/sagemaker/latest/dg/pipelines-retry-policy.html\">Retry Policy for SageMaker Pipelines steps</a>.</p>"
         },
         "FailureReason":{
           "shape":"FailureReason",
@@ -21190,6 +21200,10 @@
         "EMR":{
           "shape":"EMRStepMetadata",
           "documentation":"<p>The configurations and outcomes of an EMR step execution.</p>"
+        },
+        "Fail":{
+          "shape":"FailStepMetadata",
+          "documentation":"<p>The configurations and outcomes of a Fail step execution.</p>"
         }
       },
       "documentation":"<p>Metadata for a step execution.</p>"
@@ -21216,6 +21230,10 @@
         "PipelineExecutionDisplayName":{
           "shape":"PipelineExecutionName",
           "documentation":"<p>The display name of the pipeline execution.</p>"
+        },
+        "PipelineExecutionFailureReason":{
+          "shape":"String3072",
+          "documentation":"<p>A message generated by SageMaker Pipelines describing why the pipeline execution failed.</p>"
         }
       },
       "documentation":"<p>A pipeline execution summary.</p>"
@@ -24077,6 +24095,10 @@
       "type":"string",
       "max":256
     },
+    "String3072":{
+      "type":"string",
+      "max":3072
+    },
     "String40":{
       "type":"string",
       "max":40
diff --git a/botocore/data/securityhub/2018-10-26/service-2.json b/botocore/data/securityhub/2018-10-26/service-2.json
index fad21a4a4..14d5c9cf8 100644
--- a/botocore/data/securityhub/2018-10-26/service-2.json
+++ b/botocore/data/securityhub/2018-10-26/service-2.json
@@ -9309,6 +9309,10 @@
         "FindingProviderFields":{
           "shape":"FindingProviderFields",
           "documentation":"<p>In a <code>BatchImportFindings</code> request, finding providers use <code>FindingProviderFields</code> to provide and update their own values for confidence, criticality, related findings, severity, and types.</p>"
+        },
+        "Sample":{
+          "shape":"Boolean",
+          "documentation":"<p>Indicates whether the finding is a sample finding.</p>"
         }
       },
       "documentation":"<p>Provides consistent format for the contents of the Security Hub-aggregated findings. <code>AwsSecurityFinding</code> format enables you to share findings between Amazon Web Services security services and third-party solutions, and security standards checks.</p> <note> <p>A finding is a potential security issue generated either by Amazon Web Services services or by the integrated third-party solutions and standards checks.</p> </note>"
@@ -9699,6 +9703,10 @@
         "FindingProviderFieldsTypes":{
           "shape":"StringFilterList",
           "documentation":"<p>One or more finding types that the finding provider assigned to the finding. Uses the format of <code>namespace/category/classifier</code> that classify a finding.</p> <p>Valid namespace values are: Software and Configuration Checks | TTPs | Effects | Unusual Behaviors | Sensitive Data Identifications</p>"
+        },
+        "Sample":{
+          "shape":"BooleanFilterList",
+          "documentation":"<p>Indicates whether or not sample findings are included in the filter results.</p>"
         }
       },
       "documentation":"<p>A collection of attributes that are applied to all active Security Hub-aggregated findings and that result in a subset of findings that are included in this insight.</p> <p>You can filter by up to 10 finding attributes. For each attribute, you can provide up to 20 filter values.</p>"
@@ -10230,6 +10238,20 @@
       "member":{"shape":"BatchUpdateFindingsUnprocessedFinding"}
     },
     "Boolean":{"type":"boolean"},
+    "BooleanFilter":{
+      "type":"structure",
+      "members":{
+        "Value":{
+          "shape":"Boolean",
+          "documentation":"<p>The value of the boolean.</p>"
+        }
+      },
+      "documentation":"<p>Boolean filter for querying findings.</p>"
+    },
+    "BooleanFilterList":{
+      "type":"list",
+      "member":{"shape":"BooleanFilter"}
+    },
     "CategoryList":{
       "type":"list",
       "member":{"shape":"NonEmptyString"}
diff --git a/botocore/utils.py b/botocore/utils.py
index 8a32cf6ea..eca5885b9 100644
--- a/botocore/utils.py
+++ b/botocore/utils.py
@@ -416,9 +416,15 @@ class IMDSFetcher(object):
 
         return chosen_base_url
 
+    def _construct_url(self, path):
+        sep = ''
+        if self._base_url and not self._base_url.endswith('/'):
+            sep = '/'
+        return f'{self._base_url}{sep}{path}'
+
     def _fetch_metadata_token(self):
         self._assert_enabled()
-        url = self._base_url + self._TOKEN_PATH
+        url = self._construct_url(self._TOKEN_PATH)
         headers = {
             'x-aws-ec2-metadata-token-ttl-seconds': self._TOKEN_TTL,
         }
@@ -466,7 +472,7 @@ class IMDSFetcher(object):
         self._assert_enabled()
         if retry_func is None:
             retry_func = self._default_retry
-        url = self._base_url + url_path
+        url = self._construct_url(url_path)
         headers = {}
         if token is not None:
             headers['x-aws-ec2-metadata-token'] = token
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 71f30b982..bd1ce8d9c 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -54,7 +54,7 @@ copyright = u'2013, Mitch Garnaat'
 # The short X.Y version.
 version = '1.23.'
 # The full version, including alpha/beta/rc tags.
-release = '1.23.43'
+release = '1.23.44'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.


Test patch:
diff --git a/tests/unit/test_auth_sigv4.py b/tests/unit/test_auth_sigv4.py
index 7c253ee9b..5eff95368 100644
--- a/tests/unit/test_auth_sigv4.py
+++ b/tests/unit/test_auth_sigv4.py
@@ -31,3 +31,24 @@ class TestSigV4Auth(unittest.TestCase):
         request = AWSRequest(method='GET', url=endpoint)
         headers_to_sign = self.sigv4.headers_to_sign(request)
         self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_without_port(self):
+        endpoint = 'http://[::1]'
+        expected_host = '[::1]'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_with_default_port(self):
+        endpoint = 'http://[::1]:80'
+        expected_host = '[::1]'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))
+
+    def test_signed_host_is_ipv6_with_explicit_port(self):
+        endpoint = 'http://[::1]:6789'
+        expected_host = '[::1]:6789'
+        request = AWSRequest(method='GET', url=endpoint)
+        headers_to_sign = self.sigv4.headers_to_sign(request)
+        self.assertEqual(expected_host, headers_to_sign.get('host'))
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
index c021cce0e..04ed045cf 100644
--- a/tests/unit/test_utils.py
+++ b/tests/unit/test_utils.py
@@ -2786,6 +2786,16 @@ class TestIMDSRegionProvider(unittest.TestCase):
         args, _ = send.call_args
         self.assertIn('http://myendpoint/', args[0].url)
 
+    @mock.patch('botocore.httpsession.URLLib3Session.send')
+    def test_can_set_imds_service_endpoint_custom(self, send):
+        session = Session()
+        session.set_config_variable(
+            'ec2_metadata_service_endpoint', 'http://myendpoint')
+        provider = IMDSRegionProvider(session)
+        provider.provide()
+        args, _ = send.call_args
+        self.assertIn('http://myendpoint/latest/meta-data', args[0].url)
+
     @mock.patch('botocore.httpsession.URLLib3Session.send')
     def test_imds_service_endpoint_overrides_ipv6_endpoint(self, send):
         session = Session()