Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Materials for python-circular-import Article #593

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python-circular-import/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Materials for `python-circular-import`
Empty file.
12 changes: 12 additions & 0 deletions python-circular-import/entity-relations/people/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from people import club, person


def main():
p1 = person.Person("John", 30)
p2 = person.Person("Jane", 29)
c = club.Club("Tennis Club", [p1, p2])
print(c)


if __name__ == "__main__":
main()
17 changes: 17 additions & 0 deletions python-circular-import/entity-relations/people/club.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# from __future__ import annotations

# from typing import TYPE_CHECKING

# if TYPE_CHECKING:
# from people.person import Person

from people.person import Person


class Club:
def __init__(self, name, members: list[Person] = []):
self.name = name
self.members = members

def __str__(self):
return f"{self.name} ({len(self.members)} members)"
18 changes: 18 additions & 0 deletions python-circular-import/entity-relations/people/person.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# from __future__ import annotations

# from typing import TYPE_CHECKING

# if TYPE_CHECKING:
# from people.club import Club

from people.club import Club


class Person:
def __init__(self, name, age, clubs: list[Club] = []):
self.name = name
self.age = age
self.clubs = clubs

def __str__(self):
return f"{self.name} ({self.age})"
Empty file.
14 changes: 14 additions & 0 deletions python-circular-import/entity-relations/people_runtime/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from people_runtime import club, person


def main():
p1 = person.Person("John", 30)
p2 = person.Person("Jane", 29)
c = club.Club("Tennis Club")
c.assign_member(p1)
c.assign_member(p2)
print(c)


if __name__ == "__main__":
main()
21 changes: 21 additions & 0 deletions python-circular-import/entity-relations/people_runtime/club.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# from people_runtime.person import Person


class Club:
def __init__(self, name):
self.name = name
self.members = set()

def __str__(self):
return f"{self.name} ({len(self.members)} members)"

def assign_member(self, person):
from people_runtime.person import Person

if isinstance(person, Person):
self.members.add(person)

if self not in person.clubs:
person.join_club(self)
else:
raise ValueError("Can only assign instances of Person")
21 changes: 21 additions & 0 deletions python-circular-import/entity-relations/people_runtime/person.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# from people_runtime.club import Club


class Person:
def __init__(self, name, age):
self.name = name
self.age = age
self.clubs = set()

def __str__(self):
return f"{self.name} ({self.age})"

def join_club(self, club):
from people_runtime.club import Club

if isinstance(club, Club):
self.clubs.add(club)
if self not in club.members:
club.assign_member(self)
else:
raise ValueError("Can only join instances of Club")
Empty file.
Empty file.
Empty file.
5 changes: 5 additions & 0 deletions python-circular-import/layered-architecture/shop/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from shop.controllers.shop_controller import ShopController

if __name__ == "__main__":
controller = ShopController()
controller.handle_request()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from shop.services.shop_service import ShopService


class ShopController:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(ShopController, cls).__new__(
cls, *args, **kwargs
)
return cls._instance

def __init__(self):
if not hasattr(self, "service"):
self.service = ShopService()

def handle_request(self):
print("Handling request")
self.service.perform_service_task()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from shop.controllers.shop_controller import ShopController


class ShopService:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(ShopService, cls).__new__(
cls, *args, **kwargs
)
return cls._instance

def __init__(self):
if not hasattr(self, "controller"):
self.controller = ShopController()

def perform_service_task(self):
print("Service task performed")
self.controller.handle_request()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from shop_di.controllers.shop_controller import ShopController
from shop_di.services.shop_service import ShopService

if __name__ == "__main__":
service = ShopService()
controller = ShopController(service)
controller.handle_request()
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class ShopController:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance

def __init__(self, service):
if not hasattr(self, "service"):
self.service = service

def handle_request(self):
print("Handling request")
self.service.perform_service_task()
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class ShopService:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls)
return cls._instance

def __init__(self):
pass

def perform_service_task(self):
print("Service task performed")
Copy link
Contributor Author

