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