### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current Behavior

When performing a multipart upload, sending part number 10000, because [this check](https://github.com/localstack/localstack/blob/d23f0717cc66c0cc8a61b6b85e7531a6f4a03f96/localstack/services/s3/provider.py#L792C88-L792C88) in s3/provider.py is wrong.

### Expected Behavior

Part number 10000 is accepted

### How are you starting LocalStack?

With a docker-compose file

### Steps To Reproduce

#### How are you starting localstack (e.g., `bin/localstack` command, arguments, or `docker-compose.yml`)

    docker run localstack/localstack

#### Client commands (e.g., AWS SDK code snippet, or sequence of "awslocal" commands)

I'm using AWS .NET SDK:

        using var inputStream = new MemoryStream(buffer, 0, readBytes, false);

        var uploadRequest = new UploadPartRequest
        {
            BucketName = bucketName,
            InputStream = inputStream,
            Key = key,
            PartNumber = partNumber,
            PartSize = readBytes,
            UploadId = uploadId,
        };

        await _client.UploadPartAsync(uploadRequest);


### Environment

```markdown
- OS: Ubuntu 22.04
- LocalStack: 2.2
```


### Anything else?

As referenced above, the bug is in [this line](https://github.com/localstack/localstack/blob/d23f0717cc66c0cc8a61b6b85e7531a6f4a03f96/localstack/services/s3/provider.py#L792C88-L792C88). It should read

    elif (part_number := request.get("PartNumber", 0)) < 1 or part_number > 10000:
self, context: RequestContext, bucket: BucketName, expected_bucket_owner: AccountId = None
    ) -> None:
        call_moto(context)
        self._clear_bucket_from_store(bucket)
        self._cors_handler.invalidate_cache()
    def get_bucket_location(
        self, context: RequestContext, bucket: BucketName, expected_bucket_owner: AccountId = None
    ) -> GetBucketLocationOutput:
        """
        When implementing the ASF provider, this operation is implemented because:
        - The spec defines a root element GetBucketLocationOutput containing a LocationConstraint member, where
          S3 actually just returns the LocationConstraint on the root level (only operation so far that we know of).
        - We circumvent the root level element here by patching the spec such that this operation returns a
          single "payload" (the XML body response), which causes the serializer to directly take the payload element.
        - The above "hack" causes the fix in the serializer to not be picked up here as we're passing the XML body as
          the payload, which is why we need to manually do this here by manipulating the string.
        Botocore implements this hack for parsing the response in `botocore.handlers.py#parse_get_bucket_location`
        """
        response = call_moto(context)
        location_constraint_xml = response["LocationConstraint"]
        xml_root_end = location_constraint_xml.find(">") + 1
        location_constraint_xml = (
            f"{location_constraint_xml[:xml_root_end]}\n{location_constraint_xml[xml_root_end:]}"
        )
        response["LocationConstraint"] = location_constraint_xml[:]
        return response
    @handler("ListObjects", expand=False)
    def list_objects(