15 KiB
GAM Codebase Rearchitecture — Developer Guide
1. Goals, Benefits & What Changes for Users
What Changed for Users
Nothing. GAM behaves identically. Every command, flag, and output format is unchanged. Performance is the same or slightly better. This was a purely internal restructuring.
Why We Did This
gam/__init__.py was a 72,735-line monolith — a single file containing virtually all of GAM's logic. Every Google Workspace API surface (Drive, Gmail, Calendar, Classroom, Groups, Vault, Chat, ChromeOS, etc.) lived in one file. This created real problems:
- Navigating the codebase was painful. Finding where a command was implemented meant scrolling through 73K lines or relying on grep. No IDE could provide useful file-level structure because everything was in one file.
- Merge conflicts were constant. Any two developers touching different features (say, Drive and Gmail) would conflict because they were editing the same file.
- Understanding scope was impossible. Which constants belong to Drive? Which helper functions are shared vs. specific to Gmail? When everything is in one namespace, you can't tell.
- Code review was difficult. A PR that modifies "Drive label permissions" shouldn't require a reviewer to open a 73K-line file and locate the relevant section.
What the New Layout Achieves
| Before | After |
|---|---|
| 1 file, 72,735 lines | 95 files, all under 100KB |
gam/__init__.py does everything |
gam/__init__.py is a thin hub (~4,660 lines) |
| Find a function: scroll or grep the monolith | Find a function: look in the obvious module |
| Merge conflicts on every PR | Parallel work on Drive, Gmail, etc. with no conflicts |
Concrete benefits for developers:
-
Find things fast. Working on Gmail labels? Open
gam/cmd/gmail/labels.py. Working on shared drive permissions? Opengam/cmd/drive/shareddrives.py. The file name tells you what's inside. -
Smaller blast radius. A change to Drive copy/move logic only touches
gam/cmd/drive/copymove/. Reviewers can focus on the relevant 50-90KB file instead of a 73K-line monolith. -
Better IDE support. File outlines, go-to-definition, and symbol search all work better with reasonably-sized files. Your IDE's file explorer now shows a meaningful tree.
-
Easier onboarding. A new contributor can understand the codebase structure by looking at the directory tree. "Oh, Chat commands are in
cmd/chat/, Drive stuff is incmd/drive/" — it's self-documenting. -
No performance cost.
gam print usersruns in ~0.39s, identical to before. Python's import system caches modules, so the split adds negligible startup overhead.
2. Where Did Everything Go?
The New Layout
src/gam/
├── __init__.py # Hub: re-exports, dispatch tables, core infra (~4,660 lines)
├── __main__.py # Entry point
│
├── cmd/ # ALL command implementations live here
│ ├── __init__.py
│ │
│ │── admin.py # Admin roles & privileges
│ │── alerts.py # Alert Center
│ │── aliases.py # User/group aliases
│ │── analytics.py # Google Analytics
│ │── audit.py # Email Audit monitors (removed — stub)
│ │── browsers.py # Chrome browser management
│ │── caa.py # Context-Aware Access levels
│ │── calendar.py # Calendar ACLs, events, settings
│ │── chromeapps.py # Chrome app management
│ │── chromepolicies.py # Chrome policy management
│ │── cidevices.py # Cloud Identity devices
│ │── ciuserinvitations.py # Cloud Identity user invitations
│ │── cloudstorage.py # Cloud Storage management
│ │── contacts.py # Domain Shared Contacts (removed — stub) + People API utilities
│ │── cros.py # ChromeOS device management
│ │── customer.py # Customer/domain settings
│ │── datatransfer.py # Data transfer management
│ │── delegates.py # Domain-wide delegation
│ │── domains.py # Domain management
│ │── licenses.py # License management (domain-level)
│ │── meet.py # Google Meet
│ │── mobile.py # Mobile device management
│ │── notes.py # Google Keep notes
│ │── oauth.py # OAuth credential management
│ │── orgunits.py # Organizational units
│ │── people.py # People API / contacts
│ │── printers.py # Printer management
│ │── project.py # API project management
│ │── reports.py # Admin reports
│ │── reseller.py # Reseller operations
│ │── resources.py # Calendar resources, buildings, features
│ │── schemas.py # User schema management
│ │── send_email.py # Send email commands
│ │── sites.py # Google Sites
│ │── sso.py # Inbound SSO profiles & credentials
│ │── tasks.py # Google Tasks & Tag Manager
│ │── userservices.py # ASPs, backup codes, calendars, working locations
│ │
│ │── chat/ # Google Chat (sub-package)
│ │ ├── __init__.py # Re-exports from sub-modules
│ │ ├── setup.py # Chat setup, emoji, sections
│ │ ├── spaces.py # Chat space CRUD
│ │ └── members.py # Members, messages, reactions
│ │
│ │── cigroups/ # Cloud Identity Groups (sub-package)
│ │ ├── __init__.py
│ │ ├── groups.py # CI group CRUD
│ │ └── members.py # CI group members
│ │
│ │── courses/ # Google Classroom (sub-package)
│ │ ├── __init__.py
│ │ ├── courses.py # Course CRUD, info, listing
│ │ ├── content.py # Announcements, topics, materials, work
│ │ ├── participants.py # Student/teacher add/remove/sync
│ │ └── guardians.py # Guardian invitations
│ │
│ │── drive/ # Google Drive (sub-package)
│ │ ├── __init__.py # Re-exports all drive symbols
│ │ ├── core.py # Search, entities, file attributes, MIME types
│ │ ├── activity.py # Drive activity reporting, settings
│ │ ├── filepaths.py # Path resolution, field mapping
│ │ ├── revisions.py # File revision management
│ │ ├── filetree.py # File tree building, permission matching
│ │ ├── filelist.py # File listing & printing
│ │ ├── fileinfo.py # File counts, comments, disk usage
│ │ ├── files.py # File create/update/shortcuts
│ │ ├── permissions.py # File ACLs & permissions
│ │ ├── labels.py # Drive labels & label permissions
│ │ ├── shareddrives.py # Shared drive management
│ │ ├── looker.py # Looker Studio assets
│ │ ├── copymove/ # Copy & move (nested sub-package)
│ │ │ ├── __init__.py
│ │ │ ├── copymove_util.py # Statistics, options, copy logic
│ │ │ └── copymove_move.py # Move logic, permission updates
│ │ └── transfer/ # Transfer & ownership (nested sub-package)
│ │ ├── __init__.py
│ │ ├── fileops.py # Delete, trash, download, documents
│ │ └── ownership.py # Transfer drive, claim ownership
│ │
│ │── gmail/ # Gmail (sub-package)
│ │ ├── __init__.py
│ │ ├── profile.py # Gmail profile, watch
│ │ ├── labels.py # Label CRUD
│ │ ├── messages.py # Messages, threads, drafts, export, forward
│ │ ├── delegates.py # Mail delegation
│ │ ├── filters.py # Mail filters
│ │ ├── forms.py # Google Forms
│ │ ├── settings.py # Forwarding, IMAP, POP, language, SendAs
│ │ ├── smime.py # S/MIME certificates
│ │ ├── cse.py # Client-side encryption
│ │ └── signature.py # Signatures & vacation responders
│ │
│ │── groups/ # Google Groups (sub-package)
│ │ ├── __init__.py
│ │ ├── groups.py # Group CRUD, settings, info
│ │ └── members.py # Member management, display, sync
│ │
│ │── userop/ # Per-user operations (sub-package)
│ │ ├── __init__.py
│ │ ├── usergroups.py # User group membership, Looker Studio
│ │ ├── licenses.py # Per-user license management
│ │ ├── photos.py # User photos & profile
│ │ ├── sheets.py # Google Sheets operations
│ │ └── tokens.py # OAuth tokens & deprovisioning
│ │
│ │── users/ # User management (sub-package)
│ │ ├── __init__.py
│ │ ├── manage.py # User CRUD, attributes, schemas
│ │ └── display.py # User info & print/show
│ │
│ └── vault/ # Google Vault (sub-package)
│ ├── __init__.py
│ ├── matters.py # Vault matters & exports
│ └── holds.py # Vault holds, queries, counts
│
├── gamlib/ # GAM library: config, constants, API definitions
├── gapi/ # Google API wrappers
└── util/ # Utilities: CSV, batch, args, entity, api
What Stayed in __init__.py
The hub file (~4,660 lines) still contains:
| Section | Purpose |
|---|---|
| Imports (~300 lines) | Standard library + GAM internal imports |
| Re-export blocks (~2,300 lines) | from gam.cmd.X import (...) — backward compatibility |
| Core infrastructure (~500 lines) | Error handling, entity operations, output formatting |
| Dispatch tables (~800 lines) | MAIN_COMMANDS, MAIN_COMMANDS_WITH_OBJECTS — maps command names to handler functions |
| Command processing (~700 lines) | ProcessGAMCommand(), batch/CSV handling, resource command routing |
The re-export blocks ensure that any code doing gam.copyDriveFile() or from gam import copyDriveFile continues to work without changes.
How to Find a Function
Method 1: Intuition. The module names are descriptive. If you're looking for Gmail label code, check gam/cmd/gmail/labels.py. If you want Drive shared drive logic, check gam/cmd/drive/shareddrives.py.
Method 2: grep.
# Find where a function is defined
grep -rn "def copyDriveFile" src/gam/cmd/
# Result: src/gam/cmd/drive/copymove/copymove_util.py:947
Method 3: Check the dispatch table. If you know the GAM command name, search the dispatch tables in __init__.py:
grep "copyDriveFile\|copy.*drivefile" src/gam/__init__.py
# Shows: Cmd.ARG_DRIVEFILE: copyDriveFile,
# Then grep for the import to find its module:
# from gam.cmd.drive import ( ... copyDriveFile, ... )
Method 4: Follow the re-export chain. Every function is re-exported from __init__.py. Search for the name there to see which cmd/ module it comes from:
grep -B50 "copyDriveFile," src/gam/__init__.py | grep "^from"
# Result: from gam.cmd.drive import (
# Then check drive/__init__.py to find the sub-module:
grep -B20 "copyDriveFile," src/gam/cmd/drive/__init__.py | grep "^from"
# Result: from gam.cmd.drive.copymove import (
# And finally:
grep -B20 "copyDriveFile," src/gam/cmd/drive/copymove/__init__.py | grep "^from"
# Result: from gam.cmd.drive.copymove.copymove_util import (
Method 5: Your IDE. Open __init__.py, Ctrl/Cmd-click on any function name, and your IDE will jump to the definition in the correct cmd/ sub-module.
The Module Pattern
Every extracted module follows the same pattern:
"""Brief description of what this module handles."""
import sys
from gamlib import glaction
from gamlib import glapi as API
from gamlib import glcfg as GC
from gamlib import glclargs
from gamlib import glentity
from gamlib import glindent
# ... other imports as needed
Act = glaction.GamAction()
Ent = glentity.GamEntity()
Ind = glindent.GamIndent()
Cmd = glclargs.GamCLArgs()
def _getMain():
return sys.modules['gam']
Key points:
_getMain()provides lazy runtime access to thegammodule (i.e.,__init__.py). This avoids circular imports — modules can't directlyimport gamduring initialization becausegamis still loading. Instead, they call_getMain()at runtime when the function is actually executed. Any function defined in__init__.pyor re-exported through it is accessible as_getMain().functionName().Act,Ent,Ind,Cmdare local instances of the standard GAM helper classes. These four classes (GamAction,GamEntity,GamIndent,GamCLArgs) use class-level shared state, meaning all instances across all modules see the same values. When__init__.pycallsAct.Set(Act.CREATE)before dispatching to a command handler, every module'sActinstance immediately reflects that action. This was necessary because the original monolith had a single instance of each; the refactoring created multiple instances that must stay synchronized.- Module-level constants that were originally in
__init__.py(likeMIMETYPE_GA_FOLDER,ME_IN_OWNERS,UNKNOWN,UTF8, etc.) are duplicated into the modules that need them. This avoids import-time dependencies on the not-yet-loaded hub. - Cross-module function calls use
_getMain()for GAM-internal functions (e.g.,_getMain().entityUnknownWarning(...)) rather than direct imports, which would cause circular import errors during module loading. Standard library and third-party imports (e.g.,from passlib.hash import sha512_crypt) are safe to import directly at module level.
3. What Happened to GData (Audit and Domain Shared Contacts)?
GAM previously used two legacy Google APIs built on the GData XML protocol:
- Email Audit API —
gam audit monitor create/delete/list - Domain Shared Contacts API —
gam create/update/delete/print contacts
Both APIs, along with the vendored gdata/ and atom/ libraries (~10,000 lines), have been completely removed.
The affected commands now print a deprecation notice:
ERROR: GAM no longer supports the legacy <API> API and this command.
If you must use this API you can install a copy of GAM 7.46.07
which is the last version to support this command.
contacts.py still contains the People API utilities (shared constants, PeopleManager, etc.) used by people.py for modern user contact management.