I've never had a circular dependency I couldn't resolve. Just organize your modules into a DAG. I have a pretty standard flow:
- protocols and abcs. Depend only on builtins, this sits at the top of the hierarchy and can be used anywhere
- top-level types/models, data structures. Only depends on external libraries
- config and logging. Depends on pydantic, which handles all init config validation, sets up log level. Many things import this
- specific class implementations, pure functions, transforms. imports from all the above
- dependency-injected stuff
- application logic
- routers and endpoints
I've had some close calls since I type check heavily, but protocols (since 3...6? 3.7 for sure) are a fantastic tool for both structuring and appeasing mypy.
- protocols and abcs. Depend only on builtins, this sits at the top of the hierarchy and can be used anywhere
- top-level types/models, data structures. Only depends on external libraries
- config and logging. Depends on pydantic, which handles all init config validation, sets up log level. Many things import this
- specific class implementations, pure functions, transforms. imports from all the above
- dependency-injected stuff
- application logic
- routers and endpoints
I've had some close calls since I type check heavily, but protocols (since 3...6? 3.7 for sure) are a fantastic tool for both structuring and appeasing mypy.