> built 2026-09-22 15:28 UTC from 14764ec (master) · pydrivedol 0.0.7. Details: build_info.json

# index.html.md

<!-- generated by epythet -->

# pydrivedol

> Google Drive Data Object Layer - Pythonic mapping interfaces to Google Drive

[![Python](https://img.shields.io/badge/python-3.7+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)]()

**pydrivedol** provides clean, Pythonic `Mapping` and `MutableMapping` interfaces to Google Drive, following the design patterns of the [`dol` package](https://github.com/i2mint/dol). Access Google Drive files and folders as if they were dictionaries!

## Features

- 🚀 **Simple public downloads** - No API setup required for public files
- 🔒 **Private files too** - Pass an authenticated `drive=` to reach files shared only with you
- 🗂️ **Files as dict** - `GDFiles` keyed by file id *or* Drive URL
- 📂 **Folder as dict** - Browse folders with `dict`-like interface
- 💾 **Read/write operations** - Full CRUD support through mapping protocol
- 🔄 **Recursive traversal** - Control depth with `max_levels`
- 🎯 **Minimal boilerplate** - Follows `dol` patterns you already know
- 🔐 **OAuth2 handled** - Simple authentication flow

## Installation

```bash
pip install pydrive2 requests
```

Then install pydrivedol:

```bash
pip install pydrivedol  # When published to PyPI
# OR for development:
git clone https://github.com/i2mint/pydrivedol.git
cd pydrivedol
pip install -e .
```

## Quick Start

### Simple Downloads (No Setup Required!)

Download from public Google Drive URLs without any API configuration:

```python
from pydrivedol import get_bytes

# Download public file
url = "https://drive.google.com/file/d/YOUR_FILE_ID/view"
content = get_bytes(url)

# Save to temp file
temp_path = get_bytes(url, local_path=True)

# Save to specific path
get_bytes(url, local_path="/path/to/save.pdf")

# With caching
get_bytes(url, use_cache=True)  # Uses ~/.cache/pydrivedol/cached/
```

If the file is **not** publicly shared, Google answers with its HTML sign-in page — under
HTTP 200, so it looks like a successful download. pydrivedol refuses to hand that back as
file content and raises `NotPubliclyShared` instead, telling you to authenticate:

```python
from pydrivedol import get_bytes, NotPubliclyShared

try:
    content = get_bytes(url)
except NotPubliclyShared:
    content = get_bytes(url, drive=drive)  # see below
```

(If you really are downloading an HTML file, pass `allow_html=True`.)

### Private Files (Requires API Setup)

Authenticate once, then pass the `drive` wherever bytes are needed:

```python
from pydrivedol import drive_from_service_account, get_bytes, get_metadata, GDFiles

drive = drive_from_service_account("service-account-key.json")

# Is it worth downloading? Metadata is cheap — it fetches no content.
info = get_metadata(url, drive=drive)
info["title"], info["fileSize"], info["mimeType"], info["modifiedDate"]
# ('client_export.xlsx', 18512345, 'application/vnd...sheet', '2026-08-01T12:00:00.000Z')

# Same get_bytes, now authenticated — local_path= and use_cache= work as before
content = get_bytes(url, drive=drive)
```

`fileSize` comes back as an `int` (Drive sends it as a string), and is **absent** for
Google-native files — Sheets/Docs/Slides have no stored byte size.

### Files as a Mapping: `GDFiles`

When files arrive as *links* rather than as a folder listing, key by the link:

```python
files = GDFiles(drive)

content = files[url]  # a Drive file URL...
content = files[file_id]  # ...or the bare id: same entry
url in files  # metadata probe, never a download
files.metadata(url)  # name / size / mimeType / modifiedDate

# Iteration needs a scope — an unscoped GDFiles addresses the whole Drive
scoped = GDFiles(drive, folder_url=folder_url)
list(scoped)  # file ids
```

Unscoped, `iter()` and `len()` raise `NotImplementedError` (with a message naming
`folder_url=`) rather than silently paginating your entire Drive. Lookup works either way.

### Working with Folders (Requires API Setup)

```python
from pydrivedol import GDReader, GDStore

# Read-only access
folder_url = "https://drive.google.com/drive/folders/YOUR_FOLDER_ID"
reader = GDReader(folder_url)

# List all files (keys are relative paths with extensions)
for filepath in reader:
    print(filepath)
# Output:
# file.txt
# folder/nested.pdf
# data/report.xlsx

# Get file contents (values are bytes)
content = reader["file.txt"]
pdf_bytes = reader["folder/nested.pdf"]

# Check if file exists
if "data/report.xlsx" in reader:
    print("Found report!")

# Get number of files
num_files = len(reader)

# Get shareable URL
url = reader.get_url("file.txt")
```

### Read-Write Operations

```python
from pydrivedol import GDStore

# Read-write access
store = GDStore(folder_url)

# Write a file
store["newfile.txt"] = b"Hello, World!"

# Write to nested folder (creates folders automatically)
store["reports/2024/summary.txt"] = b"Q1 results..."

# Update existing file
store["newfile.txt"] = b"Updated content"

# Delete file
del store["newfile.txt"]

# Full CRUD operations
store["data.json"] = b'{"key": "value"}'
data = store["data.json"]  # Read
store["data.json"] = b'{"key": "new"}'  # Update
del store["data.json"]  # Delete
```

## API Setup (for GDReader/GDStore)

### 1. Create Google Cloud Project

1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Create a new project
3. Enable **Google Drive API**:
   - Navigate to “APIs & Services” → “Library”
   - Search for “Google Drive API”
   - Click “Enable”

### 2. Create OAuth2 Credentials

1. Go to “APIs & Services” → “Credentials”
2. Click “Create Credentials” → “OAuth client ID”
3. Choose “Desktop app” as application type
4. Download the JSON file
5. Rename it to `client_secrets.json`
6. Place it in your working directory

### 3. First-Time Authentication

On first use, a browser window will open for authentication:

```python
from pydrivedol import GDReader

# This will open browser for authentication
reader = GDReader(folder_url)
```

1. Sign in with your Google account
2. Grant permissions
3. Credentials are saved for future use

That’s it! You only need to authenticate once.

### Headless / server: use a service account instead

The browser flow above is unusable from a script, a server, or an agent. For those, create a
**service account** — a robot identity with its own key file and no interactive login:

1. [Google Cloud Console](https://console.cloud.google.com/) → your project →
   “APIs & Services” → “Credentials”
2. “Create Credentials” → **Service account**. Any name; no roles needed.
3. Open the new service account → “Keys” → “Add key” → “Create new key” → **JSON**. The key
   file downloads once and cannot be re-downloaded.
4. Copy the service account’s **`client_email`** (it looks like
   `something@your-project.iam.gserviceaccount.com`).
5. In Google Drive, **share the file or folder with that `client_email`** — Viewer to read,
   Editor to write. This is the step people forget: the service account is a separate
   identity, and a file shared with *you* is not shared with *it*.
6. Point pydrivedol at the key file:

```python
from pydrivedol import drive_from_service_account

drive = drive_from_service_account("service-account-key.json")
```

Keep the key file out of version control (this repo’s `.gitignore` covers the usual names) and
out of the repo entirely if you can — read its path from an environment variable.

Read-only by token, if you want the extra guarantee:

```python
drive = drive_from_service_account(
    key_file, scopes=("https://www.googleapis.com/auth/drive.readonly",)
)
```

## Advanced Usage

### Control Recursion Depth

```python
from pydrivedol import GDReader

# Only files in the root folder
reader = GDReader(folder_url, max_levels=0)

# One level deep
reader = GDReader(folder_url, max_levels=1)

# Fully recursive (default)
reader = GDReader(folder_url, max_levels=None)
```

### Include Hidden Files

```python
reader = GDReader(folder_url, include_hidden=True)
```

### Custom Credentials Location

```python
reader = GDReader(
    folder_url,
    credentials_file="/path/to/client_secrets.json",
    settings_file="/path/to/settings.yaml",
)
```

### Generate Shareable URLs

```python
# Get public URL for a file
url = reader.get_url("file.txt")

# With specific permissions
url = reader.get_url(
    "file.txt",
    permission_type="anyone",  # 'anyone', 'user', 'group', 'domain'
    permission_role="reader",  # 'reader', 'writer', 'commenter'
)
```

### Caching Downloads

```python
from pydrivedol import get_bytes

# Use default cache directory (~/.cache/pydrivedol/cached/)
content = get_bytes(url, use_cache=True)

# Use custom cache directory
content = get_bytes(url, use_cache="/path/to/cache/")

# Files are cached by ID, subsequent calls are instant
content = get_bytes(url, use_cache=True)  # From cache!
```

## Examples

### Backup Local Files to Google Drive

```python
from pydrivedol import GDStore
from pathlib import Path

store = GDStore(folder_url)

# Backup all .py files
for filepath in Path(".").glob("**/*.py"):
    store[str(filepath)] = filepath.read_bytes()
```

### Download All Files from a Folder

```python
from pydrivedol import GDReader
from pathlib import Path

reader = GDReader(folder_url)

for filepath in reader:
    # Preserve folder structure
    local_path = Path(filepath)
    local_path.parent.mkdir(parents=True, exist_ok=True)
    local_path.write_bytes(reader[filepath])
```

### Sync Between Two Folders

```python
from pydrivedol import GDReader, GDStore

source = GDReader(source_folder_url)
target = GDStore(target_folder_url)

# Copy missing files
for filepath in source:
    if filepath not in target:
        target[filepath] = source[filepath]
        print(f"Copied: {filepath}")
```

### Process CSV Files in Drive

```python
from pydrivedol import GDReader
import csv
from io import StringIO

reader = GDReader(folder_url)

for filepath in reader:
    if filepath.endswith(".csv"):
        content = reader[filepath].decode("utf-8")
        csv_reader = csv.DictReader(StringIO(content))
        for row in csv_reader:
            print(row)
```

## Architecture

pydrivedol follows the `dol` package patterns:

```default
Helper Functions
  └─ get_bytes(url)                    # Simple public downloads (no API)
  └─ get_bytes(url, drive=...)         # Authenticated: reaches private files
  └─ get_metadata(url, drive=...)      # Name/size/mimeType/date, no download

API-Based Classes
  └─ GDFiles (Mapping)                 # Files, keyed by file id or file URL
  └─ GDReader (Mapping)                # Read-only folder access, keyed by path
      └─ GDStore (MutableMapping)      # Read-write folder access
```

**Design Principles:**

- Collections as Mappings
- Minimal boilerplate
- Familiar dict-like interface
- Lazy evaluation where possible
- Clear separation of concerns

## Comparison with Other Tools

### vs. PyDrive2 directly

```python
# PyDrive2
from pydrive2.auth import GoogleAuth
from pydrive2.drive import GoogleDrive

gauth = GoogleAuth()
gauth.LocalWebserverAuth()
drive = GoogleDrive(gauth)

folder_id = "YOUR_FOLDER_ID"  # a Drive id: [A-Za-z0-9_-]; quote any other value
file_list = drive.ListFile({"q": f"'{folder_id}' in parents"}).GetList()
for file in file_list:
    content = file.GetContentString()

# pydrivedol
from pydrivedol import GDReader

reader = GDReader(folder_url)
for filepath, content in reader.items():
    pass  # content is already bytes!
```

### vs. google-api-python-client

```python
# google-api-python-client
from googleapiclient.discovery import build
from google.oauth2.credentials import Credentials

creds = Credentials.from_authorized_user_file("token.json", SCOPES)
service = build("drive", "v3", credentials=creds)
results = service.files().list().execute()
items = results.get("files", [])

# pydrivedol
from pydrivedol import GDReader

reader = GDReader(folder_url)
items = list(reader)  # Just keys!
```

**pydrivedol advantages:**

- ✅ Dict-like interface
- ✅ Less boilerplate
- ✅ Follows familiar patterns
- ✅ Recursive traversal built-in
- ✅ Public file downloads without API

## Testing

### Quick Test (No Setup)

```bash
pytest test_pydrivedol.py -v
# Runs helper function tests, skips API tests
```

### Full Test Setup

```bash
# 1. Set environment variables
export PYDRIVEDOL_TEST_FOLDER_URL="https://drive.google.com/drive/folders/YOUR_ID"
export PYDRIVEDOL_TEST_PUBLIC_FILE_URL="https://drive.google.com/file/d/YOUR_ID/view"

# 2. Ensure client_secrets.json is in place

# 3. Run tests
pytest test_pydrivedol.py -v
```

See [TEST_SETUP.md]() for detailed instructions.

## FAQ

**Q: Do I need a Google Cloud project for `get_bytes()`?**<br />
\\\\
A: No! `get_bytes()` works with public URLs without any API setup.

**Q: Can I use this in production?**<br />
\\\\
A: Yes, but be aware of [Google Drive API quotas](https://developers.google.com/drive/api/guides/limits).

**Q: How do I handle large files?**<br />
\\\\
A: Files are loaded into memory as bytes. For very large files, consider streaming or using the PyDrive2 API directly.

**Q: Can I use service accounts?**<br />
\\\\
A: Currently pydrivedol uses OAuth2 for user accounts. Service account support is planned.

**Q: What about Google Workspace files (Docs, Sheets)?**<br />
\\\\
A: These need to be exported first. Currently pydrivedol focuses on regular files.

**Q: Is this thread-safe?**<br />
\\\\
A: File operations are atomic, but concurrent modifications to the same file may conflict.

## Troubleshooting

### “No module named ‘pydrive2’”

```bash
pip install pydrive2
```

### “Invalid client secrets file”

1. Ensure `client_secrets.json` is in your working directory
2. Verify it’s the correct OAuth2 credentials JSON
3. Try creating new credentials in Google Cloud Console

### “Permission denied”

1. Check that your Google account has access to the folder
2. Verify folder sharing settings
3. Re-authenticate: delete saved credentials and run again

### `NotPubliclyShared` from `get_bytes`

The file is not shared “anyone with the link”, so the unauthenticated endpoint cannot reach
it. Either make it public, or authenticate and pass `drive=` (see *Private Files* above). For
a service account, remember the file/folder must be shared with the account’s `client_email`.

### Downloaded file won’t open / `BadZipFile` on an xlsx

You are almost certainly holding Google’s sign-in page rather than the file. Recent versions
raise `NotPubliclyShared` for exactly this; if you are pinned to an older one, upgrade.

### Tests are skipped

Check environment variables:

```bash
python test_pydrivedol.py  # Shows configuration status
```

## Contributing

Contributions welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Add tests for new functionality
4. Ensure all tests pass
5. Submit a pull request

## Related Projects

- [dol](https://github.com/i2mint/dol) - The underlying data object layer framework
- [s3dol](https://github.com/i2mint/s3dol) - Similar interface for AWS S3
- [PyDrive2](https://github.com/iterative/PyDrive2) - Google Drive API wrapper (used by pydrivedol)

## License

MIT License - see [LICENSE]() file for details.

## Credits

Built with ❤️ using:

- [PyDrive2](https://github.com/iterative/PyDrive2) for Google Drive API
- [dol](https://github.com/i2mint/dol) patterns for clean interfaces
- [requests](https://requests.readthedocs.io/) for HTTP operations

---

**Part of the [i2mint](https://github.com/i2mint) ecosystem of data access tools.**

<p class="epythet-aggregates">This documentation as a single file: <a href="pydrivedol.md">pydrivedol.md</a> (Markdown, for agents).</p>


# _autosummary/pydrivedol.base.html.md

# pydrivedol.base

Base objects for pydrivedol: download functions, GDFiles, GDReader, GDStore.

Two access levels, both returning `bytes`:

- **Public, no setup**: [`get_bytes()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_bytes) against Google’s unauthenticated download endpoint.
  It raises [`NotPubliclyShared`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.NotPubliclyShared) rather than handing back the HTML sign-in page Drive
  serves (with HTTP 200) when the file is not shared publicly.
- **Authenticated**: pass a PyDrive2 `GoogleDrive` – from [`drive_from_service_account()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.drive_from_service_account)
  for headless use – as `drive=` to [`get_bytes()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_bytes) / [`get_metadata()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_metadata), or build a
  [`GDFiles`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDFiles) (file-level Mapping, keyed by file id or URL), [`GDReader`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDReader) /
  [`GDStore`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDStore) (folder-level, keyed by relative path).

### Module Attributes

| [`DEFAULT_METADATA_FIELDS`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.DEFAULT_METADATA_FIELDS)   | enough to decide whether a download is worth making.   |
|----------------------------------------------------------------------------|--------------------------------------------------------|

### Functions

| [`drive_from_service_account`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.drive_from_service_account)(key_file, \*[, ...])   | Build an authenticated `GoogleDrive` from a service-account key file.        |
|----------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [`get_bytes`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_bytes)(url, \*[, local_path, use_cache, ...])  | Download bytes from a Google Drive URL.                                      |
| [`get_metadata`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_metadata)(url_or_id, \*, drive[, fields])      | Fetch a Drive file's metadata **without downloading its content**.           |
| [`xlsx_to_google_sheet`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.xlsx_to_google_sheet)(folder_url, title, xlsx, \*) | Upload an `.xlsx` as a **native Google Sheet** and return its shareable URL. |

### Classes

| [`GDFiles`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDFiles)(drive, \*[, folder_url, max_levels, ...])   | Read-only Mapping of Google Drive **files**, keyed by file id or file URL.   |
|------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| [`GDReader`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDReader)(folder_url, \*[, max_levels, ...])         | Read-only Mapping to Google Drive folder.                                    |
| [`GDStore`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDStore)(folder_url, \*[, convert_office])           | Read-write MutableMapping to Google Drive folder.                            |

### Exceptions

| [`NotPubliclyShared`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.NotPubliclyShared)   | Raised when an unauthenticated download returns a sign-in page instead of file content.   |
|----------------------------------------------------------------------|-------------------------------------------------------------------------------------------|

### pydrivedol.base.DEFAULT_METADATA_FIELDS *= ('id', 'title', 'mimeType', 'fileSize', 'modifiedDate', 'alternateLink')*

enough to decide whether a download is worth making.

* **Type:**
  Metadata fields fetched by default

### *class* pydrivedol.base.GDFiles(drive, , folder_url=None, max_levels=None, include_hidden=False)

Bases: [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)

Read-only Mapping of Google Drive **files**, keyed by file id or file URL.

The file-level sibling of [`GDReader`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDReader). Where `GDReader` is scoped to a folder and
keyed by relative path, `GDFiles` is keyed by whatever identifies a single file: a bare
file id, or any Drive file URL – both normalise to the same key through
`_extract_file_id()`, so `files[url]` and `files[file_id]` are one entry. Values
are `bytes`, fetched over the authenticated API, so **private** files work as long as
they are shared with the authenticated identity.

That is the shape a caller has when files arrive as *links* – the usual Drive sharing
idiom – rather than as a folder listing.

`drive` is required and injected: a file-level view is pointless without auth, since its
whole reason to exist is reaching files the public endpoint cannot.

**Iteration requires a scope.** Unscoped, this mapping covers the entire Drive, which is
unbounded and paginated; enumerating it is never what a caller wants, so offering it would
be a trap. `__iter__`/`__len__` therefore raise `NotImplementedError` unless
`folder_url` is given, in which case they yield that folder’s file **ids** (honouring
`max_levels` and `include_hidden`, the same traversal [`GDReader`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDReader) uses). Lookup
always works, scoped or not – it is the primary use case and needs no listing.

```pycon
>>> drive = drive_from_service_account('service-account-key.json')
>>> files = GDFiles(drive)
>>> files.metadata(url)['fileSize']  # cheap: no download
>>> content = files[url]  # or files[file_id]
>>> url in files  # metadata probe, not a download
True
```

Scoped, so it can be listed:

```pycon
>>> scoped = GDFiles(drive, folder_url=folder_url)
>>> list(scoped)  # file ids
```

#### metadata(key, , fields=('id', 'title', 'mimeType', 'fileSize', 'modifiedDate', 'alternateLink'))

Metadata (name, size, mimeType, modifiedDate) for `key`, without downloading it.

Thin method form of [`get_metadata()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_metadata) – see it for the field semantics.

* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)

### *class* pydrivedol.base.GDReader(folder_url, , max_levels=None, credentials_file='client_secrets.json', settings_file='settings.yaml', include_hidden=False, drive=None)

Bases: [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)

Read-only Mapping to Google Drive folder.

Keys are relative file paths, values are file contents as bytes.

```pycon
>>> reader = GDReader(folder_url)
>>> list(reader)[:3]
>>> content = reader['path/to/file.txt']
>>> len(reader)
>>> 'file.txt' in reader
>>> url = reader.get_url('file.txt')
```

#### get_url(key, , permission_type='anyone', permission_role='reader')

Get shareable URL for file.

* **Parameters:**
  * **key** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – File path
  * **permission_type** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – ‘anyone’, ‘user’, ‘group’, ‘domain’
  * **permission_role** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – ‘reader’, ‘writer’, ‘commenter’
* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)
* **Returns:**
  Shareable URL

### *class* pydrivedol.base.GDStore(folder_url, , convert_office=False, \*\*kwargs)

Bases: [`GDReader`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDReader), [`MutableMapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.MutableMapping)

Read-write MutableMapping to Google Drive folder.

Extends GDReader with write and delete operations.

```pycon
>>> store = GDStore(folder_url)
>>> store['file.txt'] = b'Hello'
>>> store['dir/file.txt'] = b'Nested'
>>> del store['file.txt']
```

Pass `convert_office=True` to make `store['x.xlsx'] = xlsx_bytes` create a \*native
Google Sheet\* (xlsx → Sheet, docx → Doc, pptx → Slides) instead of an uploaded blob. For
one-off control use [`upload()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.GDStore.upload) with `convert=True`.

#### upload(key, value=None, , path=None, convert=None, google_mimetype=None)

Upload `value` (bytes) or a file `path` to `key`; return the shareable URL.

With `convert=True` (or the store’s `convert_office` default) an office file becomes a
native Google doc — e.g. `store.upload('schema.xlsx', xlsx_bytes, convert=True)` yields a
Google Sheet. Updates an existing same-named file in the target folder, else creates it.

* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)

### *exception* pydrivedol.base.NotPubliclyShared

Bases: [`RuntimeError`](https://docs.python.org/3/builtins/exceptions.html#RuntimeError)

Raised when an unauthenticated download returns a sign-in page instead of file content.

Google serves its HTML sign-in / permission interstitial with HTTP **200**, so without an
explicit check that page body would be returned as if it were the file: plausible-looking
bytes that only blow up much later, in whatever tries to parse them. Catch this to fall
back to an authenticated fetch (`get_bytes(url, drive=...)`).

### pydrivedol.base.drive_from_service_account(key_file, , scopes=('https://www.googleapis.com/auth/drive',), subject=None)

Build an authenticated `GoogleDrive` from a service-account key file.

For headless / server use — no browser OAuth flow. Share the target Drive folder
with the service account’s `client_email` (Viewer for read, Editor for write).
Pass the resulting drive to `GDReader`/`GDStore` via their `drive=` argument.

* **Parameters:**
  * **key_file** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – path to the service-account JSON key.
  * **scopes** – OAuth scopes; default is full Drive (use `.../auth/drive.readonly`
    to enforce read-only at the token level).
  * **subject** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – optional user email to impersonate (domain-wide delegation).

```pycon
>>> drive = drive_from_service_account('sa-key.json')
>>> reader = GDReader(folder_url, drive=drive)
```

### pydrivedol.base.get_bytes(url, , local_path=False, use_cache=False, drive=None, allow_html=False)

Download bytes from a Google Drive URL.

Without `drive` this uses Google’s public download endpoint – no API setup, but it only
reaches files shared “anyone with the link”, and it raises [`NotPubliclyShared`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.NotPubliclyShared) if
Drive answers with its sign-in page instead of the file. Pass an authenticated `drive`
(see [`drive_from_service_account()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.drive_from_service_account)) to reach **private** files shared with that
identity.

* **Parameters:**
  * **url** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – Google Drive file link (a bare file id is accepted too).
  * **local_path** (`Union`[[`bool`](https://docs.python.org/3/builtins/functions.html#bool), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – False (return bytes), True (save to temp), or str (save to path)
  * **use_cache** (`Union`[[`bool`](https://docs.python.org/3/builtins/functions.html#bool), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – False (no cache), True (use ~/.cache/pydrivedol/cached/), or str (use dir)
  * **drive** – an authenticated PyDrive2 `GoogleDrive`. When given, the download goes through
    the API; `None` (default) keeps the public, unauthenticated behaviour exactly.
  * **allow_html** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – by default an HTML payload from the *public* endpoint raises, because it is
    Google’s login page masquerading as file content. Set True only when the file you
    are downloading genuinely is HTML. Ignored on the authenticated path.
* **Return type:**
  `Union`[[`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]
* **Returns:**
  bytes if local_path is False or str, filepath str if local_path is True
* **Raises:**
  [**NotPubliclyShared**](_autosummary/pydrivedol.base.html.md#pydrivedol.base.NotPubliclyShared) – the public endpoint returned a sign-in page; pass `drive=`.

```pycon
>>> content = get_bytes(url)
>>> path = get_bytes(url, local_path=True)
>>> content = get_bytes(url, local_path='/tmp/file.txt')
```

A private file, shared with a service account:

```pycon
>>> drive = drive_from_service_account('service-account-key.json')
>>> content = get_bytes(private_url, drive=drive)
```

### pydrivedol.base.get_metadata(url_or_id, , drive, fields=('id', 'title', 'mimeType', 'fileSize', 'modifiedDate', 'alternateLink'))

Fetch a Drive file’s metadata **without downloading its content**.

The cheap half of [`get_bytes()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.get_bytes): use it to decide *whether* to download – an 18MB
spreadsheet is not something you fetch just to learn its name.

* **Parameters:**
  * **url_or_id** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – a Drive file URL or a bare file id.
  * **drive** – an authenticated PyDrive2 `GoogleDrive` (see
    [`drive_from_service_account()`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.drive_from_service_account)).
  * **fields** – which metadata fields to request; `None` fetches everything Drive offers.
    The default, [`DEFAULT_METADATA_FIELDS`](_autosummary/pydrivedol.base.html.md#pydrivedol.base.DEFAULT_METADATA_FIELDS), covers name (`title`), `fileSize`,
    `mimeType` and `modifiedDate`.
* **Return type:**
  [`dict`](https://docs.python.org/3/builtins/stdtypes.html#dict)
* **Returns:**
  A plain `dict` of the requested fields that the file actually has. `fileSize` is
  returned as an `int` (Drive sends it as a string), and is **absent** for
  Google-native files – Sheets/Docs/Slides have no stored byte size.

```pycon
>>> drive = drive_from_service_account('service-account-key.json')
>>> info = get_metadata(url, drive=drive)
>>> info['title'], info['fileSize']
('client_export.xlsx', 18512345)
```

### pydrivedol.base.xlsx_to_google_sheet(folder_url, title, xlsx, , share_with=(), anyone_reader=False, credentials_file='client_secrets.json', settings_file='settings.yaml', drive=None)

Upload an `.xlsx` as a **native Google Sheet** and return its shareable URL.

The whole point: Drive can *convert* an uploaded spreadsheet into an editable Google Sheet
(preserving cell formatting), rather than parking an `.xlsx` blob. This wraps that.

* **Parameters:**
  * **folder_url** ([`Optional`](https://docs.python.org/3/library/typing.html#typing.Optional)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – destination Drive folder URL (`None` → the account’s root).
  * **title** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – the Google Sheet’s name.
  * **xlsx** (`Union`[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Path`](https://docs.python.org/3/library/pathlib.html#pathlib.Path), [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)]) – a path (`str`/`Path`) or raw `bytes`.
  * **share_with** – emails to grant `writer` access (no notification email sent).
  * **anyone_reader** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – also grant anyone-with-link `reader` access.
  * **settings_file** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – PyDrive2 auth (ignored if `drive` is given).
  * **drive** – an existing PyDrive2 `GoogleDrive` (skips auth).
* **Return type:**
  [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)
* **Returns:**
  The Google Sheet URL (`alternateLink`).

```pycon
>>> url = xlsx_to_google_sheet(folder_url, 'My Schema', '/tmp/schema.xlsx',
...                            anyone_reader=True)
```


# _autosummary/pydrivedol.html.md

# pydrivedol

Google Drive Data Object Layer

Provides Mapping and MutableMapping interfaces to Google Drive data.

Key Features:

- get_bytes(url): Download bytes from public Google Drive URLs (no API setup needed)
- get_bytes(url, drive=…): Same, but authenticated – reaches *private* files
- get_metadata(url, drive=…): Name/size/mimeType/modifiedDate without downloading
- GDFiles: Read-only Mapping of files, keyed by file id or file URL
- GDReader: Read-only Mapping interface to a Google Drive folder
- GDStore: Read-write MutableMapping interface to a Google Drive folder

Setup (optional) for API-based features (GDReader/GDStore):

1. pip install pydrive2
2. Go to [https://console.cloud.google.com/](https://console.cloud.google.com/)
3. Create project, enable Google Drive API
4. Create OAuth 2.0 credentials (Desktop app), download client_secrets.json
5. First run opens browser for authentication

Usage:

> Simple download (no API needed) – public files only. If the file is not public,
> Google answers with its HTML sign-in page (under HTTP 200); pydrivedol raises
> NotPubliclyShared rather than handing that page back as the file’s content.

> ```pycon
> >>> content = get_bytes(url)
> >>> path = get_bytes(url, local_path=True)
> ```

> Private files: authenticate once, then pass the drive around

> ```pycon
> >>> drive = drive_from_service_account('service-account-key.json')
> >>> get_metadata(url, drive=drive)['fileSize']  # decide before downloading
> >>> content = get_bytes(url, drive=drive)
> ```

> Files as a Mapping, keyed by file id or URL

> ```pycon
> >>> files = GDFiles(drive)
> >>> content = files[url]
> ```

> Read folder

> ```pycon
> >>> reader = GDReader(folder_url)
> >>> list(reader)
> >>> content = reader['path/to/file.txt']
> ```

> Write to folder

> ```pycon
> >>> store = GDStore(folder_url)
> >>> store['file.txt'] = b'content'
> >>> del store['file.txt']
> ```

> Make a native Google Sheet from an .xlsx (Drive converts it, keeping formatting)

> ```pycon
> >>> url = xlsx_to_google_sheet(folder_url, 'My Schema', '/tmp/schema.xlsx')
> >>> store = GDStore(folder_url, convert_office=True)
> >>> store['schema.xlsx'] = xlsx_bytes  # -> a Google Sheet
> ```

### Modules

| [`base`](_autosummary/pydrivedol.base.html.md#module-pydrivedol.base)   | Base objects for pydrivedol: download functions, GDFiles, GDReader, GDStore.   |
|--------------------------------------------------------------------------------|--------------------------------------------------------------------------------|


# about-this-build.html.md

<!-- generated by epythet -->

# About this build

This documentation was built on **2026-09-22 15:28 UTC** from commit <a href="https://github.com/i2mint/pydrivedol/commit/14764ec279a7ebf51edbf53b2fea4b85f54db1dc"><code>14764ec</code></a> on branch <code>master</code>, for **pydrivedol 0.0.7** (from <code>pyproject.toml</code>).

#### WARNING
The documentation and the package may be misaligned:

- The documented version (0.0.7) is behind the latest release on PyPI (0.0.8): `pip install pydrivedol` gives newer code than these docs describe.

## Source

|                     |                                                                                                                                                          |
|---------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|
| Commit              | <a href="https://github.com/i2mint/pydrivedol/commit/14764ec279a7ebf51edbf53b2fea4b85f54db1dc"><code>14764ec279a7ebf51edbf53b2fea4b85f54db1dc</code></a> |
| Branch              | <code>master</code>                                                                                                                                      |
| Tags at this commit | none                                                                                                                                                     |
| Working tree        | clean                                                                                                                                                    |
| Remote              | <code>https://github.com/i2mint/pydrivedol</code>                                                                                                        |

## Continuous integration

|              |                                                                                            |
|--------------|--------------------------------------------------------------------------------------------|
| Repository   | <code>i2mint/pydrivedol</code>                                                             |
| Run          | <a href="https://github.com/i2mint/pydrivedol/actions/runs/35747365412">35747365412</a>    |
| Ref          | <code>refs/heads/master</code>                                                             |
| Event commit | <code>14764ec279a7ebf51edbf53b2fea4b85f54db1dc</code> (in the history of the built commit) |

## Tools

|          |         |
|----------|---------|
| epythet  | 0.2.12  |
| Sphinx   | 9.1.0   |
| docutils | 0.22.4  |
| Python   | 3.12.14 |

## Configuration as resolved

|               |                                                                   |
|---------------|-------------------------------------------------------------------|
| theme         | <code>auto</code> (Sphinx theme <code>sphinxawesome_theme</code>) |
| accent        | <code>#91380e</code>                                              |
| api_generator | <code>autosummary</code>                                          |
| ignore        | <code>tests/</code>, <code>scrap/</code>, <code>examples/</code>  |
| agent_outputs | <code>true</code>                                                 |
| aggregates    | <code>md</code>                                                   |
| ai_artifacts  | <code>true</code>                                                 |

## Package on PyPI

Latest release: <a href="https://pypi.org/project/pydrivedol/0.0.8/">0.0.8</a>, newer than the documented version (0.0.7).

## Reproduce

```bash
git clone https://github.com/i2mint/pydrivedol && cd pydrivedol
git checkout 14764ec279a7ebf51edbf53b2fea4b85f54db1dc
pip install "epythet==0.2.12"
epythet quickstart . --ignore tests/ scrap/ examples/
```

The same data, for machines: <a href="build_info.json"><code>build_info.json</code></a> (schema version 1).


# api.html.md

# API reference

| [`pydrivedol`](_autosummary/pydrivedol.html.md#module-pydrivedol)   | Google Drive Data Object Layer   |
|---------------------------------------------------------------------------------|----------------------------------|


