IDEs such as [VS Code](https://code.visualstudio.com/docs/python/editing#_autocomplete-and-intellisense) and [PyCharm](https://www.jetbrains.com/help/pycharm/auto-completing-code.html) offer built-in
code completion for the Python language.

The `google-ads-python` library generates protobuf message classes dynamically
at runtime using [getter methods](https://developers.google.com/google-ads/api/docs/client-libs/python/proto-getters.md.txt) on the `GoogleAdsClient` class,
which can inhibit IDE code completion features that rely on static analysis of
source code.

To improve compatibility with code completion tools that rely on static
analysis, you can either:

1. Use type annotations with dynamic [getter methods](https://developers.google.com/google-ads/api/docs/client-libs/python/proto-getters.md.txt) (recommended).
2. Import protobuf message classes and service clients directly.

## Dynamic imports with type hints

Using dynamic imports ensures your code remains resilient across library
updates. Adding type annotations enables IDE code completion without losing the
benefits of dynamic initialization:

```python
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.v26.resources.types.campaign import (
    Campaign,
)

client = GoogleAdsClient.load_from_storage()

# Type hints allow the IDE to provide code completion suggestions while
# retaining dynamic class generation.
campaign: Campaign = client.get_type("Campaign")
```

## Direct imports

You can also import protobuf message classes directly. This approach enables IDE
code completion out of the box, without using getter methods:

```python
from google.ads.googleads.v26.resources import Campaign

# The Campaign class is imported directly, enabling the IDE to read the
# class definition and make code completion suggestions.
campaign = Campaign()
```

### Disadvantages of direct imports

While directly importing generated classes helps with code completion, it also
introduces some trade-offs:

1. **Harder to locate:** It's not always obvious which module a given class is located in, so finding the correct import path can be difficult.
2. **Brittle across upgrades:** The directory structure of generated classes can change with new versions of the client library. If you import classes directly, your code may break when you upgrade, whereas code that uses getter methods is unaffected by these types of changes.
3. **Manual initialization required:** The `get_service` method initializes
   services with authentication, transport settings, and gRPC interceptors
   (which attach headers such as `login-customer-id` and `linked-customer-id`,
   handle logging, and wrap errors in `GoogleAdsException`) before returning
   them. If you import and instantiate services directly, those interceptors
   are bypassed and you must configure credentials and transport channels
   manually:

   ```python
   from google.ads.googleads.v26.services.services import (
       campaign_service as campaign_service_module,
   )

   # Dynamic service getter (initializes credentials, transport, and gRPC
   # interceptors automatically).
   campaign_service = client.get_service("CampaignService")

   # Direct service client import (requires manual credential and interceptor
   # configuration).
   campaign_service_client = campaign_service_module.CampaignServiceClient(
       credentials=client.credentials,
   )
   ```