Why Use `from module import A as A` Instead of Just `import A`? FastAPI's Starlette TestClient Example Explained

Python’s import system is a cornerstone of its modularity, allowing developers to organize code into reusable components. However, not all import patterns are created equal—and some that seem redundant at first glance serve critical purposes. One such pattern is from module import A as A, which appears to重复 (repeat) the same name twice. At first blush, this might look like unnecessary boilerplate. Why not just use import A or from module import A?

In this blog, we’ll demystify this pattern using a real-world example: FastAPI’s TestClient, which is actually a re-exported component from Starlette, FastAPI’s underlying ASGI framework. We’ll explore why explicitly writing from fastapi.testclient import TestClient as TestClient (instead of import TestClient or even from fastapi.testclient import TestClient) improves clarity, maintainability, and avoids subtle bugs. By the end, you’ll understand when and why to use this pattern in your own code.

Table of Contents#

  1. Understanding Python Imports: The Basics
  2. The Starlette TestClient in FastAPI: A Real-World Context
  3. Why from module import A as A? 4 Key Reasons
  4. Common Pitfalls with import A
  5. Conclusion
  6. References

1. Understanding Python Imports: The Basics#

Before diving into the TestClient example, let’s clarify how Python’s import system works. This will help you see why from module import A as A is distinct from import A.

1.1 What is import A?#

When you write import A, Python does two things:

  1. Searches for a module named A (e.g., A.py, A/__init__.py, or a built-in module).
  2. Creates a reference to that module in the current namespace, using the name A.

Example: If A.py contains a class MyClass, import A lets you access MyClass as A.MyClass—not directly as MyClass.

# A.py  
class MyClass:  
    pass  
 
# main.py  
import A  
obj = A.MyClass()  # Works: Accesses MyClass via the module A  
obj = MyClass()    # Fails: MyClass is not in the current namespace  

1.2 What is from module import A?#

The syntax from module import A is different:

  1. Python imports the module module.
  2. It extracts the object A (could be a class, function, variable, etc.) from module.
  3. It adds A directly to the current namespace, so you can use A without prefixing it with module..

Example: Using the same A.py as above:

# main.py  
from A import MyClass  
obj = MyClass()  # Works: MyClass is now in the current namespace  

1.3 What is from module import A as A?#

This is a variation of from module import A with an explicit alias. The as A clause renames the imported object to A in the current namespace. Since the alias matches the original name (A), this is functionally identical to from module import A.

So why write the redundant as A? As we’ll see, it’s not about functionality—it’s about clarity and documentation.

2. The Starlette TestClient in FastAPI Context#

To understand the practical value of from module import A as A, let’s focus on FastAPI’s TestClient. FastAPI is built on top of Starlette, a lightweight ASGI framework. Many of FastAPI’s features (including TestClient) are actually re-exported from Starlette for convenience.

2.1 FastAPI’s Relationship with Starlette#

FastAPI relies heavily on Starlette for core ASGI functionality. For example:

  • Request/Response handling
  • Routing
  • Background tasks
  • Testing utilities like TestClient

Rather than making users install Starlette separately and import from starlette.testclient, FastAPI re-exports TestClient via its own fastapi.testclient module. This simplifies the user experience: you can install FastAPI alone and get TestClient "for free."

2.2 The TestClient Re-export#

If you look at FastAPI’s source code for fastapi/testclient.py, you’ll see:

# fastapi/testclient.py  
from starlette.testclient import TestClient as TestClient  # <-- Key line  
 
__all__ = ["TestClient"]  

Here, FastAPI imports TestClient from starlette.testclient and immediately re-exports it with the same name. This means when you write from fastapi.testclient import TestClient, you’re actually getting Starlette’s TestClient—but via FastAPI’s module.

3. Why from module import A as A?#

Now, let’s circle back to the original question: Why would FastAPI (and other libraries) use from starlette.testclient import TestClient as TestClient instead of the simpler from starlette.testclient import TestClient? The answer lies in explicitness and maintainability.

3.1 Explicitness: Documenting the Source#

The as A alias makes the code self-documenting. When a developer reads from starlette.testclient import TestClient as TestClient, it’s immediately clear that:

  • TestClient originates from starlette.testclient.
  • The import is intentional, and the name TestClient is preserved.

This is far more readable than a bare from starlette.testclient import TestClient, which doesn’t explicitly call out the re-export. For FastAPI’s maintainers, this clarifies that TestClient is not a FastAPI-native component but a re-export from Starlette.

3.2 Avoiding Namespace Confusion#

Suppose FastAPI ever decides to add its own TestClient (unlikely, but hypothetically). Without the as TestClient alias, the import from starlette.testclient import TestClient might accidentally shadow a local TestClient in the module. The explicit alias ensures there’s no ambiguity: "We are importing Starlette’s TestClient and calling it TestClient here."

3.3 Future-Proofing Against API Changes#

If Starlette renames TestClient to APITestClient in a future version, FastAPI could update the import to:

from starlette.testclient import APITestClient as TestClient  

This would let FastAPI users continue using TestClient without changing their code, while the alias (as TestClient) makes it clear that the name was adjusted internally. Without the alias, this change would require rewriting the import entirely, reducing readability.

3.4 Consistency with Linting and Code Style#

Many code linters (e.g., flake8, pylint) and formatters (e.g., black) encourage explicit imports. For libraries that re-export multiple components, using as A for each import ensures consistency. For example:

# Consistent re-exports with explicit aliases  
from starlette.testclient import TestClient as TestClient  
from starlette.websockets import WebSocket as WebSocket  
from starlette.background import BackgroundTasks as BackgroundTasks  

This uniformity makes the codebase easier to scan and maintain.

4. Common Pitfalls with import A#

At this point, you might wonder: "Why not just use import TestClient?" This is a common mistake among new Python developers. Remember: import TestClient tries to import a module named TestClient—not a class or function.

If you try to run:

# test.py  
import TestClient  # Fails! No module named "TestClient"  
 
client = TestClient(app)  

Python will throw a ModuleNotFoundError, because there’s no module named TestClient. The correct approach is to import the TestClient class from its parent module (either fastapi.testclient or starlette.testclient):

# Correct: Import the class from the module  
from fastapi.testclient import TestClient  
client = TestClient(app)  # Works!  

5. Conclusion#

The syntax from module import A as A may seem redundant at first, but it serves a critical purpose in Python: explicit documentation and maintainability. In FastAPI’s case, re-exporting Starlette’s TestClient with from starlette.testclient import TestClient as TestClient clarifies the origin of the component, avoids namespace confusion, and future-proofs the codebase.

For users, this pattern means you can confidently use from fastapi.testclient import TestClient knowing it’s a well-documented re-export. For developers, it’s a reminder that clarity often matters more than brevity in code.

6. References#