
import django django.setup() django.utils.timezone ), and django.urls.reverse ( django.urls.reverse ), and much more. If this is not done, then you will get an error: django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet. .py file, twist some things in it, figure it out - and then embed it in the project.django.setup() annoys me a lot. Firstly, you get tired of repeating it everywhere; and, secondly, django initialization takes a few seconds (we have a big monolith), and when you restart the same file 10, 20, 100 times - it just slows down the development.django.setup() ? You need to write code that minimally depends on django. from django.conf import settings class APIClient: def __init__(self): self.api_key = settings.SOME_API_KEY # : client = APIClient() class APIClient: def __init__(self, api_key): self.api_key = api_key # : client = APIClient(api_key='abc') django.conf.settings settings? Just lock them with the @override_settings decorator. And if the component does not depend on anything, then there will be nothing to get wet: it passed the parameters to the constructor - and drove it.django dependency story is the most striking example of a problem that I encounter every day: dependency management problems in python - and the overall architecture of python applications. def main(): """ """ with open("/etc/hosts") as file: for line in parse_hosts(file): print(line) def parse_hosts(lines): """ - """ for line in lines: if line.startswith("#"): continue yield line # shortener_client.py import requests class ShortenerClient: def __init__(self, api_key): self.api_key = api_key def shorten_link(self, url): response = requests.post( url='https://fstrk.cc/short', headers={'Authorization': self.api_key}, json={'url': url} ) return response.json()['url'] # text_processor.py import re from shortener_client import ShortenerClient class TextProcessor: def __init__(self, text): self.text = text def process(self): changed_text = self.text links = re.findall( r'https?://[^\r\n\t") ]*', self.text, flags=re.MULTILINE ) api_client = ShortenerClient('abc') for link in links: shortened = api_client.shorten_link(link) changed_text = changed_text.replace(link, shortened) return changed_text # controller.py from text_processor import TextProcessor processor = TextProcessor(""" 1: https://ya.ru 2: https://google.com """) print(processor.process()) 
TextProcessor class depends on the ShortenerClient class - and breaks when the ShortenerClient interface changes .shorten_link and added the callback_url argument to the shorten_link method. This argument means the address to which notifications should come when clicking on a link.ShortenerClient.shorten_link method began to look like this: def shorten_link(self, url, callback_url): response = requests.post( url='https://fstrk.cc/short', headers={'Authorization': self.api_key}, json={'url': url, 'callback_on_click': callback_url} ) return response.json()['url'] TypeError: shorten_link() missing 1 required positional argument: 'callback_url' 
ShortenerClient class, and your colleague writes TextProcessor , you get an offensive situation: you changed the code, but it broke. And it broke in a place that you have not seen in life, and now you need to sit down and understand someone else's code.ShortenerClient interface is changed, ShortenerClient itself ShortenerClient , and not its consumers (of which there can be many)?
from abc import ABC, abstractmethod class AbstractClient(ABC): @abstractmethod def __init__(self, api_key): pass @abstractmethod def shorten_link(self, link): pass class ShortenerClient(AbstractClient): def __ini__(self, api_key): self.api_key = api_key client = ShortenerClient('123') >>> TypeError: Can't instantiate abstract class ShortenerClient with abstract methods __init__, shorten_link mypy . It will help verify the signatures of inherited methods. To do this, we must add annotations to the interface: # shortener_client.py from abc import ABC, abstractmethod class AbstractClient(ABC): @abstractmethod def __init__(self, api_key: str) -> None: pass @abstractmethod def shorten_link(self, link: str) -> str: pass class ShortenerClient(AbstractClient): def __init__(self, api_key: str) -> None: self.api_key = api_key def shorten_link(self, link: str, callback_url: str) -> str: return 'xxx' mypy , we get an error due to the extra callback_url argument: mypy shortener_client.py >>> error: Signature of "shorten_link" incompatible with supertype "AbstractClient" shortener_client.py file. For example, you can drag the interface directly to the consumer - to a file with the TextProcessor processor: # text_processor.py import re from abc import ABC, abstractmethod class AbstractClient(ABC): @abstractmethod def __init__(self, api_key: str) -> None: pass @abstractmethod def shorten_link(self, link: str) -> str: pass class TextProcessor: def __init__(self, text, shortener_client: AbstractClient) -> None: self.text = text self.shortener_client = shortener_client def process(self) -> str: changed_text = self.text links = re.findall( r'https?://[^\r\n\t") ]*', self.text, flags=re.MULTILINE ) for link in links: shortened = self.shortener_client.shorten_link(link) changed_text = changed_text.replace(link, shortened) return changed_text TextProcessor owns the interaction interface, and as a result, ShortenerClient depends on it, and not vice versa.
TextProcessor says: I am a processor, and I am involved in text conversion. I do not want to know anything about the shortening mechanism: this is not my business. I want to pull the shorten_link method so that it shorten_link everything for me. So please, give me an object that plays according to my rules. Decisions about the way I interact are made by me, not him.ShortenerClient says: it seems that I cannot exist in a vacuum, and they require certain behavior from me. I'll go ask TextProcessor what I need to match so as not to break.
ShortenerClient , then who does import it and create a class object? It must be a control component - in our case it is controller.py . # controller.py import TextProcessor import ShortenerClient processor = TextProcessor( text=' 1: https://ya.ru 2: https://google.com', shortener_client=ShortenerClient(api_key='123') ) print(processor.process()) TextProcessor , making it inheritable: # text_processor.py class TextProcessor: def __init__(self, text: str) -> None: self.text = text self.shortener_client: AbstractClient = self.get_shortener_client() def get_shortener_client(self) -> AbstractClient: """ """ raise NotImplementedError # controller.py import TextProcessor import ShortenerClient class ProcessorWithClient(TextProcessor): """ , """ def get_shortener_client(self) -> ShortenerClient: return ShortenerClient(api_key='abc') processor = ProcessorWithClient( text=' 1: https://ya.ru 2: https://google.com' ) print(processor.process()) TextProcessor is not an independent class, but only one of the TextPipeline elements that processes the text and sends it to the mail: class TextPipeline: def __init__(self, text, email): self.text_processor = TextProcessor(text) self.mailer = Mailer(email) def process_and_mail(self) -> None: processed_text = self.text_processor.process() self.mailer.send_text(text=processed_text) TextPipeline from the classes used, we must follow the same procedure as before:TextPipeline class will declare interfaces for used components;
import TextProcessor import ShortenerClient import Mailer import TextPipeline class ProcessorWithClient(TextProcessor): def get_shortener_client(self) -> ShortenerClient: return ShortenerClient(api_key='123') class PipelineWithDependencies(TextPipeline): def get_text_processor(self, text: str) -> ProcessorWithClient: return ProcessorWithClient(text) def get_mailer(self, email: str) -> Mailer: return Mailer(email) pipeline = PipelineWithDependencies( email='abc@def.com', text=' 1: https://ya.ru 2: https://google.com' ) pipeline.process_and_mail() TextProcessor class to insert the ShortenerClient into it, and then inherit the TextPipeline to insert our overridden TextProcessor (as well as Mailer ) into it. We have several levels of sequential redefinition. Already complicated.FormField to insert it into an override of a Form , to insert a form into an override of View . Everything. Three levels. import TextProcessor import ShortenerClient import Mailer import TextPipeline pipeline = TextPipeline( text_processor=TextProcessor( text=' 1: https://ya.ru 2: https://google.com', shortener_client=ShortenerClient(api_key='abc') ), mailer=Mailer('abc@def.com') ) pipeline.process_and_mail() INSTANCE_DICT : # text_processor.py import INSTANCE_DICT class TextProcessor(AbstractTextProcessor): def __init__(self, text) -> None: self.text = text def process(self) -> str: shortener_client: AbstractClient = INSTANCE_DICT['Shortener'] # ... # text_pipeline.py import INSTANCE_DICT class TextPipeline: def __init__(self) -> None: self.text_processor: AbstractTextProcessor = INSTANCE_DICT[ 'TextProcessor'] self.mailer: AbstractMailer = INSTANCE_DICT['Mailer'] def process_and_mail(self) -> None: processed_text = self.text_processor.process() self.mailer.send_text(text=processed_text) controller.py : # controller.py import INSTANCE_DICT import TextProcessor import ShortenerClient import Mailer import TextPipeline INSTANCE_DICT['Shortener'] = ShortenerClient('123') INSTANCE_DICT['Mailer'] = Mailer('abc@def.com') INSTANCE_DICT['TextProcessor'] = TextProcessor(text=' : https://ya.ru') pipeline = TextPipeline() pipeline.process_and_mail() INSTANCE_DICT , you can use some kind of DI framework; but the essence of this will not change. The framework will provide more flexible management of instances; he will allow you to create them in the form of singleton or packs, like a factory; but the idea will remain the same.Source: https://habr.com/ru/post/461511/
All Articles