@iansedano iansedano Oct 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This package, shop_import_recursion, can be ignored for now. It's an interesting example of a circular import not getting caught as an ImportError but as a RecursionError. However, my feeling is that it's a bit too contrived to include in a short "How To" article.

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from shop_import_recursion.controllers.shop_controller import ShopController

if __name__ == "__main__":
controller = ShopController()
controller.handle_request()
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# from shop_import_recursion.services import shop_service
import shop_import_recursion


class ShopController:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(ShopController, cls).__new__(
cls, *args, **kwargs
)
return cls._instance

def __init__(self):
if not hasattr(self, "service"):
# self.service = shop_service.ShopService()
print(dir(shop_import_recursion))
self.service = (
shop_import_recursion.services.shop_service.ShopService()
)

def handle_request(self):
print("Handling request")
self.service.perform_service_task()
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# from shop_import_recursion.controllers import shop_controller
import shop_import_recursion


class ShopService:
_instance = None

def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(ShopService, cls).__new__(
cls, *args, **kwargs
)
return cls._instance

def __init__(self):
if not hasattr(self, "controller"):
# self.controller = shop_controller.ShopController()
self.controller = (
shop_import_recursion.controllers.shop_controller.ShopController()
)

def perform_service_task(self):
print("Service task performed")
self.controller.handle_request()
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from state_machine.state_a import StateA

if __name__ == "__main__":
current_state = StateA()
current_state = current_state.on_event("to_b")
9 changes: 9 additions & 0 deletions python-circular-import/state-machine/state_machine/state_a.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from state_machine.state_b import StateB


class StateA:
def on_event(self, event):
if event == "to_b":
return StateB()
else:
return self
9 changes: 9 additions & 0 deletions python-circular-import/state-machine/state_machine/state_b.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from state_machine.state_a import StateA


class StateB:
def on_event(self, event):
if event == "to_a":
return StateA()
else:
return self
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from state_machine_base.base_state import BaseState

if __name__ == "__main__":
current_state = BaseState()
current_state = current_state.on_event("to_b")
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class BaseState:
state_registry = {}

@classmethod
def register_state(cls, name, state_cls):
cls.state_registry[name] = state_cls

def on_event(self, event):
next_state_cls = self.state_registry.get(event)
if next_state_cls:
return next_state_cls()
return self
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from state_machine_base.base_state import BaseState


class StateA(BaseState):
pass


BaseState.register_state("to_b", "StateB")
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from state_machine_base.base_state import BaseState


class StateB(BaseState):
pass


BaseState.register_state("to_a", "StateA")
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from state_machine_controller.controller import Controller

if __name__ == "__main__":
controller = Controller()
print(controller.state)
controller.on_event("to_b")
print(controller.state)
controller.on_event("to_a")
print(controller.state)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from state_machine_controller.state_a import StateA
from state_machine_controller.state_b import StateB


class Controller:
def __init__(self):
self.state = StateA()

def on_event(self, event):
match event:
case "to_a":
self.state = StateA()
case "to_b":
self.state = StateB()
case _:
self.state = self.state
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class StateA:
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class StateB:
pass
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from state_machine_module.states import StateA

if __name__ == "__main__":
current_state = StateA()
current_state = current_state.on_event("to_b")
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class StateA:
def on_event(self, event):
if event == "to_b":
return StateB()
else:
return self


class StateB:
def on_event(self, event):
if event == "to_a":
return StateA()
else:
return self
Empty file.
15 changes: 15 additions & 0 deletions python-circular-import/user-code/code_with_me/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from interface import create_instance


def main():
# Create an instance of ImplementationA
implementation = create_instance("A")
implementation.do_something() # Output: Implementation A is doing something.

# Create an instance of ImplementationB
implementation = create_instance("B")
implementation.do_something() # Output: Implementation B is doing something.


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from interface import BaseInterface


class ImplementationA(BaseInterface):
"""Concrete implementation A."""

def do_something(self):
print("Implementation A is doing something.")
Loading
Loading