Repository: stravalib/stravalib

Issue:
unable to upload_activity if its bytes type
When calling `client.upload_activity` and `activity_file` type is `bytes`, 
I get this error 
`Invalid type specified for activity_file: bytes`

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/stravalib/stravalib/pull/309?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib) Report
> Merging [#309](https://codecov.io/gh/stravalib/stravalib/pull/309?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib) (4b050d2) into [master](https://codecov.io/gh/stravalib/stravalib/commit/19e051ed0d7f52b7f3fc17522eeb0c1b0f3a991f?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib) (19e051e) will **increase** coverage by `0.11%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master     #309      +/-   ##
==========================================
+ Coverage   73.18%   73.30%   +0.11%     
==========================================
  Files           9        9              
  Lines        1671     1671              
  Branches      221      221              
==========================================
+ Hits         1223     1225       +2     
+ Misses        398      397       -1     
+ Partials       50       49       -1     
```


| [Impacted Files](https://codecov.io/gh/stravalib/stravalib/pull/309?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib) | Coverage Δ | |
|---|---|---|
| [stravalib/client.py](https://codecov.io/gh/stravalib/stravalib/pull/309?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib#diff-c3RyYXZhbGliL2NsaWVudC5weQ==) | `56.03% <100.00%> (+0.45%)` | :arrow_up: |

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=stravalib)

lwasser: hey @gitexel thank you for this pr!

@jsamoocha can you show me where ou see the bot tried to commit here? i only see two commits and then it errored. it error because the config file is NOT here yet. But the bot it setup to run on the repository - it's a github based installation on the entire repo so it needs the config file to run

<img width="940" alt="Screen Shot 2023-01-06 at 1 24 28 PM" src="https://user-images.githubusercontent.com/7649194/211093774-2354f953-df70-4464-86c2-b936fcb402e8.png">

if you see something different please say the word! we probably should just add the config here as well. i don't know that there is a way to customize the bot to ONLY run on specific branches without a config file but i'll look into that if you want to hold off on running code format pr's here. 
lwasser: i might be able to set it up here to 

1. NOT fix pr's ?
2. OR skip all of the ci checks - https://pre-commit.ci/#configuration-skip  
but maybe we just should add it here and we could turn off the auto pr feature and run it manually on pr's before they are merged (if we are worried about it) i am not worried about it at this point!
jsamoocha: @lwasser I see now how pre-commit.ci is monitoring the whole repo and cannot be configured to only monitor specific branches for changes. However, I don't think we want it to make extra commits on PRs on master (yet). That's why we merged it into the `pydantic_model` feature branch.

Can we solve this pragmatically by adding an "empty" (but valid) .pre-commit-config.yaml to master? That way, CI will pass but will not make additional commits (yet). I'll make a new tiny PR to propose this.

Patch:
diff --git a/changelog.md b/changelog.md
index 9350dcf..1871743 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,7 @@
 # Change Log
 
 ## Unreleased
+* Add: Support uploading `activity_file` object with type `bytes` (@gitexel, #308)
 
 ## v1.1.0
 
diff --git a/stravalib/client.py b/stravalib/client.py
index e2ac772..fa23a81 100644
--- a/stravalib/client.py
+++ b/stravalib/client.py
@@ -694,7 +694,7 @@ class Client(object):
         https://developers.strava.com/docs/reference/#api-Uploads-createUpload
 
         :param activity_file: The file object to upload or file contents.
-        :type activity_file: file or str
+        :type activity_file: TextIOWrapper, str or bytes
 
         :param data_type: File format for upload. Possible values: fit, fit.gz, tcx, tcx.gz, gpx, gpx.gz
         :type data_type: str
@@ -734,7 +734,7 @@ class Client(object):
         if not hasattr(activity_file, 'read'):
             if isinstance(activity_file, str):
                 activity_file = BytesIO(activity_file.encode('utf-8'))
-            elif isinstance(activity_file, str):
+            elif isinstance(activity_file, bytes):
                 activity_file = BytesIO(activity_file)
             else:
                 raise TypeError("Invalid type specified for activity_file: {0}".format(type(activity_file)))


Test patch:
diff --git a/stravalib/tests/integration/test_client.py b/stravalib/tests/integration/test_client.py
index 30d1812..10aa27d 100644
--- a/stravalib/tests/integration/test_client.py
+++ b/stravalib/tests/integration/test_client.py
@@ -83,6 +83,7 @@ def test_update_activity(
     (
         ('file', 'tcx', {}, {'data_type': 'tcx'}, None, None),
         ('str', 'tcx', {}, {'data_type': 'tcx'}, None, None),
+        ('bytes', 'tcx', {}, {'data_type': 'tcx'}, None, None),
         ('not_supported', 'tcx', {}, {}, None, TypeError),
         ('file', 'invalid', {}, {}, None, ValueError),
         ('file', 'tcx', {'name': 'name'}, {'data_type': 'tcx', 'name': 'name'}, None, None),
@@ -135,6 +136,8 @@ def test_upload_activity(
             _call_upload(f)
         elif activity_file_type == 'str':
             _call_upload(f.read())
+        elif activity_file_type == 'bytes':
+            _call_upload(f.read().encode('utf-8'))
         else:
             _call_upload({})
 
diff --git a/stravalib/tests/unit/test_client_utils.py b/stravalib/tests/unit/test_client_utils.py
index 88a8a8a..e3e6cd4 100644
--- a/stravalib/tests/unit/test_client_utils.py
+++ b/stravalib/tests/unit/test_client_utils.py
@@ -1,5 +1,8 @@
+import os
+from unittest import mock
+
 from stravalib.client import Client
-from stravalib.tests import TestBase
+from stravalib.tests import TestBase, RESOURCES_DIR
 import datetime
 import pytz
 from urllib import parse as urlparse
@@ -54,3 +57,32 @@ class ClientAuthorizationUrlTest(TestBase):
         self.assertEqual(self.get_url_param(url, "client_id"), "1")
         self.assertEqual(self.get_url_param(url, "redirect_uri"), "www.example.com")
         self.assertEqual(self.get_url_param(url, "approval_prompt"), "auto")
+
+
+class TestClientUploadActivity(TestBase):
+    client = Client()
+
+    def test_upload_activity_file_with_different_types(self):
+        """
+        Test uploading an activity with different activity_file object types.
+
+        """
+
+        with mock.patch("stravalib.protocol.ApiV3.post", return_value={}), open(
+            os.path.join(RESOURCES_DIR, "sample.tcx")
+        ) as fp:
+            # test activity_file with type TextIOWrapper
+            uploader = self.client.upload_activity(fp, data_type="tcx")
+            self.assertTrue(uploader.is_processing)
+
+            # test activity_file with type str
+            uploader = self.client.upload_activity(
+                fp.read(), data_type="tcx", activity_type="ride"
+            )
+            self.assertTrue(uploader.is_processing)
+
+            # test activity_file with type bytes
+            uploader = self.client.upload_activity(
+                fp.read().encode("utf-8"), data_type="tcx", activity_type="ride"
+            )
+            self.assertTrue(uploader.is_processing)