Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Google Photos service for uploading content #124956

Merged
merged 5 commits into from
Aug 31, 2024

Conversation

allenporter
Copy link
Contributor

Proposed change

Add Google Photos service for uploading content. This adds additonal scopes for uploading content and only enables capabilities based on the authorized scopes.

Milestones for future PRs include:

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New integration (thank you!)
  • New feature (which adds functionality to an existing integration)
  • Deprecation (breaking change to happen in the future)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

  • This PR fixes or closes issue: fixes #
  • This PR is related to issue:
  • Link to documentation pull request:

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • I have followed the perfect PR recommendations
  • The code has been formatted using Ruff (ruff format homeassistant tests)
  • Tests have been added to verify that the new code works.

If user exposed functionality or configuration variables are added/changed:

If the code communicates with devices, web services, or third-party tools:

  • The manifest file has all fields filled out correctly.
    Updated and included derived files by running: python3 -m script.hassfest.
  • New or updated dependencies have been added to requirements_all.txt.
    Updated by running python3 -m script.gen_requirements_all.
  • For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.

To help with the load of incoming pull requests:

Copy link

@home-assistant home-assistant bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a merge conflict.

@home-assistant
Copy link

Please take a look at the requested changes, and use the Ready for review button when you are done, thanks 👍

Learn more about our pull request process.

@allenporter allenporter force-pushed the google_photos_upload branch from fee0aed to 556d54c Compare August 31, 2024 03:31
@allenporter allenporter marked this pull request as ready for review August 31, 2024 03:32
homeassistant/components/google_photos/__init__.py Outdated Show resolved Hide resolved
homeassistant/components/google_photos/__init__.py Outdated Show resolved Hide resolved
homeassistant/components/google_photos/services.yaml Outdated Show resolved Hide resolved
@home-assistant home-assistant bot marked this pull request as draft August 31, 2024 07:56
@allenporter allenporter marked this pull request as ready for review August 31, 2024 14:20
@home-assistant home-assistant bot requested a review from joostlek August 31, 2024 14:20
translation_key="no_access_to_path",
translation_placeholders={"filename": filename},
)
if not Path(filename).exists():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I/O inside the event loop. I would move this whole for block inside a single executor job. That way you also don't have to jump between asyncio and executor land if there are multiple files.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Member

@balloob balloob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. 1 minor comment.

@balloob balloob merged commit ef84a88 into home-assistant:dev Aug 31, 2024
26 checks passed
token = await self.async_get_access_token()
session = aiohttp_client.async_get_clientsession(self._hass)
try:
result = await session.post(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want all transport calls to be part of a 3rd party library.

Copy link
Contributor Author

@allenporter allenporter Aug 31, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was definitely considering if its worth extracting a library for this or not.

Would you expect a library for this single upload call or a wrapper for the whole library including the other google photos api calls? In general the google photos libraries are already pretty thin anyway so thinking it makes sense to move out everything.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the library could handle the async interface that is now in the integration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is https://github.com/omarryhan/aiogoogle Async Google API Client + Async Google Auth. Note I have no experience with it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think aiogoogle is fine, though still low level given how generic it is, like the google python library. Not bad at all, just still quite low level. I looked at this in the past for calendar:
#70140 (comment)
In that case, i ended up also providing local sync for google calendar. Not sure that we'll want to do that for Photos though.

I started this https://github.com/allenporter/python-google-photos-library-api copying the patterns of the google nest and calendar libraries i maintain and will incorporate your feedback into it.

client_api = config_entry.runtime_data
upload_tasks = []
file_results = await hass.async_add_executor_job(
_read_file_contents, hass, call.data[CONF_FILENAME]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should you have multiple tasks like you have for the upload below? Should the reading happen together with the upload?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An advantage of moving the reading together with the upload, and if you support resumable uploads, is that you could support uploading large files. Right now all the files to be uploaded need to fit in memory before even starting the upload.

Copy link
Contributor Author

@allenporter allenporter Sep 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't love reading all the files at once, and agree read + file upload together makes sense. e.g. https://docs.aiohttp.org/en/stable/client_quickstart.html#streaming-uploads

Having some sort of limit on # of concurrent uploads could be helpful for throttling as well.

Perhaps can handle in the new library also.

for mime_type, content in file_results:
upload_tasks.append(client_api.upload_content(content, mime_type))
upload_tokens = await asyncio.gather(*upload_tasks)
media_ids = await client_api.create_media_items(upload_tokens)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to support partial successes? Right now if a single upload fails the code will never call batchCreate. You could call batchCreate for the ones that succeeded but then you would also need to change the API response at line 105 below. Not sure it's worth the complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to support partial success given it's an advanced technique for callers to handle. While it would be more efficient, it would mean the API for service responses is more complex and was decided against generally in home-assistant/architecture#777

Maybe could be considered if some sort of on_error handler is implemented
home-assistant/architecture#845 that way the default behavior is fail closed with an option to resume if needed?

An alternative could be to not support multiple files at all in a single call which would also be ok...

@@ -70,6 +72,40 @@ async def list_media_items(
)
return await self._execute(cmd)

async def upload_content(self, content: bytes, mime_type: str) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API also supports resumable uploads which are more reliable and can be retried. It would add a lot of complexity that I'm not sure it's worth it. Maybe something to consider if you extract this to a 3rd party library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, agreed it would be cool, though maybe not needed. I could see this being pretty helpful for larger video use cases. I agree that having the 3rd party library implement this could be the way to go, and maybe it could work like a little backup producer/consumer queue.

@github-actions github-actions bot locked and limited conversation to collaborators Sep 3, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants